import { Test , TestingModule } from '@nestjs/testing' ; import { ConfigService } from '@fc/config' ; import { IdentityProviderAdapterEnvService } from '@fc/identity-provider-adapter-env' ; import { LoggerService } from '@fc/logger' ; import { OidcClientService } from '@fc/oidc-client' ; import { SessionCsrfService , SessionInvalidCsrfSelectIdpException , SessionService , } from '@fc/session' ; import { TrackingService } from '@fc/tracking' ; import { OidcClientController } from './oidc-client.controller' ; describe ( 'OidcClient Controller' , ( ) => { let controller ; let res ; const oidcClientServiceMock = { utils : { getAuthorizeUrl : jest . fn ( ) , wellKnownKeys : jest . fn ( ) , buildAuthorizeParameters : jest . fn ( ) , checkIdpBlacklisted : jest . fn ( ) , checkCsrfTokenValidity : jest . fn ( ) , } , } ; const loggerServiceMock = { setContext : jest . fn ( ) , verbose : jest . fn ( ) , debug : jest . fn ( ) , businessEvent : jest . fn ( ) , trace : jest . fn ( ) , } as unknown as LoggerService ; const sessionServiceMock = { set : jest . fn ( ) , get : jest . fn ( ) , } ; const sessionCsrfServiceMock = { get : jest . fn ( ) , save : jest . fn ( ) , validate : jest . fn ( ) , } ; const identityProviderServiceMock = { getById : jest . fn ( ) , } ; const trackingMock = { track : jest . fn ( ) , } ; const appConfigMock = { urlPrefix : '/api/v2' , } ; const configServiceMock = { get : ( ) => appConfigMock , } ; const stateMock = 'stateMock' ; const nonceMock = 'nonceMock' ; const providerIdMock = 'providerIdMockValue' ; beforeEach ( async ( ) => { jest . resetAllMocks ( ) ; jest . restoreAllMocks ( ) ; const module = await Test . createTestingModule ( { controllers : [ OidcClientController ] , providers : [ OidcClientService , LoggerService , SessionService , SessionCsrfService , TrackingService , ConfigService , IdentityProviderAdapterEnvService , ] , } ) . overrideProvider ( OidcClientService ) . useValue ( oidcClientServiceMock ) . overrideProvider ( LoggerService ) . useValue ( loggerServiceMock ) . overrideProvider ( SessionService ) . useValue ( sessionServiceMock ) . overrideProvider ( TrackingService ) . useValue ( trackingMock ) . overrideProvider ( ConfigService ) . useValue ( configServiceMock ) . overrideProvider ( SessionCsrfService ) . useValue ( sessionCsrfServiceMock ) . overrideProvider ( IdentityProviderAdapterEnvService ) . useValue ( identityProviderServiceMock ) . compile ( ) ; controller = module . get < OidcClientController > ( OidcClientController ) ; res = { redirect : jest . fn ( ) , } ; identityProviderServiceMock . getById . mockReturnValue ( { name : 'foo' } ) ; sessionServiceMock . get . mockResolvedValue ( { idpState : stateMock , idpNonce : nonceMock , } ) ; oidcClientServiceMock . utils . buildAuthorizeParameters . mockReturnValue ( { state : stateMock , nonce : nonceMock , scope : 'scopeMock' , providerUid : providerIdMock , acr_values : 'acrMock' , } ) ; sessionCsrfServiceMock . save . mockResolvedValueOnce ( true ) ; } ) ; it ( 'should be defined' , ( ) => { expect ( controller ) . toBeDefined ( ) ; } ) ; describe ( 'redirectToIdp()' , ( ) => { it ( 'should call oidc-client-service for retrieve authorize url' , async ( ) => { const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , claims : 'json_stringified' , nonce : nonceMock , csrfToken : 'csrfMockValue' , } ; const authorizeUrlMock = 'https://my-authentication-openid-url.com' ; oidcClientServiceMock . utils . getAuthorizeUrl . mockReturnValueOnce ( authorizeUrlMock ) ; const expectedGetAuthorizeCallParameter = { acr_values : 'eidas3' , claims : 'json_stringified' , nonce : 'nonceMock' , idpId : 'providerIdMockValue' , scope : 'openid' , state : 'stateMock' , } ; await controller . redirectToIdp ( res , body , sessionServiceMock ) ; expect ( oidcClientServiceMock . utils . getAuthorizeUrl ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . utils . getAuthorizeUrl ) . toHaveBeenCalledWith ( expectedGetAuthorizeCallParameter ) ; } ) ; it ( 'should call res.redirect() with the authorizeUrl' , async ( ) => { const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , claims : 'json_stringified' , nonce : nonceMock , csrfToken : 'csrfMockValue' , } ; const authorizeUrlMock = 'https://my-authentication-openid-url.com' ; oidcClientServiceMock . utils . getAuthorizeUrl . mockReturnValueOnce ( authorizeUrlMock ) ; await controller . redirectToIdp ( res , body , sessionServiceMock ) ; expect ( res . redirect ) . toHaveBeenCalledTimes ( 1 ) ; expect ( res . redirect ) . toHaveBeenCalledWith ( authorizeUrlMock ) ; } ) ; it ( 'should store state and nonce in session' , async ( ) => { const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , claims : 'json_stringified' , nonce : nonceMock , csrfToken : 'csrfMockValue' , } ; const authorizeUrlMock = 'https://my-authentication-openid-url.com' ; oidcClientServiceMock . utils . getAuthorizeUrl . mockReturnValueOnce ( authorizeUrlMock ) ; await controller . redirectToIdp ( res , body , sessionServiceMock ) ; expect ( sessionServiceMock . set ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionServiceMock . set ) . toHaveBeenCalledWith ( { idpId : body . providerUid , idpName : 'foo' , idpState : stateMock , idpNonce : nonceMock , } ) ; } ) ; it ( 'should resolve even if no spId are fetchable' , async ( ) => { const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , nonce : nonceMock , claims : 'any_formatted_json_string' , csrfToken : 'csrfMockValue' , } ; sessionServiceMock . get . mockImplementationOnce ( ( ) => { throw new Error ( ) ; } ) ; await controller . redirectToIdp ( res , body , sessionServiceMock ) ; expect ( res . redirect ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should throw an error because idp is blacklisted' , async ( ) => { const csrfTokenBody = 'invalidCsrfMockValue' ; const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , nonce : nonceMock , claims : 'any_formatted_json_string' , csrfToken : csrfTokenBody , } ; sessionServiceMock . get . mockReturnValueOnce ( 'spId' ) ; sessionCsrfServiceMock . validate . mockReset ( ) . mockImplementation ( ( ) => { throw new Error ( 'Une erreur technique est survenue, fermez l’onglet de votre navigateur et reconnectez-vous.' ) ; } ) ; await expect ( controller . redirectToIdp ( res , body , sessionServiceMock ) ) . rejects . toThrow ( SessionInvalidCsrfSelectIdpException ) ; } ) ; it ( 'should throw an error if the two CSRF tokens (provided in request and previously stored in session) are not the same.' , async ( ) => { const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , nonce : nonceMock , claims : 'any_formatted_json_string' , csrfToken : 'csrfMockValue' , } ; sessionServiceMock . get . mockReturnValueOnce ( 'spId' ) ; await controller . redirectToIdp ( res , body , sessionServiceMock ) ; expect ( sessionServiceMock . get ) . toHaveBeenLastCalledWith ( ) ; expect ( res . redirect ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; describe ( 'Idp blacklisted scenario for redirect to idp' , ( ) => { let isBlacklistedMock ; beforeEach ( ( ) => { isBlacklistedMock = oidcClientServiceMock . utils . checkIdpBlacklisted = jest . fn ( ) ; } ) ; it ( 'idp is blacklisted' , async ( ) => { const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , nonce : nonceMock , claims : 'any_formatted_json_string' , csrfToken : 'csrfMockValue' , } ; const errorMock = new Error ( 'New Error' ) ; sessionServiceMock . get . mockReturnValueOnce ( { spId : 'spIdValue' , } ) ; isBlacklistedMock . mockRejectedValueOnce ( errorMock ) ; await expect ( ( ) => controller . redirectToIdp ( res , body , sessionServiceMock ) ) . rejects . toThrow ( errorMock ) ; expect ( sessionServiceMock . get ) . toHaveBeenLastCalledWith ( ) ; } ) ; it ( 'idp is not blacklisted' , async ( ) => { const body = { scope : 'openid' , providerUid : providerIdMock , acr_values : 'eidas3' , nonce : nonceMock , claims : 'any_formatted_json_string' , csrfToken : 'csrfMockValue' , } ; sessionServiceMock . get . mockReturnValueOnce ( { spId : 'spIdValue' , } ) ; isBlacklistedMock . mockReturnValueOnce ( false ) ; await controller . redirectToIdp ( res , body , sessionServiceMock ) ; expect ( sessionServiceMock . get ) . toHaveBeenLastCalledWith ( ) ; expect ( res . redirect ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; } ) ; } ) ; describe ( 'getWellKnownKeys()' , ( ) => { it ( 'should call oidc-client-service for wellKnownKeys' , async ( ) => { await controller . getWellKnownKeys ( ) ; expect ( oidcClientServiceMock . utils . wellKnownKeys ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; } ) ; } ) ;