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