Skip to main content

vantage_aws/models/iam/
user.rs

1use serde::{Deserialize, Serialize};
2use vantage_table::table::Table;
3
4use crate::types::{Arn, AwsDateTime};
5use crate::{AwsAccount, eq};
6
7use super::access_key::{AccessKey, access_keys_table};
8use super::attached_policy::{AttachedPolicy, attached_user_policies_table};
9use super::group::{Group, groups_for_user_table};
10
11/// One IAM user from `ListUsers`. Field names match the wire shape —
12/// the Query protocol returns these as XML elements; we expose them
13/// 1:1 so existing IAM docs translate directly.
14///
15/// Dates come through as the raw ISO-8601 string AWS sends.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct User {
18    #[serde(rename = "UserName")]
19    pub user_name: String,
20    #[serde(rename = "UserId", default)]
21    pub user_id: String,
22    #[serde(rename = "Arn", default)]
23    pub arn: String,
24    #[serde(rename = "Path", default)]
25    pub path: String,
26    #[serde(rename = "CreateDate", default)]
27    pub create_date: String,
28    #[serde(rename = "PasswordLastUsed", default)]
29    pub password_last_used: String,
30}
31
32/// `ListUsers` table — every IAM user in the account. Optional
33/// filters: `PathPrefix` to narrow by path, `MaxItems` to cap the
34/// page (v0 still only reads the first page either way).
35///
36/// Three relations:
37///   - `groups` → `ListGroupsForUser` for this user
38///   - `access_keys` → `ListAccessKeys` for this user
39///   - `attached_policies` → `ListAttachedUserPolicies` for this user
40///
41/// AWS doesn't accept multi-value filters, so the source has to
42/// narrow to a single user before traversal — otherwise the call
43/// errors at execute time.
44///
45/// ```no_run
46/// # use vantage_aws::{AwsAccount, eq};
47/// # use vantage_aws::models::iam::users_table;
48/// # async fn run() -> vantage_core::Result<()> {
49/// # let aws = AwsAccount::from_default()?;
50/// let mut users = users_table(aws);
51/// users.add_condition(eq("PathPrefix", "/admin/"));
52/// # Ok(()) }
53/// ```
54pub fn users_table(aws: AwsAccount) -> Table<AwsAccount, User> {
55    Table::new("query/Users:iam/2010-05-08.ListUsers", aws)
56        .with_id_column("UserName")
57        .with_column_of::<String>("UserId")
58        .with_column_of::<Arn>("Arn")
59        .with_title_column_of::<String>("Path")
60        .with_title_column_of::<AwsDateTime>("CreateDate")
61        .with_column_of::<AwsDateTime>("PasswordLastUsed")
62        .with_many("groups", "UserName", groups_for_user_table)
63        .with_many("access_keys", "UserName", access_keys_table)
64        .with_many(
65            "attached_policies",
66            "UserName",
67            attached_user_policies_table,
68        )
69}
70
71impl User {
72    /// Build a [`users_table`] narrowed to the user named in `arn`.
73    ///
74    /// Accepts ARNs of the shape
75    /// `arn:aws:iam::<account>:user/<name>`. Returns `None` if `arn`
76    /// isn't an IAM-user ARN.
77    pub fn from_arn(arn: &str, aws: AwsAccount) -> Option<Table<AwsAccount, User>> {
78        let name = arn.strip_prefix("arn:aws:iam::")?.split(":user/").nth(1)?;
79        if name.is_empty() {
80            return None;
81        }
82        let mut t = users_table(aws);
83        t.add_condition(eq("UserName", name.to_string()));
84        Some(t)
85    }
86
87    /// Groups *this* user belongs to.
88    pub fn ref_groups(&self, aws: AwsAccount) -> Table<AwsAccount, Group> {
89        let mut t = groups_for_user_table(aws);
90        t.add_condition(eq("UserName", self.user_name.clone()));
91        t
92    }
93
94    /// Access keys for *this* user.
95    pub fn ref_access_keys(&self, aws: AwsAccount) -> Table<AwsAccount, AccessKey> {
96        let mut t = access_keys_table(aws);
97        t.add_condition(eq("UserName", self.user_name.clone()));
98        t
99    }
100
101    /// Attached managed policies for *this* user.
102    pub fn ref_attached_policies(&self, aws: AwsAccount) -> Table<AwsAccount, AttachedPolicy> {
103        let mut t = attached_user_policies_table(aws);
104        t.add_condition(eq("UserName", self.user_name.clone()));
105        t
106    }
107}