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/{}/_attr/{}", id, 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(
115            format!("/v1/service_account/{}/_attr/{}", id, attr).as_str(),
116            m,
117        )
118        .await
119    }
120
121    pub async fn idm_service_account_get_attr(
122        &self,
123        id: &str,
124        attr: &str,
125    ) -> Result<Option<Vec<String>>, ClientError> {
126        self.perform_get_request(format!("/v1/service_account/{}/_attr/{}", id, attr).as_str())
127            .await
128    }
129
130    pub async fn idm_service_account_purge_attr(
131        &self,
132        id: &str,
133        attr: &str,
134    ) -> Result<(), ClientError> {
135        self.perform_delete_request(format!("/v1/service_account/{}/_attr/{}", id, attr).as_str())
136            .await
137    }
138
139    pub async fn idm_service_account_post_ssh_pubkey(
140        &self,
141        id: &str,
142        tag: &str,
143        pubkey: &str,
144    ) -> Result<(), ClientError> {
145        let sk = (tag.to_string(), pubkey.to_string());
146        self.perform_post_request(
147            format!("/v1/service_account/{}/_ssh_pubkeys", id).as_str(),
148            sk,
149        )
150        .await
151    }
152
153    pub async fn idm_service_account_delete_ssh_pubkey(
154        &self,
155        id: &str,
156        tag: &str,
157    ) -> Result<(), ClientError> {
158        self.perform_delete_request(
159            format!("/v1/service_account/{}/_ssh_pubkeys/{}", id, tag).as_str(),
160        )
161        .await
162    }
163
164    pub async fn idm_service_account_unix_extend(
165        &self,
166        // The username or uuid of the account
167        id: &str,
168        // The GID number to set for the account
169        gidnumber: Option<u32>,
170        // Set a default login shell
171        shell: Option<&str>,
172    ) -> Result<(), ClientError> {
173        let ux = AccountUnixExtend {
174            shell: shell.map(str::to_string),
175            gidnumber,
176        };
177        self.perform_post_request(format!("/v1/service_account/{}/_unix", id).as_str(), ux)
178            .await
179    }
180
181    // TODO: test coverage for this, but there's a weird issue with ACPs on apply
182    pub async fn idm_service_account_into_person(&self, id: &str) -> Result<(), ClientError> {
183        self.perform_post_request(
184            format!("/v1/service_account/{}/_into_person", id).as_str(),
185            (),
186        )
187        .await
188    }
189
190    pub async fn idm_service_account_get_credential_status(
191        &self,
192        id: &str,
193    ) -> Result<CredentialStatus, ClientError> {
194        let res: Result<CredentialStatus, ClientError> = self
195            .perform_get_request(format!("/v1/service_account/{}/_credential/_status", id).as_str())
196            .await;
197        res.and_then(|cs| {
198            if cs.creds.is_empty() {
199                Err(ClientError::EmptyResponse)
200            } else {
201                Ok(cs)
202            }
203        })
204    }
205
206    pub async fn idm_service_account_generate_password(
207        &self,
208        id: &str,
209    ) -> Result<String, ClientError> {
210        let res: Result<String, ClientError> = self
211            .perform_get_request(
212                format!("/v1/service_account/{}/_credential/_generate", id).as_str(),
213            )
214            .await;
215        res.and_then(|pw| {
216            if pw.is_empty() {
217                Err(ClientError::EmptyResponse)
218            } else {
219                Ok(pw)
220            }
221        })
222    }
223
224    pub async fn idm_service_account_list_api_token(
225        &self,
226        id: &str,
227    ) -> Result<Vec<ApiToken>, ClientError> {
228        // This ends up at [kanidmd_core::actors::v1_write::QueryServerWriteV1::handle_service_account_api_token_generate]
229        self.perform_get_request(format!("/v1/service_account/{}/_api_token", id).as_str())
230            .await
231    }
232
233    pub async fn idm_service_account_generate_api_token(
234        &self,
235        id: &str,
236        label: &str,
237        expiry: Option<OffsetDateTime>,
238        read_write: bool,
239    ) -> Result<String, ClientError> {
240        let new_token = ApiTokenGenerate {
241            label: label.to_string(),
242            expiry,
243            read_write,
244        };
245        self.perform_post_request(
246            format!("/v1/service_account/{}/_api_token", id).as_str(),
247            new_token,
248        )
249        .await
250    }
251
252    pub async fn idm_service_account_destroy_api_token(
253        &self,
254        id: &str,
255        token_id: Uuid,
256    ) -> Result<(), ClientError> {
257        self.perform_delete_request(
258            format!(
259                "/v1/service_account/{}/_api_token/{}",
260                id,
261                &token_id.to_string()
262            )
263            .as_str(),
264        )
265        .await
266    }
267}