kanidm_cli/opt/
kanidm.rs

1use clap::{builder::PossibleValue, Args, Subcommand, ValueEnum};
2use kanidm_proto::constants::CLIENT_TOKEN_CACHE;
3use kanidm_proto::internal::ImageType;
4use kanidm_proto::scim_v1::ScimFilter;
5use std::fmt;
6use time::format_description::well_known::Rfc3339;
7use time::OffsetDateTime;
8
9fn parse_rfc3339(input: &str) -> Result<OffsetDateTime, time::error::Parse> {
10    if input == "now" {
11        Ok(OffsetDateTime::now_utc())
12    } else {
13        OffsetDateTime::parse(input, &Rfc3339)
14    }
15}
16
17#[derive(Debug, Args, Clone)]
18pub struct Named {
19    pub name: String,
20}
21
22#[derive(Debug, Args, Clone)]
23pub struct DebugOpt {
24    /// Enable debugging of the kanidm tool
25    #[clap(short, long, env = "KANIDM_DEBUG")]
26    pub debug: bool,
27}
28
29#[derive(Debug, Clone, Copy, Default)]
30/// The CLI output mode, either text or json, falls back to text if you ask for something other than text/json
31pub enum OutputMode {
32    #[default]
33    Text,
34    Json,
35}
36
37impl From<OutputMode> for clap::builder::OsStr {
38    fn from(output_mode: OutputMode) -> Self {
39        match output_mode {
40            OutputMode::Text => "text".into(),
41            OutputMode::Json => "json".into(),
42        }
43    }
44}
45
46impl std::str::FromStr for OutputMode {
47    type Err = String;
48    fn from_str(s: &str) -> Result<OutputMode, std::string::String> {
49        match s.to_lowercase().as_str() {
50            "text" => Ok(OutputMode::Text),
51            "json" => Ok(OutputMode::Json),
52            _ => Ok(OutputMode::Text),
53        }
54    }
55}
56
57impl OutputMode {
58    pub fn print_message<T>(self, input: T)
59    where
60        T: serde::Serialize + fmt::Debug + fmt::Display,
61    {
62        match self {
63            OutputMode::Json => {
64                println!(
65                    "{}",
66                    serde_json::to_string(&input).unwrap_or(format!("{input:?}"))
67                );
68            }
69            OutputMode::Text => {
70                println!("{input}");
71            }
72        }
73    }
74}
75
76#[derive(Debug, Args, Clone)]
77pub struct GroupNamedMembers {
78    name: String,
79    #[clap(required = true, num_args(1..))]
80    members: Vec<String>,
81}
82
83#[derive(Debug, Args, Clone)]
84pub struct GroupPosixOpt {
85    name: String,
86    #[clap(long)]
87    gidnumber: Option<u32>,
88}
89
90#[derive(Debug, Subcommand, Clone)]
91pub enum GroupPosix {
92    /// Show details of a specific posix group
93    #[clap(name = "show")]
94    Show(Named),
95    /// Setup posix group properties, or alter them
96    #[clap(name = "set")]
97    Set(GroupPosixOpt),
98    /// Reset the gidnumber of this group to the generated default
99    #[clap(name = "reset-gidnumber")]
100    ResetGidnumber { group_id: String },
101}
102
103#[derive(Debug, Clone, Copy, Eq, PartialEq)]
104pub enum AccountPolicyCredentialType {
105    Any,
106    Mfa,
107    Passkey,
108    AttestedPasskey,
109}
110
111impl AccountPolicyCredentialType {
112    pub fn as_str(&self) -> &'static str {
113        match self {
114            Self::Any => "any",
115            Self::Mfa => "mfa",
116            Self::Passkey => "passkey",
117            Self::AttestedPasskey => "attested_passkey",
118        }
119    }
120}
121
122impl ValueEnum for AccountPolicyCredentialType {
123    fn value_variants<'a>() -> &'a [Self] {
124        &[Self::Any, Self::Mfa, Self::Passkey, Self::AttestedPasskey]
125    }
126
127    fn to_possible_value(&self) -> Option<PossibleValue> {
128        Some(self.as_str().into())
129    }
130}
131
132#[derive(Debug, Subcommand, Clone)]
133pub enum GroupAccountPolicyOpt {
134    /// Enable account policy for this group
135    #[clap(name = "enable")]
136    Enable { name: String },
137    /// Set the maximum time for session expiry in seconds.
138    #[clap(name = "auth-expiry")]
139    AuthSessionExpiry { name: String, expiry: u32 },
140    /// Set the minimum credential class that members may authenticate with. Valid values
141    /// in order of weakest to strongest are: "any" "mfa" "passkey" "attested_passkey".
142    #[clap(name = "credential-type-minimum")]
143    CredentialTypeMinimum {
144        name: String,
145        #[clap(value_enum)]
146        value: AccountPolicyCredentialType,
147    },
148    /// Set the minimum character length of passwords for accounts.
149    #[clap(name = "password-minimum-length")]
150    PasswordMinimumLength { name: String, length: u32 },
151
152    /// Set the maximum time for privilege session expiry in seconds.
153    #[clap(name = "privilege-expiry")]
154    PrivilegedSessionExpiry { name: String, expiry: u32 },
155
156    /// The WebAuthn attestation CA list that should be enforced
157    /// on members of this group. Prevents use of passkeys that are
158    /// not in this list. To create this list, use `fido-mds-tool`
159    /// from <https://crates.io/crates/fido-mds-tool>
160    #[clap(name = "webauthn-attestation-ca-list")]
161    WebauthnAttestationCaList {
162        name: String,
163        attestation_ca_list_json_file: PathBuf,
164    },
165
166    /// Sets the maximum number of entries that may be returned in a
167    /// search operation.
168    #[clap(name = "limit-search-max-results")]
169    LimitSearchMaxResults { name: String, maximum: u32 },
170    /// Sets the maximum number of entries that are examined during
171    /// a partially indexed search. This does not affect fully
172    /// indexed searches. If in doubt, set this to 1.5x limit-search-max-results
173    #[clap(name = "limit-search-max-filter-test")]
174    LimitSearchMaxFilterTest { name: String, maximum: u32 },
175    /// Sets whether during login the primary password can be used
176    /// as a fallback if no posix password has been defined
177    #[clap(name = "allow-primary-cred-fallback")]
178    AllowPrimaryCredFallback {
179        name: String,
180        #[clap(name = "allow", action = clap::ArgAction::Set)]
181        allow: bool,
182    },
183
184    /// Reset the maximum time for session expiry to its default value
185    #[clap(name = "reset-auth-expiry")]
186    ResetAuthSessionExpiry { name: String },
187    /// Reset the minimum character length of passwords to its default value.
188    #[clap(name = "reset-password-minimum-length")]
189    ResetPasswordMinimumLength { name: String },
190    /// Reset the maximum time for privilege session expiry to its default value.
191    #[clap(name = "reset-privilege-expiry")]
192    ResetPrivilegedSessionExpiry { name: String },
193    /// Reset the WebAuthn attestation CA list to its default value
194    /// allowing any passkey to be used by members of this group.
195    #[clap(name = "reset-webauthn-attestation-ca-list")]
196    ResetWebauthnAttestationCaList { name: String },
197    /// Reset the search maximum results limit to its default value.
198    #[clap(name = "reset-limit-search-max-results")]
199    ResetLimitSearchMaxResults { name: String },
200    /// Reset the max filter test limit to its default value.
201    #[clap(name = "reset-limit-search-max-filter-test")]
202    ResetLimitSearchMaxFilterTest { name: String },
203}
204
205#[derive(Debug, Subcommand, Clone)]
206pub enum GroupOpt {
207    /// List all groups
208    #[clap(name = "list")]
209    List,
210    /// View a specific group
211    #[clap(name = "get")]
212    Get(Named),
213    /// Search a group by name
214    #[clap(name = "search")]
215    Search {
216        /// The name of the group
217        name: String,
218    },
219    /// Create a new group
220    #[clap(name = "create")]
221    Create {
222        /// The name of the group
223        name: String,
224        /// Optional name/spn of a group that have entry manager rights over this group.
225        #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new())]
226        entry_managed_by: Option<String>,
227    },
228    /// Delete a group
229    #[clap(name = "delete")]
230    Delete(Named),
231    /// List the members of a group
232    #[clap(name = "list-members")]
233    ListMembers(Named),
234    /// Set the exact list of members that this group should contain, removing any not listed in the
235    /// set operation.
236    #[clap(name = "set-members")]
237    SetMembers(GroupNamedMembers),
238    /// Set the exact list of mail addresses that this group is associated with. The first
239    /// mail address in the list is the `primary` and the remainder are aliases. Setting
240    /// an empty list will clear the mail attribute.
241    #[clap(name = "set-mail")]
242    SetMail { name: String, mail: Vec<String> },
243    /// Set the description of this group. If no description is provided, the value is cleared
244    #[clap(name = "set-description")]
245    SetDescription {
246        name: String,
247        description: Option<String>,
248    },
249    /// Set a new entry-managed-by for this group.
250    #[clap(name = "set-entry-manager")]
251    SetEntryManagedBy {
252        /// The name of the group
253        name: String,
254        /// Optional name/spn of a group that have entry manager rights over this group.
255        entry_managed_by: String,
256    },
257    /// Rename an existing group
258    #[clap(name = "rename")]
259    Rename {
260        /// The name of the group
261        name: String,
262        /// The new name of the group
263        new_name: String,
264    },
265    /// Delete all members of a group.
266    #[clap(name = "purge-members")]
267    PurgeMembers(Named),
268    /// Add new members to a group
269    #[clap(name = "add-members")]
270    AddMembers(GroupNamedMembers),
271    /// Remove the named members from this group
272    #[clap(name = "remove-members")]
273    RemoveMembers(GroupNamedMembers),
274    /// Manage posix extensions for this group allowing groups to be used on unix/linux systems
275    #[clap(name = "posix")]
276    Posix {
277        #[clap(subcommand)]
278        commands: GroupPosix,
279    },
280    /// Manage the policies that apply to members of this group.
281    #[clap(name = "account-policy")]
282    AccountPolicy {
283        #[clap(subcommand)]
284        commands: GroupAccountPolicyOpt,
285    },
286}
287
288#[derive(Clone, Debug, ValueEnum)]
289pub enum GraphType {
290    Graphviz,
291    Mermaid,
292    MermaidElk,
293}
294
295#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, ValueEnum)]
296pub enum ObjectType {
297    Group,
298    BuiltinGroup,
299    ServiceAccount,
300    Person,
301}
302
303#[derive(Debug, Args, Clone)]
304pub struct GraphCommonOpt {
305    #[arg(value_enum)]
306    pub graph_type: GraphType,
307    #[clap()]
308    pub filter: Vec<ObjectType>,
309}
310
311#[derive(Debug, Args, Clone)]
312pub struct AccountCommonOpt {
313    #[clap()]
314    account_id: String,
315}
316
317#[derive(Debug, Args, Clone)]
318pub struct AccountNamedOpt {
319    #[clap(flatten)]
320    aopts: AccountCommonOpt,
321}
322
323#[derive(Debug, Args, Clone)]
324pub struct AccountNamedExpireDateTimeOpt {
325    #[clap(flatten)]
326    aopts: AccountCommonOpt,
327    #[clap(name = "datetime", verbatim_doc_comment)]
328    /// This accepts multiple options:
329    /// - An RFC3339 time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00"
330    /// - One of "any", "clear" or "never" to remove account expiry.
331    /// - "epoch" to set the expiry to the UNIX epoch
332    /// - "now" to expire immediately (this will affect authentication with Kanidm, but external systems may not be aware of the change until next time it's validated, typically ~15 minutes)
333    datetime: String,
334}
335
336#[derive(Debug, Args, Clone)]
337pub struct AccountNamedValidDateTimeOpt {
338    #[clap(flatten)]
339    aopts: AccountCommonOpt,
340    #[clap(name = "datetime")]
341    /// An rfc3339 time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00"
342    /// or the word "any", "clear" to remove valid from enforcement.
343    datetime: String,
344}
345
346#[derive(Debug, Args, Clone)]
347pub struct AccountNamedTagOpt {
348    #[clap(flatten)]
349    aopts: AccountCommonOpt,
350    #[clap(name = "tag")]
351    tag: String,
352}
353
354#[derive(Debug, Args, Clone)]
355pub struct AccountNamedTagPkOpt {
356    #[clap(flatten)]
357    aopts: AccountCommonOpt,
358    #[clap(name = "tag")]
359    tag: String,
360    #[clap(name = "pubkey")]
361    pubkey: String,
362}
363
364#[derive(Debug, Args, Clone)]
365/// Command-line options for account credential use-reset-token
366pub struct UseResetTokenOpt {
367    #[clap(name = "token")]
368    token: String,
369}
370
371#[derive(Debug, Args, Clone)]
372pub struct AccountCreateOpt {
373    #[clap(flatten)]
374    aopts: AccountCommonOpt,
375    #[clap(name = "display-name")]
376    display_name: String,
377}
378
379#[derive(Debug, Subcommand, Clone)]
380pub enum AccountCredential {
381    /// Show the status of this accounts credentials.
382    #[clap(name = "status")]
383    Status(AccountNamedOpt),
384    /// Interactively update/change the credentials for an account
385    #[clap(name = "update")]
386    Update(AccountNamedOpt),
387    /// Using a reset token, interactively reset credentials for a user
388    #[clap(name = "use-reset-token")]
389    UseResetToken(UseResetTokenOpt),
390    /// Create a reset token that can be given to another person so they can
391    /// recover or reset their account credentials.
392    #[clap(name = "create-reset-token")]
393    CreateResetToken {
394        #[clap(flatten)]
395        aopts: AccountCommonOpt,
396
397        /// Optionally set how many seconds the reset token should be valid for.
398        /// Default: 3600 seconds
399        ttl: Option<u32>,
400    },
401}
402
403/// RADIUS secret management
404#[derive(Debug, Subcommand, Clone)]
405pub enum AccountRadius {
406    /// Show the RADIUS secret for a user.
407    #[clap(name = "show-secret")]
408    Show(AccountNamedOpt),
409    /// Generate a randomized RADIUS secret for a user.
410    #[clap(name = "generate-secret")]
411    Generate(AccountNamedOpt),
412    #[clap(name = "delete-secret")]
413    /// Remove the configured RADIUS secret for the user.
414    DeleteSecret(AccountNamedOpt),
415}
416
417#[derive(Debug, Args, Clone)]
418pub struct AccountPosixOpt {
419    #[clap(flatten)]
420    aopts: AccountCommonOpt,
421    #[clap(long)]
422    gidnumber: Option<u32>,
423    #[clap(long, value_parser = clap::builder::NonEmptyStringValueParser::new())]
424    /// Set the user's login shell
425    shell: Option<String>,
426}
427
428#[derive(Debug, Subcommand, Clone)]
429pub enum PersonPosix {
430    #[clap(name = "show")]
431    Show(AccountNamedOpt),
432    #[clap(name = "set")]
433    Set(AccountPosixOpt),
434    #[clap(name = "set-password")]
435    SetPassword(AccountNamedOpt),
436    /// Reset the gidnumber of this person to the generated default
437    #[clap(name = "reset-gidnumber")]
438    ResetGidnumber { account_id: String },
439}
440
441#[derive(Debug, Subcommand, Clone)]
442pub enum ServiceAccountPosix {
443    #[clap(name = "show")]
444    Show(AccountNamedOpt),
445    #[clap(name = "set")]
446    Set(AccountPosixOpt),
447    /// Reset the gidnumber of this service account to the generated default
448    #[clap(name = "reset-gidnumber")]
449    ResetGidnumber { account_id: String },
450}
451
452#[derive(Debug, Args, Clone)]
453pub struct PersonUpdateOpt {
454    #[clap(flatten)]
455    aopts: AccountCommonOpt,
456    #[clap(long, short, help = "Set the legal name for the person.",
457    value_parser = clap::builder::NonEmptyStringValueParser::new())]
458    legalname: Option<String>,
459    #[clap(long, short, help = "Set the account name for the person.",
460    value_parser = clap::builder::NonEmptyStringValueParser::new())]
461    newname: Option<String>,
462    #[clap(long, short = 'i', help = "Set the display name for the person.",
463    value_parser = clap::builder::NonEmptyStringValueParser::new())]
464    displayname: Option<String>,
465    #[clap(
466        long,
467        short,
468        help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
469    )]
470    mail: Option<Vec<String>>,
471}
472
473#[derive(Debug, Subcommand, Clone)]
474pub enum AccountSsh {
475    #[clap(name = "list-publickeys")]
476    List(AccountNamedOpt),
477    #[clap(name = "add-publickey")]
478    Add(AccountNamedTagPkOpt),
479    #[clap(name = "delete-publickey")]
480    Delete(AccountNamedTagOpt),
481}
482
483#[derive(Debug, Subcommand, Clone)]
484pub enum AccountValidity {
485    /// Show an accounts validity window
486    #[clap(name = "show")]
487    Show(AccountNamedOpt),
488    /// Set an accounts expiry time
489    #[clap(name = "expire-at")]
490    ExpireAt(AccountNamedExpireDateTimeOpt),
491    /// Set an account valid from time
492    #[clap(name = "begin-from")]
493    BeginFrom(AccountNamedValidDateTimeOpt),
494}
495
496#[derive(Debug, Subcommand, Clone)]
497pub enum AccountCertificate {
498    #[clap(name = "status")]
499    Status { account_id: String },
500    #[clap(name = "create")]
501    Create {
502        account_id: String,
503        certificate_path: PathBuf,
504    },
505}
506
507#[derive(Debug, Subcommand, Clone)]
508pub enum AccountUserAuthToken {
509    /// Show the status of logged in sessions associated to this account.
510    #[clap(name = "status")]
511    Status(AccountNamedOpt),
512    /// Destroy / revoke a session for this account. Access to the
513    /// session (user auth token) is NOT required, only the uuid of the session.
514    #[clap(name = "destroy")]
515    Destroy {
516        #[clap(flatten)]
517        aopts: AccountCommonOpt,
518
519        /// The UUID of the token to destroy.
520        #[clap(name = "session-id")]
521        session_id: Uuid,
522    },
523}
524
525#[derive(Debug, Subcommand, Clone)]
526pub enum PersonOpt {
527    /// Manage the credentials this person uses for authentication
528    #[clap(name = "credential")]
529    Credential {
530        #[clap(subcommand)]
531        commands: AccountCredential,
532    },
533    /// Manage radius access for this person
534    #[clap(name = "radius")]
535    Radius {
536        #[clap(subcommand)]
537        commands: AccountRadius,
538    },
539    /// Manage posix extensions for this person allowing access to unix/linux systems
540    #[clap(name = "posix")]
541    Posix {
542        #[clap(subcommand)]
543        commands: PersonPosix,
544    },
545    /// Manage sessions (user auth tokens) associated to this person.
546    #[clap(name = "session")]
547    Session {
548        #[clap(subcommand)]
549        commands: AccountUserAuthToken,
550    },
551    /// Manage ssh public key's associated to this person
552    #[clap(name = "ssh")]
553    Ssh {
554        #[clap(subcommand)]
555        commands: AccountSsh,
556    },
557    /// List all persons
558    #[clap(name = "list")]
559    List,
560    /// View a specific person
561    #[clap(name = "get")]
562    Get(AccountNamedOpt),
563    /// Search persons by name
564    #[clap(name = "search")]
565    Search { account_id: String },
566    /// Update a specific person's attributes
567    #[clap(name = "update")]
568    Update(PersonUpdateOpt),
569    /// Create a new person's account
570    #[clap(name = "create")]
571    Create(AccountCreateOpt),
572    /// Delete a person's account
573    #[clap(name = "delete")]
574    Delete(AccountNamedOpt),
575    /// Manage a person's account validity, such as expiry time (account lock/unlock)
576    #[clap(name = "validity")]
577    Validity {
578        #[clap(subcommand)]
579        commands: AccountValidity,
580    },
581    #[clap(name = "certificate", hide = true)]
582    Certificate {
583        #[clap(subcommand)]
584        commands: AccountCertificate,
585    },
586}
587
588#[derive(Debug, Subcommand, Clone)]
589pub enum ServiceAccountCredential {
590    /// Show the status of this accounts password
591    #[clap(name = "status")]
592    Status(AccountNamedOpt),
593    /// Reset and generate a new service account password. This password can NOT
594    /// be used with the LDAP interface.
595    #[clap(name = "generate")]
596    GeneratePw(AccountNamedOpt),
597}
598
599#[derive(Debug, Subcommand, Clone)]
600pub enum ServiceAccountApiToken {
601    /// Show the status of api tokens associated to this service account.
602    #[clap(name = "status")]
603    Status(AccountNamedOpt),
604    /// Generate a new api token for this service account.
605    #[clap(name = "generate")]
606    Generate {
607        #[clap(flatten)]
608        aopts: AccountCommonOpt,
609
610        /// A string describing the token. This is not used to identify the token, it is only
611        /// for human description of the tokens purpose.
612        #[clap(name = "label")]
613        label: String,
614        #[clap(name = "expiry")]
615        /// An optional rfc3339 time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00".
616        /// After this time the api token will no longer be valid.
617        #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new())]
618        expiry: Option<String>,
619        /// Generate this token with read-write permissions - default is read-only
620        #[clap(short = 'w', long = "readwrite")]
621        read_write: bool,
622    },
623    /// Destroy / revoke an api token from this service account. Access to the
624    /// token is NOT required, only the tag/uuid of the token.
625    #[clap(name = "destroy")]
626    Destroy {
627        #[clap(flatten)]
628        aopts: AccountCommonOpt,
629
630        /// The UUID of the token to destroy.
631        #[clap(name = "token-id")]
632        token_id: Uuid,
633    },
634}
635
636#[derive(Debug, Args, Clone)]
637pub struct ServiceAccountUpdateOpt {
638    #[clap(flatten)]
639    aopts: AccountCommonOpt,
640    #[clap(long, short, help = "Set the account name for the service account.",
641    value_parser = clap::builder::NonEmptyStringValueParser::new())]
642    newname: Option<String>,
643    #[clap(
644        long,
645        short = 'i',
646        help = "Set the display name for the service account.",
647        value_parser = clap::builder::NonEmptyStringValueParser::new()
648    )]
649    displayname: Option<String>,
650    #[clap(
651        long,
652        short = 'e',
653        help = "Set the entry manager for the service account.",
654        value_parser = clap::builder::NonEmptyStringValueParser::new()
655    )]
656    entry_managed_by: Option<String>,
657    #[clap(
658        long,
659        short,
660        help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
661    )]
662    mail: Option<Vec<String>>,
663}
664
665#[derive(Debug, Subcommand, Clone)]
666pub enum ServiceAccountOpt {
667    /// Manage the generated password of this service account.
668    #[clap(name = "credential")]
669    Credential {
670        #[clap(subcommand)]
671        commands: ServiceAccountCredential,
672    },
673    /// Manage api tokens associated to this service account.
674    #[clap(name = "api-token")]
675    ApiToken {
676        #[clap(subcommand)]
677        commands: ServiceAccountApiToken,
678    },
679    /// Manage posix extensions for this service account allowing access to unix/linux systems
680    #[clap(name = "posix")]
681    Posix {
682        #[clap(subcommand)]
683        commands: ServiceAccountPosix,
684    },
685    /// Manage sessions (user auth tokens) associated to this service account.
686    #[clap(name = "session")]
687    Session {
688        #[clap(subcommand)]
689        commands: AccountUserAuthToken,
690    },
691    /// Manage ssh public key's associated to this person
692    #[clap(name = "ssh")]
693    Ssh {
694        #[clap(subcommand)]
695        commands: AccountSsh,
696    },
697    /// List all service accounts
698    #[clap(name = "list")]
699    List,
700    /// View a specific service account
701    #[clap(name = "get")]
702    Get(AccountNamedOpt),
703    /// Create a new service account
704    #[clap(name = "create")]
705    Create {
706        #[clap(flatten)]
707        aopts: AccountCommonOpt,
708        #[clap(name = "display-name")]
709        display_name: String,
710        #[clap(name = "entry-managed-by")]
711        entry_managed_by: String,
712    },
713    /// Update a specific service account's attributes
714    #[clap(name = "update")]
715    Update(ServiceAccountUpdateOpt),
716    /// Delete a service account
717    #[clap(name = "delete")]
718    Delete(AccountNamedOpt),
719    /// Manage a service account validity, such as expiry time (account lock/unlock)
720    #[clap(name = "validity")]
721    Validity {
722        #[clap(subcommand)]
723        commands: AccountValidity,
724    },
725    /// (Deprecated - due for removal in v1.1.0-15) - Convert a service account into a person. This is used during the alpha.9
726    /// to alpha.10 migration to "fix up" accounts that were not previously marked
727    /// as persons.
728    #[clap(name = "into-person")]
729    IntoPerson(AccountNamedOpt),
730}
731
732#[derive(Debug, Subcommand, Clone)]
733pub enum RecycleOpt {
734    #[clap(name = "list")]
735    /// List objects that are in the recycle bin
736    List,
737    #[clap(name = "get")]
738    /// Display an object from the recycle bin
739    Get(Named),
740    #[clap(name = "revive")]
741    /// Revive a recycled object into a live (accessible) state - this is the opposite of "delete"
742    Revive(Named),
743}
744
745#[derive(Debug, Args, Clone)]
746pub struct LoginOpt {}
747
748#[derive(Debug, Args, Clone)]
749pub struct LogoutOpt {
750    #[clap(short, long)]
751    /// Do not send a logout request to the server - only remove the session token locally.
752    local_only: bool,
753}
754
755#[derive(Debug, Subcommand, Clone)]
756pub enum SessionOpt {
757    #[clap(name = "list")]
758    /// List current active sessions
759    List,
760    #[clap(name = "cleanup")]
761    /// Remove sessions that have expired or are invalid.
762    Cleanup,
763}
764
765#[derive(Debug, Subcommand, Clone)]
766pub enum RawOpt {
767    #[clap(name = "search")]
768    Search {
769        filter: ScimFilter
770    },
771    #[clap(name = "create")]
772    Create {
773        file: PathBuf
774    },
775    #[clap(name = "update")]
776    Update {
777        file: PathBuf
778    },
779    #[clap(name = "delete")]
780    Delete {
781        id: String
782    },
783}
784
785#[derive(Debug, Subcommand, Clone)]
786pub enum SelfOpt {
787    /// Use the identify user feature
788    #[clap(name = "identify-user")]
789    IdentifyUser,
790    /// Show the current authenticated user's identity
791    Whoami,
792}
793
794#[derive(Debug, Args, Clone)]
795pub struct Oauth2SetDisplayname {
796    #[clap(flatten)]
797    nopt: Named,
798    #[clap(name = "displayname")]
799    displayname: String,
800}
801
802#[derive(Debug, Args, Clone)]
803pub struct Oauth2SetImplicitScopes {
804    #[clap(flatten)]
805    nopt: Named,
806    #[clap(name = "scopes")]
807    scopes: Vec<String>,
808}
809
810#[derive(Debug, Args, Clone)]
811pub struct Oauth2CreateScopeMapOpt {
812    #[clap(flatten)]
813    nopt: Named,
814    #[clap(name = "group")]
815    group: String,
816    #[clap(name = "scopes", required = true, num_args=1.. )]
817    scopes: Vec<String>,
818}
819
820#[derive(Debug, Args, Clone)]
821pub struct Oauth2DeleteScopeMapOpt {
822    #[clap(flatten)]
823    nopt: Named,
824    #[clap(name = "group")]
825    group: String,
826}
827
828#[derive(Debug, Clone, Copy, Eq, PartialEq)]
829pub enum Oauth2ClaimMapJoin {
830    Csv,
831    Ssv,
832    Array,
833}
834
835impl Oauth2ClaimMapJoin {
836    pub fn as_str(&self) -> &'static str {
837        match self {
838            Self::Csv => "csv",
839            Self::Ssv => "ssv",
840            Self::Array => "array",
841        }
842    }
843}
844
845impl ValueEnum for Oauth2ClaimMapJoin {
846    fn value_variants<'a>() -> &'a [Self] {
847        &[Self::Csv, Self::Ssv, Self::Array]
848    }
849
850    fn to_possible_value(&self) -> Option<PossibleValue> {
851        Some(self.as_str().into())
852    }
853}
854
855#[derive(Debug, Subcommand, Clone)]
856pub enum Oauth2Opt {
857    #[clap(name = "list")]
858    /// List all configured oauth2 clients
859    List,
860    #[clap(name = "get")]
861    /// Display a selected oauth2 client
862    Get(Named),
863    // #[clap(name = "set")]
864    // /// Set options for a selected oauth2 client
865    // Set(),
866    #[clap(name = "create")]
867    /// Create a new oauth2 confidential client that is protected by basic auth.
868    CreateBasic {
869        #[clap(name = "name")]
870        name: String,
871        #[clap(name = "displayname")]
872        displayname: String,
873        #[clap(name = "origin")]
874        origin: String,
875    },
876    #[clap(name = "create-public")]
877    /// Create a new OAuth2 public client that requires PKCE. You should prefer
878    /// using confidential client types if possible over public ones.
879    ///
880    /// Public clients have many limitations and can not access all API's of OAuth2. For
881    /// example rfc7662 token introspection requires client authentication.
882    CreatePublic {
883        #[clap(name = "name")]
884        name: String,
885        #[clap(name = "displayname")]
886        displayname: String,
887        #[clap(name = "origin")]
888        origin: String,
889    },
890    #[clap(name = "update-scope-map", visible_aliases=&["create-scope-map"])]
891    /// Update or add a new mapping from a group to scopes that it provides to members
892    UpdateScopeMap(Oauth2CreateScopeMapOpt),
893    #[clap(name = "delete-scope-map")]
894    /// Remove a mapping from groups to scopes
895    DeleteScopeMap(Oauth2DeleteScopeMapOpt),
896
897    #[clap(name = "update-sup-scope-map", visible_aliases=&["create-sup-scope-map"])]
898    /// Update or add a new mapping from a group to scopes that it provides to members
899    UpdateSupScopeMap(Oauth2CreateScopeMapOpt),
900    #[clap(name = "delete-sup-scope-map")]
901    /// Remove a mapping from groups to scopes
902    DeleteSupScopeMap(Oauth2DeleteScopeMapOpt),
903
904    #[clap(name = "update-claim-map", visible_aliases=&["create-claim-map"])]
905    /// Update or add a new mapping from a group to custom claims that it provides to members
906    UpdateClaimMap {
907        name: String,
908        claim_name: String,
909        group: String,
910        values: Vec<String>,
911    },
912    #[clap(name = "update-claim-map-join")]
913    UpdateClaimMapJoin {
914        name: String,
915        claim_name: String,
916        /// The join strategy. Valid values are csv (comma separated value), ssv (space
917        /// separated value) and array.
918        join: Oauth2ClaimMapJoin,
919    },
920    #[clap(name = "delete-claim-map")]
921    /// Remove a mapping from groups to a custom claim
922    DeleteClaimMap {
923        name: String,
924        claim_name: String,
925        group: String,
926    },
927
928    #[clap(name = "reset-basic-secret")]
929    /// Reset the client basic secret. You will need to update your client after
930    /// executing this.
931    ResetSecrets(Named),
932    #[clap(name = "show-basic-secret")]
933    /// Show the associated basic secret for this client
934    ShowBasicSecret(Named),
935    #[clap(name = "delete")]
936    /// Delete a oauth2 client
937    Delete(Named),
938    /// Set a new display name for a client
939    #[clap(name = "set-displayname")]
940    SetDisplayname(Oauth2SetDisplayname),
941    /// Set a new name for this client. You will need to update
942    /// your integrated applications after this so that they continue to
943    /// function correctly.
944    #[clap(name = "set-name")]
945    SetName {
946        #[clap(flatten)]
947        nopt: Named,
948        #[clap(name = "newname")]
949        name: String,
950    },
951
952    /// The landing URL is the default origin of the OAuth2 client. Additionally, this landing
953    /// URL is the target when Kanidm redirects the user from the apps listing page.
954    #[clap(name = "set-landing-url")]
955    SetLandingUrl {
956        #[clap(flatten)]
957        nopt: Named,
958        #[clap(name = "landing-url")]
959        url: Url,
960    },
961    /// The image presented on the Kanidm Apps Listing page for an OAuth2 resource server.
962    #[clap(name = "set-image")]
963    SetImage {
964        #[clap(flatten)]
965        nopt: Named,
966        #[clap(name = "file-path")]
967        /// A local file path to an image to use as the icon for this OAuth2 client.
968        path: PathBuf,
969        #[clap(name = "image-type")]
970        /// The type of image being uploaded.
971        image_type: Option<ImageType>,
972    },
973    /// Removes the custom image previously set.
974    #[clap(name = "remove-image")]
975    RemoveImage(Named),
976
977    /// Add a supplemental URL as a redirection target. For example a phone app
978    /// may use a redirect URL such as `app://my-cool-app` to trigger a native
979    /// redirection event out of a browser.
980    #[clap(name = "add-redirect-url")]
981    AddOrigin {
982        name: String,
983        #[clap(name = "url")]
984        origin: Url,
985    },
986
987    /// Remove a supplemental redirect URL from the OAuth2 client configuration.
988    #[clap(name = "remove-redirect-url")]
989    RemoveOrigin {
990        name: String,
991        #[clap(name = "url")]
992        origin: Url,
993    },
994    #[clap(name = "enable-pkce")]
995    /// Enable PKCE on this oauth2 client. This defaults to being enabled.
996    EnablePkce(Named),
997    /// Disable PKCE on this oauth2 client to work around insecure clients that
998    /// may not support it. You should request the client to enable PKCE!
999    #[clap(name = "warning-insecure-client-disable-pkce")]
1000    DisablePkce(Named),
1001    #[clap(name = "warning-enable-legacy-crypto")]
1002    /// Enable legacy signing crypto on this oauth2 client. This defaults to being disabled.
1003    /// You only need to enable this for openid clients that do not support modern cryptographic
1004    /// operations.
1005    EnableLegacyCrypto(Named),
1006    /// Disable legacy signing crypto on this oauth2 client. This is the default.
1007    #[clap(name = "disable-legacy-crypto")]
1008    DisableLegacyCrypto(Named),
1009    /// Enable strict validation of redirect URLs. Previously redirect URLs only
1010    /// validated the origin of the URL matched. When enabled, redirect URLs must
1011    /// match exactly.
1012    #[clap(name = "enable-strict-redirect-url")]
1013    EnableStrictRedirectUri { name: String },
1014    #[clap(name = "disable-strict-redirect-url")]
1015    DisableStrictRedirectUri { name: String },
1016    #[clap(name = "enable-localhost-redirects")]
1017    /// Allow public clients to redirect to localhost.
1018    EnablePublicLocalhost { name: String },
1019    /// Disable public clients redirecting to localhost.
1020    #[clap(name = "disable-localhost-redirects")]
1021    DisablePublicLocalhost { name: String },
1022    /// Use the 'name' attribute instead of 'spn' for the preferred_username
1023    #[clap(name = "prefer-short-username")]
1024    PreferShortUsername(Named),
1025    /// Use the 'spn' attribute instead of 'name' for the preferred_username
1026    #[clap(name = "prefer-spn-username")]
1027    PreferSPNUsername(Named),
1028    #[cfg(feature = "dev-oauth2-device-flow")]
1029    /// Enable OAuth2 Device Flow authentication
1030    DeviceFlowEnable(Named),
1031    #[cfg(feature = "dev-oauth2-device-flow")]
1032    /// Disable OAuth2 Device Flow authentication
1033    DeviceFlowDisable(Named),
1034    /// Rotate the signing and encryption keys used by this client. The rotation
1035    /// will occur at the specified time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00"
1036    /// or immediately if the time is set to the value "now".
1037    /// Past signatures will continue to operate even after a rotation occurs. If you
1038    /// have concerns a key is compromised, then you should revoke it instead.
1039    #[clap(name = "rotate-cryptographic-keys")]
1040    RotateCryptographicKeys {
1041        name: String,
1042        #[clap(value_parser = parse_rfc3339)]
1043        rotate_at: OffsetDateTime,
1044    },
1045    /// Revoke the signing and encryption keys used by this client. This will immediately
1046    /// trigger a rotation of the key in question, and signtatures or tokens issued by
1047    /// the revoked key will not be considered valid.
1048    #[clap(name = "revoke-cryptographic-key")]
1049    RevokeCryptographicKey { name: String, key_id: String },
1050    /// Disable the prompt that asks for user consent when first authorizing or when scopes change.
1051    /// When disabled the user will be redirected to the app immediately. Defaults to being
1052    /// enabled.
1053    #[clap(name = "disable-consent-prompt")]
1054    DisableConsentPrompt(Named),
1055    /// Enable the regular user consent prompt.
1056    #[clap(name = "enable-consent-prompt")]
1057    EnableConsentPrompt(Named),
1058}
1059
1060#[derive(Args, Debug, Clone)]
1061pub struct OptSetDomainDisplayname {
1062    #[clap(name = "new-display-name")]
1063    new_display_name: String,
1064}
1065
1066#[derive(Debug, Subcommand, Clone)]
1067pub enum PwBadlistOpt {
1068    #[clap[name = "show"]]
1069    /// Show information about this system's password badlist
1070    Show,
1071    #[clap[name = "upload"]]
1072    /// Upload an extra badlist, appending to the currently configured one.
1073    /// This badlist will be preprocessed to remove items that are already
1074    /// caught by "zxcvbn" at the configured level.
1075    Upload {
1076        #[clap(value_parser, required = true, num_args(1..))]
1077        paths: Vec<PathBuf>,
1078        /// Perform a dry run and display the list that would have been uploaded instead.
1079        #[clap(short = 'n', long)]
1080        dryrun: bool,
1081    },
1082    #[clap[name = "remove", hide = true]]
1083    /// Remove the content of these lists if present in the configured
1084    /// badlist.
1085    Remove {
1086        #[clap(value_parser, required = true, num_args(1..))]
1087        paths: Vec<PathBuf>,
1088    },
1089}
1090
1091#[derive(Debug, Subcommand, Clone)]
1092pub enum DeniedNamesOpt {
1093    #[clap[name = "show"]]
1094    /// Show information about this system's denied name list
1095    Show,
1096    #[clap[name = "append"]]
1097    Append {
1098        #[clap(value_parser, required = true, num_args(1..))]
1099        names: Vec<String>,
1100    },
1101    #[clap[name = "remove"]]
1102    /// Remove a name from the denied name list.
1103    Remove {
1104        #[clap(value_parser, required = true, num_args(1..))]
1105        names: Vec<String>,
1106    },
1107}
1108
1109#[derive(Debug, Subcommand, Clone)]
1110pub enum DomainOpt {
1111    #[clap[name = "set-displayname"]]
1112    /// Set the domain display name
1113    SetDisplayname(OptSetDomainDisplayname),
1114    /// Sets the maximum number of LDAP attributes that can be queried in one operation.
1115    #[clap[name = "set-ldap-queryable-attrs"]]
1116    SetLdapMaxQueryableAttrs {
1117        #[clap(name = "maximum-queryable-attrs")]
1118        new_max_queryable_attrs: usize,
1119    },
1120    #[clap[name = "set-ldap-basedn"]]
1121    /// Change the basedn of this server. Takes effect after a server restart.
1122    /// Examples are `o=organisation` or `dc=domain,dc=name`. Must be a valid ldap
1123    /// dn containing only alphanumerics, and dn components must be org (o), domain (dc) or
1124    /// orgunit (ou).
1125    SetLdapBasedn {
1126        #[clap(name = "new-basedn")]
1127        new_basedn: String,
1128    },
1129    /// Enable or disable unix passwords being used to bind via LDAP. Unless you have a specific
1130    /// requirement for this, you should disable this.
1131    SetLdapAllowUnixPasswordBind {
1132        #[clap(name = "allow", action = clap::ArgAction::Set)]
1133        enable: bool,
1134    },
1135    /// Enable or disable easter eggs in the server. This includes seasonal icons, kanidm
1136    /// birthday surprises and other fun components. Defaults to false for production releases
1137    /// and true in development builds.
1138    SetAllowEasterEggs {
1139        #[clap(name = "allow", action = clap::ArgAction::Set)]
1140        enable: bool,
1141    },
1142    #[clap(name = "show")]
1143    /// Show information about this system's domain
1144    Show,
1145    #[clap(name = "revoke-key")]
1146    /// Revoke a key by its key id. This will cause all user sessions to be
1147    /// invalidated (logged out).
1148    RevokeKey { key_id: String },
1149    /// The image presented as the instance logo
1150    #[clap(name = "set-image")]
1151    SetImage {
1152        #[clap(name = "file-path")]
1153        path: PathBuf,
1154        #[clap(name = "image-type")]
1155        image_type: Option<ImageType>,
1156    },
1157    /// The remove the current instance logo, reverting to the default.
1158    #[clap(name = "remove-image")]
1159    RemoveImage,
1160}
1161
1162#[derive(Debug, Subcommand, Clone)]
1163pub enum MessageOpt {
1164    #[clap(name = "list")]
1165    /// List all queued messages
1166    List,
1167
1168    #[clap(name = "get")]
1169    /// Display the message identified by its message ID.
1170    Get {
1171        message_id: Uuid
1172    },
1173
1174    #[clap(name = "mark-as-sent")]
1175    /// Mark the message with this message ID as sent. This will prevent it
1176    /// being sent by any mail sender.
1177    MarkAsSent {
1178        message_id: Uuid
1179    },
1180
1181    #[clap(name = "send-test-message")]
1182    SendTestMessage {
1183        /// The account name of the person who this message should be sent to.
1184        to: String,
1185    }
1186}
1187
1188#[derive(Debug, Subcommand, Clone)]
1189pub enum SynchOpt {
1190    #[clap(name = "list")]
1191    /// List all configured IDM sync accounts
1192    List,
1193    #[clap(name = "get")]
1194    /// Display a selected IDM sync account
1195    Get(Named),
1196    #[clap(name = "set-credential-portal")]
1197    /// Set the url to the external credential portal. This will be displayed to synced users
1198    /// so that they can be redirected to update their credentials on this portal.
1199    SetCredentialPortal {
1200        #[clap()]
1201        account_id: String,
1202
1203        #[clap(name = "url")]
1204        url: Option<Url>,
1205    },
1206    /// Create a new IDM sync account
1207    #[clap(name = "create")]
1208    Create {
1209        #[clap()]
1210        account_id: String,
1211
1212        #[clap(name = "description",
1213        value_parser = clap::builder::NonEmptyStringValueParser::new())]
1214        description: Option<String>,
1215    },
1216    /// Generate a bearer token for an IDM sync account
1217    #[clap(name = "generate-token")]
1218    GenerateToken {
1219        #[clap()]
1220        account_id: String,
1221        #[clap()]
1222        label: String,
1223    },
1224    /// Destroy (revoke) the bearer token for an IDM sync account
1225    #[clap(name = "destroy-token")]
1226    DestroyToken {
1227        #[clap()]
1228        account_id: String,
1229    },
1230    /// Set the list of attributes that have their authority yielded from the sync account
1231    /// and are allowed to be modified by kanidm and users. Any attributes not listed in
1232    /// in this command will have their authority returned to the sync account.
1233    #[clap(name = "set-yield-attributes")]
1234    SetYieldAttributes {
1235        #[clap()]
1236        account_id: String,
1237
1238        #[clap(name = "attributes")]
1239        attrs: Vec<String>,
1240    },
1241    /// Reset the sync cookie of this connector, so that on the next operation of the sync tool
1242    /// a full refresh of the provider is requested. Kanidm attributes that have been granted
1243    /// authority will *not* be lost or deleted.
1244    #[clap(name = "force-refresh")]
1245    ForceRefresh {
1246        #[clap()]
1247        account_id: String,
1248    },
1249    /// Finalise and remove this sync account. This will transfer all synchronised entries into
1250    /// the authority of Kanidm. This signals the end of a migration from an external IDM into
1251    /// Kanidm. ⚠️  This action can NOT be undone. Once complete, it is most likely
1252    /// that attempting to recreate a sync account from the same IDM will fail due to conflicting
1253    /// entries that Kanidm now owns.
1254    #[clap(name = "finalise")]
1255    Finalise {
1256        #[clap()]
1257        account_id: String,
1258    },
1259    /// Terminate and remove this sync account. This will DELETE all entries that were imported
1260    /// from the external IDM source. ⚠️  This action can NOT be undone, and will require you to
1261    /// recreate the sync account if you
1262    /// wish to re-import data. Recreating the sync account may fail until the recycle bin and
1263    /// and tombstones are purged.
1264    #[clap(name = "terminate")]
1265    Terminate {
1266        #[clap()]
1267        account_id: String,
1268    },
1269}
1270
1271#[derive(Debug, Subcommand, Clone)]
1272pub enum AuthSessionExpiryOpt {
1273    #[clap[name = "get"]]
1274    /// Show information about this system auth session expiry
1275    Get,
1276    #[clap[name = "set"]]
1277    /// Sets the system auth session expiry in seconds
1278    Set {
1279        #[clap(name = "expiry")]
1280        expiry: u32,
1281    },
1282}
1283
1284#[derive(Debug, Subcommand, Clone)]
1285pub enum PrivilegedSessionExpiryOpt {
1286    #[clap[name = "get"]]
1287    /// Show information about this system privileged session expiry
1288    Get,
1289    #[clap[name = "set"]]
1290    /// Sets the system auth privilege session expiry in seconds
1291    Set {
1292        #[clap(name = "expiry")]
1293        expiry: u32,
1294    },
1295}
1296
1297#[derive(Args, Debug, Clone)]
1298pub struct ApiSchemaDownloadOpt {
1299    /// Where to put the file, defaults to ./kanidm-openapi.json
1300    #[clap(name = "filename", env, default_value = "./kanidm-openapi.json")]
1301    filename: PathBuf,
1302    /// Force overwriting the file if it exists
1303    #[clap(short, long, env)]
1304    force: bool,
1305}
1306
1307#[derive(Debug, Subcommand, Clone)]
1308pub enum ApiOpt {
1309    /// Download the OpenAPI schema file
1310    #[clap(name = "download-schema")]
1311    DownloadSchema(ApiSchemaDownloadOpt),
1312}
1313
1314#[derive(Debug, Subcommand, Clone)]
1315pub enum SchemaClassOpt {
1316    /// List all classes
1317    List,
1318    Search {
1319        query: String,
1320    },
1321}
1322
1323#[derive(Debug, Subcommand, Clone)]
1324pub enum SchemaAttrOpt {
1325    /// List all attributes
1326    List,
1327    Search {
1328        query: String,
1329    },
1330}
1331
1332#[derive(Debug, Subcommand, Clone)]
1333pub enum SchemaOpt {
1334    /// Class related operations
1335    #[clap(name = "class")]
1336    Class {
1337        #[clap(subcommand)]
1338        commands: SchemaClassOpt,
1339    },
1340    /// Attribute related operations
1341    #[clap(name = "attribute", visible_alias = "attr")]
1342    Attribute {
1343        #[clap(subcommand)]
1344        commands: SchemaAttrOpt,
1345    },
1346}
1347
1348#[derive(Debug, Subcommand, Clone)]
1349pub enum SystemOpt {
1350    #[clap(name = "pw-badlist")]
1351    /// Configure and manage the password badlist entry
1352    PwBadlist {
1353        #[clap(subcommand)]
1354        commands: PwBadlistOpt,
1355    },
1356    #[clap(name = "denied-names")]
1357    /// Configure and manage denied names
1358    DeniedNames {
1359        #[clap(subcommand)]
1360        commands: DeniedNamesOpt,
1361    },
1362    #[clap(name = "oauth2")]
1363    /// Configure and display oauth2/oidc client configuration
1364    Oauth2 {
1365        #[clap(subcommand)]
1366        commands: Oauth2Opt,
1367    },
1368    #[clap(name = "domain")]
1369    /// Configure and display domain configuration
1370    Domain {
1371        #[clap(subcommand)]
1372        commands: DomainOpt,
1373    },
1374    #[clap(name = "sync")]
1375    /// Configure synchronisation from an external IDM system
1376    Synch {
1377        #[clap(subcommand)]
1378        commands: SynchOpt,
1379    },
1380    #[clap(name = "message-queue", alias = "message")]
1381    /// Manage the outbound message queue
1382    Message {
1383        #[clap(subcommand)]
1384        commands: MessageOpt,
1385    },
1386    #[clap(name = "api")]
1387    /// API related things
1388    Api {
1389        #[clap(subcommand)]
1390        commands: ApiOpt,
1391    },
1392}
1393
1394#[derive(Debug, Subcommand, Clone)]
1395#[clap(about = "Kanidm Client Utility")]
1396pub enum KanidmClientOpt {
1397    /// Login to an account to use with future cli operations
1398    Login(LoginOpt),
1399    /// Reauthenticate to access privileged functions of this account for a short period.
1400    Reauth,
1401    /// Logout of an active cli session
1402    Logout(LogoutOpt),
1403    /// Manage active cli sessions
1404    Session {
1405        #[clap(subcommand)]
1406        commands: SessionOpt,
1407    },
1408    #[clap(name = "self")]
1409    /// Actions for the current authenticated account
1410    CSelf {
1411        #[clap(subcommand)]
1412        commands: SelfOpt,
1413    },
1414    /// Actions to manage and view person (user) accounts
1415    Person {
1416        #[clap(subcommand)]
1417        commands: PersonOpt,
1418    },
1419    /// Actions to manage groups
1420    Group {
1421        #[clap(subcommand)]
1422        commands: GroupOpt,
1423    },
1424    /// Actions to manage and view service accounts
1425    #[clap(name = "service-account")]
1426    ServiceAccount {
1427        #[clap(subcommand)]
1428        commands: ServiceAccountOpt,
1429    },
1430    /// Prints graphviz dot file of all groups
1431    #[clap(name = "graph")]
1432    Graph(GraphCommonOpt),
1433
1434    /// Schema management operations
1435    #[clap(hide = true)]
1436    Schema {
1437        #[clap(subcommand)]
1438        commands: SchemaOpt,
1439    },
1440
1441    /// System configuration operations
1442    System {
1443        #[clap(subcommand)]
1444        commands: SystemOpt,
1445    },
1446    #[clap(name = "recycle-bin")]
1447    /// Recycle Bin operations
1448    Recycle {
1449        #[clap(subcommand)]
1450        commands: RecycleOpt,
1451    },
1452    /// Unsafe - low level, raw database queries and operations.
1453    #[clap(hide = true)]
1454    Raw {
1455        #[clap(subcommand)]
1456        commands: RawOpt,
1457    },
1458    /// Print the program version and exit
1459    Version,
1460}
1461
1462#[derive(Debug, clap::Parser, Clone)]
1463#[clap(about = "Kanidm Client Utility")]
1464pub struct KanidmClientParser {
1465    #[clap(subcommand)]
1466    pub commands: KanidmClientOpt,
1467
1468    /// Enable debugging of the kanidm tool
1469    #[clap(short, long, env = "KANIDM_DEBUG", global = true)]
1470    pub debug: bool,
1471    /// Select the instance name you wish to connect to
1472    #[clap(short = 'I', long = "instance", env = "KANIDM_INSTANCE", global = true,
1473    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1474    pub instance: Option<String>,
1475    /// The URL of the kanidm instance
1476    #[clap(short = 'H', long = "url", env = "KANIDM_URL", global = true,
1477    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1478    pub addr: Option<String>,
1479    /// User which will initiate requests
1480    #[clap(
1481        short = 'D',
1482        long = "name",
1483        env = "KANIDM_NAME",
1484        value_parser = clap::builder::NonEmptyStringValueParser::new(), global=true
1485    )]
1486    pub username: Option<String>,
1487    /// Path to a CA certificate file
1488    #[clap(
1489        value_parser,
1490        short = 'C',
1491        long = "ca",
1492        env = "KANIDM_CA_PATH",
1493        global = true
1494    )]
1495    pub ca_path: Option<PathBuf>,
1496    /// Log format
1497    #[clap(short, long = "output", env = "KANIDM_OUTPUT", global = true, default_value=OutputMode::default())]
1498    output_mode: OutputMode,
1499    /// Skip hostname verification
1500    #[clap(
1501        long = "skip-hostname-verification",
1502        env = "KANIDM_SKIP_HOSTNAME_VERIFICATION",
1503        default_value_t = false,
1504        global = true
1505    )]
1506    skip_hostname_verification: bool,
1507    /// Don't verify CA
1508    #[clap(
1509        long = "accept-invalid-certs",
1510        env = "KANIDM_ACCEPT_INVALID_CERTS",
1511        default_value_t = false,
1512        global = true
1513    )]
1514    accept_invalid_certs: bool,
1515    /// Path to a file to cache tokens in, defaults to ~/.cache/kanidm_tokens
1516    #[clap(
1517        short,
1518        long,
1519        env = "KANIDM_TOKEN_CACHE_PATH",
1520    hide = true,
1521     default_value = None,
1522    global=true,
1523    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1524    token_cache_path: Option<String>,
1525
1526    #[clap(
1527        short,
1528        long,
1529        env = "KANIDM_PASSWORD",
1530        hide = true,
1531        global = true,
1532        value_parser = clap::builder::NonEmptyStringValueParser::new())]
1533    /// Supply a password to the login option
1534    password: Option<String>,
1535}
1536
1537impl KanidmClientParser {
1538    fn get_token_cache_path(&self) -> String {
1539        match self.token_cache_path.clone() {
1540            None => CLIENT_TOKEN_CACHE.to_string(),
1541            Some(val) => val.clone(),
1542        }
1543    }
1544}