1use crate::common::try_expire_at_from_string;
2use crate::OpType;
3use kanidm_proto::constants::{
4 ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM, ATTR_GIDNUMBER, ATTR_SSH_PUBLICKEY,
5};
6use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus};
7use time::OffsetDateTime;
8
9use crate::{
10 handle_client_error, AccountSsh, AccountUserAuthToken, AccountValidity, KanidmClientParser,
11 OutputMode, ServiceAccountApiToken, ServiceAccountCredential, ServiceAccountOpt,
12 ServiceAccountPosix,
13};
14use time::format_description::well_known::Rfc3339;
15
16impl ServiceAccountOpt {
17 pub async fn exec(&self, opt: KanidmClientParser) {
18 match self {
19 ServiceAccountOpt::Credential { commands } => match commands {
20 ServiceAccountCredential::Status(apo) => {
21 let client = opt.to_client(OpType::Read).await;
22 match client
23 .idm_service_account_get_credential_status(apo.aopts.account_id.as_str())
24 .await
25 {
26 Ok(cstatus) => {
27 println!("{cstatus}");
28 }
29 Err(e) => {
30 error!("Error getting credential status -> {:?}", e);
31 }
32 }
33 }
34 ServiceAccountCredential::GeneratePw(apo) => {
35 let client = opt.to_client(OpType::Write).await;
36 match client
37 .idm_service_account_generate_password(apo.aopts.account_id.as_str())
38 .await
39 {
40 Ok(new_pw) => {
41 println!("Success: {new_pw}");
42 }
43 Err(e) => {
44 error!("Error generating service account credential -> {:?}", e);
45 }
46 }
47 }
48 }, ServiceAccountOpt::ApiToken { commands } => match commands {
50 ServiceAccountApiToken::Status(apo) => {
51 let client = opt.to_client(OpType::Read).await;
52 match client
53 .idm_service_account_list_api_token(apo.aopts.account_id.as_str())
54 .await
55 {
56 Ok(tokens) => {
57 if tokens.is_empty() {
58 println!("No api tokens exist");
59 } else {
60 for token in tokens {
61 println!("token: {token}");
62 }
63 }
64 }
65 Err(e) => {
66 error!("Error listing service account api tokens -> {:?}", e);
67 }
68 }
69 }
70 ServiceAccountApiToken::Generate {
71 aopts,
72 label,
73 expiry,
74 read_write,
75 compact,
76 } => {
77 let expiry_odt = if let Some(t) = expiry {
78 match OffsetDateTime::parse(t, &Rfc3339).map(|odt| {
80 odt.to_offset(
81 time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)
82 .unwrap_or(time::UtcOffset::UTC),
83 )
84 }) {
85 Ok(odt) => {
86 debug!("valid until: {}", odt);
87 Some(odt)
88 }
89 Err(e) => {
90 error!("Error parsing expiry (input: {t:?}) -> {:?}", e);
91 return;
92 }
93 }
94 } else {
95 None
96 };
97
98 let client = opt.to_client(OpType::Write).await;
99
100 match client
101 .idm_service_account_generate_api_token(
102 aopts.account_id.as_str(),
103 label,
104 expiry_odt,
105 *read_write,
106 *compact,
107 )
108 .await
109 {
110 Ok(new_token) => match opt.output_mode {
111 OutputMode::Json => {
112 let message = AccountChangeMessage {
113 output_mode: ConsoleOutputMode::JSON,
114 action: "api-token generate".to_string(),
115 result: new_token,
116 status: kanidm_proto::messages::MessageStatus::Success,
117 src_user: opt
118 .username
119 .clone()
120 .unwrap_or("<unknown username>".to_string()),
121 dest_user: aopts.account_id.clone(),
122 };
123 println!("{message}");
124 }
125 OutputMode::Text => {
126 println!("Success: This token will only be displayed ONCE");
127 println!("{new_token}")
128 }
129 },
130 Err(e) => {
131 error!("Error generating service account api token -> {:?}", e);
132 }
133 }
134 }
135 ServiceAccountApiToken::Destroy { aopts, token_id } => {
136 let client = opt.to_client(OpType::Write).await;
137 match client
138 .idm_service_account_destroy_api_token(aopts.account_id.as_str(), *token_id)
139 .await
140 {
141 Ok(()) => {
142 println!("Success");
143 }
144 Err(e) => {
145 error!("Error destroying service account token -> {:?}", e);
146 }
147 }
148 }
149 }, ServiceAccountOpt::Posix { commands } => match commands {
151 ServiceAccountPosix::Show(aopt) => {
152 let client = opt.to_client(OpType::Read).await;
153 match client
154 .idm_account_unix_token_get(aopt.aopts.account_id.as_str())
155 .await
156 {
157 Ok(token) => println!("{token}"),
158 Err(e) => handle_client_error(e, opt.output_mode),
159 }
160 }
161 ServiceAccountPosix::Set(aopt) => {
162 let client = opt.to_client(OpType::Write).await;
163 if let Err(e) = client
164 .idm_service_account_unix_extend(
165 aopt.aopts.account_id.as_str(),
166 aopt.gidnumber,
167 aopt.shell.as_deref(),
168 )
169 .await
170 {
171 handle_client_error(e, opt.output_mode)
172 }
173 }
174 ServiceAccountPosix::ResetGidnumber { account_id } => {
175 let client = opt.to_client(OpType::Write).await;
176 if let Err(e) = client
177 .idm_service_account_purge_attr(account_id.as_str(), ATTR_GIDNUMBER)
178 .await
179 {
180 handle_client_error(e, opt.output_mode)
181 }
182 }
183 }, ServiceAccountOpt::Session { commands } => match commands {
185 AccountUserAuthToken::Status(apo) => {
186 let client = opt.to_client(OpType::Read).await;
187 match client
188 .idm_account_list_user_auth_token(apo.aopts.account_id.as_str())
189 .await
190 {
191 Ok(tokens) => {
192 if tokens.is_empty() {
193 println!("No sessions exist");
194 } else {
195 for token in tokens {
196 println!("token: {token}");
197 }
198 }
199 }
200 Err(e) => {
201 error!("Error listing sessions -> {:?}", e);
202 }
203 }
204 }
205 AccountUserAuthToken::Destroy { aopts, session_id } => {
206 let client = opt.to_client(OpType::Write).await;
207 match client
208 .idm_account_destroy_user_auth_token(aopts.account_id.as_str(), *session_id)
209 .await
210 {
211 Ok(()) => {
212 println!("Success");
213 }
214 Err(e) => {
215 error!("Error destroying account session -> {:?}", e);
216 }
217 }
218 }
219 }, ServiceAccountOpt::Ssh { commands } => match commands {
221 AccountSsh::List(aopt) => {
222 let client = opt.to_client(OpType::Read).await;
223
224 match client
225 .idm_service_account_get_attr(
226 aopt.aopts.account_id.as_str(),
227 ATTR_SSH_PUBLICKEY,
228 )
229 .await
230 {
231 Ok(pkeys) => pkeys.iter().flatten().for_each(|pkey| println!("{pkey}")),
232 Err(e) => handle_client_error(e, opt.output_mode),
233 }
234 }
235 AccountSsh::Add(aopt) => {
236 let client = opt.to_client(OpType::Write).await;
237 if let Err(e) = client
238 .idm_service_account_post_ssh_pubkey(
239 aopt.aopts.account_id.as_str(),
240 aopt.tag.as_str(),
241 aopt.pubkey.as_str(),
242 )
243 .await
244 {
245 handle_client_error(e, opt.output_mode)
246 }
247 }
248 AccountSsh::Delete(aopt) => {
249 let client = opt.to_client(OpType::Write).await;
250 if let Err(e) = client
251 .idm_service_account_delete_ssh_pubkey(
252 aopt.aopts.account_id.as_str(),
253 aopt.tag.as_str(),
254 )
255 .await
256 {
257 handle_client_error(e, opt.output_mode)
258 }
259 }
260 }, ServiceAccountOpt::List => {
262 let client = opt.to_client(OpType::Read).await;
263 match client.idm_service_account_list().await {
264 Ok(r) => r.iter().for_each(|ent| println!("{ent}")),
265 Err(e) => handle_client_error(e, opt.output_mode),
266 }
267 }
268 ServiceAccountOpt::Update(aopt) => {
269 let client = opt.to_client(OpType::Write).await;
270 match client
271 .idm_service_account_update(
272 aopt.aopts.account_id.as_str(),
273 aopt.newname.as_deref(),
274 aopt.displayname.as_deref(),
275 aopt.entry_managed_by.as_deref(),
276 aopt.mail.as_deref(),
277 )
278 .await
279 {
280 Ok(()) => println!("Success"),
281 Err(e) => handle_client_error(e, opt.output_mode),
282 }
283 }
284 ServiceAccountOpt::Get(aopt) => {
285 let client = opt.to_client(OpType::Read).await;
286 let res = client
287 .idm_service_account_get(aopt.aopts.account_id.as_str())
288 .await;
289 match res {
290 Ok(Some(e)) => opt.output_mode.print_message(e),
291 Ok(None) => opt.output_mode.print_message("No matching entries"),
292 Err(e) => handle_client_error(e, opt.output_mode),
293 }
294 }
295 ServiceAccountOpt::Delete(aopt) => {
296 let client = opt.to_client(OpType::Write).await;
297 let mut modmessage = AccountChangeMessage {
298 output_mode: ConsoleOutputMode::Text,
299 action: "account delete".to_string(),
300 result: "deleted".to_string(),
301 src_user: opt
302 .username
303 .to_owned()
304 .unwrap_or(format!("{:?}", client.whoami().await)),
305 dest_user: aopt.aopts.account_id.to_string(),
306 status: MessageStatus::Success,
307 };
308 match client
309 .idm_service_account_delete(aopt.aopts.account_id.as_str())
310 .await
311 {
312 Err(e) => {
313 modmessage.result = format!("Error -> {e:?}");
314 modmessage.status = MessageStatus::Failure;
315 eprintln!("{modmessage}");
316 }
317 Ok(result) => {
318 debug!("{:?}", result);
319 println!("{modmessage}");
320 }
321 };
322 }
323 ServiceAccountOpt::Create {
324 aopts,
325 display_name,
326 entry_managed_by,
327 } => {
328 let client = opt.to_client(OpType::Write).await;
329 if let Err(e) = client
330 .idm_service_account_create(
331 aopts.account_id.as_str(),
332 display_name.as_str(),
333 entry_managed_by.as_str(),
334 )
335 .await
336 {
337 handle_client_error(e, opt.output_mode)
338 }
339 }
340 ServiceAccountOpt::Validity { commands } => match commands {
341 AccountValidity::Show(ano) => {
342 let client = opt.to_client(OpType::Read).await;
343
344 let entry = match client
345 .idm_service_account_get(ano.aopts.account_id.as_str())
346 .await
347 {
348 Err(err) => {
349 error!(
350 "No account {} found, or other error occurred: {:?}",
351 ano.aopts.account_id.as_str(),
352 err
353 );
354 return;
355 }
356 Ok(val) => match val {
357 Some(val) => val,
358 None => {
359 error!("No account {} found!", ano.aopts.account_id.as_str());
360 return;
361 }
362 },
363 };
364
365 println!("user: {}", ano.aopts.account_id.as_str());
366 if let Some(t) = entry.attrs.get(ATTR_ACCOUNT_VALID_FROM) {
367 let t = OffsetDateTime::parse(&t[0], &Rfc3339)
369 .map(|odt| {
370 odt.to_offset(
371 time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)
372 .unwrap_or(time::UtcOffset::UTC),
373 )
374 .format(&Rfc3339)
375 .unwrap_or(odt.to_string())
376 })
377 .unwrap_or_else(|_| "invalid timestamp".to_string());
378
379 println!("valid after: {t}");
380 } else {
381 println!("valid after: any time");
382 }
383
384 if let Some(t) = entry.attrs.get(ATTR_ACCOUNT_EXPIRE) {
385 let t = OffsetDateTime::parse(&t[0], &Rfc3339)
386 .map(|odt| {
387 odt.to_offset(
388 time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)
389 .unwrap_or(time::UtcOffset::UTC),
390 )
391 .format(&Rfc3339)
392 .unwrap_or(odt.to_string())
393 })
394 .unwrap_or_else(|_| "invalid timestamp".to_string());
395 println!("expire: {t:?}");
396 } else {
397 println!("expire: never");
398 }
399 }
400 AccountValidity::ExpireAt(ano) => {
401 let client = opt.to_client(OpType::Write).await;
402 let validity = match try_expire_at_from_string(ano.datetime.as_str()) {
403 Ok(val) => val,
404 Err(()) => return,
405 };
406 let res = match validity {
407 None => {
408 client
409 .idm_service_account_purge_attr(
410 ano.aopts.account_id.as_str(),
411 ATTR_ACCOUNT_EXPIRE,
412 )
413 .await
414 }
415 Some(new_expiry) => {
416 client
417 .idm_service_account_set_attr(
418 ano.aopts.account_id.as_str(),
419 ATTR_ACCOUNT_EXPIRE,
420 &[&new_expiry],
421 )
422 .await
423 }
424 };
425 match res {
426 Err(e) => handle_client_error(e, opt.output_mode),
427 _ => println!("Success"),
428 };
429 }
430 AccountValidity::BeginFrom(ano) => {
431 let client = opt.to_client(OpType::Write).await;
432 if matches!(ano.datetime.as_str(), "any" | "clear" | "whenever") {
433 match client
435 .idm_service_account_purge_attr(
436 ano.aopts.account_id.as_str(),
437 ATTR_ACCOUNT_VALID_FROM,
438 )
439 .await
440 {
441 Err(e) => handle_client_error(e, opt.output_mode),
442 _ => println!("Success"),
443 }
444 } else {
445 if let Err(e) = OffsetDateTime::parse(ano.datetime.as_str(), &Rfc3339) {
447 error!("Error -> {:?}", e);
448 return;
449 }
450
451 match client
452 .idm_service_account_set_attr(
453 ano.aopts.account_id.as_str(),
454 ATTR_ACCOUNT_VALID_FROM,
455 &[ano.datetime.as_str()],
456 )
457 .await
458 {
459 Err(e) => handle_client_error(e, opt.output_mode),
460 _ => println!("Success"),
461 }
462 }
463 }
464 }, ServiceAccountOpt::IntoPerson(aopt) => {
466 warn!("This command is deprecated and will be removed in a future release");
467 let client = opt.to_client(OpType::Write).await;
468 match client
469 .idm_service_account_into_person(aopt.aopts.account_id.as_str())
470 .await
471 {
472 Ok(()) => println!("Success"),
473 Err(e) => handle_client_error(e, opt.output_mode),
474 }
475 }
476 }
477 }
478}