From 49abdd882d07e1076fb0159257a13001d81a1ff1 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Fri, 20 Dec 2024 11:27:55 +0100 Subject: [PATCH] :sparkles: api: create jwt-parcoursup authentication strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexandre COIN Co-authored-by: Andreia Pena <58915422+AndreiaPena@users.noreply.github.com> Co-authored-by: Geoffroy Begouaussel Co-authored-by: Jérémy PLUQUET <37305474+D0C702WH0@users.noreply.github.com> Co-authored-by: Yannick François --- api/lib/infrastructure/authentication.js | 7 ++ .../application/certification-route.js | 8 +- api/src/shared/config.js | 17 ++++ ...es_test.js => certification-route_test.js} | 14 +++- .../application/certification-route_test.js | 84 ++++++++++++++++++- 5 files changed, 115 insertions(+), 15 deletions(-) rename api/tests/parcoursup/acceptance/application/{certification-routes_test.js => certification-route_test.js} (75%) diff --git a/api/lib/infrastructure/authentication.js b/api/lib/infrastructure/authentication.js index c36f6f690b8..cf3781b3bbd 100644 --- a/api/lib/infrastructure/authentication.js +++ b/api/lib/infrastructure/authentication.js @@ -88,6 +88,13 @@ const authentication = { validate: validateClientApplication, }, }, + { + name: 'jwt-parcoursup', + configuration: { + key: config.jwtConfig.parcoursup.secret, + validate: validateClientApplication, + }, + }, ], defaultStrategy: 'jwt-user', diff --git a/api/src/parcoursup/application/certification-route.js b/api/src/parcoursup/application/certification-route.js index c2b4e31b6b2..5d99e0fd048 100644 --- a/api/src/parcoursup/application/certification-route.js +++ b/api/src/parcoursup/application/certification-route.js @@ -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'; @@ -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' + diff --git a/api/src/shared/config.js b/api/src/shared/config.js index 81773974779..65878aae4ed 100644 --- a/api/src/shared/config.js +++ b/api/src/shared/config.js @@ -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), @@ -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', @@ -539,6 +549,12 @@ const configuration = (function () { scope: 'statistics', source: 'pixData', }, + { + clientId: 'parcoursupClientId', + clientSecret: 'parcoursupClientSecret', + scope: 'parcoursup', + source: 'parcoursup', + }, ]; config.cpf.storage = { @@ -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; diff --git a/api/tests/parcoursup/acceptance/application/certification-routes_test.js b/api/tests/parcoursup/acceptance/application/certification-route_test.js similarity index 75% rename from api/tests/parcoursup/acceptance/application/certification-routes_test.js rename to api/tests/parcoursup/acceptance/application/certification-route_test.js index fc3675f32fd..875811eec12 100644 --- a/api/tests/parcoursup/acceptance/application/certification-routes_test.js +++ b/api/tests/parcoursup/acceptance/application/certification-route_test.js @@ -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(); }); @@ -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, + ), }, }; diff --git a/api/tests/parcoursup/unit/application/certification-route_test.js b/api/tests/parcoursup/unit/application/certification-route_test.js index 9f2ffe4e3ff..a9d3f9e2d40 100644 --- a/api/tests/parcoursup/unit/application/certification-route_test.js +++ b/api/tests/parcoursup/unit/application/certification-route_test.js @@ -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); + }); + }); }); });