Skip to content

Commit 861078a

Browse files
committed
feat: Add a memoized variant of Oidc::fetch_account_management_url
1 parent aa9aef4 commit 861078a

File tree

7 files changed

+119
-5
lines changed

7 files changed

+119
-5
lines changed

bindings/matrix-sdk-ffi/src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ impl Client {
626626
return Ok(None);
627627
}
628628

629-
match self.inner.oidc().fetch_account_management_url(action.map(Into::into)).await {
629+
match self.inner.oidc().account_management_url(action.map(Into::into)).await {
630630
Ok(url) => Ok(url.map(|u| u.to_string())),
631631
Err(e) => {
632632
tracing::error!("Failed retrieving account management URL: {e}");

crates/matrix-sdk/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ All notable changes to this project will be documented in this file.
88

99
### Features
1010

11+
- [**breaking**]: The `Oidc::account_management_url` method now caches the
12+
result of a call, subsequent calls to the method will not contact the OIDC
13+
provider for a while, instead the cached URI will be returned. If caching of
14+
this URI is not desirable, the `Oidc::fetch_account_management_url` method
15+
can be used.
16+
([#4663](https://github.com/matrix-org/matrix-rust-sdk/pull/4663))
17+
1118
- The `MediaRetentionPolicy` can now trigger regular cleanups with its new
1219
`cleanup_frequency` setting.
1320
([#4603](https://github.com/matrix-org/matrix-rust-sdk/pull/4603))

crates/matrix-sdk/src/authentication/oidc/backend/mock.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ pub(crate) struct MockImpl {
6767
/// Must be an HTTPS URL.
6868
registration_endpoint: Option<Url>,
6969

70+
account_management_uri: Option<String>,
71+
7072
/// The next session tokens that will be returned by a login or refresh.
7173
next_session_tokens: Option<OidcSessionTokens>,
7274

@@ -94,6 +96,7 @@ impl MockImpl {
9496
registration_endpoint: Some(Url::parse(REGISTRATION_URL).unwrap()),
9597
next_session_tokens: None,
9698
expected_refresh_token: None,
99+
account_management_uri: None,
97100
num_refreshes: Default::default(),
98101
revoked_tokens: Default::default(),
99102
is_insecure: false,
@@ -119,6 +122,11 @@ impl MockImpl {
119122
self.registration_endpoint = registration_endpoint;
120123
self
121124
}
125+
126+
pub fn account_management_uri(mut self, uri: String) -> Self {
127+
self.account_management_uri = Some(uri);
128+
self
129+
}
122130
}
123131

124132
#[async_trait::async_trait]
@@ -147,6 +155,10 @@ impl OidcBackend for MockImpl {
147155
response_types_supported: Some(vec![]),
148156
subject_types_supported: Some(vec![]),
149157
id_token_signing_alg_values_supported: Some(vec![]),
158+
account_management_uri: self
159+
.account_management_uri
160+
.as_ref()
161+
.map(|uri| Url::parse(uri).unwrap()),
150162
..Default::default()
151163
}
152164
.validate(issuer)

crates/matrix-sdk/src/authentication/oidc/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,14 @@ impl Oidc {
630630
action: Option<AccountManagementActionFull>,
631631
) -> Result<Option<Url>, OidcError> {
632632
let provider_metadata = self.provider_metadata().await?;
633+
self.management_url_from_provider_metadata(provider_metadata, action)
634+
}
633635

636+
fn management_url_from_provider_metadata(
637+
&self,
638+
provider_metadata: VerifiedProviderMetadata,
639+
action: Option<AccountManagementActionFull>,
640+
) -> Result<Option<Url>, OidcError> {
634641
let Some(base_url) = provider_metadata.account_management_uri.clone() else {
635642
return Ok(None);
636643
};
@@ -643,6 +650,43 @@ impl Oidc {
643650
Ok(Some(url))
644651
}
645652

653+
/// Get the account management URL where the user can manage their
654+
/// identity-related settings.
655+
///
656+
/// # Arguments
657+
///
658+
/// * `action` - An optional action that wants to be performed by the user
659+
/// when they open the URL. The list of supported actions by the account
660+
/// management URL can be found in the [`VerifiedProviderMetadata`], or
661+
/// directly with [`Oidc::account_management_actions_supported()`].
662+
///
663+
/// Returns `Ok(None)` if the URL was not found. Returns an error if the
664+
/// request to get the provider metadata fails or the URL could not be
665+
/// parsed.
666+
///
667+
/// This method will cache the URL for a while, if the cache is not
668+
/// populated it will internally call
669+
/// [`Oidc::fetch_account_management_url()`] and cache the resulting URL
670+
/// before returning it.
671+
pub async fn account_management_url(
672+
&self,
673+
action: Option<AccountManagementActionFull>,
674+
) -> Result<Option<Url>, OidcError> {
675+
const CACHE_KEY: &str = "PROVIDER_METADATA";
676+
677+
let mut cache = self.client.inner.caches.provider_metadata.lock().await;
678+
679+
let metadata = if let Some(metadata) = cache.get("PROVIDER_METADATA") {
680+
metadata
681+
} else {
682+
let provider_metadata = self.provider_metadata().await?;
683+
cache.insert(CACHE_KEY.to_owned(), provider_metadata.clone());
684+
provider_metadata
685+
};
686+
687+
self.management_url_from_provider_metadata(metadata, action)
688+
}
689+
646690
/// Fetch the OpenID Connect metadata of the given issuer.
647691
///
648692
/// Returns an error if fetching the metadata failed.

crates/matrix-sdk/src/authentication/oidc/tests.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use std::{collections::HashMap, sync::Arc};
33
use anyhow::Context as _;
44
use assert_matches::assert_matches;
55
use mas_oidc_client::{
6-
requests::authorization_code::AuthorizationValidationData,
6+
requests::{
7+
account_management::AccountManagementActionFull,
8+
authorization_code::AuthorizationValidationData,
9+
},
710
types::{
811
errors::ClientErrorCode,
912
iana::oauth::OAuthClientAuthenticationMethod,
@@ -29,7 +32,10 @@ use super::{
2932
AuthorizationCode, AuthorizationError, AuthorizationResponse, Oidc, OidcError, OidcSession,
3033
OidcSessionTokens, RedirectUriQueryParseError, UserSession,
3134
};
32-
use crate::{test_utils::test_client_builder, Client, Error};
35+
use crate::{
36+
test_utils::{client::MockClientBuilder, test_client_builder},
37+
Client, Error,
38+
};
3339

3440
const REDIRECT_URI_STRING: &str = "http://matrix.example.com/oidc/callback";
3541

@@ -474,3 +480,36 @@ async fn test_register_client() {
474480
assert_eq!(auth_data.client_id.0, response.client_id);
475481
assert_eq!(auth_data.metadata, client_metadata);
476482
}
483+
484+
#[async_test]
485+
async fn test_management_url_cache() {
486+
let client = MockClientBuilder::new("http://localhost".to_owned()).unlogged().build().await;
487+
let backend = Arc::new(
488+
MockImpl::new().mark_insecure().account_management_uri("http://localhost".to_owned()),
489+
);
490+
let oidc = Oidc { client: client.clone(), backend: backend.clone() };
491+
492+
let tokens = OidcSessionTokens {
493+
access_token: "4cc3ss".to_owned(),
494+
refresh_token: Some("r3fr3sh".to_owned()),
495+
latest_id_token: None,
496+
};
497+
498+
let session = mock_session(tokens.clone());
499+
oidc.restore_session(session.clone())
500+
.await
501+
.expect("We should be able to restore an OIDC session");
502+
503+
// The cache should not contain the entry.
504+
assert!(!client.inner.caches.provider_metadata.lock().await.contains("PROVIDER_METADATA"));
505+
506+
let management_url = oidc
507+
.account_management_url(Some(AccountManagementActionFull::Profile))
508+
.await
509+
.expect("We should be able to fetch the account management url");
510+
511+
assert!(management_url.is_some());
512+
513+
// Check that the provider metadata has been inserted into the cache.
514+
assert!(client.inner.caches.provider_metadata.lock().await.contains("PROVIDER_METADATA"));
515+
}

crates/matrix-sdk/src/client/caches.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,20 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#[cfg(feature = "experimental-oidc")]
16+
use mas_oidc_client::types::oidc::VerifiedProviderMetadata;
17+
#[cfg(feature = "experimental-oidc")]
18+
use matrix_sdk_base::ttl_cache::TtlCache;
1519
use tokio::sync::RwLock;
1620

1721
use super::ClientServerCapabilities;
1822

1923
/// A collection of in-memory data that the `Client` might want to cache to
2024
/// avoid hitting the homeserver every time users request the data.
21-
pub(super) struct ClientCaches {
25+
pub(crate) struct ClientCaches {
2226
/// Server capabilities, either prefilled during building or fetched from
2327
/// the server.
2428
pub(super) server_capabilities: RwLock<ClientServerCapabilities>,
29+
#[cfg(feature = "experimental-oidc")]
30+
pub(crate) provider_metadata: tokio::sync::Mutex<TtlCache<String, VerifiedProviderMetadata>>,
2531
}

crates/matrix-sdk/src/client/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ use matrix_sdk_base::{
3636
BaseClient, RoomInfoNotableUpdate, RoomState, RoomStateFilter, SendOutsideWasm, SessionMeta,
3737
StateStoreDataKey, StateStoreDataValue, SyncOutsideWasm,
3838
};
39+
#[cfg(feature = "experimental-oidc")]
40+
use matrix_sdk_common::ttl_cache::TtlCache;
3941
#[cfg(feature = "e2e-encryption")]
4042
use ruma::events::{room::encryption::RoomEncryptionEventContent, InitialStateEvent};
4143
use ruma::{
@@ -352,7 +354,11 @@ impl ClientInner {
352354
#[cfg(feature = "e2e-encryption")] encryption_settings: EncryptionSettings,
353355
cross_process_store_locks_holder_name: String,
354356
) -> Arc<Self> {
355-
let caches = ClientCaches { server_capabilities: server_capabilities.into() };
357+
let caches = ClientCaches {
358+
server_capabilities: server_capabilities.into(),
359+
#[cfg(feature = "experimental-oidc")]
360+
provider_metadata: Mutex::new(TtlCache::new()),
361+
};
356362

357363
let client = Self {
358364
server,

0 commit comments

Comments
 (0)