kanidm_client/
service_account.rs

1use std::collections::BTreeMap;
2
3use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_ENTRY_MANAGED_BY, ATTR_MAIL, ATTR_NAME};
4use kanidm_proto::internal::{ApiToken, CredentialStatus};
5use kanidm_proto::v1::{AccountUnixExtend, ApiTokenGenerate, Entry};
6use time::OffsetDateTime;
7use uuid::Uuid;
8
9use crate::{ClientError, KanidmClient};
10
11impl KanidmClient {
12    pub async fn idm_service_account_list(&self) -> Result<Vec<Entry>, ClientError> {
13        self.perform_get_request("/v1/service_account").await
14    }
15
16    pub async fn idm_service_account_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
17        self.perform_get_request(format!("/v1/service_account/{id}").as_str())
18            .await
19    }
20
21    /// Handles creating a service account
22    pub async fn idm_service_account_create(
23        &self,
24        name: &str,
25        displayname: &str,
26        entry_managed_by: &str,
27    ) -> Result<(), ClientError> {
28        let mut new_acct = Entry {
29            attrs: BTreeMap::new(),
30        };
31        new_acct
32            .attrs
33            .insert(ATTR_NAME.to_string(), vec![name.to_string()]);
34        new_acct
35            .attrs
36            .insert(ATTR_DISPLAYNAME.to_string(), vec![displayname.to_string()]);
37        new_acct.attrs.insert(
38            ATTR_ENTRY_MANAGED_BY.to_string(),
39            vec![entry_managed_by.to_string()],
40        );
41
42        self.perform_post_request("/v1/service_account", new_acct)
43            .await
44    }
45
46    pub async fn idm_service_account_delete(&self, id: &str) -> Result<(), ClientError> {
47        self.perform_delete_request(["/v1/service_account/", id].concat().as_str())
48            .await
49    }
50
51    pub async fn idm_service_account_update(
52        &self,
53        id: &str,
54        newname: Option<&str>,
55        displayname: Option<&str>,
56        entry_managed_by: Option<&str>,
57        mail: Option<&[String]>,
58    ) -> Result<(), ClientError> {
59        let mut update_entry = Entry {
60            attrs: BTreeMap::new(),
61        };
62
63        if let Some(newname) = newname {
64            update_entry
65                .attrs
66                .insert(ATTR_NAME.to_string(), vec![newname.to_string()]);
67        }
68
69        if let Some(newdisplayname) = displayname {
70            update_entry.attrs.insert(
71                ATTR_DISPLAYNAME.to_string(),
72                vec![newdisplayname.to_string()],
73            );
74        }
75
76        if let Some(entry_managed_by) = entry_managed_by {
77            update_entry.attrs.insert(
78                ATTR_ENTRY_MANAGED_BY.to_string(),
79                vec![entry_managed_by.to_string()],
80            );
81        }
82
83        if let Some(mail) = mail {
84            update_entry
85                .attrs
86                .insert(ATTR_MAIL.to_string(), mail.to_vec());
87        }
88
89        self.perform_patch_request(format!("/v1/service_account/{id}").as_str(), update_entry)
90            .await
91    }
92
93    pub async fn idm_service_account_add_attr(
94        &self,
95        id: &str,
96        attr: &str,
97        values: &[&str],
98    ) -> Result<(), ClientError> {
99        let msg: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
100        self.perform_post_request(
101            format!("/v1/service_account/{id}/_attr/{attr}").as_str(),
102            msg,
103        )
104        .await
105    }
106
107    pub async fn idm_service_account_set_attr(
108        &self,
109        id: &str,
110        attr: &str,
111        values: &[&str],
112    ) -> Result<(), ClientError> {
113        let m: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
114        self.perform_put_request(format!("/v1/service_account/{id}/_attr/{attr}").as_str(), m)
115            .await
116    }
117
118    pub async fn idm_service_account_get_attr(
119        &self,
120        id: &str,
121        attr: &str,
122    ) -> Result<Option<Vec<String>>, ClientError> {
123        self.perform_get_request(format!("/v1/service_account/{id}/_attr/{attr}").as_str())
124            .await
125    }
126
127    pub async fn idm_service_account_purge_attr(
128        &self,
129        id: &str,
130        attr: &str,
131    ) -> Result<(), ClientError> {
132        self.perform_delete_request(format!("/v1/service_account/{id}/_attr/{attr}").as_str())
133            .await
134    }
135
136    pub async fn idm_service_account_post_ssh_pubkey(
137        &self,
138        id: &str,
139        tag: &str,
140        pubkey: &str,
141    ) -> Result<(), ClientError> {
142        let sk = (tag.to_string(), pubkey.to_string());
143        self.perform_post_request(
144            format!("/v1/service_account/{id}/_ssh_pubkeys").as_str(),
145            sk,
146        )
147        .await
148    }
149
150    pub async fn idm_service_account_delete_ssh_pubkey(
151        &self,
152        id: &str,
153        tag: &str,
154    ) -> Result<(), ClientError> {
155        self.perform_delete_request(format!("/v1/service_account/{id}/_ssh_pubkeys/{tag}").as_str())
156            .await
157    }
158
159    pub async fn idm_service_account_unix_extend(
160        &self,
161        // The username or uuid of the account
162        id: &str,
163        // The GID number to set for the account
164        gidnumber: Option<u32>,
165        // Set a default login shell
166        shell: Option<&str>,
167    ) -> Result<(), ClientError> {
168        let ux = AccountUnixExtend {
169            shell: shell.map(str::to_string),
170            gidnumber,
171        };
172        self.perform_post_request(format!("/v1/service_account/{id}/_unix").as_str(), ux)
173            .await
174    }
175
176    // TODO: test coverage for this, but there's a weird issue with ACPs on apply
177    pub async fn idm_service_account_into_person(&self, id: &str) -> Result<(), ClientError> {
178        self.perform_post_request(
179            format!("/v1/service_account/{id}/_into_person").as_str(),
180            (),
181        )
182        .await
183    }
184
185    pub async fn idm_service_account_get_credential_status(
186        &self,
187        id: &str,
188    ) -> Result<CredentialStatus, ClientError> {
189        let res: Result<CredentialStatus, ClientError> = self
190            .perform_get_request(format!("/v1/service_account/{id}/_credential/_status").as_str())
191            .await;
192        res.and_then(|cs| {
193            if cs.creds.is_empty() {
194                Err(ClientError::EmptyResponse)
195            } else {
196                Ok(cs)
197            }
198        })
199    }
200
201    pub async fn idm_service_account_generate_password(
202        &self,
203        id: &str,
204    ) -> Result<String, ClientError> {
205        let res: Result<String, ClientError> = self
206            .perform_get_request(format!("/v1/service_account/{id}/_credential/_generate").as_str())
207            .await;
208        res.and_then(|pw| {
209            if pw.is_empty() {
210                Err(ClientError::EmptyResponse)
211            } else {
212                Ok(pw)
213            }
214        })
215    }
216
217    pub async fn idm_service_account_list_api_token(
218        &self,
219        id: &str,
220    ) -> Result<Vec<ApiToken>, ClientError> {
221        // This ends up at [kanidmd_core::actors::v1_write::QueryServerWriteV1::handle_service_account_api_token_generate]
222        self.perform_get_request(format!("/v1/service_account/{id}/_api_token").as_str())
223            .await
224    }
225
226    pub async fn idm_service_account_generate_api_token(
227        &self,
228        id: &str,
229        label: &str,
230        expiry: Option<OffsetDateTime>,
231        read_write: bool,
232    ) -> Result<String, ClientError> {
233        let new_token = ApiTokenGenerate {
234            label: label.to_string(),
235            expiry,
236            read_write,
237        };
238        self.perform_post_request(
239            format!("/v1/service_account/{id}/_api_token").as_str(),
240            new_token,
241        )
242        .await
243    }
244
245    pub async fn idm_service_account_destroy_api_token(
246        &self,
247        id: &str,
248        token_id: Uuid,
249    ) -> Result<(), ClientError> {
250        self.perform_delete_request(
251            format!(
252                "/v1/service_account/{}/_api_token/{}",
253                id,
254                &token_id.to_string()
255            )
256            .as_str(),
257        )
258        .await
259    }
260}