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_test.js b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js index 2a3cb3213fb..df6a0020c27 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'); }); @@ -211,13 +221,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 +233,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, }); @@ -246,13 +254,9 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { }; 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 }); - const oidcAuthenticationService = new OidcAuthenticationService(settings.oidcExampleNet, { sessionTemporaryStorage, + openIdClient, }); await oidcAuthenticationService.initializeClientConfig(); 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 index 130419332ed..d776cc542fa 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,4 +1,4 @@ -import { Issuer } from 'openid-client'; +import * as client 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'; @@ -29,9 +29,7 @@ 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(client, 'discovery').resolves(); sinon.stub(settings, 'poleEmploi').value(settings.oidcExampleNet); const poleEmploiOidcAuthenticationService = new PoleEmploiOidcAuthenticationService({ @@ -49,16 +47,15 @@ describe('Unit | Identity Access Management | Domain | Services | pole-emploi-oi await poleEmploiOidcAuthenticationService.initializeClientConfig(); // then - expect(Issuer.discover).to.have.been.calledWithExactly( + expect(client.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', - }); }); });