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}
1051
1052#[derive(Args, Debug, Clone)]
1053pub struct OptSetDomainDisplayname {
1054    #[clap(name = "new-display-name")]
1055    new_display_name: String,
1056}
1057
1058#[derive(Debug, Subcommand, Clone)]
1059pub enum PwBadlistOpt {
1060    #[clap[name = "show"]]
1061    /// Show information about this system's password badlist
1062    Show,
1063    #[clap[name = "upload"]]
1064    /// Upload an extra badlist, appending to the currently configured one.
1065    /// This badlist will be preprocessed to remove items that are already
1066    /// caught by "zxcvbn" at the configured level.
1067    Upload {
1068        #[clap(value_parser, required = true, num_args(1..))]
1069        paths: Vec<PathBuf>,
1070        /// Perform a dry run and display the list that would have been uploaded instead.
1071        #[clap(short = 'n', long)]
1072        dryrun: bool,
1073    },
1074    #[clap[name = "remove", hide = true]]
1075    /// Remove the content of these lists if present in the configured
1076    /// badlist.
1077    Remove {
1078        #[clap(value_parser, required = true, num_args(1..))]
1079        paths: Vec<PathBuf>,
1080    },
1081}
1082
1083#[derive(Debug, Subcommand, Clone)]
1084pub enum DeniedNamesOpt {
1085    #[clap[name = "show"]]
1086    /// Show information about this system's denied name list
1087    Show,
1088    #[clap[name = "append"]]
1089    Append {
1090        #[clap(value_parser, required = true, num_args(1..))]
1091        names: Vec<String>,
1092    },
1093    #[clap[name = "remove"]]
1094    /// Remove a name from the denied name list.
1095    Remove {
1096        #[clap(value_parser, required = true, num_args(1..))]
1097        names: Vec<String>,
1098    },
1099}
1100
1101#[derive(Debug, Subcommand, Clone)]
1102pub enum DomainOpt {
1103    #[clap[name = "set-displayname"]]
1104    /// Set the domain display name
1105    SetDisplayname(OptSetDomainDisplayname),
1106    /// Sets the maximum number of LDAP attributes that can be queried in one operation.
1107    #[clap[name = "set-ldap-queryable-attrs"]]
1108    SetLdapMaxQueryableAttrs {
1109        #[clap(name = "maximum-queryable-attrs")]
1110        new_max_queryable_attrs: usize,
1111    },
1112    #[clap[name = "set-ldap-basedn"]]
1113    /// Change the basedn of this server. Takes effect after a server restart.
1114    /// Examples are `o=organisation` or `dc=domain,dc=name`. Must be a valid ldap
1115    /// dn containing only alphanumerics, and dn components must be org (o), domain (dc) or
1116    /// orgunit (ou).
1117    SetLdapBasedn {
1118        #[clap(name = "new-basedn")]
1119        new_basedn: String,
1120    },
1121    /// Enable or disable unix passwords being used to bind via LDAP. Unless you have a specific
1122    /// requirement for this, you should disable this.
1123    SetLdapAllowUnixPasswordBind {
1124        #[clap(name = "allow", action = clap::ArgAction::Set)]
1125        enable: bool,
1126    },
1127    /// Enable or disable easter eggs in the server. This includes seasonal icons, kanidm
1128    /// birthday surprises and other fun components. Defaults to false for production releases
1129    /// and true in development builds.
1130    SetAllowEasterEggs {
1131        #[clap(name = "allow", action = clap::ArgAction::Set)]
1132        enable: bool,
1133    },
1134    #[clap(name = "show")]
1135    /// Show information about this system's domain
1136    Show,
1137    #[clap(name = "revoke-key")]
1138    /// Revoke a key by its key id. This will cause all user sessions to be
1139    /// invalidated (logged out).
1140    RevokeKey { key_id: String },
1141    /// The image presented as the instance logo
1142    #[clap(name = "set-image")]
1143    SetImage {
1144        #[clap(name = "file-path")]
1145        path: PathBuf,
1146        #[clap(name = "image-type")]
1147        image_type: Option<ImageType>,
1148    },
1149    /// The remove the current instance logo, reverting to the default.
1150    #[clap(name = "remove-image")]
1151    RemoveImage,
1152}
1153
1154#[derive(Debug, Subcommand, Clone)]
1155pub enum MessageOpt {
1156    #[clap(name = "list")]
1157    /// List all queued messages
1158    List,
1159
1160    #[clap(name = "get")]
1161    /// Display the message identified by its message ID.
1162    Get {
1163        message_id: Uuid
1164    },
1165
1166    #[clap(name = "mark-as-sent")]
1167    /// Mark the message with this message ID as sent. This will prevent it
1168    /// being sent by any mail sender.
1169    MarkAsSent {
1170        message_id: Uuid
1171    },
1172
1173    #[clap(name = "send-test-message")]
1174    SendTestMessage {
1175        /// The account name of the person who this message should be sent to.
1176        to: String,
1177    }
1178}
1179
1180#[derive(Debug, Subcommand, Clone)]
1181pub enum SynchOpt {
1182    #[clap(name = "list")]
1183    /// List all configured IDM sync accounts
1184    List,
1185    #[clap(name = "get")]
1186    /// Display a selected IDM sync account
1187    Get(Named),
1188    #[clap(name = "set-credential-portal")]
1189    /// Set the url to the external credential portal. This will be displayed to synced users
1190    /// so that they can be redirected to update their credentials on this portal.
1191    SetCredentialPortal {
1192        #[clap()]
1193        account_id: String,
1194
1195        #[clap(name = "url")]
1196        url: Option<Url>,
1197    },
1198    /// Create a new IDM sync account
1199    #[clap(name = "create")]
1200    Create {
1201        #[clap()]
1202        account_id: String,
1203
1204        #[clap(name = "description",
1205        value_parser = clap::builder::NonEmptyStringValueParser::new())]
1206        description: Option<String>,
1207    },
1208    /// Generate a bearer token for an IDM sync account
1209    #[clap(name = "generate-token")]
1210    GenerateToken {
1211        #[clap()]
1212        account_id: String,
1213        #[clap()]
1214        label: String,
1215    },
1216    /// Destroy (revoke) the bearer token for an IDM sync account
1217    #[clap(name = "destroy-token")]
1218    DestroyToken {
1219        #[clap()]
1220        account_id: String,
1221    },
1222    /// Set the list of attributes that have their authority yielded from the sync account
1223    /// and are allowed to be modified by kanidm and users. Any attributes not listed in
1224    /// in this command will have their authority returned to the sync account.
1225    #[clap(name = "set-yield-attributes")]
1226    SetYieldAttributes {
1227        #[clap()]
1228        account_id: String,
1229
1230        #[clap(name = "attributes")]
1231        attrs: Vec<String>,
1232    },
1233    /// Reset the sync cookie of this connector, so that on the next operation of the sync tool
1234    /// a full refresh of the provider is requested. Kanidm attributes that have been granted
1235    /// authority will *not* be lost or deleted.
1236    #[clap(name = "force-refresh")]
1237    ForceRefresh {
1238        #[clap()]
1239        account_id: String,
1240    },
1241    /// Finalise and remove this sync account. This will transfer all synchronised entries into
1242    /// the authority of Kanidm. This signals the end of a migration from an external IDM into
1243    /// Kanidm. ⚠️  This action can NOT be undone. Once complete, it is most likely
1244    /// that attempting to recreate a sync account from the same IDM will fail due to conflicting
1245    /// entries that Kanidm now owns.
1246    #[clap(name = "finalise")]
1247    Finalise {
1248        #[clap()]
1249        account_id: String,
1250    },
1251    /// Terminate and remove this sync account. This will DELETE all entries that were imported
1252    /// from the external IDM source. ⚠️  This action can NOT be undone, and will require you to
1253    /// recreate the sync account if you
1254    /// wish to re-import data. Recreating the sync account may fail until the recycle bin and
1255    /// and tombstones are purged.
1256    #[clap(name = "terminate")]
1257    Terminate {
1258        #[clap()]
1259        account_id: String,
1260    },
1261}
1262
1263#[derive(Debug, Subcommand, Clone)]
1264pub enum AuthSessionExpiryOpt {
1265    #[clap[name = "get"]]
1266    /// Show information about this system auth session expiry
1267    Get,
1268    #[clap[name = "set"]]
1269    /// Sets the system auth session expiry in seconds
1270    Set {
1271        #[clap(name = "expiry")]
1272        expiry: u32,
1273    },
1274}
1275
1276#[derive(Debug, Subcommand, Clone)]
1277pub enum PrivilegedSessionExpiryOpt {
1278    #[clap[name = "get"]]
1279    /// Show information about this system privileged session expiry
1280    Get,
1281    #[clap[name = "set"]]
1282    /// Sets the system auth privilege session expiry in seconds
1283    Set {
1284        #[clap(name = "expiry")]
1285        expiry: u32,
1286    },
1287}
1288
1289#[derive(Args, Debug, Clone)]
1290pub struct ApiSchemaDownloadOpt {
1291    /// Where to put the file, defaults to ./kanidm-openapi.json
1292    #[clap(name = "filename", env, default_value = "./kanidm-openapi.json")]
1293    filename: PathBuf,
1294    /// Force overwriting the file if it exists
1295    #[clap(short, long, env)]
1296    force: bool,
1297}
1298
1299#[derive(Debug, Subcommand, Clone)]
1300pub enum ApiOpt {
1301    /// Download the OpenAPI schema file
1302    #[clap(name = "download-schema")]
1303    DownloadSchema(ApiSchemaDownloadOpt),
1304}
1305
1306#[derive(Debug, Subcommand, Clone)]
1307pub enum SchemaClassOpt {
1308    /// List all classes
1309    List,
1310    Search {
1311        query: String,
1312    },
1313}
1314
1315#[derive(Debug, Subcommand, Clone)]
1316pub enum SchemaAttrOpt {
1317    /// List all attributes
1318    List,
1319    Search {
1320        query: String,
1321    },
1322}
1323
1324#[derive(Debug, Subcommand, Clone)]
1325pub enum SchemaOpt {
1326    /// Class related operations
1327    #[clap(name = "class")]
1328    Class {
1329        #[clap(subcommand)]
1330        commands: SchemaClassOpt,
1331    },
1332    /// Attribute related operations
1333    #[clap(name = "attribute", visible_alias = "attr")]
1334    Attribute {
1335        #[clap(subcommand)]
1336        commands: SchemaAttrOpt,
1337    },
1338}
1339
1340#[derive(Debug, Subcommand, Clone)]
1341pub enum SystemOpt {
1342    #[clap(name = "pw-badlist")]
1343    /// Configure and manage the password badlist entry
1344    PwBadlist {
1345        #[clap(subcommand)]
1346        commands: PwBadlistOpt,
1347    },
1348    #[clap(name = "denied-names")]
1349    /// Configure and manage denied names
1350    DeniedNames {
1351        #[clap(subcommand)]
1352        commands: DeniedNamesOpt,
1353    },
1354    #[clap(name = "oauth2")]
1355    /// Configure and display oauth2/oidc client configuration
1356    Oauth2 {
1357        #[clap(subcommand)]
1358        commands: Oauth2Opt,
1359    },
1360    #[clap(name = "domain")]
1361    /// Configure and display domain configuration
1362    Domain {
1363        #[clap(subcommand)]
1364        commands: DomainOpt,
1365    },
1366    #[clap(name = "sync")]
1367    /// Configure synchronisation from an external IDM system
1368    Synch {
1369        #[clap(subcommand)]
1370        commands: SynchOpt,
1371    },
1372    #[clap(name = "message-queue", alias = "message")]
1373    /// Manage the outbound message queue
1374    Message {
1375        #[clap(subcommand)]
1376        commands: MessageOpt,
1377    },
1378    #[clap(name = "api")]
1379    /// API related things
1380    Api {
1381        #[clap(subcommand)]
1382        commands: ApiOpt,
1383    },
1384}
1385
1386#[derive(Debug, Subcommand, Clone)]
1387#[clap(about = "Kanidm Client Utility")]
1388pub enum KanidmClientOpt {
1389    /// Login to an account to use with future cli operations
1390    Login(LoginOpt),
1391    /// Reauthenticate to access privileged functions of this account for a short period.
1392    Reauth {
1393        #[clap()]
1394        mode: kanidm_proto::cli::OpType,
1395    },
1396    /// Logout of an active cli session
1397    Logout(LogoutOpt),
1398    /// Manage active cli sessions
1399    Session {
1400        #[clap(subcommand)]
1401        commands: SessionOpt,
1402    },
1403    #[clap(name = "self")]
1404    /// Actions for the current authenticated account
1405    CSelf {
1406        #[clap(subcommand)]
1407        commands: SelfOpt,
1408    },
1409    /// Actions to manage and view person (user) accounts
1410    Person {
1411        #[clap(subcommand)]
1412        commands: PersonOpt,
1413    },
1414    /// Actions to manage groups
1415    Group {
1416        #[clap(subcommand)]
1417        commands: GroupOpt,
1418    },
1419    /// Actions to manage and view service accounts
1420    #[clap(name = "service-account")]
1421    ServiceAccount {
1422        #[clap(subcommand)]
1423        commands: ServiceAccountOpt,
1424    },
1425    /// Prints graphviz dot file of all groups
1426    #[clap(name = "graph")]
1427    Graph(GraphCommonOpt),
1428
1429    /// Schema management operations
1430    #[clap(hide = true)]
1431    Schema {
1432        #[clap(subcommand)]
1433        commands: SchemaOpt,
1434    },
1435
1436    /// System configuration operations
1437    System {
1438        #[clap(subcommand)]
1439        commands: SystemOpt,
1440    },
1441    #[clap(name = "recycle-bin")]
1442    /// Recycle Bin operations
1443    Recycle {
1444        #[clap(subcommand)]
1445        commands: RecycleOpt,
1446    },
1447    /// Unsafe - low level, raw database queries and operations.
1448    #[clap(hide = true)]
1449    Raw {
1450        #[clap(subcommand)]
1451        commands: RawOpt,
1452    },
1453    /// Print the program version and exit
1454    Version,
1455}
1456
1457#[derive(Debug, clap::Parser, Clone)]
1458#[clap(about = "Kanidm Client Utility")]
1459pub struct KanidmClientParser {
1460    #[clap(subcommand)]
1461    pub commands: KanidmClientOpt,
1462
1463    /// Enable debugging of the kanidm tool
1464    #[clap(short, long, env = "KANIDM_DEBUG", global = true)]
1465    pub debug: bool,
1466    /// Select the instance name you wish to connect to
1467    #[clap(short = 'I', long = "instance", env = "KANIDM_INSTANCE", global = true,
1468    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1469    pub instance: Option<String>,
1470    /// The URL of the kanidm instance
1471    #[clap(short = 'H', long = "url", env = "KANIDM_URL", global = true,
1472    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1473    pub addr: Option<String>,
1474    /// User which will initiate requests
1475    #[clap(
1476        short = 'D',
1477        long = "name",
1478        env = "KANIDM_NAME",
1479        value_parser = clap::builder::NonEmptyStringValueParser::new(), global=true
1480    )]
1481    pub username: Option<String>,
1482    /// Path to a CA certificate file
1483    #[clap(
1484        value_parser,
1485        short = 'C',
1486        long = "ca",
1487        env = "KANIDM_CA_PATH",
1488        global = true
1489    )]
1490    pub ca_path: Option<PathBuf>,
1491    /// Log format
1492    #[clap(short, long = "output", env = "KANIDM_OUTPUT", global = true, default_value=OutputMode::default())]
1493    output_mode: OutputMode,
1494    /// Skip hostname verification
1495    #[clap(
1496        long = "skip-hostname-verification",
1497        env = "KANIDM_SKIP_HOSTNAME_VERIFICATION",
1498        default_value_t = false,
1499        global = true
1500    )]
1501    skip_hostname_verification: bool,
1502    /// Don't verify CA
1503    #[clap(
1504        long = "accept-invalid-certs",
1505        env = "KANIDM_ACCEPT_INVALID_CERTS",
1506        default_value_t = false,
1507        global = true
1508    )]
1509    accept_invalid_certs: bool,
1510    /// Path to a file to cache tokens in, defaults to ~/.cache/kanidm_tokens
1511    #[clap(
1512        short,
1513        long,
1514        env = "KANIDM_TOKEN_CACHE_PATH",
1515    hide = true,
1516     default_value = None,
1517    global=true,
1518    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1519    token_cache_path: Option<String>,
1520
1521    #[clap(
1522        short,
1523        long,
1524        env = "KANIDM_PASSWORD",
1525        hide = true,
1526        global = true,
1527        value_parser = clap::builder::NonEmptyStringValueParser::new())]
1528    /// Supply a password to the login option
1529    password: Option<String>,
1530}
1531
1532impl KanidmClientParser {
1533    fn get_token_cache_path(&self) -> String {
1534        match self.token_cache_path.clone() {
1535            None => CLIENT_TOKEN_CACHE.to_string(),
1536            Some(val) => val.clone(),
1537        }
1538    }
1539}