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