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 JweA128GCM,
1255}
1256
1257impl fmt::Display for KeyUsage {
1258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1259 write!(
1260 f,
1261 "{}",
1262 match self {
1263 KeyUsage::JwsEs256 => "jws_es256",
1264 KeyUsage::JweA128GCM => "jwe_a128gcm",
1265 }
1266 )
1267 }
1268}
1269
1270#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1271pub enum KeyStatus {
1272 Valid,
1273 Retained,
1274 Revoked,
1275}
1276
1277impl fmt::Display for KeyStatus {
1278 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1279 write!(
1280 f,
1281 "{}",
1282 match self {
1283 KeyStatus::Valid => "valid",
1284 KeyStatus::Retained => "retained",
1285 KeyStatus::Revoked => "revoked",
1286 }
1287 )
1288 }
1289}
1290
1291#[derive(Clone, Debug)]
1298pub enum Value {
1299 Utf8(String),
1300 Iutf8(String),
1302 Iname(String),
1304 Uuid(Uuid),
1305 Bool(bool),
1306 Syntax(SyntaxType),
1307 Index(IndexType),
1308 Refer(Uuid),
1309 JsonFilt(ProtoFilter),
1310 Cred(String, Credential),
1311 SshKey(String, SshPublicKey),
1312 SecretValue(String),
1313 Spn(String, String),
1314 Uint32(u32),
1315 Cid(Cid),
1316 Nsuniqueid(String),
1317 DateTime(OffsetDateTime),
1318 EmailAddress(String, bool),
1319 PhoneNumber(String, bool),
1320 Address(Address),
1321 Url(Url),
1322 OauthScope(String),
1323 OauthScopeMap(Uuid, BTreeSet<String>),
1324 PrivateBinary(Vec<u8>),
1325 PublicBinary(String, Vec<u8>),
1326 RestrictedString(String),
1327 IntentToken(String, IntentTokenState),
1328 Passkey(Uuid, String, PasskeyV4),
1329 AttestedPasskey(Uuid, String, AttestedPasskeyV4),
1330
1331 Session(Uuid, Session),
1332 ApiToken(Uuid, ApiToken),
1333 Oauth2Session(Uuid, Oauth2Session),
1334
1335 JwsKeyEs256(JwsEs256Signer),
1336 JwsKeyRs256(JwsRs256Signer),
1337 UiHint(UiHint),
1338
1339 TotpSecret(String, Totp),
1340 AuditLogString(Cid, String),
1341 EcKeyPrivate(EcKey<Private>),
1342
1343 Image(ImageValue),
1344 CredentialType(CredentialType),
1345 WebauthnAttestationCaList(AttestationCaList),
1346
1347 OauthClaimValue(String, Uuid, BTreeSet<String>),
1348 OauthClaimMap(String, OauthClaimMapJoin),
1349
1350 KeyInternal {
1351 id: KeyId,
1352 usage: KeyUsage,
1353 valid_from: u64,
1354 status: KeyStatus,
1355 status_cid: Cid,
1356 der: Vec<u8>,
1357 },
1358
1359 HexString(String),
1360
1361 Certificate(Box<Certificate>),
1362 ApplicationPassword(ApplicationPassword),
1363}
1364
1365impl PartialEq for Value {
1366 fn eq(&self, other: &Self) -> bool {
1367 match (self, other) {
1368 (Value::Utf8(a), Value::Utf8(b))
1369 | (Value::Iutf8(a), Value::Iutf8(b))
1370 | (Value::Iname(a), Value::Iname(b))
1371 | (Value::Cred(a, _), Value::Cred(b, _))
1372 | (Value::SshKey(a, _), Value::SshKey(b, _))
1373 | (Value::Nsuniqueid(a), Value::Nsuniqueid(b))
1374 | (Value::EmailAddress(a, _), Value::EmailAddress(b, _))
1375 | (Value::PhoneNumber(a, _), Value::PhoneNumber(b, _))
1376 | (Value::OauthScope(a), Value::OauthScope(b))
1377 | (Value::PublicBinary(a, _), Value::PublicBinary(b, _))
1378 | (Value::RestrictedString(a), Value::RestrictedString(b)) => a.eq(b),
1379 (Value::Spn(a, c), Value::Spn(b, d)) => a.eq(b) && c.eq(d),
1381 (Value::Uuid(a), Value::Uuid(b)) | (Value::Refer(a), Value::Refer(b)) => a.eq(b),
1383 (Value::Bool(a), Value::Bool(b)) => a.eq(b),
1385 (Value::Syntax(a), Value::Syntax(b)) => a.eq(b),
1387 (Value::Index(a), Value::Index(b)) => a.eq(b),
1389 (Value::JsonFilt(a), Value::JsonFilt(b)) => a.eq(b),
1391 (Value::Uint32(a), Value::Uint32(b)) => a.eq(b),
1393 (Value::Cid(a), Value::Cid(b)) => a.eq(b),
1395 (Value::DateTime(a), Value::DateTime(b)) => a.eq(b),
1397 (Value::Url(a), Value::Url(b)) => a.eq(b),
1399 (Value::OauthScopeMap(a, c), Value::OauthScopeMap(b, d)) => a.eq(b) && c.eq(d),
1401
1402 (Value::Image(image1), Value::Image(image2)) => {
1403 image1.hash_imagevalue().eq(&image2.hash_imagevalue())
1404 }
1405 (Value::Address(_), Value::Address(_))
1406 | (Value::PrivateBinary(_), Value::PrivateBinary(_))
1407 | (Value::SecretValue(_), Value::SecretValue(_)) => false,
1408 (Value::Iutf8(_), Value::Iname(_)) | (Value::Iname(_), Value::Iutf8(_)) => false,
1410 (Value::Uuid(_), Value::Iname(_))
1412 | (Value::Iname(_), Value::Spn(_, _))
1413 | (Value::Uuid(_), Value::Spn(_, _)) => false,
1414 (l, r) => {
1415 error!(?l, ?r, "mismatched value types");
1416 debug_assert!(false);
1417 false
1418 }
1419 }
1420 }
1421}
1422
1423impl Eq for Value {}
1424
1425impl From<bool> for Value {
1426 fn from(b: bool) -> Self {
1427 Value::Bool(b)
1428 }
1429}
1430
1431impl From<&bool> for Value {
1432 fn from(b: &bool) -> Self {
1433 Value::Bool(*b)
1434 }
1435}
1436
1437impl From<SyntaxType> for Value {
1438 fn from(s: SyntaxType) -> Self {
1439 Value::Syntax(s)
1440 }
1441}
1442
1443impl From<IndexType> for Value {
1444 fn from(i: IndexType) -> Self {
1445 Value::Index(i)
1446 }
1447}
1448
1449impl From<ProtoFilter> for Value {
1450 fn from(i: ProtoFilter) -> Self {
1451 Value::JsonFilt(i)
1452 }
1453}
1454
1455impl From<OffsetDateTime> for Value {
1456 fn from(i: OffsetDateTime) -> Self {
1457 Value::DateTime(i)
1458 }
1459}
1460
1461impl From<u32> for Value {
1462 fn from(i: u32) -> Self {
1463 Value::Uint32(i)
1464 }
1465}
1466
1467impl From<Url> for Value {
1468 fn from(i: Url) -> Self {
1469 Value::Url(i)
1470 }
1471}
1472
1473#[cfg(test)]
1476impl From<&str> for Value {
1477 fn from(s: &str) -> Self {
1478 match Uuid::parse_str(s) {
1480 Ok(u) => Value::Uuid(u),
1481 Err(_) => Value::Utf8(s.to_string()),
1482 }
1483 }
1484}
1485
1486#[cfg(test)]
1487impl From<&Uuid> for Value {
1488 fn from(u: &Uuid) -> Self {
1489 Value::Uuid(*u)
1490 }
1491}
1492
1493#[cfg(test)]
1494impl From<Uuid> for Value {
1495 fn from(u: Uuid) -> Self {
1496 Value::Uuid(u)
1497 }
1498}
1499
1500impl From<DbIdentSpn> for Value {
1501 fn from(dis: DbIdentSpn) -> Self {
1502 match dis {
1503 DbIdentSpn::Spn(n, r) => Value::Spn(n, r),
1504 DbIdentSpn::Iname(n) => Value::Iname(n),
1505 DbIdentSpn::Uuid(u) => Value::Uuid(u),
1506 }
1507 }
1508}
1509
1510impl Value {
1511 pub fn new_utf8(s: String) -> Self {
1513 Value::Utf8(s)
1514 }
1515
1516 pub fn new_utf8s(s: &str) -> Self {
1517 Value::Utf8(s.to_string())
1518 }
1519
1520 pub fn is_utf8(&self) -> bool {
1521 matches!(self, Value::Utf8(_))
1522 }
1523
1524 pub fn new_iutf8(s: &str) -> Self {
1525 Value::Iutf8(s.to_lowercase())
1526 }
1527
1528 pub fn is_iutf8(&self) -> bool {
1529 matches!(self, Value::Iutf8(_))
1530 }
1531
1532 #[inline(always)]
1533 pub fn new_class(s: &str) -> Self {
1534 Value::Iutf8(s.to_lowercase())
1535 }
1536
1537 pub fn new_attr(s: &str) -> Self {
1538 Value::Iutf8(s.to_lowercase())
1539 }
1540
1541 pub fn is_insensitive_utf8(&self) -> bool {
1542 matches!(self, Value::Iutf8(_))
1543 }
1544
1545 pub fn new_iname(s: &str) -> Self {
1546 Value::Iname(s.to_lowercase())
1547 }
1548
1549 pub fn is_iname(&self) -> bool {
1550 matches!(self, Value::Iname(_))
1551 }
1552
1553 pub fn new_uuid_s(s: &str) -> Option<Self> {
1554 Uuid::parse_str(s).map(Value::Uuid).ok()
1555 }
1556
1557 pub fn is_uuid(&self) -> bool {
1559 matches!(self, Value::Uuid(_))
1560 }
1561
1562 pub fn new_bool(b: bool) -> Self {
1563 Value::Bool(b)
1564 }
1565
1566 pub fn new_bools(s: &str) -> Option<Self> {
1567 bool::from_str(s).map(Value::Bool).ok()
1568 }
1569
1570 pub fn new_audit_log_string(e: (Cid, String)) -> Option<Self> {
1571 Some(Value::AuditLogString(e.0, e.1))
1572 }
1573
1574 #[inline]
1575 pub fn is_bool(&self) -> bool {
1576 matches!(self, Value::Bool(_))
1577 }
1578
1579 pub fn new_syntaxs(s: &str) -> Option<Self> {
1580 SyntaxType::try_from(s).map(Value::Syntax).ok()
1581 }
1582
1583 pub fn new_syntax(s: SyntaxType) -> Self {
1584 Value::Syntax(s)
1585 }
1586
1587 pub fn is_syntax(&self) -> bool {
1588 matches!(self, Value::Syntax(_))
1589 }
1590
1591 pub fn new_indexes(s: &str) -> Option<Self> {
1592 IndexType::try_from(s).map(Value::Index).ok()
1593 }
1594
1595 pub fn new_index(i: IndexType) -> Self {
1596 Value::Index(i)
1597 }
1598
1599 pub fn is_index(&self) -> bool {
1600 matches!(self, Value::Index(_))
1601 }
1602
1603 pub fn new_refer_s(us: &str) -> Option<Self> {
1604 Uuid::parse_str(us).map(Value::Refer).ok()
1605 }
1606
1607 pub fn is_refer(&self) -> bool {
1608 matches!(self, Value::Refer(_))
1609 }
1610
1611 pub fn new_json_filter_s(s: &str) -> Option<Self> {
1612 serde_json::from_str(s).map(Value::JsonFilt).ok()
1613 }
1614
1615 pub fn new_json_filter(f: ProtoFilter) -> Self {
1616 Value::JsonFilt(f)
1617 }
1618
1619 pub fn is_json_filter(&self) -> bool {
1620 matches!(self, Value::JsonFilt(_))
1621 }
1622
1623 pub fn as_json_filter(&self) -> Option<&ProtoFilter> {
1624 match &self {
1625 Value::JsonFilt(f) => Some(f),
1626 _ => None,
1627 }
1628 }
1629
1630 pub fn new_credential(tag: &str, cred: Credential) -> Self {
1631 Value::Cred(tag.to_string(), cred)
1632 }
1633
1634 pub fn is_credential(&self) -> bool {
1635 matches!(&self, Value::Cred(_, _))
1636 }
1637
1638 pub fn to_credential(&self) -> Option<&Credential> {
1639 match &self {
1640 Value::Cred(_, cred) => Some(cred),
1641 _ => None,
1642 }
1643 }
1644
1645 pub fn new_hex_string_s(hexstr: &str) -> Option<Self> {
1646 let hexstr_lower = hexstr.to_lowercase();
1647 if HEXSTR_RE.is_match(&hexstr_lower) {
1648 Some(Value::HexString(hexstr_lower))
1649 } else {
1650 None
1651 }
1652 }
1653
1654 pub fn new_certificate_s(cert_str: &str) -> Option<Self> {
1655 Certificate::from_pem(cert_str)
1656 .map(Box::new)
1657 .map(Value::Certificate)
1658 .ok()
1659 }
1660
1661 pub fn new_image(input: &str) -> Result<Self, OperationError> {
1663 serde_json::from_str::<ImageValue>(input)
1664 .map(Value::Image)
1665 .map_err(|_e| OperationError::InvalidValueState)
1666 }
1667
1668 pub fn new_secret_str(cleartext: &str) -> Self {
1669 Value::SecretValue(cleartext.to_string())
1670 }
1671
1672 pub fn is_secret_string(&self) -> bool {
1673 matches!(&self, Value::SecretValue(_))
1674 }
1675
1676 pub fn get_secret_str(&self) -> Option<&str> {
1677 match &self {
1678 Value::SecretValue(c) => Some(c.as_str()),
1679 _ => None,
1680 }
1681 }
1682
1683 pub fn new_sshkey_str(tag: &str, key: &str) -> Result<Self, OperationError> {
1684 SshPublicKey::from_string(key)
1685 .map(|pk| Value::SshKey(tag.to_string(), pk))
1686 .map_err(|err| {
1687 error!(?err, "value sshkey failed to parse string");
1688 OperationError::VL0001ValueSshPublicKeyString
1689 })
1690 }
1691
1692 pub fn is_sshkey(&self) -> bool {
1693 matches!(&self, Value::SshKey(_, _))
1694 }
1695
1696 pub fn get_sshkey(&self) -> Option<String> {
1697 match &self {
1698 Value::SshKey(_, key) => Some(key.to_string()),
1699 _ => None,
1700 }
1701 }
1702
1703 pub fn new_spn_parse(s: &str) -> Option<Self> {
1704 SPN_RE.captures(s).and_then(|caps| {
1705 let name = match caps.name(Attribute::Name.as_ref()) {
1706 Some(v) => v.as_str().to_string(),
1707 None => return None,
1708 };
1709 let realm = match caps.name("realm") {
1710 Some(v) => v.as_str().to_string(),
1711 None => return None,
1712 };
1713 Some(Value::Spn(name, realm))
1714 })
1715 }
1716
1717 pub fn new_spn_str(n: &str, r: &str) -> Self {
1718 Value::Spn(n.to_string(), r.to_string())
1719 }
1720
1721 pub fn is_spn(&self) -> bool {
1722 matches!(&self, Value::Spn(_, _))
1723 }
1724
1725 pub fn new_uint32(u: u32) -> Self {
1726 Value::Uint32(u)
1727 }
1728
1729 pub fn new_uint32_str(u: &str) -> Option<Self> {
1730 u.parse::<u32>().ok().map(Value::Uint32)
1731 }
1732
1733 pub fn is_uint32(&self) -> bool {
1734 matches!(&self, Value::Uint32(_))
1735 }
1736
1737 pub fn new_cid(c: Cid) -> Self {
1738 Value::Cid(c)
1739 }
1740
1741 pub fn is_cid(&self) -> bool {
1742 matches!(&self, Value::Cid(_))
1743 }
1744
1745 pub fn new_nsuniqueid_s(s: &str) -> Option<Self> {
1746 if NSUNIQUEID_RE.is_match(s) {
1747 Some(Value::Nsuniqueid(s.to_lowercase()))
1748 } else {
1749 None
1750 }
1751 }
1752
1753 pub fn is_nsuniqueid(&self) -> bool {
1754 matches!(&self, Value::Nsuniqueid(_))
1755 }
1756
1757 pub fn new_datetime_epoch(ts: Duration) -> Self {
1758 Value::DateTime(OffsetDateTime::UNIX_EPOCH + ts)
1759 }
1760
1761 pub fn new_datetime_s(s: &str) -> Option<Self> {
1762 OffsetDateTime::parse(s, &Rfc3339)
1763 .ok()
1764 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
1765 .map(Value::DateTime)
1766 }
1767
1768 pub fn new_datetime(dt: OffsetDateTime) -> Self {
1769 Value::DateTime(dt)
1770 }
1771
1772 pub fn to_datetime(&self) -> Option<OffsetDateTime> {
1773 match &self {
1774 Value::DateTime(odt) => {
1775 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
1776 Some(*odt)
1777 }
1778 _ => None,
1779 }
1780 }
1781
1782 pub fn is_datetime(&self) -> bool {
1783 matches!(&self, Value::DateTime(_))
1784 }
1785
1786 pub fn new_email_address_s(s: &str) -> Option<Self> {
1787 if VALIDATE_EMAIL_RE.is_match(s) {
1788 Some(Value::EmailAddress(s.to_string(), false))
1789 } else {
1790 None
1791 }
1792 }
1793
1794 pub fn new_email_address_primary_s(s: &str) -> Option<Self> {
1795 if VALIDATE_EMAIL_RE.is_match(s) {
1796 Some(Value::EmailAddress(s.to_string(), true))
1797 } else {
1798 None
1799 }
1800 }
1801
1802 pub fn is_email_address(&self) -> bool {
1803 matches!(&self, Value::EmailAddress(_, _))
1804 }
1805
1806 pub fn new_phonenumber_s(s: &str) -> Self {
1807 Value::PhoneNumber(s.to_string(), false)
1808 }
1809
1810 pub fn new_address(a: Address) -> Self {
1811 Value::Address(a)
1812 }
1813
1814 pub fn new_url_s(s: &str) -> Option<Self> {
1815 Url::parse(s).ok().map(Value::Url)
1816 }
1817
1818 pub fn new_url(u: Url) -> Self {
1819 Value::Url(u)
1820 }
1821
1822 pub fn is_url(&self) -> bool {
1823 matches!(&self, Value::Url(_))
1824 }
1825
1826 pub fn new_oauthscope(s: &str) -> Option<Self> {
1827 if OAUTHSCOPE_RE.is_match(s) {
1828 Some(Value::OauthScope(s.to_string()))
1829 } else {
1830 None
1831 }
1832 }
1833
1834 pub fn is_oauthscope(&self) -> bool {
1835 matches!(&self, Value::OauthScope(_))
1836 }
1837
1838 pub fn new_oauthscopemap(u: Uuid, m: BTreeSet<String>) -> Option<Self> {
1839 if m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1840 Some(Value::OauthScopeMap(u, m))
1841 } else {
1842 None
1843 }
1844 }
1845
1846 pub fn new_oauthclaimmap(n: String, u: Uuid, c: BTreeSet<String>) -> Option<Self> {
1847 if OAUTHSCOPE_RE.is_match(&n) && c.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1848 Some(Value::OauthClaimValue(n, u, c))
1849 } else {
1850 None
1851 }
1852 }
1853
1854 pub fn is_oauthscopemap(&self) -> bool {
1855 matches!(&self, Value::OauthScopeMap(_, _))
1856 }
1857
1858 #[cfg(test)]
1859 pub fn new_privatebinary_base64(der: &str) -> Self {
1860 let der = general_purpose::STANDARD
1861 .decode(der)
1862 .expect("Failed to decode base64 der value");
1863 Value::PrivateBinary(der)
1864 }
1865
1866 pub fn new_privatebinary(der: &[u8]) -> Self {
1867 Value::PrivateBinary(der.to_owned())
1868 }
1869
1870 pub fn to_privatebinary(&self) -> Option<&Vec<u8>> {
1871 match &self {
1872 Value::PrivateBinary(c) => Some(c),
1873 _ => None,
1874 }
1875 }
1876
1877 pub fn is_privatebinary(&self) -> bool {
1878 matches!(&self, Value::PrivateBinary(_))
1879 }
1880
1881 pub fn new_publicbinary(tag: String, der: Vec<u8>) -> Self {
1882 Value::PublicBinary(tag, der)
1883 }
1884
1885 pub fn new_restrictedstring(s: String) -> Self {
1886 Value::RestrictedString(s)
1887 }
1888
1889 pub fn new_webauthn_attestation_ca_list(s: &str) -> Option<Self> {
1890 serde_json::from_str(s)
1891 .map(Value::WebauthnAttestationCaList)
1892 .map_err(|err| {
1893 debug!(?err, ?s);
1894 })
1895 .ok()
1896 }
1897
1898 #[allow(clippy::unreachable)]
1899 pub(crate) fn to_db_ident_spn(&self) -> DbIdentSpn {
1900 match &self {
1902 Value::Spn(n, r) => DbIdentSpn::Spn(n.clone(), r.clone()),
1903 Value::Iname(s) => DbIdentSpn::Iname(s.clone()),
1904 Value::Uuid(u) => DbIdentSpn::Uuid(*u),
1905 v => unreachable!("-> {:?}", v),
1909 }
1910 }
1911
1912 pub fn to_str(&self) -> Option<&str> {
1913 match &self {
1914 Value::Utf8(s) => Some(s.as_str()),
1915 Value::Iutf8(s) => Some(s.as_str()),
1916 Value::Iname(s) => Some(s.as_str()),
1917 _ => None,
1918 }
1919 }
1920
1921 pub fn to_url(&self) -> Option<&Url> {
1922 match &self {
1923 Value::Url(u) => Some(u),
1924 _ => None,
1925 }
1926 }
1927
1928 pub fn as_string(&self) -> Option<&String> {
1929 match &self {
1930 Value::Utf8(s) => Some(s),
1931 Value::Iutf8(s) => Some(s),
1932 Value::Iname(s) => Some(s),
1933 _ => None,
1934 }
1935 }
1936
1937 pub fn to_ref_uuid(&self) -> Option<Uuid> {
1940 match &self {
1941 Value::Refer(u) => Some(*u),
1942 Value::OauthScopeMap(u, _) => Some(*u),
1943 Value::Oauth2Session(_, m) => Some(m.rs_uuid),
1945 _ => None,
1946 }
1947 }
1948
1949 pub fn to_uuid(&self) -> Option<&Uuid> {
1950 match &self {
1951 Value::Uuid(u) => Some(u),
1952 _ => None,
1953 }
1954 }
1955
1956 pub fn to_indextype(&self) -> Option<&IndexType> {
1957 match &self {
1958 Value::Index(i) => Some(i),
1959 _ => None,
1960 }
1961 }
1962
1963 pub fn to_syntaxtype(&self) -> Option<&SyntaxType> {
1964 match &self {
1965 Value::Syntax(s) => Some(s),
1966 _ => None,
1967 }
1968 }
1969
1970 pub fn to_bool(&self) -> Option<bool> {
1971 match self {
1972 Value::Bool(v) => Some(*v),
1974 _ => None,
1975 }
1976 }
1977
1978 pub fn to_uint32(&self) -> Option<u32> {
1979 match &self {
1980 Value::Uint32(v) => Some(*v),
1981 _ => None,
1982 }
1983 }
1984
1985 pub fn to_utf8(self) -> Option<String> {
1986 match self {
1987 Value::Utf8(s) => Some(s),
1988 _ => None,
1989 }
1990 }
1991
1992 pub fn to_iutf8(self) -> Option<String> {
1993 match self {
1994 Value::Iutf8(s) => Some(s),
1995 _ => None,
1996 }
1997 }
1998
1999 pub fn to_iname(self) -> Option<String> {
2000 match self {
2001 Value::Iname(s) => Some(s),
2002 _ => None,
2003 }
2004 }
2005
2006 pub fn to_jsonfilt(self) -> Option<ProtoFilter> {
2007 match self {
2008 Value::JsonFilt(f) => Some(f),
2009 _ => None,
2010 }
2011 }
2012
2013 pub fn to_cred(self) -> Option<(String, Credential)> {
2014 match self {
2015 Value::Cred(tag, c) => Some((tag, c)),
2016 _ => None,
2017 }
2018 }
2019
2020 pub fn to_spn(self) -> Option<(String, String)> {
2030 match self {
2031 Value::Spn(n, d) => Some((n, d)),
2032 _ => None,
2033 }
2034 }
2035
2036 pub fn to_cid(self) -> Option<Cid> {
2037 match self {
2038 Value::Cid(s) => Some(s),
2039 _ => None,
2040 }
2041 }
2042
2043 pub fn to_nsuniqueid(self) -> Option<String> {
2044 match self {
2045 Value::Nsuniqueid(s) => Some(s),
2046 _ => None,
2047 }
2048 }
2049
2050 pub fn to_emailaddress(self) -> Option<String> {
2051 match self {
2052 Value::EmailAddress(s, _) => Some(s),
2053 _ => None,
2054 }
2055 }
2056
2057 pub fn to_oauthscope(self) -> Option<String> {
2058 match self {
2059 Value::OauthScope(s) => Some(s),
2060 _ => None,
2061 }
2062 }
2063
2064 pub fn to_oauthscopemap(self) -> Option<(Uuid, BTreeSet<String>)> {
2065 match self {
2066 Value::OauthScopeMap(u, m) => Some((u, m)),
2067 _ => None,
2068 }
2069 }
2070
2071 pub fn to_restrictedstring(self) -> Option<String> {
2072 match self {
2073 Value::RestrictedString(s) => Some(s),
2074 _ => None,
2075 }
2076 }
2077
2078 pub fn to_phonenumber(self) -> Option<String> {
2079 match self {
2080 Value::PhoneNumber(p, _b) => Some(p),
2081 _ => None,
2082 }
2083 }
2084
2085 pub fn to_publicbinary(self) -> Option<(String, Vec<u8>)> {
2086 match self {
2087 Value::PublicBinary(t, d) => Some((t, d)),
2088 _ => None,
2089 }
2090 }
2091
2092 pub fn to_address(self) -> Option<Address> {
2093 match self {
2094 Value::Address(a) => Some(a),
2095 _ => None,
2096 }
2097 }
2098
2099 pub fn to_intenttoken(self) -> Option<(String, IntentTokenState)> {
2100 match self {
2101 Value::IntentToken(u, s) => Some((u, s)),
2102 _ => None,
2103 }
2104 }
2105
2106 pub fn to_session(self) -> Option<(Uuid, Session)> {
2107 match self {
2108 Value::Session(u, s) => Some((u, s)),
2109 _ => None,
2110 }
2111 }
2112
2113 pub fn migrate_iutf8_iname(self) -> Option<Self> {
2114 match self {
2115 Value::Iutf8(v) => Some(Value::Iname(v)),
2116 _ => None,
2117 }
2118 }
2119
2120 #[allow(clippy::unreachable)]
2122 pub(crate) fn to_proto_string_clone(&self) -> String {
2123 match &self {
2124 Value::Iname(s) => s.clone(),
2125 Value::Uuid(u) => u.as_hyphenated().to_string(),
2126 Value::SshKey(tag, key) => format!("{}: {}", tag, key),
2128 Value::Spn(n, r) => format!("{n}@{r}"),
2129 _ => unreachable!(
2130 "You've specified the wrong type for the attribute, got: {:?}",
2131 self
2132 ),
2133 }
2134 }
2135
2136 pub(crate) fn validate(&self) -> bool {
2137 match &self {
2141 Value::Utf8(s)
2143 | Value::Iutf8(s)
2144 | Value::Cred(s, _)
2145 | Value::PublicBinary(s, _)
2146 | Value::IntentToken(s, _)
2147 | Value::Passkey(_, s, _)
2148 | Value::AttestedPasskey(_, s, _)
2149 | Value::TotpSecret(s, _) => {
2150 Value::validate_str_escapes(s) && Value::validate_singleline(s)
2151 }
2152
2153 Value::Spn(a, b) => {
2154 Value::validate_str_escapes(a)
2155 && Value::validate_str_escapes(b)
2156 && Value::validate_singleline(a)
2157 && Value::validate_singleline(b)
2158 }
2159 Value::Image(image) => image.validate_image().is_ok(),
2160 Value::Iname(s) => {
2161 Value::validate_str_escapes(s)
2162 && Value::validate_iname(s)
2163 && Value::validate_singleline(s)
2164 }
2165
2166 Value::SshKey(s, _key) => {
2167 Value::validate_str_escapes(s)
2168 && Value::validate_singleline(s)
2170 }
2171
2172 Value::ApiToken(_, at) => {
2173 Value::validate_str_escapes(&at.label) && Value::validate_singleline(&at.label)
2174 }
2175 Value::AuditLogString(_, s) => {
2176 Value::validate_str_escapes(s) && Value::validate_singleline(s)
2177 }
2178 Value::ApplicationPassword(ap) => {
2179 Value::validate_str_escapes(&ap.label) && Value::validate_singleline(&ap.label)
2180 }
2181
2182 Value::Nsuniqueid(s) => NSUNIQUEID_RE.is_match(s),
2184 Value::DateTime(odt) => odt.offset() == time::UtcOffset::UTC,
2185 Value::EmailAddress(mail, _) => VALIDATE_EMAIL_RE.is_match(mail.as_str()),
2186 Value::OauthScope(s) => OAUTHSCOPE_RE.is_match(s),
2187 Value::OauthScopeMap(_, m) => m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)),
2188
2189 Value::OauthClaimMap(name, _) => OAUTHSCOPE_RE.is_match(name),
2190 Value::OauthClaimValue(name, _, value) => {
2191 OAUTHSCOPE_RE.is_match(name) && value.iter().all(|s| OAUTHSCOPE_RE.is_match(s))
2192 }
2193
2194 Value::HexString(id) | Value::KeyInternal { id, .. } => {
2195 Value::validate_str_escapes(id.as_str())
2196 && Value::validate_singleline(id.as_str())
2197 && Value::validate_hexstr(id.as_str())
2198 }
2199
2200 Value::PhoneNumber(_, _) => true,
2201 Value::Address(_) => true,
2202 Value::Certificate(_) => true,
2203
2204 Value::Uuid(_)
2205 | Value::Bool(_)
2206 | Value::Syntax(_)
2207 | Value::Index(_)
2208 | Value::Refer(_)
2209 | Value::JsonFilt(_)
2210 | Value::SecretValue(_)
2211 | Value::Uint32(_)
2212 | Value::Url(_)
2213 | Value::Cid(_)
2214 | Value::PrivateBinary(_)
2215 | Value::RestrictedString(_)
2216 | Value::JwsKeyEs256(_)
2217 | Value::Session(_, _)
2218 | Value::Oauth2Session(_, _)
2219 | Value::JwsKeyRs256(_)
2220 | Value::EcKeyPrivate(_)
2221 | Value::UiHint(_)
2222 | Value::CredentialType(_)
2223 | Value::WebauthnAttestationCaList(_) => true,
2224 }
2225 }
2226
2227 pub(crate) fn validate_iname(s: &str) -> bool {
2228 match Uuid::parse_str(s) {
2229 Ok(_) => {
2231 error!("iname values may not contain uuids");
2232 false
2233 }
2234 Err(_) => {
2236 if !INAME_RE.is_match(s) {
2237 error!("iname values may only contain limited characters - \"{}\" does not pass regex pattern \"{}\"", s, *INAME_RE);
2238 false
2239 } else if DISALLOWED_NAMES.contains(s) {
2240 error!("iname value \"{}\" is in denied list", s);
2241 false
2242 } else {
2243 true
2244 }
2245 }
2246 }
2247 }
2248
2249 pub(crate) fn validate_hexstr(s: &str) -> bool {
2250 if !HEXSTR_RE.is_match(s) {
2251 error!("hexstrings may only contain limited characters. - \"{}\" does not pass regex pattern \"{}\"", s, *HEXSTR_RE);
2252 false
2253 } else {
2254 true
2255 }
2256 }
2257
2258 pub(crate) fn validate_singleline(s: &str) -> bool {
2259 if !SINGLELINE_RE.is_match(s) {
2260 true
2261 } else {
2262 error!(
2263 "value contains invalid whitespace chars forbidden by \"{}\"",
2264 *SINGLELINE_RE
2265 );
2266 trace!(?s, "Invalid whitespace");
2268 false
2269 }
2270 }
2271
2272 pub(crate) fn validate_str_escapes(s: &str) -> bool {
2273 if UNICODE_CONTROL_RE.is_match(s) {
2275 error!("value contains invalid unicode control character",);
2276 trace!(?s, "Invalid Unicode Control");
2278 false
2279 } else {
2280 true
2281 }
2282 }
2283}
2284
2285#[cfg(test)]
2286mod tests {
2287 use crate::value::*;
2288
2289 #[test]
2290 fn test_value_index_tryfrom() {
2291 let r1 = IndexType::try_from("EQualiTY");
2292 assert_eq!(r1, Ok(IndexType::Equality));
2293
2294 let r2 = IndexType::try_from("PResenCE");
2295 assert_eq!(r2, Ok(IndexType::Presence));
2296
2297 let r3 = IndexType::try_from("SUbstrING");
2298 assert_eq!(r3, Ok(IndexType::SubString));
2299
2300 let r4 = IndexType::try_from("thaoeusaneuh");
2301 assert_eq!(r4, Err(()));
2302 }
2303
2304 #[test]
2305 fn test_value_syntax_tryfrom() {
2306 let r1 = SyntaxType::try_from("UTF8strinG");
2307 assert_eq!(r1, Ok(SyntaxType::Utf8String));
2308
2309 let r2 = SyntaxType::try_from("UTF8STRING_INSensitIVE");
2310 assert_eq!(r2, Ok(SyntaxType::Utf8StringInsensitive));
2311
2312 let r3 = SyntaxType::try_from("BOOLEAN");
2313 assert_eq!(r3, Ok(SyntaxType::Boolean));
2314
2315 let r4 = SyntaxType::try_from("SYNTAX_ID");
2316 assert_eq!(r4, Ok(SyntaxType::SyntaxId));
2317
2318 let r5 = SyntaxType::try_from("INDEX_ID");
2319 assert_eq!(r5, Ok(SyntaxType::IndexId));
2320
2321 let r6 = SyntaxType::try_from("zzzzantheou");
2322 assert_eq!(r6, Err(()));
2323 }
2324
2325 #[test]
2326 fn test_value_sshkey_validation_display() {
2327 let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
2328 "tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
2329 "zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
2330 "methyst");
2331 let ed25519 = concat!(
2332 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP",
2333 " william@amethyst"
2334 );
2335 let rsa = concat!("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTcXpclurQpyOHZBM/cDY9EvInSYkYSGe51by/wJP0Njgi",
2336 "GZUJ3HTaPqoGWux0PKd7KJki+onLYt4IwDV1RhV/GtMML2U9v94+pA8RIK4khCxvpUxlM7Kt/svjOzzzqiZfKdV37/",
2337 "OUXmM7bwVGOvm3EerDOwmO/QdzNGfkca12aWLoz97YrleXnCoAzr3IN7j3rwmfJGDyuUtGTdmyS/QWhK9FPr8Ic3eM",
2338 "QK1JSAQqVfGhA8lLbJHmnQ/b/KMl2lzzp7SXej0wPUfvI/IP3NGb8irLzq8+JssAzXGJ+HMql+mNHiSuPaktbFzZ6y",
2339 "ikMR6Rx/psU07nAkxKZDEYpNVv william@amethyst");
2340
2341 let sk1 = Value::new_sshkey_str("tag", ecdsa).expect("Invalid ssh key");
2342 assert!(sk1.validate());
2343 let psk1 = sk1.to_proto_string_clone();
2345 assert_eq!(psk1, format!("tag: {}", ecdsa));
2346
2347 let sk2 = Value::new_sshkey_str("tag", ed25519).expect("Invalid ssh key");
2348 assert!(sk2.validate());
2349 let psk2 = sk2.to_proto_string_clone();
2350 assert_eq!(psk2, format!("tag: {}", ed25519));
2351
2352 let sk3 = Value::new_sshkey_str("tag", rsa).expect("Invalid ssh key");
2353 assert!(sk3.validate());
2354 let psk3 = sk3.to_proto_string_clone();
2355 assert_eq!(psk3, format!("tag: {}", rsa));
2356
2357 let sk4 = Value::new_sshkey_str("tag", "ntaouhtnhtnuehtnuhotnuhtneouhtneouh");
2358 assert!(sk4.is_err());
2359
2360 let sk5 = Value::new_sshkey_str(
2361 "tag",
2362 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo",
2363 );
2364 assert!(sk5.is_err());
2365 }
2366
2367 #[test]
2403 fn test_value_cid() {
2404 assert!(PartialValue::new_cid_s("_").is_none());
2405 }
2406
2407 #[test]
2408 fn test_value_iname() {
2409 let inv1 = Value::new_iname("1234");
2420 let inv2 = Value::new_iname("bc23f637-4439-4c07-b95d-eaed0d9e4b8b");
2421 let inv3 = Value::new_iname("hello@test.com");
2422 let inv4 = Value::new_iname("_bad");
2423 let inv5 = Value::new_iname("no spaces I'm sorry :(");
2424 let inv6 = Value::new_iname("bad=equals");
2425 let inv7 = Value::new_iname("bad,comma");
2426 let inv8 = Value::new_iname("123_456");
2427 let inv9 = Value::new_iname("🍿");
2428
2429 let val1 = Value::new_iname("William");
2430 let val2 = Value::new_iname("this_is_okay");
2431 let val3 = Value::new_iname("a123_456");
2432
2433 assert!(!inv1.validate());
2434 assert!(!inv2.validate());
2435 assert!(!inv3.validate());
2436 assert!(!inv4.validate());
2437 assert!(!inv5.validate());
2438 assert!(!inv6.validate());
2439 assert!(!inv7.validate());
2440 assert!(!inv8.validate());
2441 assert!(!inv9.validate());
2442
2443 assert!(val1.validate());
2444 assert!(val2.validate());
2445 assert!(val3.validate());
2446 }
2447
2448 #[test]
2449 fn test_value_nsuniqueid() {
2450 let val1 = Value::new_nsuniqueid_s("d765e707-48e111e6-8c9ebed8-f7926cc3");
2455 let val2 = Value::new_nsuniqueid_s("D765E707-48E111E6-8C9EBED8-F7926CC3");
2456 let inv1 = Value::new_nsuniqueid_s("d765e707-48e1-11e6-8c9e-bed8f7926cc3");
2457 let inv2 = Value::new_nsuniqueid_s("xxxx");
2458
2459 assert!(inv1.is_none());
2460 assert!(inv2.is_none());
2461 assert!(val1.unwrap().validate());
2462 assert!(val2.unwrap().validate());
2463 }
2464
2465 #[test]
2466 fn test_value_datetime() {
2467 let val1 = Value::new_datetime_s("2020-09-25T11:22:02+10:00").expect("Must be valid");
2469 assert!(val1.validate());
2470 let val2 = Value::new_datetime_s("2020-09-25T01:22:02+00:00").expect("Must be valid");
2471 assert!(val2.validate());
2472 let val3 = Value::new_datetime_s("2020-09-25 01:22:02+00:00").expect("Must be valid");
2474 assert!(val3.validate());
2475
2476 assert!(Value::new_datetime_s("2020-09-25T01:22:02").is_none());
2477 assert!(Value::new_datetime_s("2020-09-25").is_none());
2478 assert!(Value::new_datetime_s("2020-09-25T01:22:02+10").is_none());
2479
2480 let inv1 = Value::DateTime(
2482 OffsetDateTime::now_utc()
2483 .to_offset(time::UtcOffset::from_whole_seconds(36000).unwrap()),
2484 );
2485 assert!(!inv1.validate());
2486
2487 let val3 = Value::DateTime(OffsetDateTime::now_utc());
2488 assert!(val3.validate());
2489 }
2490
2491 #[test]
2492 fn test_value_email_address() {
2493 let val1 = Value::new_email_address_s("william@blackhats.net.au");
2495 let val2 = Value::new_email_address_s("alice@idm.example.com");
2496 let val3 = Value::new_email_address_s("test+mailbox@foo.com");
2497 let inv1 = Value::new_email_address_s("william");
2498 let inv2 = Value::new_email_address_s("test~uuid");
2499
2500 assert!(inv1.is_none());
2501 assert!(inv2.is_none());
2502 assert!(val1.unwrap().validate());
2503 assert!(val2.unwrap().validate());
2504 assert!(val3.unwrap().validate());
2505 }
2506
2507 #[test]
2508 fn test_value_url() {
2509 let val1 = Value::new_url_s("https://localhost:8000/search?q=text#hello");
2511 let val2 = Value::new_url_s("https://github.com/kanidm/kanidm");
2512 let val3 = Value::new_url_s("ldap://foo.com");
2513 let inv1 = Value::new_url_s("127.0.");
2514 let inv2 = Value::new_url_s("🤔");
2515
2516 assert!(inv1.is_none());
2517 assert!(inv2.is_none());
2518 assert!(val1.is_some());
2519 assert!(val2.is_some());
2520 assert!(val3.is_some());
2521 }
2522
2523 #[test]
2524 fn test_singleline() {
2525 assert!(Value::validate_singleline("no new lines"));
2526
2527 assert!(!Value::validate_singleline("contains a \n new line"));
2528 assert!(!Value::validate_singleline("contains a \r return feed"));
2529 assert!(!Value::validate_singleline("contains a \t tab char"));
2530 }
2531
2532 #[test]
2533 fn test_str_escapes() {
2534 assert!(Value::validate_str_escapes("safe str"));
2535 assert!(Value::validate_str_escapes("🙃 emoji are 👍"));
2536
2537 assert!(!Value::validate_str_escapes("naughty \x1b[31mred"));
2538 }
2539
2540 #[test]
2541 fn test_value_key_internal_status_order() {
2542 assert!(KeyStatus::Valid < KeyStatus::Retained);
2543 assert!(KeyStatus::Retained < KeyStatus::Revoked);
2544 }
2545
2546 #[test]
2547 fn test_value_session_state_order() {
2548 assert!(
2549 SessionState::RevokedAt(Cid::new_zero()) > SessionState::RevokedAt(Cid::new_count(1))
2550 );
2551 assert!(
2552 SessionState::RevokedAt(Cid::new_zero())
2553 > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2554 );
2555 assert!(
2556 SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH + Duration::from_secs(1))
2557 > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2558 );
2559 assert!(SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH) > SessionState::NeverExpires);
2560 }
2561}