kanidm_cli/
serviceaccount.rs

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            }, // 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                    compact,
76                } => {
77                    let expiry_odt = if let Some(t) = expiry {
78                        // Convert the time to local timezone.
79                        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            }, // End ServiceAccountOpt::ApiToken
150            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            }, // end ServiceAccountOpt::Posix
184            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            }, // End ServiceAccountOpt::Session
220            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            }, // end ServiceAccountOpt::Ssh
261            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                        // Convert the time to local timezone.
368                        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                        // Unset the value
434                        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                        // Attempt to parse and set
446                        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            }, // end ServiceAccountOpt::Validity
465            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}