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 Ok(OffsetDateTime::now_utc())
12 } else {
13 OffsetDateTime::parse(input, &Rfc3339)
14 }
15}
16
17#[derive(Debug, Args, Clone)]
18pub struct Named {
19 pub name: String,
20}
21
22#[derive(Debug, Args, Clone)]
23pub struct DebugOpt {
24 #[clap(short, long, env = "KANIDM_DEBUG")]
26 pub debug: bool,
27}
28
29#[derive(Debug, Clone, Copy, Default)]
30pub enum OutputMode {
32 #[default]
33 Text,
34 Json,
35}
36
37impl From<OutputMode> for clap::builder::OsStr {
38 fn from(output_mode: OutputMode) -> Self {
39 match output_mode {
40 OutputMode::Text => "text".into(),
41 OutputMode::Json => "json".into(),
42 }
43 }
44}
45
46impl std::str::FromStr for OutputMode {
47 type Err = String;
48 fn from_str(s: &str) -> Result<OutputMode, std::string::String> {
49 match s.to_lowercase().as_str() {
50 "text" => Ok(OutputMode::Text),
51 "json" => Ok(OutputMode::Json),
52 _ => Ok(OutputMode::Text),
53 }
54 }
55}
56
57impl OutputMode {
58 pub fn print_message<T>(self, input: T)
59 where
60 T: serde::Serialize + fmt::Debug + fmt::Display,
61 {
62 match self {
63 OutputMode::Json => {
64 println!(
65 "{}",
66 serde_json::to_string(&input).unwrap_or(format!("{input:?}"))
67 );
68 }
69 OutputMode::Text => {
70 println!("{input}");
71 }
72 }
73 }
74}
75
76#[derive(Debug, Args, Clone)]
77pub struct GroupNamedMembers {
78 name: String,
79 #[clap(required = true, num_args(1..))]
80 members: Vec<String>,
81}
82
83#[derive(Debug, Args, Clone)]
84pub struct GroupPosixOpt {
85 name: String,
86 #[clap(long)]
87 gidnumber: Option<u32>,
88}
89
90#[derive(Debug, Subcommand, Clone)]
91pub enum GroupPosix {
92 #[clap(name = "show")]
94 Show(Named),
95 #[clap(name = "set")]
97 Set(GroupPosixOpt),
98 #[clap(name = "reset-gidnumber")]
100 ResetGidnumber { group_id: String },
101}
102
103#[derive(Debug, Clone, Copy, Eq, PartialEq)]
104pub enum AccountPolicyCredentialType {
105 Any,
106 Mfa,
107 Passkey,
108 AttestedPasskey,
109}
110
111impl AccountPolicyCredentialType {
112 pub fn as_str(&self) -> &'static str {
113 match self {
114 Self::Any => "any",
115 Self::Mfa => "mfa",
116 Self::Passkey => "passkey",
117 Self::AttestedPasskey => "attested_passkey",
118 }
119 }
120}
121
122impl ValueEnum for AccountPolicyCredentialType {
123 fn value_variants<'a>() -> &'a [Self] {
124 &[Self::Any, Self::Mfa, Self::Passkey, Self::AttestedPasskey]
125 }
126
127 fn to_possible_value(&self) -> Option<PossibleValue> {
128 Some(self.as_str().into())
129 }
130}
131
132#[derive(Debug, Subcommand, Clone)]
133pub enum GroupAccountPolicyOpt {
134 #[clap(name = "enable")]
136 Enable { name: String },
137 #[clap(name = "auth-expiry")]
139 AuthSessionExpiry { name: String, expiry: u32 },
140 #[clap(name = "credential-type-minimum")]
143 CredentialTypeMinimum {
144 name: String,
145 #[clap(value_enum)]
146 value: AccountPolicyCredentialType,
147 },
148 #[clap(name = "password-minimum-length")]
150 PasswordMinimumLength { name: String, length: u32 },
151
152 #[clap(name = "privilege-expiry")]
154 PrivilegedSessionExpiry { name: String, expiry: u32 },
155
156 #[clap(name = "webauthn-attestation-ca-list")]
161 WebauthnAttestationCaList {
162 name: String,
163 attestation_ca_list_json_file: PathBuf,
164 },
165
166 #[clap(name = "limit-search-max-results")]
169 LimitSearchMaxResults { name: String, maximum: u32 },
170 #[clap(name = "limit-search-max-filter-test")]
174 LimitSearchMaxFilterTest { name: String, maximum: u32 },
175 #[clap(name = "allow-primary-cred-fallback")]
178 AllowPrimaryCredFallback {
179 name: String,
180 #[clap(name = "allow", action = clap::ArgAction::Set)]
181 allow: bool,
182 },
183
184 #[clap(name = "reset-auth-expiry")]
186 ResetAuthSessionExpiry { name: String },
187 #[clap(name = "reset-password-minimum-length")]
189 ResetPasswordMinimumLength { name: String },
190 #[clap(name = "reset-privilege-expiry")]
192 ResetPrivilegedSessionExpiry { name: String },
193 #[clap(name = "reset-webauthn-attestation-ca-list")]
196 ResetWebauthnAttestationCaList { name: String },
197 #[clap(name = "reset-limit-search-max-results")]
199 ResetLimitSearchMaxResults { name: String },
200 #[clap(name = "reset-limit-search-max-filter-test")]
202 ResetLimitSearchMaxFilterTest { name: String },
203}
204
205#[derive(Debug, Subcommand, Clone)]
206pub enum GroupOpt {
207 #[clap(name = "list")]
209 List,
210 #[clap(name = "get")]
212 Get(Named),
213 #[clap(name = "search")]
215 Search {
216 name: String,
218 },
219 #[clap(name = "create")]
221 Create {
222 name: String,
224 #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new())]
226 entry_managed_by: Option<String>,
227 },
228 #[clap(name = "delete")]
230 Delete(Named),
231 #[clap(name = "list-members")]
233 ListMembers(Named),
234 #[clap(name = "set-members")]
237 SetMembers(GroupNamedMembers),
238 #[clap(name = "set-mail")]
242 SetMail { name: String, mail: Vec<String> },
243 #[clap(name = "set-description")]
245 SetDescription {
246 name: String,
247 description: Option<String>,
248 },
249 #[clap(name = "set-entry-manager")]
251 SetEntryManagedBy {
252 name: String,
254 entry_managed_by: String,
256 },
257 #[clap(name = "rename")]
259 Rename {
260 name: String,
262 new_name: String,
264 },
265 #[clap(name = "purge-members")]
267 PurgeMembers(Named),
268 #[clap(name = "add-members")]
270 AddMembers(GroupNamedMembers),
271 #[clap(name = "remove-members")]
273 RemoveMembers(GroupNamedMembers),
274 #[clap(name = "posix")]
276 Posix {
277 #[clap(subcommand)]
278 commands: GroupPosix,
279 },
280 #[clap(name = "account-policy")]
282 AccountPolicy {
283 #[clap(subcommand)]
284 commands: GroupAccountPolicyOpt,
285 },
286}
287
288#[derive(Clone, Debug, ValueEnum)]
289pub enum GraphType {
290 Graphviz,
291 Mermaid,
292 MermaidElk,
293}
294
295#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, ValueEnum)]
296pub enum ObjectType {
297 Group,
298 BuiltinGroup,
299 ServiceAccount,
300 Person,
301}
302
303#[derive(Debug, Args, Clone)]
304pub struct GraphCommonOpt {
305 #[arg(value_enum)]
306 pub graph_type: GraphType,
307 #[clap()]
308 pub filter: Vec<ObjectType>,
309}
310
311#[derive(Debug, Args, Clone)]
312pub struct AccountCommonOpt {
313 #[clap()]
314 account_id: String,
315}
316
317#[derive(Debug, Args, Clone)]
318pub struct AccountNamedOpt {
319 #[clap(flatten)]
320 aopts: AccountCommonOpt,
321}
322
323#[derive(Debug, Args, Clone)]
324pub struct AccountNamedExpireDateTimeOpt {
325 #[clap(flatten)]
326 aopts: AccountCommonOpt,
327 #[clap(name = "datetime", verbatim_doc_comment)]
328 datetime: String,
334}
335
336#[derive(Debug, Args, Clone)]
337pub struct AccountNamedValidDateTimeOpt {
338 #[clap(flatten)]
339 aopts: AccountCommonOpt,
340 #[clap(name = "datetime")]
341 datetime: String,
344}
345
346#[derive(Debug, Args, Clone)]
347pub struct AccountNamedTagOpt {
348 #[clap(flatten)]
349 aopts: AccountCommonOpt,
350 #[clap(name = "tag")]
351 tag: String,
352}
353
354#[derive(Debug, Args, Clone)]
355pub struct AccountNamedTagPkOpt {
356 #[clap(flatten)]
357 aopts: AccountCommonOpt,
358 #[clap(name = "tag")]
359 tag: String,
360 #[clap(name = "pubkey")]
361 pubkey: String,
362}
363
364#[derive(Debug, Args, Clone)]
365pub struct UseResetTokenOpt {
367 #[clap(name = "token")]
368 token: String,
369}
370
371#[derive(Debug, Args, Clone)]
372pub struct AccountCreateOpt {
373 #[clap(flatten)]
374 aopts: AccountCommonOpt,
375 #[clap(name = "display-name")]
376 display_name: String,
377}
378
379#[derive(Debug, Subcommand, Clone)]
380pub enum AccountCredential {
381 #[clap(name = "status")]
383 Status(AccountNamedOpt),
384 #[clap(name = "update")]
386 Update(AccountNamedOpt),
387 #[clap(name = "use-reset-token")]
389 UseResetToken(UseResetTokenOpt),
390 #[clap(name = "create-reset-token")]
393 CreateResetToken {
394 #[clap(flatten)]
395 aopts: AccountCommonOpt,
396
397 ttl: Option<u32>,
400 },
401}
402
403#[derive(Debug, Subcommand, Clone)]
405pub enum AccountRadius {
406 #[clap(name = "show-secret")]
408 Show(AccountNamedOpt),
409 #[clap(name = "generate-secret")]
411 Generate(AccountNamedOpt),
412 #[clap(name = "delete-secret")]
413 DeleteSecret(AccountNamedOpt),
415}
416
417#[derive(Debug, Args, Clone)]
418pub struct AccountPosixOpt {
419 #[clap(flatten)]
420 aopts: AccountCommonOpt,
421 #[clap(long)]
422 gidnumber: Option<u32>,
423 #[clap(long, value_parser = clap::builder::NonEmptyStringValueParser::new())]
424 shell: Option<String>,
426}
427
428#[derive(Debug, Subcommand, Clone)]
429pub enum PersonPosix {
430 #[clap(name = "show")]
431 Show(AccountNamedOpt),
432 #[clap(name = "set")]
433 Set(AccountPosixOpt),
434 #[clap(name = "set-password")]
435 SetPassword(AccountNamedOpt),
436 #[clap(name = "reset-gidnumber")]
438 ResetGidnumber { account_id: String },
439}
440
441#[derive(Debug, Subcommand, Clone)]
442pub enum ServiceAccountPosix {
443 #[clap(name = "show")]
444 Show(AccountNamedOpt),
445 #[clap(name = "set")]
446 Set(AccountPosixOpt),
447 #[clap(name = "reset-gidnumber")]
449 ResetGidnumber { account_id: String },
450}
451
452#[derive(Debug, Args, Clone)]
453pub struct PersonUpdateOpt {
454 #[clap(flatten)]
455 aopts: AccountCommonOpt,
456 #[clap(long, short, help = "Set the legal name for the person.",
457 value_parser = clap::builder::NonEmptyStringValueParser::new())]
458 legalname: Option<String>,
459 #[clap(long, short, help = "Set the account name for the person.",
460 value_parser = clap::builder::NonEmptyStringValueParser::new())]
461 newname: Option<String>,
462 #[clap(long, short = 'i', help = "Set the display name for the person.",
463 value_parser = clap::builder::NonEmptyStringValueParser::new())]
464 displayname: Option<String>,
465 #[clap(
466 long,
467 short,
468 help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
469 )]
470 mail: Option<Vec<String>>,
471}
472
473#[derive(Debug, Subcommand, Clone)]
474pub enum AccountSsh {
475 #[clap(name = "list-publickeys")]
476 List(AccountNamedOpt),
477 #[clap(name = "add-publickey")]
478 Add(AccountNamedTagPkOpt),
479 #[clap(name = "delete-publickey")]
480 Delete(AccountNamedTagOpt),
481}
482
483#[derive(Debug, Subcommand, Clone)]
484pub enum AccountValidity {
485 #[clap(name = "show")]
487 Show(AccountNamedOpt),
488 #[clap(name = "expire-at")]
490 ExpireAt(AccountNamedExpireDateTimeOpt),
491 #[clap(name = "begin-from")]
493 BeginFrom(AccountNamedValidDateTimeOpt),
494}
495
496#[derive(Debug, Subcommand, Clone)]
497pub enum AccountCertificate {
498 #[clap(name = "status")]
499 Status { account_id: String },
500 #[clap(name = "create")]
501 Create {
502 account_id: String,
503 certificate_path: PathBuf,
504 },
505}
506
507#[derive(Debug, Subcommand, Clone)]
508pub enum AccountUserAuthToken {
509 #[clap(name = "status")]
511 Status(AccountNamedOpt),
512 #[clap(name = "destroy")]
515 Destroy {
516 #[clap(flatten)]
517 aopts: AccountCommonOpt,
518
519 #[clap(name = "session-id")]
521 session_id: Uuid,
522 },
523}
524
525#[derive(Debug, Subcommand, Clone)]
526pub enum PersonOpt {
527 #[clap(name = "credential")]
529 Credential {
530 #[clap(subcommand)]
531 commands: AccountCredential,
532 },
533 #[clap(name = "radius")]
535 Radius {
536 #[clap(subcommand)]
537 commands: AccountRadius,
538 },
539 #[clap(name = "posix")]
541 Posix {
542 #[clap(subcommand)]
543 commands: PersonPosix,
544 },
545 #[clap(name = "session")]
547 Session {
548 #[clap(subcommand)]
549 commands: AccountUserAuthToken,
550 },
551 #[clap(name = "ssh")]
553 Ssh {
554 #[clap(subcommand)]
555 commands: AccountSsh,
556 },
557 #[clap(name = "list")]
559 List,
560 #[clap(name = "get")]
562 Get(AccountNamedOpt),
563 #[clap(name = "search")]
565 Search { account_id: String },
566 #[clap(name = "update")]
568 Update(PersonUpdateOpt),
569 #[clap(name = "create")]
571 Create(AccountCreateOpt),
572 #[clap(name = "delete")]
574 Delete(AccountNamedOpt),
575 #[clap(name = "validity")]
577 Validity {
578 #[clap(subcommand)]
579 commands: AccountValidity,
580 },
581 #[clap(name = "certificate", hide = true)]
582 Certificate {
583 #[clap(subcommand)]
584 commands: AccountCertificate,
585 },
586}
587
588#[derive(Debug, Subcommand, Clone)]
589pub enum ServiceAccountCredential {
590 #[clap(name = "status")]
592 Status(AccountNamedOpt),
593 #[clap(name = "generate")]
596 GeneratePw(AccountNamedOpt),
597}
598
599#[derive(Debug, Subcommand, Clone)]
600pub enum ServiceAccountApiToken {
601 #[clap(name = "status")]
603 Status(AccountNamedOpt),
604 #[clap(name = "generate")]
606 Generate {
607 #[clap(flatten)]
608 aopts: AccountCommonOpt,
609
610 #[clap(name = "label")]
613 label: String,
614 #[clap(name = "expiry")]
615 #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new())]
618 expiry: Option<String>,
619 #[clap(short = 'w', long = "readwrite")]
621 read_write: bool,
622 },
623 #[clap(name = "destroy")]
626 Destroy {
627 #[clap(flatten)]
628 aopts: AccountCommonOpt,
629
630 #[clap(name = "token-id")]
632 token_id: Uuid,
633 },
634}
635
636#[derive(Debug, Args, Clone)]
637pub struct ServiceAccountUpdateOpt {
638 #[clap(flatten)]
639 aopts: AccountCommonOpt,
640 #[clap(long, short, help = "Set the account name for the service account.",
641 value_parser = clap::builder::NonEmptyStringValueParser::new())]
642 newname: Option<String>,
643 #[clap(
644 long,
645 short = 'i',
646 help = "Set the display name for the service account.",
647 value_parser = clap::builder::NonEmptyStringValueParser::new()
648 )]
649 displayname: Option<String>,
650 #[clap(
651 long,
652 short = 'e',
653 help = "Set the entry manager for the service account.",
654 value_parser = clap::builder::NonEmptyStringValueParser::new()
655 )]
656 entry_managed_by: Option<String>,
657 #[clap(
658 long,
659 short,
660 help = "Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'"
661 )]
662 mail: Option<Vec<String>>,
663}
664
665#[derive(Debug, Subcommand, Clone)]
666pub enum ServiceAccountOpt {
667 #[clap(name = "credential")]
669 Credential {
670 #[clap(subcommand)]
671 commands: ServiceAccountCredential,
672 },
673 #[clap(name = "api-token")]
675 ApiToken {
676 #[clap(subcommand)]
677 commands: ServiceAccountApiToken,
678 },
679 #[clap(name = "posix")]
681 Posix {
682 #[clap(subcommand)]
683 commands: ServiceAccountPosix,
684 },
685 #[clap(name = "session")]
687 Session {
688 #[clap(subcommand)]
689 commands: AccountUserAuthToken,
690 },
691 #[clap(name = "ssh")]
693 Ssh {
694 #[clap(subcommand)]
695 commands: AccountSsh,
696 },
697 #[clap(name = "list")]
699 List,
700 #[clap(name = "get")]
702 Get(AccountNamedOpt),
703 #[clap(name = "create")]
705 Create {
706 #[clap(flatten)]
707 aopts: AccountCommonOpt,
708 #[clap(name = "display-name")]
709 display_name: String,
710 #[clap(name = "entry-managed-by")]
711 entry_managed_by: String,
712 },
713 #[clap(name = "update")]
715 Update(ServiceAccountUpdateOpt),
716 #[clap(name = "delete")]
718 Delete(AccountNamedOpt),
719 #[clap(name = "validity")]
721 Validity {
722 #[clap(subcommand)]
723 commands: AccountValidity,
724 },
725 #[clap(name = "into-person")]
729 IntoPerson(AccountNamedOpt),
730}
731
732#[derive(Debug, Subcommand, Clone)]
733pub enum RecycleOpt {
734 #[clap(name = "list")]
735 List,
737 #[clap(name = "get")]
738 Get(Named),
740 #[clap(name = "revive")]
741 Revive(Named),
743}
744
745#[derive(Debug, Args, Clone)]
746pub struct LoginOpt {}
747
748#[derive(Debug, Args, Clone)]
749pub struct LogoutOpt {
750 #[clap(short, long)]
751 local_only: bool,
753}
754
755#[derive(Debug, Subcommand, Clone)]
756pub enum SessionOpt {
757 #[clap(name = "list")]
758 List,
760 #[clap(name = "cleanup")]
761 Cleanup,
763}
764
765#[derive(Debug, Subcommand, Clone)]
766pub enum RawOpt {
767 #[clap(name = "search")]
768 Search {
769 filter: ScimFilter
770 },
771 #[clap(name = "create")]
772 Create {
773 file: PathBuf
774 },
775 #[clap(name = "update")]
776 Update {
777 file: PathBuf
778 },
779 #[clap(name = "delete")]
780 Delete {
781 id: String
782 },
783}
784
785#[derive(Debug, Subcommand, Clone)]
786pub enum SelfOpt {
787 #[clap(name = "identify-user")]
789 IdentifyUser,
790 Whoami,
792}
793
794#[derive(Debug, Args, Clone)]
795pub struct Oauth2SetDisplayname {
796 #[clap(flatten)]
797 nopt: Named,
798 #[clap(name = "displayname")]
799 displayname: String,
800}
801
802#[derive(Debug, Args, Clone)]
803pub struct Oauth2SetImplicitScopes {
804 #[clap(flatten)]
805 nopt: Named,
806 #[clap(name = "scopes")]
807 scopes: Vec<String>,
808}
809
810#[derive(Debug, Args, Clone)]
811pub struct Oauth2CreateScopeMapOpt {
812 #[clap(flatten)]
813 nopt: Named,
814 #[clap(name = "group")]
815 group: String,
816 #[clap(name = "scopes", required = true, num_args=1.. )]
817 scopes: Vec<String>,
818}
819
820#[derive(Debug, Args, Clone)]
821pub struct Oauth2DeleteScopeMapOpt {
822 #[clap(flatten)]
823 nopt: Named,
824 #[clap(name = "group")]
825 group: String,
826}
827
828#[derive(Debug, Clone, Copy, Eq, PartialEq)]
829pub enum Oauth2ClaimMapJoin {
830 Csv,
831 Ssv,
832 Array,
833}
834
835impl Oauth2ClaimMapJoin {
836 pub fn as_str(&self) -> &'static str {
837 match self {
838 Self::Csv => "csv",
839 Self::Ssv => "ssv",
840 Self::Array => "array",
841 }
842 }
843}
844
845impl ValueEnum for Oauth2ClaimMapJoin {
846 fn value_variants<'a>() -> &'a [Self] {
847 &[Self::Csv, Self::Ssv, Self::Array]
848 }
849
850 fn to_possible_value(&self) -> Option<PossibleValue> {
851 Some(self.as_str().into())
852 }
853}
854
855#[derive(Debug, Subcommand, Clone)]
856pub enum Oauth2Opt {
857 #[clap(name = "list")]
858 List,
860 #[clap(name = "get")]
861 Get(Named),
863 #[clap(name = "create")]
867 CreateBasic {
869 #[clap(name = "name")]
870 name: String,
871 #[clap(name = "displayname")]
872 displayname: String,
873 #[clap(name = "origin")]
874 origin: String,
875 },
876 #[clap(name = "create-public")]
877 CreatePublic {
883 #[clap(name = "name")]
884 name: String,
885 #[clap(name = "displayname")]
886 displayname: String,
887 #[clap(name = "origin")]
888 origin: String,
889 },
890 #[clap(name = "update-scope-map", visible_aliases=&["create-scope-map"])]
891 UpdateScopeMap(Oauth2CreateScopeMapOpt),
893 #[clap(name = "delete-scope-map")]
894 DeleteScopeMap(Oauth2DeleteScopeMapOpt),
896
897 #[clap(name = "update-sup-scope-map", visible_aliases=&["create-sup-scope-map"])]
898 UpdateSupScopeMap(Oauth2CreateScopeMapOpt),
900 #[clap(name = "delete-sup-scope-map")]
901 DeleteSupScopeMap(Oauth2DeleteScopeMapOpt),
903
904 #[clap(name = "update-claim-map", visible_aliases=&["create-claim-map"])]
905 UpdateClaimMap {
907 name: String,
908 claim_name: String,
909 group: String,
910 values: Vec<String>,
911 },
912 #[clap(name = "update-claim-map-join")]
913 UpdateClaimMapJoin {
914 name: String,
915 claim_name: String,
916 join: Oauth2ClaimMapJoin,
919 },
920 #[clap(name = "delete-claim-map")]
921 DeleteClaimMap {
923 name: String,
924 claim_name: String,
925 group: String,
926 },
927
928 #[clap(name = "reset-basic-secret")]
929 ResetSecrets(Named),
932 #[clap(name = "show-basic-secret")]
933 ShowBasicSecret(Named),
935 #[clap(name = "delete")]
936 Delete(Named),
938 #[clap(name = "set-displayname")]
940 SetDisplayname(Oauth2SetDisplayname),
941 #[clap(name = "set-name")]
945 SetName {
946 #[clap(flatten)]
947 nopt: Named,
948 #[clap(name = "newname")]
949 name: String,
950 },
951
952 #[clap(name = "set-landing-url")]
955 SetLandingUrl {
956 #[clap(flatten)]
957 nopt: Named,
958 #[clap(name = "landing-url")]
959 url: Url,
960 },
961 #[clap(name = "set-image")]
963 SetImage {
964 #[clap(flatten)]
965 nopt: Named,
966 #[clap(name = "file-path")]
967 path: PathBuf,
969 #[clap(name = "image-type")]
970 image_type: Option<ImageType>,
972 },
973 #[clap(name = "remove-image")]
975 RemoveImage(Named),
976
977 #[clap(name = "add-redirect-url")]
981 AddOrigin {
982 name: String,
983 #[clap(name = "url")]
984 origin: Url,
985 },
986
987 #[clap(name = "remove-redirect-url")]
989 RemoveOrigin {
990 name: String,
991 #[clap(name = "url")]
992 origin: Url,
993 },
994 #[clap(name = "enable-pkce")]
995 EnablePkce(Named),
997 #[clap(name = "warning-insecure-client-disable-pkce")]
1000 DisablePkce(Named),
1001 #[clap(name = "warning-enable-legacy-crypto")]
1002 EnableLegacyCrypto(Named),
1006 #[clap(name = "disable-legacy-crypto")]
1008 DisableLegacyCrypto(Named),
1009 #[clap(name = "enable-strict-redirect-url")]
1013 EnableStrictRedirectUri { name: String },
1014 #[clap(name = "disable-strict-redirect-url")]
1015 DisableStrictRedirectUri { name: String },
1016 #[clap(name = "enable-localhost-redirects")]
1017 EnablePublicLocalhost { name: String },
1019 #[clap(name = "disable-localhost-redirects")]
1021 DisablePublicLocalhost { name: String },
1022 #[clap(name = "prefer-short-username")]
1024 PreferShortUsername(Named),
1025 #[clap(name = "prefer-spn-username")]
1027 PreferSPNUsername(Named),
1028 #[cfg(feature = "dev-oauth2-device-flow")]
1029 DeviceFlowEnable(Named),
1031 #[cfg(feature = "dev-oauth2-device-flow")]
1032 DeviceFlowDisable(Named),
1034 #[clap(name = "rotate-cryptographic-keys")]
1040 RotateCryptographicKeys {
1041 name: String,
1042 #[clap(value_parser = parse_rfc3339)]
1043 rotate_at: OffsetDateTime,
1044 },
1045 #[clap(name = "revoke-cryptographic-key")]
1049 RevokeCryptographicKey { name: String, key_id: String },
1050 #[clap(name = "disable-consent-prompt")]
1054 DisableConsentPrompt(Named),
1055 #[clap(name = "enable-consent-prompt")]
1057 EnableConsentPrompt(Named),
1058}
1059
1060#[derive(Args, Debug, Clone)]
1061pub struct OptSetDomainDisplayname {
1062 #[clap(name = "new-display-name")]
1063 new_display_name: String,
1064}
1065
1066#[derive(Debug, Subcommand, Clone)]
1067pub enum PwBadlistOpt {
1068 #[clap[name = "show"]]
1069 Show,
1071 #[clap[name = "upload"]]
1072 Upload {
1076 #[clap(value_parser, required = true, num_args(1..))]
1077 paths: Vec<PathBuf>,
1078 #[clap(short = 'n', long)]
1080 dryrun: bool,
1081 },
1082 #[clap[name = "remove", hide = true]]
1083 Remove {
1086 #[clap(value_parser, required = true, num_args(1..))]
1087 paths: Vec<PathBuf>,
1088 },
1089}
1090
1091#[derive(Debug, Subcommand, Clone)]
1092pub enum DeniedNamesOpt {
1093 #[clap[name = "show"]]
1094 Show,
1096 #[clap[name = "append"]]
1097 Append {
1098 #[clap(value_parser, required = true, num_args(1..))]
1099 names: Vec<String>,
1100 },
1101 #[clap[name = "remove"]]
1102 Remove {
1104 #[clap(value_parser, required = true, num_args(1..))]
1105 names: Vec<String>,
1106 },
1107}
1108
1109#[derive(Debug, Subcommand, Clone)]
1110pub enum DomainOpt {
1111 #[clap[name = "set-displayname"]]
1112 SetDisplayname(OptSetDomainDisplayname),
1114 #[clap[name = "set-ldap-queryable-attrs"]]
1116 SetLdapMaxQueryableAttrs {
1117 #[clap(name = "maximum-queryable-attrs")]
1118 new_max_queryable_attrs: usize,
1119 },
1120 #[clap[name = "set-ldap-basedn"]]
1121 SetLdapBasedn {
1126 #[clap(name = "new-basedn")]
1127 new_basedn: String,
1128 },
1129 SetLdapAllowUnixPasswordBind {
1132 #[clap(name = "allow", action = clap::ArgAction::Set)]
1133 enable: bool,
1134 },
1135 SetAllowEasterEggs {
1139 #[clap(name = "allow", action = clap::ArgAction::Set)]
1140 enable: bool,
1141 },
1142 #[clap(name = "show")]
1143 Show,
1145 #[clap(name = "revoke-key")]
1146 RevokeKey { key_id: String },
1149 #[clap(name = "set-image")]
1151 SetImage {
1152 #[clap(name = "file-path")]
1153 path: PathBuf,
1154 #[clap(name = "image-type")]
1155 image_type: Option<ImageType>,
1156 },
1157 #[clap(name = "remove-image")]
1159 RemoveImage,
1160}
1161
1162#[derive(Debug, Subcommand, Clone)]
1163pub enum MessageOpt {
1164 #[clap(name = "list")]
1165 List,
1167
1168 #[clap(name = "get")]
1169 Get {
1171 message_id: Uuid
1172 },
1173
1174 #[clap(name = "mark-as-sent")]
1175 MarkAsSent {
1178 message_id: Uuid
1179 },
1180
1181 #[clap(name = "send-test-message")]
1182 SendTestMessage {
1183 to: String,
1185 }
1186}
1187
1188#[derive(Debug, Subcommand, Clone)]
1189pub enum SynchOpt {
1190 #[clap(name = "list")]
1191 List,
1193 #[clap(name = "get")]
1194 Get(Named),
1196 #[clap(name = "set-credential-portal")]
1197 SetCredentialPortal {
1200 #[clap()]
1201 account_id: String,
1202
1203 #[clap(name = "url")]
1204 url: Option<Url>,
1205 },
1206 #[clap(name = "create")]
1208 Create {
1209 #[clap()]
1210 account_id: String,
1211
1212 #[clap(name = "description",
1213 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1214 description: Option<String>,
1215 },
1216 #[clap(name = "generate-token")]
1218 GenerateToken {
1219 #[clap()]
1220 account_id: String,
1221 #[clap()]
1222 label: String,
1223 },
1224 #[clap(name = "destroy-token")]
1226 DestroyToken {
1227 #[clap()]
1228 account_id: String,
1229 },
1230 #[clap(name = "set-yield-attributes")]
1234 SetYieldAttributes {
1235 #[clap()]
1236 account_id: String,
1237
1238 #[clap(name = "attributes")]
1239 attrs: Vec<String>,
1240 },
1241 #[clap(name = "force-refresh")]
1245 ForceRefresh {
1246 #[clap()]
1247 account_id: String,
1248 },
1249 #[clap(name = "finalise")]
1255 Finalise {
1256 #[clap()]
1257 account_id: String,
1258 },
1259 #[clap(name = "terminate")]
1265 Terminate {
1266 #[clap()]
1267 account_id: String,
1268 },
1269}
1270
1271#[derive(Debug, Subcommand, Clone)]
1272pub enum AuthSessionExpiryOpt {
1273 #[clap[name = "get"]]
1274 Get,
1276 #[clap[name = "set"]]
1277 Set {
1279 #[clap(name = "expiry")]
1280 expiry: u32,
1281 },
1282}
1283
1284#[derive(Debug, Subcommand, Clone)]
1285pub enum PrivilegedSessionExpiryOpt {
1286 #[clap[name = "get"]]
1287 Get,
1289 #[clap[name = "set"]]
1290 Set {
1292 #[clap(name = "expiry")]
1293 expiry: u32,
1294 },
1295}
1296
1297#[derive(Args, Debug, Clone)]
1298pub struct ApiSchemaDownloadOpt {
1299 #[clap(name = "filename", env, default_value = "./kanidm-openapi.json")]
1301 filename: PathBuf,
1302 #[clap(short, long, env)]
1304 force: bool,
1305}
1306
1307#[derive(Debug, Subcommand, Clone)]
1308pub enum ApiOpt {
1309 #[clap(name = "download-schema")]
1311 DownloadSchema(ApiSchemaDownloadOpt),
1312}
1313
1314#[derive(Debug, Subcommand, Clone)]
1315pub enum SchemaClassOpt {
1316 List,
1318 Search {
1319 query: String,
1320 },
1321}
1322
1323#[derive(Debug, Subcommand, Clone)]
1324pub enum SchemaAttrOpt {
1325 List,
1327 Search {
1328 query: String,
1329 },
1330}
1331
1332#[derive(Debug, Subcommand, Clone)]
1333pub enum SchemaOpt {
1334 #[clap(name = "class")]
1336 Class {
1337 #[clap(subcommand)]
1338 commands: SchemaClassOpt,
1339 },
1340 #[clap(name = "attribute", visible_alias = "attr")]
1342 Attribute {
1343 #[clap(subcommand)]
1344 commands: SchemaAttrOpt,
1345 },
1346}
1347
1348#[derive(Debug, Subcommand, Clone)]
1349pub enum SystemOpt {
1350 #[clap(name = "pw-badlist")]
1351 PwBadlist {
1353 #[clap(subcommand)]
1354 commands: PwBadlistOpt,
1355 },
1356 #[clap(name = "denied-names")]
1357 DeniedNames {
1359 #[clap(subcommand)]
1360 commands: DeniedNamesOpt,
1361 },
1362 #[clap(name = "oauth2")]
1363 Oauth2 {
1365 #[clap(subcommand)]
1366 commands: Oauth2Opt,
1367 },
1368 #[clap(name = "domain")]
1369 Domain {
1371 #[clap(subcommand)]
1372 commands: DomainOpt,
1373 },
1374 #[clap(name = "sync")]
1375 Synch {
1377 #[clap(subcommand)]
1378 commands: SynchOpt,
1379 },
1380 #[clap(name = "message-queue", alias = "message")]
1381 Message {
1383 #[clap(subcommand)]
1384 commands: MessageOpt,
1385 },
1386 #[clap(name = "api")]
1387 Api {
1389 #[clap(subcommand)]
1390 commands: ApiOpt,
1391 },
1392}
1393
1394#[derive(Debug, Subcommand, Clone)]
1395#[clap(about = "Kanidm Client Utility")]
1396pub enum KanidmClientOpt {
1397 Login(LoginOpt),
1399 Reauth,
1401 Logout(LogoutOpt),
1403 Session {
1405 #[clap(subcommand)]
1406 commands: SessionOpt,
1407 },
1408 #[clap(name = "self")]
1409 CSelf {
1411 #[clap(subcommand)]
1412 commands: SelfOpt,
1413 },
1414 Person {
1416 #[clap(subcommand)]
1417 commands: PersonOpt,
1418 },
1419 Group {
1421 #[clap(subcommand)]
1422 commands: GroupOpt,
1423 },
1424 #[clap(name = "service-account")]
1426 ServiceAccount {
1427 #[clap(subcommand)]
1428 commands: ServiceAccountOpt,
1429 },
1430 #[clap(name = "graph")]
1432 Graph(GraphCommonOpt),
1433
1434 #[clap(hide = true)]
1436 Schema {
1437 #[clap(subcommand)]
1438 commands: SchemaOpt,
1439 },
1440
1441 System {
1443 #[clap(subcommand)]
1444 commands: SystemOpt,
1445 },
1446 #[clap(name = "recycle-bin")]
1447 Recycle {
1449 #[clap(subcommand)]
1450 commands: RecycleOpt,
1451 },
1452 #[clap(hide = true)]
1454 Raw {
1455 #[clap(subcommand)]
1456 commands: RawOpt,
1457 },
1458 Version,
1460}
1461
1462#[derive(Debug, clap::Parser, Clone)]
1463#[clap(about = "Kanidm Client Utility")]
1464pub struct KanidmClientParser {
1465 #[clap(subcommand)]
1466 pub commands: KanidmClientOpt,
1467
1468 #[clap(short, long, env = "KANIDM_DEBUG", global = true)]
1470 pub debug: bool,
1471 #[clap(short = 'I', long = "instance", env = "KANIDM_INSTANCE", global = true,
1473 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1474 pub instance: Option<String>,
1475 #[clap(short = 'H', long = "url", env = "KANIDM_URL", global = true,
1477 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1478 pub addr: Option<String>,
1479 #[clap(
1481 short = 'D',
1482 long = "name",
1483 env = "KANIDM_NAME",
1484 value_parser = clap::builder::NonEmptyStringValueParser::new(), global=true
1485 )]
1486 pub username: Option<String>,
1487 #[clap(
1489 value_parser,
1490 short = 'C',
1491 long = "ca",
1492 env = "KANIDM_CA_PATH",
1493 global = true
1494 )]
1495 pub ca_path: Option<PathBuf>,
1496 #[clap(short, long = "output", env = "KANIDM_OUTPUT", global = true, default_value=OutputMode::default())]
1498 output_mode: OutputMode,
1499 #[clap(
1501 long = "skip-hostname-verification",
1502 env = "KANIDM_SKIP_HOSTNAME_VERIFICATION",
1503 default_value_t = false,
1504 global = true
1505 )]
1506 skip_hostname_verification: bool,
1507 #[clap(
1509 long = "accept-invalid-certs",
1510 env = "KANIDM_ACCEPT_INVALID_CERTS",
1511 default_value_t = false,
1512 global = true
1513 )]
1514 accept_invalid_certs: bool,
1515 #[clap(
1517 short,
1518 long,
1519 env = "KANIDM_TOKEN_CACHE_PATH",
1520 hide = true,
1521 default_value = None,
1522 global=true,
1523 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1524 token_cache_path: Option<String>,
1525
1526 #[clap(
1527 short,
1528 long,
1529 env = "KANIDM_PASSWORD",
1530 hide = true,
1531 global = true,
1532 value_parser = clap::builder::NonEmptyStringValueParser::new())]
1533 password: Option<String>,
1535}
1536
1537impl KanidmClientParser {
1538 fn get_token_cache_path(&self) -> String {
1539 match self.token_cache_path.clone() {
1540 None => CLIENT_TOKEN_CACHE.to_string(),
1541 Some(val) => val.clone(),
1542 }
1543 }
1544}