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