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 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 #[clap(short, long, env = "KANIDM_DEBUG")]
28 pub debug: bool,
29}
30
31#[derive(Debug, Clone, Copy, Default)]
32pub enum OutputMode {
34 #[default]
35 Text,
36 Json,
37}
38
39impl From<OutputMode> for clap::builder::OsStr {
40 fn from(output_mode: OutputMode) -> Self {
41 match output_mode {
42 OutputMode::Text => "text".into(),
43 OutputMode::Json => "json".into(),
44 }
45 }
46}
47
48impl std::str::FromStr for OutputMode {
49 type Err = String;
50 fn from_str(s: &str) -> Result<OutputMode, std::string::String> {
51 match s.to_lowercase().as_str() {
52 "text" => Ok(OutputMode::Text),
53 "json" => Ok(OutputMode::Json),
54 _ => Ok(OutputMode::Text),
55 }
56 }
57}
58
59impl OutputMode {
60 pub fn print_message<T>(self, input: T)
61 where
62 T: serde::Serialize + fmt::Debug + fmt::Display,
63 {
64 match self {
65 OutputMode::Json => {
66 println!(
67 "{}",
68 serde_json::to_string(&input).unwrap_or(format!("{input:?}"))
69 );
70 }
71 OutputMode::Text => {
72 println!("{input}");
73 }
74 }
75 }
76}
77
78#[derive(Debug, Args, Clone)]
79pub struct GroupNamedMembers {
80 name: String,
81 #[clap(required = true, num_args(1..))]
82 members: Vec<String>,
83}
84
85#[derive(Debug, Args, Clone)]
86pub struct GroupPosixOpt {
87 name: String,
88 #[clap(long)]
89 gidnumber: Option<u32>,
90}
91
92#[derive(Debug, Subcommand, Clone)]
93pub enum GroupPosix {
94 #[clap(name = "show")]
96 Show(Named),
97 #[clap(name = "set")]
99 Set(GroupPosixOpt),
100 #[clap(name = "reset-gidnumber")]
102 ResetGidnumber { group_id: String },
103}
104
105#[derive(Debug, Clone, Copy, Eq, PartialEq)]
106pub enum AccountPolicyCredentialType {
107 Any,
108 Mfa,
109 Passkey,
110 AttestedPasskey,
111}
112
113impl AccountPolicyCredentialType {
114 pub fn as_str(&self) -> &'static str {
115 match self {
116 Self::Any => "any",
117 Self::Mfa => "mfa",
118 Self::Passkey => "passkey",
119 Self::AttestedPasskey => "attested_passkey",
120 }
121 }
122}
123
124impl ValueEnum for AccountPolicyCredentialType {
125 fn value_variants<'a>() -> &'a [Self] {
126 &[Self::Any, Self::Mfa, Self::Passkey, Self::AttestedPasskey]
127 }
128
129 fn to_possible_value(&self) -> Option<PossibleValue> {
130 Some(self.as_str().into())
131 }
132}
133
134#[derive(Debug, Subcommand, Clone)]
135pub enum GroupAccountPolicyOpt {
136 #[clap(name = "enable")]
138 Enable { name: String },
139 #[clap(name = "auth-expiry")]
141 AuthSessionExpiry { name: String, expiry: u32 },
142 #[clap(name = "credential-type-minimum")]
145 CredentialTypeMinimum {
146 name: String,
147 #[clap(value_enum)]
148 value: AccountPolicyCredentialType,
149 },
150 #[clap(name = "password-minimum-length")]
152 PasswordMinimumLength { name: String, length: u32 },
153
154 #[clap(name = "privilege-expiry")]
156 PrivilegedSessionExpiry { name: String, expiry: u32 },
157
158 #[clap(name = "webauthn-attestation-ca-list")]
163 WebauthnAttestationCaList {
164 name: String,
165 attestation_ca_list_json_file: PathBuf,
166 },
167
168 #[clap(name = "limit-search-max-results")]
171 LimitSearchMaxResults { name: String, maximum: u32 },
172 #[clap(name = "limit-search-max-filter-test")]
176 LimitSearchMaxFilterTest { name: String, maximum: u32 },
177 #[clap(name = "allow-primary-cred-fallback")]
180 AllowPrimaryCredFallback {
181 name: String,
182 #[clap(name = "allow", action = clap::ArgAction::Set)]
183 allow: bool,
184 },
185
186 #[clap(name = "reset-auth-expiry")]
188 ResetAuthSessionExpiry { name: String },
189 #[clap(name = "reset-password-minimum-length")]
191 ResetPasswordMinimumLength { name: String },
192 #[clap(name = "reset-privilege-expiry")]
194 ResetPrivilegedSessionExpiry { name: String },
195 #[clap(name = "reset-webauthn-attestation-ca-list")]
198 ResetWebauthnAttestationCaList { name: String },
199 #[clap(name = "reset-limit-search-max-results")]
201 ResetLimitSearchMaxResults { name: String },
202 #[clap(name = "reset-limit-search-max-filter-test")]
204 ResetLimitSearchMaxFilterTest { name: String },
205}
206
207#[derive(Debug, Subcommand, Clone)]
208pub enum GroupOpt {
209 #[clap(name = "list")]
211 List,
212 #[clap(name = "get")]
214 Get(Named),
215 #[clap(name = "search")]
217 Search {
218 name: String,
220 },
221 #[clap(name = "create")]
223 Create {
224 name: String,
226 #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new())]
228 entry_managed_by: Option<String>,
229 },
230 #[clap(name = "delete")]
232 Delete(Named),
233 #[clap(name = "list-members")]
235 ListMembers(Named),
236 #[clap(name = "set-members")]
239 SetMembers(GroupNamedMembers),
240 #[clap(name = "set-mail")]
244 SetMail { name: String, mail: Vec<String> },
245 #[clap(name = "set-description")]
247 SetDescription {
248 name: String,
249 description: Option<String>,
250 },
251 #[clap(name = "set-entry-manager")]
253 SetEntryManagedBy {
254 name: String,
256 entry_managed_by: String,
258 },
259 #[clap(name = "rename")]
261 Rename {
262 name: String,
264 new_name: String,
266 },
267 #[clap(name = "purge-members")]
269 PurgeMembers(Named),
270 #[clap(name = "add-members")]
272 AddMembers(GroupNamedMembers),
273 #[clap(name = "remove-members")]
275 RemoveMembers(GroupNamedMembers),
276 #[clap(name = "posix")]
278 Posix {
279 #[clap(subcommand)]
280 commands: GroupPosix,
281 },
282 #[clap(name = "account-policy")]
284 AccountPolicy {
285 #[clap(subcommand)]
286 commands: GroupAccountPolicyOpt,
287 },
288}
289
290#[derive(Clone, Debug, ValueEnum)]
291pub enum GraphType {
292 Graphviz,
293 Mermaid,
294 MermaidElk,
295}
296
297#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, ValueEnum)]
298pub enum ObjectType {
299 Group,
300 BuiltinGroup,
301 ServiceAccount,
302 Person,
303}
304
305#[derive(Debug, Args, Clone)]
306pub struct GraphCommonOpt {
307 #[arg(value_enum)]
308 pub graph_type: GraphType,
309 #[clap()]
310 pub filter: Vec<ObjectType>,
311}
312
313#[derive(Debug, Args, Clone)]
314pub struct AccountCommonOpt {
315 #[clap()]
316 account_id: String,
317}
318
319#[derive(Debug, Args, Clone)]
320pub struct AccountNamedOpt {
321 #[clap(flatten)]
322 aopts: AccountCommonOpt,
323}
324
325#[derive(Debug, Args, Clone)]
326pub struct AccountNamedExpireDateTimeOpt {
327 #[clap(flatten)]
328 aopts: AccountCommonOpt,
329 #[clap(name = "datetime", verbatim_doc_comment)]
330 datetime: String,
336}
337
338#[derive(Debug, Args, Clone)]
339pub struct AccountNamedValidDateTimeOpt {
340 #[clap(flatten)]
341 aopts: AccountCommonOpt,
342 #[clap(name = "datetime")]
343 datetime: String,
346}
347
348#[derive(Debug, Args, Clone)]
349pub struct AccountNamedTagOpt {
350 #[clap(flatten)]
351 aopts: AccountCommonOpt,
352 #[clap(name = "tag")]
353 tag: String,
354}
355
356#[derive(Debug, Args, Clone)]
357pub struct AccountNamedTagPkOpt {
358 #[clap(flatten)]
359 aopts: AccountCommonOpt,
360 #[clap(name = "tag")]
361 tag: String,
362 #[clap(name = "pubkey")]
363 pubkey: String,
364}
365
366#[derive(Debug, Args, Clone)]
367pub struct UseResetTokenOpt {
369 #[clap(name = "token")]
370 token: String,
371}
372
373#[derive(Debug, Args, Clone)]
374pub struct AccountCreateOpt {
375 #[clap(flatten)]
376 aopts: AccountCommonOpt,
377 #[clap(name = "display-name")]
378 display_name: String,
379}
380
381#[derive(Debug, Subcommand, Clone)]
382pub enum AccountCredential {
383 #[clap(name = "status")]
385 Status(AccountNamedOpt),
386 #[clap(name = "update")]
388 Update(AccountNamedOpt),
389 #[clap(name = "use-reset-token")]
391 UseResetToken(UseResetTokenOpt),
392 #[clap(name = "create-reset-token")]
395 CreateResetToken {
396 #[clap(flatten)]
397 aopts: AccountCommonOpt,
398
399 ttl: Option<u32>,
402 },
403 #[clap(name = "send-reset-token")]
406 SendResetToken {
407 account_id: String,
408
409 ttl: Option<u64>,
412
413 alternate_email: Option<String>,
416 },
417 #[clap(name = "softlock-reset")]
419 SoftlockReset {
420 account_id: String,
421 #[clap(name = "datetime", default_value = "now", verbatim_doc_comment)]
422 datetime: String,
426 }
427}
428
429#[derive(Debug, Subcommand, Clone)]
431pub enum AccountRadius {
432 #[clap(name = "show-secret")]
434 Show(AccountNamedOpt),
435 #[clap(name = "generate-secret")]
437 Generate(AccountNamedOpt),
438 #[clap(name = "delete-secret")]
439 DeleteSecret(AccountNamedOpt),
441}
442
443#[derive(Debug, Args, Clone)]
444pub struct AccountPosixOpt {
445 #[clap(flatten)]
446 aopts: AccountCommonOpt,
447 #[clap(long)]
448 gidnumber: Option<u32>,
449 #[clap(long, value_parser = clap::builder::NonEmptyStringValueParser::new())]
450 shell: Option<String>,
452}
453
454#[derive(Debug, Subcommand, Clone)]
455pub enum PersonPosix {
456 #[clap(name = "show")]
457 Show(AccountNamedOpt),
458 #[clap(name = "set")]
459 Set(AccountPosixOpt),
460 #[clap(name = "set-password")]
461 SetPassword(AccountNamedOpt),
462 #[clap(name = "reset-gidnumber")]
464 ResetGidnumber { account_id: String },
465}
466
467#[derive(Debug, Subcommand, Clone)]
468pub enum ServiceAccountPosix {
469 #[clap(name = "show")]
470 Show(AccountNamedOpt),
471 #[clap(name = "set")]
472 Set(AccountPosixOpt),
473 #[clap(name = "reset-gidnumber")]
475 ResetGidnumber { account_id: String },
476}
477
478#[derive(Debug, Args, Clone)]
479pub struct PersonUpdateOpt {
480 #[clap(flatten)]
481 aopts: AccountCommonOpt,
482 #[clap(long, short, help = "Set the legal name for the person.",
483 value_parser = clap::builder::NonEmptyStringValueParser::new())]
484 legalname: Option<String>,
485 #[clap(long, short, help = "Set the account name for the person.",
486 value_parser = clap::builder::NonEmptyStringValueParser::new())]
487 newname: Option<String>,
488 #[clap(long, short = 'i', help = "Set the display name for the person.",
489 value_parser = clap::builder::NonEmptyStringValueParser::new())]
490 displayname: Option<String>,
491 #[clap(
492 long,
493 short,
494 help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
495 )]
496 mail: Option<Vec<String>>,
497}
498
499#[derive(Debug, Subcommand, Clone)]
500pub enum AccountSsh {
501 #[clap(name = "list-publickeys")]
502 List(AccountNamedOpt),
503 #[clap(name = "add-publickey")]
504 Add(AccountNamedTagPkOpt),
505 #[clap(name = "delete-publickey")]
506 Delete(AccountNamedTagOpt),
507}
508
509#[derive(Debug, Subcommand, Clone)]
510pub enum AccountValidity {
511 #[clap(name = "show")]
513 Show(AccountNamedOpt),
514 #[clap(name = "expire-at")]
516 ExpireAt(AccountNamedExpireDateTimeOpt),
517 #[clap(name = "begin-from")]
519 BeginFrom(AccountNamedValidDateTimeOpt),
520}
521
522#[derive(Debug, Subcommand, Clone)]
523pub enum AccountCertificate {
524 #[clap(name = "status")]
525 Status { account_id: String },
526 #[clap(name = "create")]
527 Create {
528 account_id: String,
529 certificate_path: PathBuf,
530 },
531}
532
533#[derive(Debug, Subcommand, Clone)]
534pub enum AccountUserAuthToken {
535 #[clap(name = "status")]
537 Status(AccountNamedOpt),
538 #[clap(name = "destroy")]
541 Destroy {
542 #[clap(flatten)]
543 aopts: AccountCommonOpt,
544
545 #[clap(name = "session-id")]
547 session_id: Uuid,
548 },
549}
550
551#[derive(Debug, Subcommand, Clone)]
552pub enum PersonOpt {
553 #[clap(name = "credential")]
555 Credential {
556 #[clap(subcommand)]
557 commands: AccountCredential,
558 },
559 #[clap(name = "radius")]
561 Radius {
562 #[clap(subcommand)]
563 commands: AccountRadius,
564 },
565 #[clap(name = "posix")]
567 Posix {
568 #[clap(subcommand)]
569 commands: PersonPosix,
570 },
571 #[clap(name = "session")]
573 Session {
574 #[clap(subcommand)]
575 commands: AccountUserAuthToken,
576 },
577 #[clap(name = "ssh")]
579 Ssh {
580 #[clap(subcommand)]
581 commands: AccountSsh,
582 },
583 #[clap(name = "list")]
585 List,
586 #[clap(name = "get")]
588 Get(AccountNamedOpt),
589 #[clap(name = "search")]
591 Search { account_id: String },
592 #[clap(name = "update")]
594 Update(PersonUpdateOpt),
595 #[clap(name = "create")]
597 Create(AccountCreateOpt),
598 #[clap(name = "delete")]
600 Delete(AccountNamedOpt),
601 #[clap(name = "validity")]
603 Validity {
604 #[clap(subcommand)]
605 commands: AccountValidity,
606 },
607 #[clap(name = "certificate", hide = true)]
608 Certificate {
609 #[clap(subcommand)]
610 commands: AccountCertificate,
611 },
612}
613
614#[derive(Debug, Subcommand, Clone)]
615pub enum ServiceAccountCredential {
616 #[clap(name = "status")]
618 Status(AccountNamedOpt),
619 #[clap(name = "generate")]
622 GeneratePw(AccountNamedOpt),
623}
624
625#[derive(Debug, Subcommand, Clone)]
626pub enum ServiceAccountApiToken {
627 #[clap(name = "status")]
629 Status(AccountNamedOpt),
630 #[clap(name = "generate")]
632 Generate {
633 #[clap(flatten)]
634 aopts: AccountCommonOpt,
635
636 #[clap(name = "label")]
639 label: String,
640 #[clap(name = "expiry")]
641 #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new())]
644 expiry: Option<String>,
645 #[clap(short = 'w', long = "readwrite")]
647 read_write: bool,
648
649 #[clap(short = 'c', long = "compact")]
655 compact: bool,
656 },
657 #[clap(name = "destroy")]
660 Destroy {
661 #[clap(flatten)]
662 aopts: AccountCommonOpt,
663
664 #[clap(name = "token-id")]
666 token_id: Uuid,
667 },
668}
669
670#[derive(Debug, Args, Clone)]
671pub struct ServiceAccountUpdateOpt {
672 #[clap(flatten)]
673 aopts: AccountCommonOpt,
674 #[clap(long, short, help = "Set the account name for the service account.",
675 value_parser = clap::builder::NonEmptyStringValueParser::new())]
676 newname: Option<String>,
677 #[clap(
678 long,
679 short = 'i',
680 help = "Set the display name for the service account.",
681 value_parser = clap::builder::NonEmptyStringValueParser::new()
682 )]
683 displayname: Option<String>,
684 #[clap(
685 long,
686 short = 'e',
687 help = "Set the entry manager for the service account.",
688 value_parser = clap::builder::NonEmptyStringValueParser::new()
689 )]
690 entry_managed_by: Option<String>,
691 #[clap(
692 long,
693 short,
694 help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
695 )]
696 mail: Option<Vec<String>>,
697}
698
699#[derive(Debug, Subcommand, Clone)]
700pub enum ServiceAccountOpt {
701 #[clap(name = "credential")]
703 Credential {
704 #[clap(subcommand)]
705 commands: ServiceAccountCredential,
706 },
707 #[clap(name = "api-token")]
709 ApiToken {
710 #[clap(subcommand)]
711 commands: ServiceAccountApiToken,
712 },
713 #[clap(name = "posix")]
715 Posix {
716 #[clap(subcommand)]
717 commands: ServiceAccountPosix,
718 },
719 #[clap(name = "session")]
721 Session {
722 #[clap(subcommand)]
723 commands: AccountUserAuthToken,
724 },
725 #[clap(name = "ssh")]
727 Ssh {
728 #[clap(subcommand)]
729 commands: AccountSsh,
730 },
731 #[clap(name = "list")]
733 List,
734 #[clap(name = "get")]
736 Get(AccountNamedOpt),
737 #[clap(name = "create")]
739 Create {
740 #[clap(flatten)]
741 aopts: AccountCommonOpt,
742 #[clap(name = "display-name")]
743 display_name: String,
744 #[clap(name = "entry-managed-by")]
745 entry_managed_by: String,
746 },
747 #[clap(name = "update")]
749 Update(ServiceAccountUpdateOpt),
750 #[clap(name = "delete")]
752 Delete(AccountNamedOpt),
753 #[clap(name = "validity")]
755 Validity {
756 #[clap(subcommand)]
757 commands: AccountValidity,
758 },
759 #[clap(name = "into-person")]
763 IntoPerson(AccountNamedOpt),
764}
765
766#[derive(Debug, Subcommand, Clone)]
767pub enum RecycleOpt {
768 #[clap(name = "list")]
769 List,
771 #[clap(name = "get")]
772 Get(Named),
774 #[clap(name = "revive")]
775 Revive(Named),
777}
778
779#[derive(Debug, Args, Clone)]
780pub struct LoginOpt {}
781
782#[derive(Debug, Args, Clone)]
783pub struct LogoutOpt {
784 #[clap(short, long)]
785 local_only: bool,
787}
788
789#[derive(Debug, Subcommand, Clone)]
790pub enum SessionOpt {
791 #[clap(name = "list")]
792 List,
794 #[clap(name = "cleanup")]
795 Cleanup,
797}
798
799#[derive(Debug, Subcommand, Clone)]
800pub enum RawOpt {
801 #[clap(name = "search")]
802 Search {
803 filter: ScimFilter
804 },
805 #[clap(name = "create")]
806 Create {
807 file: PathBuf
808 },
809 #[clap(name = "update")]
810 Update {
811 file: PathBuf
812 },
813 #[clap(name = "delete")]
814 Delete {
815 id: String
816 },
817}
818
819#[derive(Debug, Subcommand, Clone)]
820pub enum SelfOpt {
821 #[clap(name = "identify-user")]
823 IdentifyUser,
824 Whoami,
826}
827
828#[derive(Debug, Args, Clone)]
829pub struct Oauth2SetDisplayname {
830 #[clap(flatten)]
831 nopt: Named,
832 #[clap(name = "displayname")]
833 displayname: String,
834}
835
836#[derive(Debug, Args, Clone)]
837pub struct Oauth2SetImplicitScopes {
838 #[clap(flatten)]
839 nopt: Named,
840 #[clap(name = "scopes")]
841 scopes: Vec<String>,
842}
843
844#[derive(Debug, Args, Clone)]
845pub struct Oauth2CreateScopeMapOpt {
846 #[clap(flatten)]
847 nopt: Named,
848 #[clap(name = "group")]
849 group: String,
850 #[clap(name = "scopes", required = true, num_args=1.. )]
851 scopes: Vec<String>,
852}
853
854#[derive(Debug, Args, Clone)]
855pub struct Oauth2DeleteScopeMapOpt {
856 #[clap(flatten)]
857 nopt: Named,
858 #[clap(name = "group")]
859 group: String,
860}
861
862#[derive(Debug, Clone, Copy, Eq, PartialEq)]
863pub enum Oauth2ClaimMapJoin {
864 Csv,
865 Ssv,
866 Array,
867}
868
869impl Oauth2ClaimMapJoin {
870 pub fn as_str(&self) -> &'static str {
871 match self {
872 Self::Csv => "csv",
873 Self::Ssv => "ssv",
874 Self::Array => "array",
875 }
876 }
877}
878
879impl ValueEnum for Oauth2ClaimMapJoin {
880 fn value_variants<'a>() -> &'a [Self] {
881 &[Self::Csv, Self::Ssv, Self::Array]
882 }
883
884 fn to_possible_value(&self) -> Option<PossibleValue> {
885 Some(self.as_str().into())
886 }
887}
888
889#[derive(Debug, Subcommand, Clone)]
890pub enum Oauth2Opt {
891 #[clap(name = "list")]
892 List,
894 #[clap(name = "get")]
895 Get(Named),
897 #[clap(name = "create")]
901 CreateBasic {
903 #[clap(name = "name")]
904 name: String,
905 #[clap(name = "displayname")]
906 displayname: String,
907 #[clap(name = "origin")]
908 origin: String,
909 },
910 #[clap(name = "create-public")]
911 CreatePublic {
917 #[clap(name = "name")]
918 name: String,
919 #[clap(name = "displayname")]
920 displayname: String,
921 #[clap(name = "origin")]
922 origin: String,
923 },
924 #[clap(name = "update-scope-map", visible_aliases=&["create-scope-map"])]
925 UpdateScopeMap(Oauth2CreateScopeMapOpt),
927 #[clap(name = "delete-scope-map")]
928 DeleteScopeMap(Oauth2DeleteScopeMapOpt),
930
931 #[clap(name = "update-sup-scope-map", visible_aliases=&["create-sup-scope-map"])]
932 UpdateSupScopeMap(Oauth2CreateScopeMapOpt),
934 #[clap(name = "delete-sup-scope-map")]
935 DeleteSupScopeMap(Oauth2DeleteScopeMapOpt),
937
938 #[clap(name = "update-claim-map", visible_aliases=&["create-claim-map"])]
939 UpdateClaimMap {
941 name: String,
942 claim_name: String,
943 group: String,
944 values: Vec<String>,
945 },
946 #[clap(name = "update-claim-map-join")]
947 UpdateClaimMapJoin {
948 name: String,
949 claim_name: String,
950 join: Oauth2ClaimMapJoin,
953 },
954 #[clap(name = "delete-claim-map")]
955 DeleteClaimMap {
957 name: String,
958 claim_name: String,
959 group: String,
960 },
961
962 #[clap(name = "reset-basic-secret")]
963 ResetSecrets(Named),
966 #[clap(name = "show-basic-secret")]
967 ShowBasicSecret(Named),
969 #[clap(name = "delete")]
970 Delete(Named),
972 #[clap(name = "set-displayname")]
974 SetDisplayname(Oauth2SetDisplayname),
975 #[clap(name = "set-name")]
979 SetName {
980 #[clap(flatten)]
981 nopt: Named,
982 #[clap(name = "newname")]
983 name: String,
984 },
985
986 #[clap(name = "set-landing-url")]
989 SetLandingUrl {
990 #[clap(flatten)]
991 nopt: Named,
992 #[clap(name = "landing-url")]
993 url: Url,
994 },
995 #[clap(name = "set-image")]
997 SetImage {
998 #[clap(flatten)]
999 nopt: Named,
1000 #[clap(name = "file-path")]
1001 path: PathBuf,
1003 #[clap(name = "image-type")]
1004 image_type: Option<ImageType>,
1006 },
1007 #[clap(name = "remove-image")]
1009 RemoveImage(Named),
1010
1011 #[clap(name = "add-redirect-url")]
1015 AddOrigin {
1016 name: String,
1017 #[clap(name = "url")]
1018 origin: Url,
1019 },
1020
1021 #[clap(name = "remove-redirect-url")]
1023 RemoveOrigin {
1024 name: String,
1025 #[clap(name = "url")]
1026 origin: Url,
1027 },
1028 #[clap(name = "enable-pkce")]
1029 EnablePkce(Named),
1031 #[clap(name = "warning-insecure-client-disable-pkce")]
1034 DisablePkce(Named),
1035 #[clap(name = "warning-enable-legacy-crypto")]
1036 EnableLegacyCrypto(Named),
1040 #[clap(name = "disable-legacy-crypto")]
1042 DisableLegacyCrypto(Named),
1043 #[clap(name = "enable-strict-redirect-url")]
1047 EnableStrictRedirectUri { name: String },
1048 #[clap(name = "disable-strict-redirect-url")]
1049 DisableStrictRedirectUri { name: String },
1050 #[clap(name = "enable-localhost-redirects")]
1051 EnablePublicLocalhost { name: String },
1053 #[clap(name = "disable-localhost-redirects")]
1055 DisablePublicLocalhost { name: String },
1056 #[clap(name = "prefer-short-username")]
1058 PreferShortUsername(Named),
1059 #[clap(name = "prefer-spn-username")]
1061 PreferSPNUsername(Named),
1062 #[cfg(feature = "dev-oauth2-device-flow")]
1063 DeviceFlowEnable(Named),
1065 #[cfg(feature = "dev-oauth2-device-flow")]
1066 DeviceFlowDisable(Named),
1068 #[clap(name = "rotate-cryptographic-keys")]
1074 RotateCryptographicKeys {
1075 name: String,
1076 #[clap(value_parser = parse_rfc3339)]
1077 rotate_at: OffsetDateTime,
1078 },
1079 #[clap(name = "revoke-cryptographic-key")]
1083 RevokeCryptographicKey { name: String, key_id: String },
1084 #[clap(name = "disable-consent-prompt")]
1088 DisableConsentPrompt(Named),
1089 #[clap(name = "enable-consent-prompt")]
1091 EnableConsentPrompt(Named),
1092}
1093
1094#[derive(Args, Debug, Clone)]
1095pub struct OptSetDomainDisplayname {
1096 #[clap(name = "new-display-name")]
1097 new_display_name: String,
1098}
1099
1100#[derive(Debug, Subcommand, Clone)]
1101pub enum PwBadlistOpt {
1102 #[clap[name = "show"]]
1103 Show,
1105 #[clap[name = "upload"]]
1106 Upload {
1110 #[clap(value_parser, required = true, num_args(1..))]
1111 paths: Vec<PathBuf>,
1112 #[clap(short = 'n', long)]
1114 dryrun: bool,
1115 },
1116 #[clap[name = "remove", hide = true]]
1117 Remove {
1120 #[clap(value_parser, required = true, num_args(1..))]
1121 paths: Vec<PathBuf>,
1122 },
1123}
1124
1125#[derive(Debug, Subcommand, Clone)]
1126pub enum DeniedNamesOpt {
1127 #[clap[name = "show"]]
1128 Show,
1130 #[clap[name = "append"]]
1131 Append {
1132 #[clap(value_parser, required = true, num_args(1..))]
1133 names: Vec<String>,
1134 },
1135 #[clap[name = "remove"]]
1136 Remove {
1138 #[clap(value_parser, required = true, num_args(1..))]
1139 names: Vec<String>,
1140 },
1141}
1142
1143#[derive(Debug, Subcommand, Clone)]
1144pub enum DomainOpt {
1145 #[clap[name = "set-displayname"]]
1146 SetDisplayname(OptSetDomainDisplayname),
1148 #[clap[name = "set-ldap-queryable-attrs"]]
1150 SetLdapMaxQueryableAttrs {
1151 #[clap(name = "maximum-queryable-attrs")]
1152 new_max_queryable_attrs: usize,
1153 },
1154 #[clap[name = "set-ldap-basedn"]]
1155 SetLdapBasedn {
1160 #[clap(name = "new-basedn")]
1161 new_basedn: String,
1162 },
1163 SetLdapAllowUnixPasswordBind {
1166 #[clap(name = "allow", action = clap::ArgAction::Set)]
1167 enable: bool,
1168 },
1169 SetAllowEasterEggs {
1173 #[clap(name = "allow", action = clap::ArgAction::Set)]
1174 enable: bool,
1175 },
1176 #[clap(name = "show")]
1177 Show,
1179 #[clap(name = "revoke-key")]
1180 RevokeKey { key_id: String },
1183 #[clap(name = "set-image")]
1185 SetImage {
1186 #[clap(name = "file-path")]
1187 path: PathBuf,
1188 #[clap(name = "image-type")]
1189 image_type: Option<ImageType>,
1190 },
1191 #[clap(name = "remove-image")]
1193 RemoveImage,
1194}
1195
1196#[derive(Debug, Subcommand, Clone)]
1197pub enum MessageOpt {
1198 #[clap(name = "list")]
1199 List,
1201
1202 #[clap(name = "get")]
1203 Get {
1205 message_id: Uuid
1206 },
1207
1208 #[clap(name = "mark-as-sent")]
1209 MarkAsSent {
1212 message_id: Uuid
1213 },
1214
1215 #[clap(name = "send-test-message")]
1216 SendTestMessage {
1217 to: String,
1219 }
1220}
1221
1222#[derive(Debug, Subcommand, Clone)]
1223pub enum SynchOpt {
1224 #[clap(name = "list")]
1225 List,
1227 #[clap(name = "get")]
1228 Get(Named),
1230 #[clap(name = "set-credential-portal")]
1231 SetCredentialPortal {
1234 #[clap()]
1235 account_id: String,
1236
1237 #[clap(name = "url")]
1238 url: Option<Url>,
1239 },
1240 #[clap(name = "create")]
1242 Create {
1243 #[clap()]
1244 account_id: String,
1245
1246 #[clap(name = "description",
1247 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1248 description: Option<String>,
1249 },
1250 #[clap(name = "generate-token")]
1252 GenerateToken {
1253 #[clap()]
1254 account_id: String,
1255 #[clap()]
1256 label: String,
1257 },
1258 #[clap(name = "destroy-token")]
1260 DestroyToken {
1261 #[clap()]
1262 account_id: String,
1263 },
1264 #[clap(name = "set-yield-attributes")]
1268 SetYieldAttributes {
1269 #[clap()]
1270 account_id: String,
1271
1272 #[clap(name = "attributes")]
1273 attrs: Vec<String>,
1274 },
1275 #[clap(name = "force-refresh")]
1279 ForceRefresh {
1280 #[clap()]
1281 account_id: String,
1282 },
1283 #[clap(name = "finalise")]
1289 Finalise {
1290 #[clap()]
1291 account_id: String,
1292 },
1293 #[clap(name = "terminate")]
1299 Terminate {
1300 #[clap()]
1301 account_id: String,
1302 },
1303}
1304
1305#[derive(Debug, Subcommand, Clone)]
1306pub enum AuthSessionExpiryOpt {
1307 #[clap[name = "get"]]
1308 Get,
1310 #[clap[name = "set"]]
1311 Set {
1313 #[clap(name = "expiry")]
1314 expiry: u32,
1315 },
1316}
1317
1318#[derive(Debug, Subcommand, Clone)]
1319pub enum PrivilegedSessionExpiryOpt {
1320 #[clap[name = "get"]]
1321 Get,
1323 #[clap[name = "set"]]
1324 Set {
1326 #[clap(name = "expiry")]
1327 expiry: u32,
1328 },
1329}
1330
1331#[derive(Args, Debug, Clone)]
1332pub struct ApiSchemaDownloadOpt {
1333 #[clap(name = "filename", env, default_value = "./kanidm-openapi.json")]
1335 filename: PathBuf,
1336 #[clap(short, long, env)]
1338 force: bool,
1339}
1340
1341#[derive(Debug, Subcommand, Clone)]
1342pub enum ApiOpt {
1343 #[clap(name = "download-schema")]
1345 DownloadSchema(ApiSchemaDownloadOpt),
1346}
1347
1348#[derive(Debug, Subcommand, Clone)]
1349pub enum SchemaClassOpt {
1350 List,
1352 Search {
1353 query: String,
1354 },
1355}
1356
1357#[derive(Debug, Subcommand, Clone)]
1358pub enum SchemaAttrOpt {
1359 List,
1361 Search {
1362 query: String,
1363 },
1364}
1365
1366#[derive(Debug, Subcommand, Clone)]
1367pub enum SchemaOpt {
1368 #[clap(name = "class")]
1370 Class {
1371 #[clap(subcommand)]
1372 commands: SchemaClassOpt,
1373 },
1374 #[clap(name = "attribute", visible_alias = "attr")]
1376 Attribute {
1377 #[clap(subcommand)]
1378 commands: SchemaAttrOpt,
1379 },
1380}
1381
1382#[derive(Debug, Subcommand, Clone)]
1383pub enum SystemOpt {
1384 #[clap(name = "pw-badlist")]
1385 PwBadlist {
1387 #[clap(subcommand)]
1388 commands: PwBadlistOpt,
1389 },
1390 #[clap(name = "denied-names")]
1391 DeniedNames {
1393 #[clap(subcommand)]
1394 commands: DeniedNamesOpt,
1395 },
1396 #[clap(name = "oauth2")]
1397 Oauth2 {
1399 #[clap(subcommand)]
1400 commands: Oauth2Opt,
1401 },
1402 #[clap(name = "domain")]
1403 Domain {
1405 #[clap(subcommand)]
1406 commands: DomainOpt,
1407 },
1408 #[clap(name = "sync")]
1409 Synch {
1411 #[clap(subcommand)]
1412 commands: SynchOpt,
1413 },
1414 #[clap(name = "message-queue", alias = "message")]
1415 Message {
1417 #[clap(subcommand)]
1418 commands: MessageOpt,
1419 },
1420 #[clap(name = "api")]
1421 Api {
1423 #[clap(subcommand)]
1424 commands: ApiOpt,
1425 },
1426}
1427
1428#[derive(Debug, Subcommand, Clone)]
1429#[clap(about = "Kanidm Client Utility")]
1430pub enum KanidmClientOpt {
1431 Login(LoginOpt),
1433 Reauth,
1435 Logout(LogoutOpt),
1437 Session {
1439 #[clap(subcommand)]
1440 commands: SessionOpt,
1441 },
1442 #[clap(name = "self")]
1443 CSelf {
1445 #[clap(subcommand)]
1446 commands: SelfOpt,
1447 },
1448 Person {
1450 #[clap(subcommand)]
1451 commands: PersonOpt,
1452 },
1453 Group {
1455 #[clap(subcommand)]
1456 commands: GroupOpt,
1457 },
1458 #[clap(name = "service-account")]
1460 ServiceAccount {
1461 #[clap(subcommand)]
1462 commands: ServiceAccountOpt,
1463 },
1464 #[clap(name = "graph")]
1466 Graph(GraphCommonOpt),
1467
1468 #[clap(hide = true)]
1470 Schema {
1471 #[clap(subcommand)]
1472 commands: SchemaOpt,
1473 },
1474
1475 System {
1477 #[clap(subcommand)]
1478 commands: SystemOpt,
1479 },
1480 #[clap(name = "recycle-bin")]
1481 Recycle {
1483 #[clap(subcommand)]
1484 commands: RecycleOpt,
1485 },
1486 #[clap(hide = true)]
1488 Raw {
1489 #[clap(subcommand)]
1490 commands: RawOpt,
1491 },
1492 Version,
1494}
1495
1496#[derive(Debug, clap::Parser, Clone)]
1497#[clap(about = "Kanidm Client Utility")]
1498pub struct KanidmClientParser {
1499 #[clap(subcommand)]
1500 pub commands: KanidmClientOpt,
1501
1502 #[clap(short, long, env = "KANIDM_DEBUG", global = true)]
1504 pub debug: bool,
1505 #[clap(short = 'I', long = "instance", env = "KANIDM_INSTANCE", global = true,
1507 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1508 pub instance: Option<String>,
1509 #[clap(short = 'H', long = "url", env = "KANIDM_URL", global = true,
1511 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1512 pub addr: Option<String>,
1513 #[clap(
1515 short = 'D',
1516 long = "name",
1517 env = "KANIDM_NAME",
1518 value_parser = clap::builder::NonEmptyStringValueParser::new(), global=true
1519 )]
1520 pub username: Option<String>,
1521 #[clap(
1523 value_parser,
1524 short = 'C',
1525 long = "ca",
1526 env = "KANIDM_CA_PATH",
1527 global = true
1528 )]
1529 pub ca_path: Option<PathBuf>,
1530 #[clap(short, long = "output", env = "KANIDM_OUTPUT", global = true, default_value=OutputMode::default())]
1532 output_mode: OutputMode,
1533 #[clap(
1535 long = "skip-hostname-verification",
1536 env = "KANIDM_SKIP_HOSTNAME_VERIFICATION",
1537 default_value_t = false,
1538 global = true
1539 )]
1540 skip_hostname_verification: bool,
1541 #[clap(
1543 long = "accept-invalid-certs",
1544 env = "KANIDM_ACCEPT_INVALID_CERTS",
1545 default_value_t = false,
1546 global = true
1547 )]
1548 accept_invalid_certs: bool,
1549 #[clap(
1551 short,
1552 long,
1553 env = "KANIDM_TOKEN_CACHE_PATH",
1554 hide = true,
1555 default_value = None,
1556 global=true,
1557 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1558 token_cache_path: Option<String>,
1559
1560 #[clap(
1561 short,
1562 long,
1563 env = "KANIDM_PASSWORD",
1564 hide = true,
1565 global = true,
1566 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1567 password: Option<String>,
1569}
1570
1571impl KanidmClientParser {
1572 fn get_token_cache_path(&self) -> String {
1573 match self.token_cache_path.clone() {
1574 None => CLIENT_TOKEN_CACHE.to_string(),
1575 Some(val) => val.clone(),
1576 }
1577 }
1578}