diff --git a/api/src/identity-access-management/domain/services/oidc-authentication-service.js b/api/src/identity-access-management/domain/services/oidc-authentication-service.js index 9e77b7bf613..4361ea8eb5f 100644 --- a/api/src/identity-access-management/domain/services/oidc-authentication-service.js +++ b/api/src/identity-access-management/domain/services/oidc-authentication-service.js @@ -30,7 +30,8 @@ const defaultSessionTemporaryStorage = temporaryStorage.withPrefix('oidc-session export class OidcAuthenticationService { #isReady = false; #isReadyForPixAdmin = false; - #oidcClientConfig = null; + #openIdClient; + #oidcClientConfig; // TODO: À modifier en openIdClientConfig constructor( { @@ -55,7 +56,7 @@ export class OidcAuthenticationService { isVisible = true, claimMapping, }, - { sessionTemporaryStorage = defaultSessionTemporaryStorage } = {}, + { sessionTemporaryStorage = defaultSessionTemporaryStorage, openIdClient = client } = {}, ) { this.accessTokenLifespanMs = ms(accessTokenLifespan); this.additionalRequiredProperties = additionalRequiredProperties; @@ -76,6 +77,7 @@ export class OidcAuthenticationService { this.slug = slug; this.source = source; this.isVisible = isVisible; + this.#openIdClient = openIdClient; claimMapping = claimMapping || DEFAULT_CLAIM_MAPPING; @@ -125,7 +127,7 @@ export class OidcAuthenticationService { ...this.openidClientExtraMetadata, }; - this.#oidcClientConfig = await client.discovery(this.openidConfigurationUrl, this.clientId, metadata); + this.#oidcClientConfig = await this.#openIdClient.discovery(this.openidConfigurationUrl, this.clientId, metadata); } catch (error) { logger.error(`OIDC Provider "${this.identityProvider}" is UNAVAILABLE: ${error}`); } @@ -158,7 +160,7 @@ export class OidcAuthenticationService { const checks = { nonce, state: sessionState }; const tokenEndpointParameters = { code, state, iss }; - tokenSet = await client.authorizationCodeGrant( + tokenSet = await this.#openIdClient.authorizationCodeGrant( this.#oidcClientConfig, this.redirectUri, checks, @@ -190,7 +192,7 @@ export class OidcAuthenticationService { getAuthorizationUrl() { const state = randomUUID(); - const nonce = randomUUID(); // TODO: client.randomNonce() ? + const nonce = randomUUID(); // TODO: this.#openIdClient.randomNonce() ? let redirectTarget; @@ -203,7 +205,7 @@ export class OidcAuthenticationService { ...this.extraAuthorizationUrlParameters, }; - redirectTarget = client.buildAuthorizationUrl(this.#oidcClientConfig, parameters); + redirectTarget = this.#openIdClient.buildAuthorizationUrl(this.#oidcClientConfig, parameters); } catch (error) { _monitorOidcError(error.message, { data: { organizationName: this.organizationName }, @@ -273,7 +275,7 @@ export class OidcAuthenticationService { } try { - const endSessionUrl = client.buildEndSessionUrl(this.#oidcClientConfig, parameters); + const endSessionUrl = this.#openIdClient.buildEndSessionUrl(this.#oidcClientConfig, parameters); await this.sessionTemporaryStorage.delete(key); @@ -292,11 +294,7 @@ export class OidcAuthenticationService { let userInfo; try { - userInfo = await client.fetchUserInfo( - this.#oidcClientConfig, - accessToken, - expectedSubject || client.skipSubjectCheck, - ); + userInfo = await this.#openIdClient.fetchUserInfo(this.#oidcClientConfig, accessToken, expectedSubject); } catch (error) { _monitorOidcError(error.message, { data: { organizationName: this.organizationName }, diff --git a/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service-registry_test.js b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service-registry.test.js similarity index 100% rename from api/tests/identity-access-management/unit/domain/services/oidc-authentication-service-registry_test.js rename to api/tests/identity-access-management/unit/domain/services/oidc-authentication-service-registry.test.js diff --git a/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service.test.js similarity index 85% rename from api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js rename to api/tests/identity-access-management/unit/domain/services/oidc-authentication-service.test.js index 2a3cb3213fb..83ef337c10c 100644 --- a/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js +++ b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service.test.js @@ -1,6 +1,5 @@ import jsonwebtoken from 'jsonwebtoken'; import ms from 'ms'; -import { Issuer } from 'openid-client'; import { OidcAuthenticationService } from '../../../../../src/identity-access-management/domain/services/oidc-authentication-service.js'; import { config as settings } from '../../../../../src/shared/config.js'; @@ -17,8 +16,19 @@ import { catchErr, catchErrSync, expect, sinon } from '../../../../test-helper.j const uuidV4Regex = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; +const MOCK_OPENID_CLIENT_CONFIG = Symbol('config'); + describe('Unit | Domain | Services | oidc-authentication-service', function () { + let openIdClient; + beforeEach(function () { + openIdClient = { + discovery: sinon.stub().resolves(MOCK_OPENID_CLIENT_CONFIG), + authorizationCodeGrant: sinon.stub(), + buildAuthorizationUrl: sinon.stub(), + buildEndSessionUrl: sinon.stub(), + fetchUserInfo: sinon.stub(), + }; sinon.stub(monitoringTools, 'logErrorWithCorrelationIds'); }); @@ -29,7 +39,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const args = {}; // when - const oidcAuthenticationService = new OidcAuthenticationService(args); + const oidcAuthenticationService = new OidcAuthenticationService(args, { openIdClient }); // then expect(oidcAuthenticationService.shouldCloseSession).to.be.false; @@ -44,7 +54,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const args = { claimMapping: null, claimsToStore: null }; // when - const { claimManager } = new OidcAuthenticationService(args); + const { claimManager } = new OidcAuthenticationService(args, { openIdClient }); const claims = claimManager.getMissingClaims(); // then @@ -58,7 +68,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const args = { claimMapping: { firstName: ['hello'] }, claimsToStore: null }; // when - const { claimManager } = new OidcAuthenticationService(args); + const { claimManager } = new OidcAuthenticationService(args, { openIdClient }); const claims = claimManager.getMissingClaims(); // then @@ -72,7 +82,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const args = { claimMapping: { firstName: ['hello'] }, claimsToStore: 'employeeNumber,studentGroup' }; // when - const { claimManager } = new OidcAuthenticationService(args); + const { claimManager } = new OidcAuthenticationService(args, { openIdClient }); const claims = claimManager.getMissingClaims(); // then @@ -85,16 +95,19 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { context('when enabled in config', function () { it('returns true', function () { // given - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId: 'anId', - clientSecret: 'aSecret', - additionalRequiredProperties: { - aProperty: 'a property value', + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId: 'anId', + clientSecret: 'aSecret', + additionalRequiredProperties: { + aProperty: 'a property value', + }, + enabled: true, + openidConfigurationUrl: 'https://example.net/.well-known/openid-configuration', + redirectUri: 'https://example.net/connexion/redirect', }, - enabled: true, - openidConfigurationUrl: 'https://example.net/.well-known/openid-configuration', - redirectUri: 'https://example.net/connexion/redirect', - }); + { openIdClient }, + ); // when const isOidcAuthenticationServiceReady = oidcAuthenticationService.isReady; @@ -107,7 +120,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { context('when not enabled in config', function () { it('returns false', function () { // given - const oidcAuthenticationService = new OidcAuthenticationService({}); + const oidcAuthenticationService = new OidcAuthenticationService({}, { openIdClient }); // when const result = oidcAuthenticationService.isReady; @@ -131,7 +144,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { .withArgs(payload, settings.authentication.secret, jwtOptions) .returns(accessToken); - const oidcAuthenticationService = new OidcAuthenticationService(settings.oidcExampleNet); + const oidcAuthenticationService = new OidcAuthenticationService(settings.oidcExampleNet, { openIdClient }); // when const result = oidcAuthenticationService.createAccessToken({ userId, audience }); @@ -147,7 +160,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { // given const userInfo = {}; const identityProvider = 'genericOidcProviderCode'; - const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider }); + const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider }, { openIdClient }); // when const result = oidcAuthenticationService.createAuthenticationComplement({ userInfo }); @@ -166,7 +179,10 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const claimsToStoreWithValues = { family_name, given_name }; const userInfo = { ...claimsToStoreWithValues }; const identityProvider = 'genericOidcProviderCode'; - const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider, claimsToStore }); + const oidcAuthenticationService = new OidcAuthenticationService( + { identityProvider, claimsToStore }, + { openIdClient }, + ); // when const result = oidcAuthenticationService.createAuthenticationComplement({ userInfo }); @@ -188,6 +204,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const oidcAuthenticationService = new OidcAuthenticationService(settings.oidcExampleNet, { sessionTemporaryStorage, + openIdClient, }); await oidcAuthenticationService.initializeClientConfig(); @@ -211,13 +228,11 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { }; const postLogoutRedirectUriEncoded = encodeURIComponent(settings.oidcExampleNet.postLogoutRedirectUri); const endSessionUrl = `https://example.net/logout?post_logout_redirect_uri=${postLogoutRedirectUriEncoded}&id_token_hint=some_dummy_id_token`; - const clientInstance = { endSessionUrl: sinon.stub().resolves(endSessionUrl) }; - const Client = sinon.stub().returns(clientInstance); - - sinon.stub(Issuer, 'discover').resolves({ Client }); + openIdClient.buildEndSessionUrl.resolves(endSessionUrl); const oidcAuthenticationService = new OidcAuthenticationService(settings.oidcExampleNet, { sessionTemporaryStorage, + openIdClient, }); await oidcAuthenticationService.initializeClientConfig(); @@ -225,7 +240,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const result = await oidcAuthenticationService.getRedirectLogoutUrl({ userId, logoutUrlUUID }); // then - expect(clientInstance.endSessionUrl).to.have.been.calledWith({ + expect(openIdClient.buildEndSessionUrl).to.have.been.calledWith(MOCK_OPENID_CLIENT_CONFIG, { id_token_hint: idToken, post_logout_redirect_uri: settings.oidcExampleNet.postLogoutRedirectUri, }); @@ -245,14 +260,11 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { delete: sinon.stub().resolves(), }; const errorThrown = new Error('Fails to generate endSessionUrl'); - - const clientInstance = { endSessionUrl: sinon.stub().throws(errorThrown) }; - const Client = sinon.stub().returns(clientInstance); - - sinon.stub(Issuer, 'discover').resolves({ Client }); + openIdClient.buildEndSessionUrl.throws(errorThrown); const oidcAuthenticationService = new OidcAuthenticationService(settings.oidcExampleNet, { sessionTemporaryStorage, + openIdClient, }); await oidcAuthenticationService.initializeClientConfig(); @@ -298,24 +310,23 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { expiresIn, refreshToken, }); - const tokenSet = { + openIdClient.authorizationCodeGrant.resolves({ access_token: accessToken, expires_in: expiresIn, id_token: idToken, refresh_token: refreshToken, - }; - const clientInstance = { callback: sinon.stub().resolves(tokenSet) }; - const Client = sinon.stub().returns(clientInstance); - - sinon.stub(Issuer, 'discover').resolves({ Client }); - - const oidcAuthenticationService = new OidcAuthenticationService({ - clientSecret, - clientId, - redirectUri, - openidConfigurationUrl, - tokenUrl, }); + + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientSecret, + clientId, + redirectUri, + openidConfigurationUrl, + tokenUrl, + }, + { openIdClient }, + ); await oidcAuthenticationService.initializeClientConfig(); // when @@ -347,19 +358,19 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { errorThrown.error_uri = '/oauth2/token'; errorThrown.response = 'api call response here'; - const clientInstance = { callback: sinon.stub().rejects(errorThrown) }; - const Client = sinon.stub().returns(clientInstance); - - sinon.stub(Issuer, 'discover').resolves({ Client }); + openIdClient.authorizationCodeGrant.rejects(errorThrown); - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - identityProvider, - redirectUri, - openidConfigurationUrl, - organizationName: 'Oidc Example', - }); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + identityProvider, + redirectUri, + openidConfigurationUrl, + organizationName: 'Oidc Example', + }, + { openIdClient }, + ); await oidcAuthenticationService.initializeClientConfig(); // when @@ -401,16 +412,17 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const clientSecret = 'OIDC_CLIENT_SECRET'; const redirectUri = 'https://example.org/please-redirect-to-me'; - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - redirectUri, - }); + openIdClient.buildAuthorizationUrl.returns(''); - const clientInstance = { authorizationUrl: sinon.stub().returns('') }; - const Client = sinon.stub().returns(clientInstance); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + redirectUri, + }, + { openIdClient }, + ); - sinon.stub(Issuer, 'discover').resolves({ Client }); await oidcAuthenticationService.initializeClientConfig(); // when @@ -420,7 +432,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { expect(nonce).to.match(uuidV4Regex); expect(state).to.match(uuidV4Regex); - expect(clientInstance.authorizationUrl).to.have.been.calledWithExactly({ + expect(openIdClient.buildAuthorizationUrl).to.have.been.calledWithExactly(MOCK_OPENID_CLIENT_CONFIG, { nonce, redirect_uri: 'https://example.org/please-redirect-to-me', scope: 'openid profile', @@ -438,19 +450,19 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const openidConfigurationUrl = Symbol('openidConfigurationUrl'); const errorThrown = new Error('Fails to generate authorization url'); - const clientInstance = { authorizationUrl: sinon.stub().throws(errorThrown) }; - const Client = sinon.stub().returns(clientInstance); + openIdClient.buildAuthorizationUrl.throws(errorThrown); - sinon.stub(Issuer, 'discover').resolves({ Client }); - - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - identityProvider, - redirectUri, - openidConfigurationUrl, - organizationName: 'Oidc Example', - }); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + identityProvider, + redirectUri, + openidConfigurationUrl, + organizationName: 'Oidc Example', + }, + { openIdClient }, + ); await oidcAuthenticationService.initializeClientConfig(); // when @@ -484,7 +496,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { 'secret', ); - const oidcAuthenticationService = new OidcAuthenticationService({}); + const oidcAuthenticationService = new OidcAuthenticationService({}, { openIdClient }); // when const result = await oidcAuthenticationService.getUserInfo({ @@ -514,7 +526,10 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { 'secret', ); - const oidcAuthenticationService = new OidcAuthenticationService({ claimsToStore: 'employeeNumber' }); + const oidcAuthenticationService = new OidcAuthenticationService( + { claimsToStore: 'employeeNumber' }, + { openIdClient }, + ); // when const result = await oidcAuthenticationService.getUserInfo({ @@ -550,7 +565,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { lastName: ['usual_name'], externalIdentityId: ['sub'], }; - const oidcAuthenticationService = new OidcAuthenticationService({ claimMapping }); + const oidcAuthenticationService = new OidcAuthenticationService({ claimMapping }, { openIdClient }); // when const result = await oidcAuthenticationService.getUserInfo({ @@ -586,10 +601,10 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { lastName: ['usual_name'], externalIdentityId: ['sub'], }; - const oidcAuthenticationService = new OidcAuthenticationService({ - claimMapping, - claimsToStore: 'employeeNumber', - }); + const oidcAuthenticationService = new OidcAuthenticationService( + { claimMapping, claimsToStore: 'employeeNumber' }, + { openIdClient }, + ); // when const result = await oidcAuthenticationService.getUserInfo({ @@ -618,18 +633,16 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { 'secret', ); - const oidcAuthenticationService = new OidcAuthenticationService({}); + const oidcAuthenticationService = new OidcAuthenticationService({}, { openIdClient }); sinon.stub(oidcAuthenticationService, '_getUserInfoFromEndpoint').resolves({}); // when - await oidcAuthenticationService.getUserInfo({ - idToken, - accessToken: 'accessToken', - }); + await oidcAuthenticationService.getUserInfo({ idToken, accessToken: 'accessToken' }); // then expect(oidcAuthenticationService._getUserInfoFromEndpoint).to.have.been.calledOnceWithExactly({ accessToken: 'accessToken', + expectedSubject: 'sub-id', }); }); }); @@ -647,7 +660,10 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { 'secret', ); - const oidcAuthenticationService = new OidcAuthenticationService({ claimsToStore: 'employeeNumber' }); + const oidcAuthenticationService = new OidcAuthenticationService( + { claimsToStore: 'employeeNumber' }, + { openIdClient }, + ); sinon.stub(oidcAuthenticationService, '_getUserInfoFromEndpoint').resolves({}); // when @@ -659,6 +675,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { // then expect(oidcAuthenticationService._getUserInfoFromEndpoint).to.have.been.calledOnceWithExactly({ accessToken: 'accessToken', + expectedSubject: 'sub-id', }); }); }); @@ -671,31 +688,37 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const clientSecret = 'OIDC_CLIENT_SECRET'; const redirectUri = 'https://example.org/please-redirect-to-me'; - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - redirectUri, + openIdClient.fetchUserInfo.returns({ + sub: 'sub-id', + given_name: 'givenName', + family_name: 'familyName', }); - const clientInstance = { - userinfo: sinon.stub().resolves({ - sub: 'sub-id', - given_name: 'givenName', - family_name: 'familyName', - }), - }; - const Client = sinon.stub().returns(clientInstance); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + redirectUri, + }, + { openIdClient }, + ); - sinon.stub(Issuer, 'discover').resolves({ Client }); await oidcAuthenticationService.initializeClientConfig(); const accessToken = 'thisIsSerializedInformation'; // when - const pickedUserInfo = await oidcAuthenticationService._getUserInfoFromEndpoint({ accessToken }); + const pickedUserInfo = await oidcAuthenticationService._getUserInfoFromEndpoint({ + accessToken, + expectedSubject: 'sub-id', + }); // then - expect(clientInstance.userinfo).to.have.been.calledOnceWithExactly(accessToken); + expect(openIdClient.fetchUserInfo).to.have.been.calledOnceWithExactly( + MOCK_OPENID_CLIENT_CONFIG, + accessToken, + 'sub-id', + ); expect(pickedUserInfo).to.deep.equal({ sub: 'sub-id', given_name: 'givenName', @@ -712,19 +735,19 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const openidConfigurationUrl = Symbol('openidConfigurationUrl'); const errorThrown = new Error('Fails to get user info'); - const clientInstance = { userinfo: sinon.stub().rejects(errorThrown) }; - const Client = sinon.stub().returns(clientInstance); + openIdClient.fetchUserInfo.rejects(errorThrown); - sinon.stub(Issuer, 'discover').resolves({ Client }); - - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - identityProvider, - redirectUri, - openidConfigurationUrl, - organizationName: 'Oidc Example', - }); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + identityProvider, + redirectUri, + openidConfigurationUrl, + organizationName: 'Oidc Example', + }, + { openIdClient }, + ); await oidcAuthenticationService.initializeClientConfig(); // when @@ -752,23 +775,22 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const redirectUri = 'https://example.org/please-redirect-to-me'; const organizationName = 'Example'; - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - redirectUri, - organizationName, + openIdClient.fetchUserInfo.returns({ + sub: 'sub-id', + given_name: 'givenName', + family_name: undefined, }); - const clientInstance = { - userinfo: sinon.stub().resolves({ - sub: 'sub-id', - given_name: 'givenName', - family_name: undefined, - }), - }; - const Client = sinon.stub().returns(clientInstance); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + redirectUri, + organizationName, + }, + { openIdClient }, + ); - sinon.stub(Issuer, 'discover').resolves({ Client }); await oidcAuthenticationService.initializeClientConfig(); const accessToken = 'thisIsSerializedInformation'; @@ -778,9 +800,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const error = await catchErr( oidcAuthenticationService._getUserInfoFromEndpoint, oidcAuthenticationService, - )({ - accessToken, - }); + )({ accessToken, expectedSubject: 'sub-id' }); // then expect(error).to.be.instanceOf(OidcMissingFieldsError); @@ -811,25 +831,23 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const redirectUri = 'https://example.org/please-redirect-to-me'; const organizationName = 'Example'; - const oidcAuthenticationService = new OidcAuthenticationService({ - claimsToStore: 'population', - clientId, - clientSecret, - redirectUri, - organizationName, + openIdClient.fetchUserInfo.returns({ + sub: 'sub-id', + given_name: 'givenName', + family_name: 'familyName', + population: '', }); - const clientInstance = { - userinfo: sinon.stub().resolves({ - sub: 'sub-id', - given_name: 'givenName', - family_name: 'familyName', - population: '', - }), - }; - const Client = sinon.stub().returns(clientInstance); - - sinon.stub(Issuer, 'discover').resolves({ Client }); + const oidcAuthenticationService = new OidcAuthenticationService( + { + claimsToStore: 'population', + clientId, + clientSecret, + redirectUri, + organizationName, + }, + { openIdClient }, + ); await oidcAuthenticationService.initializeClientConfig(); const accessToken = 'thisIsSerializedInformation'; @@ -839,9 +857,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const error = await catchErr( oidcAuthenticationService._getUserInfoFromEndpoint, oidcAuthenticationService, - )({ - accessToken, - }); + )({ accessToken, expectedSubject: 'sub-id' }); // then expect(error).to.be.instanceOf(OidcMissingFieldsError); @@ -899,7 +915,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { externalIdentifier: externalIdentityId, userId, }); - const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider }); + const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider }, { openIdClient }); // when const result = await oidcAuthenticationService.createUserAccount({ @@ -935,7 +951,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { externalIdentifier: externalIdentityId, userId, }); - const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider }); + const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider }, { openIdClient }); // when await oidcAuthenticationService.createUserAccount({ @@ -974,7 +990,10 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { externalIdentifier: externalIdentityId, userId, }); - const oidcAuthenticationService = new OidcAuthenticationService({ identityProvider, claimsToStore }); + const oidcAuthenticationService = new OidcAuthenticationService( + { identityProvider, claimsToStore }, + { openIdClient }, + ); // when await oidcAuthenticationService.createUserAccount({ @@ -1001,26 +1020,23 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const identityProvider = Symbol('identityProvider'); const redirectUri = Symbol('redirectUri'); const openidConfigurationUrl = Symbol('openidConfigurationUrl'); - const Client = sinon.spy(); - - sinon.stub(Issuer, 'discover').resolves({ Client }); - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - identityProvider, - redirectUri, - openidConfigurationUrl, - }); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + identityProvider, + redirectUri, + openidConfigurationUrl, + }, + { openIdClient }, + ); // when await oidcAuthenticationService.initializeClientConfig(); // then - expect(Issuer.discover).to.have.been.calledWithExactly(openidConfigurationUrl); - expect(Client).to.have.been.calledWithNew; - expect(Client).to.have.been.calledWithExactly({ - client_id: clientId, + expect(openIdClient.discovery).to.have.been.calledWithExactly(openidConfigurationUrl, clientId, { client_secret: clientSecret, redirect_uris: [redirectUri], }); @@ -1034,27 +1050,24 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const redirectUri = Symbol('redirectUri'); const openidConfigurationUrl = Symbol('openidConfigurationUrl'); const openidClientExtraMetadata = { token_endpoint_auth_method: 'client_secret_post' }; - const Client = sinon.spy(); - - sinon.stub(Issuer, 'discover').resolves({ Client }); - const oidcAuthenticationService = new OidcAuthenticationService({ - clientId, - clientSecret, - identityProvider, - redirectUri, - openidConfigurationUrl, - openidClientExtraMetadata, - }); + const oidcAuthenticationService = new OidcAuthenticationService( + { + clientId, + clientSecret, + identityProvider, + redirectUri, + openidConfigurationUrl, + openidClientExtraMetadata, + }, + { openIdClient }, + ); // when await oidcAuthenticationService.initializeClientConfig(); // then - expect(Issuer.discover).to.have.been.calledWithExactly(openidConfigurationUrl); - expect(Client).to.have.been.calledWithNew; - expect(Client).to.have.been.calledWithExactly({ - client_id: clientId, + expect(openIdClient.discovery).to.have.been.calledWithExactly(openidConfigurationUrl, clientId, { client_secret: clientSecret, redirect_uris: [redirectUri], token_endpoint_auth_method: 'client_secret_post', diff --git a/api/tests/identity-access-management/unit/domain/services/pix-authentication-service_test.js b/api/tests/identity-access-management/unit/domain/services/pix-authentication-service.test.js similarity index 100% rename from api/tests/identity-access-management/unit/domain/services/pix-authentication-service_test.js rename to api/tests/identity-access-management/unit/domain/services/pix-authentication-service.test.js diff --git a/api/tests/identity-access-management/unit/domain/services/pole-emploi-oidc-authentication-service_test.js b/api/tests/identity-access-management/unit/domain/services/pole-emploi-oidc-authentication-service.test.js similarity index 58% rename from api/tests/identity-access-management/unit/domain/services/pole-emploi-oidc-authentication-service_test.js rename to api/tests/identity-access-management/unit/domain/services/pole-emploi-oidc-authentication-service.test.js index 130419332ed..a3c136f126e 100644 --- a/api/tests/identity-access-management/unit/domain/services/pole-emploi-oidc-authentication-service_test.js +++ b/api/tests/identity-access-management/unit/domain/services/pole-emploi-oidc-authentication-service.test.js @@ -1,24 +1,37 @@ -import { Issuer } from 'openid-client'; - import { AuthenticationMethod } from '../../../../../src/identity-access-management/domain/models/AuthenticationMethod.js'; import { PoleEmploiOidcAuthenticationService } from '../../../../../src/identity-access-management/domain/services/pole-emploi-oidc-authentication-service.js'; import { config as settings } from '../../../../../src/shared/config.js'; import { expect, sinon } from '../../../../test-helper.js'; describe('Unit | Identity Access Management | Domain | Services | pole-emploi-oidc-authentication-service', function () { + let openIdClient; + + beforeEach(function () { + openIdClient = { + discovery: sinon.stub().resolves(Symbol('config')), + authorizationCodeGrant: sinon.stub(), + buildAuthorizationUrl: sinon.stub(), + buildEndSessionUrl: sinon.stub(), + fetchUserInfo: sinon.stub(), + }; + }); + describe('#constructor', function () { describe('when additionalRequiredProperties is not defined', function () { it('is not ready', async function () { // when - const oidcAuthenticationService = new PoleEmploiOidcAuthenticationService({ - ...settings.oidcExampleNet, - openidClientExtraMetadata: { token_endpoint_auth_method: 'client_secret_post' }, - identityProvider: 'POLE_EMPLOI', - organizationName: 'France Travail', - shouldCloseSession: true, - slug: 'pole-emploi', - source: 'pole_emploi_connect', - }); + const oidcAuthenticationService = new PoleEmploiOidcAuthenticationService( + { + ...settings.oidcExampleNet, + openidClientExtraMetadata: { token_endpoint_auth_method: 'client_secret_post' }, + identityProvider: 'POLE_EMPLOI', + organizationName: 'France Travail', + shouldCloseSession: true, + slug: 'pole-emploi', + source: 'pole_emploi_connect', + }, + { openIdClient }, + ); // then expect(oidcAuthenticationService.isReady).to.be.false; @@ -29,36 +42,35 @@ describe('Unit | Identity Access Management | Domain | Services | pole-emploi-oi describe('#initializeClientConfig', function () { it('creates an openid client config with extra metadata', async function () { // given - const Client = sinon.spy(); - - sinon.stub(Issuer, 'discover').resolves({ Client }); sinon.stub(settings, 'poleEmploi').value(settings.oidcExampleNet); - const poleEmploiOidcAuthenticationService = new PoleEmploiOidcAuthenticationService({ - ...settings.oidcExampleNet, - additionalRequiredProperties: { logoutUrl: '', afterLogoutUrl: '', sendingUrl: '' }, - openidClientExtraMetadata: { token_endpoint_auth_method: 'client_secret_post' }, - identityProvider: 'POLE_EMPLOI', - organizationName: 'France Travail', - shouldCloseSession: true, - slug: 'pole-emploi', - source: 'pole_emploi_connect', - }); + const poleEmploiOidcAuthenticationService = new PoleEmploiOidcAuthenticationService( + { + ...settings.oidcExampleNet, + additionalRequiredProperties: { logoutUrl: '', afterLogoutUrl: '', sendingUrl: '' }, + openidClientExtraMetadata: { token_endpoint_auth_method: 'client_secret_post' }, + identityProvider: 'POLE_EMPLOI', + organizationName: 'France Travail', + shouldCloseSession: true, + slug: 'pole-emploi', + source: 'pole_emploi_connect', + }, + { openIdClient }, + ); // when await poleEmploiOidcAuthenticationService.initializeClientConfig(); // then - expect(Issuer.discover).to.have.been.calledWithExactly( + expect(openIdClient.discovery).to.have.been.calledWithExactly( 'https://oidc.example.net/.well-known/openid-configuration', + 'client', + { + client_secret: 'secret', + redirect_uris: ['https://app.dev.pix.local/connexion/oidc-example-net'], + token_endpoint_auth_method: 'client_secret_post', + }, ); - expect(Client).to.have.been.calledWithNew; - expect(Client).to.have.been.calledWithExactly({ - client_id: 'client', - client_secret: 'secret', - redirect_uris: ['https://app.dev.pix.local/connexion/oidc-example-net'], - token_endpoint_auth_method: 'client_secret_post', - }); }); }); @@ -71,16 +83,19 @@ describe('Unit | Identity Access Management | Domain | Services | pole-emploi-oi expiresIn: 10, refreshToken: 'refreshToken', }; - const poleEmploiOidcAuthenticationService = new PoleEmploiOidcAuthenticationService({ - ...settings.oidcExampleNet, - additionalRequiredProperties: { logoutUrl: '', afterLogoutUrl: '', sendingUrl: '' }, - openidClientExtraMetadata: { token_endpoint_auth_method: 'client_secret_post' }, - identityProvider: 'POLE_EMPLOI', - organizationName: 'France Travail', - shouldCloseSession: true, - slug: 'pole-emploi', - source: 'pole_emploi_connect', - }); + const poleEmploiOidcAuthenticationService = new PoleEmploiOidcAuthenticationService( + { + ...settings.oidcExampleNet, + additionalRequiredProperties: { logoutUrl: '', afterLogoutUrl: '', sendingUrl: '' }, + openidClientExtraMetadata: { token_endpoint_auth_method: 'client_secret_post' }, + identityProvider: 'POLE_EMPLOI', + organizationName: 'France Travail', + shouldCloseSession: true, + slug: 'pole-emploi', + source: 'pole_emploi_connect', + }, + { openIdClient }, + ); // when const result = poleEmploiOidcAuthenticationService.createAuthenticationComplement({ sessionContent });