aws_runtime/static_stability/
invalidation.rs1use aws_smithy_runtime::client::orchestrator::InvalidateResolvedIdentity;
9use aws_smithy_runtime_api::box_error::BoxError;
10use aws_smithy_runtime_api::client::interceptors::context::AfterDeserializationInterceptorContextRef;
11use aws_smithy_runtime_api::client::interceptors::Intercept;
12use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
13use aws_smithy_types::config_bag::ConfigBag;
14use aws_smithy_types::error::metadata::ProvideErrorMetadata;
15use std::error::Error as StdError;
16use std::fmt;
17use std::marker::PhantomData;
18
19const CREDENTIAL_AUTH_FAILURE_ERRORS: &[&str] =
23 &["ExpiredToken", "ExpiredTokenException", "InvalidToken"];
24
25pub struct CredentialAuthFailureInterceptor<E> {
33 _marker: PhantomData<fn() -> E>,
34}
35
36impl<E> CredentialAuthFailureInterceptor<E> {
37 pub fn new() -> Self {
39 Self {
40 _marker: PhantomData,
41 }
42 }
43}
44
45impl<E> Default for CredentialAuthFailureInterceptor<E> {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl<E> fmt::Debug for CredentialAuthFailureInterceptor<E> {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.write_str("CredentialAuthFailureInterceptor")
54 }
55}
56
57impl<E> Intercept for CredentialAuthFailureInterceptor<E>
58where
59 E: StdError + ProvideErrorMetadata + Send + Sync + 'static,
60{
61 fn name(&self) -> &'static str {
62 "CredentialAuthFailure"
63 }
64
65 fn read_after_deserialization(
66 &self,
67 context: &AfterDeserializationInterceptorContextRef<'_>,
68 _runtime_components: &RuntimeComponents,
69 cfg: &mut ConfigBag,
70 ) -> Result<(), BoxError> {
71 let is_auth_failure = context
72 .output_or_error()
73 .err()
74 .and_then(|err| err.as_operation_error())
75 .and_then(|err| err.downcast_ref::<E>())
76 .and_then(|err| err.code())
77 .is_some_and(|code| CREDENTIAL_AUTH_FAILURE_ERRORS.contains(&code));
78 if is_auth_failure {
79 cfg.interceptor_state()
80 .store_put(InvalidateResolvedIdentity);
81 }
82 Ok(())
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, InterceptorContext};
90 use aws_smithy_runtime_api::client::orchestrator::OrchestratorError;
91 use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
92 use aws_smithy_types::error::ErrorMetadata;
93
94 #[derive(Debug)]
96 struct CodedError {
97 metadata: ErrorMetadata,
98 }
99
100 impl CodedError {
101 fn new(code: &'static str) -> Self {
102 Self {
103 metadata: ErrorMetadata::builder().code(code).build(),
104 }
105 }
106 }
107
108 impl fmt::Display for CodedError {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 write!(f, "coded error")
111 }
112 }
113
114 impl StdError for CodedError {}
115
116 impl ProvideErrorMetadata for CodedError {
117 fn meta(&self) -> &ErrorMetadata {
118 &self.metadata
119 }
120 }
121
122 fn sets_invalidate_marker(code: &'static str) -> bool {
125 let interceptor = CredentialAuthFailureInterceptor::<CodedError>::new();
126 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
127 let mut cfg = ConfigBag::base();
128 let mut ctx = InterceptorContext::new(Input::doesnt_matter());
129 ctx.set_output_or_error(Err(OrchestratorError::operation(Error::erase(
130 CodedError::new(code),
131 ))));
132 let ctx_ref = AfterDeserializationInterceptorContextRef::from(&ctx);
133 interceptor
134 .read_after_deserialization(&ctx_ref, &rc, &mut cfg)
135 .unwrap();
136 cfg.load::<InvalidateResolvedIdentity>().is_some()
137 }
138
139 #[test]
140 fn triggers_on_expired_and_invalid_token_only() {
141 assert!(
142 sets_invalidate_marker("ExpiredToken"),
143 "ExpiredToken must trigger invalidation"
144 );
145 assert!(
146 sets_invalidate_marker("ExpiredTokenException"),
147 "STS/SSO-OIDC ExpiredTokenException must trigger invalidation"
148 );
149 assert!(
150 sets_invalidate_marker("InvalidToken"),
151 "InvalidToken must trigger invalidation"
152 );
153 assert!(
155 !sets_invalidate_marker("AccessDenied"),
156 "AccessDenied is authz, not credential validity"
157 );
158 assert!(
159 !sets_invalidate_marker("ThrottlingException"),
160 "unrelated errors must not trigger invalidation"
161 );
162 }
163}