kanidm_cli/
serviceaccount.rs

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