Add account management URL for clients
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
committed by
Quentin Gliech
parent
7dd59c962c
commit
3303e939ca
@@ -164,6 +164,78 @@ pub enum ClaimType {
|
||||
Distributed,
|
||||
}
|
||||
|
||||
/// An account management action that a user can take.
|
||||
///
|
||||
/// Source: <https://github.com/matrix-org/matrix-spec-proposals/pull/2965>
|
||||
#[derive(
|
||||
SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub enum AccountManagementAction {
|
||||
/// `org.matrix.profile`
|
||||
///
|
||||
/// The user wishes to view their profile (name, avatar, contact details).
|
||||
Profile,
|
||||
|
||||
/// `org.matrix.sessions_list`
|
||||
///
|
||||
/// The user wishes to view a list of their sessions.
|
||||
SessionsList,
|
||||
|
||||
/// `org.matrix.session_view`
|
||||
///
|
||||
/// The user wishes to view the details of a specific session.
|
||||
SessionView,
|
||||
|
||||
/// `org.matrix.session_end`
|
||||
///
|
||||
/// The user wishes to end/log out of a specific session.
|
||||
SessionEnd,
|
||||
|
||||
/// `org.matrix.account_deactivate`
|
||||
///
|
||||
/// The user wishes to deactivate their account.
|
||||
AccountDeactivate,
|
||||
|
||||
/// `org.matrix.cross_signing_reset`
|
||||
///
|
||||
/// The user wishes to reset their cross-signing keys.
|
||||
CrossSigningReset,
|
||||
|
||||
/// An unknown value.
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AccountManagementAction {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Profile => write!(f, "org.matrix.profile"),
|
||||
Self::SessionsList => write!(f, "org.matrix.sessions_list"),
|
||||
Self::SessionView => write!(f, "org.matrix.session_view"),
|
||||
Self::SessionEnd => write!(f, "org.matrix.session_end"),
|
||||
Self::AccountDeactivate => write!(f, "org.matrix.account_deactivate"),
|
||||
Self::CrossSigningReset => write!(f, "org.matrix.cross_signing_reset"),
|
||||
Self::Unknown(value) => write!(f, "{value}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::str::FromStr for AccountManagementAction {
|
||||
type Err = core::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"org.matrix.profile" => Ok(Self::Profile),
|
||||
"org.matrix.sessions_list" => Ok(Self::SessionsList),
|
||||
"org.matrix.session_view" => Ok(Self::SessionView),
|
||||
"org.matrix.session_end" => Ok(Self::SessionEnd),
|
||||
"org.matrix.account_deactivate" => Ok(Self::AccountDeactivate),
|
||||
"org.matrix.cross_signing_reset" => Ok(Self::CrossSigningReset),
|
||||
value => Ok(Self::Unknown(value.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The default value of `response_modes_supported` if it is not set.
|
||||
pub static DEFAULT_RESPONSE_MODES_SUPPORTED: &[ResponseMode] =
|
||||
&[ResponseMode::Query, ResponseMode::Fragment];
|
||||
@@ -479,6 +551,17 @@ pub struct ProviderMetadata {
|
||||
///
|
||||
/// [RP-Initiated Logout endpoint]: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||
pub end_session_endpoint: Option<Url>,
|
||||
|
||||
/// URL where the user is able to access the account management capabilities
|
||||
/// of this OP.
|
||||
///
|
||||
/// This is a Matrix extension introduced in [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).
|
||||
pub account_management_uri: Option<Url>,
|
||||
|
||||
/// Array of actions that the account management URL supports.
|
||||
///
|
||||
/// This is a Matrix extension introduced in [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).
|
||||
pub account_management_actions_supported: Option<Vec<AccountManagementAction>>,
|
||||
}
|
||||
|
||||
impl ProviderMetadata {
|
||||
|
||||
127
crates/oidc-client/src/requests/account_management.rs
Normal file
127
crates/oidc-client/src/requests/account_management.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
//! Methods related to the account management URL.
|
||||
//!
|
||||
//! This is a Matrix extension introduced in [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_with::skip_serializing_none;
|
||||
use url::Url;
|
||||
|
||||
/// An account management action that a user can take, including a device ID for
|
||||
/// the actions that support it.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(tag = "action")]
|
||||
#[non_exhaustive]
|
||||
pub enum AccountManagementActionFull {
|
||||
/// `org.matrix.profile`
|
||||
///
|
||||
/// The user wishes to view their profile (name, avatar, contact details).
|
||||
#[serde(rename = "org.matrix.profile")]
|
||||
Profile,
|
||||
|
||||
/// `org.matrix.sessions_list`
|
||||
///
|
||||
/// The user wishes to view a list of their sessions.
|
||||
#[serde(rename = "org.matrix.sessions_list")]
|
||||
SessionsList,
|
||||
|
||||
/// `org.matrix.session_view`
|
||||
///
|
||||
/// The user wishes to view the details of a specific session.
|
||||
#[serde(rename = "org.matrix.session_view")]
|
||||
SessionView {
|
||||
/// The ID of the session to view the details of.
|
||||
device_id: String,
|
||||
},
|
||||
|
||||
/// `org.matrix.session_end`
|
||||
///
|
||||
/// The user wishes to end/log out of a specific session.
|
||||
#[serde(rename = "org.matrix.session_end")]
|
||||
SessionEnd {
|
||||
/// The ID of the session to end.
|
||||
device_id: String,
|
||||
},
|
||||
|
||||
/// `org.matrix.account_deactivate`
|
||||
///
|
||||
/// The user wishes to deactivate their account.
|
||||
#[serde(rename = "org.matrix.account_deactivate")]
|
||||
AccountDeactivate,
|
||||
|
||||
/// `org.matrix.cross_signing_reset`
|
||||
///
|
||||
/// The user wishes to reset their cross-signing keys.
|
||||
#[serde(rename = "org.matrix.cross_signing_reset")]
|
||||
CrossSigningReset,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct AccountManagementData {
|
||||
#[serde(flatten)]
|
||||
action: Option<AccountManagementActionFull>,
|
||||
id_token_hint: Option<String>,
|
||||
}
|
||||
|
||||
/// Build the URL for accessing the account management capabilities.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `account_management_uri` - The URL to access the issuer's account
|
||||
/// management capabilities.
|
||||
///
|
||||
/// * `action` - The action that the user wishes to take.
|
||||
///
|
||||
/// * `id_token_hint` - An ID Token that was previously issued to the client,
|
||||
/// used as a hint for which user is requesting to manage their account.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A URL to be opened in a web browser where the end-user will be able to
|
||||
/// access the account management capabilities of the issuer.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if serializing the URL fails.
|
||||
pub fn build_account_management_url(
|
||||
mut account_management_uri: Url,
|
||||
action: Option<AccountManagementActionFull>,
|
||||
id_token_hint: Option<String>,
|
||||
) -> Result<Url, serde_urlencoded::ser::Error> {
|
||||
let data = AccountManagementData {
|
||||
action,
|
||||
id_token_hint,
|
||||
};
|
||||
let extra_query = serde_urlencoded::to_string(data)?;
|
||||
|
||||
if !extra_query.is_empty() {
|
||||
// Add our parameters to the query, because the URL might already have one.
|
||||
let mut full_query = account_management_uri
|
||||
.query()
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !full_query.is_empty() {
|
||||
full_query.push('&');
|
||||
}
|
||||
full_query.push_str(&extra_query);
|
||||
|
||||
account_management_uri.set_query(Some(&full_query));
|
||||
}
|
||||
|
||||
Ok(account_management_uri)
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
//! Methods to interact with OpenID Connect and OAuth2.0 endpoints.
|
||||
|
||||
pub mod account_management;
|
||||
pub mod authorization_code;
|
||||
pub mod client_credentials;
|
||||
pub mod discovery;
|
||||
|
||||
135
crates/oidc-client/tests/it/requests/account_management.rs
Normal file
135
crates/oidc-client/tests/it/requests/account_management.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mas_oidc_client::requests::account_management::{
|
||||
build_account_management_url, AccountManagementActionFull,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn build_url() {
|
||||
let account_management_uri = Url::parse("http://localhost/account_management/").unwrap();
|
||||
|
||||
// No params
|
||||
let url = build_account_management_url(account_management_uri.clone(), None, None).unwrap();
|
||||
|
||||
assert_eq!(url.query(), None);
|
||||
|
||||
// Action without device ID.
|
||||
let url = build_account_management_url(
|
||||
account_management_uri.clone(),
|
||||
Some(AccountManagementActionFull::Profile),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let query_pairs = url.query_pairs().collect::<HashMap<_, _>>();
|
||||
assert_eq!(query_pairs.len(), 1);
|
||||
assert_eq!(query_pairs.get("action").unwrap(), "org.matrix.profile");
|
||||
|
||||
// Action with device ID.
|
||||
let url = build_account_management_url(
|
||||
account_management_uri.clone(),
|
||||
Some(AccountManagementActionFull::SessionEnd {
|
||||
device_id: "mydevice".to_owned(),
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let query_pairs = url.query_pairs().collect::<HashMap<_, _>>();
|
||||
assert_eq!(query_pairs.len(), 2);
|
||||
assert_eq!(query_pairs.get("action").unwrap(), "org.matrix.session_end");
|
||||
assert_eq!(query_pairs.get("device_id").unwrap(), "mydevice");
|
||||
|
||||
// ID Token hint.
|
||||
let url = build_account_management_url(
|
||||
account_management_uri.clone(),
|
||||
None,
|
||||
Some("anidtokenthat.might.looksomethinglikethis".to_owned()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let query_pairs = url.query_pairs().collect::<HashMap<_, _>>();
|
||||
assert_eq!(query_pairs.len(), 1);
|
||||
assert_eq!(
|
||||
query_pairs.get("id_token_hint").unwrap(),
|
||||
"anidtokenthat.might.looksomethinglikethis"
|
||||
);
|
||||
|
||||
// Action without device ID and ID Token hint.
|
||||
let url = build_account_management_url(
|
||||
account_management_uri.clone(),
|
||||
Some(AccountManagementActionFull::AccountDeactivate),
|
||||
Some("anotheridtokenthat.might.looksomethinglikethis".to_owned()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let query_pairs = url.query_pairs().collect::<HashMap<_, _>>();
|
||||
assert_eq!(query_pairs.len(), 2);
|
||||
assert_eq!(
|
||||
query_pairs.get("action").unwrap(),
|
||||
"org.matrix.account_deactivate"
|
||||
);
|
||||
assert_eq!(
|
||||
query_pairs.get("id_token_hint").unwrap(),
|
||||
"anotheridtokenthat.might.looksomethinglikethis"
|
||||
);
|
||||
|
||||
// Action with device ID and ID Token hint.
|
||||
let url = build_account_management_url(
|
||||
account_management_uri,
|
||||
Some(AccountManagementActionFull::SessionView {
|
||||
device_id: "myseconddevice".to_owned(),
|
||||
}),
|
||||
Some("athirdidtokenthat.might.looksomethinglikethis".to_owned()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let query_pairs = url.query_pairs().collect::<HashMap<_, _>>();
|
||||
assert_eq!(query_pairs.len(), 3);
|
||||
assert_eq!(
|
||||
query_pairs.get("action").unwrap(),
|
||||
"org.matrix.session_view"
|
||||
);
|
||||
assert_eq!(query_pairs.get("device_id").unwrap(), "myseconddevice");
|
||||
assert_eq!(
|
||||
query_pairs.get("id_token_hint").unwrap(),
|
||||
"athirdidtokenthat.might.looksomethinglikethis"
|
||||
);
|
||||
|
||||
// Account management URI with a query already.
|
||||
let account_management_uri_with_query =
|
||||
Url::parse("http://localhost/account_management?param=value").unwrap();
|
||||
|
||||
let url = build_account_management_url(
|
||||
account_management_uri_with_query,
|
||||
Some(AccountManagementActionFull::SessionsList),
|
||||
Some("afinalidtokenthat.might.looksomethinglikethis".to_owned()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let query_pairs = url.query_pairs().collect::<HashMap<_, _>>();
|
||||
assert_eq!(query_pairs.len(), 3);
|
||||
assert_eq!(
|
||||
query_pairs.get("action").unwrap(),
|
||||
"org.matrix.sessions_list"
|
||||
);
|
||||
assert_eq!(
|
||||
query_pairs.get("id_token_hint").unwrap(),
|
||||
"afinalidtokenthat.might.looksomethinglikethis"
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod account_management;
|
||||
mod authorization_code;
|
||||
mod client_credentials;
|
||||
mod discovery;
|
||||
|
||||
Reference in New Issue
Block a user