kanidm_cli/
serviceaccount.rs

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            }, // End ServiceAccountOpt::Credential
86            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                        // Convert the time to local timezone.
116                        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            }, // End ServiceAccountOpt::ApiToken
190            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            }, // end ServiceAccountOpt::Posix
224            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            }, // End ServiceAccountOpt::Session
264            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            }, // end ServiceAccountOpt::Ssh
305            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                        // Convert the time to local timezone.
414                        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                        // Unset the value
480                        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                        // Attempt to parse and set
492                        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            }, // end ServiceAccountOpt::Validity
511            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}