kanidm_cli/opt/
kanidm.rs

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