1use aws_smithy_types::config_bag::Layer;
7use aws_smithy_types::date_time::Format;
8use aws_smithy_types::type_erasure::TypeErasedBox;
9use std::any::{Any, TypeId};
10use std::collections::HashMap;
11use std::fmt;
12use std::fmt::{Debug, Formatter};
13use std::sync::Arc;
14use std::time::{SystemTime, UNIX_EPOCH};
15use zeroize::Zeroizing;
16
17use aws_smithy_runtime_api::client::identity::Identity;
18
19use crate::attributes::AccountId;
20use crate::credential_feature::AwsCredentialFeature;
21
22pub struct Credentials(Arc<Inner>, HashMap<TypeId, TypeErasedBox>);
30
31impl Clone for Credentials {
32 fn clone(&self) -> Self {
33 let mut new_map = HashMap::with_capacity(self.1.len());
34 for (k, v) in &self.1 {
35 new_map.insert(
36 *k,
37 v.try_clone()
38 .expect("values are guaranteed to implement `Clone` via `set_property`"),
39 );
40 }
41 Self(self.0.clone(), new_map)
42 }
43}
44
45impl PartialEq for Credentials {
46 #[inline] fn eq(&self, other: &Credentials) -> bool {
48 self.0 == other.0
49 }
50}
51
52impl Eq for Credentials {}
53
54#[derive(Clone, Eq, PartialEq)]
55struct Inner {
56 access_key_id: Zeroizing<String>,
57 secret_access_key: Zeroizing<String>,
58 session_token: Zeroizing<Option<String>>,
59
60 expires_after: Option<SystemTime>,
68
69 account_id: Option<AccountId>,
72
73 provider_name: &'static str,
74}
75
76impl Debug for Credentials {
77 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78 let mut creds = f.debug_struct("Credentials");
79 creds
80 .field("provider_name", &self.0.provider_name)
81 .field("access_key_id", &self.0.access_key_id.as_str())
82 .field("secret_access_key", &"** redacted **");
83 if let Some(expiry) = self.expiry() {
84 if let Some(formatted) = expiry.duration_since(UNIX_EPOCH).ok().and_then(|dur| {
85 aws_smithy_types::DateTime::from_secs(dur.as_secs() as _)
86 .fmt(Format::DateTime)
87 .ok()
88 }) {
89 creds.field("expires_after", &formatted);
90 } else {
91 creds.field("expires_after", &expiry);
92 }
93 } else {
94 creds.field("expires_after", &"never");
95 }
96 if let Some(account_id) = &self.0.account_id {
97 creds.field("account_id", &account_id.as_str());
98 }
99 for (i, prop) in self.1.values().enumerate() {
100 creds.field(&format!("property_{i}"), prop);
101 }
102 creds.finish()
103 }
104}
105
106#[cfg(feature = "hardcoded-credentials")]
107const STATIC_CREDENTIALS: &str = "Static";
108
109impl Credentials {
110 pub fn builder() -> CredentialsBuilder {
112 CredentialsBuilder::default()
113 }
114
115 pub fn new(
120 access_key_id: impl Into<String>,
121 secret_access_key: impl Into<String>,
122 session_token: Option<String>,
123 expires_after: Option<SystemTime>,
124 provider_name: &'static str,
125 ) -> Self {
126 Credentials(
127 Arc::new(Inner {
128 access_key_id: Zeroizing::new(access_key_id.into()),
129 secret_access_key: Zeroizing::new(secret_access_key.into()),
130 session_token: Zeroizing::new(session_token),
131 expires_after,
132 account_id: None,
133 provider_name,
134 }),
135 HashMap::new(),
136 )
137 }
138
139 #[cfg(feature = "hardcoded-credentials")]
181 pub fn from_keys(
182 access_key_id: impl Into<String>,
183 secret_access_key: impl Into<String>,
184 session_token: Option<String>,
185 ) -> Self {
186 Self::new(
187 access_key_id,
188 secret_access_key,
189 session_token,
190 None,
191 STATIC_CREDENTIALS,
192 )
193 }
194
195 pub fn access_key_id(&self) -> &str {
197 &self.0.access_key_id
198 }
199
200 pub fn secret_access_key(&self) -> &str {
202 &self.0.secret_access_key
203 }
204
205 pub fn expiry(&self) -> Option<SystemTime> {
207 self.0.expires_after
208 }
209
210 pub fn expiry_mut(&mut self) -> &mut Option<SystemTime> {
212 &mut Arc::make_mut(&mut self.0).expires_after
213 }
214
215 pub fn account_id(&self) -> Option<&AccountId> {
217 self.0.account_id.as_ref()
218 }
219
220 pub fn session_token(&self) -> Option<&str> {
222 self.0.session_token.as_deref()
223 }
224
225 #[doc(hidden)]
227 pub fn set_property<T: Any + Clone + Debug + Send + Sync + 'static>(&mut self, prop: T) {
228 self.1
229 .insert(TypeId::of::<T>(), TypeErasedBox::new_with_clone(prop));
230 }
231
232 #[doc(hidden)]
234 pub fn get_property<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
235 self.1
236 .get(&TypeId::of::<T>())
237 .and_then(|b| b.downcast_ref())
238 }
239
240 #[doc(hidden)]
242 pub fn get_property_mut<T: Any + Debug + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
243 self.1
244 .get_mut(&TypeId::of::<T>())
245 .and_then(|b| b.downcast_mut())
246 }
247
248 #[doc(hidden)]
251 pub fn get_property_mut_or_default<T: Any + Clone + Debug + Default + Send + Sync + 'static>(
252 &mut self,
253 ) -> &mut T {
254 self.1
255 .entry(TypeId::of::<T>())
256 .or_insert_with(|| TypeErasedBox::new_with_clone(T::default()))
257 .downcast_mut()
258 .expect("typechecked")
259 }
260}
261
262#[derive(Default, Clone)]
267#[allow(missing_debug_implementations)] pub struct CredentialsBuilder {
269 access_key_id: Option<Zeroizing<String>>,
270 secret_access_key: Option<Zeroizing<String>>,
271 session_token: Zeroizing<Option<String>>,
272 expires_after: Option<SystemTime>,
273 account_id: Option<AccountId>,
274 provider_name: Option<&'static str>,
275}
276
277impl CredentialsBuilder {
278 pub fn access_key_id(mut self, access_key_id: impl Into<String>) -> Self {
280 self.access_key_id = Some(Zeroizing::new(access_key_id.into()));
281 self
282 }
283
284 pub fn secret_access_key(mut self, secret_access_key: impl Into<String>) -> Self {
286 self.secret_access_key = Some(Zeroizing::new(secret_access_key.into()));
287 self
288 }
289
290 pub fn session_token(mut self, session_token: impl Into<String>) -> Self {
292 self.set_session_token(Some(session_token.into()));
293 self
294 }
295
296 pub fn set_session_token(&mut self, session_token: Option<String>) {
298 self.session_token = Zeroizing::new(session_token);
299 }
300
301 pub fn expiry(mut self, expiry: SystemTime) -> Self {
303 self.set_expiry(Some(expiry));
304 self
305 }
306
307 pub fn set_expiry(&mut self, expiry: Option<SystemTime>) {
309 self.expires_after = expiry;
310 }
311
312 pub fn account_id(mut self, account_id: impl Into<AccountId>) -> Self {
314 self.set_account_id(Some(account_id.into()));
315 self
316 }
317
318 pub fn set_account_id(&mut self, account_id: Option<AccountId>) {
320 self.account_id = account_id;
321 }
322
323 pub fn provider_name(mut self, provider_name: &'static str) -> Self {
325 self.provider_name = Some(provider_name);
326 self
327 }
328
329 pub fn build(self) -> Credentials {
331 Credentials(
332 Arc::new(Inner {
333 access_key_id: self
334 .access_key_id
335 .expect("required field `access_key_id` missing"),
336 secret_access_key: self
337 .secret_access_key
338 .expect("required field `secret_access_key` missing"),
339 session_token: self.session_token,
340 expires_after: self.expires_after,
341 account_id: self.account_id,
342 provider_name: self
343 .provider_name
344 .expect("required field `provider_name` missing"),
345 }),
346 HashMap::new(),
347 )
348 }
349}
350
351#[cfg(feature = "test-util")]
352impl Credentials {
353 pub fn for_tests() -> Self {
355 Self::new(
356 "ANOTREAL",
357 "notrealrnrELgWzOk3IfjzDKtFBhDby",
358 None,
359 None,
360 "test",
361 )
362 }
363
364 pub fn for_tests_with_session_token() -> Self {
366 Self::new(
367 "ANOTREAL",
368 "notrealrnrELgWzOk3IfjzDKtFBhDby",
369 Some("notarealsessiontoken".to_string()),
370 None,
371 "test",
372 )
373 }
374}
375
376#[cfg(feature = "test-util")]
377impl CredentialsBuilder {
378 pub fn for_tests() -> Self {
381 CredentialsBuilder::default()
382 .access_key_id("ANOTREAL")
383 .secret_access_key("notrealrnrELgWzOk3IfjzDKtFBhDby")
384 .provider_name("test")
385 }
386}
387
388impl From<Credentials> for Identity {
389 fn from(val: Credentials) -> Self {
390 let expiry = val.expiry();
391 let mut builder = if let Some(account_id) = val.account_id() {
392 Identity::builder().property(account_id.clone())
393 } else {
394 Identity::builder()
395 };
396
397 builder.set_expiration(expiry);
398
399 if let Some(features) = val.get_property::<Vec<AwsCredentialFeature>>().cloned() {
400 let mut layer = Layer::new("IdentityResolutionFeatureIdTracking");
401 for feat in features {
402 layer.store_append(feat);
403 }
404 builder.set_property(layer.freeze());
405 }
406
407 builder.data(val).build().expect("set required fields")
408 }
409}
410
411#[cfg(test)]
412mod test {
413 use crate::Credentials;
414 use std::time::{Duration, UNIX_EPOCH};
415
416 #[cfg(feature = "test-util")]
417 use crate::credential_feature::AwsCredentialFeature;
418
419 #[test]
420 fn debug_impl() {
421 let creds = Credentials::new(
422 "akid",
423 "secret",
424 Some("token".into()),
425 Some(UNIX_EPOCH + Duration::from_secs(1234567890)),
426 "debug tester",
427 );
428 assert_eq!(
429 format!("{:?}", creds),
430 r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z" }"#
431 );
432
433 let creds = Credentials::builder()
435 .access_key_id("akid")
436 .secret_access_key("secret")
437 .session_token("token")
438 .expiry(UNIX_EPOCH + Duration::from_secs(1234567890))
439 .account_id("012345678901")
440 .provider_name("debug tester")
441 .build();
442 assert_eq!(
443 format!("{:?}", creds),
444 r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z", account_id: "012345678901" }"#
445 );
446 }
447
448 #[cfg(feature = "test-util")]
449 #[test]
450 fn equality_ignores_properties() {
451 #[derive(Clone, Debug)]
452 struct Foo;
453 let mut creds1 = Credentials::for_tests_with_session_token();
454 creds1.set_property(AwsCredentialFeature::CredentialsCode);
455
456 let mut creds2 = Credentials::for_tests_with_session_token();
457 creds2.set_property(Foo);
458
459 assert_eq!(creds1, creds2)
460 }
461
462 #[cfg(feature = "test-util")]
463 #[test]
464 fn identity_inherits_feature_properties() {
465 use aws_smithy_runtime_api::client::identity::Identity;
466 use aws_smithy_types::config_bag::FrozenLayer;
467
468 let mut creds = Credentials::for_tests_with_session_token();
469 let mut feature_props = vec![
470 AwsCredentialFeature::CredentialsCode,
471 AwsCredentialFeature::CredentialsStsSessionToken,
472 ];
473 creds.set_property(feature_props.clone());
474
475 let identity = Identity::from(creds);
476
477 let maybe_props = identity
478 .property::<FrozenLayer>()
479 .unwrap()
480 .load::<AwsCredentialFeature>()
481 .cloned()
482 .collect::<Vec<AwsCredentialFeature>>();
483
484 feature_props.reverse();
486 assert_eq!(maybe_props, feature_props)
487 }
488}