1use aws_smithy_types::date_time::Format;
7use std::fmt;
8use std::fmt::{Debug, Formatter};
9use std::sync::Arc;
10use std::time::{SystemTime, UNIX_EPOCH};
11use zeroize::Zeroizing;
12
13use aws_smithy_runtime_api::client::identity::Identity;
14
15use crate::attributes::AccountId;
16
17#[derive(Clone, Eq, PartialEq)]
25pub struct Credentials(Arc<Inner>);
26
27#[derive(Clone, Eq, PartialEq)]
28struct Inner {
29 access_key_id: Zeroizing<String>,
30 secret_access_key: Zeroizing<String>,
31 session_token: Zeroizing<Option<String>>,
32
33 expires_after: Option<SystemTime>,
41
42 account_id: Option<AccountId>,
45
46 provider_name: &'static str,
47}
48
49impl Debug for Credentials {
50 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
51 let mut creds = f.debug_struct("Credentials");
52 creds
53 .field("provider_name", &self.0.provider_name)
54 .field("access_key_id", &self.0.access_key_id.as_str())
55 .field("secret_access_key", &"** redacted **");
56 if let Some(expiry) = self.expiry() {
57 if let Some(formatted) = expiry.duration_since(UNIX_EPOCH).ok().and_then(|dur| {
58 aws_smithy_types::DateTime::from_secs(dur.as_secs() as _)
59 .fmt(Format::DateTime)
60 .ok()
61 }) {
62 creds.field("expires_after", &formatted);
63 } else {
64 creds.field("expires_after", &expiry);
65 }
66 } else {
67 creds.field("expires_after", &"never");
68 }
69 if let Some(account_id) = &self.0.account_id {
70 creds.field("account_id", &account_id.as_str());
71 }
72 creds.finish()
73 }
74}
75
76#[cfg(feature = "hardcoded-credentials")]
77const STATIC_CREDENTIALS: &str = "Static";
78
79impl Credentials {
80 pub fn builder() -> CredentialsBuilder {
82 CredentialsBuilder::default()
83 }
84
85 pub fn new(
90 access_key_id: impl Into<String>,
91 secret_access_key: impl Into<String>,
92 session_token: Option<String>,
93 expires_after: Option<SystemTime>,
94 provider_name: &'static str,
95 ) -> Self {
96 Credentials(Arc::new(Inner {
97 access_key_id: Zeroizing::new(access_key_id.into()),
98 secret_access_key: Zeroizing::new(secret_access_key.into()),
99 session_token: Zeroizing::new(session_token),
100 expires_after,
101 account_id: None,
102 provider_name,
103 }))
104 }
105
106 #[cfg(feature = "hardcoded-credentials")]
148 pub fn from_keys(
149 access_key_id: impl Into<String>,
150 secret_access_key: impl Into<String>,
151 session_token: Option<String>,
152 ) -> Self {
153 Self::new(
154 access_key_id,
155 secret_access_key,
156 session_token,
157 None,
158 STATIC_CREDENTIALS,
159 )
160 }
161
162 pub fn access_key_id(&self) -> &str {
164 &self.0.access_key_id
165 }
166
167 pub fn secret_access_key(&self) -> &str {
169 &self.0.secret_access_key
170 }
171
172 pub fn expiry(&self) -> Option<SystemTime> {
174 self.0.expires_after
175 }
176
177 pub fn expiry_mut(&mut self) -> &mut Option<SystemTime> {
179 &mut Arc::make_mut(&mut self.0).expires_after
180 }
181
182 pub fn account_id(&self) -> Option<&AccountId> {
184 self.0.account_id.as_ref()
185 }
186
187 pub fn session_token(&self) -> Option<&str> {
189 self.0.session_token.as_deref()
190 }
191}
192
193#[derive(Default, Clone)]
198#[allow(missing_debug_implementations)] pub struct CredentialsBuilder {
200 access_key_id: Option<Zeroizing<String>>,
201 secret_access_key: Option<Zeroizing<String>>,
202 session_token: Zeroizing<Option<String>>,
203 expires_after: Option<SystemTime>,
204 account_id: Option<AccountId>,
205 provider_name: Option<&'static str>,
206}
207
208impl CredentialsBuilder {
209 pub fn access_key_id(mut self, access_key_id: impl Into<String>) -> Self {
211 self.access_key_id = Some(Zeroizing::new(access_key_id.into()));
212 self
213 }
214
215 pub fn secret_access_key(mut self, secret_access_key: impl Into<String>) -> Self {
217 self.secret_access_key = Some(Zeroizing::new(secret_access_key.into()));
218 self
219 }
220
221 pub fn session_token(mut self, session_token: impl Into<String>) -> Self {
223 self.set_session_token(Some(session_token.into()));
224 self
225 }
226
227 pub fn set_session_token(&mut self, session_token: Option<String>) {
229 self.session_token = Zeroizing::new(session_token);
230 }
231
232 pub fn expiry(mut self, expiry: SystemTime) -> Self {
234 self.set_expiry(Some(expiry));
235 self
236 }
237
238 pub fn set_expiry(&mut self, expiry: Option<SystemTime>) {
240 self.expires_after = expiry;
241 }
242
243 pub fn account_id(mut self, account_id: impl Into<AccountId>) -> Self {
245 self.set_account_id(Some(account_id.into()));
246 self
247 }
248
249 pub fn set_account_id(&mut self, account_id: Option<AccountId>) {
251 self.account_id = account_id;
252 }
253
254 pub fn provider_name(mut self, provider_name: &'static str) -> Self {
256 self.provider_name = Some(provider_name);
257 self
258 }
259
260 pub fn build(self) -> Credentials {
262 Credentials(Arc::new(Inner {
263 access_key_id: self
264 .access_key_id
265 .expect("required field `access_key_id` missing"),
266 secret_access_key: self
267 .secret_access_key
268 .expect("required field `secret_access_key` missing"),
269 session_token: self.session_token,
270 expires_after: self.expires_after,
271 account_id: self.account_id,
272 provider_name: self
273 .provider_name
274 .expect("required field `provider_name` missing"),
275 }))
276 }
277}
278
279#[cfg(feature = "test-util")]
280impl Credentials {
281 pub fn for_tests() -> Self {
283 Self::new(
284 "ANOTREAL",
285 "notrealrnrELgWzOk3IfjzDKtFBhDby",
286 None,
287 None,
288 "test",
289 )
290 }
291
292 pub fn for_tests_with_session_token() -> Self {
294 Self::new(
295 "ANOTREAL",
296 "notrealrnrELgWzOk3IfjzDKtFBhDby",
297 Some("notarealsessiontoken".to_string()),
298 None,
299 "test",
300 )
301 }
302}
303
304#[cfg(feature = "test-util")]
305impl CredentialsBuilder {
306 pub fn for_tests() -> Self {
309 CredentialsBuilder::default()
310 .access_key_id("ANOTREAL")
311 .secret_access_key("notrealrnrELgWzOk3IfjzDKtFBhDby")
312 .provider_name("test")
313 }
314}
315
316impl From<Credentials> for Identity {
317 fn from(val: Credentials) -> Self {
318 let expiry = val.expiry();
319 let mut builder = if let Some(account_id) = val.account_id() {
320 Identity::builder().property(account_id.clone()).data(val)
321 } else {
322 Identity::builder().data(val)
323 };
324 builder.set_expiration(expiry);
325 builder.build().expect("set required fields")
326 }
327}
328
329#[cfg(test)]
330mod test {
331 use crate::Credentials;
332 use std::time::{Duration, UNIX_EPOCH};
333
334 #[test]
335 fn debug_impl() {
336 let creds = Credentials::new(
337 "akid",
338 "secret",
339 Some("token".into()),
340 Some(UNIX_EPOCH + Duration::from_secs(1234567890)),
341 "debug tester",
342 );
343 assert_eq!(
344 format!("{:?}", creds),
345 r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z" }"#
346 );
347
348 let creds = Credentials::builder()
350 .access_key_id("akid")
351 .secret_access_key("secret")
352 .session_token("token")
353 .expiry(UNIX_EPOCH + Duration::from_secs(1234567890))
354 .account_id("012345678901")
355 .provider_name("debug tester")
356 .build();
357 assert_eq!(
358 format!("{:?}", creds),
359 r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z", account_id: "012345678901" }"#
360 );
361 }
362}