Skip to content

Commit

Permalink
[FEATURE] ✨ Ajoute une stratégie de contrôle plus strict pour l'accès…
Browse files Browse the repository at this point in the history
… au endpoint Parcoursup (PIX-15801)

 #10881
  • Loading branch information
pix-service-auto-merge authored Dec 24, 2024
2 parents 54c7425 + 49abdd8 commit 17ce069
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 15 deletions.
7 changes: 7 additions & 0 deletions api/lib/infrastructure/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ const authentication = {
validate: validateClientApplication,
},
},
{
name: 'jwt-parcoursup',
configuration: {
key: config.jwtConfig.parcoursup.secret,
validate: validateClientApplication,
},
},
],

defaultStrategy: 'jwt-user',
Expand Down
8 changes: 1 addition & 7 deletions api/src/parcoursup/application/certification-route.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Joi from 'joi';

import { securityPreHandlers } from '../../shared/application/security-pre-handlers.js';
import { studentIdentifierType } from '../../shared/domain/types/identifiers-type.js';
import { certificationController } from './certification-controller.js';

Expand All @@ -9,18 +8,13 @@ const register = async function (server) {
method: 'GET',
path: '/api/parcoursup/students/{ine}/certification',
config: {
auth: 'jwt-parcoursup',
validate: {
params: Joi.object({
ine: studentIdentifierType,
}),
},
handler: certificationController.getCertificationResult,
pre: [
{
method: securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
assign: 'hasAuthorizationToAccessAdminScope',
},
],
tags: ['api', 'parcoursup'],
notes: [
'- **Cette route est accessible uniquement à Parcours Sup**\n' +
Expand Down
17 changes: 17 additions & 0 deletions api/src/shared/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ const configuration = (function () {
scope: 'statistics',
source: 'pixData',
},
{
clientId: process.env.APIM_PIX_PARCOURSUP_CLIENT_ID,
clientSecret: process.env.APIM_PIX_PARCOURSUP_CLIENT_SECRET,
scope: 'parcoursup',
source: 'parcoursup',
},
],
auditLogger: {
isEnabled: toBoolean(process.env.PIX_AUDIT_LOGGER_ENABLED),
Expand Down Expand Up @@ -291,6 +297,10 @@ const configuration = (function () {
secret: process.env.PIX_DATA_AUTH_SECRET,
tokenLifespan: process.env.TOKEN_LIFE_SPAN || '1h',
},
parcoursup: {
secret: process.env.PIX_PARCOURSUP_AUTH_SECRET,
tokenLifespan: process.env.TOKEN_LIFE_SPAN || '1h',
},
certificationResults: {
scope: process.env.CERTIFICATION_RESULTS_JWT_SCOPE || 'certificationResultsLink',
tokenLifespan: process.env.CERTIFICATION_RESULTS_JWT_TOKEN_LIFE_SPAN || '30d',
Expand Down Expand Up @@ -539,6 +549,12 @@ const configuration = (function () {
scope: 'statistics',
source: 'pixData',
},
{
clientId: 'parcoursupClientId',
clientSecret: 'parcoursupClientSecret',
scope: 'parcoursup',
source: 'parcoursup',
},
];

config.cpf.storage = {
Expand Down Expand Up @@ -573,6 +589,7 @@ const configuration = (function () {
config.jwtConfig.livretScolaire = { secret: 'secretosmose', tokenLifespan: '1h' };
config.jwtConfig.poleEmploi = { secret: 'secretPoleEmploi', tokenLifespan: '1h' };
config.jwtConfig.pixData = { secret: 'secretPixData', tokenLifespan: '1h' };
config.jwtConfig.parcoursup = { secret: 'secretPixParcoursup', tokenLifespan: '1h' };

config.logging.enabled = toBoolean(process.env.TEST_LOG_ENABLED);
config.logging.enableLogKnexQueries = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import {
createServer,
databaseBuilder,
expect,
generateValidRequestAuthorizationHeader,
insertUserWithRoleSuperAdmin,
generateValidRequestAuthorizationHeaderForApplication,
} from '../../../test-helper.js';

describe('Parcoursup | Acceptance | Application | certification-route', function () {
let server;

const PARCOURSUP_CLIENT_ID = 'parcoursupClientId';
const PARCOURSUP_SCOPE = 'parcoursup';
const PARCOURSUP_SOURCE = 'parcoursup';

beforeEach(async function () {
server = await createServer();
});
Expand All @@ -17,12 +20,15 @@ describe('Parcoursup | Acceptance | Application | certification-route', function
it('should return 200 HTTP status code and a certification for a given INE', async function () {
// given
const ine = '123456789OK';
const superAdmin = await insertUserWithRoleSuperAdmin();
const options = {
method: 'GET',
url: `/api/parcoursup/students/${ine}/certification`,
headers: {
authorization: generateValidRequestAuthorizationHeader(superAdmin.id),
authorization: generateValidRequestAuthorizationHeaderForApplication(
PARCOURSUP_CLIENT_ID,
PARCOURSUP_SOURCE,
PARCOURSUP_SCOPE,
),
},
};

Expand Down
84 changes: 80 additions & 4 deletions api/tests/parcoursup/unit/application/certification-route_test.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,99 @@
import { certificationController } from '../../../../src/parcoursup/application/certification-controller.js';
import * as moduleUnderTest from '../../../../src/parcoursup/application/certification-route.js';
import { securityPreHandlers } from '../../../../src/shared/application/security-pre-handlers.js';
import { expect, HttpTestServer, sinon } from '../../../test-helper.js';
import {
expect,
generateValidRequestAuthorizationHeaderForApplication,
HttpTestServer,
sinon,
} from '../../../test-helper.js';

describe('Parcoursup | Unit | Application | Routes | Certification', function () {
describe('GET /parcoursup/students/{ine}/certification', function () {
it('should return 200', async function () {
//given
sinon.stub(securityPreHandlers, 'checkAdminMemberHasRoleSuperAdmin').returns(true);
sinon.stub(certificationController, 'getCertificationResult').callsFake((request, h) => h.response().code(200));

const httpTestServer = new HttpTestServer();
httpTestServer.setupAuthentication();
await httpTestServer.register(moduleUnderTest);

const PARCOURSUP_CLIENT_ID = 'parcoursupClientId';
const PARCOURSUP_SCOPE = 'parcoursup';
const PARCOURSUP_SOURCE = 'parcoursup';

const method = 'GET';
const url = '/api/parcoursup/students/123456789OK/certification';
const headers = {
authorization: generateValidRequestAuthorizationHeaderForApplication(
PARCOURSUP_CLIENT_ID,
PARCOURSUP_SOURCE,
PARCOURSUP_SCOPE,
),
};

// when
const response = await httpTestServer.request('GET', '/api/parcoursup/students/123456789OK/certification');
const response = await httpTestServer.request(method, url, null, null, headers);

// then
expect(response.statusCode).to.equal(200);
});

context('with the wrong scope', function () {
it('should return 403', async function () {
//given
const httpTestServer = new HttpTestServer();
httpTestServer.setupAuthentication();
await httpTestServer.register(moduleUnderTest);

const PARCOURSUP_CLIENT_ID = 'parcoursupClientId';
const PARCOURSUP_SCOPE = 'a-wrong-scope';
const PARCOURSUP_SOURCE = 'parcoursup';

const method = 'GET';
const url = '/api/parcoursup/students/123456789OK/certification';
const headers = {
authorization: generateValidRequestAuthorizationHeaderForApplication(
PARCOURSUP_CLIENT_ID,
PARCOURSUP_SOURCE,
PARCOURSUP_SCOPE,
),
};

// when
const response = await httpTestServer.request(method, url, null, null, headers);

// then
expect(response.statusCode).to.equal(403);
});
});

context('with the wrong clientId', function () {
it('should return 401', async function () {
//given
const httpTestServer = new HttpTestServer();
httpTestServer.setupAuthentication();
await httpTestServer.register(moduleUnderTest);

const PARCOURSUP_CLIENT_ID = 'wrongClientId';
const PARCOURSUP_SCOPE = 'parcoursup';
const PARCOURSUP_SOURCE = 'parcoursup';

const method = 'GET';
const url = '/api/parcoursup/students/123456789OK/certification';
const headers = {
authorization: generateValidRequestAuthorizationHeaderForApplication(
PARCOURSUP_CLIENT_ID,
PARCOURSUP_SOURCE,
PARCOURSUP_SCOPE,
),
};

// when
const response = await httpTestServer.request(method, url, null, null, headers);

// then
expect(response.statusCode).to.equal(401);
});
});
});
});

0 comments on commit 17ce069

Please sign in to comment.