Skip to main content

aws_smithy_runtime_api/client/
identity.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use 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 aws_smithy_types::type_erasure::TypeErasedBox;
12use std::any::{Any, TypeId};
13use std::collections::HashMap;
14use std::fmt;
15use std::fmt::Debug;
16use std::sync::atomic::{AtomicUsize, Ordering};
17use std::sync::Arc;
18use std::time::SystemTime;
19
20#[cfg(feature = "http-auth")]
21pub mod http;
22
23new_type_future! {
24    #[doc = "Future for [`IdentityResolver::resolve_identity`]."]
25    pub struct IdentityFuture<'a, Identity, BoxError>;
26}
27
28static NEXT_CACHE_PARTITION: AtomicUsize = AtomicUsize::new(0);
29
30/// Cache partition key for identity caching.
31///
32/// Identities need cache partitioning because a single identity cache is used across
33/// multiple identity providers across multiple auth schemes. In addition, a single auth scheme
34/// may have many different identity providers due to operation-level config overrides.
35///
36/// This partition _must_ be respected when retrieving from the identity cache and _should_
37/// be part of the cache key.
38///
39/// Calling [`IdentityCachePartition::new`] will create a new globally unique cache partition key,
40/// and the [`SharedIdentityResolver`] will automatically create and store a partion on construction.
41/// Thus, every configured identity resolver will be assigned a unique partition.
42#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
43pub struct IdentityCachePartition(usize);
44
45impl IdentityCachePartition {
46    /// Create a new globally unique cache partition key.
47    pub fn new() -> Self {
48        Self(NEXT_CACHE_PARTITION.fetch_add(1, Ordering::Relaxed))
49    }
50
51    /// Helper for unit tests to create an identity cache partition with a known value.
52    #[cfg(feature = "test-util")]
53    pub fn new_for_tests(value: usize) -> IdentityCachePartition {
54        Self(value)
55    }
56}
57
58/// Caching resolver for identities.
59pub trait ResolveCachedIdentity: fmt::Debug + Send + Sync {
60    /// Returns a cached identity, or resolves an identity and caches it if its not already cached.
61    fn resolve_cached_identity<'a>(
62        &'a self,
63        resolver: SharedIdentityResolver,
64        runtime_components: &'a RuntimeComponents,
65        config_bag: &'a ConfigBag,
66    ) -> IdentityFuture<'a>;
67
68    /// Marks the cached identity for refresh.
69    ///
70    /// The default implementation is a no-op; caches override it to support invalidation.
71    fn invalidate(&self, rejected: &Identity) {
72        let _ = rejected;
73    }
74
75    #[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
76    fn validate_base_client_config(
77        &self,
78        runtime_components: &RuntimeComponentsBuilder,
79        cfg: &ConfigBag,
80    ) -> Result<(), BoxError> {
81        let _ = (runtime_components, cfg);
82        Ok(())
83    }
84
85    #[doc = include_str!("../../rustdoc/validate_final_config.md")]
86    fn validate_final_config(
87        &self,
88        runtime_components: &RuntimeComponents,
89        cfg: &ConfigBag,
90    ) -> Result<(), BoxError> {
91        let _ = (runtime_components, cfg);
92        Ok(())
93    }
94}
95
96/// Shared identity cache.
97#[derive(Clone, Debug)]
98pub struct SharedIdentityCache(Arc<dyn ResolveCachedIdentity>);
99
100impl SharedIdentityCache {
101    /// Creates a new [`SharedIdentityCache`] from the given cache implementation.
102    pub fn new(cache: impl ResolveCachedIdentity + 'static) -> Self {
103        Self(Arc::new(cache))
104    }
105}
106
107impl ResolveCachedIdentity for SharedIdentityCache {
108    fn resolve_cached_identity<'a>(
109        &'a self,
110        resolver: SharedIdentityResolver,
111        runtime_components: &'a RuntimeComponents,
112        config_bag: &'a ConfigBag,
113    ) -> IdentityFuture<'a> {
114        self.0
115            .resolve_cached_identity(resolver, runtime_components, config_bag)
116    }
117
118    fn invalidate(&self, rejected: &Identity) {
119        self.0.invalidate(rejected)
120    }
121}
122
123impl ValidateConfig for SharedIdentityResolver {}
124
125impl ValidateConfig for SharedIdentityCache {
126    fn validate_base_client_config(
127        &self,
128        runtime_components: &RuntimeComponentsBuilder,
129        cfg: &ConfigBag,
130    ) -> Result<(), BoxError> {
131        self.0.validate_base_client_config(runtime_components, cfg)
132    }
133
134    fn validate_final_config(
135        &self,
136        runtime_components: &RuntimeComponents,
137        cfg: &ConfigBag,
138    ) -> Result<(), BoxError> {
139        self.0.validate_final_config(runtime_components, cfg)
140    }
141}
142
143impl_shared_conversions!(convert SharedIdentityCache from ResolveCachedIdentity using SharedIdentityCache::new);
144
145/// Resolver for identities.
146///
147/// Every [`AuthScheme`](crate::client::auth::AuthScheme) has one or more compatible
148/// identity resolvers, which are selected from runtime components by the auth scheme
149/// implementation itself.
150///
151/// The identity resolver must return an [`IdentityFuture`] with the resolved identity, or an error
152/// if resolution failed. There is no optionality for identity resolvers. The identity either
153/// resolves successfully, or it fails. The orchestrator will choose exactly one auth scheme
154/// to use, and thus, its chosen identity resolver is the only identity resolver that runs.
155/// There is no fallback to other auth schemes in the absence of an identity.
156pub trait ResolveIdentity: Send + Sync + Debug {
157    /// Asynchronously resolves an identity for a request using the given config.
158    fn resolve_identity<'a>(
159        &'a self,
160        runtime_components: &'a RuntimeComponents,
161        config_bag: &'a ConfigBag,
162    ) -> IdentityFuture<'a>;
163
164    /// Returns a fallback identity.
165    ///
166    /// This method should be used as a fallback plan, i.e., when a call to `resolve_identity`
167    /// is interrupted by a timeout and its future fails to complete.
168    ///
169    /// The fallback identity should be set aside and ready to be returned
170    /// immediately. Therefore, a new identity should NOT be fetched
171    /// within this method, which might cause a long-running operation.
172    fn fallback_on_interrupt(&self) -> Option<Identity> {
173        None
174    }
175
176    /// Returns the location of an identity cache associated with this identity resolver.
177    ///
178    /// By default, identity resolvers will use the identity cache stored in runtime components.
179    /// Implementing types can change the cache location if they want to. Refer to [`IdentityCacheLocation`]
180    /// explaining why a concrete identity resolver might want to change the cache location.
181    fn cache_location(&self) -> IdentityCacheLocation {
182        IdentityCacheLocation::RuntimeComponents
183    }
184
185    /// Returns the identity cache partition associated with this identity resolver.
186    ///
187    /// By default this returns `None` and cache partitioning is left up to `SharedIdentityResolver`.
188    fn cache_partition(&self) -> Option<IdentityCachePartition> {
189        None
190    }
191}
192
193/// Cache location for identity caching.
194///
195/// Identities are usually cached in the identity cache owned by [`RuntimeComponents`]. However,
196/// we do have identities whose caching mechanism is internally managed by their identity resolver,
197/// in which case we want to avoid the `RuntimeComponents`-owned identity cache interfering with
198/// the internal caching policy.
199#[non_exhaustive]
200#[derive(Copy, Clone, Debug, Eq, PartialEq)]
201pub enum IdentityCacheLocation {
202    /// Indicates the identity cache is owned by [`RuntimeComponents`].
203    RuntimeComponents,
204    /// Indicates the identity cache is internally managed by the identity resolver.
205    IdentityResolver,
206}
207
208/// Container for a shared identity resolver.
209#[derive(Clone, Debug)]
210pub struct SharedIdentityResolver {
211    inner: Arc<dyn ResolveIdentity>,
212    cache_partition: IdentityCachePartition,
213}
214
215impl SharedIdentityResolver {
216    /// Creates a new [`SharedIdentityResolver`] from the given resolver.
217    pub fn new(resolver: impl ResolveIdentity + 'static) -> Self {
218        // NOTE: `IdentityCachePartition` is globally unique by construction so even
219        // custom implementations of `ResolveIdentity::cache_partition()` are unique.
220        let partition = match resolver.cache_partition() {
221            Some(p) => p,
222            None => IdentityCachePartition::new(),
223        };
224
225        Self {
226            inner: Arc::new(resolver),
227            cache_partition: partition,
228        }
229    }
230
231    /// Returns the globally unique cache partition key for this identity resolver.
232    ///
233    /// See the [`IdentityCachePartition`] docs for more information on what this is used for
234    /// and why.
235    pub fn cache_partition(&self) -> IdentityCachePartition {
236        self.cache_partition
237    }
238}
239
240impl ResolveIdentity for SharedIdentityResolver {
241    fn resolve_identity<'a>(
242        &'a self,
243        runtime_components: &'a RuntimeComponents,
244        config_bag: &'a ConfigBag,
245    ) -> IdentityFuture<'a> {
246        self.inner.resolve_identity(runtime_components, config_bag)
247    }
248
249    fn cache_location(&self) -> IdentityCacheLocation {
250        self.inner.cache_location()
251    }
252
253    fn cache_partition(&self) -> Option<IdentityCachePartition> {
254        Some(self.cache_partition())
255    }
256}
257
258impl_shared_conversions!(convert SharedIdentityResolver from ResolveIdentity using SharedIdentityResolver::new);
259
260type DataDebug = Arc<dyn (Fn(&Arc<dyn Any + Send + Sync>) -> &dyn Debug) + Send + Sync>;
261
262/// An identity that can be used for authentication.
263///
264/// The [`Identity`] is a container for any arbitrary identity data that may be used
265/// by a [`Sign`](crate::client::auth::Sign) implementation. Under the hood, it
266/// has an `Arc<dyn Any>`, and it is the responsibility of the signer to downcast
267/// to the appropriate data type using the `data()` function.
268///
269/// The `Identity` also holds an optional expiration time, which may duplicate
270/// an expiration time on the identity data. This is because an `Arc<dyn Any>`
271/// can't be downcast to any arbitrary trait, and expiring identities are
272/// common enough to be built-in.
273#[derive(Clone)]
274pub struct Identity {
275    data: Arc<dyn Any + Send + Sync>,
276    data_debug: DataDebug,
277    expiration: Option<SystemTime>,
278    properties: HashMap<TypeId, Arc<TypeErasedBox>>,
279}
280
281impl Identity {
282    /// Creates a new identity with the given data and expiration time.
283    pub fn new<T>(data: T, expiration: Option<SystemTime>) -> Self
284    where
285        T: Any + Debug + Send + Sync,
286    {
287        Self {
288            data: Arc::new(data),
289            data_debug: Arc::new(|d| d.downcast_ref::<T>().expect("type-checked") as _),
290            expiration,
291            properties: HashMap::default(),
292        }
293    }
294
295    /// Returns [`Builder`] for [`Identity`].
296    pub fn builder() -> Builder {
297        Builder::default()
298    }
299
300    /// Returns the raw identity data.
301    pub fn data<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
302        self.data.downcast_ref()
303    }
304
305    /// Returns the expiration time for this identity, if any.
306    pub fn expiration(&self) -> Option<SystemTime> {
307        self.expiration
308    }
309
310    /// Returns arbitrary property associated with this `Identity`.
311    pub fn property<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
312        self.properties
313            .get(&TypeId::of::<T>())
314            .and_then(|b| b.downcast_ref())
315    }
316
317    /// Returns `true` if `self` and `other` share the same underlying identity data allocation.
318    ///
319    /// Compares the `Arc` data-pointer (allocation identity), **not** the data contents: clones of
320    /// an `Identity` share their `data` allocation and compare equal, while independently built
321    /// `Identity` values compare unequal even when their contents are identical.
322    pub fn ptr_eq(&self, other: &Identity) -> bool {
323        Arc::ptr_eq(&self.data, &other.data)
324    }
325}
326
327impl Debug for Identity {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        let mut debug_struct = f.debug_struct("Identity");
330        debug_struct
331            .field("data", (self.data_debug)(&self.data))
332            .field("expiration", &self.expiration);
333        for (i, prop) in self.properties.values().enumerate() {
334            debug_struct.field(&format!("property_{i}"), prop);
335        }
336        debug_struct.finish()
337    }
338}
339
340impl ResolveIdentity for Identity {
341    fn resolve_identity<'a>(
342        &'a self,
343        _runtime_components: &'a RuntimeComponents,
344        _config_bag: &'a ConfigBag,
345    ) -> IdentityFuture<'a> {
346        IdentityFuture::ready(Ok(self.clone()))
347    }
348}
349
350#[derive(Debug)]
351enum ErrorKind {
352    /// Field required to build the target type is missing.
353    MissingRequiredField(&'static str),
354}
355
356/// Error constructing [`Identity`].
357#[derive(Debug)]
358pub struct BuildError {
359    kind: ErrorKind,
360}
361
362impl BuildError {
363    fn missing_required_field(field_name: &'static str) -> Self {
364        BuildError {
365            kind: ErrorKind::MissingRequiredField(field_name),
366        }
367    }
368}
369
370impl fmt::Display for BuildError {
371    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
372        use ErrorKind::*;
373        match self.kind {
374            MissingRequiredField(field_name) => write!(f, "missing required field: `{field_name}`"),
375        }
376    }
377}
378
379impl std::error::Error for BuildError {}
380
381/// Builder for [`Identity`]
382#[derive(Default)]
383pub struct Builder {
384    data: Option<Arc<dyn Any + Send + Sync>>,
385    data_debug: Option<DataDebug>,
386    expiration: Option<SystemTime>,
387    properties: HashMap<TypeId, Arc<TypeErasedBox>>,
388}
389
390impl Builder {
391    /// Set raw identity data for the builder.
392    pub fn data<T: Any + Debug + Send + Sync + 'static>(mut self, data: T) -> Self {
393        self.set_data(data);
394        self
395    }
396
397    /// Set raw identity data for the builder.
398    pub fn set_data<T: Any + Debug + Send + Sync + 'static>(&mut self, data: T) {
399        self.data = Some(Arc::new(data));
400        self.data_debug = Some(Arc::new(|d| {
401            d.downcast_ref::<T>().expect("type-checked") as _
402        }));
403    }
404
405    /// Set expiration for the builder.
406    pub fn expiration(mut self, expiration: SystemTime) -> Self {
407        self.set_expiration(Some(expiration));
408        self
409    }
410
411    /// Set expiration for the builder.
412    pub fn set_expiration(&mut self, expiration: Option<SystemTime>) {
413        self.expiration = expiration;
414    }
415
416    /// Set arbitrary property for the builder.
417    pub fn property<T: Any + Debug + Send + Sync + 'static>(mut self, prop: T) -> Self {
418        self.set_property(prop);
419        self
420    }
421
422    /// Set arbitrary property for the builder.
423    pub fn set_property<T: Any + Debug + Send + Sync + 'static>(&mut self, prop: T) {
424        self.properties
425            .insert(TypeId::of::<T>(), Arc::new(TypeErasedBox::new(prop)));
426    }
427
428    /// Build [`Identity`].
429    pub fn build(self) -> Result<Identity, BuildError> {
430        Ok(Identity {
431            data: self
432                .data
433                .ok_or_else(|| BuildError::missing_required_field("data"))?,
434            data_debug: self
435                .data_debug
436                .expect("should always be set when `data` is set"),
437            expiration: self.expiration,
438            properties: self.properties,
439        })
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use aws_smithy_async::time::{SystemTimeSource, TimeSource};
447
448    #[test]
449    fn check_send_sync() {
450        fn is_send_sync<T: Send + Sync>(_: T) {}
451        is_send_sync(Identity::new("foo", None));
452    }
453
454    #[test]
455    fn create_retrieve_identity() {
456        #[derive(Debug)]
457        struct MyIdentityData {
458            first: String,
459            last: String,
460        }
461
462        let ts = SystemTimeSource::new();
463        let expiration = ts.now();
464        let identity = Identity::new(
465            MyIdentityData {
466                first: "foo".into(),
467                last: "bar".into(),
468            },
469            Some(expiration),
470        );
471
472        assert_eq!("foo", identity.data::<MyIdentityData>().unwrap().first);
473        assert_eq!("bar", identity.data::<MyIdentityData>().unwrap().last);
474        assert_eq!(Some(expiration), identity.expiration());
475    }
476
477    #[test]
478    fn insert_get_identity_properties() {
479        #[derive(Debug)]
480        struct MyIdentityData {
481            first: String,
482            last: String,
483        }
484        #[derive(Debug)]
485        struct PropertyAlpha;
486        #[derive(Debug)]
487        struct PropertyBeta;
488
489        let ts = SystemTimeSource::new();
490        let expiration = ts.now();
491        let identity = Identity::builder()
492            .data(MyIdentityData {
493                first: "foo".into(),
494                last: "bar".into(),
495            })
496            .expiration(expiration)
497            .property(PropertyAlpha)
498            .property(PropertyBeta)
499            .build()
500            .unwrap();
501
502        assert_eq!("foo", identity.data::<MyIdentityData>().unwrap().first);
503        assert_eq!("bar", identity.data::<MyIdentityData>().unwrap().last);
504        assert_eq!(Some(expiration), identity.expiration());
505        assert!(identity.property::<PropertyAlpha>().is_some());
506        assert!(identity.property::<PropertyBeta>().is_some());
507    }
508
509    #[test]
510    fn ptr_eq_true_for_clone() {
511        // A clone shares the same underlying `data` allocation, so it is `ptr_eq` to the original.
512        let identity = Identity::new("some-identity-data", None);
513        let clone = identity.clone();
514        assert!(identity.ptr_eq(&clone));
515        assert!(clone.ptr_eq(&identity)); // symmetric
516        assert!(identity.ptr_eq(&identity)); // reflexive
517    }
518
519    #[test]
520    fn ptr_eq_false_for_independently_built_identities() {
521        // Identities built separately have distinct `data` allocations, so they are not `ptr_eq`
522        // even when their contents are identical.
523        let a = Identity::new("same-data", None);
524        let b = Identity::new("same-data", None);
525        assert!(!a.ptr_eq(&b));
526    }
527}