You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-06 06:02:40 +03:00
Add client-side support for RP-Initiated logout
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
committed by
Quentin Gliech
parent
4cdb24ffe4
commit
ba7d17f25c
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3414,6 +3414,7 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
|
"language-tags",
|
||||||
"mas-http",
|
"mas-http",
|
||||||
"mas-iana",
|
"mas-iana",
|
||||||
"mas-jose",
|
"mas-jose",
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
//!
|
//!
|
||||||
//! [OpenID Connect]: https://openid.net/connect/
|
//! [OpenID Connect]: https://openid.net/connect/
|
||||||
|
|
||||||
use std::{ops::Deref, str::FromStr};
|
use std::{fmt, ops::Deref, str::FromStr};
|
||||||
|
|
||||||
use language_tags::LanguageTag;
|
use language_tags::LanguageTag;
|
||||||
use mas_iana::{
|
use mas_iana::{
|
||||||
@@ -25,7 +25,10 @@ use mas_iana::{
|
|||||||
};
|
};
|
||||||
use parse_display::{Display, FromStr, ParseError};
|
use parse_display::{Display, FromStr, ParseError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::{skip_serializing_none, DeserializeFromStr, SerializeDisplay};
|
use serde_with::{
|
||||||
|
formats::SpaceSeparator, serde_as, skip_serializing_none, DeserializeFromStr, SerializeDisplay,
|
||||||
|
StringWithSeparator,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -471,6 +474,11 @@ pub struct ProviderMetadata {
|
|||||||
///
|
///
|
||||||
/// [device authorization endpoint]: https://www.rfc-editor.org/rfc/rfc8628
|
/// [device authorization endpoint]: https://www.rfc-editor.org/rfc/rfc8628
|
||||||
pub device_authorization_endpoint: Option<Url>,
|
pub device_authorization_endpoint: Option<Url>,
|
||||||
|
|
||||||
|
/// URL of the authorization server's [RP-Initiated Logout endpoint].
|
||||||
|
///
|
||||||
|
/// [RP-Initiated Logout endpoint]: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||||
|
pub end_session_endpoint: Option<Url>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProviderMetadata {
|
impl ProviderMetadata {
|
||||||
@@ -593,6 +601,10 @@ impl ProviderMetadata {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(url) = &metadata.end_session_endpoint {
|
||||||
|
validate_url("end_session_endpoint", url, ExtraUrlRestrictions::None)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(metadata)
|
Ok(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,6 +1069,66 @@ fn validate_signing_alg_values_supported<'a>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The body of a request to the [RP-Initiated Logout Endpoint].
|
||||||
|
///
|
||||||
|
/// [RP-Initiated Logout Endpoint]: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[serde_as]
|
||||||
|
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct RpInitiatedLogoutRequest {
|
||||||
|
/// ID Token previously issued by the OP to the RP.
|
||||||
|
///
|
||||||
|
/// Recommended, used as a hint about the End-User's current authenticated
|
||||||
|
/// session with the Client.
|
||||||
|
pub id_token_hint: Option<String>,
|
||||||
|
|
||||||
|
/// Hint to the Authorization Server about the End-User that is logging out.
|
||||||
|
///
|
||||||
|
/// The value and meaning of this parameter is left up to the OP's
|
||||||
|
/// discretion. For instance, the value might contain an email address,
|
||||||
|
/// phone number, username, or session identifier pertaining to the RP's
|
||||||
|
/// session with the OP for the End-User.
|
||||||
|
pub logout_hint: Option<String>,
|
||||||
|
|
||||||
|
/// OAuth 2.0 Client Identifier valid at the Authorization Server.
|
||||||
|
///
|
||||||
|
/// The most common use case for this parameter is to specify the Client
|
||||||
|
/// Identifier when `post_logout_redirect_uri` is used but `id_token_hint`
|
||||||
|
/// is not. Another use is for symmetrically encrypted ID Tokens used as
|
||||||
|
/// `id_token_hint` values that require the Client Identifier to be
|
||||||
|
/// specified by other means, so that the ID Tokens can be decrypted by
|
||||||
|
/// the OP.
|
||||||
|
pub client_id: Option<String>,
|
||||||
|
|
||||||
|
/// URI to which the RP is requesting that the End-User's User Agent be
|
||||||
|
/// redirected after a logout has been performed.
|
||||||
|
///
|
||||||
|
/// The value MUST have been previously registered with the OP, using the
|
||||||
|
/// `post_logout_redirect_uris` registration parameter.
|
||||||
|
pub post_logout_redirect_uri: Option<Url>,
|
||||||
|
|
||||||
|
/// Opaque value used by the RP to maintain state between the logout request
|
||||||
|
/// and the callback to the endpoint specified by the
|
||||||
|
/// `post_logout_redirect_uri` parameter.
|
||||||
|
pub state: Option<String>,
|
||||||
|
|
||||||
|
/// End-User's preferred languages and scripts for the user interface,
|
||||||
|
/// ordered by preference.
|
||||||
|
#[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, LanguageTag>>")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub ui_locales: Option<Vec<LanguageTag>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RpInitiatedLogoutRequest {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("RpInitiatedLogoutRequest")
|
||||||
|
.field("logout_hint", &self.logout_hint)
|
||||||
|
.field("post_logout_redirect_uri", &self.post_logout_redirect_uri)
|
||||||
|
.field("ui_locales", &self.ui_locales)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
@@ -124,6 +124,7 @@ pub struct ClientMetadataSerdeHelper {
|
|||||||
introspection_signed_response_alg: Option<JsonWebSignatureAlg>,
|
introspection_signed_response_alg: Option<JsonWebSignatureAlg>,
|
||||||
introspection_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
introspection_encrypted_response_alg: Option<JsonWebEncryptionAlg>,
|
||||||
introspection_encrypted_response_enc: Option<JsonWebEncryptionEnc>,
|
introspection_encrypted_response_enc: Option<JsonWebEncryptionEnc>,
|
||||||
|
post_logout_redirect_uris: Option<Vec<Url>>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
extra: ClientMetadataLocalizedFields,
|
extra: ClientMetadataLocalizedFields,
|
||||||
}
|
}
|
||||||
@@ -168,6 +169,7 @@ impl From<VerifiedClientMetadata> for ClientMetadataSerdeHelper {
|
|||||||
introspection_signed_response_alg,
|
introspection_signed_response_alg,
|
||||||
introspection_encrypted_response_alg,
|
introspection_encrypted_response_alg,
|
||||||
introspection_encrypted_response_enc,
|
introspection_encrypted_response_enc,
|
||||||
|
post_logout_redirect_uris,
|
||||||
},
|
},
|
||||||
} = metadata;
|
} = metadata;
|
||||||
|
|
||||||
@@ -202,6 +204,7 @@ impl From<VerifiedClientMetadata> for ClientMetadataSerdeHelper {
|
|||||||
introspection_signed_response_alg,
|
introspection_signed_response_alg,
|
||||||
introspection_encrypted_response_alg,
|
introspection_encrypted_response_alg,
|
||||||
introspection_encrypted_response_enc,
|
introspection_encrypted_response_enc,
|
||||||
|
post_logout_redirect_uris,
|
||||||
extra: ClientMetadataLocalizedFields {
|
extra: ClientMetadataLocalizedFields {
|
||||||
client_name,
|
client_name,
|
||||||
logo_uri,
|
logo_uri,
|
||||||
@@ -246,6 +249,7 @@ impl From<ClientMetadataSerdeHelper> for ClientMetadata {
|
|||||||
introspection_signed_response_alg,
|
introspection_signed_response_alg,
|
||||||
introspection_encrypted_response_alg,
|
introspection_encrypted_response_alg,
|
||||||
introspection_encrypted_response_enc,
|
introspection_encrypted_response_enc,
|
||||||
|
post_logout_redirect_uris,
|
||||||
extra:
|
extra:
|
||||||
ClientMetadataLocalizedFields {
|
ClientMetadataLocalizedFields {
|
||||||
client_name,
|
client_name,
|
||||||
@@ -292,6 +296,7 @@ impl From<ClientMetadataSerdeHelper> for ClientMetadata {
|
|||||||
introspection_signed_response_alg,
|
introspection_signed_response_alg,
|
||||||
introspection_encrypted_response_alg,
|
introspection_encrypted_response_alg,
|
||||||
introspection_encrypted_response_enc,
|
introspection_encrypted_response_enc,
|
||||||
|
post_logout_redirect_uris,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -418,6 +418,12 @@ pub struct ClientMetadata {
|
|||||||
/// [JWE]: http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption
|
/// [JWE]: http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption
|
||||||
/// [introspection endpoint]: https://www.rfc-editor.org/info/rfc7662
|
/// [introspection endpoint]: https://www.rfc-editor.org/info/rfc7662
|
||||||
pub introspection_encrypted_response_enc: Option<JsonWebEncryptionEnc>,
|
pub introspection_encrypted_response_enc: Option<JsonWebEncryptionEnc>,
|
||||||
|
|
||||||
|
/// `post_logout_redirect_uri` values that are pre-registered by the client
|
||||||
|
/// for use at the provider's [RP-Initiated Logout endpoint].
|
||||||
|
///
|
||||||
|
/// [RP-Initiated Logout endpoint]: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||||
|
pub post_logout_redirect_uris: Option<Vec<Url>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientMetadata {
|
impl ClientMetadata {
|
||||||
|
@@ -26,6 +26,7 @@ futures = "0.3.28"
|
|||||||
futures-util = "0.3.28"
|
futures-util = "0.3.28"
|
||||||
headers = "0.3.8"
|
headers = "0.3.8"
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
|
language-tags = "0.3.2"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
@@ -22,5 +22,6 @@ pub mod jose;
|
|||||||
pub mod refresh_token;
|
pub mod refresh_token;
|
||||||
pub mod registration;
|
pub mod registration;
|
||||||
pub mod revocation;
|
pub mod revocation;
|
||||||
|
pub mod rp_initiated_logout;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
pub mod userinfo;
|
pub mod userinfo;
|
||||||
|
135
crates/oidc-client/src/requests/rp_initiated_logout.rs
Normal file
135
crates/oidc-client/src/requests/rp_initiated_logout.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2023 Kévin Commaille.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Requests for [RP-Initiated Logout].
|
||||||
|
//!
|
||||||
|
//! [RP-Initiated Logout]: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||||
|
|
||||||
|
use language_tags::LanguageTag;
|
||||||
|
use oauth2_types::oidc::RpInitiatedLogoutRequest;
|
||||||
|
use rand::{
|
||||||
|
distributions::{Alphanumeric, DistString},
|
||||||
|
Rng,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// The data necessary to build a logout request.
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct LogoutData {
|
||||||
|
/// ID Token previously issued by the OP to the RP.
|
||||||
|
///
|
||||||
|
/// Recommended, used as a hint about the End-User's current authenticated
|
||||||
|
/// session with the Client.
|
||||||
|
pub id_token_hint: Option<String>,
|
||||||
|
|
||||||
|
/// Hint to the Authorization Server about the End-User that is logging out.
|
||||||
|
///
|
||||||
|
/// The value and meaning of this parameter is left up to the OP's
|
||||||
|
/// discretion. For instance, the value might contain an email address,
|
||||||
|
/// phone number, username, or session identifier pertaining to the RP's
|
||||||
|
/// session with the OP for the End-User.
|
||||||
|
pub logout_hint: Option<String>,
|
||||||
|
|
||||||
|
/// OAuth 2.0 Client Identifier valid at the Authorization Server.
|
||||||
|
///
|
||||||
|
/// The most common use case for this parameter is to specify the Client
|
||||||
|
/// Identifier when `post_logout_redirect_uri` is used but `id_token_hint`
|
||||||
|
/// is not. Another use is for symmetrically encrypted ID Tokens used as
|
||||||
|
/// `id_token_hint` values that require the Client Identifier to be
|
||||||
|
/// specified by other means, so that the ID Tokens can be decrypted by
|
||||||
|
/// the OP.
|
||||||
|
pub client_id: Option<String>,
|
||||||
|
|
||||||
|
/// URI to which the RP is requesting that the End-User's User Agent be
|
||||||
|
/// redirected after a logout has been performed.
|
||||||
|
///
|
||||||
|
/// The value MUST have been previously registered with the OP, using the
|
||||||
|
/// `post_logout_redirect_uris` registration parameter.
|
||||||
|
pub post_logout_redirect_uri: Option<Url>,
|
||||||
|
|
||||||
|
/// The End-User's preferred languages and scripts for the user interface,
|
||||||
|
/// ordered by preference.
|
||||||
|
pub ui_locales: Option<Vec<LanguageTag>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the URL for initiating logout at the logout endpoint.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `end_session_endpoint` - The URL of the issuer's logout endpoint.
|
||||||
|
///
|
||||||
|
/// * `logout_data` - The data necessary to build the logout request.
|
||||||
|
///
|
||||||
|
/// * `rng` - A random number generator.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A URL to be opened in a web browser where the end-user will be able to
|
||||||
|
/// logout of their session, and an optional `state` string.
|
||||||
|
///
|
||||||
|
/// The `state` will only be set if `post_logout_redirect_uri` is set. It should
|
||||||
|
/// be present in the query when the end user is redirected to the
|
||||||
|
/// `post_logout_redirect_uri`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if preparing the URL fails.
|
||||||
|
///
|
||||||
|
/// [`VerifiedClientMetadata`]: oauth2_types::registration::VerifiedClientMetadata
|
||||||
|
/// [`ClientErrorCode`]: oauth2_types::errors::ClientErrorCode
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub fn build_end_session_url(
|
||||||
|
mut end_session_endpoint: Url,
|
||||||
|
logout_data: LogoutData,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
) -> Result<(Url, Option<String>), serde_urlencoded::ser::Error> {
|
||||||
|
let LogoutData {
|
||||||
|
id_token_hint,
|
||||||
|
logout_hint,
|
||||||
|
client_id,
|
||||||
|
post_logout_redirect_uri,
|
||||||
|
ui_locales,
|
||||||
|
} = logout_data;
|
||||||
|
|
||||||
|
let state = if post_logout_redirect_uri.is_some() {
|
||||||
|
Some(Alphanumeric.sample_string(rng, 16))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let logout_request = RpInitiatedLogoutRequest {
|
||||||
|
id_token_hint,
|
||||||
|
logout_hint,
|
||||||
|
client_id,
|
||||||
|
post_logout_redirect_uri,
|
||||||
|
state: state.clone(),
|
||||||
|
ui_locales,
|
||||||
|
};
|
||||||
|
|
||||||
|
let logout_query = serde_urlencoded::to_string(logout_request)?;
|
||||||
|
|
||||||
|
// Add our parameters to the query, because the URL might already have one.
|
||||||
|
let mut full_query = end_session_endpoint
|
||||||
|
.query()
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.unwrap_or_default();
|
||||||
|
if !full_query.is_empty() {
|
||||||
|
full_query.push('&');
|
||||||
|
}
|
||||||
|
full_query.push_str(&logout_query);
|
||||||
|
|
||||||
|
end_session_endpoint.set_query(Some(&full_query));
|
||||||
|
|
||||||
|
Ok((end_session_endpoint, state))
|
||||||
|
}
|
Reference in New Issue
Block a user