1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
56use crate::box_error::BoxError;
7use crate::client::runtime_components::sealed::ValidateConfig;
8use crate::client::runtime_components::{RuntimeComponents, RuntimeComponentsBuilder};
9use crate::impl_shared_conversions;
10use aws_smithy_types::config_bag::ConfigBag;
11use std::any::Any;
12use std::fmt;
13use std::fmt::Debug;
14use std::sync::atomic::{AtomicUsize, Ordering};
15use std::sync::Arc;
16use std::time::SystemTime;
1718#[cfg(feature = "http-auth")]
19pub mod http;
2021new_type_future! {
22#[doc = "Future for [`IdentityResolver::resolve_identity`]."]
23pub struct IdentityFuture<'a, Identity, BoxError>;
24}
2526static NEXT_CACHE_PARTITION: AtomicUsize = AtomicUsize::new(0);
2728/// Cache partition key for identity caching.
29///
30/// Identities need cache partitioning because a single identity cache is used across
31/// multiple identity providers across multiple auth schemes. In addition, a single auth scheme
32/// may have many different identity providers due to operation-level config overrides.
33///
34/// This partition _must_ be respected when retrieving from the identity cache and _should_
35/// be part of the cache key.
36///
37/// Calling [`IdentityCachePartition::new`] will create a new globally unique cache partition key,
38/// and the [`SharedIdentityResolver`] will automatically create and store a partion on construction.
39/// Thus, every configured identity resolver will be assigned a unique partition.
40#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
41pub struct IdentityCachePartition(usize);
4243impl IdentityCachePartition {
44/// Create a new globally unique cache partition key.
45pub fn new() -> Self {
46Self(NEXT_CACHE_PARTITION.fetch_add(1, Ordering::Relaxed))
47 }
4849/// Helper for unit tests to create an identity cache partition with a known value.
50#[cfg(feature = "test-util")]
51pub fn new_for_tests(value: usize) -> IdentityCachePartition {
52Self(value)
53 }
54}
5556/// Caching resolver for identities.
57pub trait ResolveCachedIdentity: fmt::Debug + Send + Sync {
58/// Returns a cached identity, or resolves an identity and caches it if its not already cached.
59fn resolve_cached_identity<'a>(
60&'a self,
61 resolver: SharedIdentityResolver,
62 runtime_components: &'a RuntimeComponents,
63 config_bag: &'a ConfigBag,
64 ) -> IdentityFuture<'a>;
6566#[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
67fn validate_base_client_config(
68&self,
69 runtime_components: &RuntimeComponentsBuilder,
70 cfg: &ConfigBag,
71 ) -> Result<(), BoxError> {
72let _ = (runtime_components, cfg);
73Ok(())
74 }
7576#[doc = include_str!("../../rustdoc/validate_final_config.md")]
77fn validate_final_config(
78&self,
79 runtime_components: &RuntimeComponents,
80 cfg: &ConfigBag,
81 ) -> Result<(), BoxError> {
82let _ = (runtime_components, cfg);
83Ok(())
84 }
85}
8687/// Shared identity cache.
88#[derive(Clone, Debug)]
89pub struct SharedIdentityCache(Arc<dyn ResolveCachedIdentity>);
9091impl SharedIdentityCache {
92/// Creates a new [`SharedIdentityCache`] from the given cache implementation.
93pub fn new(cache: impl ResolveCachedIdentity + 'static) -> Self {
94Self(Arc::new(cache))
95 }
96}
9798impl ResolveCachedIdentity for SharedIdentityCache {
99fn resolve_cached_identity<'a>(
100&'a self,
101 resolver: SharedIdentityResolver,
102 runtime_components: &'a RuntimeComponents,
103 config_bag: &'a ConfigBag,
104 ) -> IdentityFuture<'a> {
105self.0
106.resolve_cached_identity(resolver, runtime_components, config_bag)
107 }
108}
109110impl ValidateConfig for SharedIdentityResolver {}
111112impl ValidateConfig for SharedIdentityCache {
113fn validate_base_client_config(
114&self,
115 runtime_components: &RuntimeComponentsBuilder,
116 cfg: &ConfigBag,
117 ) -> Result<(), BoxError> {
118self.0.validate_base_client_config(runtime_components, cfg)
119 }
120121fn validate_final_config(
122&self,
123 runtime_components: &RuntimeComponents,
124 cfg: &ConfigBag,
125 ) -> Result<(), BoxError> {
126self.0.validate_final_config(runtime_components, cfg)
127 }
128}
129130impl_shared_conversions!(convert SharedIdentityCache from ResolveCachedIdentity using SharedIdentityCache::new);
131132/// Resolver for identities.
133///
134/// Every [`AuthScheme`](crate::client::auth::AuthScheme) has one or more compatible
135/// identity resolvers, which are selected from runtime components by the auth scheme
136/// implementation itself.
137///
138/// The identity resolver must return an [`IdentityFuture`] with the resolved identity, or an error
139/// if resolution failed. There is no optionality for identity resolvers. The identity either
140/// resolves successfully, or it fails. The orchestrator will choose exactly one auth scheme
141/// to use, and thus, its chosen identity resolver is the only identity resolver that runs.
142/// There is no fallback to other auth schemes in the absence of an identity.
143pub trait ResolveIdentity: Send + Sync + Debug {
144/// Asynchronously resolves an identity for a request using the given config.
145fn resolve_identity<'a>(
146&'a self,
147 runtime_components: &'a RuntimeComponents,
148 config_bag: &'a ConfigBag,
149 ) -> IdentityFuture<'a>;
150151/// Returns a fallback identity.
152 ///
153 /// This method should be used as a fallback plan, i.e., when a call to `resolve_identity`
154 /// is interrupted by a timeout and its future fails to complete.
155 ///
156 /// The fallback identity should be set aside and ready to be returned
157 /// immediately. Therefore, a new identity should NOT be fetched
158 /// within this method, which might cause a long-running operation.
159fn fallback_on_interrupt(&self) -> Option<Identity> {
160None
161}
162163/// Returns the location of an identity cache associated with this identity resolver.
164 ///
165 /// By default, identity resolvers will use the identity cache stored in runtime components.
166 /// Implementing types can change the cache location if they want to. Refer to [`IdentityCacheLocation`]
167 /// explaining why a concrete identity resolver might want to change the cache location.
168fn cache_location(&self) -> IdentityCacheLocation {
169 IdentityCacheLocation::RuntimeComponents
170 }
171172/// Returns the identity cache partition associated with this identity resolver.
173 ///
174 /// By default this returns `None` and cache partitioning is left up to `SharedIdentityResolver`.
175fn cache_partition(&self) -> Option<IdentityCachePartition> {
176None
177}
178}
179180/// Cache location for identity caching.
181///
182/// Identities are usually cached in the identity cache owned by [`RuntimeComponents`]. However,
183/// we do have identities whose caching mechanism is internally managed by their identity resolver,
184/// in which case we want to avoid the `RuntimeComponents`-owned identity cache interfering with
185/// the internal caching policy.
186#[non_exhaustive]
187#[derive(Copy, Clone, Debug, Eq, PartialEq)]
188pub enum IdentityCacheLocation {
189/// Indicates the identity cache is owned by [`RuntimeComponents`].
190RuntimeComponents,
191/// Indicates the identity cache is internally managed by the identity resolver.
192IdentityResolver,
193}
194195/// Container for a shared identity resolver.
196#[derive(Clone, Debug)]
197pub struct SharedIdentityResolver {
198 inner: Arc<dyn ResolveIdentity>,
199 cache_partition: IdentityCachePartition,
200}
201202impl SharedIdentityResolver {
203/// Creates a new [`SharedIdentityResolver`] from the given resolver.
204pub fn new(resolver: impl ResolveIdentity + 'static) -> Self {
205// NOTE: `IdentityCachePartition` is globally unique by construction so even
206 // custom implementations of `ResolveIdentity::cache_partition()` are unique.
207let partition = match resolver.cache_partition() {
208Some(p) => p,
209None => IdentityCachePartition::new(),
210 };
211212Self {
213 inner: Arc::new(resolver),
214 cache_partition: partition,
215 }
216 }
217218/// Returns the globally unique cache partition key for this identity resolver.
219 ///
220 /// See the [`IdentityCachePartition`] docs for more information on what this is used for
221 /// and why.
222pub fn cache_partition(&self) -> IdentityCachePartition {
223self.cache_partition
224 }
225}
226227impl ResolveIdentity for SharedIdentityResolver {
228fn resolve_identity<'a>(
229&'a self,
230 runtime_components: &'a RuntimeComponents,
231 config_bag: &'a ConfigBag,
232 ) -> IdentityFuture<'a> {
233self.inner.resolve_identity(runtime_components, config_bag)
234 }
235236fn cache_location(&self) -> IdentityCacheLocation {
237self.inner.cache_location()
238 }
239240fn cache_partition(&self) -> Option<IdentityCachePartition> {
241Some(self.cache_partition())
242 }
243}
244245impl_shared_conversions!(convert SharedIdentityResolver from ResolveIdentity using SharedIdentityResolver::new);
246247/// An identity that can be used for authentication.
248///
249/// The [`Identity`] is a container for any arbitrary identity data that may be used
250/// by a [`Sign`](crate::client::auth::Sign) implementation. Under the hood, it
251/// has an `Arc<dyn Any>`, and it is the responsibility of the signer to downcast
252/// to the appropriate data type using the `data()` function.
253///
254/// The `Identity` also holds an optional expiration time, which may duplicate
255/// an expiration time on the identity data. This is because an `Arc<dyn Any>`
256/// can't be downcast to any arbitrary trait, and expiring identities are
257/// common enough to be built-in.
258#[derive(Clone)]
259pub struct Identity {
260 data: Arc<dyn Any + Send + Sync>,
261#[allow(clippy::type_complexity)]
262data_debug: Arc<dyn (Fn(&Arc<dyn Any + Send + Sync>) -> &dyn Debug) + Send + Sync>,
263 expiration: Option<SystemTime>,
264}
265266impl Identity {
267/// Creates a new identity with the given data and expiration time.
268pub fn new<T>(data: T, expiration: Option<SystemTime>) -> Self
269where
270T: Any + Debug + Send + Sync,
271 {
272Self {
273 data: Arc::new(data),
274 data_debug: Arc::new(|d| d.downcast_ref::<T>().expect("type-checked") as _),
275 expiration,
276 }
277 }
278279/// Returns the raw identity data.
280pub fn data<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
281self.data.downcast_ref()
282 }
283284/// Returns the expiration time for this identity, if any.
285pub fn expiration(&self) -> Option<SystemTime> {
286self.expiration
287 }
288}
289290impl Debug for Identity {
291fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 f.debug_struct("Identity")
293 .field("data", (self.data_debug)(&self.data))
294 .field("expiration", &self.expiration)
295 .finish()
296 }
297}
298299#[cfg(test)]
300mod tests {
301use super::*;
302use aws_smithy_async::time::{SystemTimeSource, TimeSource};
303304#[test]
305fn check_send_sync() {
306fn is_send_sync<T: Send + Sync>(_: T) {}
307 is_send_sync(Identity::new("foo", None));
308 }
309310#[test]
311fn create_retrieve_identity() {
312#[derive(Debug)]
313struct MyIdentityData {
314 first: String,
315 last: String,
316 }
317318let ts = SystemTimeSource::new();
319let expiration = ts.now();
320let identity = Identity::new(
321 MyIdentityData {
322 first: "foo".into(),
323 last: "bar".into(),
324 },
325Some(expiration),
326 );
327328assert_eq!("foo", identity.data::<MyIdentityData>().unwrap().first);
329assert_eq!("bar", identity.data::<MyIdentityData>().unwrap().last);
330assert_eq!(Some(expiration), identity.expiration());
331 }
332}