Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TECH] Mise à jour de openid-client en version 6 (PIX-16870) #11586

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 18 additions & 47 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"node-cache": "^5.1.2",
"node-stream-zip": "^1.15.0",
"nodemailer": "^6.9.6",
"openid-client": "^5.6.4",
"openid-client": "^6.3.3",
"papaparse": "^5.3.2",
"pdf-lib": "^1.17.1",
"pg": "^8.7.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export class OidcAuthenticationServiceRegistry {
);

if (!oidcProviderService) return;
if (oidcProviderService.client) return;

await oidcProviderService.createClient();
await oidcProviderService.initializeClientConfig();

return true;
}

Expand Down Expand Up @@ -88,4 +88,10 @@ export class OidcAuthenticationServiceRegistry {
);
return true;
}

testOnly_reset() {
this.#allOidcProviderServices = null;
this.#readyOidcProviderServices = null;
this.#readyOidcProviderServicesForPixAdmin = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
import jsonwebtoken from 'jsonwebtoken';
import lodash from 'lodash';
import ms from 'ms';
import { Issuer } from 'openid-client';
import * as client from 'openid-client';

import { config } from '../../../shared/config.js';
import { OIDC_ERRORS } from '../../../shared/domain/constants.js';
Expand All @@ -23,6 +23,8 @@ const defaultSessionTemporaryStorage = temporaryStorage.withPrefix('oidc-session
export class OidcAuthenticationService {
#isReady = false;
#isReadyForPixAdmin = false;
#openIdClient;
#openIdClientConfig;

constructor(
{
Expand All @@ -47,7 +49,7 @@ export class OidcAuthenticationService {
isVisible = true,
claimMapping,
},
{ sessionTemporaryStorage = defaultSessionTemporaryStorage } = {},
{ sessionTemporaryStorage = defaultSessionTemporaryStorage, openIdClient = client } = {},
) {
this.accessTokenLifespanMs = ms(accessTokenLifespan);
this.additionalRequiredProperties = additionalRequiredProperties;
Expand All @@ -68,6 +70,7 @@ export class OidcAuthenticationService {
this.slug = slug;
this.source = source;
this.isVisible = isVisible;
this.#openIdClient = openIdClient;

claimMapping = claimMapping || DEFAULT_CLAIM_MAPPING;

Expand Down Expand Up @@ -105,20 +108,20 @@ export class OidcAuthenticationService {
return this.#isReadyForPixAdmin;
}

async createClient() {
async initializeClientConfig() {
if (this.#openIdClientConfig) return;

try {
const issuer = await Issuer.discover(this.openidConfigurationUrl);
const metadata = {
client_id: this.clientId,
client_secret: this.clientSecret,
redirect_uris: [this.redirectUri],
...this.openidClientExtraMetadata,
};

if (this.openidClientExtraMetadata) {
Object.assign(metadata, this.openidClientExtraMetadata);
}

this.client = new issuer.Client(metadata);
this.#openIdClientConfig = await this.#openIdClient.discovery(
new URL(this.openidConfigurationUrl),
this.clientId,
metadata,
);
} catch (error) {
logger.error(`OIDC Provider "${this.identityProvider}" is UNAVAILABLE: ${error}`);
}
Expand All @@ -145,10 +148,26 @@ export class OidcAuthenticationService {
}

async exchangeCodeForTokens({ code, state, iss, nonce, sessionState }) {
let tokenSet;

try {
tokenSet = await this.client.callback(this.redirectUri, { code, state, iss }, { nonce, state: sessionState });
const currentUrl = new URL(this.redirectUri);
currentUrl.searchParams.append('code', code);
currentUrl.searchParams.append('state', state);
currentUrl.searchParams.append('session_state', sessionState);

const checks = { expectedNonce: nonce, expectedState: sessionState };

const tokenResponse = await this.#openIdClient.authorizationCodeGrant(
this.#openIdClientConfig,
currentUrl,
checks,
);

return new AuthenticationSessionContent({
accessToken: tokenResponse.access_token,
expiresIn: tokenResponse.expires_in,
idToken: tokenResponse.id_token,
refreshToken: tokenResponse.refresh_token,
});
} catch (error) {
_monitorOidcError(error.message, {
data: { code, nonce, organizationName: this.organizationName, sessionState, state, iss },
Expand All @@ -157,40 +176,23 @@ export class OidcAuthenticationService {
});
throw new OidcError({ message: error.message });
}

const {
access_token: accessToken,
expires_in: expiresIn,
id_token: idToken,
refresh_token: refreshToken,
} = tokenSet;

return new AuthenticationSessionContent({
accessToken,
expiresIn,
idToken,
refreshToken,
});
}

getAuthorizationUrl() {
const state = randomUUID();
const nonce = randomUUID();
const authorizationParameters = {
nonce,
redirect_uri: this.redirectUri,
scope: this.scope,
state,
};

if (this.extraAuthorizationUrlParameters) {
Object.assign(authorizationParameters, this.extraAuthorizationUrlParameters);
}
try {
const state = randomUUID();
const nonce = randomUUID();
const parameters = {
nonce,
redirect_uri: this.redirectUri,
scope: this.scope,
state,
...this.extraAuthorizationUrlParameters,
};

let redirectTarget;
const redirectTarget = this.#openIdClient.buildAuthorizationUrl(this.#openIdClientConfig, parameters);

try {
redirectTarget = this.client.authorizationUrl(authorizationParameters);
return { redirectTarget, state, nonce };
} catch (error) {
_monitorOidcError(error.message, {
data: { organizationName: this.organizationName },
Expand All @@ -199,15 +201,13 @@ export class OidcAuthenticationService {
});
throw new OidcError({ message: error.message });
}

return { redirectTarget, state, nonce };
}

async getUserInfo({ idToken, accessToken }) {
let userInfo = jsonwebtoken.decode(idToken);

if (this.claimManager.hasMissingClaims(userInfo)) {
userInfo = await this._getUserInfoFromEndpoint({ accessToken });
userInfo = await this._getUserInfoFromEndpoint({ accessToken, expectedSubject: userInfo.sub });
}

return {
Expand Down Expand Up @@ -260,7 +260,7 @@ export class OidcAuthenticationService {
}

try {
const endSessionUrl = this.client.endSessionUrl(parameters);
const endSessionUrl = this.#openIdClient.buildEndSessionUrl(this.#openIdClientConfig, parameters);

await this.sessionTemporaryStorage.delete(key);

Expand All @@ -275,11 +275,11 @@ export class OidcAuthenticationService {
}
}

async _getUserInfoFromEndpoint({ accessToken }) {
async _getUserInfoFromEndpoint({ accessToken, expectedSubject }) {
let userInfo;

try {
userInfo = await this.client.userinfo(accessToken);
userInfo = await this.#openIdClient.fetchUserInfo(this.#openIdClientConfig, accessToken, expectedSubject);
} catch (error) {
_monitorOidcError(error.message, {
data: { organizationName: this.organizationName },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {
insertUserWithRoleSuperAdmin,
knex,
} from '../../../test-helper.js';
import { createMockedTestOidcProvider } from '../../../tooling/openid-client/openid-client-mocks.js';

describe('Acceptance | Identity Access Management | Route | Admin | oidc-provider', function () {
let server;

beforeEach(async function () {
await createMockedTestOidcProvider();
server = await createServer();
});

Expand Down
Loading