Skip to content

Commit

Permalink
feat(api): Block the access to the school if the session is not started
Browse files Browse the repository at this point in the history
  • Loading branch information
yaelle6 authored and theotime2005 committed Jul 5, 2024
1 parent bf60481 commit 4c6b77c
Show file tree
Hide file tree
Showing 19 changed files with 213 additions and 22 deletions.
1 change: 0 additions & 1 deletion .idea/runConfigurations/debug_api_tests_watch.run.xml

This file was deleted.

4 changes: 2 additions & 2 deletions .run/Acceptance _ Pix Api.run.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Acceptance | Pix Api" type="mocha-javascript-test-runner">
<node-interpreter>project</node-interpreter>
<node-interpreter>$USER_HOME$/.nvm/versions/node/v20.15.0/bin/node</node-interpreter>
<node-options />
<mocha-package>$PROJECT_DIR$/api/node_modules/mocha</mocha-package>
<working-directory>$PROJECT_DIR$/api</working-directory>
Expand Down Expand Up @@ -28,4 +28,4 @@
</option>
</method>
</configuration>
</component>
</component>
4 changes: 2 additions & 2 deletions .run/Integration _ Pix Api.run.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Integration | Pix Api" type="mocha-javascript-test-runner">
<node-interpreter>project</node-interpreter>
<node-interpreter>$USER_HOME$/.nvm/versions/node/v20.15.0/bin/node</node-interpreter>
<node-options />
<mocha-package>$PROJECT_DIR$/api/node_modules/mocha</mocha-package>
<working-directory>$PROJECT_DIR$/api</working-directory>
Expand Down Expand Up @@ -28,4 +28,4 @@
</option>
</method>
</configuration>
</component>
</component>
2 changes: 1 addition & 1 deletion .run/Unit _ Pix Api.run.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Unit | Pix Api" type="mocha-javascript-test-runner">
<node-interpreter>project</node-interpreter>
<node-interpreter>$USER_HOME$/.nvm/versions/node/v20.15.0/bin/node</node-interpreter>
<node-options />
<mocha-package>$PROJECT_DIR$/api/node_modules/mocha</mocha-package>
<working-directory>$PROJECT_DIR$/api</working-directory>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="api tests with watch" type="mocha-javascript-test-runner">
<node-interpreter>project</node-interpreter>
<node-interpreter>$USER_HOME$/.nvm/versions/node/v20.15.0/bin/node</node-interpreter>
<node-options />
<mocha-package>$PROJECT_DIR$/api/node_modules/mocha</mocha-package>
<working-directory>$PROJECT_DIR$/api</working-directory>
Expand Down
2 changes: 1 addition & 1 deletion api/eslint.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ module.exports = [
from: ['api/lib/infrastructure/repositories', 'lib/infrastructure/repositories'],
except: [],
message:
"Repositories are automatically injected in use-case, you don't need to import them. Check for further details: https://github.com/1024pix/pix/blob/dev/docs/adr/0046-injecter-les-dependances-api.md",
"Repositories are automatically injected in usecases, you don't need to import them. Check for further details: https://github.com/1024pix/pix/blob/dev/docs/adr/0046-injecter-les-dependances-api.md",
},
{ target: 'tests/unit', from: 'db' },
],
Expand Down
5 changes: 4 additions & 1 deletion api/src/school/application/school-route.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ const register = async function (server) {
method: 'GET',
path: '/api/pix1d/schools',
config: {
pre: [{ method: securityPreHandlers.checkPix1dActivated }],
pre: [
{ method: securityPreHandlers.checkPix1dActivated },
{ method: securityPreHandlers.checkSchoolSessionIsActive },
],
auth: false,
validate: {
query: Joi.object({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as schoolRepository from '../../infrastructure/repositories/school-repository.js';

const execute = async function ({ schoolCode, dependencies = { schoolRepository } }) {
const result = await dependencies.schoolRepository.getSessionExpirationDate({ code: schoolCode });

return result && new Date() < new Date(result) ? true : false;
};

export { execute };
18 changes: 17 additions & 1 deletion api/src/school/infrastructure/repositories/school-repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,20 @@ const getDivisions = async function ({ organizationId, organizationLearnerApi })
return [...new Set(divisionLearners)].sort().map((divisionName) => new Division({ name: divisionName }));
};

export { getByCode, getById, getDivisions, isCodeAvailable, save, updateSessionExpirationDate };
const getSessionExpirationDate = async function ({ code }) {
const [sessionExpirationDate] = await knex('schools')
.select('sessionExpirationDate')
.where({ code })
.pluck('sessionExpirationDate');
return sessionExpirationDate;
};

export {
getByCode,
getById,
getDivisions,
getSessionExpirationDate,
isCodeAvailable,
save,
updateSessionExpirationDate,
};
20 changes: 20 additions & 0 deletions api/src/shared/application/security-pre-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Organization } from '../../../lib/domain/models/index.js';
import { PIX_ADMIN } from '../../authorization/domain/constants.js';
import * as checkUserIsCandidateUseCase from '../../certification/enrolment/application/usecases/checkUserIsCandidate.js';
import * as certificationIssueReportRepository from '../../certification/shared/infrastructure/repositories/certification-issue-report-repository.js';
import * as isSchoolSessionActive from '../../school/application/usecases/is-school-session-active.js';
import { ForbiddenAccess, NotFoundError } from '../domain/errors.js';
import * as organizationRepository from '../infrastructure/repositories/organization-repository.js';
import * as checkOrganizationHasFeatureUseCase from './usecases/checkOrganizationHasFeature.js';
Expand All @@ -49,6 +50,17 @@ function _replyForbiddenError(h) {
return h.response(jsonApiError).code(errorHttpStatusCode).takeover();
}

function _replyNotFoundError(h) {
const errorHttpStatusCode = 404;

const jsonApiError = new JSONAPIError({
code: errorHttpStatusCode,
title: 'Not found',
});

return h.response(jsonApiError).code(errorHttpStatusCode).takeover();
}

async function checkIfUserIsBlocked(
request,
h,
Expand Down Expand Up @@ -176,6 +188,13 @@ function checkRequestedUserIsAuthenticatedUser(request, h) {
return authenticatedUserId === requestedUserId ? h.response(true) : _replyForbiddenError(h);
}

async function checkSchoolSessionIsActive(request, h, dependencies = { isSchoolSessionActive }) {
if (await dependencies.isSchoolSessionActive.execute({ schoolCode: request.query.code })) {
return h.response(true);
}
return _replyNotFoundError(h);
}

function checkUserIsAdminInOrganization(request, h, dependencies = { checkUserIsAdminInOrganizationUseCase }) {
if (!request.auth.credentials || !request.auth.credentials.userId) {
return _replyForbiddenError(h);
Expand Down Expand Up @@ -742,6 +761,7 @@ const securityPreHandlers = {
checkIfUserIsBlocked,
checkPix1dActivated,
checkRequestedUserIsAuthenticatedUser,
checkSchoolSessionIsActive,
checkUserBelongsToLearnersOrganization,
checkUserBelongsToOrganization,
checkUserBelongsToOrganizationManagingStudents,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { expect, HttpTestServer, sinon } from '../../../../test-helper.js';

describe('Unit | Devcomp | Application | Modules | Module Controller', function () {
describe('#getBySlug', function () {
it('should call getModule use-case and return serialized modules', async function () {
it('should call getModule usecases and return serialized modules', async function () {
const slug = 'slug';
const serializedModule = Symbol('serialized modules');
const module = Symbol('modules');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expect, sinon } from '../../../../test-helper.js';

describe('Unit | Devcomp | Application | Passages | Controller', function () {
describe('#create', function () {
it('should call createPassage use-case and return serialized passage', async function () {
it('should call createPassage usecases and return serialized passage', async function () {
// given
const serializedPassage = Symbol('serialized modules');
const moduleId = Symbol('module-id');
Expand Down Expand Up @@ -41,7 +41,7 @@ describe('Unit | Devcomp | Application | Passages | Controller', function () {
});

describe('#verifyAndSaveAnswer', function () {
it('should call verifyAndSave use-case and return serialized element-answer', async function () {
it('should call verifyAndSave usecases and return serialized element-answer', async function () {
// given
const passageId = Symbol('passage-id');
const elementId = Symbol('element-id');
Expand Down Expand Up @@ -88,7 +88,7 @@ describe('Unit | Devcomp | Application | Passages | Controller', function () {
});

describe('#terminate', function () {
it('should call terminate use-case and return serialized passage', async function () {
it('should call terminate usecases and return serialized passage', async function () {
// given
const serializedPassage = Symbol('serialized modules');
const passageId = Symbol('passage-id');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { expect, hFake, sinon } from '../../../../test-helper.js';

describe('Unit | Devcomp | Application | Trainings | Controller | training-controller', function () {
describe('#findPaginatedTrainingSummaries', function () {
it('should call the training findPaginatedTrainingSummaries use-case', async function () {
it('should call the training findPaginatedTrainingSummaries usecases', async function () {
// given
const expectedResult = Symbol('serialized-training-summaries');
const trainingSummaries = Symbol('trainingSummary');
Expand Down Expand Up @@ -97,7 +97,7 @@ describe('Unit | Devcomp | Application | Trainings | Controller | training-contr
sinon.stub(usecases, 'createTraining').resolves(createdTraining);
});

it('should call the training create use-case', async function () {
it('should call the training create usecases', async function () {
// given
const payload = {
data: {
Expand Down Expand Up @@ -170,7 +170,7 @@ describe('Unit | Devcomp | Application | Trainings | Controller | training-contr
});

describe('when request is valid', function () {
it('should call the training update use-case', async function () {
it('should call the training update usecases', async function () {
// given
const useCaseParameters = {
training: { ...deserializedTraining, id: 134 },
Expand Down Expand Up @@ -234,7 +234,7 @@ describe('Unit | Devcomp | Application | Trainings | Controller | training-contr
});

describe('#createOrUpdateTrigger', function () {
it('should call the createOrUpdateTrigger use-case', async function () {
it('should call the createOrUpdateTrigger usecases', async function () {
// given
const payload = {
data: {
Expand Down Expand Up @@ -310,7 +310,7 @@ describe('Unit | Devcomp | Application | Trainings | Controller | training-contr
});

describe('#findTargetProfileSummaries', function () {
it('should call the findTargetProfileSummaries use-case', async function () {
it('should call the findTargetProfileSummaries usecases', async function () {
// given
const trainingId = 145;
const targetProfileSummaries = Symbol('targetProfileSummaries');
Expand All @@ -336,7 +336,7 @@ describe('Unit | Devcomp | Application | Trainings | Controller | training-contr
});

describe('attachTargetProfiles', function () {
it('should call the attachTargetProfilesTraining use-case', async function () {
it('should call the attachTargetProfilesTraining usecases', async function () {
// given
const trainingId = 145;
const targetProfileIds = [1, 2, 3];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ describe('Unit | Controller | admin-target-profile-controller', function () {
});

describe('#detachOrganizations', function () {
it('should call the detachOrganizationsFromTargetProfile use-case', async function () {
it('should call the detachOrganizationsFromTargetProfile usecases', async function () {
// given
const expectedResult = Symbol('result');
const organizationIds = Symbol('organizationIds');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,24 @@ describe('Integration | Repository | School', function () {
expect(divisions).to.be.empty;
});
});

describe('#getSessionExpirationDate', function () {
it('should return the session expiration date', async function () {
databaseBuilder.factory.buildSchool({ code: 'FRANCE998', sessionExpirationDate: '2022-07-04' });
await databaseBuilder.commit();
// when
const sessionExpirationDate = await repositories.schoolRepository.getSessionExpirationDate({ code: 'FRANCE998' });

// then
expect(sessionExpirationDate).to.deep.equal(new Date('2022-07-04'));
});

it('should return undefined when there is no school corresponding to the code', async function () {
// when
const sessionExpirationDate = await repositories.schoolRepository.getSessionExpirationDate({ code: 'FRANCE998' });

// then
expect(sessionExpirationDate).to.equal(undefined);
});
});
});
3 changes: 3 additions & 0 deletions api/tests/school/unit/application/school-route_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('Unit | Router | school-router', function () {
sinon.stub(schoolController, 'getSchool').callsFake((request, h) => h.response('ok'));

sinon.stub(securityPreHandlers, 'checkPix1dActivated').callsFake((request, h) => h.response());
sinon.stub(securityPreHandlers, 'checkSchoolSessionIsActive').callsFake((request, h) => h.response());
const httpTestServer = new HttpTestServer();
await httpTestServer.register(moduleUnderTest);

Expand All @@ -20,6 +21,8 @@ describe('Unit | Router | school-router', function () {

// then
expect(response.statusCode).to.equal(200);
expect(securityPreHandlers.checkPix1dActivated).to.have.been.called;
expect(securityPreHandlers.checkSchoolSessionIsActive).to.have.been.called;
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { execute as isSchoolSessionActive } from '../../../../../src/school/application/usecases/is-school-session-active.js';
import { expect, sinon } from '../../../../test-helper.js';

describe('Unit | Domain | Use Cases | is-session-active', function () {
let clock;

beforeEach(function () {
const now = new Date('2022-08-04');
clock = sinon.useFakeTimers({ now, toFake: ['Date'] });
});

afterEach(function () {
clock.restore();
});

it('should return false if no sessionExpirationDate found for school', async function () {
const dependencies = { schoolRepository: { getSessionExpirationDate: sinon.stub() } };
dependencies.schoolRepository.getSessionExpirationDate.resolves(undefined);

const result = await isSchoolSessionActive({ schoolCode: 'CODE1', dependencies });

expect(dependencies.schoolRepository.getSessionExpirationDate).to.have.been.calledWithExactly({ code: 'CODE1' });
expect(result).to.be.false;
});

it('should return false if date is passed', async function () {
const dependencies = { schoolRepository: { getSessionExpirationDate: sinon.stub() } };
dependencies.schoolRepository.getSessionExpirationDate.resolves('2022-07-04T00:00:00.000Z');

const result = await isSchoolSessionActive({ schoolCode: 'CODE1', dependencies });

expect(result).to.be.false;
});

it('should return true if session is still active', async function () {
const dependencies = { schoolRepository: { getSessionExpirationDate: sinon.stub() } };
dependencies.schoolRepository.getSessionExpirationDate.resolves('2022-08-04T01:00:00.000Z');

const result = await isSchoolSessionActive({ schoolCode: 'CODE1', dependencies });

expect(result).to.be.true;
});
});
Loading

0 comments on commit 4c6b77c

Please sign in to comment.