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 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 id: &str,
163 gidnumber: Option<u32>,
165 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 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 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}