Skip to content

Commit

Permalink
feat: allow RP-Initiated Logout 1.0 feature to be disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Aug 12, 2020
1 parent aa62927 commit a2ef044
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 92 deletions.
135 changes: 77 additions & 58 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,8 @@ location / {
- [httpOptions](#httpoptions)
- [interactions ❗](#interactions)
- [issueRefreshToken](#issuerefreshtoken)
- [logoutSource](#logoutsource)
- [pairwiseIdentifier](#pairwiseidentifier)
- [pkce](#pkce)
- [postLogoutSuccessSource](#postlogoutsuccesssource)
- [renderError](#rendererror)
- [responseTypes](#responsetypes)
- [rotateRefreshToken](#rotaterefreshtoken)
Expand Down Expand Up @@ -1725,6 +1723,83 @@ _**default value**_:
}
```

### features.rpInitiatedLogout

[RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0-01.html)

Enables RP-Initiated Logout features


_**default value**_:
```js
{
enabled: true,
logoutSource: [AsyncFunction: logoutSource], // see expanded details below
postLogoutSuccessSource: [AsyncFunction: postLogoutSuccessSource] // see expanded details below
}
```

<details><summary>(Click to expand) features.rpInitiatedLogout options details</summary><br>


#### logoutSource

HTML source rendered when session management feature renders a confirmation prompt for the User-Agent.


_**default value**_:
```js
async function logoutSource(ctx, form) {
// @param ctx - koa request context
// @param form - form source (id="op.logoutForm") to be embedded in the page and submitted by
// the End-User
ctx.body = `<!DOCTYPE html>
<head>
<title>Logout Request</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
<h1>Do you want to sign-out from ${ctx.host}?</h1>
${form}
<button autofocus type="submit" form="op.logoutForm" value="yes" name="logout">Yes, sign me out</button>
<button type="submit" form="op.logoutForm">No, stay signed in</button>
</div>
</body>
</html>`;
}
```

#### postLogoutSuccessSource

HTML source rendered when session management feature concludes a logout but there was no `post_logout_redirect_uri` provided by the client.


_**default value**_:
```js
async function postLogoutSuccessSource(ctx) {
// @param ctx - koa request context
const {
clientId, clientName, clientUri, initiateLoginUri, logoUri, policyUri, tosUri,
} = ctx.oidc.client || {}; // client is defined if the user chose to stay logged in with the OP
const display = clientName || clientId;
ctx.body = `<!DOCTYPE html>
<head>
<title>Sign-out Success</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
<h1>Sign-out Success</h1>
<p>Your sign-out ${display ? `with ${display}` : ''} was successful.</p>
</div>
</body>
</html>`;
}
```

</details>

### features.sessionManagement

[Session Management 1.0 - draft 30](https://openid.net/specs/openid-connect-session-1_0-30.html)
Expand Down Expand Up @@ -2793,34 +2868,6 @@ async issueRefreshToken(ctx, client, code) {
```
</details>

### logoutSource

HTML source rendered when session management feature renders a confirmation prompt for the User-Agent.


_**default value**_:
```js
async function logoutSource(ctx, form) {
// @param ctx - koa request context
// @param form - form source (id="op.logoutForm") to be embedded in the page and submitted by
// the End-User
ctx.body = `<!DOCTYPE html>
<head>
<title>Logout Request</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
<h1>Do you want to sign-out from ${ctx.host}?</h1>
${form}
<button autofocus type="submit" form="op.logoutForm" value="yes" name="logout">Yes, sign me out</button>
<button type="submit" form="op.logoutForm">No, stay signed in</button>
</div>
</body>
</html>`;
}
```

### pairwiseIdentifier

Function used by the OP when resolving pairwise ID Token and Userinfo sub claim values. See [Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg)
Expand Down Expand Up @@ -2872,34 +2919,6 @@ function pkceRequired(ctx, client) {
}
```

### postLogoutSuccessSource

HTML source rendered when session management feature concludes a logout but there was no `post_logout_redirect_uri` provided by the client.


_**default value**_:
```js
async function postLogoutSuccessSource(ctx) {
// @param ctx - koa request context
const {
clientId, clientName, clientUri, initiateLoginUri, logoUri, policyUri, tosUri,
} = ctx.oidc.client || {}; // client is defined if the user chose to stay logged in with the OP
const display = clientName || clientId;
ctx.body = `<!DOCTYPE html>
<head>
<title>Sign-out Success</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
<h1>Sign-out Success</h1>
<p>Your sign-out ${display ? `with ${display}` : ''} was successful.</p>
</div>
</body>
</html>`;
}
```

### renderError

Function used to present errors to the User-Agent
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/discovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = function discovery(ctx, next) {
claims_parameter_supported: features.claimsParameter.enabled,
claims_supported: [...config.claimsSupported],
code_challenge_methods_supported: config.pkce.methods,
end_session_endpoint: ctx.oidc.urlFor('end_session'),
end_session_endpoint: features.rpInitiatedLogout.enabled ? ctx.oidc.urlFor('end_session') : undefined,
check_session_iframe: features.sessionManagement.enabled ? ctx.oidc.urlFor('check_session') : undefined,
grant_types_supported: [...config.grantTypes],
id_token_signing_alg_values_supported: config.idTokenSigningAlgValues,
Expand Down
4 changes: 2 additions & 2 deletions lib/actions/end_session.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ module.exports = {
ctx.status = 200;

const formHtml = `<form id="op.logoutForm" method="post" action="${action}"><input type="hidden" name="xsrf" value="${secret}"/></form>`;
await instance(ctx.oidc.provider).configuration('logoutSource')(ctx, formHtml);
await instance(ctx.oidc.provider).configuration('features.rpInitiatedLogout.logoutSource')(ctx, formHtml);
} else {
await formPost(ctx, action, {
xsrf: secret,
Expand Down Expand Up @@ -274,7 +274,7 @@ module.exports = {
}
ctx.oidc.entity('Client', client);
}
await instance(ctx.oidc.provider).configuration('postLogoutSuccessSource')(ctx);
await instance(ctx.oidc.provider).configuration('features.rpInitiatedLogout.postLogoutSuccessSource')(ctx);
},
],
};
20 changes: 13 additions & 7 deletions lib/helpers/client_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ module.exports = function getSchema(provider) {
}
}

if (features.rpInitiatedLogout.enabled) {
RECOGNIZED_METADATA.push('post_logout_redirect_uris');
}

if (features.backchannelLogout.enabled) {
RECOGNIZED_METADATA.push('backchannel_logout_session_required');
RECOGNIZED_METADATA.push('backchannel_logout_uri');
Expand Down Expand Up @@ -483,13 +487,15 @@ module.exports = function getSchema(provider) {
}

postLogoutRedirectUris() {
this.post_logout_redirect_uris.forEach((uri) => {
try {
new url.URL(uri); // eslint-disable-line no-new
} catch (err) {
this.invalidate('post_logout_redirect_uris must only contain uris');
}
});
if (this.post_logout_redirect_uris) {
this.post_logout_redirect_uris.forEach((uri) => {
try {
new url.URL(uri); // eslint-disable-line no-new
} catch (err) {
this.invalidate('post_logout_redirect_uris must only contain uris');
}
});
}
}

webMessageUris() {
Expand Down
27 changes: 27 additions & 0 deletions lib/helpers/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Configuration {
this.collectGrantTypes();
this.checkSubjectTypes();
this.checkPkceMethods();
this.checkRpInitiatedLogoutHelpers();
this.checkDependantFeatures();
this.checkDeviceFlow();
this.checkAuthMethods();
Expand Down Expand Up @@ -359,6 +360,20 @@ class Configuration {
});
}

checkRpInitiatedLogoutHelpers() {
if (this.postLogoutSuccessSource) {
this.postLogoutSuccessSourceDeprecationNotice();
this.features.rpInitiatedLogout.postLogoutSuccessSource = this.postLogoutSuccessSource;
delete this.postLogoutSuccessSource;
}

if (this.logoutSource) {
this.logoutSourceDeprecationNotice();
this.features.rpInitiatedLogout.logoutSource = this.logoutSource;
delete this.logoutSource;
}
}

checkPkceMethods() {
if (this.pkceMethods) {
this.pkceMethodsDeprecationNotice();
Expand Down Expand Up @@ -572,4 +587,16 @@ Configuration.prototype.pkceMethodsDeprecationNotice = deprecate(
`pkceMethods is deprecated, use pkce.methods for configuring it instead, see ${docs('pkce')}`,
);

Configuration.prototype.postLogoutSuccessSourceDeprecationNotice = deprecate(
/* istanbul ignore next */
() => {},
`postLogoutSuccessSourceDeprecationNotice is deprecated, use features.rpInitiatedLogout.postLogoutSuccessSourceDeprecationNotice for configuring it instead, see ${docs('featuresrpinitiatedlogout')}`,
);

Configuration.prototype.logoutSourceDeprecationNotice = deprecate(
/* istanbul ignore next */
() => {},
`logoutSourceDeprecationNotice is deprecated, use features.rpInitiatedLogout.logoutSourceDeprecationNotice for configuring it instead, see ${docs('featuresrpinitiatedlogout')}`,
);

module.exports = Configuration;
48 changes: 30 additions & 18 deletions lib/helpers/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ function extraClientMetadataValidator(key, value, metadata, ctx) { // eslint-dis

async function postLogoutSuccessSource(ctx) {
// @param ctx - koa request context
shouldChange('postLogoutSuccessSource', 'customize the look of the default post logout success page');
shouldChange('features.rpInitiatedLogout.postLogoutSuccessSource', 'customize the look of the default post logout success page');
const {
clientId, clientName, clientUri, initiateLoginUri, logoUri, policyUri, tosUri, // eslint-disable-line no-unused-vars, max-len
} = ctx.oidc.client || {}; // client is defined if the user chose to stay logged in with the OP
Expand Down Expand Up @@ -349,7 +349,7 @@ async function logoutSource(ctx, form) {
// @param ctx - koa request context
// @param form - form source (id="op.logoutForm") to be embedded in the page and submitted by
// the End-User
shouldChange('logoutSource', 'customize the look of the logout page');
shouldChange('features.rpInitiatedLogout.logoutSource', 'customize the look of the logout page');
ctx.body = `<!DOCTYPE html>
<head>
<meta charset="utf-8">
Expand Down Expand Up @@ -1055,6 +1055,32 @@ function getDefaults() {
*/
fapiRW: { enabled: false },

/*
* features.rpInitiatedLogout
*
* title: [RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0-01.html)
*
* description: Enables RP-Initiated Logout features
*/
rpInitiatedLogout: {
enabled: true,

/*
* features.rpInitiatedLogout.postLogoutSuccessSource
*
* description: HTML source rendered when session management feature concludes a logout but there
* was no `post_logout_redirect_uri` provided by the client.
*/
postLogoutSuccessSource,

/*
* features.rpInitiatedLogout.logoutSource
*
* description: HTML source rendered when session management feature renders a confirmation
* prompt for the User-Agent.
*/
logoutSource,
},
/*
* features.frontchannelLogout
*
Expand Down Expand Up @@ -2100,22 +2126,6 @@ function getDefaults() {
validator: extraClientMetadataValidator,
},

/*
* postLogoutSuccessSource
*
* description: HTML source rendered when session management feature concludes a logout but there
* was no `post_logout_redirect_uri` provided by the client.
*/
postLogoutSuccessSource,

/*
* logoutSource
*
* description: HTML source rendered when session management feature renders a confirmation
* prompt for the User-Agent.
*/
logoutSource,

/*
* renderError
*
Expand Down Expand Up @@ -2660,6 +2670,8 @@ function getDefaults() {
},

pkceMethods: undefined,
postLogoutSuccessSource: undefined,
logoutSource: undefined,
};

if (!runtimeSupport.EdDSA) {
Expand Down
1 change: 1 addition & 0 deletions lib/helpers/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const STABLE = new Set([
'registrationManagement',
'requestObjects',
'revocation',
'rpInitiatedLogout',
'userinfo',
]);

Expand Down
9 changes: 6 additions & 3 deletions lib/helpers/initialize_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,13 @@ module.exports = function initializeApp() {
post('check_session_origin', routes.check_session, error(this, 'check_session_origin.error'), ...checkSession.post);
}

get('end_session', routes.end_session, error(this, 'end_session.error'), ...endSession.init);
post('end_session', routes.end_session, error(this, 'end_session.error'), ...endSession.init);
post('end_session_confirm', `${routes.end_session}/confirm`, error(this, 'end_session_confirm.error'), ...endSession.confirm);
get('end_session_success', `${routes.end_session}/success`, error(this, 'end_session_success.error'), ...endSession.success);

if (configuration.features.rpInitiatedLogout.enabled) {
post('end_session', routes.end_session, error(this, 'end_session.error'), ...endSession.init);
get('end_session', routes.end_session, error(this, 'end_session.error'), ...endSession.init);
get('end_session_success', `${routes.end_session}/success`, error(this, 'end_session_success.error'), ...endSession.success);
}

if (configuration.features.deviceFlow.enabled) {
const deviceAuthorization = getAuthorization(this, 'device_authorization');
Expand Down
1 change: 1 addition & 0 deletions test/device_code/device_code.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ merge(config.features, {
claimsParameter: { enabled: true },
requestObjects: { request: false, requestUri: false },
resourceIndicators: { enabled: true },
rpInitiatedLogout: { enabled: false },
});

config.extraParams = [
Expand Down
Loading

0 comments on commit a2ef044

Please sign in to comment.