1#![allow(non_upper_case_globals)]
7
8use crate::be::dbentry::DbIdentSpn;
9use crate::be::dbvalue::DbValueOauthClaimMapJoinV1;
10use crate::credential::{apppwd::ApplicationPassword, totp::Totp, Credential};
11use crate::prelude::*;
12use crate::repl::cid::Cid;
13use crate::server::identity::IdentityId;
14use crate::server::keys::KeyId;
15use crate::valueset::image::ImageValueThings;
16use crate::valueset::uuid_to_proto_string;
17use compact_jwt::{crypto::JwsRs256Signer, JwsEs256Signer};
18use crypto_glue::{s256::Sha256Output, traits::Zeroizing};
19use hashbrown::HashSet;
20use kanidm_lib_crypto::x509_cert::{der::DecodePem, Certificate};
21use kanidm_proto::internal::ImageValue;
22use kanidm_proto::internal::{ApiTokenPurpose, Filter as ProtoFilter, UiHint};
23use kanidm_proto::scim_v1::ScimOauth2ClaimMapJoinChar;
24use kanidm_proto::v1::UatPurposeStatus;
25use num_enum::TryFromPrimitive;
26use openssl::ec::EcKey;
27use openssl::pkey::Private;
28use regex::Regex;
29use serde::{Deserialize, Serialize};
30use sshkey_attest::proto::PublicKey as SshPublicKey;
31use std::cmp::Ordering;
32use std::collections::BTreeSet;
33use std::convert::TryFrom;
34use std::fmt;
35use std::fmt::Formatter;
36use std::hash::Hash;
37use std::str::FromStr;
38use std::time::Duration;
39use time::OffsetDateTime;
40use url::Url;
41use uuid::Uuid;
42use webauthn_rs::prelude::{
43 AttestationCaList, AttestedPasskey as AttestedPasskeyV4, Passkey as PasskeyV4,
44};
45
46#[cfg(test)]
47use base64::{engine::general_purpose, Engine as _};
48
49lazy_static! {
50 pub static ref SPN_RE: Regex = {
51 #[allow(clippy::expect_used)]
52 Regex::new("(?P<name>[^@]+)@(?P<realm>[^@]+)").expect("Invalid SPN regex found")
53 };
54
55 pub static ref DISALLOWED_NAMES: HashSet<&'static str> = {
56 let mut m = HashSet::with_capacity(2);
59 m.insert("root");
60 m.insert("dn=token");
61 m
62 };
63
64 pub static ref INAME_RE: Regex = {
66 #[allow(clippy::expect_used)]
67 Regex::new("^[a-z][a-z0-9-_\\.]{0,63}$").expect("Invalid Iname regex found")
68 };
69
70 pub static ref LABEL_RE: Regex = {
72 #[allow(clippy::expect_used)]
73 Regex::new("^[a-zA-Z0-9][ a-zA-Z0-9-_\\.@]{0,63}$").expect("Invalid Iname regex found")
74 };
75
76 pub static ref HEXSTR_RE: Regex = {
78 #[allow(clippy::expect_used)]
79 Regex::new("^[a-f0-9]+$").expect("Invalid hexstring regex found")
80 };
81
82 pub static ref EXTRACT_VAL_DN: Regex = {
83 #[allow(clippy::expect_used)]
84 Regex::new("^(([^=,]+)=)?(?P<val>[^=,]+)").expect("extract val from dn regex")
85 };
87
88 pub static ref NSUNIQUEID_RE: Regex = {
89 #[allow(clippy::expect_used)]
90 Regex::new("^[0-9a-fA-F]{8}-[0-9a-fA-F]{8}-[0-9a-fA-F]{8}-[0-9a-fA-F]{8}$").expect("Invalid Nsunique regex found")
91 };
92
93 pub static ref OAUTHSCOPE_RE: Regex = {
95 #[allow(clippy::expect_used)]
96 Regex::new("^[0-9a-zA-Z_]+$").expect("Invalid oauthscope regex found")
97 };
98
99 pub static ref SINGLELINE_RE: Regex = {
100 #[allow(clippy::expect_used)]
101 Regex::new("[\n\r\t]").expect("Invalid singleline regex found")
102 };
103
104 pub static ref VALIDATE_EMAIL_RE: Regex = {
107 #[allow(clippy::expect_used)]
108 Regex::new(r"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
109 .expect("Invalid singleline regex found")
110 };
111
112 pub static ref UNICODE_CONTROL_RE: Regex = {
122 #[allow(clippy::expect_used)]
123 Regex::new(r"[[:cntrl:]]")
124 .expect("Invalid unicode control regex found")
125 };
126}
127
128#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Hash)]
129pub struct Address {
131 pub formatted: String,
132 pub street_address: String,
133 pub locality: String,
134 pub region: String,
135 pub postal_code: String,
136 pub country: String,
138}
139#[derive(Debug, Copy, Clone, PartialEq, Eq)]
140pub struct CredUpdateSessionPerms {
141 pub ext_cred_portal_can_view: bool,
142 pub primary_can_edit: bool,
143 pub passkeys_can_edit: bool,
144 pub attested_passkeys_can_edit: bool,
145 pub unixcred_can_edit: bool,
146 pub sshpubkey_can_edit: bool,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub enum IntentTokenState {
151 Valid {
152 max_ttl: Duration,
153 perms: CredUpdateSessionPerms,
154 },
155 InProgress {
156 max_ttl: Duration,
157 perms: CredUpdateSessionPerms,
158 session_id: Uuid,
159 session_ttl: Duration,
160 },
161 Consumed {
162 max_ttl: Duration,
163 },
164}
165
166#[allow(non_camel_case_types)]
167#[derive(
168 Debug,
169 Clone,
170 Copy,
171 PartialEq,
172 Eq,
173 PartialOrd,
174 Ord,
175 Deserialize,
176 Serialize,
177 Hash,
178 TryFromPrimitive,
179)]
180#[repr(u16)]
181pub enum IndexType {
182 Equality,
183 Presence,
184 SubString,
185 Ordering,
186}
187
188impl TryFrom<&str> for IndexType {
189 type Error = ();
190
191 fn try_from(value: &str) -> Result<Self, Self::Error> {
192 let n_value = value.to_uppercase();
193 match n_value.as_str() {
194 "EQUALITY" => Ok(IndexType::Equality),
195 "PRESENCE" => Ok(IndexType::Presence),
196 "SUBSTRING" => Ok(IndexType::SubString),
197 "ORDERING" => Ok(IndexType::Ordering),
198 _ => Err(()),
201 }
202 }
203}
204
205impl IndexType {
206 pub fn as_idx_str(&self) -> &str {
207 match self {
208 IndexType::Equality => "eq",
209 IndexType::Presence => "pres",
210 IndexType::SubString => "sub",
211 IndexType::Ordering => "ord",
212 }
213 }
214}
215
216impl fmt::Display for IndexType {
217 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218 write!(
219 f,
220 "{}",
221 match self {
222 IndexType::Equality => "EQUALITY",
223 IndexType::Presence => "PRESENCE",
224 IndexType::SubString => "SUBSTRING",
225 IndexType::Ordering => "ORDERING",
226 }
227 )
228 }
229}
230
231#[allow(non_camel_case_types)]
232#[derive(
233 Hash,
234 Debug,
235 Clone,
236 Copy,
237 PartialEq,
238 Eq,
239 PartialOrd,
240 Ord,
241 Deserialize,
242 Serialize,
243 TryFromPrimitive,
244 Default,
245)]
246#[repr(u16)]
247pub enum SyntaxType {
248 #[default]
249 Utf8String = 0,
250 Utf8StringInsensitive = 1,
251 Uuid = 2,
252 Boolean = 3,
253 SyntaxId = 4,
254 IndexId = 5,
255 ReferenceUuid = 6,
256 JsonFilter = 7,
257 Credential = 8,
258 SecretUtf8String = 9,
259 SshKey = 10,
260 SecurityPrincipalName = 11,
261 Uint32 = 12,
262 Cid = 13,
263 Utf8StringIname = 14,
264 NsUniqueId = 15,
265 DateTime = 16,
266 EmailAddress = 17,
267 Url = 18,
268 OauthScope = 19,
269 OauthScopeMap = 20,
270 PrivateBinary = 21,
271 IntentToken = 22,
272 Passkey = 23,
273 AttestedPasskey = 24,
274 Session = 25,
275 JwsKeyEs256 = 26,
276 JwsKeyRs256 = 27,
277 Oauth2Session = 28,
278 UiHint = 29,
279 TotpSecret = 30,
280 ApiToken = 31,
281 AuditLogString = 32,
282 EcKeyPrivate = 33,
283 Image = 34,
284 CredentialType = 35,
285 WebauthnAttestationCaList = 36,
286 OauthClaimMap = 37,
287 KeyInternal = 38,
288 HexString = 39,
289 Certificate = 40,
290 ApplicationPassword = 41,
291 Json = 42,
292 Message = 43,
293 Sha256 = 44,
294 Int64 = 45,
295 Uint64 = 46,
296}
297
298impl TryFrom<&str> for SyntaxType {
299 type Error = ();
300
301 fn try_from(value: &str) -> Result<SyntaxType, Self::Error> {
302 let n_value = value.to_uppercase();
303 match n_value.as_str() {
304 "UTF8STRING" => Ok(SyntaxType::Utf8String),
305 "UTF8STRING_INSENSITIVE" => Ok(SyntaxType::Utf8StringInsensitive),
306 "UTF8STRING_INAME" => Ok(SyntaxType::Utf8StringIname),
307 "UUID" => Ok(SyntaxType::Uuid),
308 "BOOLEAN" => Ok(SyntaxType::Boolean),
309 "SYNTAX_ID" => Ok(SyntaxType::SyntaxId),
310 "INDEX_ID" => Ok(SyntaxType::IndexId),
311 "REFERENCE_UUID" => Ok(SyntaxType::ReferenceUuid),
312 "JSON_FILTER" => Ok(SyntaxType::JsonFilter),
313 "CREDENTIAL" => Ok(SyntaxType::Credential),
314 "RADIUS_UTF8STRING" | "SECRET_UTF8STRING" => Ok(SyntaxType::SecretUtf8String),
316 "SSHKEY" => Ok(SyntaxType::SshKey),
317 "SECURITY_PRINCIPAL_NAME" => Ok(SyntaxType::SecurityPrincipalName),
318 "UINT32" => Ok(SyntaxType::Uint32),
319 "CID" => Ok(SyntaxType::Cid),
320 "NSUNIQUEID" => Ok(SyntaxType::NsUniqueId),
321 "DATETIME" => Ok(SyntaxType::DateTime),
322 "EMAIL_ADDRESS" => Ok(SyntaxType::EmailAddress),
323 "URL" => Ok(SyntaxType::Url),
324 "OAUTH_SCOPE" => Ok(SyntaxType::OauthScope),
325 "OAUTH_SCOPE_MAP" => Ok(SyntaxType::OauthScopeMap),
326 "PRIVATE_BINARY" => Ok(SyntaxType::PrivateBinary),
327 "INTENT_TOKEN" => Ok(SyntaxType::IntentToken),
328 "PASSKEY" => Ok(SyntaxType::Passkey),
329 "ATTESTED_PASSKEY" => Ok(SyntaxType::AttestedPasskey),
330 "SESSION" => Ok(SyntaxType::Session),
331 "JWS_KEY_ES256" => Ok(SyntaxType::JwsKeyEs256),
332 "JWS_KEY_RS256" => Ok(SyntaxType::JwsKeyRs256),
333 "OAUTH2SESSION" => Ok(SyntaxType::Oauth2Session),
334 "UIHINT" => Ok(SyntaxType::UiHint),
335 "TOTPSECRET" => Ok(SyntaxType::TotpSecret),
336 "APITOKEN" => Ok(SyntaxType::ApiToken),
337 "AUDIT_LOG_STRING" => Ok(SyntaxType::AuditLogString),
338 "EC_KEY_PRIVATE" => Ok(SyntaxType::EcKeyPrivate),
339 "CREDENTIAL_TYPE" => Ok(SyntaxType::CredentialType),
340 "WEBAUTHN_ATTESTATION_CA_LIST" => Ok(SyntaxType::WebauthnAttestationCaList),
341 "OAUTH_CLAIM_MAP" => Ok(SyntaxType::OauthClaimMap),
342 "KEY_INTERNAL" => Ok(SyntaxType::KeyInternal),
343 "HEX_STRING" => Ok(SyntaxType::HexString),
344 "CERTIFICATE" => Ok(SyntaxType::Certificate),
345 "APPLICATION_PASSWORD" => Ok(SyntaxType::ApplicationPassword),
346 "JSON" => Ok(SyntaxType::Json),
347 "MESSAGE" => Ok(SyntaxType::Message),
348 "SHA256" => Ok(SyntaxType::Sha256),
349 "INT64" => Ok(SyntaxType::Int64),
350 "UINT64" => Ok(SyntaxType::Uint64),
351 _ => Err(()),
352 }
353 }
354}
355
356impl fmt::Display for SyntaxType {
357 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
358 f.write_str(match self {
359 SyntaxType::Utf8String => "UTF8STRING",
360 SyntaxType::Utf8StringInsensitive => "UTF8STRING_INSENSITIVE",
361 SyntaxType::Utf8StringIname => "UTF8STRING_INAME",
362 SyntaxType::Uuid => "UUID",
363 SyntaxType::Boolean => "BOOLEAN",
364 SyntaxType::SyntaxId => "SYNTAX_ID",
365 SyntaxType::IndexId => "INDEX_ID",
366 SyntaxType::ReferenceUuid => "REFERENCE_UUID",
367 SyntaxType::JsonFilter => "JSON_FILTER",
368 SyntaxType::Credential => "CREDENTIAL",
369 SyntaxType::SecretUtf8String => "SECRET_UTF8STRING",
370 SyntaxType::SshKey => "SSHKEY",
371 SyntaxType::SecurityPrincipalName => "SECURITY_PRINCIPAL_NAME",
372 SyntaxType::Uint32 => "UINT32",
373 SyntaxType::Cid => "CID",
374 SyntaxType::NsUniqueId => "NSUNIQUEID",
375 SyntaxType::DateTime => "DATETIME",
376 SyntaxType::EmailAddress => "EMAIL_ADDRESS",
377 SyntaxType::Url => "URL",
378 SyntaxType::OauthScope => "OAUTH_SCOPE",
379 SyntaxType::OauthScopeMap => "OAUTH_SCOPE_MAP",
380 SyntaxType::PrivateBinary => "PRIVATE_BINARY",
381 SyntaxType::IntentToken => "INTENT_TOKEN",
382 SyntaxType::Passkey => "PASSKEY",
383 SyntaxType::AttestedPasskey => "ATTESTED_PASSKEY",
384 SyntaxType::Session => "SESSION",
385 SyntaxType::JwsKeyEs256 => "JWS_KEY_ES256",
386 SyntaxType::JwsKeyRs256 => "JWS_KEY_RS256",
387 SyntaxType::Oauth2Session => "OAUTH2SESSION",
388 SyntaxType::UiHint => "UIHINT",
389 SyntaxType::TotpSecret => "TOTPSECRET",
390 SyntaxType::ApiToken => "APITOKEN",
391 SyntaxType::AuditLogString => "AUDIT_LOG_STRING",
392 SyntaxType::EcKeyPrivate => "EC_KEY_PRIVATE",
393 SyntaxType::Image => "IMAGE",
394 SyntaxType::CredentialType => "CREDENTIAL_TYPE",
395 SyntaxType::WebauthnAttestationCaList => "WEBAUTHN_ATTESTATION_CA_LIST",
396 SyntaxType::OauthClaimMap => "OAUTH_CLAIM_MAP",
397 SyntaxType::KeyInternal => "KEY_INTERNAL",
398 SyntaxType::HexString => "HEX_STRING",
399 SyntaxType::Certificate => "CERTIFICATE",
400 SyntaxType::ApplicationPassword => "APPLICATION_PASSWORD",
401 SyntaxType::Json => "JSON",
402 SyntaxType::Message => "MESSAGE",
403 SyntaxType::Sha256 => "SHA256",
404 SyntaxType::Int64 => "INT64",
405 SyntaxType::Uint64 => "UINT64",
406 })
407 }
408}
409
410impl SyntaxType {
411 pub fn index_types(&self) -> &[IndexType] {
412 match self {
413 SyntaxType::Utf8String => &[IndexType::Equality, IndexType::Presence],
414 SyntaxType::Utf8StringInsensitive => &[IndexType::Equality, IndexType::Presence],
417 SyntaxType::Utf8StringIname => &[
418 IndexType::Equality,
419 IndexType::Presence,
420 IndexType::SubString,
421 ],
422 SyntaxType::Uuid => &[IndexType::Equality, IndexType::Presence],
423 SyntaxType::Boolean => &[IndexType::Equality],
424 SyntaxType::ReferenceUuid => &[IndexType::Equality, IndexType::Presence],
425 SyntaxType::Credential => &[IndexType::Equality],
426 SyntaxType::SshKey => &[IndexType::Equality, IndexType::Presence],
427 SyntaxType::SecurityPrincipalName => &[
428 IndexType::Equality,
429 IndexType::Presence,
430 IndexType::SubString,
431 ],
432 SyntaxType::Uint32 | SyntaxType::Int64 | SyntaxType::Uint64 => &[
433 IndexType::Equality,
434 IndexType::Presence,
435 IndexType::Ordering,
436 ],
437 SyntaxType::Cid => &[
438 IndexType::Equality,
439 IndexType::Presence,
440 IndexType::Ordering,
441 ],
442 SyntaxType::NsUniqueId => &[IndexType::Equality, IndexType::Presence],
443 SyntaxType::DateTime => &[
444 IndexType::Equality,
445 IndexType::Presence,
446 IndexType::Ordering,
447 ],
448 SyntaxType::EmailAddress => &[IndexType::Equality, IndexType::SubString],
449 SyntaxType::OauthScopeMap => &[IndexType::Equality],
450 SyntaxType::IntentToken => &[IndexType::Equality],
451 SyntaxType::Passkey => &[IndexType::Equality],
452 SyntaxType::AttestedPasskey => &[IndexType::Equality],
453 SyntaxType::Session => &[IndexType::Equality],
454 SyntaxType::Oauth2Session => &[IndexType::Equality],
455 SyntaxType::ApiToken => &[IndexType::Equality],
456 SyntaxType::OauthClaimMap => &[IndexType::Equality],
457 SyntaxType::ApplicationPassword => &[IndexType::Equality],
458 SyntaxType::SecretUtf8String => &[],
459 SyntaxType::Url => &[],
460 SyntaxType::OauthScope => &[],
461 SyntaxType::PrivateBinary => &[],
462 SyntaxType::JwsKeyEs256 => &[],
463 SyntaxType::JwsKeyRs256 => &[],
464 SyntaxType::UiHint => &[],
465 SyntaxType::TotpSecret => &[],
466 SyntaxType::AuditLogString => &[],
467 SyntaxType::EcKeyPrivate => &[],
468 SyntaxType::Image => &[],
469 SyntaxType::CredentialType => &[],
470 SyntaxType::WebauthnAttestationCaList => &[],
471 SyntaxType::KeyInternal => &[],
472 SyntaxType::HexString => &[],
473 SyntaxType::Certificate => &[],
474 SyntaxType::SyntaxId => &[],
475 SyntaxType::IndexId => &[],
476 SyntaxType::JsonFilter => &[],
477 SyntaxType::Json => &[],
478 SyntaxType::Message => &[],
479 SyntaxType::Sha256 => &[IndexType::Equality],
480 }
481 }
482}
483
484#[derive(
485 Hash,
486 Debug,
487 Clone,
488 Copy,
489 PartialEq,
490 Eq,
491 PartialOrd,
492 Ord,
493 Deserialize,
494 Serialize,
495 TryFromPrimitive,
496 Default,
497)]
498#[repr(u16)]
499pub enum CredentialType {
500 Any = 0,
501 External = 5,
504 #[default]
505 Mfa = 10,
506 Passkey = 20,
507 AttestedPasskey = 30,
508 AttestedResidentkey = 40,
509 Invalid = u16::MAX,
510}
511
512impl TryFrom<&str> for CredentialType {
513 type Error = ();
514
515 fn try_from(value: &str) -> Result<CredentialType, Self::Error> {
516 match value {
517 "any" => Ok(CredentialType::Any),
518 "external" => Ok(CredentialType::External),
519 "mfa" => Ok(CredentialType::Mfa),
520 "passkey" => Ok(CredentialType::Passkey),
521 "attested_passkey" => Ok(CredentialType::AttestedPasskey),
522 "attested_residentkey" => Ok(CredentialType::AttestedResidentkey),
523 "invalid" => Ok(CredentialType::Invalid),
524 _ => Err(()),
525 }
526 }
527}
528
529impl fmt::Display for CredentialType {
530 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
531 f.write_str(match self {
532 CredentialType::Any => "any",
533 CredentialType::External => "external",
534 CredentialType::Mfa => "mfa",
535 CredentialType::Passkey => "passkey",
536 CredentialType::AttestedPasskey => "attested_passkey",
537 CredentialType::AttestedResidentkey => "attested_residentkey",
538 CredentialType::Invalid => "invalid",
539 })
540 }
541}
542
543impl From<CredentialType> for Value {
544 fn from(ct: CredentialType) -> Value {
545 Value::CredentialType(ct)
546 }
547}
548
549impl From<CredentialType> for PartialValue {
550 fn from(ct: CredentialType) -> PartialValue {
551 PartialValue::CredentialType(ct)
552 }
553}
554
555impl From<Attribute> for Value {
556 fn from(attr: Attribute) -> Value {
557 let s: &str = attr.as_str();
558 Value::new_iutf8(s)
559 }
560}
561
562impl From<Attribute> for PartialValue {
563 fn from(attr: Attribute) -> PartialValue {
564 let s: &str = attr.as_str();
565 PartialValue::new_iutf8(s)
566 }
567}
568
569#[derive(Hash, Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)]
576pub enum PartialValue {
577 Utf8(String),
578 Iutf8(String),
579 Iname(String),
580 Uuid(Uuid),
581 Bool(bool),
582 Syntax(SyntaxType),
583 Index(IndexType),
584 Refer(Uuid),
585 JsonFilt(ProtoFilter),
588 Cred(String),
590 SshKey(String),
591 SecretValue,
592 Spn(String, String),
593 Uint32(u32),
594 Cid(Cid),
595 Nsuniqueid(String),
596 DateTime(OffsetDateTime),
597 EmailAddress(String),
598 PhoneNumber(String),
599 Address(String),
600 Url(Url),
602 OauthScope(String),
603 PrivateBinary,
605 PublicBinary(String),
606 RestrictedString(String),
609 IntentToken(String),
610 UiHint(UiHint),
611 Passkey(Uuid),
612 AttestedPasskey(Uuid),
613 Image(String),
615 CredentialType(CredentialType),
616
617 OauthClaim(String, Uuid),
618 OauthClaimValue(String, Uuid, String),
619
620 HexString(String),
621 Json,
622 Sha256(Sha256Output),
623 Int64(i64),
624 Uint64(u64),
625}
626
627impl From<SyntaxType> for PartialValue {
628 fn from(s: SyntaxType) -> Self {
629 PartialValue::Syntax(s)
630 }
631}
632
633impl From<IndexType> for PartialValue {
634 fn from(i: IndexType) -> Self {
635 PartialValue::Index(i)
636 }
637}
638
639impl From<bool> for PartialValue {
640 fn from(b: bool) -> Self {
641 PartialValue::Bool(b)
642 }
643}
644
645impl From<&bool> for PartialValue {
646 fn from(b: &bool) -> Self {
647 PartialValue::Bool(*b)
648 }
649}
650
651impl From<ProtoFilter> for PartialValue {
652 fn from(i: ProtoFilter) -> Self {
653 PartialValue::JsonFilt(i)
654 }
655}
656
657impl From<u32> for PartialValue {
658 fn from(i: u32) -> Self {
659 PartialValue::Uint32(i)
660 }
661}
662
663impl From<OffsetDateTime> for PartialValue {
664 fn from(i: OffsetDateTime) -> Self {
665 PartialValue::DateTime(i)
666 }
667}
668
669impl From<Url> for PartialValue {
670 fn from(i: Url) -> Self {
671 PartialValue::Url(i)
672 }
673}
674
675impl From<Sha256Output> for PartialValue {
676 fn from(i: Sha256Output) -> Self {
677 PartialValue::Sha256(i)
678 }
679}
680
681impl PartialValue {
682 pub fn new_utf8(s: String) -> Self {
683 PartialValue::Utf8(s)
684 }
685
686 pub fn new_utf8s(s: &str) -> Self {
687 PartialValue::Utf8(s.to_string())
688 }
689
690 pub fn is_utf8(&self) -> bool {
691 matches!(self, PartialValue::Utf8(_))
692 }
693
694 pub fn new_iutf8(s: &str) -> Self {
695 PartialValue::Iutf8(s.to_lowercase())
696 }
697
698 pub fn new_iname(s: &str) -> Self {
699 PartialValue::Iname(s.to_lowercase())
700 }
701
702 pub fn is_iutf8(&self) -> bool {
703 matches!(self, PartialValue::Iutf8(_))
704 }
705
706 pub fn is_iname(&self) -> bool {
707 matches!(self, PartialValue::Iname(_))
708 }
709
710 pub fn new_bool(b: bool) -> Self {
711 PartialValue::Bool(b)
712 }
713
714 pub fn new_bools(s: &str) -> Option<Self> {
715 bool::from_str(s).map(PartialValue::Bool).ok()
716 }
717
718 pub fn is_bool(&self) -> bool {
719 matches!(self, PartialValue::Bool(_))
720 }
721
722 pub fn new_uuid_s(us: &str) -> Option<Self> {
723 Uuid::parse_str(us).map(PartialValue::Uuid).ok()
724 }
725
726 pub fn is_uuid(&self) -> bool {
727 matches!(self, PartialValue::Uuid(_))
728 }
729
730 pub fn new_refer_s(us: &str) -> Option<Self> {
731 match Uuid::parse_str(us) {
732 Ok(u) => Some(PartialValue::Refer(u)),
733 Err(_) => None,
734 }
735 }
736
737 pub fn is_refer(&self) -> bool {
738 matches!(self, PartialValue::Refer(_))
739 }
740
741 pub fn new_indexes(s: &str) -> Option<Self> {
742 IndexType::try_from(s).map(PartialValue::Index).ok()
743 }
744
745 pub fn is_index(&self) -> bool {
746 matches!(self, PartialValue::Index(_))
747 }
748
749 pub fn new_syntaxs(s: &str) -> Option<Self> {
750 SyntaxType::try_from(s).map(PartialValue::Syntax).ok()
751 }
752
753 pub fn is_syntax(&self) -> bool {
754 matches!(self, PartialValue::Syntax(_))
755 }
756
757 pub fn new_json_filter_s(s: &str) -> Option<Self> {
758 serde_json::from_str(s)
759 .map(PartialValue::JsonFilt)
760 .map_err(|e| {
761 trace!(?e, ?s);
762 })
763 .ok()
764 }
765
766 pub fn is_json_filter(&self) -> bool {
767 matches!(self, PartialValue::JsonFilt(_))
768 }
769
770 pub fn new_credential_tag(s: &str) -> Self {
771 PartialValue::Cred(s.to_lowercase())
772 }
773
774 pub fn is_credential(&self) -> bool {
775 matches!(self, PartialValue::Cred(_))
776 }
777
778 pub fn new_secret_str() -> Self {
779 PartialValue::SecretValue
780 }
781
782 pub fn is_secret_string(&self) -> bool {
783 matches!(self, PartialValue::SecretValue)
784 }
785
786 pub fn new_sshkey_tag(s: String) -> Self {
787 PartialValue::SshKey(s)
788 }
789
790 pub fn new_sshkey_tag_s(s: &str) -> Self {
791 PartialValue::SshKey(s.to_string())
792 }
793
794 pub fn is_sshkey(&self) -> bool {
795 matches!(self, PartialValue::SshKey(_))
796 }
797
798 pub fn new_spn_s(s: &str) -> Option<Self> {
799 SPN_RE.captures(s).and_then(|caps| {
800 let name = match caps.name(Attribute::Name.as_ref()) {
801 Some(v) => v.as_str().to_string(),
802 None => return None,
803 };
804 let realm = match caps.name("realm") {
805 Some(v) => v.as_str().to_string(),
806 None => return None,
807 };
808 Some(PartialValue::Spn(name, realm))
809 })
810 }
811
812 pub fn new_spn_nrs(n: &str, r: &str) -> Self {
813 PartialValue::Spn(n.to_string(), r.to_string())
814 }
815
816 pub fn is_spn(&self) -> bool {
817 matches!(self, PartialValue::Spn(_, _))
818 }
819
820 pub fn new_uint32(u: u32) -> Self {
821 PartialValue::Uint32(u)
822 }
823
824 pub fn new_uint32_str(u: &str) -> Option<Self> {
825 u.parse::<u32>().ok().map(PartialValue::Uint32)
826 }
827
828 pub fn new_int64_str(u: &str) -> Option<Self> {
829 u.parse::<i64>().ok().map(PartialValue::Int64)
830 }
831
832 pub fn new_uint64_str(u: &str) -> Option<Self> {
833 u.parse::<u64>().ok().map(PartialValue::Uint64)
834 }
835
836 pub fn is_uint32(&self) -> bool {
837 matches!(self, PartialValue::Uint32(_))
838 }
839
840 pub fn new_cid(c: Cid) -> Self {
841 PartialValue::Cid(c)
842 }
843
844 pub fn new_cid_s(_c: &str) -> Option<Self> {
845 None
846 }
847
848 pub fn is_cid(&self) -> bool {
849 matches!(self, PartialValue::Cid(_))
850 }
851
852 pub fn new_nsuniqueid_s(s: &str) -> Self {
853 PartialValue::Nsuniqueid(s.to_lowercase())
854 }
855
856 pub fn is_nsuniqueid(&self) -> bool {
857 matches!(self, PartialValue::Nsuniqueid(_))
858 }
859
860 pub fn new_datetime_epoch(ts: Duration) -> Self {
861 PartialValue::DateTime(OffsetDateTime::UNIX_EPOCH + ts)
862 }
863
864 pub fn new_datetime_s(s: &str) -> Option<Self> {
865 OffsetDateTime::parse(s, &Rfc3339)
866 .ok()
867 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
868 .map(PartialValue::DateTime)
869 }
870
871 pub fn is_datetime(&self) -> bool {
872 matches!(self, PartialValue::DateTime(_))
873 }
874
875 pub fn new_email_address_s(s: &str) -> Self {
876 PartialValue::EmailAddress(s.to_string())
877 }
878
879 pub fn is_email_address(&self) -> bool {
880 matches!(self, PartialValue::EmailAddress(_))
881 }
882
883 pub fn new_phonenumber_s(s: &str) -> Self {
884 PartialValue::PhoneNumber(s.to_string())
885 }
886
887 pub fn new_address(s: &str) -> Self {
888 PartialValue::Address(s.to_string())
889 }
890
891 pub fn new_url_s(s: &str) -> Option<Self> {
892 Url::parse(s).ok().map(PartialValue::Url)
893 }
894
895 pub fn is_url(&self) -> bool {
896 matches!(self, PartialValue::Url(_))
897 }
898
899 pub fn new_oauthscope(s: &str) -> Self {
900 PartialValue::OauthScope(s.to_string())
901 }
902
903 pub fn is_oauthscope(&self) -> bool {
904 matches!(self, PartialValue::OauthScope(_))
905 }
906
907 pub fn is_privatebinary(&self) -> bool {
925 matches!(self, PartialValue::PrivateBinary)
926 }
927
928 pub fn new_publicbinary_tag_s(s: &str) -> Self {
929 PartialValue::PublicBinary(s.to_string())
930 }
931
932 pub fn new_restrictedstring_s(s: &str) -> Self {
933 PartialValue::RestrictedString(s.to_string())
934 }
935
936 pub fn new_intenttoken_s(s: String) -> Option<Self> {
937 Some(PartialValue::IntentToken(s))
938 }
939
940 pub fn new_passkey_s(us: &str) -> Option<Self> {
941 Uuid::parse_str(us).map(PartialValue::Passkey).ok()
942 }
943
944 pub fn new_attested_passkey_s(us: &str) -> Option<Self> {
945 Uuid::parse_str(us).map(PartialValue::AttestedPasskey).ok()
946 }
947
948 pub fn new_hex_string_s(hexstr: &str) -> Option<Self> {
949 let hexstr_lower = hexstr.to_lowercase();
950 if HEXSTR_RE.is_match(&hexstr_lower) {
951 Some(PartialValue::HexString(hexstr_lower))
952 } else {
953 None
954 }
955 }
956
957 pub fn new_image(input: &str) -> Self {
958 PartialValue::Image(input.to_string())
959 }
960
961 pub fn to_str(&self) -> Option<&str> {
962 match self {
963 PartialValue::Utf8(s) => Some(s.as_str()),
964 PartialValue::Iutf8(s) => Some(s.as_str()),
965 PartialValue::Iname(s) => Some(s.as_str()),
966 _ => None,
967 }
968 }
969
970 pub fn to_url(&self) -> Option<&Url> {
971 match self {
972 PartialValue::Url(u) => Some(u),
973 _ => None,
974 }
975 }
976
977 pub fn get_idx_eq_key(&self) -> String {
978 match self {
979 PartialValue::Utf8(s)
980 | PartialValue::Iutf8(s)
981 | PartialValue::Iname(s)
982 | PartialValue::Nsuniqueid(s)
983 | PartialValue::EmailAddress(s)
984 | PartialValue::RestrictedString(s) => s.clone(),
985 PartialValue::Passkey(u)
986 | PartialValue::AttestedPasskey(u)
987 | PartialValue::Refer(u)
988 | PartialValue::Uuid(u) => u.as_hyphenated().to_string(),
989 PartialValue::Bool(b) => b.to_string(),
990 PartialValue::Syntax(syn) => syn.to_string(),
991 PartialValue::Index(it) => it.to_string(),
992 PartialValue::JsonFilt(s) =>
993 {
994 #[allow(clippy::expect_used)]
995 serde_json::to_string(s).expect("A json filter value was corrupted during run-time")
996 }
997 PartialValue::Cred(tag)
998 | PartialValue::PublicBinary(tag)
999 | PartialValue::SshKey(tag) => tag.to_string(),
1000 PartialValue::SecretValue | PartialValue::PrivateBinary => "_".to_string(),
1002 PartialValue::Spn(name, realm) => format!("{name}@{realm}"),
1003 PartialValue::Uint32(u) => u.to_string(),
1004 PartialValue::Int64(u) => u.to_string(),
1005 PartialValue::Uint64(u) => u.to_string(),
1006 PartialValue::DateTime(odt) => {
1007 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
1008 #[allow(clippy::expect_used)]
1009 odt.format(&Rfc3339)
1010 .expect("Failed to format timestamp into RFC3339")
1011 }
1012 PartialValue::Url(u) => u.to_string(),
1013 PartialValue::OauthScope(u) => u.to_string(),
1014 PartialValue::Address(a) => a.to_string(),
1015 PartialValue::PhoneNumber(a) => a.to_string(),
1016 PartialValue::IntentToken(u) => u.clone(),
1017 PartialValue::UiHint(u) => (*u as u16).to_string(),
1018 PartialValue::Image(imagehash) => imagehash.to_owned(),
1019 PartialValue::CredentialType(ct) => ct.to_string(),
1020 PartialValue::Cid(_) => "_".to_string(),
1022 PartialValue::OauthClaim(_, _) => "_".to_string(),
1024 PartialValue::OauthClaimValue(_, _, _) => "_".to_string(),
1025 PartialValue::HexString(hexstr) => hexstr.to_string(),
1026 PartialValue::Json => "_".to_string(),
1027 PartialValue::Sha256(bytes) => hex::encode(bytes),
1028 }
1029 }
1030
1031 pub fn get_idx_sub_key(&self) -> Option<String> {
1032 match self {
1033 PartialValue::Utf8(s)
1034 | PartialValue::Iutf8(s)
1035 | PartialValue::Iname(s)
1036 | PartialValue::EmailAddress(s)
1038 | PartialValue::RestrictedString(s) => Some(s.to_lowercase()),
1039
1040 PartialValue::Cred(tag)
1041 | PartialValue::PublicBinary(tag)
1042 | PartialValue::SshKey(tag) => Some(tag.to_lowercase()),
1043
1044 _ => None,
1046 }
1047 }
1048}
1049
1050#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1051pub enum ApiTokenScope {
1052 ReadOnly,
1053 ReadWrite,
1054 Synchronise,
1055}
1056
1057impl fmt::Display for ApiTokenScope {
1058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1059 match self {
1060 ApiTokenScope::ReadOnly => write!(f, "read_only"),
1061 ApiTokenScope::ReadWrite => write!(f, "read_write"),
1062 ApiTokenScope::Synchronise => write!(f, "synchronise"),
1063 }
1064 }
1065}
1066
1067impl TryInto<ApiTokenPurpose> for ApiTokenScope {
1068 type Error = OperationError;
1069
1070 fn try_into(self: ApiTokenScope) -> Result<ApiTokenPurpose, OperationError> {
1071 match self {
1072 ApiTokenScope::ReadOnly => Ok(ApiTokenPurpose::ReadOnly),
1073 ApiTokenScope::ReadWrite => Ok(ApiTokenPurpose::ReadWrite),
1074 ApiTokenScope::Synchronise => Ok(ApiTokenPurpose::Synchronise),
1075 }
1076 }
1077}
1078
1079#[derive(Clone, Debug, PartialEq, Eq)]
1080pub struct ApiToken {
1081 pub label: String,
1082 pub expiry: Option<OffsetDateTime>,
1083 pub issued_at: OffsetDateTime,
1084 pub issued_by: IdentityId,
1085 pub scope: ApiTokenScope,
1086}
1087
1088#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1089pub enum SessionScope {
1090 ReadOnly,
1091 ReadWrite,
1092 PrivilegeCapable,
1093 Synchronise,
1095}
1096
1097impl fmt::Display for SessionScope {
1098 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1099 match self {
1100 SessionScope::ReadOnly => write!(f, "read_only"),
1101 SessionScope::ReadWrite => write!(f, "read_write"),
1102 SessionScope::PrivilegeCapable => write!(f, "privilege_capable"),
1103 SessionScope::Synchronise => write!(f, "synchronise"),
1104 }
1105 }
1106}
1107
1108impl TryInto<UatPurposeStatus> for SessionScope {
1109 type Error = OperationError;
1110
1111 fn try_into(self: SessionScope) -> Result<UatPurposeStatus, OperationError> {
1112 match self {
1113 SessionScope::ReadOnly => Ok(UatPurposeStatus::ReadOnly),
1114 SessionScope::ReadWrite => Ok(UatPurposeStatus::ReadWrite),
1115 SessionScope::PrivilegeCapable => Ok(UatPurposeStatus::PrivilegeCapable),
1116 SessionScope::Synchronise => Err(OperationError::InvalidEntryState),
1117 }
1118 }
1119}
1120
1121#[derive(Clone, Debug, PartialEq, Eq)]
1122pub enum SessionState {
1123 RevokedAt(Cid),
1127 ExpiresAt(OffsetDateTime),
1128 NeverExpires,
1129}
1130
1131impl PartialOrd for SessionState {
1132 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1133 Some(self.cmp(other))
1134 }
1135}
1136
1137impl Ord for SessionState {
1138 fn cmp(&self, other: &Self) -> Ordering {
1139 match (self, other) {
1142 (SessionState::RevokedAt(c_self), SessionState::RevokedAt(c_other)) => {
1144 c_other.cmp(c_self)
1147 }
1148 (SessionState::RevokedAt(_), _) => Ordering::Greater,
1149 (_, SessionState::RevokedAt(_)) => Ordering::Less,
1150 (SessionState::ExpiresAt(e_self), SessionState::ExpiresAt(e_other)) => {
1152 e_self.cmp(e_other)
1155 }
1156 (SessionState::ExpiresAt(_), _) => Ordering::Greater,
1157 (_, SessionState::ExpiresAt(_)) => Ordering::Less,
1158 (SessionState::NeverExpires, SessionState::NeverExpires) => Ordering::Equal,
1160 }
1161 }
1162}
1163
1164#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
1165pub enum AuthType {
1166 Anonymous,
1167 Password,
1168 GeneratedPassword,
1169 PasswordTotp,
1170 PasswordBackupCode,
1171 PasswordSecurityKey,
1172 Passkey,
1173 AttestedPasskey,
1174 OAuth2Trust,
1175}
1176
1177impl fmt::Display for AuthType {
1178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1179 match self {
1180 AuthType::Anonymous => write!(f, "anonymous"),
1181 AuthType::Password => write!(f, "password"),
1182 AuthType::GeneratedPassword => write!(f, "generatedpassword"),
1183 AuthType::PasswordTotp => write!(f, "passwordtotp"),
1184 AuthType::PasswordBackupCode => write!(f, "passwordbackupcode"),
1185 AuthType::PasswordSecurityKey => write!(f, "passwordsecuritykey"),
1186 AuthType::Passkey => write!(f, "passkey"),
1187 AuthType::AttestedPasskey => write!(f, "attested_passkey"),
1188 AuthType::OAuth2Trust => write!(f, "oauth2_trust"),
1189 }
1190 }
1191}
1192
1193#[derive(Clone, PartialEq, Eq, Default)]
1194pub enum SessionExtMetadata {
1195 #[default]
1196 None,
1197 OAuth2 {
1198 access_expires_at: Duration,
1199 access_token: String,
1200 refresh_token: Option<String>,
1201 },
1202}
1203
1204impl fmt::Debug for SessionExtMetadata {
1205 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1206 match self {
1207 Self::None => f.debug_struct("SessionExtMetadata::None").finish(),
1208 Self::OAuth2 {
1209 access_expires_at, ..
1210 } => f
1211 .debug_struct("SessionExtMetadata::OAuth2")
1212 .field("access_expires_at", access_expires_at)
1213 .finish(),
1214 }
1215 }
1216}
1217
1218#[derive(Clone, PartialEq, Eq)]
1219pub struct Session {
1220 pub label: String,
1221 pub state: SessionState,
1223 pub issued_at: OffsetDateTime,
1224 pub issued_by: IdentityId,
1225 pub cred_id: Uuid,
1226 pub scope: SessionScope,
1227 pub type_: AuthType,
1228 pub ext_metadata: SessionExtMetadata,
1229}
1230
1231impl fmt::Debug for Session {
1232 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1233 let issuer = match self.issued_by {
1234 IdentityId::User(u) => format!("User - {}", uuid_to_proto_string(u)),
1235 IdentityId::Synch(u) => format!("Synch - {}", uuid_to_proto_string(u)),
1236 IdentityId::Internal => "Internal".to_string(),
1237 };
1238 let expiry = match self.state {
1239 SessionState::ExpiresAt(e) => e.to_string(),
1240 SessionState::NeverExpires => "never".to_string(),
1241 SessionState::RevokedAt(_) => "revoked".to_string(),
1242 };
1243 write!(
1244 f,
1245 "state: {}, issued at: {}, issued by: {}, credential id: {}, scope: {:?}",
1246 expiry, self.issued_at, issuer, self.cred_id, self.scope
1247 )
1248 }
1249}
1250
1251#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
1252pub enum OauthClaimMapJoin {
1253 CommaSeparatedValue,
1254 SpaceSeparatedValue,
1255 #[default]
1256 JsonArray,
1257}
1258
1259impl From<OauthClaimMapJoin> for ScimOauth2ClaimMapJoinChar {
1260 fn from(value: OauthClaimMapJoin) -> Self {
1261 match value {
1262 OauthClaimMapJoin::CommaSeparatedValue => {
1263 ScimOauth2ClaimMapJoinChar::CommaSeparatedValue
1264 }
1265 OauthClaimMapJoin::SpaceSeparatedValue => {
1266 ScimOauth2ClaimMapJoinChar::SpaceSeparatedValue
1267 }
1268 OauthClaimMapJoin::JsonArray => ScimOauth2ClaimMapJoinChar::JsonArray,
1269 }
1270 }
1271}
1272
1273impl From<ScimOauth2ClaimMapJoinChar> for OauthClaimMapJoin {
1274 fn from(value: ScimOauth2ClaimMapJoinChar) -> Self {
1275 match value {
1276 ScimOauth2ClaimMapJoinChar::CommaSeparatedValue => {
1277 OauthClaimMapJoin::CommaSeparatedValue
1278 }
1279 ScimOauth2ClaimMapJoinChar::SpaceSeparatedValue => {
1280 OauthClaimMapJoin::SpaceSeparatedValue
1281 }
1282 ScimOauth2ClaimMapJoinChar::JsonArray => OauthClaimMapJoin::JsonArray,
1283 }
1284 }
1285}
1286
1287impl OauthClaimMapJoin {
1288 pub(crate) fn to_str(self) -> &'static str {
1289 match self {
1290 OauthClaimMapJoin::CommaSeparatedValue => ",",
1291 OauthClaimMapJoin::SpaceSeparatedValue => " ",
1292 OauthClaimMapJoin::JsonArray => ";",
1294 }
1295 }
1296}
1297
1298impl From<DbValueOauthClaimMapJoinV1> for OauthClaimMapJoin {
1299 fn from(value: DbValueOauthClaimMapJoinV1) -> OauthClaimMapJoin {
1300 match value {
1301 DbValueOauthClaimMapJoinV1::CommaSeparatedValue => {
1302 OauthClaimMapJoin::CommaSeparatedValue
1303 }
1304 DbValueOauthClaimMapJoinV1::SpaceSeparatedValue => {
1305 OauthClaimMapJoin::SpaceSeparatedValue
1306 }
1307 DbValueOauthClaimMapJoinV1::JsonArray => OauthClaimMapJoin::JsonArray,
1308 }
1309 }
1310}
1311
1312impl From<OauthClaimMapJoin> for DbValueOauthClaimMapJoinV1 {
1313 fn from(value: OauthClaimMapJoin) -> DbValueOauthClaimMapJoinV1 {
1314 match value {
1315 OauthClaimMapJoin::CommaSeparatedValue => {
1316 DbValueOauthClaimMapJoinV1::CommaSeparatedValue
1317 }
1318 OauthClaimMapJoin::SpaceSeparatedValue => {
1319 DbValueOauthClaimMapJoinV1::SpaceSeparatedValue
1320 }
1321 OauthClaimMapJoin::JsonArray => DbValueOauthClaimMapJoinV1::JsonArray,
1322 }
1323 }
1324}
1325
1326#[derive(Clone, Debug, PartialEq, Eq)]
1327pub struct Oauth2Session {
1328 pub parent: Option<Uuid>,
1329 pub state: SessionState,
1330 pub issued_at: OffsetDateTime,
1331 pub rs_uuid: Uuid,
1332}
1333
1334#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1335pub enum KeyUsage {
1336 JwsEs256,
1337 JwsHs256,
1338 JwsRs256,
1339 JweA128GCM,
1340 HkdfS256,
1341}
1342
1343impl fmt::Display for KeyUsage {
1344 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1345 write!(
1346 f,
1347 "{}",
1348 match self {
1349 KeyUsage::JwsEs256 => "jws_es256",
1350 KeyUsage::JwsHs256 => "jws_hs256",
1351 KeyUsage::JwsRs256 => "jws_rs256",
1352 KeyUsage::JweA128GCM => "jwe_a128gcm",
1353 KeyUsage::HkdfS256 => "hkdf_s256",
1354 }
1355 )
1356 }
1357}
1358
1359#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1360pub enum KeyStatus {
1361 Valid,
1362 Retained,
1363 Revoked,
1364}
1365
1366impl fmt::Display for KeyStatus {
1367 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1368 write!(
1369 f,
1370 "{}",
1371 match self {
1372 KeyStatus::Valid => "valid",
1373 KeyStatus::Retained => "retained",
1374 KeyStatus::Revoked => "revoked",
1375 }
1376 )
1377 }
1378}
1379
1380#[derive(Clone, Debug)]
1387pub enum Value {
1388 Utf8(String),
1389 Iutf8(String),
1391 Iname(String),
1393 Uuid(Uuid),
1394 Bool(bool),
1395 Syntax(SyntaxType),
1396 Index(IndexType),
1397 Refer(Uuid),
1398 JsonFilt(ProtoFilter),
1399 Cred(String, Credential),
1400 SshKey(String, SshPublicKey),
1401 SecretValue(String),
1402 Spn(String, String),
1403 Uint32(u32),
1404 Int64(i64),
1405 Uint64(u64),
1406 Cid(Cid),
1407 Nsuniqueid(String),
1408 DateTime(OffsetDateTime),
1409 EmailAddress(String, bool),
1410 PhoneNumber(String, bool),
1411 Address(Address),
1412 Url(Url),
1413 OauthScope(String),
1414 OauthScopeMap(Uuid, BTreeSet<String>),
1415 PrivateBinary(Vec<u8>),
1416 PublicBinary(String, Vec<u8>),
1417 RestrictedString(String),
1418 IntentToken(String, IntentTokenState),
1419 Passkey(Uuid, String, PasskeyV4),
1420 AttestedPasskey(Uuid, String, AttestedPasskeyV4),
1421
1422 Session(Uuid, Session),
1423 ApiToken(Uuid, ApiToken),
1424 Oauth2Session(Uuid, Oauth2Session),
1425
1426 JwsKeyEs256(JwsEs256Signer),
1427 JwsKeyRs256(JwsRs256Signer),
1428 UiHint(UiHint),
1429
1430 TotpSecret(String, Totp),
1431 AuditLogString(Cid, String),
1432 EcKeyPrivate(EcKey<Private>),
1433
1434 Image(ImageValue),
1435 CredentialType(CredentialType),
1436 WebauthnAttestationCaList(AttestationCaList),
1437
1438 OauthClaimValue(String, Uuid, BTreeSet<String>),
1439 OauthClaimMap(String, OauthClaimMapJoin),
1440
1441 KeyInternal {
1442 id: KeyId,
1443 usage: KeyUsage,
1444 valid_from: u64,
1445 status: KeyStatus,
1446 status_cid: Cid,
1447 der: Zeroizing<Vec<u8>>,
1448 },
1449
1450 HexString(String),
1451
1452 Certificate(Box<Certificate>),
1453 ApplicationPassword(ApplicationPassword),
1454 Json(JsonValue),
1455 Sha256(Sha256Output),
1456}
1457
1458impl PartialEq for Value {
1459 fn eq(&self, other: &Self) -> bool {
1460 match (self, other) {
1461 (Value::Utf8(a), Value::Utf8(b))
1462 | (Value::Iutf8(a), Value::Iutf8(b))
1463 | (Value::Iname(a), Value::Iname(b))
1464 | (Value::Cred(a, _), Value::Cred(b, _))
1465 | (Value::SshKey(a, _), Value::SshKey(b, _))
1466 | (Value::Nsuniqueid(a), Value::Nsuniqueid(b))
1467 | (Value::EmailAddress(a, _), Value::EmailAddress(b, _))
1468 | (Value::PhoneNumber(a, _), Value::PhoneNumber(b, _))
1469 | (Value::OauthScope(a), Value::OauthScope(b))
1470 | (Value::PublicBinary(a, _), Value::PublicBinary(b, _))
1471 | (Value::RestrictedString(a), Value::RestrictedString(b)) => a.eq(b),
1472 (Value::Spn(a, c), Value::Spn(b, d)) => a.eq(b) && c.eq(d),
1474 (Value::Uuid(a), Value::Uuid(b)) | (Value::Refer(a), Value::Refer(b)) => a.eq(b),
1476 (Value::Bool(a), Value::Bool(b)) => a.eq(b),
1478 (Value::Syntax(a), Value::Syntax(b)) => a.eq(b),
1480 (Value::Index(a), Value::Index(b)) => a.eq(b),
1482 (Value::JsonFilt(a), Value::JsonFilt(b)) => a.eq(b),
1484 (Value::Uint32(a), Value::Uint32(b)) => a.eq(b),
1486 (Value::Int64(a), Value::Int64(b)) => a.eq(b),
1488 (Value::Uint64(a), Value::Uint64(b)) => a.eq(b),
1490 (Value::Cid(a), Value::Cid(b)) => a.eq(b),
1492 (Value::DateTime(a), Value::DateTime(b)) => a.eq(b),
1494 (Value::Url(a), Value::Url(b)) => a.eq(b),
1496 (Value::OauthScopeMap(a, c), Value::OauthScopeMap(b, d)) => a.eq(b) && c.eq(d),
1498
1499 (Value::Image(image1), Value::Image(image2)) => {
1500 image1.hash_imagevalue().eq(&image2.hash_imagevalue())
1501 }
1502 (Value::Address(_), Value::Address(_))
1503 | (Value::PrivateBinary(_), Value::PrivateBinary(_))
1504 | (Value::SecretValue(_), Value::SecretValue(_)) => false,
1505 (Value::Iutf8(_), Value::Iname(_)) | (Value::Iname(_), Value::Iutf8(_)) => false,
1507 (Value::Uuid(_), Value::Iname(_))
1509 | (Value::Iname(_), Value::Spn(_, _))
1510 | (Value::Uuid(_), Value::Spn(_, _)) => false,
1511 (l, r) => {
1512 error!(?l, ?r, "mismatched value types");
1513 debug_assert!(false);
1514 false
1515 }
1516 }
1517 }
1518}
1519
1520impl Eq for Value {}
1521
1522impl From<bool> for Value {
1523 fn from(b: bool) -> Self {
1524 Value::Bool(b)
1525 }
1526}
1527
1528impl From<&bool> for Value {
1529 fn from(b: &bool) -> Self {
1530 Value::Bool(*b)
1531 }
1532}
1533
1534impl From<SyntaxType> for Value {
1535 fn from(s: SyntaxType) -> Self {
1536 Value::Syntax(s)
1537 }
1538}
1539
1540impl From<IndexType> for Value {
1541 fn from(i: IndexType) -> Self {
1542 Value::Index(i)
1543 }
1544}
1545
1546impl From<ProtoFilter> for Value {
1547 fn from(i: ProtoFilter) -> Self {
1548 Value::JsonFilt(i)
1549 }
1550}
1551
1552impl From<OffsetDateTime> for Value {
1553 fn from(i: OffsetDateTime) -> Self {
1554 Value::DateTime(i)
1555 }
1556}
1557
1558impl From<u32> for Value {
1559 fn from(i: u32) -> Self {
1560 Value::Uint32(i)
1561 }
1562}
1563
1564impl From<Url> for Value {
1565 fn from(i: Url) -> Self {
1566 Value::Url(i)
1567 }
1568}
1569
1570#[cfg(test)]
1573impl From<&str> for Value {
1574 fn from(s: &str) -> Self {
1575 match Uuid::parse_str(s) {
1577 Ok(u) => Value::Uuid(u),
1578 Err(_) => Value::Utf8(s.to_string()),
1579 }
1580 }
1581}
1582
1583#[cfg(test)]
1584impl From<&Uuid> for Value {
1585 fn from(u: &Uuid) -> Self {
1586 Value::Uuid(*u)
1587 }
1588}
1589
1590#[cfg(test)]
1591impl From<Uuid> for Value {
1592 fn from(u: Uuid) -> Self {
1593 Value::Uuid(u)
1594 }
1595}
1596
1597impl From<DbIdentSpn> for Value {
1598 fn from(dis: DbIdentSpn) -> Self {
1599 match dis {
1600 DbIdentSpn::Spn(n, r) => Value::Spn(n, r),
1601 DbIdentSpn::Iname(n) => Value::Iname(n),
1602 DbIdentSpn::Uuid(u) => Value::Uuid(u),
1603 }
1604 }
1605}
1606
1607impl Value {
1608 pub fn new_utf8(s: String) -> Self {
1610 Value::Utf8(s)
1611 }
1612
1613 pub fn new_utf8s(s: &str) -> Self {
1614 Value::Utf8(s.to_string())
1615 }
1616
1617 pub fn is_utf8(&self) -> bool {
1618 matches!(self, Value::Utf8(_))
1619 }
1620
1621 pub fn new_iutf8(s: &str) -> Self {
1622 Value::Iutf8(s.to_lowercase())
1623 }
1624
1625 pub fn is_iutf8(&self) -> bool {
1626 matches!(self, Value::Iutf8(_))
1627 }
1628
1629 pub fn new_attr(s: &str) -> Self {
1630 Value::Iutf8(s.to_lowercase())
1631 }
1632
1633 pub fn is_insensitive_utf8(&self) -> bool {
1634 matches!(self, Value::Iutf8(_))
1635 }
1636
1637 pub fn new_iname(s: &str) -> Self {
1638 Value::Iname(s.to_lowercase())
1639 }
1640
1641 pub fn is_iname(&self) -> bool {
1642 matches!(self, Value::Iname(_))
1643 }
1644
1645 pub fn new_uuid_s(s: &str) -> Option<Self> {
1646 Uuid::parse_str(s).map(Value::Uuid).ok()
1647 }
1648
1649 pub fn is_uuid(&self) -> bool {
1651 matches!(self, Value::Uuid(_))
1652 }
1653
1654 pub fn new_bool(b: bool) -> Self {
1655 Value::Bool(b)
1656 }
1657
1658 pub fn new_bools(s: &str) -> Option<Self> {
1659 bool::from_str(s).map(Value::Bool).ok()
1660 }
1661
1662 pub fn new_audit_log_string(e: (Cid, String)) -> Option<Self> {
1663 Some(Value::AuditLogString(e.0, e.1))
1664 }
1665
1666 #[inline]
1667 pub fn is_bool(&self) -> bool {
1668 matches!(self, Value::Bool(_))
1669 }
1670
1671 pub fn new_syntaxs(s: &str) -> Option<Self> {
1672 SyntaxType::try_from(s).map(Value::Syntax).ok()
1673 }
1674
1675 pub fn new_syntax(s: SyntaxType) -> Self {
1676 Value::Syntax(s)
1677 }
1678
1679 pub fn is_syntax(&self) -> bool {
1680 matches!(self, Value::Syntax(_))
1681 }
1682
1683 pub fn new_indexes(s: &str) -> Option<Self> {
1684 IndexType::try_from(s).map(Value::Index).ok()
1685 }
1686
1687 pub fn new_index(i: IndexType) -> Self {
1688 Value::Index(i)
1689 }
1690
1691 pub fn is_index(&self) -> bool {
1692 matches!(self, Value::Index(_))
1693 }
1694
1695 pub fn new_refer_s(us: &str) -> Option<Self> {
1696 Uuid::parse_str(us).map(Value::Refer).ok()
1697 }
1698
1699 pub fn is_refer(&self) -> bool {
1700 matches!(self, Value::Refer(_))
1701 }
1702
1703 pub fn new_json_filter_s(s: &str) -> Option<Self> {
1704 serde_json::from_str(s).map(Value::JsonFilt).ok()
1705 }
1706
1707 pub fn new_json_filter(f: ProtoFilter) -> Self {
1708 Value::JsonFilt(f)
1709 }
1710
1711 pub fn is_json_filter(&self) -> bool {
1712 matches!(self, Value::JsonFilt(_))
1713 }
1714
1715 pub fn as_json_filter(&self) -> Option<&ProtoFilter> {
1716 match &self {
1717 Value::JsonFilt(f) => Some(f),
1718 _ => None,
1719 }
1720 }
1721
1722 pub fn new_credential(tag: &str, cred: Credential) -> Self {
1723 Value::Cred(tag.to_string(), cred)
1724 }
1725
1726 pub fn is_credential(&self) -> bool {
1727 matches!(&self, Value::Cred(_, _))
1728 }
1729
1730 pub fn to_credential(&self) -> Option<&Credential> {
1731 match &self {
1732 Value::Cred(_, cred) => Some(cred),
1733 _ => None,
1734 }
1735 }
1736
1737 pub fn new_hex_string_s(hexstr: &str) -> Option<Self> {
1738 let hexstr_lower = hexstr.to_lowercase();
1739 if HEXSTR_RE.is_match(&hexstr_lower) {
1740 Some(Value::HexString(hexstr_lower))
1741 } else {
1742 None
1743 }
1744 }
1745
1746 pub fn new_certificate_s(cert_str: &str) -> Option<Self> {
1747 Certificate::from_pem(cert_str)
1748 .map(Box::new)
1749 .map(Value::Certificate)
1750 .ok()
1751 }
1752
1753 pub fn new_image(input: &str) -> Result<Self, OperationError> {
1755 serde_json::from_str::<ImageValue>(input)
1756 .map(Value::Image)
1757 .map_err(|_e| OperationError::InvalidValueState)
1758 }
1759
1760 pub fn new_secret_str(cleartext: &str) -> Self {
1761 Value::SecretValue(cleartext.to_string())
1762 }
1763
1764 pub fn is_secret_string(&self) -> bool {
1765 matches!(&self, Value::SecretValue(_))
1766 }
1767
1768 pub fn get_secret_str(&self) -> Option<&str> {
1769 match &self {
1770 Value::SecretValue(c) => Some(c.as_str()),
1771 _ => None,
1772 }
1773 }
1774
1775 pub fn new_sshkey_str(tag: &str, key: &str) -> Result<Self, OperationError> {
1776 SshPublicKey::from_string(key)
1777 .map(|pk| Value::SshKey(tag.to_string(), pk))
1778 .map_err(|err| {
1779 error!(?err, "value sshkey failed to parse string");
1780 OperationError::VL0001ValueSshPublicKeyString
1781 })
1782 }
1783
1784 pub fn is_sshkey(&self) -> bool {
1785 matches!(&self, Value::SshKey(_, _))
1786 }
1787
1788 pub fn get_sshkey(&self) -> Option<String> {
1789 match &self {
1790 Value::SshKey(_, key) => Some(key.to_string()),
1791 _ => None,
1792 }
1793 }
1794
1795 pub fn new_spn_parse(s: &str) -> Option<Self> {
1796 SPN_RE.captures(s).and_then(|caps| {
1797 let name = match caps.name(Attribute::Name.as_ref()) {
1798 Some(v) => v.as_str().to_string(),
1799 None => return None,
1800 };
1801 let realm = match caps.name("realm") {
1802 Some(v) => v.as_str().to_string(),
1803 None => return None,
1804 };
1805 Some(Value::Spn(name, realm))
1806 })
1807 }
1808
1809 pub fn new_spn_str(n: &str, r: &str) -> Self {
1810 Value::Spn(n.to_string(), r.to_string())
1811 }
1812
1813 pub fn is_spn(&self) -> bool {
1814 matches!(&self, Value::Spn(_, _))
1815 }
1816
1817 pub fn new_uint32(u: u32) -> Self {
1818 Value::Uint32(u)
1819 }
1820
1821 pub fn new_uint32_str(u: &str) -> Option<Self> {
1822 u.parse::<u32>().ok().map(Value::Uint32)
1823 }
1824
1825 pub fn is_uint32(&self) -> bool {
1826 matches!(&self, Value::Uint32(_))
1827 }
1828
1829 pub fn new_int64_str(u: &str) -> Option<Self> {
1830 u.parse::<i64>().ok().map(Value::Int64)
1831 }
1832
1833 pub fn new_uint64_str(u: &str) -> Option<Self> {
1834 u.parse::<u64>().ok().map(Value::Uint64)
1835 }
1836
1837 pub fn new_cid(c: Cid) -> Self {
1838 Value::Cid(c)
1839 }
1840
1841 pub fn is_cid(&self) -> bool {
1842 matches!(&self, Value::Cid(_))
1843 }
1844
1845 pub fn new_nsuniqueid_s(s: &str) -> Option<Self> {
1846 if NSUNIQUEID_RE.is_match(s) {
1847 Some(Value::Nsuniqueid(s.to_lowercase()))
1848 } else {
1849 None
1850 }
1851 }
1852
1853 pub fn is_nsuniqueid(&self) -> bool {
1854 matches!(&self, Value::Nsuniqueid(_))
1855 }
1856
1857 pub fn new_datetime_epoch(ts: Duration) -> Self {
1858 Value::DateTime(OffsetDateTime::UNIX_EPOCH + ts)
1859 }
1860
1861 pub fn new_datetime_s(s: &str) -> Option<Self> {
1862 OffsetDateTime::parse(s, &Rfc3339)
1863 .ok()
1864 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
1865 .map(Value::DateTime)
1866 }
1867
1868 pub fn new_datetime(dt: OffsetDateTime) -> Self {
1869 Value::DateTime(dt)
1870 }
1871
1872 pub fn to_datetime(&self) -> Option<OffsetDateTime> {
1873 match &self {
1874 Value::DateTime(odt) => {
1875 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
1876 Some(*odt)
1877 }
1878 _ => None,
1879 }
1880 }
1881
1882 pub fn is_datetime(&self) -> bool {
1883 matches!(&self, Value::DateTime(_))
1884 }
1885
1886 pub fn new_email_address_s(s: &str) -> Option<Self> {
1887 if VALIDATE_EMAIL_RE.is_match(s) {
1888 Some(Value::EmailAddress(s.to_string(), false))
1889 } else {
1890 None
1891 }
1892 }
1893
1894 pub fn new_email_address_primary_s(s: &str) -> Option<Self> {
1895 if VALIDATE_EMAIL_RE.is_match(s) {
1896 Some(Value::EmailAddress(s.to_string(), true))
1897 } else {
1898 None
1899 }
1900 }
1901
1902 pub fn is_email_address(&self) -> bool {
1903 matches!(&self, Value::EmailAddress(_, _))
1904 }
1905
1906 pub fn new_phonenumber_s(s: &str) -> Self {
1907 Value::PhoneNumber(s.to_string(), false)
1908 }
1909
1910 pub fn new_address(a: Address) -> Self {
1911 Value::Address(a)
1912 }
1913
1914 pub fn new_url_s(s: &str) -> Option<Self> {
1915 Url::parse(s).ok().map(Value::Url)
1916 }
1917
1918 pub fn new_url(u: Url) -> Self {
1919 Value::Url(u)
1920 }
1921
1922 pub fn is_url(&self) -> bool {
1923 matches!(&self, Value::Url(_))
1924 }
1925
1926 pub fn new_oauthscope(s: &str) -> Option<Self> {
1927 if OAUTHSCOPE_RE.is_match(s) {
1928 Some(Value::OauthScope(s.to_string()))
1929 } else {
1930 None
1931 }
1932 }
1933
1934 pub fn is_oauthscope(&self) -> bool {
1935 matches!(&self, Value::OauthScope(_))
1936 }
1937
1938 pub fn new_oauthscopemap(u: Uuid, m: BTreeSet<String>) -> Option<Self> {
1939 if m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1940 Some(Value::OauthScopeMap(u, m))
1941 } else {
1942 None
1943 }
1944 }
1945
1946 pub fn new_oauthclaimmap(n: String, u: Uuid, c: BTreeSet<String>) -> Option<Self> {
1947 if OAUTHSCOPE_RE.is_match(&n) && c.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1948 Some(Value::OauthClaimValue(n, u, c))
1949 } else {
1950 None
1951 }
1952 }
1953
1954 pub fn is_oauthscopemap(&self) -> bool {
1955 matches!(&self, Value::OauthScopeMap(_, _))
1956 }
1957
1958 #[cfg(test)]
1959 pub fn new_privatebinary_base64(der: &str) -> Self {
1960 let der = general_purpose::STANDARD
1961 .decode(der)
1962 .expect("Failed to decode base64 der value");
1963 Value::PrivateBinary(der)
1964 }
1965
1966 pub fn new_privatebinary(der: &[u8]) -> Self {
1967 Value::PrivateBinary(der.to_owned())
1968 }
1969
1970 pub fn to_privatebinary(&self) -> Option<&Vec<u8>> {
1971 match &self {
1972 Value::PrivateBinary(c) => Some(c),
1973 _ => None,
1974 }
1975 }
1976
1977 pub fn is_privatebinary(&self) -> bool {
1978 matches!(&self, Value::PrivateBinary(_))
1979 }
1980
1981 pub fn new_publicbinary(tag: String, der: Vec<u8>) -> Self {
1982 Value::PublicBinary(tag, der)
1983 }
1984
1985 pub fn new_restrictedstring(s: String) -> Self {
1986 Value::RestrictedString(s)
1987 }
1988
1989 pub fn new_webauthn_attestation_ca_list(s: &str) -> Option<Self> {
1990 serde_json::from_str(s)
1991 .map(Value::WebauthnAttestationCaList)
1992 .map_err(|err| {
1993 debug!(?err, ?s);
1994 })
1995 .ok()
1996 }
1997
1998 #[allow(clippy::unreachable)]
1999 pub(crate) fn to_db_ident_spn(&self) -> DbIdentSpn {
2000 match &self {
2002 Value::Spn(n, r) => DbIdentSpn::Spn(n.clone(), r.clone()),
2003 Value::Iname(s) => DbIdentSpn::Iname(s.clone()),
2004 Value::Uuid(u) => DbIdentSpn::Uuid(*u),
2005 v => unreachable!("-> {:?}", v),
2009 }
2010 }
2011
2012 pub fn to_str(&self) -> Option<&str> {
2013 match &self {
2014 Value::Utf8(s) => Some(s.as_str()),
2015 Value::Iutf8(s) => Some(s.as_str()),
2016 Value::Iname(s) => Some(s.as_str()),
2017 _ => None,
2018 }
2019 }
2020
2021 pub fn to_url(&self) -> Option<&Url> {
2022 match &self {
2023 Value::Url(u) => Some(u),
2024 _ => None,
2025 }
2026 }
2027
2028 pub fn as_string(&self) -> Option<&String> {
2029 match &self {
2030 Value::Utf8(s) => Some(s),
2031 Value::Iutf8(s) => Some(s),
2032 Value::Iname(s) => Some(s),
2033 _ => None,
2034 }
2035 }
2036
2037 pub fn to_ref_uuid(&self) -> Option<Uuid> {
2040 match &self {
2041 Value::Refer(u) => Some(*u),
2042 Value::OauthScopeMap(u, _) => Some(*u),
2043 Value::Oauth2Session(_, m) => Some(m.rs_uuid),
2045 _ => None,
2046 }
2047 }
2048
2049 pub fn to_uuid(&self) -> Option<&Uuid> {
2050 match &self {
2051 Value::Uuid(u) => Some(u),
2052 _ => None,
2053 }
2054 }
2055
2056 pub fn to_indextype(&self) -> Option<&IndexType> {
2057 match &self {
2058 Value::Index(i) => Some(i),
2059 _ => None,
2060 }
2061 }
2062
2063 pub fn to_syntaxtype(&self) -> Option<&SyntaxType> {
2064 match &self {
2065 Value::Syntax(s) => Some(s),
2066 _ => None,
2067 }
2068 }
2069
2070 pub fn to_bool(&self) -> Option<bool> {
2071 match self {
2072 Value::Bool(v) => Some(*v),
2074 _ => None,
2075 }
2076 }
2077
2078 pub fn to_uint32(&self) -> Option<u32> {
2079 match &self {
2080 Value::Uint32(v) => Some(*v),
2081 _ => None,
2082 }
2083 }
2084
2085 pub fn to_utf8(self) -> Option<String> {
2086 match self {
2087 Value::Utf8(s) => Some(s),
2088 _ => None,
2089 }
2090 }
2091
2092 pub fn to_iutf8(self) -> Option<String> {
2093 match self {
2094 Value::Iutf8(s) => Some(s),
2095 _ => None,
2096 }
2097 }
2098
2099 pub fn to_iname(self) -> Option<String> {
2100 match self {
2101 Value::Iname(s) => Some(s),
2102 _ => None,
2103 }
2104 }
2105
2106 pub fn to_jsonfilt(self) -> Option<ProtoFilter> {
2107 match self {
2108 Value::JsonFilt(f) => Some(f),
2109 _ => None,
2110 }
2111 }
2112
2113 pub fn to_cred(self) -> Option<(String, Credential)> {
2114 match self {
2115 Value::Cred(tag, c) => Some((tag, c)),
2116 _ => None,
2117 }
2118 }
2119
2120 pub fn to_spn(self) -> Option<(String, String)> {
2130 match self {
2131 Value::Spn(n, d) => Some((n, d)),
2132 _ => None,
2133 }
2134 }
2135
2136 pub fn to_cid(self) -> Option<Cid> {
2137 match self {
2138 Value::Cid(s) => Some(s),
2139 _ => None,
2140 }
2141 }
2142
2143 pub fn to_nsuniqueid(self) -> Option<String> {
2144 match self {
2145 Value::Nsuniqueid(s) => Some(s),
2146 _ => None,
2147 }
2148 }
2149
2150 pub fn to_emailaddress(self) -> Option<String> {
2151 match self {
2152 Value::EmailAddress(s, _) => Some(s),
2153 _ => None,
2154 }
2155 }
2156
2157 pub fn to_oauthscope(self) -> Option<String> {
2158 match self {
2159 Value::OauthScope(s) => Some(s),
2160 _ => None,
2161 }
2162 }
2163
2164 pub fn to_oauthscopemap(self) -> Option<(Uuid, BTreeSet<String>)> {
2165 match self {
2166 Value::OauthScopeMap(u, m) => Some((u, m)),
2167 _ => None,
2168 }
2169 }
2170
2171 pub fn to_restrictedstring(self) -> Option<String> {
2172 match self {
2173 Value::RestrictedString(s) => Some(s),
2174 _ => None,
2175 }
2176 }
2177
2178 pub fn to_phonenumber(self) -> Option<String> {
2179 match self {
2180 Value::PhoneNumber(p, _b) => Some(p),
2181 _ => None,
2182 }
2183 }
2184
2185 pub fn to_publicbinary(self) -> Option<(String, Vec<u8>)> {
2186 match self {
2187 Value::PublicBinary(t, d) => Some((t, d)),
2188 _ => None,
2189 }
2190 }
2191
2192 pub fn to_address(self) -> Option<Address> {
2193 match self {
2194 Value::Address(a) => Some(a),
2195 _ => None,
2196 }
2197 }
2198
2199 pub fn to_intenttoken(self) -> Option<(String, IntentTokenState)> {
2200 match self {
2201 Value::IntentToken(u, s) => Some((u, s)),
2202 _ => None,
2203 }
2204 }
2205
2206 pub fn to_session(self) -> Option<(Uuid, Session)> {
2207 match self {
2208 Value::Session(u, s) => Some((u, s)),
2209 _ => None,
2210 }
2211 }
2212
2213 pub fn migrate_iutf8_iname(self) -> Option<Self> {
2214 match self {
2215 Value::Iutf8(v) => Some(Value::Iname(v)),
2216 _ => None,
2217 }
2218 }
2219
2220 #[allow(clippy::unreachable)]
2222 pub(crate) fn to_proto_string_clone(&self) -> String {
2223 match &self {
2224 Value::Iname(s) => s.clone(),
2225 Value::Uuid(u) => u.as_hyphenated().to_string(),
2226 Value::SshKey(tag, key) => format!("{tag}: {key}"),
2228 Value::Spn(n, r) => format!("{n}@{r}"),
2229 _ => unreachable!(
2230 "You've specified the wrong type for the attribute, got: {:?}",
2231 self
2232 ),
2233 }
2234 }
2235
2236 pub(crate) fn validate(&self) -> bool {
2237 match &self {
2241 Value::Utf8(s)
2243 | Value::Iutf8(s)
2244 | Value::Cred(s, _)
2245 | Value::PublicBinary(s, _)
2246 | Value::IntentToken(s, _)
2247 | Value::Passkey(_, s, _)
2248 | Value::AttestedPasskey(_, s, _)
2249 | Value::TotpSecret(s, _) => {
2250 Value::validate_str_escapes(s) && Value::validate_singleline(s)
2251 }
2252
2253 Value::Spn(a, b) => {
2254 Value::validate_str_escapes(a)
2255 && Value::validate_str_escapes(b)
2256 && Value::validate_singleline(a)
2257 && Value::validate_singleline(b)
2258 }
2259 Value::Image(image) => image.validate_image().is_ok(),
2260 Value::Iname(s) => {
2261 Value::validate_str_escapes(s)
2262 && Value::validate_iname(s)
2263 && Value::validate_singleline(s)
2264 }
2265
2266 Value::SshKey(s, _key) => {
2267 Value::validate_str_escapes(s)
2268 && Value::validate_singleline(s)
2270 }
2271
2272 Value::ApiToken(_, at) => {
2273 Value::validate_str_escapes(&at.label) && Value::validate_singleline(&at.label)
2274 }
2275 Value::AuditLogString(_, s) => {
2276 Value::validate_str_escapes(s) && Value::validate_singleline(s)
2277 }
2278 Value::ApplicationPassword(ap) => {
2279 Value::validate_str_escapes(&ap.label) && Value::validate_singleline(&ap.label)
2280 }
2281
2282 Value::Nsuniqueid(s) => NSUNIQUEID_RE.is_match(s),
2284 Value::DateTime(odt) => odt.offset() == time::UtcOffset::UTC,
2285 Value::EmailAddress(mail, _) => VALIDATE_EMAIL_RE.is_match(mail.as_str()),
2286 Value::OauthScope(s) => OAUTHSCOPE_RE.is_match(s),
2287 Value::OauthScopeMap(_, m) => m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)),
2288
2289 Value::OauthClaimMap(name, _) => OAUTHSCOPE_RE.is_match(name),
2290 Value::OauthClaimValue(name, _, value) => {
2291 OAUTHSCOPE_RE.is_match(name) && value.iter().all(|s| OAUTHSCOPE_RE.is_match(s))
2292 }
2293
2294 Value::HexString(id) | Value::KeyInternal { id, .. } => {
2295 Value::validate_str_escapes(id.as_str())
2296 && Value::validate_singleline(id.as_str())
2297 && Value::validate_hexstr(id.as_str())
2298 }
2299
2300 Value::PhoneNumber(_, _) => true,
2301 Value::Address(_) => true,
2302 Value::Certificate(_) => true,
2303
2304 Value::Uuid(_)
2305 | Value::Bool(_)
2306 | Value::Syntax(_)
2307 | Value::Index(_)
2308 | Value::Refer(_)
2309 | Value::JsonFilt(_)
2310 | Value::SecretValue(_)
2311 | Value::Uint32(_)
2312 | Value::Int64(_)
2313 | Value::Uint64(_)
2314 | Value::Url(_)
2315 | Value::Cid(_)
2316 | Value::PrivateBinary(_)
2317 | Value::RestrictedString(_)
2318 | Value::JwsKeyEs256(_)
2319 | Value::Session(_, _)
2320 | Value::Oauth2Session(_, _)
2321 | Value::JwsKeyRs256(_)
2322 | Value::EcKeyPrivate(_)
2323 | Value::UiHint(_)
2324 | Value::CredentialType(_)
2325 | Value::Json(_)
2326 | Value::Sha256(_)
2327 | Value::WebauthnAttestationCaList(_) => true,
2328 }
2329 }
2330
2331 pub(crate) fn validate_iname(s: &str) -> bool {
2332 match Uuid::parse_str(s) {
2333 Ok(_) => {
2335 error!("iname values may not contain uuids");
2336 false
2337 }
2338 Err(_) => {
2340 if !INAME_RE.is_match(s) {
2341 error!("iname values may only contain limited characters - \"{}\" does not pass regex pattern \"{}\"", s, *INAME_RE);
2342 false
2343 } else if DISALLOWED_NAMES.contains(s) {
2344 error!("iname value \"{}\" is in denied list", s);
2345 false
2346 } else {
2347 true
2348 }
2349 }
2350 }
2351 }
2352
2353 pub(crate) fn validate_hexstr(s: &str) -> bool {
2354 if !HEXSTR_RE.is_match(s) {
2355 error!("hexstrings may only contain limited characters. - \"{}\" does not pass regex pattern \"{}\"", s, *HEXSTR_RE);
2356 false
2357 } else {
2358 true
2359 }
2360 }
2361
2362 pub(crate) fn validate_singleline(s: &str) -> bool {
2363 if !SINGLELINE_RE.is_match(s) {
2364 true
2365 } else {
2366 error!(
2367 "value contains invalid whitespace chars forbidden by \"{}\"",
2368 *SINGLELINE_RE
2369 );
2370 trace!(?s, "Invalid whitespace");
2372 false
2373 }
2374 }
2375
2376 pub(crate) fn validate_str_escapes(s: &str) -> bool {
2377 if UNICODE_CONTROL_RE.is_match(s) {
2379 error!("value contains invalid unicode control character",);
2380 trace!(?s, "Invalid Unicode Control");
2382 false
2383 } else {
2384 true
2385 }
2386 }
2387}
2388
2389#[cfg(test)]
2390mod tests {
2391 use crate::value::*;
2392
2393 #[test]
2394 fn test_value_index_tryfrom() {
2395 let r1 = IndexType::try_from("EQualiTY");
2396 assert_eq!(r1, Ok(IndexType::Equality));
2397
2398 let r2 = IndexType::try_from("PResenCE");
2399 assert_eq!(r2, Ok(IndexType::Presence));
2400
2401 let r3 = IndexType::try_from("SUbstrING");
2402 assert_eq!(r3, Ok(IndexType::SubString));
2403
2404 let r4 = IndexType::try_from("thaoeusaneuh");
2405 assert_eq!(r4, Err(()));
2406 }
2407
2408 #[test]
2409 fn test_value_syntax_tryfrom() {
2410 let r1 = SyntaxType::try_from("UTF8strinG");
2411 assert_eq!(r1, Ok(SyntaxType::Utf8String));
2412
2413 let r2 = SyntaxType::try_from("UTF8STRING_INSensitIVE");
2414 assert_eq!(r2, Ok(SyntaxType::Utf8StringInsensitive));
2415
2416 let r3 = SyntaxType::try_from("BOOLEAN");
2417 assert_eq!(r3, Ok(SyntaxType::Boolean));
2418
2419 let r4 = SyntaxType::try_from("SYNTAX_ID");
2420 assert_eq!(r4, Ok(SyntaxType::SyntaxId));
2421
2422 let r5 = SyntaxType::try_from("INDEX_ID");
2423 assert_eq!(r5, Ok(SyntaxType::IndexId));
2424
2425 let r6 = SyntaxType::try_from("zzzzantheou");
2426 assert_eq!(r6, Err(()));
2427 }
2428
2429 #[test]
2430 fn test_value_sshkey_validation_display() {
2431 let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
2432 "tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
2433 "zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
2434 "methyst");
2435 let ed25519 = concat!(
2436 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP",
2437 " william@amethyst"
2438 );
2439 let rsa = concat!("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTcXpclurQpyOHZBM/cDY9EvInSYkYSGe51by/wJP0Njgi",
2440 "GZUJ3HTaPqoGWux0PKd7KJki+onLYt4IwDV1RhV/GtMML2U9v94+pA8RIK4khCxvpUxlM7Kt/svjOzzzqiZfKdV37/",
2441 "OUXmM7bwVGOvm3EerDOwmO/QdzNGfkca12aWLoz97YrleXnCoAzr3IN7j3rwmfJGDyuUtGTdmyS/QWhK9FPr8Ic3eM",
2442 "QK1JSAQqVfGhA8lLbJHmnQ/b/KMl2lzzp7SXej0wPUfvI/IP3NGb8irLzq8+JssAzXGJ+HMql+mNHiSuPaktbFzZ6y",
2443 "ikMR6Rx/psU07nAkxKZDEYpNVv william@amethyst");
2444
2445 let sk1 = Value::new_sshkey_str("tag", ecdsa).expect("Invalid ssh key");
2446 assert!(sk1.validate());
2447 let psk1 = sk1.to_proto_string_clone();
2449 assert_eq!(psk1, format!("tag: {ecdsa}"));
2450
2451 let sk2 = Value::new_sshkey_str("tag", ed25519).expect("Invalid ssh key");
2452 assert!(sk2.validate());
2453 let psk2 = sk2.to_proto_string_clone();
2454 assert_eq!(psk2, format!("tag: {ed25519}"));
2455
2456 let sk3 = Value::new_sshkey_str("tag", rsa).expect("Invalid ssh key");
2457 assert!(sk3.validate());
2458 let psk3 = sk3.to_proto_string_clone();
2459 assert_eq!(psk3, format!("tag: {rsa}"));
2460
2461 let sk4 = Value::new_sshkey_str("tag", "ntaouhtnhtnuehtnuhotnuhtneouhtneouh");
2462 assert!(sk4.is_err());
2463
2464 let sk5 = Value::new_sshkey_str(
2465 "tag",
2466 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo",
2467 );
2468 assert!(sk5.is_err());
2469 }
2470
2471 #[test]
2507 fn test_value_cid() {
2508 assert!(PartialValue::new_cid_s("_").is_none());
2509 }
2510
2511 #[test]
2512 fn test_value_iname() {
2513 let inv1 = Value::new_iname("1234");
2524 let inv2 = Value::new_iname("bc23f637-4439-4c07-b95d-eaed0d9e4b8b");
2525 let inv3 = Value::new_iname("hello@test.com");
2526 let inv4 = Value::new_iname("_bad");
2527 let inv5 = Value::new_iname("no spaces I'm sorry :(");
2528 let inv6 = Value::new_iname("bad=equals");
2529 let inv7 = Value::new_iname("bad,comma");
2530 let inv8 = Value::new_iname("123_456");
2531 let inv9 = Value::new_iname("🍿");
2532
2533 let val1 = Value::new_iname("William");
2534 let val2 = Value::new_iname("this_is_okay");
2535 let val3 = Value::new_iname("a123_456");
2536
2537 assert!(!inv1.validate());
2538 assert!(!inv2.validate());
2539 assert!(!inv3.validate());
2540 assert!(!inv4.validate());
2541 assert!(!inv5.validate());
2542 assert!(!inv6.validate());
2543 assert!(!inv7.validate());
2544 assert!(!inv8.validate());
2545 assert!(!inv9.validate());
2546
2547 assert!(val1.validate());
2548 assert!(val2.validate());
2549 assert!(val3.validate());
2550 }
2551
2552 #[test]
2553 fn test_value_nsuniqueid() {
2554 let val1 = Value::new_nsuniqueid_s("d765e707-48e111e6-8c9ebed8-f7926cc3");
2559 let val2 = Value::new_nsuniqueid_s("D765E707-48E111E6-8C9EBED8-F7926CC3");
2560 let inv1 = Value::new_nsuniqueid_s("d765e707-48e1-11e6-8c9e-bed8f7926cc3");
2561 let inv2 = Value::new_nsuniqueid_s("xxxx");
2562
2563 assert!(inv1.is_none());
2564 assert!(inv2.is_none());
2565 assert!(val1.unwrap().validate());
2566 assert!(val2.unwrap().validate());
2567 }
2568
2569 #[test]
2570 fn test_value_datetime() {
2571 let val1 = Value::new_datetime_s("2020-09-25T11:22:02+10:00").expect("Must be valid");
2573 assert!(val1.validate());
2574 let val2 = Value::new_datetime_s("2020-09-25T01:22:02+00:00").expect("Must be valid");
2575 assert!(val2.validate());
2576 let val3 = Value::new_datetime_s("2020-09-25 01:22:02+00:00").expect("Must be valid");
2578 assert!(val3.validate());
2579
2580 assert!(Value::new_datetime_s("2020-09-25T01:22:02").is_none());
2581 assert!(Value::new_datetime_s("2020-09-25").is_none());
2582 assert!(Value::new_datetime_s("2020-09-25T01:22:02+10").is_none());
2583
2584 let inv1 = Value::DateTime(
2586 OffsetDateTime::now_utc()
2587 .to_offset(time::UtcOffset::from_whole_seconds(36000).unwrap()),
2588 );
2589 assert!(!inv1.validate());
2590
2591 let val3 = Value::DateTime(OffsetDateTime::now_utc());
2592 assert!(val3.validate());
2593 }
2594
2595 #[test]
2596 fn test_value_email_address() {
2597 let val1 = Value::new_email_address_s("william@blackhats.net.au");
2599 let val2 = Value::new_email_address_s("alice@idm.example.com");
2600 let val3 = Value::new_email_address_s("test+mailbox@foo.com");
2601 let inv1 = Value::new_email_address_s("william");
2602 let inv2 = Value::new_email_address_s("test~uuid");
2603
2604 assert!(inv1.is_none());
2605 assert!(inv2.is_none());
2606 assert!(val1.unwrap().validate());
2607 assert!(val2.unwrap().validate());
2608 assert!(val3.unwrap().validate());
2609 }
2610
2611 #[test]
2612 fn test_value_url() {
2613 let val1 = Value::new_url_s("https://localhost:8000/search?q=text#hello");
2615 let val2 = Value::new_url_s("https://github.com/kanidm/kanidm");
2616 let val3 = Value::new_url_s("ldap://foo.com");
2617 let inv1 = Value::new_url_s("127.0.");
2618 let inv2 = Value::new_url_s("🤔");
2619
2620 assert!(inv1.is_none());
2621 assert!(inv2.is_none());
2622 assert!(val1.is_some());
2623 assert!(val2.is_some());
2624 assert!(val3.is_some());
2625 }
2626
2627 #[test]
2628 fn test_singleline() {
2629 assert!(Value::validate_singleline("no new lines"));
2630
2631 assert!(!Value::validate_singleline("contains a \n new line"));
2632 assert!(!Value::validate_singleline("contains a \r return feed"));
2633 assert!(!Value::validate_singleline("contains a \t tab char"));
2634 }
2635
2636 #[test]
2637 fn test_str_escapes() {
2638 assert!(Value::validate_str_escapes("safe str"));
2639 assert!(Value::validate_str_escapes("🙃 emoji are 👍"));
2640
2641 assert!(!Value::validate_str_escapes("naughty \x1b[31mred"));
2642 }
2643
2644 #[test]
2645 fn test_value_key_internal_status_order() {
2646 assert!(KeyStatus::Valid < KeyStatus::Retained);
2647 assert!(KeyStatus::Retained < KeyStatus::Revoked);
2648 }
2649
2650 #[test]
2651 fn test_value_session_state_order() {
2652 assert!(
2653 SessionState::RevokedAt(Cid::new_zero()) > SessionState::RevokedAt(Cid::new_count(1))
2654 );
2655 assert!(
2656 SessionState::RevokedAt(Cid::new_zero())
2657 > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2658 );
2659 assert!(
2660 SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH + Duration::from_secs(1))
2661 > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2662 );
2663 assert!(SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH) > SessionState::NeverExpires);
2664 }
2665
2666 #[test]
2667 fn test_extract_val_dn_regexn() {
2668 fn do_extract(name: &str) -> &str {
2669 EXTRACT_VAL_DN
2670 .captures(name)
2671 .and_then(|caps| caps.name("val"))
2672 .map(|v| v.as_str())
2673 .unwrap()
2674 }
2675
2676 assert_eq!(do_extract("william"), "william");
2677 assert_eq!(do_extract("cn=william"), "william");
2678 assert_eq!(do_extract("cn=william,o=blackhats"), "william");
2679 assert_eq!(do_extract("cn=william@example.com"), "william@example.com");
2680 }
2681}