1use crate::{
9 AwsHttpClient, Result,
10 ops::iam::IamOps,
11 types::iam::{
12 AttachRolePolicyRequest, CreateServiceLinkedRoleRequest, CreateServiceLinkedRoleResponse,
13 DeleteAccessKeyRequest, DeleteUserPolicyRequest, DetachRolePolicyRequest,
14 DetachUserPolicyRequest, GenerateCredentialReportResponse, GetAccessKeyLastUsedRequest,
15 GetAccessKeyLastUsedResponse, GetAccountPasswordPolicyResponse, GetAccountSummaryResponse,
16 GetCredentialReportResponse, GetLoginProfileRequest, GetLoginProfileResponse,
17 GetPolicyVersionRequest, GetPolicyVersionResponse, GetUserPolicyRequest,
18 GetUserPolicyResponse, ListAccessKeysRequest, ListAccessKeysResponse,
19 ListAttachedGroupPoliciesRequest, ListAttachedGroupPoliciesResponse,
20 ListAttachedUserPoliciesRequest, ListAttachedUserPoliciesResponse,
21 ListEntitiesForPolicyRequest, ListEntitiesForPolicyResponse, ListGroupsForUserRequest,
22 ListGroupsForUserResponse, ListMFADevicesRequest, ListMFADevicesResponse,
23 ListPoliciesRequest, ListPoliciesResponse, ListRolesRequest, ListRolesResponse,
24 ListServerCertificatesRequest, ListServerCertificatesResponse, ListUserPoliciesRequest,
25 ListUserPoliciesResponse, ListUsersRequest, ListUsersResponse,
26 ListVirtualMFADevicesRequest, ListVirtualMFADevicesResponse, Policy,
27 UpdateAccessKeyRequest, UpdateAccountPasswordPolicyRequest, VirtualMFADevice,
28 },
29};
30
31pub struct IamClient<'a> {
33 ops: IamOps<'a>,
34}
35
36impl<'a> IamClient<'a> {
37 pub(crate) fn new(client: &'a AwsHttpClient) -> Self {
39 Self {
40 ops: IamOps::new(client),
41 }
42 }
43
44 pub async fn list_users(&self) -> Result<ListUsersResponse> {
46 let body = ListUsersRequest::default();
47 self.ops.list_users(&body).await
48 }
49
50 pub async fn list_attached_user_policies(
52 &self,
53 user_name: &str,
54 ) -> Result<ListAttachedUserPoliciesResponse> {
55 let body = ListAttachedUserPoliciesRequest {
56 user_name: user_name.to_string(),
57 ..Default::default()
58 };
59 self.ops.list_attached_user_policies(&body).await
60 }
61
62 pub async fn detach_user_policy(&self, user_name: &str, policy_arn: &str) -> Result<()> {
64 let body = DetachUserPolicyRequest {
65 user_name: user_name.to_string(),
66 policy_arn: policy_arn.to_string(),
67 };
68 self.ops.detach_user_policy(&body).await
69 }
70
71 pub async fn delete_access_key(&self, user_name: &str, access_key_id: &str) -> Result<()> {
73 let body = DeleteAccessKeyRequest {
74 user_name: Some(user_name.to_string()),
75 access_key_id: access_key_id.to_string(),
76 };
77 self.ops.delete_access_key(&body).await
78 }
79
80 pub async fn list_access_keys(&self, user_name: &str) -> Result<ListAccessKeysResponse> {
82 let body = ListAccessKeysRequest {
83 user_name: Some(user_name.to_string()),
84 ..Default::default()
85 };
86 self.ops.list_access_keys(&body).await
87 }
88
89 pub async fn get_access_key_last_used(
91 &self,
92 access_key_id: &str,
93 ) -> Result<GetAccessKeyLastUsedResponse> {
94 let body = GetAccessKeyLastUsedRequest {
95 access_key_id: access_key_id.to_string(),
96 };
97 self.ops.get_access_key_last_used(&body).await
98 }
99
100 pub async fn generate_credential_report(&self) -> Result<GenerateCredentialReportResponse> {
102 self.ops.generate_credential_report().await
103 }
104
105 pub async fn get_credential_report(&self) -> Result<GetCredentialReportResponse> {
107 self.ops.get_credential_report().await
108 }
109
110 pub async fn update_access_key(
112 &self,
113 user_name: &str,
114 access_key_id: &str,
115 status: &str,
116 ) -> Result<()> {
117 let body = UpdateAccessKeyRequest {
118 user_name: Some(user_name.to_string()),
119 access_key_id: access_key_id.to_string(),
120 status: status.to_string(),
121 };
122 self.ops.update_access_key(&body).await
123 }
124
125 pub async fn list_mfa_devices(&self, user_name: &str) -> Result<ListMFADevicesResponse> {
127 let body = ListMFADevicesRequest {
128 user_name: Some(user_name.to_string()),
129 ..Default::default()
130 };
131 self.ops.list_mfa_devices(&body).await
132 }
133
134 pub async fn get_login_profile(&self, user_name: &str) -> Result<GetLoginProfileResponse> {
136 let body = GetLoginProfileRequest {
137 user_name: Some(user_name.to_string()),
138 };
139 self.ops.get_login_profile(&body).await
140 }
141
142 pub async fn get_account_summary(&self) -> Result<GetAccountSummaryResponse> {
144 self.ops.get_account_summary().await
145 }
146
147 pub async fn get_account_password_policy(&self) -> Result<GetAccountPasswordPolicyResponse> {
149 self.ops.get_account_password_policy().await
150 }
151
152 pub async fn update_account_password_policy(
154 &self,
155 body: &UpdateAccountPasswordPolicyRequest,
156 ) -> Result<()> {
157 self.ops.update_account_password_policy(body).await
158 }
159
160 pub async fn list_roles(&self) -> Result<ListRolesResponse> {
162 let body = ListRolesRequest::default();
163 self.ops.list_roles(&body).await
164 }
165
166 pub async fn list_user_policies(&self, user_name: &str) -> Result<ListUserPoliciesResponse> {
168 let body = ListUserPoliciesRequest {
169 user_name: user_name.to_string(),
170 ..Default::default()
171 };
172 self.ops.list_user_policies(&body).await
173 }
174
175 pub async fn list_groups_for_user(&self, user_name: &str) -> Result<ListGroupsForUserResponse> {
177 let body = ListGroupsForUserRequest {
178 user_name: user_name.to_string(),
179 ..Default::default()
180 };
181 self.ops.list_groups_for_user(&body).await
182 }
183
184 pub async fn list_server_certificates(&self) -> Result<ListServerCertificatesResponse> {
186 let body = ListServerCertificatesRequest::default();
187 self.ops.list_server_certificates(&body).await
188 }
189
190 pub async fn delete_user_policy(&self, user_name: &str, policy_name: &str) -> Result<()> {
192 let body = DeleteUserPolicyRequest {
193 user_name: user_name.to_string(),
194 policy_name: policy_name.to_string(),
195 };
196 self.ops.delete_user_policy(&body).await
197 }
198
199 pub async fn attach_role_policy(&self, role_name: &str, policy_arn: &str) -> Result<()> {
201 let body = AttachRolePolicyRequest {
202 role_name: role_name.to_string(),
203 policy_arn: policy_arn.to_string(),
204 };
205 self.ops.attach_role_policy(&body).await
206 }
207
208 pub async fn detach_role_policy(&self, role_name: &str, policy_arn: &str) -> Result<()> {
210 let body = DetachRolePolicyRequest {
211 role_name: role_name.to_string(),
212 policy_arn: policy_arn.to_string(),
213 };
214 self.ops.detach_role_policy(&body).await
215 }
216
217 pub async fn create_service_linked_role(
219 &self,
220 aws_service_name: &str,
221 ) -> Result<CreateServiceLinkedRoleResponse> {
222 let body = CreateServiceLinkedRoleRequest {
223 aws_service_name: aws_service_name.to_string(),
224 ..Default::default()
225 };
226 self.ops.create_service_linked_role(&body).await
227 }
228
229 pub async fn get_user_policy(
231 &self,
232 user_name: &str,
233 policy_name: &str,
234 ) -> Result<GetUserPolicyResponse> {
235 let body = GetUserPolicyRequest {
236 user_name: user_name.to_string(),
237 policy_name: policy_name.to_string(),
238 };
239 self.ops.get_user_policy(&body).await
240 }
241
242 pub async fn list_attached_group_policies(
244 &self,
245 group_name: &str,
246 ) -> Result<ListAttachedGroupPoliciesResponse> {
247 let body = ListAttachedGroupPoliciesRequest {
248 group_name: group_name.to_string(),
249 ..Default::default()
250 };
251 self.ops.list_attached_group_policies(&body).await
252 }
253
254 pub async fn list_virtual_mfa_devices(
261 &self,
262 assignment_status: Option<&str>,
263 ) -> Result<ListVirtualMFADevicesResponse> {
264 let body = ListVirtualMFADevicesRequest {
265 assignment_status: assignment_status.map(str::to_string),
266 ..Default::default()
267 };
268 self.ops.list_virtual_mfa_devices(&body).await
269 }
270
271 pub async fn list_all_virtual_mfa_devices(&self) -> Result<Vec<VirtualMFADevice>> {
277 let mut all = Vec::new();
278 let mut marker: Option<String> = None;
279 loop {
280 let body = ListVirtualMFADevicesRequest {
281 marker: marker.clone(),
282 ..Default::default()
283 };
284 let resp = self.ops.list_virtual_mfa_devices(&body).await?;
285 all.extend(resp.virtual_mfa_devices);
286 match resp.marker {
287 Some(m) if !m.is_empty() && resp.is_truncated == Some(true) => {
288 marker = Some(m);
289 }
290 _ => break,
291 }
292 }
293 Ok(all)
294 }
295
296 pub async fn list_policies(
303 &self,
304 scope: Option<&str>,
305 marker: Option<&str>,
306 ) -> Result<ListPoliciesResponse> {
307 let body = ListPoliciesRequest {
308 scope: scope.map(str::to_string),
309 marker: marker.map(str::to_string),
310 ..Default::default()
311 };
312 self.ops.list_policies(&body).await
313 }
314
315 pub async fn list_all_policies(&self, scope: Option<&str>) -> Result<Vec<Policy>> {
321 let mut all = Vec::new();
322 let mut marker: Option<String> = None;
323 loop {
324 let body = ListPoliciesRequest {
325 scope: scope.map(str::to_string),
326 marker: marker.clone(),
327 ..Default::default()
328 };
329 let resp = self.ops.list_policies(&body).await?;
330 all.extend(resp.policies);
331 match resp.marker {
332 Some(m) if !m.is_empty() && resp.is_truncated == Some(true) => {
333 marker = Some(m);
334 }
335 _ => break,
336 }
337 }
338 Ok(all)
339 }
340
341 pub async fn get_policy_version(
349 &self,
350 policy_arn: &str,
351 version_id: &str,
352 ) -> Result<GetPolicyVersionResponse> {
353 let body = GetPolicyVersionRequest {
354 policy_arn: policy_arn.to_string(),
355 version_id: version_id.to_string(),
356 };
357 self.ops.get_policy_version(&body).await
358 }
359
360 pub async fn list_entities_for_policy(
365 &self,
366 policy_arn: &str,
367 ) -> Result<ListEntitiesForPolicyResponse> {
368 let body = ListEntitiesForPolicyRequest {
369 policy_arn: policy_arn.to_string(),
370 ..Default::default()
371 };
372 self.ops.list_entities_for_policy(&body).await
373 }
374
375 pub async fn list_all_entities_for_policy(
384 &self,
385 policy_arn: &str,
386 ) -> Result<ListEntitiesForPolicyResponse> {
387 let mut groups = Vec::new();
388 let mut users = Vec::new();
389 let mut roles = Vec::new();
390 let mut marker: Option<String> = None;
391 loop {
392 let body = ListEntitiesForPolicyRequest {
393 policy_arn: policy_arn.to_string(),
394 marker: marker.clone(),
395 ..Default::default()
396 };
397 let resp = self.ops.list_entities_for_policy(&body).await?;
398 groups.extend(resp.policy_groups);
399 users.extend(resp.policy_users);
400 roles.extend(resp.policy_roles);
401 match resp.marker {
402 Some(m) if !m.is_empty() && resp.is_truncated == Some(true) => {
403 marker = Some(m);
404 }
405 _ => break,
406 }
407 }
408 Ok(ListEntitiesForPolicyResponse {
409 policy_groups: groups,
410 policy_users: users,
411 policy_roles: roles,
412 is_truncated: Some(false),
413 marker: None,
414 })
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use crate::AwsHttpClient;
421 use crate::mock_client::MockClient;
422 use crate::test_support::iam_mock_helpers::IamMockHelpers;
423
424 fn xml_envelope(action: &str, inner: &str) -> Vec<u8> {
425 format!("<{action}Response><{action}Result>{inner}</{action}Result></{action}Response>")
426 .into_bytes()
427 }
428
429 #[tokio::test]
430 async fn list_users_returns_parsed_users() {
431 let mut mock = MockClient::new();
432 mock.expect_list_users().returning_bytes(xml_envelope(
433 "ListUsers",
434 "<Users>\
435 <member>\
436 <Arn>arn:aws:iam::123456789012:user/alice</Arn>\
437 <UserName>alice</UserName>\
438 <CreateDate>2024-01-15T10:30:00Z</CreateDate>\
439 </member>\
440 </Users>",
441 ));
442
443 let client = AwsHttpClient::from_mock(mock);
444 let response = client.iam().list_users().await.unwrap();
445
446 assert_eq!(response.users.len(), 1);
447 assert_eq!(response.users[0].user_name, "alice");
448 assert_eq!(
449 response.users[0].arn,
450 "arn:aws:iam::123456789012:user/alice"
451 );
452 assert_eq!(response.users[0].create_date, "2024-01-15T10:30:00Z");
453 }
454
455 #[tokio::test]
456 async fn list_users_handles_empty_response() {
457 let mut mock = MockClient::new();
458 mock.expect_list_users()
459 .returning_bytes(xml_envelope("ListUsers", "<Users/>"));
460
461 let client = AwsHttpClient::from_mock(mock);
462 let response = client.iam().list_users().await.unwrap();
463 assert!(response.users.is_empty());
464 }
465
466 #[tokio::test]
467 async fn list_attached_user_policies_returns_policies() {
468 let mut mock = MockClient::new();
469 mock.expect_list_attached_user_policies()
470 .returning_bytes(xml_envelope(
471 "ListAttachedUserPolicies",
472 "<AttachedPolicies>\
473 <member>\
474 <PolicyArn>arn:aws:iam::aws:policy/ReadOnlyAccess</PolicyArn>\
475 <PolicyName>ReadOnlyAccess</PolicyName>\
476 </member>\
477 </AttachedPolicies>",
478 ));
479
480 let client = AwsHttpClient::from_mock(mock);
481 let response = client
482 .iam()
483 .list_attached_user_policies("alice")
484 .await
485 .unwrap();
486
487 assert_eq!(response.attached_policies.len(), 1);
488 assert_eq!(
489 response.attached_policies[0].policy_arn.as_deref(),
490 Some("arn:aws:iam::aws:policy/ReadOnlyAccess")
491 );
492 assert_eq!(
493 response.attached_policies[0].policy_name.as_deref(),
494 Some("ReadOnlyAccess")
495 );
496 }
497
498 #[tokio::test]
499 async fn detach_user_policy_succeeds() {
500 let mut mock = MockClient::new();
501 mock.expect_detach_user_policy()
502 .returning_bytes(xml_envelope("DetachUserPolicy", ""));
503
504 let client = AwsHttpClient::from_mock(mock);
505 let result = client
506 .iam()
507 .detach_user_policy("alice", "arn:aws:iam::aws:policy/ReadOnlyAccess")
508 .await;
509 assert!(result.is_ok());
510 }
511
512 #[tokio::test]
513 async fn delete_access_key_succeeds() {
514 let mut mock = MockClient::new();
515 mock.expect_delete_access_key()
516 .returning_bytes(xml_envelope("DeleteAccessKey", ""));
517
518 let client = AwsHttpClient::from_mock(mock);
519 let result = client
520 .iam()
521 .delete_access_key("alice", "AKIAIOSFODNN7EXAMPLE")
522 .await;
523 assert!(result.is_ok());
524 }
525
526 #[tokio::test]
527 async fn list_access_keys_returns_keys() {
528 let mut mock = MockClient::new();
529 mock.expect_list_access_keys().returning_bytes(xml_envelope(
530 "ListAccessKeys",
531 "<AccessKeyMetadata>\
532 <member>\
533 <UserName>alice</UserName>\
534 <AccessKeyId>AKIAIOSFODNN7EXAMPLE</AccessKeyId>\
535 <Status>Active</Status>\
536 <CreateDate>2024-03-01T12:00:00Z</CreateDate>\
537 </member>\
538 </AccessKeyMetadata>",
539 ));
540
541 let client = AwsHttpClient::from_mock(mock);
542 let response = client.iam().list_access_keys("alice").await.unwrap();
543
544 assert_eq!(response.access_key_metadata.len(), 1);
545 assert_eq!(
546 response.access_key_metadata[0].access_key_id.as_deref(),
547 Some("AKIAIOSFODNN7EXAMPLE")
548 );
549 assert_eq!(
550 response.access_key_metadata[0].user_name.as_deref(),
551 Some("alice")
552 );
553 }
554
555 #[tokio::test]
556 async fn get_access_key_last_used_returns_info() {
557 let mut mock = MockClient::new();
558 mock.expect_get_access_key_last_used()
559 .returning_bytes(xml_envelope(
560 "GetAccessKeyLastUsed",
561 "<UserName>alice</UserName>\
562 <AccessKeyLastUsed>\
563 <ServiceName>s3</ServiceName>\
564 <Region>us-east-1</Region>\
565 <LastUsedDate>2024-06-15T08:00:00Z</LastUsedDate>\
566 </AccessKeyLastUsed>",
567 ));
568
569 let client = AwsHttpClient::from_mock(mock);
570 let response = client
571 .iam()
572 .get_access_key_last_used("AKIAIOSFODNN7EXAMPLE")
573 .await
574 .unwrap();
575
576 assert_eq!(response.user_name.as_deref(), Some("alice"));
577 let last_used = response.access_key_last_used.unwrap();
578 assert_eq!(last_used.service_name, "s3");
579 assert_eq!(last_used.region, "us-east-1");
580 assert_eq!(
581 last_used.last_used_date.as_deref(),
582 Some("2024-06-15T08:00:00Z")
583 );
584 }
585
586 #[tokio::test]
587 async fn generate_credential_report_returns_state() {
588 let mut mock = MockClient::new();
589 mock.expect_generate_credential_report()
590 .returning_bytes(xml_envelope(
591 "GenerateCredentialReport",
592 "<State>STARTED</State>\
593 <Description>Starting report generation</Description>",
594 ));
595
596 let client = AwsHttpClient::from_mock(mock);
597 let response = client.iam().generate_credential_report().await.unwrap();
598
599 assert_eq!(
600 response.state,
601 Some(crate::types::iam::ReportStateType::Started)
602 );
603 assert_eq!(
604 response.description.as_deref(),
605 Some("Starting report generation")
606 );
607 }
608
609 #[tokio::test]
610 async fn get_credential_report_returns_content() {
611 let mut mock = MockClient::new();
612 mock.expect_get_credential_report()
613 .returning_bytes(xml_envelope(
614 "GetCredentialReport",
615 "<Content>dXNlcixhcm4K</Content>\
616 <ReportFormat>text/csv</ReportFormat>\
617 <GeneratedTime>2024-06-15T10:00:00Z</GeneratedTime>",
618 ));
619
620 let client = AwsHttpClient::from_mock(mock);
621 let response = client.iam().get_credential_report().await.unwrap();
622
623 assert_eq!(response.content.as_deref(), Some("user,arn\n"));
625 assert_eq!(
626 response.report_format,
627 Some(crate::types::iam::ReportFormatType::TextPercsv)
628 );
629 assert_eq!(
630 response.generated_time.as_deref(),
631 Some("2024-06-15T10:00:00Z")
632 );
633 }
634
635 #[tokio::test]
636 async fn update_access_key_succeeds() {
637 let mut mock = MockClient::new();
638 mock.expect_update_access_key()
639 .returning_bytes(xml_envelope("UpdateAccessKey", ""));
640
641 let client = AwsHttpClient::from_mock(mock);
642 let result = client
643 .iam()
644 .update_access_key("alice", "AKIAIOSFODNN7EXAMPLE", "Inactive")
645 .await;
646 assert!(result.is_ok());
647 }
648
649 #[tokio::test]
650 async fn list_mfa_devices_returns_devices() {
651 let mut mock = MockClient::new();
652 mock.expect_list_mfa_devices().returning_bytes(xml_envelope(
653 "ListMFADevices",
654 "<MFADevices>\
655 <member>\
656 <UserName>alice</UserName>\
657 <SerialNumber>arn:aws:iam::123456789012:mfa/alice</SerialNumber>\
658 <EnableDate>2024-01-15T10:00:00Z</EnableDate>\
659 </member>\
660 </MFADevices>",
661 ));
662
663 let client = AwsHttpClient::from_mock(mock);
664 let response = client.iam().list_mfa_devices("alice").await.unwrap();
665
666 assert_eq!(response.mfa_devices.len(), 1);
667 assert_eq!(response.mfa_devices[0].user_name, "alice");
668 assert_eq!(
669 response.mfa_devices[0].serial_number,
670 "arn:aws:iam::123456789012:mfa/alice"
671 );
672 assert_eq!(response.mfa_devices[0].enable_date, "2024-01-15T10:00:00Z");
673 }
674
675 #[tokio::test]
676 async fn get_login_profile_returns_profile() {
677 let mut mock = MockClient::new();
678 mock.expect_get_login_profile()
679 .returning_bytes(xml_envelope(
680 "GetLoginProfile",
681 "<LoginProfile>\
682 <UserName>alice</UserName>\
683 <CreateDate>2024-01-15T10:30:00Z</CreateDate>\
684 <PasswordResetRequired>false</PasswordResetRequired>\
685 </LoginProfile>",
686 ));
687
688 let client = AwsHttpClient::from_mock(mock);
689 let response = client.iam().get_login_profile("alice").await.unwrap();
690
691 assert_eq!(response.login_profile.user_name, "alice");
692 assert_eq!(response.login_profile.create_date, "2024-01-15T10:30:00Z");
693 assert_eq!(response.login_profile.password_reset_required, Some(false));
694 }
695
696 #[tokio::test]
697 async fn get_account_summary_returns_map() {
698 let mut mock = MockClient::new();
699 mock.expect_get_account_summary()
700 .returning_bytes(xml_envelope(
701 "GetAccountSummary",
702 "<SummaryMap>\
703 <entry><key>Users</key><value>5</value></entry>\
704 <entry><key>Roles</key><value>12</value></entry>\
705 <entry><key>AccountMFAEnabled</key><value>1</value></entry>\
706 </SummaryMap>",
707 ));
708
709 let client = AwsHttpClient::from_mock(mock);
710 let response = client.iam().get_account_summary().await.unwrap();
711
712 assert_eq!(response.summary_map.len(), 3);
713 assert_eq!(response.summary_map.get("Users"), Some(&5));
714 assert_eq!(response.summary_map.get("Roles"), Some(&12));
715 assert_eq!(response.summary_map.get("AccountMFAEnabled"), Some(&1));
716 }
717
718 #[tokio::test]
719 async fn get_account_password_policy_returns_policy() {
720 let mut mock = MockClient::new();
721 mock.expect_get_account_password_policy()
722 .returning_bytes(xml_envelope(
723 "GetAccountPasswordPolicy",
724 "<PasswordPolicy>\
725 <MinimumPasswordLength>14</MinimumPasswordLength>\
726 <RequireSymbols>true</RequireSymbols>\
727 <RequireNumbers>true</RequireNumbers>\
728 <RequireUppercaseCharacters>true</RequireUppercaseCharacters>\
729 <RequireLowercaseCharacters>true</RequireLowercaseCharacters>\
730 <MaxPasswordAge>90</MaxPasswordAge>\
731 </PasswordPolicy>",
732 ));
733
734 let client = AwsHttpClient::from_mock(mock);
735 let response = client.iam().get_account_password_policy().await.unwrap();
736
737 let policy = &response.password_policy;
738 assert_eq!(policy.minimum_password_length, Some(14));
739 assert_eq!(policy.require_symbols, Some(true));
740 assert_eq!(policy.require_numbers, Some(true));
741 assert_eq!(policy.require_uppercase_characters, Some(true));
742 assert_eq!(policy.require_lowercase_characters, Some(true));
743 assert_eq!(policy.max_password_age, Some(90));
744 }
745
746 #[tokio::test]
747 async fn update_account_password_policy_succeeds() {
748 let mut mock = MockClient::new();
749 mock.expect_update_account_password_policy()
750 .returning_bytes(xml_envelope("UpdateAccountPasswordPolicy", ""));
751
752 let client = AwsHttpClient::from_mock(mock);
753 let body = crate::types::iam::UpdateAccountPasswordPolicyRequest {
754 minimum_password_length: Some(14),
755 ..Default::default()
756 };
757 let result = client.iam().update_account_password_policy(&body).await;
758 assert!(result.is_ok());
759 }
760
761 #[tokio::test]
762 async fn list_roles_returns_roles() {
763 let mut mock = MockClient::new();
764 mock.expect_list_roles().returning_bytes(xml_envelope(
765 "ListRoles",
766 "<Roles>\
767 <member>\
768 <RoleName>admin-role</RoleName>\
769 <Arn>arn:aws:iam::123456789012:role/admin-role</Arn>\
770 <CreateDate>2024-01-15T10:30:00Z</CreateDate>\
771 <Description>Admin role</Description>\
772 </member>\
773 </Roles>",
774 ));
775
776 let client = AwsHttpClient::from_mock(mock);
777 let response = client.iam().list_roles().await.unwrap();
778
779 assert_eq!(response.roles.len(), 1);
780 assert_eq!(response.roles[0].role_name, "admin-role");
781 assert_eq!(
782 response.roles[0].arn,
783 "arn:aws:iam::123456789012:role/admin-role"
784 );
785 assert_eq!(response.roles[0].create_date, "2024-01-15T10:30:00Z");
786 assert_eq!(response.roles[0].description.as_deref(), Some("Admin role"));
787 }
788
789 #[tokio::test]
790 async fn list_user_policies_returns_names() {
791 let mut mock = MockClient::new();
792 mock.expect_list_user_policies()
793 .returning_bytes(xml_envelope(
794 "ListUserPolicies",
795 "<PolicyNames>\
796 <member>s3-read-policy</member>\
797 <member>ec2-describe-policy</member>\
798 </PolicyNames>",
799 ));
800
801 let client = AwsHttpClient::from_mock(mock);
802 let response = client.iam().list_user_policies("alice").await.unwrap();
803
804 assert_eq!(response.policy_names.len(), 2);
805 assert_eq!(response.policy_names[0], "s3-read-policy");
806 assert_eq!(response.policy_names[1], "ec2-describe-policy");
807 }
808
809 #[tokio::test]
810 async fn list_groups_for_user_returns_groups() {
811 let mut mock = MockClient::new();
812 mock.expect_list_groups_for_user()
813 .returning_bytes(xml_envelope(
814 "ListGroupsForUser",
815 "<Groups>\
816 <member>\
817 <GroupName>developers</GroupName>\
818 <Arn>arn:aws:iam::123456789012:group/developers</Arn>\
819 <CreateDate>2024-02-01T09:00:00Z</CreateDate>\
820 </member>\
821 </Groups>",
822 ));
823
824 let client = AwsHttpClient::from_mock(mock);
825 let response = client.iam().list_groups_for_user("alice").await.unwrap();
826
827 assert_eq!(response.groups.len(), 1);
828 assert_eq!(response.groups[0].group_name, "developers");
829 assert_eq!(
830 response.groups[0].arn,
831 "arn:aws:iam::123456789012:group/developers"
832 );
833 assert_eq!(response.groups[0].create_date, "2024-02-01T09:00:00Z");
834 }
835
836 #[tokio::test]
837 async fn list_server_certificates_returns_certs() {
838 let mut mock = MockClient::new();
839 mock.expect_list_server_certificates()
840 .returning_bytes(xml_envelope(
841 "ListServerCertificates",
842 "<ServerCertificateMetadataList>\
843 <member>\
844 <ServerCertificateName>my-cert</ServerCertificateName>\
845 <Arn>arn:aws:iam::123456789012:server-certificate/my-cert</Arn>\
846 <Expiration>2025-12-31T23:59:59Z</Expiration>\
847 <UploadDate>2024-01-01T00:00:00Z</UploadDate>\
848 </member>\
849 </ServerCertificateMetadataList>",
850 ));
851
852 let client = AwsHttpClient::from_mock(mock);
853 let response = client.iam().list_server_certificates().await.unwrap();
854
855 assert_eq!(response.server_certificate_metadata_list.len(), 1);
856 let cert = &response.server_certificate_metadata_list[0];
857 assert_eq!(cert.server_certificate_name, "my-cert");
858 assert_eq!(
859 cert.arn,
860 "arn:aws:iam::123456789012:server-certificate/my-cert"
861 );
862 assert_eq!(cert.expiration.as_deref(), Some("2025-12-31T23:59:59Z"));
863 assert_eq!(cert.upload_date.as_deref(), Some("2024-01-01T00:00:00Z"));
864 }
865
866 #[tokio::test]
867 async fn delete_user_policy_succeeds() {
868 let mut mock = MockClient::new();
869 mock.expect_delete_user_policy()
870 .returning_bytes(xml_envelope("DeleteUserPolicy", ""));
871
872 let client = AwsHttpClient::from_mock(mock);
873 let result = client.iam().delete_user_policy("alice", "my-policy").await;
874 assert!(result.is_ok());
875 }
876
877 #[tokio::test]
878 async fn attach_role_policy_succeeds() {
879 let mut mock = MockClient::new();
880 mock.expect_attach_role_policy()
881 .returning_bytes(xml_envelope("AttachRolePolicy", ""));
882
883 let client = AwsHttpClient::from_mock(mock);
884 let result = client
885 .iam()
886 .attach_role_policy("my-role", "arn:aws:iam::aws:policy/ReadOnlyAccess")
887 .await;
888 assert!(result.is_ok());
889 }
890
891 #[tokio::test]
892 async fn detach_role_policy_succeeds() {
893 let mut mock = MockClient::new();
894 mock.expect_detach_role_policy()
895 .returning_bytes(xml_envelope("DetachRolePolicy", ""));
896
897 let client = AwsHttpClient::from_mock(mock);
898 let result = client
899 .iam()
900 .detach_role_policy("my-role", "arn:aws:iam::aws:policy/ReadOnlyAccess")
901 .await;
902 assert!(result.is_ok());
903 }
904
905 #[tokio::test]
906 async fn create_service_linked_role_returns_role() {
907 let mut mock = MockClient::new();
908 mock.expect_create_service_linked_role()
909 .returning_bytes(xml_envelope(
910 "CreateServiceLinkedRole",
911 "<Role>\
912 <RoleName>AWSServiceRoleForElasticBeanstalk</RoleName>\
913 <Arn>arn:aws:iam::123456789012:role/aws-service-role/elasticbeanstalk.amazonaws.com/AWSServiceRoleForElasticBeanstalk</Arn>\
914 <CreateDate>2024-06-15T12:00:00Z</CreateDate>\
915 <Description>Service-linked role for Elastic Beanstalk</Description>\
916 </Role>",
917 ));
918
919 let client = AwsHttpClient::from_mock(mock);
920 let response = client
921 .iam()
922 .create_service_linked_role("elasticbeanstalk.amazonaws.com")
923 .await
924 .unwrap();
925
926 let role = response.role.unwrap();
927 assert_eq!(role.role_name, "AWSServiceRoleForElasticBeanstalk");
928 assert!(role.arn.contains("aws-service-role"));
929 assert_eq!(role.create_date, "2024-06-15T12:00:00Z");
930 assert_eq!(
931 role.description.as_deref(),
932 Some("Service-linked role for Elastic Beanstalk")
933 );
934 }
935
936 #[tokio::test]
937 async fn get_user_policy_returns_document() {
938 let mut mock = MockClient::new();
939 mock.expect_get_user_policy().returning_bytes(xml_envelope(
940 "GetUserPolicy",
941 "<UserName>alice</UserName>\
942 <PolicyName>s3-read-policy</PolicyName>\
943 <PolicyDocument>%7B%22Version%22%3A%222012-10-17%22%7D</PolicyDocument>",
944 ));
945
946 let client = AwsHttpClient::from_mock(mock);
947 let response = client
948 .iam()
949 .get_user_policy("alice", "s3-read-policy")
950 .await
951 .unwrap();
952
953 assert_eq!(response.user_name, "alice");
954 assert_eq!(response.policy_name, "s3-read-policy");
955 assert_eq!(
956 response.policy_document,
957 "%7B%22Version%22%3A%222012-10-17%22%7D"
958 );
959 }
960
961 #[tokio::test]
962 async fn list_attached_group_policies_returns_policies() {
963 let mut mock = MockClient::new();
964 mock.expect_list_attached_group_policies()
965 .returning_bytes(xml_envelope(
966 "ListAttachedGroupPolicies",
967 "<AttachedPolicies>\
968 <member>\
969 <PolicyArn>arn:aws:iam::aws:policy/ReadOnlyAccess</PolicyArn>\
970 <PolicyName>ReadOnlyAccess</PolicyName>\
971 </member>\
972 </AttachedPolicies>",
973 ));
974
975 let client = AwsHttpClient::from_mock(mock);
976 let response = client
977 .iam()
978 .list_attached_group_policies("developers")
979 .await
980 .unwrap();
981
982 assert_eq!(response.attached_policies.len(), 1);
983 assert_eq!(
984 response.attached_policies[0].policy_arn.as_deref(),
985 Some("arn:aws:iam::aws:policy/ReadOnlyAccess")
986 );
987 assert_eq!(
988 response.attached_policies[0].policy_name.as_deref(),
989 Some("ReadOnlyAccess")
990 );
991 }
992
993 #[tokio::test]
994 async fn list_attached_group_policies_handles_empty() {
995 let mut mock = MockClient::new();
996 mock.expect_list_attached_group_policies()
997 .returning_bytes(xml_envelope(
998 "ListAttachedGroupPolicies",
999 "<AttachedPolicies/>",
1000 ));
1001
1002 let client = AwsHttpClient::from_mock(mock);
1003 let response = client
1004 .iam()
1005 .list_attached_group_policies("empty-group")
1006 .await
1007 .unwrap();
1008 assert!(response.attached_policies.is_empty());
1009 }
1010
1011 #[tokio::test]
1014 async fn list_virtual_mfa_devices_returns_devices() {
1015 let mut mock = MockClient::new();
1016 mock.expect_list_virtual_mfa_devices()
1017 .returning_bytes(xml_envelope(
1018 "ListVirtualMFADevices",
1019 "<VirtualMFADevices>\
1020 <member>\
1021 <SerialNumber>arn:aws:iam::123456789012:mfa/root-account-mfa-device</SerialNumber>\
1022 <EnableDate>2024-01-01T00:00:00Z</EnableDate>\
1023 </member>\
1024 </VirtualMFADevices>",
1025 ));
1026
1027 let client = AwsHttpClient::from_mock(mock);
1028 let response = client.iam().list_virtual_mfa_devices(None).await.unwrap();
1029
1030 assert_eq!(response.virtual_mfa_devices.len(), 1);
1031 assert_eq!(
1032 response.virtual_mfa_devices[0].serial_number,
1033 "arn:aws:iam::123456789012:mfa/root-account-mfa-device"
1034 );
1035 assert_eq!(
1036 response.virtual_mfa_devices[0].enable_date.as_deref(),
1037 Some("2024-01-01T00:00:00Z")
1038 );
1039 }
1040
1041 #[tokio::test]
1042 async fn list_virtual_mfa_devices_handles_empty() {
1043 let mut mock = MockClient::new();
1044 mock.expect_list_virtual_mfa_devices()
1045 .returning_bytes(xml_envelope(
1046 "ListVirtualMFADevices",
1047 "<VirtualMFADevices/>",
1048 ));
1049
1050 let client = AwsHttpClient::from_mock(mock);
1051 let response = client.iam().list_virtual_mfa_devices(None).await.unwrap();
1052 assert!(response.virtual_mfa_devices.is_empty());
1053 }
1054
1055 #[tokio::test]
1058 async fn list_policies_returns_local_policies() {
1059 let mut mock = MockClient::new();
1060 mock.expect_list_policies().returning_bytes(xml_envelope(
1061 "ListPolicies",
1062 "<Policies>\
1063 <member>\
1064 <PolicyName>FullAdminPolicy</PolicyName>\
1065 <PolicyId>ANPA000000000EXAMPLE</PolicyId>\
1066 <Arn>arn:aws:iam::123456789012:policy/FullAdminPolicy</Arn>\
1067 <DefaultVersionId>v1</DefaultVersionId>\
1068 <IsAttachable>true</IsAttachable>\
1069 <CreateDate>2024-01-15T10:00:00Z</CreateDate>\
1070 <UpdateDate>2024-01-15T10:00:00Z</UpdateDate>\
1071 </member>\
1072 </Policies>",
1073 ));
1074
1075 let client = AwsHttpClient::from_mock(mock);
1076 let response = client
1077 .iam()
1078 .list_policies(Some("Local"), None)
1079 .await
1080 .unwrap();
1081
1082 assert_eq!(response.policies.len(), 1);
1083 assert_eq!(
1084 response.policies[0].policy_name.as_deref(),
1085 Some("FullAdminPolicy")
1086 );
1087 assert_eq!(
1088 response.policies[0].arn.as_deref(),
1089 Some("arn:aws:iam::123456789012:policy/FullAdminPolicy")
1090 );
1091 assert_eq!(
1092 response.policies[0].default_version_id.as_deref(),
1093 Some("v1")
1094 );
1095 assert_eq!(response.policies[0].is_attachable, Some(true));
1096 }
1097
1098 #[tokio::test]
1099 async fn list_policies_handles_empty() {
1100 let mut mock = MockClient::new();
1101 mock.expect_list_policies()
1102 .returning_bytes(xml_envelope("ListPolicies", "<Policies/>"));
1103
1104 let client = AwsHttpClient::from_mock(mock);
1105 let response = client.iam().list_policies(None, None).await.unwrap();
1106 assert!(response.policies.is_empty());
1107 }
1108
1109 #[tokio::test]
1110 async fn get_policy_version_returns_document() {
1111 let mut mock = MockClient::new();
1112 mock.expect_get_policy_version().returning_bytes(xml_envelope(
1113 "GetPolicyVersion",
1114 "<PolicyVersion>\
1115 <Document>%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Action%22%3A%22%2A%22%2C%22Resource%22%3A%22%2A%22%7D%5D%7D</Document>\
1116 <VersionId>v1</VersionId>\
1117 <IsDefaultVersion>true</IsDefaultVersion>\
1118 <CreateDate>2024-01-15T10:00:00Z</CreateDate>\
1119 </PolicyVersion>",
1120 ));
1121
1122 let client = AwsHttpClient::from_mock(mock);
1123 let response = client
1124 .iam()
1125 .get_policy_version("arn:aws:iam::123456789012:policy/FullAdminPolicy", "v1")
1126 .await
1127 .unwrap();
1128
1129 let version = response.policy_version.unwrap();
1130 assert_eq!(version.version_id.as_deref(), Some("v1"));
1131 assert_eq!(version.is_default_version, Some(true));
1132 assert!(version.document.as_deref().unwrap_or("").contains("%7B"));
1134 }
1135
1136 #[tokio::test]
1139 async fn list_entities_for_policy_returns_all_entity_types() {
1140 let mut mock = MockClient::new();
1141 mock.expect_list_entities_for_policy()
1142 .returning_bytes(xml_envelope(
1143 "ListEntitiesForPolicy",
1144 "<PolicyGroups>\
1145 <member>\
1146 <GroupName>SupportTeam</GroupName>\
1147 <GroupId>AGPA000000000EXAMPLE</GroupId>\
1148 </member>\
1149 </PolicyGroups>\
1150 <PolicyUsers>\
1151 <member>\
1152 <UserName>support-user</UserName>\
1153 <UserId>AIDA000000000EXAMPLE</UserId>\
1154 </member>\
1155 </PolicyUsers>\
1156 <PolicyRoles>\
1157 <member>\
1158 <RoleName>SupportRole</RoleName>\
1159 <RoleId>AROA000000000EXAMPLE</RoleId>\
1160 </member>\
1161 </PolicyRoles>",
1162 ));
1163
1164 let client = AwsHttpClient::from_mock(mock);
1165 let response = client
1166 .iam()
1167 .list_entities_for_policy("arn:aws:iam::aws:policy/AWSSupportAccess")
1168 .await
1169 .unwrap();
1170
1171 assert_eq!(response.policy_groups.len(), 1);
1172 assert_eq!(
1173 response.policy_groups[0].group_name.as_deref(),
1174 Some("SupportTeam")
1175 );
1176 assert_eq!(response.policy_users.len(), 1);
1177 assert_eq!(
1178 response.policy_users[0].user_name.as_deref(),
1179 Some("support-user")
1180 );
1181 assert_eq!(response.policy_roles.len(), 1);
1182 assert_eq!(
1183 response.policy_roles[0].role_name.as_deref(),
1184 Some("SupportRole")
1185 );
1186 }
1187
1188 #[tokio::test]
1189 async fn list_entities_for_policy_handles_empty() {
1190 let mut mock = MockClient::new();
1191 mock.expect_list_entities_for_policy()
1192 .returning_bytes(xml_envelope(
1193 "ListEntitiesForPolicy",
1194 "<PolicyGroups/><PolicyUsers/><PolicyRoles/>",
1195 ));
1196
1197 let client = AwsHttpClient::from_mock(mock);
1198 let response = client
1199 .iam()
1200 .list_entities_for_policy("arn:aws:iam::aws:policy/AWSCloudShellFullAccess")
1201 .await
1202 .unwrap();
1203 assert!(response.policy_groups.is_empty());
1204 assert!(response.policy_users.is_empty());
1205 assert!(response.policy_roles.is_empty());
1206 }
1207}