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    /// Reset the softlocks on this account. This applies to all credentials of the account.
404    #[clap(name = "softlock-reset")]
405    SoftlockReset {
406        account_id: String,
407        #[clap(name = "datetime", default_value = "now", verbatim_doc_comment)]
408        /// This accepts multiple options:
409        /// - An RFC3339 time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00"
410        /// - "now" to reset immediately
411        datetime: String,
412    }
413}
414
415/// RADIUS secret management
416#[derive(Debug, Subcommand, Clone)]
417pub enum AccountRadius {
418    /// Show the RADIUS secret for a user.
419    #[clap(name = "show-secret")]
420    Show(AccountNamedOpt),
421    /// Generate a randomized RADIUS secret for a user.
422    #[clap(name = "generate-secret")]
423    Generate(AccountNamedOpt),
424    #[clap(name = "delete-secret")]
425    /// Remove the configured RADIUS secret for the user.
426    DeleteSecret(AccountNamedOpt),
427}
428
429#[derive(Debug, Args, Clone)]
430pub struct AccountPosixOpt {
431    #[clap(flatten)]
432    aopts: AccountCommonOpt,
433    #[clap(long)]
434    gidnumber: Option<u32>,
435    #[clap(long, value_parser = clap::builder::NonEmptyStringValueParser::new())]
436    /// Set the user's login shell
437    shell: Option<String>,
438}
439
440#[derive(Debug, Subcommand, Clone)]
441pub enum PersonPosix {
442    #[clap(name = "show")]
443    Show(AccountNamedOpt),
444    #[clap(name = "set")]
445    Set(AccountPosixOpt),
446    #[clap(name = "set-password")]
447    SetPassword(AccountNamedOpt),
448    /// Reset the gidnumber of this person to the generated default
449    #[clap(name = "reset-gidnumber")]
450    ResetGidnumber { account_id: String },
451}
452
453#[derive(Debug, Subcommand, Clone)]
454pub enum ServiceAccountPosix {
455    #[clap(name = "show")]
456    Show(AccountNamedOpt),
457    #[clap(name = "set")]
458    Set(AccountPosixOpt),
459    /// Reset the gidnumber of this service account to the generated default
460    #[clap(name = "reset-gidnumber")]
461    ResetGidnumber { account_id: String },
462}
463
464#[derive(Debug, Args, Clone)]
465pub struct PersonUpdateOpt {
466    #[clap(flatten)]
467    aopts: AccountCommonOpt,
468    #[clap(long, short, help = "Set the legal name for the person.",
469    value_parser = clap::builder::NonEmptyStringValueParser::new())]
470    legalname: Option<String>,
471    #[clap(long, short, help = "Set the account name for the person.",
472    value_parser = clap::builder::NonEmptyStringValueParser::new())]
473    newname: Option<String>,
474    #[clap(long, short = 'i', help = "Set the display name for the person.",
475    value_parser = clap::builder::NonEmptyStringValueParser::new())]
476    displayname: Option<String>,
477    #[clap(
478        long,
479        short,
480        help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
481    )]
482    mail: Option<Vec<String>>,
483}
484
485#[derive(Debug, Subcommand, Clone)]
486pub enum AccountSsh {
487    #[clap(name = "list-publickeys")]
488    List(AccountNamedOpt),
489    #[clap(name = "add-publickey")]
490    Add(AccountNamedTagPkOpt),
491    #[clap(name = "delete-publickey")]
492    Delete(AccountNamedTagOpt),
493}
494
495#[derive(Debug, Subcommand, Clone)]
496pub enum AccountValidity {
497    /// Show an accounts validity window
498    #[clap(name = "show")]
499    Show(AccountNamedOpt),
500    /// Set an accounts expiry time
501    #[clap(name = "expire-at")]
502    ExpireAt(AccountNamedExpireDateTimeOpt),
503    /// Set an account valid from time
504    #[clap(name = "begin-from")]
505    BeginFrom(AccountNamedValidDateTimeOpt),
506}
507
508#[derive(Debug, Subcommand, Clone)]
509pub enum AccountCertificate {
510    #[clap(name = "status")]
511    Status { account_id: String },
512    #[clap(name = "create")]
513    Create {
514        account_id: String,
515        certificate_path: PathBuf,
516    },
517}
518
519#[derive(Debug, Subcommand, Clone)]
520pub enum AccountUserAuthToken {
521    /// Show the status of logged in sessions associated to this account.
522    #[clap(name = "status")]
523    Status(AccountNamedOpt),
524    /// Destroy / revoke a session for this account. Access to the
525    /// session (user auth token) is NOT required, only the uuid of the session.
526    #[clap(name = "destroy")]
527    Destroy {
528        #[clap(flatten)]
529        aopts: AccountCommonOpt,
530
531        /// The UUID of the token to destroy.
532        #[clap(name = "session-id")]
533        session_id: Uuid,
534    },
535}
536
537#[derive(Debug, Subcommand, Clone)]
538pub enum PersonOpt {
539    /// Manage the credentials this person uses for authentication
540    #[clap(name = "credential")]
541    Credential {
542        #[clap(subcommand)]
543        commands: AccountCredential,
544    },
545    /// Manage radius access for this person
546    #[clap(name = "radius")]
547    Radius {
548        #[clap(subcommand)]
549        commands: AccountRadius,
550    },
551    /// Manage posix extensions for this person allowing access to unix/linux systems
552    #[clap(name = "posix")]
553    Posix {
554        #[clap(subcommand)]
555        commands: PersonPosix,
556    },
557    /// Manage sessions (user auth tokens) associated to this person.
558    #[clap(name = "session")]
559    Session {
560        #[clap(subcommand)]
561        commands: AccountUserAuthToken,
562    },
563    /// Manage ssh public key's associated to this person
564    #[clap(name = "ssh")]
565    Ssh {
566        #[clap(subcommand)]
567        commands: AccountSsh,
568    },
569    /// List all persons
570    #[clap(name = "list")]
571    List,
572    /// View a specific person
573    #[clap(name = "get")]
574    Get(AccountNamedOpt),
575    /// Search persons by name
576    #[clap(name = "search")]
577    Search { account_id: String },
578    /// Update a specific person's attributes
579    #[clap(name = "update")]
580    Update(PersonUpdateOpt),
581    /// Create a new person's account
582    #[clap(name = "create")]
583    Create(AccountCreateOpt),
584    /// Delete a person's account
585    #[clap(name = "delete")]
586    Delete(AccountNamedOpt),
587    /// Manage a person's account validity, such as expiry time (account lock/unlock)
588    #[clap(name = "validity")]
589    Validity {
590        #[clap(subcommand)]
591        commands: AccountValidity,
592    },
593    #[clap(name = "certificate", hide = true)]
594    Certificate {
595        #[clap(subcommand)]
596        commands: AccountCertificate,
597    },
598}
599
600#[derive(Debug, Subcommand, Clone)]
601pub enum ServiceAccountCredential {
602    /// Show the status of this accounts password
603    #[clap(name = "status")]
604    Status(AccountNamedOpt),
605    /// Reset and generate a new service account password. This password can NOT
606    /// be used with the LDAP interface.
607    #[clap(name = "generate")]
608    GeneratePw(AccountNamedOpt),
609}
610
611#[derive(Debug, Subcommand, Clone)]
612pub enum ServiceAccountApiToken {
613    /// Show the status of api tokens associated to this service account.
614    #[clap(name = "status")]
615    Status(AccountNamedOpt),
616    /// Generate a new api token for this service account.
617    #[clap(name = "generate")]
618    Generate {
619        #[clap(flatten)]
620        aopts: AccountCommonOpt,
621
622        /// A string describing the token. This is not used to identify the token, it is only
623        /// for human description of the tokens purpose.
624        #[clap(name = "label")]
625        label: String,
626        #[clap(name = "expiry")]
627        /// An optional rfc3339 time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00".
628        /// After this time the api token will no longer be valid.
629        #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new())]
630        expiry: Option<String>,
631        /// Generate this token with read-write permissions - default is read-only
632        #[clap(short = 'w', long = "readwrite")]
633        read_write: bool,
634
635        /// Generate the token in a compact form (less than 128 ascii chars) to account for
636        /// systems that may have length limits on tokens/credentials. This format of token
637        /// after creation *may* not be valid on all servers until replication converges. It
638        /// is recommended you use non-compact tokens unless you have a system that has
639        /// limits on credential lengths.
640        #[clap(short = 'c', long = "compact")]
641        compact: bool,
642    },
643    /// Destroy / revoke an api token from this service account. Access to the
644    /// token is NOT required, only the tag/uuid of the token.
645    #[clap(name = "destroy")]
646    Destroy {
647        #[clap(flatten)]
648        aopts: AccountCommonOpt,
649
650        /// The UUID of the token to destroy.
651        #[clap(name = "token-id")]
652        token_id: Uuid,
653    },
654}
655
656#[derive(Debug, Args, Clone)]
657pub struct ServiceAccountUpdateOpt {
658    #[clap(flatten)]
659    aopts: AccountCommonOpt,
660    #[clap(long, short, help = "Set the account name for the service account.",
661    value_parser = clap::builder::NonEmptyStringValueParser::new())]
662    newname: Option<String>,
663    #[clap(
664        long,
665        short = 'i',
666        help = "Set the display name for the service account.",
667        value_parser = clap::builder::NonEmptyStringValueParser::new()
668    )]
669    displayname: Option<String>,
670    #[clap(
671        long,
672        short = 'e',
673        help = "Set the entry manager for the service account.",
674        value_parser = clap::builder::NonEmptyStringValueParser::new()
675    )]
676    entry_managed_by: Option<String>,
677    #[clap(
678        long,
679        short,
680        help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
681    )]
682    mail: Option<Vec<String>>,
683}
684
685#[derive(Debug, Subcommand, Clone)]
686pub enum ServiceAccountOpt {
687    /// Manage the generated password of this service account.
688    #[clap(name = "credential")]
689    Credential {
690        #[clap(subcommand)]
691        commands: ServiceAccountCredential,
692    },
693    /// Manage api tokens associated to this service account.
694    #[clap(name = "api-token")]
695    ApiToken {
696        #[clap(subcommand)]
697        commands: ServiceAccountApiToken,
698    },
699    /// Manage posix extensions for this service account allowing access to unix/linux systems
700    #[clap(name = "posix")]
701    Posix {
702        #[clap(subcommand)]
703        commands: ServiceAccountPosix,
704    },
705    /// Manage sessions (user auth tokens) associated to this service account.
706    #[clap(name = "session")]
707    Session {
708        #[clap(subcommand)]
709        commands: AccountUserAuthToken,
710    },
711    /// Manage ssh public key's associated to this person
712    #[clap(name = "ssh")]
713    Ssh {
714        #[clap(subcommand)]
715        commands: AccountSsh,
716    },
717    /// List all service accounts
718    #[clap(name = "list")]
719    List,
720    /// View a specific service account
721    #[clap(name = "get")]
722    Get(AccountNamedOpt),
723    /// Create a new service account
724    #[clap(name = "create")]
725    Create {
726        #[clap(flatten)]
727        aopts: AccountCommonOpt,
728        #[clap(name = "display-name")]
729        display_name: String,
730        #[clap(name = "entry-managed-by")]
731        entry_managed_by: String,
732    },
733    /// Update a specific service account's attributes
734    #[clap(name = "update")]
735    Update(ServiceAccountUpdateOpt),
736    /// Delete a service account
737    #[clap(name = "delete")]
738    Delete(AccountNamedOpt),
739    /// Manage a service account validity, such as expiry time (account lock/unlock)
740    #[clap(name = "validity")]
741    Validity {
742        #[clap(subcommand)]
743        commands: AccountValidity,
744    },
745    /// (Deprecated - due for removal in v1.1.0-15) - Convert a service account into a person. This is used during the alpha.9
746    /// to alpha.10 migration to "fix up" accounts that were not previously marked
747    /// as persons.
748    #[clap(name = "into-person")]
749    IntoPerson(AccountNamedOpt),
750}
751
752#[derive(Debug, Subcommand, Clone)]
753pub enum RecycleOpt {
754    #[clap(name = "list")]
755    /// List objects that are in the recycle bin
756    List,
757    #[clap(name = "get")]
758    /// Display an object from the recycle bin
759    Get(Named),
760    #[clap(name = "revive")]
761    /// Revive a recycled object into a live (accessible) state - this is the opposite of "delete"
762    Revive(Named),
763}
764
765#[derive(Debug, Args, Clone)]
766pub struct LoginOpt {}
767
768#[derive(Debug, Args, Clone)]
769pub struct LogoutOpt {
770    #[clap(short, long)]
771    /// Do not send a logout request to the server - only remove the session token locally.
772    local_only: bool,
773}
774
775#[derive(Debug, Subcommand, Clone)]
776pub enum SessionOpt {
777    #[clap(name = "list")]
778    /// List current active sessions
779    List,
780    #[clap(name = "cleanup")]
781    /// Remove sessions that have expired or are invalid.
782    Cleanup,
783}
784
785#[derive(Debug, Subcommand, Clone)]
786pub enum RawOpt {
787    #[clap(name = "search")]
788    Search {
789        filter: ScimFilter
790    },
791    #[clap(name = "create")]
792    Create {
793        file: PathBuf
794    },
795    #[clap(name = "update")]
796    Update {
797        file: PathBuf
798    },
799    #[clap(name = "delete")]
800    Delete {
801        id: String
802    },
803}
804
805#[derive(Debug, Subcommand, Clone)]
806pub enum SelfOpt {
807    /// Use the identify user feature
808    #[clap(name = "identify-user")]
809    IdentifyUser,
810    /// Show the current authenticated user's identity
811    Whoami,
812}
813
814#[derive(Debug, Args, Clone)]
815pub struct Oauth2SetDisplayname {
816    #[clap(flatten)]
817    nopt: Named,
818    #[clap(name = "displayname")]
819    displayname: String,
820}
821
822#[derive(Debug, Args, Clone)]
823pub struct Oauth2SetImplicitScopes {
824    #[clap(flatten)]
825    nopt: Named,
826    #[clap(name = "scopes")]
827    scopes: Vec<String>,
828}
829
830#[derive(Debug, Args, Clone)]
831pub struct Oauth2CreateScopeMapOpt {
832    #[clap(flatten)]
833    nopt: Named,
834    #[clap(name = "group")]
835    group: String,
836    #[clap(name = "scopes", required = true, num_args=1.. )]
837    scopes: Vec<String>,
838}
839
840#[derive(Debug, Args, Clone)]
841pub struct Oauth2DeleteScopeMapOpt {
842    #[clap(flatten)]
843    nopt: Named,
844    #[clap(name = "group")]
845    group: String,
846}
847
848#[derive(Debug, Clone, Copy, Eq, PartialEq)]
849pub enum Oauth2ClaimMapJoin {
850    Csv,
851    Ssv,
852    Array,
853}
854
855impl Oauth2ClaimMapJoin {
856    pub fn as_str(&self) -> &'static str {
857        match self {
858            Self::Csv => "csv",
859            Self::Ssv => "ssv",
860            Self::Array => "array",
861        }
862    }
863}
864
865impl ValueEnum for Oauth2ClaimMapJoin {
866    fn value_variants<'a>() -> &'a [Self] {
867        &[Self::Csv, Self::Ssv, Self::Array]
868    }
869
870    fn to_possible_value(&self) -> Option<PossibleValue> {
871        Some(self.as_str().into())
872    }
873}
874
875#[derive(Debug, Subcommand, Clone)]
876pub enum Oauth2Opt {
877    #[clap(name = "list")]
878    /// List all configured oauth2 clients
879    List,
880    #[clap(name = "get")]
881    /// Display a selected oauth2 client
882    Get(Named),
883    // #[clap(name = "set")]
884    // /// Set options for a selected oauth2 client
885    // Set(),
886    #[clap(name = "create")]
887    /// Create a new oauth2 confidential client that is protected by basic auth.
888    CreateBasic {
889        #[clap(name = "name")]
890        name: String,
891        #[clap(name = "displayname")]
892        displayname: String,
893        #[clap(name = "origin")]
894        origin: String,
895    },
896    #[clap(name = "create-public")]
897    /// Create a new OAuth2 public client that requires PKCE. You should prefer
898    /// using confidential client types if possible over public ones.
899    ///
900    /// Public clients have many limitations and can not access all API's of OAuth2. For
901    /// example rfc7662 token introspection requires client authentication.
902    CreatePublic {
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 = "update-scope-map", visible_aliases=&["create-scope-map"])]
911    /// Update or add a new mapping from a group to scopes that it provides to members
912    UpdateScopeMap(Oauth2CreateScopeMapOpt),
913    #[clap(name = "delete-scope-map")]
914    /// Remove a mapping from groups to scopes
915    DeleteScopeMap(Oauth2DeleteScopeMapOpt),
916
917    #[clap(name = "update-sup-scope-map", visible_aliases=&["create-sup-scope-map"])]
918    /// Update or add a new mapping from a group to scopes that it provides to members
919    UpdateSupScopeMap(Oauth2CreateScopeMapOpt),
920    #[clap(name = "delete-sup-scope-map")]
921    /// Remove a mapping from groups to scopes
922    DeleteSupScopeMap(Oauth2DeleteScopeMapOpt),
923
924    #[clap(name = "update-claim-map", visible_aliases=&["create-claim-map"])]
925    /// Update or add a new mapping from a group to custom claims that it provides to members
926    UpdateClaimMap {
927        name: String,
928        claim_name: String,
929        group: String,
930        values: Vec<String>,
931    },
932    #[clap(name = "update-claim-map-join")]
933    UpdateClaimMapJoin {
934        name: String,
935        claim_name: String,
936        /// The join strategy. Valid values are csv (comma separated value), ssv (space
937        /// separated value) and array.
938        join: Oauth2ClaimMapJoin,
939    },
940    #[clap(name = "delete-claim-map")]
941    /// Remove a mapping from groups to a custom claim
942    DeleteClaimMap {
943        name: String,
944        claim_name: String,
945        group: String,
946    },
947
948    #[clap(name = "reset-basic-secret")]
949    /// Reset the client basic secret. You will need to update your client after
950    /// executing this.
951    ResetSecrets(Named),
952    #[clap(name = "show-basic-secret")]
953    /// Show the associated basic secret for this client
954    ShowBasicSecret(Named),
955    #[clap(name = "delete")]
956    /// Delete a oauth2 client
957    Delete(Named),
958    /// Set a new display name for a client
959    #[clap(name = "set-displayname")]
960    SetDisplayname(Oauth2SetDisplayname),
961    /// Set a new name for this client. You will need to update
962    /// your integrated applications after this so that they continue to
963    /// function correctly.
964    #[clap(name = "set-name")]
965    SetName {
966        #[clap(flatten)]
967        nopt: Named,
968        #[clap(name = "newname")]
969        name: String,
970    },
971
972    /// The landing URL is the default origin of the OAuth2 client. Additionally, this landing
973    /// URL is the target when Kanidm redirects the user from the apps listing page.
974    #[clap(name = "set-landing-url")]
975    SetLandingUrl {
976        #[clap(flatten)]
977        nopt: Named,
978        #[clap(name = "landing-url")]
979        url: Url,
980    },
981    /// The image presented on the Kanidm Apps Listing page for an OAuth2 resource server.
982    #[clap(name = "set-image")]
983    SetImage {
984        #[clap(flatten)]
985        nopt: Named,
986        #[clap(name = "file-path")]
987        /// A local file path to an image to use as the icon for this OAuth2 client.
988        path: PathBuf,
989        #[clap(name = "image-type")]
990        /// The type of image being uploaded.
991        image_type: Option<ImageType>,
992    },
993    /// Removes the custom image previously set.
994    #[clap(name = "remove-image")]
995    RemoveImage(Named),
996
997    /// Add a supplemental URL as a redirection target. For example a phone app
998    /// may use a redirect URL such as `app://my-cool-app` to trigger a native
999    /// redirection event out of a browser.
1000    #[clap(name = "add-redirect-url")]
1001    AddOrigin {
1002        name: String,
1003        #[clap(name = "url")]
1004        origin: Url,
1005    },
1006
1007    /// Remove a supplemental redirect URL from the OAuth2 client configuration.
1008    #[clap(name = "remove-redirect-url")]
1009    RemoveOrigin {
1010        name: String,
1011        #[clap(name = "url")]
1012        origin: Url,
1013    },
1014    #[clap(name = "enable-pkce")]
1015    /// Enable PKCE on this oauth2 client. This defaults to being enabled.
1016    EnablePkce(Named),
1017    /// Disable PKCE on this oauth2 client to work around insecure clients that
1018    /// may not support it. You should request the client to enable PKCE!
1019    #[clap(name = "warning-insecure-client-disable-pkce")]
1020    DisablePkce(Named),
1021    #[clap(name = "warning-enable-legacy-crypto")]
1022    /// Enable legacy signing crypto on this oauth2 client. This defaults to being disabled.
1023    /// You only need to enable this for openid clients that do not support modern cryptographic
1024    /// operations.
1025    EnableLegacyCrypto(Named),
1026    /// Disable legacy signing crypto on this oauth2 client. This is the default.
1027    #[clap(name = "disable-legacy-crypto")]
1028    DisableLegacyCrypto(Named),
1029    /// Enable strict validation of redirect URLs. Previously redirect URLs only
1030    /// validated the origin of the URL matched. When enabled, redirect URLs must
1031    /// match exactly.
1032    #[clap(name = "enable-strict-redirect-url")]
1033    EnableStrictRedirectUri { name: String },
1034    #[clap(name = "disable-strict-redirect-url")]
1035    DisableStrictRedirectUri { name: String },
1036    #[clap(name = "enable-localhost-redirects")]
1037    /// Allow public clients to redirect to localhost.
1038    EnablePublicLocalhost { name: String },
1039    /// Disable public clients redirecting to localhost.
1040    #[clap(name = "disable-localhost-redirects")]
1041    DisablePublicLocalhost { name: String },
1042    /// Use the 'name' attribute instead of 'spn' for the preferred_username
1043    #[clap(name = "prefer-short-username")]
1044    PreferShortUsername(Named),
1045    /// Use the 'spn' attribute instead of 'name' for the preferred_username
1046    #[clap(name = "prefer-spn-username")]
1047    PreferSPNUsername(Named),
1048    #[cfg(feature = "dev-oauth2-device-flow")]
1049    /// Enable OAuth2 Device Flow authentication
1050    DeviceFlowEnable(Named),
1051    #[cfg(feature = "dev-oauth2-device-flow")]
1052    /// Disable OAuth2 Device Flow authentication
1053    DeviceFlowDisable(Named),
1054    /// Rotate the signing and encryption keys used by this client. The rotation
1055    /// will occur at the specified time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00"
1056    /// or immediately if the time is set to the value "now".
1057    /// Past signatures will continue to operate even after a rotation occurs. If you
1058    /// have concerns a key is compromised, then you should revoke it instead.
1059    #[clap(name = "rotate-cryptographic-keys")]
1060    RotateCryptographicKeys {
1061        name: String,
1062        #[clap(value_parser = parse_rfc3339)]
1063        rotate_at: OffsetDateTime,
1064    },
1065    /// Revoke the signing and encryption keys used by this client. This will immediately
1066    /// trigger a rotation of the key in question, and signtatures or tokens issued by
1067    /// the revoked key will not be considered valid.
1068    #[clap(name = "revoke-cryptographic-key")]
1069    RevokeCryptographicKey { name: String, key_id: String },
1070    /// Disable the prompt that asks for user consent when first authorizing or when scopes change.
1071    /// When disabled the user will be redirected to the app immediately. Defaults to being
1072    /// enabled.
1073    #[clap(name = "disable-consent-prompt")]
1074    DisableConsentPrompt(Named),
1075    /// Enable the regular user consent prompt.
1076    #[clap(name = "enable-consent-prompt")]
1077    EnableConsentPrompt(Named),
1078}
1079
1080#[derive(Args, Debug, Clone)]
1081pub struct OptSetDomainDisplayname {
1082    #[clap(name = "new-display-name")]
1083    new_display_name: String,
1084}
1085
1086#[derive(Debug, Subcommand, Clone)]
1087pub enum PwBadlistOpt {
1088    #[clap[name = "show"]]
1089    /// Show information about this system's password badlist
1090    Show,
1091    #[clap[name = "upload"]]
1092    /// Upload an extra badlist, appending to the currently configured one.
1093    /// This badlist will be preprocessed to remove items that are already
1094    /// caught by "zxcvbn" at the configured level.
1095    Upload {
1096        #[clap(value_parser, required = true, num_args(1..))]
1097        paths: Vec<PathBuf>,
1098        /// Perform a dry run and display the list that would have been uploaded instead.
1099        #[clap(short = 'n', long)]
1100        dryrun: bool,
1101    },
1102    #[clap[name = "remove", hide = true]]
1103    /// Remove the content of these lists if present in the configured
1104    /// badlist.
1105    Remove {
1106        #[clap(value_parser, required = true, num_args(1..))]
1107        paths: Vec<PathBuf>,
1108    },
1109}
1110
1111#[derive(Debug, Subcommand, Clone)]
1112pub enum DeniedNamesOpt {
1113    #[clap[name = "show"]]
1114    /// Show information about this system's denied name list
1115    Show,
1116    #[clap[name = "append"]]
1117    Append {
1118        #[clap(value_parser, required = true, num_args(1..))]
1119        names: Vec<String>,
1120    },
1121    #[clap[name = "remove"]]
1122    /// Remove a name from the denied name list.
1123    Remove {
1124        #[clap(value_parser, required = true, num_args(1..))]
1125        names: Vec<String>,
1126    },
1127}
1128
1129#[derive(Debug, Subcommand, Clone)]
1130pub enum DomainOpt {
1131    #[clap[name = "set-displayname"]]
1132    /// Set the domain display name
1133    SetDisplayname(OptSetDomainDisplayname),
1134    /// Sets the maximum number of LDAP attributes that can be queried in one operation.
1135    #[clap[name = "set-ldap-queryable-attrs"]]
1136    SetLdapMaxQueryableAttrs {
1137        #[clap(name = "maximum-queryable-attrs")]
1138        new_max_queryable_attrs: usize,
1139    },
1140    #[clap[name = "set-ldap-basedn"]]
1141    /// Change the basedn of this server. Takes effect after a server restart.
1142    /// Examples are `o=organisation` or `dc=domain,dc=name`. Must be a valid ldap
1143    /// dn containing only alphanumerics, and dn components must be org (o), domain (dc) or
1144    /// orgunit (ou).
1145    SetLdapBasedn {
1146        #[clap(name = "new-basedn")]
1147        new_basedn: String,
1148    },
1149    /// Enable or disable unix passwords being used to bind via LDAP. Unless you have a specific
1150    /// requirement for this, you should disable this.
1151    SetLdapAllowUnixPasswordBind {
1152        #[clap(name = "allow", action = clap::ArgAction::Set)]
1153        enable: bool,
1154    },
1155    /// Enable or disable easter eggs in the server. This includes seasonal icons, kanidm
1156    /// birthday surprises and other fun components. Defaults to false for production releases
1157    /// and true in development builds.
1158    SetAllowEasterEggs {
1159        #[clap(name = "allow", action = clap::ArgAction::Set)]
1160        enable: bool,
1161    },
1162    #[clap(name = "show")]
1163    /// Show information about this system's domain
1164    Show,
1165    #[clap(name = "revoke-key")]
1166    /// Revoke a key by its key id. This will cause all user sessions to be
1167    /// invalidated (logged out).
1168    RevokeKey { key_id: String },
1169    /// The image presented as the instance logo
1170    #[clap(name = "set-image")]
1171    SetImage {
1172        #[clap(name = "file-path")]
1173        path: PathBuf,
1174        #[clap(name = "image-type")]
1175        image_type: Option<ImageType>,
1176    },
1177    /// The remove the current instance logo, reverting to the default.
1178    #[clap(name = "remove-image")]
1179    RemoveImage,
1180}
1181
1182#[derive(Debug, Subcommand, Clone)]
1183pub enum MessageOpt {
1184    #[clap(name = "list")]
1185    /// List all queued messages
1186    List,
1187
1188    #[clap(name = "get")]
1189    /// Display the message identified by its message ID.
1190    Get {
1191        message_id: Uuid
1192    },
1193
1194    #[clap(name = "mark-as-sent")]
1195    /// Mark the message with this message ID as sent. This will prevent it
1196    /// being sent by any mail sender.
1197    MarkAsSent {
1198        message_id: Uuid
1199    },
1200
1201    #[clap(name = "send-test-message")]
1202    SendTestMessage {
1203        /// The account name of the person who this message should be sent to.
1204        to: String,
1205    }
1206}
1207
1208#[derive(Debug, Subcommand, Clone)]
1209pub enum SynchOpt {
1210    #[clap(name = "list")]
1211    /// List all configured IDM sync accounts
1212    List,
1213    #[clap(name = "get")]
1214    /// Display a selected IDM sync account
1215    Get(Named),
1216    #[clap(name = "set-credential-portal")]
1217    /// Set the url to the external credential portal. This will be displayed to synced users
1218    /// so that they can be redirected to update their credentials on this portal.
1219    SetCredentialPortal {
1220        #[clap()]
1221        account_id: String,
1222
1223        #[clap(name = "url")]
1224        url: Option<Url>,
1225    },
1226    /// Create a new IDM sync account
1227    #[clap(name = "create")]
1228    Create {
1229        #[clap()]
1230        account_id: String,
1231
1232        #[clap(name = "description",
1233        value_parser = clap::builder::NonEmptyStringValueParser::new())]
1234        description: Option<String>,
1235    },
1236    /// Generate a bearer token for an IDM sync account
1237    #[clap(name = "generate-token")]
1238    GenerateToken {
1239        #[clap()]
1240        account_id: String,
1241        #[clap()]
1242        label: String,
1243    },
1244    /// Destroy (revoke) the bearer token for an IDM sync account
1245    #[clap(name = "destroy-token")]
1246    DestroyToken {
1247        #[clap()]
1248        account_id: String,
1249    },
1250    /// Set the list of attributes that have their authority yielded from the sync account
1251    /// and are allowed to be modified by kanidm and users. Any attributes not listed in
1252    /// in this command will have their authority returned to the sync account.
1253    #[clap(name = "set-yield-attributes")]
1254    SetYieldAttributes {
1255        #[clap()]
1256        account_id: String,
1257
1258        #[clap(name = "attributes")]
1259        attrs: Vec<String>,
1260    },
1261    /// Reset the sync cookie of this connector, so that on the next operation of the sync tool
1262    /// a full refresh of the provider is requested. Kanidm attributes that have been granted
1263    /// authority will *not* be lost or deleted.
1264    #[clap(name = "force-refresh")]
1265    ForceRefresh {
1266        #[clap()]
1267        account_id: String,
1268    },
1269    /// Finalise and remove this sync account. This will transfer all synchronised entries into
1270    /// the authority of Kanidm. This signals the end of a migration from an external IDM into
1271    /// Kanidm. ⚠️  This action can NOT be undone. Once complete, it is most likely
1272    /// that attempting to recreate a sync account from the same IDM will fail due to conflicting
1273    /// entries that Kanidm now owns.
1274    #[clap(name = "finalise")]
1275    Finalise {
1276        #[clap()]
1277        account_id: String,
1278    },
1279    /// Terminate and remove this sync account. This will DELETE all entries that were imported
1280    /// from the external IDM source. ⚠️  This action can NOT be undone, and will require you to
1281    /// recreate the sync account if you
1282    /// wish to re-import data. Recreating the sync account may fail until the recycle bin and
1283    /// and tombstones are purged.
1284    #[clap(name = "terminate")]
1285    Terminate {
1286        #[clap()]
1287        account_id: String,
1288    },
1289}
1290
1291#[derive(Debug, Subcommand, Clone)]
1292pub enum AuthSessionExpiryOpt {
1293    #[clap[name = "get"]]
1294    /// Show information about this system auth session expiry
1295    Get,
1296    #[clap[name = "set"]]
1297    /// Sets the system auth session expiry in seconds
1298    Set {
1299        #[clap(name = "expiry")]
1300        expiry: u32,
1301    },
1302}
1303
1304#[derive(Debug, Subcommand, Clone)]
1305pub enum PrivilegedSessionExpiryOpt {
1306    #[clap[name = "get"]]
1307    /// Show information about this system privileged session expiry
1308    Get,
1309    #[clap[name = "set"]]
1310    /// Sets the system auth privilege session expiry in seconds
1311    Set {
1312        #[clap(name = "expiry")]
1313        expiry: u32,
1314    },
1315}
1316
1317#[derive(Args, Debug, Clone)]
1318pub struct ApiSchemaDownloadOpt {
1319    /// Where to put the file, defaults to ./kanidm-openapi.json
1320    #[clap(name = "filename", env, default_value = "./kanidm-openapi.json")]
1321    filename: PathBuf,
1322    /// Force overwriting the file if it exists
1323    #[clap(short, long, env)]
1324    force: bool,
1325}
1326
1327#[derive(Debug, Subcommand, Clone)]
1328pub enum ApiOpt {
1329    /// Download the OpenAPI schema file
1330    #[clap(name = "download-schema")]
1331    DownloadSchema(ApiSchemaDownloadOpt),
1332}
1333
1334#[derive(Debug, Subcommand, Clone)]
1335pub enum SchemaClassOpt {
1336    /// List all classes
1337    List,
1338    Search {
1339        query: String,
1340    },
1341}
1342
1343#[derive(Debug, Subcommand, Clone)]
1344pub enum SchemaAttrOpt {
1345    /// List all attributes
1346    List,
1347    Search {
1348        query: String,
1349    },
1350}
1351
1352#[derive(Debug, Subcommand, Clone)]
1353pub enum SchemaOpt {
1354    /// Class related operations
1355    #[clap(name = "class")]
1356    Class {
1357        #[clap(subcommand)]
1358        commands: SchemaClassOpt,
1359    },
1360    /// Attribute related operations
1361    #[clap(name = "attribute", visible_alias = "attr")]
1362    Attribute {
1363        #[clap(subcommand)]
1364        commands: SchemaAttrOpt,
1365    },
1366}
1367
1368#[derive(Debug, Subcommand, Clone)]
1369pub enum SystemOpt {
1370    #[clap(name = "pw-badlist")]
1371    /// Configure and manage the password badlist entry
1372    PwBadlist {
1373        #[clap(subcommand)]
1374        commands: PwBadlistOpt,
1375    },
1376    #[clap(name = "denied-names")]
1377    /// Configure and manage denied names
1378    DeniedNames {
1379        #[clap(subcommand)]
1380        commands: DeniedNamesOpt,
1381    },
1382    #[clap(name = "oauth2")]
1383    /// Configure and display oauth2/oidc client configuration
1384    Oauth2 {
1385        #[clap(subcommand)]
1386        commands: Oauth2Opt,
1387    },
1388    #[clap(name = "domain")]
1389    /// Configure and display domain configuration
1390    Domain {
1391        #[clap(subcommand)]
1392        commands: DomainOpt,
1393    },
1394    #[clap(name = "sync")]
1395    /// Configure synchronisation from an external IDM system
1396    Synch {
1397        #[clap(subcommand)]
1398        commands: SynchOpt,
1399    },
1400    #[clap(name = "message-queue", alias = "message")]
1401    /// Manage the outbound message queue
1402    Message {
1403        #[clap(subcommand)]
1404        commands: MessageOpt,
1405    },
1406    #[clap(name = "api")]
1407    /// API related things
1408    Api {
1409        #[clap(subcommand)]
1410        commands: ApiOpt,
1411    },
1412}
1413
1414#[derive(Debug, Subcommand, Clone)]
1415#[clap(about = "Kanidm Client Utility")]
1416pub enum KanidmClientOpt {
1417    /// Login to an account to use with future cli operations
1418    Login(LoginOpt),
1419    /// Reauthenticate to access privileged functions of this account for a short period.
1420    Reauth,
1421    /// Logout of an active cli session
1422    Logout(LogoutOpt),
1423    /// Manage active cli sessions
1424    Session {
1425        #[clap(subcommand)]
1426        commands: SessionOpt,
1427    },
1428    #[clap(name = "self")]
1429    /// Actions for the current authenticated account
1430    CSelf {
1431        #[clap(subcommand)]
1432        commands: SelfOpt,
1433    },
1434    /// Actions to manage and view person (user) accounts
1435    Person {
1436        #[clap(subcommand)]
1437        commands: PersonOpt,
1438    },
1439    /// Actions to manage groups
1440    Group {
1441        #[clap(subcommand)]
1442        commands: GroupOpt,
1443    },
1444    /// Actions to manage and view service accounts
1445    #[clap(name = "service-account")]
1446    ServiceAccount {
1447        #[clap(subcommand)]
1448        commands: ServiceAccountOpt,
1449    },
1450    /// Prints graphviz dot file of all groups
1451    #[clap(name = "graph")]
1452    Graph(GraphCommonOpt),
1453
1454    /// Schema management operations
1455    #[clap(hide = true)]
1456    Schema {
1457        #[clap(subcommand)]
1458        commands: SchemaOpt,
1459    },
1460
1461    /// System configuration operations
1462    System {
1463        #[clap(subcommand)]
1464        commands: SystemOpt,
1465    },
1466    #[clap(name = "recycle-bin")]
1467    /// Recycle Bin operations
1468    Recycle {
1469        #[clap(subcommand)]
1470        commands: RecycleOpt,
1471    },
1472    /// Unsafe - low level, raw database queries and operations.
1473    #[clap(hide = true)]
1474    Raw {
1475        #[clap(subcommand)]
1476        commands: RawOpt,
1477    },
1478    /// Print the program version and exit
1479    Version,
1480}
1481
1482#[derive(Debug, clap::Parser, Clone)]
1483#[clap(about = "Kanidm Client Utility")]
1484pub struct KanidmClientParser {
1485    #[clap(subcommand)]
1486    pub commands: KanidmClientOpt,
1487
1488    /// Enable debugging of the kanidm tool
1489    #[clap(short, long, env = "KANIDM_DEBUG", global = true)]
1490    pub debug: bool,
1491    /// Select the instance name you wish to connect to
1492    #[clap(short = 'I', long = "instance", env = "KANIDM_INSTANCE", global = true,
1493    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1494    pub instance: Option<String>,
1495    /// The URL of the kanidm instance
1496    #[clap(short = 'H', long = "url", env = "KANIDM_URL", global = true,
1497    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1498    pub addr: Option<String>,
1499    /// User which will initiate requests
1500    #[clap(
1501        short = 'D',
1502        long = "name",
1503        env = "KANIDM_NAME",
1504        value_parser = clap::builder::NonEmptyStringValueParser::new(), global=true
1505    )]
1506    pub username: Option<String>,
1507    /// Path to a CA certificate file
1508    #[clap(
1509        value_parser,
1510        short = 'C',
1511        long = "ca",
1512        env = "KANIDM_CA_PATH",
1513        global = true
1514    )]
1515    pub ca_path: Option<PathBuf>,
1516    /// Log format
1517    #[clap(short, long = "output", env = "KANIDM_OUTPUT", global = true, default_value=OutputMode::default())]
1518    output_mode: OutputMode,
1519    /// Skip hostname verification
1520    #[clap(
1521        long = "skip-hostname-verification",
1522        env = "KANIDM_SKIP_HOSTNAME_VERIFICATION",
1523        default_value_t = false,
1524        global = true
1525    )]
1526    skip_hostname_verification: bool,
1527    /// Don't verify CA
1528    #[clap(
1529        long = "accept-invalid-certs",
1530        env = "KANIDM_ACCEPT_INVALID_CERTS",
1531        default_value_t = false,
1532        global = true
1533    )]
1534    accept_invalid_certs: bool,
1535    /// Path to a file to cache tokens in, defaults to ~/.cache/kanidm_tokens
1536    #[clap(
1537        short,
1538        long,
1539        env = "KANIDM_TOKEN_CACHE_PATH",
1540    hide = true,
1541     default_value = None,
1542    global=true,
1543    value_parser = clap::builder::NonEmptyStringValueParser::new())]
1544    token_cache_path: Option<String>,
1545
1546    #[clap(
1547        short,
1548        long,
1549        env = "KANIDM_PASSWORD",
1550        hide = true,
1551        global = true,
1552        value_parser = clap::builder::NonEmptyStringValueParser::new())]
1553    /// Supply a password to the login option
1554    password: Option<String>,
1555}
1556
1557impl KanidmClientParser {
1558    fn get_token_cache_path(&self) -> String {
1559        match self.token_cache_path.clone() {
1560            None => CLIENT_TOKEN_CACHE.to_string(),
1561            Some(val) => val.clone(),
1562        }
1563    }
1564}