Skip to main content

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