refactor(oidc): Import code for building the account management URL
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
committed by
Ivan Enderlin
parent
91f9ef85ae
commit
9adff21f78
@@ -8,8 +8,8 @@ use anyhow::{anyhow, Context as _};
|
||||
use async_compat::get_runtime_handle;
|
||||
use matrix_sdk::{
|
||||
authentication::oidc::{
|
||||
registrations::ClientId, requests::account_management::AccountManagementActionFull,
|
||||
types::requests::Prompt as SdkOidcPrompt, OidcAuthorizationData, OidcSession,
|
||||
registrations::ClientId, types::requests::Prompt as SdkOidcPrompt,
|
||||
AccountManagementActionFull, OidcAuthorizationData, OidcSession,
|
||||
},
|
||||
event_cache::EventCacheError,
|
||||
media::{
|
||||
@@ -1723,8 +1723,12 @@ impl From<AccountManagementAction> for AccountManagementActionFull {
|
||||
match value {
|
||||
AccountManagementAction::Profile => Self::Profile,
|
||||
AccountManagementAction::SessionsList => Self::SessionsList,
|
||||
AccountManagementAction::SessionView { device_id } => Self::SessionView { device_id },
|
||||
AccountManagementAction::SessionEnd { device_id } => Self::SessionEnd { device_id },
|
||||
AccountManagementAction::SessionView { device_id } => {
|
||||
Self::SessionView { device_id: device_id.into() }
|
||||
}
|
||||
AccountManagementAction::SessionEnd { device_id } => {
|
||||
Self::SessionEnd { device_id: device_id.into() }
|
||||
}
|
||||
AccountManagementAction::AccountDeactivate => Self::AccountDeactivate,
|
||||
AccountManagementAction::CrossSigningReset => Self::CrossSigningReset,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
//! Types and functions related to the account management URL.
|
||||
//!
|
||||
//! This is a Matrix extension introduced in [MSC4191](https://github.com/matrix-org/matrix-spec-proposals/pull/4191).
|
||||
|
||||
use ruma::OwnedDeviceId;
|
||||
use serde::Serialize;
|
||||
use url::Url;
|
||||
|
||||
/// An account management action that a user can take, including a device ID for
|
||||
/// the actions that support it.
|
||||
///
|
||||
/// The actions are defined in [MSC4191].
|
||||
///
|
||||
/// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
|
||||
#[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: OwnedDeviceId,
|
||||
},
|
||||
|
||||
/// `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: OwnedDeviceId,
|
||||
},
|
||||
|
||||
/// `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,
|
||||
}
|
||||
|
||||
/// Build the URL for accessing the account management capabilities, as defined
|
||||
/// in [MSC].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `account_management_uri` - The URL to access the issuer's account
|
||||
/// management capabilities.
|
||||
///
|
||||
/// * `action` - The action that the user wishes to take.
|
||||
///
|
||||
/// # 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 action fails.
|
||||
///
|
||||
/// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
|
||||
pub(crate) fn build_account_management_url(
|
||||
mut account_management_uri: Url,
|
||||
action: AccountManagementActionFull,
|
||||
) -> Result<Url, serde_html_form::ser::Error> {
|
||||
let extra_query = serde_html_form::to_string(action)?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruma::owned_device_id;
|
||||
use url::Url;
|
||||
|
||||
use super::{build_account_management_url, AccountManagementActionFull};
|
||||
|
||||
#[test]
|
||||
fn test_build_account_management_url_actions() {
|
||||
let base_url = Url::parse("https://example.org").unwrap();
|
||||
let device_id = owned_device_id!("ABCDEFG");
|
||||
|
||||
let url =
|
||||
build_account_management_url(base_url.clone(), AccountManagementActionFull::Profile)
|
||||
.unwrap();
|
||||
assert_eq!(url.as_str(), "https://example.org/?action=org.matrix.profile");
|
||||
|
||||
let url = build_account_management_url(
|
||||
base_url.clone(),
|
||||
AccountManagementActionFull::SessionsList,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(url.as_str(), "https://example.org/?action=org.matrix.sessions_list");
|
||||
|
||||
let url = build_account_management_url(
|
||||
base_url.clone(),
|
||||
AccountManagementActionFull::SessionView { device_id: device_id.clone() },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
url.as_str(),
|
||||
"https://example.org/?action=org.matrix.session_view&device_id=ABCDEFG"
|
||||
);
|
||||
|
||||
let url = build_account_management_url(
|
||||
base_url.clone(),
|
||||
AccountManagementActionFull::SessionEnd { device_id },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
url.as_str(),
|
||||
"https://example.org/?action=org.matrix.session_end&device_id=ABCDEFG"
|
||||
);
|
||||
|
||||
let url = build_account_management_url(
|
||||
base_url.clone(),
|
||||
AccountManagementActionFull::AccountDeactivate,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(url.as_str(), "https://example.org/?action=org.matrix.account_deactivate");
|
||||
|
||||
let url =
|
||||
build_account_management_url(base_url, AccountManagementActionFull::CrossSigningReset)
|
||||
.unwrap();
|
||||
assert_eq!(url.as_str(), "https://example.org/?action=org.matrix.cross_signing_reset");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_account_management_url_with_query() {
|
||||
let base_url = Url::parse("https://example.org/?sid=123456").unwrap();
|
||||
|
||||
let url =
|
||||
build_account_management_url(base_url.clone(), AccountManagementActionFull::Profile)
|
||||
.unwrap();
|
||||
assert_eq!(url.as_str(), "https://example.org/?sid=123456&action=org.matrix.profile");
|
||||
|
||||
let url = build_account_management_url(
|
||||
base_url,
|
||||
AccountManagementActionFull::SessionView { device_id: owned_device_id!("ABCDEFG") },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
url.as_str(),
|
||||
"https://example.org/?sid=123456&action=org.matrix.session_view&device_id=ABCDEFG"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,10 @@ pub enum OidcError {
|
||||
#[error("failed to log out: {0}")]
|
||||
Logout(#[from] OauthTokenRevocationError),
|
||||
|
||||
/// An error occurred building the account management URL.
|
||||
#[error("failed to build account management URL: {0}")]
|
||||
AccountManagementUrl(serde_html_form::ser::Error),
|
||||
|
||||
/// An error occurred parsing a URL.
|
||||
#[error(transparent)]
|
||||
Url(url::ParseError),
|
||||
|
||||
@@ -156,7 +156,6 @@ use error::{
|
||||
use mas_oidc_client::{
|
||||
http_service::HttpService,
|
||||
requests::{
|
||||
account_management::{build_account_management_url, AccountManagementActionFull},
|
||||
discovery::{discover, insecure_discover},
|
||||
registration::register_client,
|
||||
},
|
||||
@@ -191,6 +190,7 @@ use tokio::{spawn, sync::Mutex};
|
||||
use tracing::{debug, error, info, instrument, trace, warn};
|
||||
use url::Url;
|
||||
|
||||
mod account_management_url;
|
||||
mod auth_code_builder;
|
||||
mod cross_process;
|
||||
pub mod error;
|
||||
@@ -200,15 +200,17 @@ pub mod registrations;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::{
|
||||
auth_code_builder::{OidcAuthCodeUrlBuilder, OidcAuthorizationData},
|
||||
error::OidcError,
|
||||
};
|
||||
use self::{
|
||||
account_management_url::build_account_management_url,
|
||||
cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager},
|
||||
qrcode::LoginWithQrCode,
|
||||
registrations::{ClientId, OidcRegistrations},
|
||||
};
|
||||
pub use self::{
|
||||
account_management_url::AccountManagementActionFull,
|
||||
auth_code_builder::{OidcAuthCodeUrlBuilder, OidcAuthorizationData},
|
||||
error::OidcError,
|
||||
};
|
||||
use super::{AuthData, SessionTokens};
|
||||
use crate::{client::SessionChange, Client, HttpError, RefreshTokenError, Result};
|
||||
|
||||
@@ -619,7 +621,12 @@ impl Oidc {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let url = build_account_management_url(base_url, action, None)?;
|
||||
let url = if let Some(action) = action {
|
||||
build_account_management_url(base_url, action)
|
||||
.map_err(OidcError::AccountManagementUrl)?
|
||||
} else {
|
||||
base_url
|
||||
};
|
||||
|
||||
Ok(Some(url))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||
use anyhow::Context as _;
|
||||
use assert_matches::assert_matches;
|
||||
use assert_matches2::assert_let;
|
||||
use mas_oidc_client::requests::account_management::AccountManagementActionFull;
|
||||
use matrix_sdk_test::async_test;
|
||||
use oauth2::{CsrfToken, PkceCodeChallenge, RedirectUrl};
|
||||
use ruma::{
|
||||
@@ -25,8 +24,8 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
authentication::oidc::{
|
||||
error::AuthorizationCodeErrorResponseType, AuthorizationValidationData,
|
||||
OauthAuthorizationCodeError,
|
||||
error::AuthorizationCodeErrorResponseType, AccountManagementActionFull,
|
||||
AuthorizationValidationData, OauthAuthorizationCodeError,
|
||||
},
|
||||
test_utils::{
|
||||
client::{
|
||||
|
||||
@@ -31,15 +31,14 @@ use futures_util::StreamExt;
|
||||
use matrix_sdk::{
|
||||
authentication::oidc::{
|
||||
registrations::ClientId,
|
||||
requests::account_management::AccountManagementActionFull,
|
||||
types::{
|
||||
iana::oauth::OAuthClientAuthenticationMethod,
|
||||
oidc::ApplicationType,
|
||||
registration::{ClientMetadata, Localized, VerifiedClientMetadata},
|
||||
requests::GrantType,
|
||||
},
|
||||
AuthorizationCode, AuthorizationResponse, CsrfToken, OidcAuthorizationData, OidcSession,
|
||||
UserSession,
|
||||
AccountManagementActionFull, AuthorizationCode, AuthorizationResponse, CsrfToken,
|
||||
OidcAuthorizationData, OidcSession, UserSession,
|
||||
},
|
||||
config::SyncSettings,
|
||||
encryption::{recovery::RecoveryState, CrossSigningResetAuthType},
|
||||
|
||||
Reference in New Issue
Block a user