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