diff --git a/api/src/authorization/domain/constants.js b/api/src/authorization/domain/constants.js index 8a590a942c8..3e6d5124409 100644 --- a/api/src/authorization/domain/constants.js +++ b/api/src/authorization/domain/constants.js @@ -6,18 +6,11 @@ const PIX_ADMIN = { SUPER_ADMIN: 'SUPER_ADMIN', SUPPORT: 'SUPPORT', }, - SCOPE: 'pix-admin', - TARGET: 'admin', }; const PIX_ORGA = { NOT_LINKED_ORGANIZATION_MSG: "L'accès à Pix Orga est limité aux membres invités. Chaque espace est géré par un administrateur Pix Orga propre à l'organisation qui l'utilise. Contactez-le pour qu'il vous y invite.", - SCOPE: 'pix-orga', }; -const PIX_CERTIF = { - SCOPE: 'pix-certif', -}; - -export { PIX_ADMIN, PIX_CERTIF, PIX_ORGA }; +export { PIX_ADMIN, PIX_ORGA }; diff --git a/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js b/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js index 236a5f7f68c..42336774fdc 100644 --- a/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js +++ b/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js @@ -1,5 +1,4 @@ import { oidcAuthenticationServiceRegistry } from '../../../../lib/domain/usecases/index.js'; -import { PIX_ADMIN } from '../../../authorization/domain/constants.js'; import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js'; import { usecases } from '../../domain/usecases/index.js'; import * as oidcProviderSerializer from '../../infrastructure/serializers/jsonapi/oidc-identity-providers.serializer.js'; @@ -52,7 +51,7 @@ async function reconcileUserForAdmin( const oidcAuthenticationService = dependencies.oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode: identityProvider, - target: PIX_ADMIN.TARGET, + requestedApplication, }); const accessToken = await usecases.reconcileOidcUserForAdmin({ diff --git a/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js b/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js index 1fbbce464b0..10f56be75b5 100644 --- a/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js +++ b/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js @@ -12,7 +12,7 @@ import { getForwardedOrigin, RequestedApplication } from '../../infrastructure/u * @return {Promise<*>} */ async function authenticateOidcUser(request, h) { - const { code, state, iss, identityProvider: identityProviderCode, target } = request.deserializedPayload; + const { code, state, iss, identityProvider: identityProviderCode } = request.deserializedPayload; const origin = getForwardedOrigin(request.headers); const requestedApplication = RequestedApplication.fromOrigin(origin); @@ -25,7 +25,6 @@ async function authenticateOidcUser(request, h) { } const result = await usecases.authenticateOidcUser({ - target, code, state, iss, @@ -104,9 +103,11 @@ async function findUserForReconciliation(request, h, dependencies = { oidcSerial * @return {Promise} */ async function getAuthorizationUrl(request, h) { - const { identity_provider: identityProvider, target } = request.query; + const { identity_provider: identityProvider } = request.query; + const origin = getForwardedOrigin(request.headers); + const requestedApplication = RequestedApplication.fromOrigin(origin); - const { nonce, state, ...payload } = await usecases.getAuthorizationUrl({ target, identityProvider }); + const { nonce, state, ...payload } = await usecases.getAuthorizationUrl({ identityProvider, requestedApplication }); request.yar.set('state', state); request.yar.set('nonce', nonce); @@ -122,8 +123,11 @@ async function getAuthorizationUrl(request, h) { * @return {Promise<*>} */ async function getIdentityProviders(request, h) { - const target = request.query.target; - const identityProviders = await usecases.getReadyIdentityProviders({ target }); + const origin = getForwardedOrigin(request.headers); + const requestedApplication = RequestedApplication.fromOrigin(origin); + + const identityProviders = await usecases.getReadyIdentityProviders({ requestedApplication }); + return h.response(oidcProviderSerializer.serialize(identityProviders)).code(200); } diff --git a/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js b/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js index 5c07b150c87..b9fe2ed7fef 100644 --- a/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js +++ b/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js @@ -10,7 +10,7 @@ export const oidcProviderRoutes = [ options: { validate: { query: Joi.object({ - target: Joi.string().optional().default('app'), + target: Joi.string().optional().default('app'), // Now useless, will soon be removed }), }, auth: false, @@ -49,7 +49,7 @@ export const oidcProviderRoutes = [ validate: { query: Joi.object({ identity_provider: Joi.string().required(), - target: Joi.string().valid('app', 'admin').optional(), + target: Joi.string().valid('app', 'admin').optional(), // Now useless, will soon be removed }), }, handler: (request, h) => oidcProviderController.getAuthorizationUrl(request, h), @@ -73,7 +73,7 @@ export const oidcProviderRoutes = [ code: Joi.string().required(), state: Joi.string().required(), iss: Joi.string().optional(), - target: Joi.string().valid('app', 'admin').optional(), + target: Joi.string().valid('app', 'admin').optional(), // Now useless, will soon be removed }, }, }), diff --git a/api/src/identity-access-management/application/token/token.controller.js b/api/src/identity-access-management/application/token/token.controller.js index e0411c7369a..d41bc676198 100644 --- a/api/src/identity-access-management/application/token/token.controller.js +++ b/api/src/identity-access-management/application/token/token.controller.js @@ -1,6 +1,6 @@ import { tokenService } from '../../../shared/domain/services/token-service.js'; import { usecases } from '../../domain/usecases/index.js'; -import { getForwardedOrigin } from '../../infrastructure/utils/network.js'; +import { getForwardedOrigin, RequestedApplication } from '../../infrastructure/utils/network.js'; const authenticateAnonymousUser = async function (request, h) { const { campaign_code: campaignCode, lang } = request.payload; @@ -28,21 +28,22 @@ const createToken = async function (request, h, dependencies = { tokenService }) let expirationDelaySeconds; const origin = getForwardedOrigin(request.headers); + const requestedApplication = RequestedApplication.fromOrigin(origin); const grantType = request.payload.grant_type; if (grantType === 'password') { - const { username, password, scope } = request.payload; + const { username, password } = request.payload; const localeFromCookie = request.state?.locale; const source = 'pix'; const tokensInfo = await usecases.authenticateUser({ username, password, - scope, source, localeFromCookie, audience: origin, + requestedApplication, }); accessToken = tokensInfo.accessToken; diff --git a/api/src/identity-access-management/application/token/token.route.js b/api/src/identity-access-management/application/token/token.route.js index f568d90eb91..39dd6f69629 100644 --- a/api/src/identity-access-management/application/token/token.route.js +++ b/api/src/identity-access-management/application/token/token.route.js @@ -21,7 +21,7 @@ export const tokenRoutes = [ grant_type: Joi.string().valid('password').required(), username: Joi.string().required(), password: Joi.string().required(), - scope: Joi.string(), + scope: Joi.string().optional(), // Now useless, will soon be removed }), Joi.object() .required() diff --git a/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js b/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js index 2987995e2ba..7dd046d3c98 100644 --- a/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js +++ b/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js @@ -44,8 +44,10 @@ export class OidcAuthenticationServiceRegistry { return this.#readyOidcProviderServicesForPixAdmin; } - getOidcProviderServiceByCode({ identityProviderCode, target = 'app' }) { - const services = target === 'admin' ? this.#readyOidcProviderServicesForPixAdmin : this.#readyOidcProviderServices; + getOidcProviderServiceByCode({ identityProviderCode, requestedApplication }) { + const services = requestedApplication?.isPixAdmin + ? this.#readyOidcProviderServicesForPixAdmin + : this.#readyOidcProviderServices; const oidcProviderService = services.find((service) => identityProviderCode === service.code); if (!oidcProviderService) { diff --git a/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js b/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js index f4fc4ec413c..a678c689f2e 100644 --- a/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js +++ b/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js @@ -1,4 +1,3 @@ -import { PIX_ADMIN } from '../../../authorization/domain/constants.js'; import { ForbiddenAccess } from '../../../shared/domain/errors.js'; /** @@ -11,6 +10,7 @@ import { ForbiddenAccess } from '../../../shared/domain/errors.js'; * @param {string} params.sessionState * @param {string} params.state * @param {string} params.audience + * @param {RequestedApplication} params.requestedApplication * @param {AuthenticationSessionService} params.authenticationSessionService * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry * @param {AdminMemberRepository} params.adminMemberRepository @@ -18,11 +18,9 @@ import { ForbiddenAccess } from '../../../shared/domain/errors.js'; * @param {UserLoginRepository} params.userLoginRepository * @param {UserRepository} params.userRepository * @param {LastUserApplicationConnectionsRepository} params.LastUserApplicationConnectionsRepository, - * @param {RequestedApplication} params.RequestedApplication, * @return {Promise<{isAuthenticationComplete: boolean, givenName: string, familyName: string, authenticationKey: string, email: string}|{isAuthenticationComplete: boolean, pixAccessToken: string, logoutUrlUUID: string}>} */ async function authenticateOidcUser({ - target, code, state, iss, @@ -30,6 +28,7 @@ async function authenticateOidcUser({ nonce, sessionState, audience, + requestedApplication, authenticationSessionService, oidcAuthenticationServiceRegistry, adminMemberRepository, @@ -37,14 +36,13 @@ async function authenticateOidcUser({ userLoginRepository, userRepository, lastUserApplicationConnectionsRepository, - requestedApplication, }) { await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); await oidcAuthenticationServiceRegistry.configureReadyOidcProviderServiceByCode(identityProviderCode); const oidcAuthenticationService = oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode, - target, + requestedApplication, }); const sessionContent = await oidcAuthenticationService.exchangeCodeForTokens({ @@ -69,7 +67,7 @@ async function authenticateOidcUser({ return { authenticationKey, givenName, familyName, email, isAuthenticationComplete: false }; } - await _assertUserWithPixAdminAccess({ target, userId: user.id, adminMemberRepository }); + await _assertUserHasAccessToApplication({ requestedApplication, user, adminMemberRepository }); await _updateAuthenticationMethodWithComplement({ userInfo, @@ -120,9 +118,9 @@ async function _updateAuthenticationMethodWithComplement({ }); } -async function _assertUserWithPixAdminAccess({ target, userId, adminMemberRepository }) { - if (target === PIX_ADMIN.TARGET) { - const adminMember = await adminMemberRepository.get({ userId }); +async function _assertUserHasAccessToApplication({ requestedApplication, user, adminMemberRepository }) { + if (requestedApplication.isPixAdmin) { + const adminMember = await adminMemberRepository.get({ userId: user.id }); if (!adminMember?.hasAccessToAdminScope) { throw new ForbiddenAccess( 'User does not have the rights to access the application', diff --git a/api/src/identity-access-management/domain/usecases/authenticate-user.js b/api/src/identity-access-management/domain/usecases/authenticate-user.js index 4c2d79511a6..2922c049ca0 100644 --- a/api/src/identity-access-management/domain/usecases/authenticate-user.js +++ b/api/src/identity-access-management/domain/usecases/authenticate-user.js @@ -7,7 +7,6 @@ import { RefreshToken } from '../models/RefreshToken.js'; const authenticateUser = async function ({ password, - scope, source, username, localeFromCookie, @@ -21,52 +20,52 @@ const authenticateUser = async function ({ emailRepository, emailValidationDemandRepository, audience, + requestedApplication, }) { try { - const foundUser = await pixAuthenticationService.getUserByUsernameAndPassword({ + const user = await pixAuthenticationService.getUserByUsernameAndPassword({ username, password, userRepository, }); - if (foundUser.shouldChangePassword) { - const passwordResetToken = tokenService.createPasswordResetToken(foundUser.id); + if (user.shouldChangePassword) { + const passwordResetToken = tokenService.createPasswordResetToken(user.id); throw new UserShouldChangePasswordError(undefined, passwordResetToken); } - await _checkUserAccessScope(scope, foundUser, adminMemberRepository); + await _assertUserHasAccessToApplication({ requestedApplication, user, adminMemberRepository }); - const refreshToken = RefreshToken.generate({ userId: foundUser.id, source, audience }); + const refreshToken = RefreshToken.generate({ userId: user.id, source, audience }); await refreshTokenRepository.save({ refreshToken }); const { accessToken, expirationDelaySeconds } = await tokenService.createAccessTokenFromUser({ - userId: foundUser.id, + userId: user.id, source, audience, }); - foundUser.setLocaleIfNotAlreadySet(localeFromCookie); - if (foundUser.hasBeenModified) { - await userRepository.update({ id: foundUser.id, locale: foundUser.locale }); + user.setLocaleIfNotAlreadySet(localeFromCookie); + if (user.hasBeenModified) { + await userRepository.update({ id: user.id, locale: user.locale }); } - const userLogin = await userLoginRepository.findByUserId(foundUser.id); - if (foundUser.email && userLogin?.shouldSendConnectionWarning()) { - const validationToken = !foundUser.emailConfirmedAt - ? await emailValidationDemandRepository.save(foundUser.id) - : null; + + const userLogin = await userLoginRepository.findByUserId(user.id); + if (user.email && userLogin?.shouldSendConnectionWarning()) { + const validationToken = !user.emailConfirmedAt ? await emailValidationDemandRepository.save(user.id) : null; await emailRepository.sendEmailAsync( createWarningConnectionEmail({ - locale: foundUser.locale, - email: foundUser.email, - firstName: foundUser.firstName, + locale: user.locale, + email: user.email, + firstName: user.firstName, validationToken, }), ); } - await userLoginRepository.updateLastLoggedAt({ userId: foundUser.id }); + await userLoginRepository.updateLastLoggedAt({ userId: user.id }); await authenticationMethodRepository.updateLastLoggedAtByIdentityProvider({ - userId: foundUser.id, + userId: user.id, identityProvider: NON_OIDC_IDENTITY_PROVIDERS.PIX.code, }); @@ -80,12 +79,12 @@ const authenticateUser = async function ({ } }; -async function _checkUserAccessScope(scope, user, adminMemberRepository) { - if (scope === PIX_ORGA.SCOPE && !user.isLinkedToOrganizations()) { +async function _assertUserHasAccessToApplication({ requestedApplication, user, adminMemberRepository }) { + if (requestedApplication.isPixOrga && !user.isLinkedToOrganizations()) { throw new ForbiddenAccess(PIX_ORGA.NOT_LINKED_ORGANIZATION_MSG); } - if (scope === PIX_ADMIN.SCOPE) { + if (requestedApplication.isPixAdmin) { const adminMember = await adminMemberRepository.get({ userId: user.id }); if (!adminMember?.hasAccessToAdminScope) { throw new ForbiddenAccess(PIX_ADMIN.NOT_ALLOWED_MSG); diff --git a/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js b/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js index e06dfda1e71..254003d1028 100644 --- a/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js +++ b/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js @@ -1,18 +1,18 @@ /** * @typedef {function} getAuthorizationUrl * @param {Object} params - * @param {string} params.audience * @param {string} params.identityProvider + * @param {RequestedApplication} params.requestedApplication * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry * @return {Promise} */ -async function getAuthorizationUrl({ target, identityProvider, oidcAuthenticationServiceRegistry }) { +async function getAuthorizationUrl({ identityProvider, requestedApplication, oidcAuthenticationServiceRegistry }) { await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); await oidcAuthenticationServiceRegistry.configureReadyOidcProviderServiceByCode(identityProvider); const oidcAuthenticationService = oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode: identityProvider, - target, + requestedApplication, }); return oidcAuthenticationService.getAuthorizationUrl(); diff --git a/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js b/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js index 14742c33144..28d744c3d64 100644 --- a/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js +++ b/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js @@ -1,14 +1,14 @@ /** * @typedef {function} getReadyIdentityProviders * @param {Object} params - * @param {string} [params.audience=app] + * @param {RequestedApplication} params.requestedApplication * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry * @return {Promise} */ -const getReadyIdentityProviders = async function ({ target = 'app', oidcAuthenticationServiceRegistry }) { +const getReadyIdentityProviders = async function ({ requestedApplication, oidcAuthenticationServiceRegistry }) { await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); - if (target === 'admin') { + if (requestedApplication?.isPixAdmin) { return oidcAuthenticationServiceRegistry.getReadyOidcProviderServicesForPixAdmin(); } diff --git a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js index 8029fe0e4bc..cafe9873aba 100644 --- a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js +++ b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js @@ -7,6 +7,7 @@ import { AuthenticationMethod } from '../models/AuthenticationMethod.js'; * @param {string} params.authenticationKey * @param {string} params.identityProvider * @param {string} params.audience + * @param {RequestedApplication} params.requestedApplication * @param {AuthenticationSessionService} params.authenticationSessionService * @param {AuthenticationMethodRepository} params.authenticationMethodRepository * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry @@ -31,6 +32,7 @@ export const reconcileOidcUser = async function ({ const oidcAuthenticationService = oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode: identityProvider, + requestedApplication, }); const sessionContentAndUserInfo = await authenticationSessionService.getByKey(authenticationKey); diff --git a/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js b/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js index 2b362fae244..0aebc6b08c8 100644 --- a/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js @@ -30,6 +30,10 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const options = { method: 'GET', url: '/api/oidc/identity-providers', + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', + }, }; // when @@ -79,13 +83,16 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p // given const query = querystring.stringify({ identity_provider: 'OIDC_EXAMPLE_NET', - target: 'app', }); // when const response = await server.inject({ method: 'GET', url: `/api/oidc/authorization-url?${query}`, + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', + }, }); // then @@ -119,6 +126,10 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const authUrlResponse = await server.inject({ method: 'GET', url: `/api/oidc/authorization-url?${query}`, + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', + }, }); cookies = authUrlResponse.headers['set-cookie']; @@ -192,7 +203,11 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const response = await server.inject({ method: 'POST', url: '/api/oidc/token', - headers, + headers: { + cookie: cookies[0], + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', + }, payload, }); @@ -285,7 +300,7 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p }); }); - context('when target is admin', function () { + context('when the requestedApplication is admin', function () { context('when user does not have an admin role', function () { it('returns 403', async function () { // given @@ -293,8 +308,6 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const lastName = 'Doe'; const externalIdentifier = 'sub'; - payload.data.attributes.target = 'admin'; - const userId = databaseBuilder.factory.buildUser({ firstName, lastName, @@ -340,7 +353,11 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const response = await server.inject({ method: 'POST', url: '/api/oidc/token', - headers, + headers: { + cookie: cookies[0], + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'admin.pix.fr', + }, payload, }); @@ -364,8 +381,6 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const lastName = 'Doe'; const externalIdentifier = 'sub'; - payload.data.attributes.target = 'admin'; - const userId = databaseBuilder.factory.buildUser.withRole({ firstName, lastName, diff --git a/api/tests/identity-access-management/acceptance/application/token.route.test.js b/api/tests/identity-access-management/acceptance/application/token.route.test.js index b663d4452b7..566520eb62f 100644 --- a/api/tests/identity-access-management/acceptance/application/token.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/token.route.test.js @@ -32,23 +32,16 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); it('returns a 200 with an access token and a refresh token when authentication is ok', async function () { - // given / when - const response = await server.inject({ - method: 'POST', + // given + const options = _getPostFormOptions({ url: '/api/token', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'orga.pix.fr', - }, - payload: querystring.stringify({ - grant_type: 'password', - username: userEmailAddress, - password: userPassword, - scope: 'pix-orga', - }), + dataToPost: { grant_type: 'password', username: userEmailAddress, password: userPassword }, + applicationName: 'orga', }); + // when + const response = await server.inject(options); + // then const result = response.result; expect(response.statusCode).to.equal(200); @@ -73,23 +66,15 @@ describe('Acceptance | Identity Access Management | Route | Token', function () await databaseBuilder.commit(); - // when - const response = await server.inject({ - method: 'POST', + const options = _getPostFormOptions({ url: '/api/token', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'orga.pix.fr', - }, - payload: querystring.stringify({ - grant_type: 'password', - username: 'beth.rave1212', - password: userPassword, - scope: 'pix', - }), + dataToPost: { grant_type: 'password', username: 'beth.rave1212', password: userPassword }, + applicationName: 'orga', }); + // when + const response = await server.inject(options); + // then expect(response.statusCode).to.equal(401); expect(response.result.errors[0].title).equal('PasswordShouldChange'); @@ -99,36 +84,29 @@ describe('Acceptance | Identity Access Management | Route | Token', function () context('when user needs to refresh his access token', function () { it('returns a 200 with a new access token', async function () { // given - const { result: accessTokenResult } = await server.inject({ - method: 'POST', + const optionsForAccessToken = _getPostFormOptions({ url: '/api/token', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'orga.pix.fr', - }, - payload: querystring.stringify({ + dataToPost: { grant_type: 'password', username: userEmailAddress, password: userPassword, - }), + }, + applicationName: 'orga', }); + const { result: accessTokenResult } = await server.inject(optionsForAccessToken); - // when - const response = await server.inject({ - method: 'POST', + const options = _getPostFormOptions({ url: '/api/token', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'orga.pix.fr', - }, - payload: querystring.stringify({ + dataToPost: { grant_type: 'refresh_token', refresh_token: accessTokenResult.refresh_token, - }), + }, + applicationName: 'orga', }); + // when + const response = await server.inject(options); + // then const result = response.result; expect(response.statusCode).to.equal(200); @@ -143,10 +121,10 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); }); - context('when scope is admin', function () { + context('when requestedApplication is admin', function () { context('when admin member has allowed role but has been disabled', function () { it('returns http code 403', async function () { - //given + // given const user = databaseBuilder.factory.buildUser.withRawPassword({ email: 'email@example.net', rawPassword: userPassword, @@ -158,7 +136,12 @@ describe('Acceptance | Identity Access Management | Route | Token', function () disabledAt: new Date('2021-01-02'), }); await databaseBuilder.commit(); - const options = _getOptions({ scope: 'pix-admin', username: user.email, password: userPassword }); + + const options = _getPostFormOptions({ + url: '/api/token', + dataToPost: { grant_type: 'password', username: user.email, password: userPassword }, + applicationName: 'admin', + }); // when const response = await server.inject(options); @@ -169,9 +152,9 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); }); - context('when scope is pix-certif', function () { + context('when application is Pix Certif', function () { it('returns http code 200 with accessToken when authentication is ok', async function () { - //given + // given databaseBuilder.factory.buildCertificationCenter({ id: 345 }); databaseBuilder.factory.buildSession({ id: 121, certificationCenterId: 345 }); const candidate = databaseBuilder.factory.buildCertificationCandidate({ sessionId: 121 }); @@ -179,9 +162,12 @@ describe('Acceptance | Identity Access Management | Route | Token', function () databaseBuilder.factory.buildSupervisorAccess({ userId, sessionId: 121 }); await databaseBuilder.commit(); - const options = _getOptions({ scope: 'pix-certif', username: userEmailAddress, password: userPassword }); + const options = _getPostFormOptions({ + url: '/api/token', + dataToPost: { grant_type: 'password', username: userEmailAddress, password: userPassword }, + applicationName: 'certif', + }); - await databaseBuilder.commit(); // when const response = await server.inject(options); @@ -193,7 +179,7 @@ describe('Acceptance | Identity Access Management | Route | Token', function () expect(result.access_token).to.exist; const decodedAccessToken = await decodeIfValid(result.access_token); expect(decodedAccessToken).to.include({ - aud: 'https://orga.pix.fr', + aud: 'https://certif.pix.fr', }); expect(result.user_id).to.equal(userId); }); @@ -211,7 +197,11 @@ describe('Acceptance | Identity Access Management | Route | Token', function () databaseBuilder.factory.buildUserLogin({ userId, failureCount: 9 }); await databaseBuilder.commit(); - const options = _getOptions({ scope: 'pix', username: 'email@without.mb', password: 'wrongPassword' }); + const options = _getPostFormOptions({ + url: '/api/token', + dataToPost: { grant_type: 'password', username: 'email@without.mb', password: 'wrongPassword' }, + applicationName: 'app', + }); // when const { statusCode } = await server.inject(options); @@ -239,7 +229,11 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); await databaseBuilder.commit(); - const options = _getOptions({ scope: 'pix', username: 'email@without.mb', password: userPassword }); + const options = _getPostFormOptions({ + url: '/api/token', + dataToPost: { grant_type: 'password', username: 'email@without.mb', password: userPassword }, + applicationName: 'app', + }); // when const { statusCode } = await server.inject(options); @@ -264,7 +258,11 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); await databaseBuilder.commit(); - const options = _getOptions({ scope: 'pix', username: 'email@without.mb', password: userPassword }); + const options = _getPostFormOptions({ + url: '/api/token', + dataToPost: { grant_type: 'password', username: 'email@without.mb', password: userPassword }, + applicationName: 'app', + }); // when const { statusCode } = await server.inject(options); @@ -291,23 +289,16 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); await databaseBuilder.commit(); - // when - const response = await server.inject({ - method: 'POST', + const options = _getPostFormOptions({ url: '/api/token', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - cookie: `locale=${localeFromCookie}`, - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'orga.pix.fr', - }, - payload: querystring.stringify({ - grant_type: 'password', - username: userWithoutLocale.email, - password: userPassword, - }), + dataToPost: { grant_type: 'password', username: userWithoutLocale.email, password: userPassword }, + applicationName: 'app', + localeFromCookie, }); + // when + const response = await server.inject(options); + // then expect(response.statusCode).to.equal(200); const user = await knex('users').where({ id: userWithoutLocale.id }).first(); @@ -328,23 +319,20 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); await databaseBuilder.commit(); - // when - const response = await server.inject({ - method: 'POST', + const options = _getPostFormOptions({ url: '/api/token', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - cookie: `locale=${localeFromCookie}`, - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'orga.pix.fr', - }, - payload: querystring.stringify({ + dataToPost: { grant_type: 'password', username: userWithLocale.email, password: userPassword, - }), + }, + applicationName: 'app', + localeFromCookie, }); + // when + const response = await server.inject(options); + // then expect(response.statusCode).to.equal(200); const user = await knex('users').where({ id: userWithLocale.id }).first(); @@ -365,22 +353,16 @@ describe('Acceptance | Identity Access Management | Route | Token', function () beforeEach(async function () { const targetProfile = databaseBuilder.factory.buildTargetProfile({ isSimplifiedAccess: false }); databaseBuilder.factory.buildCampaign({ code: campaignCode, targetProfile }); + await databaseBuilder.commit(); - options = { - method: 'POST', + options = _getPostFormOptions({ url: '/api/token/anonymous', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'app.pix.fr', - }, - payload: querystring.stringify({ + dataToPost: { campaign_code: campaignCode, lang, - }), - }; - - await databaseBuilder.commit(); + }, + applicationName: 'app', + }); }); it('returns an 401', async function () { @@ -403,22 +385,16 @@ describe('Acceptance | Identity Access Management | Route | Token', function () beforeEach(async function () { const targetProfileId = databaseBuilder.factory.buildTargetProfile({ isSimplifiedAccess: true }).id; databaseBuilder.factory.buildCampaign({ code: simplifiedAccessCampaignCode, targetProfileId }); + await databaseBuilder.commit(); - options = { - method: 'POST', + options = _getPostFormOptions({ url: '/api/token/anonymous', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'app.pix.fr', - }, - payload: querystring.stringify({ + dataToPost: { campaign_code: simplifiedAccessCampaignCode, lang, - }), - }; - - await databaseBuilder.commit(); + }, + applicationName: 'app', + }); }); it('returns a 200 with accessToken', async function () { @@ -525,20 +501,16 @@ describe('Acceptance | Identity Access Management | Route | Token', function () }); }); -function _getOptions({ scope, password, username }) { +function _getPostFormOptions({ url, dataToPost, applicationName, localeFromCookie }) { return { method: 'POST', - url: '/api/token', + url, headers: { 'content-type': 'application/x-www-form-urlencoded', 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'orga.pix.fr', + 'x-forwarded-host': `${applicationName}.pix.fr`, + ...(localeFromCookie && { cookie: `locale=${localeFromCookie}` }), }, - payload: querystring.stringify({ - grant_type: 'password', - username, - password, - scope, - }), + payload: querystring.stringify(dataToPost), }; } diff --git a/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js b/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js index fa50af4ed03..847e7f6339c 100644 --- a/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js +++ b/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js @@ -1,6 +1,6 @@ -import { PIX_ADMIN } from '../../../../../src/authorization/domain/constants.js'; import { OidcAuthenticationServiceRegistry } from '../../../../../src/identity-access-management/domain/services/oidc-authentication-service-registry.js'; import { oidcProviderRepository } from '../../../../../src/identity-access-management/infrastructure/repositories/oidc-provider-repository.js'; +import { RequestedApplication } from '../../../../../src/identity-access-management/infrastructure/utils/network.js'; import { InvalidIdentityProviderError } from '../../../../../src/shared/domain/errors.js'; import { catchErrSync, databaseBuilder, expect } from '../../../../test-helper.js'; @@ -164,15 +164,16 @@ describe('Integration | Identity Access Management | Domain | Service | oidc-aut expect(service.code).to.equal('OIDC_EXAMPLE'); }); - describe('when the target is admin', function () { + describe('when the requestedApplication is admin', function () { it('returns a ready OIDC provider for Pix Admin', async function () { // given + const requestedApplication = new RequestedApplication('admin'); await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); // when const service = oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode: 'OIDC_EXAMPLE_FOR_PIX_ADMIN', - target: PIX_ADMIN.TARGET, + requestedApplication, }); // then diff --git a/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js b/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js index 4e70aa0bea6..a2093a69a23 100644 --- a/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js +++ b/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js @@ -53,7 +53,6 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr // then expect(usecases.authenticateOidcUser).to.have.been.calledWithExactly({ - target: undefined, code, identityProviderCode: identityProvider, nonce: 'nonce', @@ -80,27 +79,6 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr }); }); - context('when authentication is complete', function () { - it('returns PIX access token and logout url uuid', async function () { - // given - usecases.authenticateOidcUser.resolves({ - pixAccessToken, - logoutUrlUUID: '0208f50b-f612-46aa-89a0-7cdb5fb0d312', - isAuthenticationComplete: true, - }); - - // when - const response = await oidcProviderController.authenticateOidcUser(request, hFake); - - // then - const expectedResult = { - access_token: pixAccessToken, - logout_url_uuid: '0208f50b-f612-46aa-89a0-7cdb5fb0d312', - }; - expect(response.source).to.deep.equal(expectedResult); - }); - }); - context('when pix access token does not exist', function () { it('returns UnauthorizedError', async function () { // given @@ -214,6 +192,10 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr it('returns the generated authorization url', async function () { // given const request = { + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', + }, query: { identity_provider: 'OIDC' }, yar: { set: sinon.stub(), commit: sinon.stub() }, }; @@ -228,8 +210,8 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr //then expect(usecases.getAuthorizationUrl).to.have.been.calledWithExactly({ - target: undefined, identityProvider: 'OIDC', + requestedApplication: new RequestedApplication('app'), }); expect(request.yar.set).to.have.been.calledTwice; expect(request.yar.set.getCall(0)).to.have.been.calledWithExactly( @@ -248,71 +230,6 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr }); }); - describe('#getIdentityProviders', function () { - it('returns the list of oidc identity providers', async function () { - // given - sinon.stub(usecases, 'getReadyIdentityProviders').returns([ - { - code: 'SOME_OIDC_PROVIDER', - source: 'some_oidc_provider', - isVisible: true, - organizationName: 'Some OIDC Provider', - slug: 'some-oidc-provider', - shouldCloseSession: false, - }, - ]); - - // when - const response = await oidcProviderController.getIdentityProviders({ query: { target: null } }, hFake); - - // then - expect(usecases.getReadyIdentityProviders).to.have.been.called; - expect(response.statusCode).to.equal(200); - expect(response.source.data).to.have.lengthOf(1); - expect(response.source.data).to.deep.contain({ - type: 'oidc-identity-providers', - id: 'some-oidc-provider', - attributes: { - code: 'SOME_OIDC_PROVIDER', - source: 'some_oidc_provider', - 'organization-name': 'Some OIDC Provider', - slug: 'some-oidc-provider', - 'should-close-session': false, - 'is-visible': true, - }, - }); - }); - }); - - describe('#getRedirectLogoutUrl', function () { - it('calls the oidc authentication service retrieved from his code to generate the redirect logout url', async function () { - // given - const request = { - auth: { credentials: { userId: '123' } }, - query: { - identity_provider: 'OIDC', - logout_url_uuid: '1f3dbb71-f399-4c1c-85ae-0a863c78aeea', - }, - }; - - sinon.stub(usecases, 'getRedirectLogoutUrl').resolves('https://idp.net/oidc/logout?id_token_hint=ID_TOKEN'); - - // when - const response = await oidcProviderController.getRedirectLogoutUrl(request, hFake); - - // then - expect(usecases.getRedirectLogoutUrl).to.have.been.calledWithExactly({ - identityProvider: 'OIDC', - logoutUrlUUID: '1f3dbb71-f399-4c1c-85ae-0a863c78aeea', - userId: '123', - }); - expect(response.statusCode).to.equal(200); - expect(response.source).to.deep.equal({ - redirectLogoutUrl: 'https://idp.net/oidc/logout?id_token_hint=ID_TOKEN', - }); - }); - }); - describe('#reconcileUser', function () { it('calls use case and return the result', async function () { // given diff --git a/api/tests/identity-access-management/unit/application/token.controller.test.js b/api/tests/identity-access-management/unit/application/token.controller.test.js index 52319f84091..a0dd2b08a48 100644 --- a/api/tests/identity-access-management/unit/application/token.controller.test.js +++ b/api/tests/identity-access-management/unit/application/token.controller.test.js @@ -1,5 +1,6 @@ import { tokenController } from '../../../../src/identity-access-management/application/token/token.controller.js'; import { usecases } from '../../../../src/identity-access-management/domain/usecases/index.js'; +import { RequestedApplication } from '../../../../src/identity-access-management/infrastructure/utils/network.js'; import { expect, hFake, sinon } from '../../../test-helper.js'; describe('Unit | Identity Access Management | Application | Controller | Token', function () { @@ -39,9 +40,9 @@ describe('Unit | Identity Access Management | Application | Controller | Token', const accessToken = 'jwt.access.token'; const username = 'user@email.com'; const password = 'user_password'; - const scope = 'pix-orga'; const source = 'pix'; const audience = 'https://app.pix.fr'; + const requestedApplication = new RequestedApplication('app'); /** * @see https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/ @@ -62,7 +63,6 @@ describe('Unit | Identity Access Management | Application | Controller | Token', grant_type: 'password', username, password, - scope, }, state: { locale: localeFromCookie, @@ -71,7 +71,7 @@ describe('Unit | Identity Access Management | Application | Controller | Token', sinon .stub(usecases, 'authenticateUser') - .withArgs({ username, password, scope, source, localeFromCookie, audience }) + .withArgs({ username, password, source, localeFromCookie, audience, requestedApplication }) .resolves({ accessToken, refreshToken, expirationDelaySeconds }); const tokenServiceStub = { extractUserId: sinon.stub() }; @@ -111,7 +111,7 @@ describe('Unit | Identity Access Management | Application | Controller | Token', 'x-forwarded-proto': 'https', 'x-forwarded-host': 'app.pix.fr', }, - payload: { grant_type: 'refresh_token', refresh_token: refreshToken, scope }, + payload: { grant_type: 'refresh_token', refresh_token: refreshToken }, }; sinon diff --git a/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js index 2b5eaa79be1..89b1f363f00 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js @@ -1,4 +1,3 @@ -import * as appMessages from '../../../../../src/authorization/domain/constants.js'; import { POLE_EMPLOI } from '../../../../../src/identity-access-management/domain/constants/oidc-identity-providers.js'; import { AuthenticationMethod } from '../../../../../src/identity-access-management/domain/models/AuthenticationMethod.js'; import { authenticateOidcUser } from '../../../../../src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js'; @@ -19,8 +18,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi let lastUserApplicationConnectionsRepository; let oidcAuthenticationServiceRegistry; const externalIdentityId = '094b83ac-2e20-4aa8-b438-0bc91748e4a6'; - const audience = 'https://app.pix.fr'; - const requestedApplication = new RequestedApplication('app'); beforeEach(function () { oidcAuthenticationService = { @@ -55,19 +52,20 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi }; }); - context('check access by target', function () { - context('when target is pix-admin', function () { + context('check access by requestedApplication', function () { + context('when requestedApplication is Pix Admin', function () { + const requestedApplication = new RequestedApplication('admin'); + context('when user has no role and is therefore not an admin member', function () { it('throws an error', async function () { // given - const target = appMessages.PIX_ADMIN.TARGET; _fakeOidcAPI({ oidcAuthenticationService, externalIdentityId }); userRepository.findByExternalIdentifier.resolves({ id: 10 }); adminMemberRepository.get.resolves(null); // when const error = await catchErr(authenticateOidcUser)({ - target, + requestedApplication, oidcAuthenticationServiceRegistry, userRepository, adminMemberRepository, @@ -83,7 +81,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi context('when user has a role but admin membership is disabled', function () { it('throws an error', async function () { // given - const target = appMessages.PIX_ADMIN.TARGET; const adminMember = new AdminMember({ id: 567, role: 'CERTIF', @@ -95,7 +92,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when const error = await catchErr(authenticateOidcUser)({ - target, + requestedApplication, oidcAuthenticationServiceRegistry, userRepository, adminMemberRepository, @@ -190,6 +187,8 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi }); context('when user does not have an account', function () { + const audience = 'https://app.pix.fr'; + it('saves the authentication session and returns the authentication key', async function () { // given const sessionContent = new AuthenticationSessionContent({ @@ -216,6 +215,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when const result = await authenticateOidcUser({ + audience, stateReceived: 'state', stateSent: 'state', identityProviderCode: 'OIDC_EXAMPLE_NET', @@ -224,7 +224,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, - audience, }); // then @@ -245,6 +244,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when await authenticateOidcUser({ + audience, stateReceived: 'state', stateSent: 'state', identityProviderCode: 'OIDC_EXAMPLE_NET', @@ -253,7 +253,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, - audience, }); // then @@ -273,7 +272,9 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi oidcAuthenticationService.createAuthenticationComplement.returns(authenticationComplement); // when + const requestedApplication = new RequestedApplication('app'); await authenticateOidcUser({ + requestedApplication, stateReceived: 'state', stateSent: 'state', identityProviderCode: 'OIDC_EXAMPLE_NET', @@ -283,7 +284,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userRepository, userLoginRepository, lastUserApplicationConnectionsRepository, - requestedApplication, }); // then @@ -300,6 +300,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi context('when the provider has an authentication complement', function () { it('updates the authentication method', async function () { // given + const requestedApplication = new RequestedApplication('app'); _fakeOidcAPI({ oidcAuthenticationService, externalIdentityId }); userRepository.findByExternalIdentifier.resolves({ id: 10 }); const authenticationComplement = new AuthenticationMethod.OidcAuthenticationComplement({ @@ -310,6 +311,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when await authenticateOidcUser({ + requestedApplication, stateReceived: 'state', stateSent: 'state', identityProviderCode: 'OIDC_EXAMPLE_NET', @@ -318,7 +320,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, - requestedApplication, lastUserApplicationConnectionsRepository, }); @@ -382,6 +383,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi context('when user has an account', function () { it('updates the authentication method', async function () { // given + const requestedApplication = new RequestedApplication('app'); const { sessionContent } = _fakeOidcAPI({ oidcAuthenticationService, externalIdentityId }); userRepository.findByExternalIdentifier.resolves({ id: 1 }); const authenticationComplement = new AuthenticationMethod.PoleEmploiOidcAuthenticationComplement({ @@ -393,6 +395,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when await authenticateOidcUser({ + requestedApplication, stateReceived: 'state', stateSent: 'state', identityProviderCode: POLE_EMPLOI.code, @@ -402,7 +405,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userRepository, userLoginRepository, audience, - requestedApplication, lastUserApplicationConnectionsRepository, }); @@ -432,6 +434,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when const accessToken = await authenticateOidcUser({ + requestedApplication, stateReceived: 'state', stateSent: 'state', identityProviderCode: POLE_EMPLOI.code, @@ -441,7 +444,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userRepository, userLoginRepository, audience, - requestedApplication, lastUserApplicationConnectionsRepository, }); @@ -459,6 +461,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi context('when user is logged with their pix account but also has a separate oidc account', function () { it('updates the oidc authentication method', async function () { // given + const requestedApplication = new RequestedApplication('app'); const { sessionContent } = _fakeOidcAPI({ oidcAuthenticationService, externalIdentityId }); userRepository.findByExternalIdentifier .withArgs({ externalIdentityId, identityProvider: oidcAuthenticationService.identityProvider }) @@ -472,6 +475,8 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when await authenticateOidcUser({ + audience, + requestedApplication, stateReceived: 'state', stateSent: 'state', identityProviderCode: POLE_EMPLOI.code, @@ -480,8 +485,6 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, - audience, - requestedApplication, lastUserApplicationConnectionsRepository, }); diff --git a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js index d131f976ceb..6f7e81a2ae7 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js @@ -1,4 +1,4 @@ -import { PIX_ADMIN, PIX_CERTIF, PIX_ORGA } from '../../../../../src/authorization/domain/constants.js'; +import { PIX_ADMIN, PIX_ORGA } from '../../../../../src/authorization/domain/constants.js'; import { NON_OIDC_IDENTITY_PROVIDERS } from '../../../../../src/identity-access-management/domain/constants/identity-providers.js'; import { createWarningConnectionEmail } from '../../../../../src/identity-access-management/domain/emails/create-warning-connection.email.js'; import { @@ -8,6 +8,7 @@ import { import { RefreshToken } from '../../../../../src/identity-access-management/domain/models/RefreshToken.js'; import { User } from '../../../../../src/identity-access-management/domain/models/User.js'; import { authenticateUser } from '../../../../../src/identity-access-management/domain/usecases/authenticate-user.js'; +import { RequestedApplication } from '../../../../../src/identity-access-management/infrastructure/utils/network.js'; import { UserNotFoundError } from '../../../../../src/shared/domain/errors.js'; import { ForbiddenAccess } from '../../../../../src/shared/domain/errors.js'; import { AdminMember } from '../../../../../src/shared/domain/models/AdminMember.js'; @@ -72,6 +73,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = PIX_ORGA.SCOPE; const user = new User({ email: userEmail, memberships: [] }); const audience = 'https://orga.pix.fr'; + const requestedApplication = new RequestedApplication('orga'); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); // when @@ -84,6 +86,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, refreshTokenRepository, audience, + requestedApplication, }); // then @@ -98,6 +101,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = PIX_ADMIN.SCOPE; const user = new User({ email: userEmail }); const audience = 'https://admin.pix.fr'; + const requestedApplication = new RequestedApplication('admin'); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); adminMemberRepository.get.withArgs({ userId: user.id }).resolves(); @@ -113,6 +117,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u adminMemberRepository, refreshTokenRepository, audience, + requestedApplication, }); // then @@ -125,6 +130,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = PIX_ADMIN.SCOPE; const user = new User({ email: userEmail }); const audience = 'https://admin.pix.fr'; + const requestedApplication = new RequestedApplication('admin'); const adminMember = new AdminMember({ id: 567, userId: user.id, @@ -150,6 +156,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u adminMemberRepository, refreshTokenRepository, audience, + requestedApplication, }); // then @@ -163,6 +170,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const user = new User({ id: 123, email: userEmail }); const audience = 'https://admin.pix.fr'; + const requestedApplication = new RequestedApplication('admin'); const adminMember = new AdminMember({ id: 567, userId: user.id, @@ -202,6 +210,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u refreshTokenRepository, tokenService, audience, + requestedApplication, }); // then @@ -214,15 +223,15 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u }); }); - context('when scope is pix-certif', function () { + context('when application is Pix Certif', function () { context('when user is not linked to any certification centers', function () { it('should resolves a valid JWT access token when feature toggle is enabled', async function () { // given - const scope = PIX_CERTIF.SCOPE; const accessToken = 'jwt.access.token'; const expirationDelaySeconds = 1; const source = 'pix'; const audience = 'https://certif.pix.fr'; + const requestedApplication = new RequestedApplication('certif'); const user = domainBuilder.buildUser({ email: userEmail, @@ -231,7 +240,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); - const refreshToken = { value: 'jwt.refresh.token', userId: '456', scope, audience }; + const refreshToken = { value: 'jwt.refresh.token', userId: '456', audience }; sinon.stub(RefreshToken, 'generate').returns(refreshToken); tokenService.createAccessTokenFromUser @@ -242,7 +251,6 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u await authenticateUser({ username: userEmail, password, - scope, source, pixAuthenticationService, tokenService, @@ -251,6 +259,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, authenticationMethodRepository, audience, + requestedApplication, }); // then @@ -272,6 +281,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const expirationDelaySeconds = 1; const user = domainBuilder.buildUser({ email: userEmail }); const audience = 'https://certif.pix.fr'; + const requestedApplication = new RequestedApplication('certif'); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); const refreshToken = { value: 'jwt.refresh.token', userId: '456', scope, audience }; @@ -286,7 +296,6 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u username: userEmail, password, source, - scope, pixAuthenticationService, refreshTokenRepository, tokenService, @@ -294,6 +303,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, authenticationMethodRepository, audience, + requestedApplication, }); // then @@ -312,6 +322,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const audience = 'https://certif.pix.fr'; + const requestedApplication = new RequestedApplication('certif'); const user = domainBuilder.buildUser({ email: userEmail }); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); @@ -332,6 +343,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, authenticationMethodRepository, audience, + requestedApplication, }); // then @@ -392,6 +404,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const audience = 'https://certif.pix.fr'; + const requestedApplication = new RequestedApplication('certif'); const user = domainBuilder.buildUser({ email: userEmail }); const userLogins = domainBuilder.identityAccessManagement.buildUserLogin({ @@ -428,6 +441,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u emailRepository, emailValidationDemandRepository, audience, + requestedApplication, }); // then @@ -442,6 +456,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const audience = 'https://certif.pix.fr'; + const requestedApplication = new RequestedApplication('certif'); const username = 'lorie.amie'; const user = domainBuilder.buildUser({ username: username }); @@ -474,6 +489,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u authenticationMethodRepository, emailRepository, audience, + requestedApplication, }); // then @@ -490,6 +506,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const audience = 'https://certif.pix.fr'; + const requestedApplication = new RequestedApplication('certif'); const user = domainBuilder.buildUser({ email: userEmail }); const userLogins = domainBuilder.identityAccessManagement.buildUserLogin({ @@ -519,6 +536,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u authenticationMethodRepository, emailRepository, audience, + requestedApplication, }); // then @@ -576,6 +594,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const expirationDelaySeconds = 1; const user = domainBuilder.buildUser({ email: userEmail, locale: 'fr-FR' }); const audience = 'https://app.pix.fr'; + const requestedApplication = new RequestedApplication('app'); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); tokenService.createAccessTokenFromUser.resolves({ accessToken, expirationDelaySeconds }); @@ -593,6 +612,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, authenticationMethodRepository, audience, + requestedApplication, }); // then @@ -609,6 +629,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const audience = 'https://app.pix.fr'; + const requestedApplication = new RequestedApplication('app'); const user = domainBuilder.buildUser({ email: userEmail, locale: null }); const setLocaleIfNotAlreadySetStub = sinon.stub(user, 'setLocaleIfNotAlreadySet'); @@ -629,6 +650,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, authenticationMethodRepository, audience, + requestedApplication, }); // then @@ -644,6 +666,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const audience = 'https://app.pix.fr'; + const requestedApplication = new RequestedApplication('app'); const user = domainBuilder.buildUser({ email: userEmail, locale: undefined }); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); @@ -663,6 +686,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, authenticationMethodRepository, audience, + requestedApplication, }); // then diff --git a/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js index ab9c886ef9a..6303b641de7 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js @@ -4,7 +4,6 @@ import { expect, sinon } from '../../../../test-helper.js'; describe('Unit | Identity Access Management | Domain | UseCases | get-authorization-url', function () { it('returns the generated authorization url', async function () { // given - const target = 'app'; const identityProvider = 'OIDC'; const oidcAuthenticationService = { getAuthorizationUrl: sinon.stub().returns('https://authorization.url'), @@ -21,7 +20,6 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-authorizat // when const authorizationUrl = await getAuthorizationUrl({ - target, identityProvider, oidcAuthenticationServiceRegistry, }); @@ -33,7 +31,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-authorizat ); expect(oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode).to.have.been.calledWithExactly({ identityProviderCode: 'OIDC', - target: 'app', + requestedApplication: undefined, }); expect(oidcAuthenticationService.getAuthorizationUrl).to.have.been.calledOnce; expect(authorizationUrl).to.equal('https://authorization.url'); diff --git a/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js index 8a37d079aa4..49b27f597e0 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js @@ -1,5 +1,5 @@ -import { PIX_ADMIN } from '../../../../../src/authorization/domain/constants.js'; import { getReadyIdentityProviders } from '../../../../../src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js'; +import { RequestedApplication } from '../../../../../src/identity-access-management/infrastructure/utils/network.js'; import { expect, sinon } from '../../../../test-helper.js'; describe('Unit | Identity Access Management | Domain | UseCases | get-ready-identity-providers', function () { @@ -19,13 +19,16 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-ready-iden }; }); - describe('when a target is provided', function () { - describe('when the provided target is equal to "admin"', function () { + describe('when requestedApplication is provided', function () { + describe('when the provided requestedApplication is Pix Admin', function () { it('returns oidc providers from getReadyOidcProviderServicesForPixAdmin', async function () { + // given + const requestedApplication = new RequestedApplication('admin'); + // when const identityProviders = await getReadyIdentityProviders({ - target: PIX_ADMIN.TARGET, oidcAuthenticationServiceRegistry: oidcAuthenticationServiceRegistryStub, + requestedApplication, }); // then @@ -39,7 +42,6 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-ready-iden it('returns oidc providers from getReadyOidcProviderServices', async function () { // when const identityProviders = await getReadyIdentityProviders({ - target: null, oidcAuthenticationServiceRegistry: oidcAuthenticationServiceRegistryStub, }); diff --git a/high-level-tests/e2e/cypress.config.cjs b/high-level-tests/e2e/cypress.config.cjs index 8b08f41898a..000c6bc8e2f 100644 --- a/high-level-tests/e2e/cypress.config.cjs +++ b/high-level-tests/e2e/cypress.config.cjs @@ -56,7 +56,6 @@ async function setupNodeEvents(on, config) { module.exports = defineConfig({ env: { APP_URL: "http://localhost:4200", - API_URL: "http://localhost:3000", ORGA_URL: "http://localhost:4201", visualRegressionType: "regression", }, diff --git a/high-level-tests/e2e/cypress/support/commands.js b/high-level-tests/e2e/cypress/support/commands.js index c81b6319c6a..d218532ad57 100644 --- a/high-level-tests/e2e/cypress/support/commands.js +++ b/high-level-tests/e2e/cypress/support/commands.js @@ -2,12 +2,11 @@ const jsonwebtoken = require('jsonwebtoken'); const { addCompareSnapshotCommand } = require('cypress-visual-regression/dist/command'); require('@testing-library/cypress/add-commands'); -function getLoginBody(username, password, scope) { +function getLoginBody(username, password) { return { username, password, grant_type: 'password', - scope, }; } @@ -21,7 +20,6 @@ function setEmberSimpleAuthSession(response) { access_token: response.body.access_token, user_id: response.body.user_id, refresh_token: response.body.refresh_token, - scope: response.body.scope, expires_in: 1000, }, }) @@ -31,10 +29,10 @@ function setEmberSimpleAuthSession(response) { Cypress.Commands.add("login", (username, password, url) => { cy.intercept("/api/users/me").as("getCurrentUser"); cy.request({ - url: `${Cypress.env('API_URL')}/api/token`, + url: `${Cypress.env('APP_URL')}/api/token`, method: 'POST', form: true, - body: getLoginBody(username, password, 'mon-pix'), + body: getLoginBody(username, password), }).then(setEmberSimpleAuthSession); if (url) cy.visitMonPix(url); @@ -44,10 +42,10 @@ Cypress.Commands.add("login", (username, password, url) => { Cypress.Commands.add('loginOrga', (username, password) => { cy.intercept('/api/prescription/prescribers/**').as('getCurrentUser'); cy.request({ - url: `${Cypress.env('API_URL')}/api/token`, + url: `${Cypress.env('ORGA_URL')}/api/token`, method: 'POST', form: true, - body: getLoginBody(username, password, 'pix-orga'), + body: getLoginBody(username, password), }).then(setEmberSimpleAuthSession); cy.wait(["@getCurrentUser"]); }); diff --git a/high-level-tests/load-testing/scenarios/signup-and-campaign-participation.yaml b/high-level-tests/load-testing/scenarios/signup-and-campaign-participation.yaml index 2ed60508db9..6d704f5c849 100644 --- a/high-level-tests/load-testing/scenarios/signup-and-campaign-participation.yaml +++ b/high-level-tests/load-testing/scenarios/signup-and-campaign-participation.yaml @@ -37,7 +37,7 @@ scenarios: url: "/api/token" headers: content-type: "application/x-www-form-urlencoded" - body: "grant_type=password&scope=mon-pix&username={{ email }}&password={{ password }}" + body: "grant_type=password&username={{ email }}&password={{ password }}" capture: - json: "$.access_token" as: "accessToken" diff --git a/high-level-tests/load-testing/scenarios/signup-and-competence-evaluation.yaml b/high-level-tests/load-testing/scenarios/signup-and-competence-evaluation.yaml index d0eafcf0e80..553f87d3b6a 100644 --- a/high-level-tests/load-testing/scenarios/signup-and-competence-evaluation.yaml +++ b/high-level-tests/load-testing/scenarios/signup-and-competence-evaluation.yaml @@ -35,7 +35,7 @@ scenarios: url: "/api/token" headers: content-type: "application/x-www-form-urlencoded" - body: "grant_type=password&scope=mon-pix&username={{ email }}&password={{ password }}" + body: "grant_type=password&username={{ email }}&password={{ password }}" capture: - json: "$.access_token" as: "accessToken" diff --git a/high-level-tests/load-testing/scenarios/signup-and-profil.yaml b/high-level-tests/load-testing/scenarios/signup-and-profil.yaml index dc3fac4c90d..669f033851a 100644 --- a/high-level-tests/load-testing/scenarios/signup-and-profil.yaml +++ b/high-level-tests/load-testing/scenarios/signup-and-profil.yaml @@ -37,7 +37,7 @@ scenarios: url: "/api/token" headers: content-type: "application/x-www-form-urlencoded" - body: "grant_type=password&scope=mon-pix&username={{ email }}&password={{ password }}" + body: "grant_type=password&username={{ email }}&password={{ password }}" capture: - json: "$.access_token" as: "accessToken"