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 pub fn is_iutf8(&self) -> bool {
654 matches!(self, PartialValue::Iutf8(_))
655 }
656
657 pub fn is_iname(&self) -> bool {
658 matches!(self, PartialValue::Iname(_))
659 }
660
661 pub fn new_bool(b: bool) -> Self {
662 PartialValue::Bool(b)
663 }
664
665 pub fn new_bools(s: &str) -> Option<Self> {
666 bool::from_str(s).map(PartialValue::Bool).ok()
667 }
668
669 pub fn is_bool(&self) -> bool {
670 matches!(self, PartialValue::Bool(_))
671 }
672
673 pub fn new_uuid_s(us: &str) -> Option<Self> {
674 Uuid::parse_str(us).map(PartialValue::Uuid).ok()
675 }
676
677 pub fn is_uuid(&self) -> bool {
678 matches!(self, PartialValue::Uuid(_))
679 }
680
681 pub fn new_refer_s(us: &str) -> Option<Self> {
682 match Uuid::parse_str(us) {
683 Ok(u) => Some(PartialValue::Refer(u)),
684 Err(_) => None,
685 }
686 }
687
688 pub fn is_refer(&self) -> bool {
689 matches!(self, PartialValue::Refer(_))
690 }
691
692 pub fn new_indexes(s: &str) -> Option<Self> {
693 IndexType::try_from(s).map(PartialValue::Index).ok()
694 }
695
696 pub fn is_index(&self) -> bool {
697 matches!(self, PartialValue::Index(_))
698 }
699
700 pub fn new_syntaxs(s: &str) -> Option<Self> {
701 SyntaxType::try_from(s).map(PartialValue::Syntax).ok()
702 }
703
704 pub fn is_syntax(&self) -> bool {
705 matches!(self, PartialValue::Syntax(_))
706 }
707
708 pub fn new_json_filter_s(s: &str) -> Option<Self> {
709 serde_json::from_str(s)
710 .map(PartialValue::JsonFilt)
711 .map_err(|e| {
712 trace!(?e, ?s);
713 })
714 .ok()
715 }
716
717 pub fn is_json_filter(&self) -> bool {
718 matches!(self, PartialValue::JsonFilt(_))
719 }
720
721 pub fn new_credential_tag(s: &str) -> Self {
722 PartialValue::Cred(s.to_lowercase())
723 }
724
725 pub fn is_credential(&self) -> bool {
726 matches!(self, PartialValue::Cred(_))
727 }
728
729 pub fn new_secret_str() -> Self {
730 PartialValue::SecretValue
731 }
732
733 pub fn is_secret_string(&self) -> bool {
734 matches!(self, PartialValue::SecretValue)
735 }
736
737 pub fn new_sshkey_tag(s: String) -> Self {
738 PartialValue::SshKey(s)
739 }
740
741 pub fn new_sshkey_tag_s(s: &str) -> Self {
742 PartialValue::SshKey(s.to_string())
743 }
744
745 pub fn is_sshkey(&self) -> bool {
746 matches!(self, PartialValue::SshKey(_))
747 }
748
749 pub fn new_spn_s(s: &str) -> Option<Self> {
750 SPN_RE.captures(s).and_then(|caps| {
751 let name = match caps.name(Attribute::Name.as_ref()) {
752 Some(v) => v.as_str().to_string(),
753 None => return None,
754 };
755 let realm = match caps.name("realm") {
756 Some(v) => v.as_str().to_string(),
757 None => return None,
758 };
759 Some(PartialValue::Spn(name, realm))
760 })
761 }
762
763 pub fn new_spn_nrs(n: &str, r: &str) -> Self {
764 PartialValue::Spn(n.to_string(), r.to_string())
765 }
766
767 pub fn is_spn(&self) -> bool {
768 matches!(self, PartialValue::Spn(_, _))
769 }
770
771 pub fn new_uint32(u: u32) -> Self {
772 PartialValue::Uint32(u)
773 }
774
775 pub fn new_uint32_str(u: &str) -> Option<Self> {
776 u.parse::<u32>().ok().map(PartialValue::Uint32)
777 }
778
779 pub fn is_uint32(&self) -> bool {
780 matches!(self, PartialValue::Uint32(_))
781 }
782
783 pub fn new_cid(c: Cid) -> Self {
784 PartialValue::Cid(c)
785 }
786
787 pub fn new_cid_s(_c: &str) -> Option<Self> {
788 None
789 }
790
791 pub fn is_cid(&self) -> bool {
792 matches!(self, PartialValue::Cid(_))
793 }
794
795 pub fn new_nsuniqueid_s(s: &str) -> Self {
796 PartialValue::Nsuniqueid(s.to_lowercase())
797 }
798
799 pub fn is_nsuniqueid(&self) -> bool {
800 matches!(self, PartialValue::Nsuniqueid(_))
801 }
802
803 pub fn new_datetime_epoch(ts: Duration) -> Self {
804 PartialValue::DateTime(OffsetDateTime::UNIX_EPOCH + ts)
805 }
806
807 pub fn new_datetime_s(s: &str) -> Option<Self> {
808 OffsetDateTime::parse(s, &Rfc3339)
809 .ok()
810 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
811 .map(PartialValue::DateTime)
812 }
813
814 pub fn is_datetime(&self) -> bool {
815 matches!(self, PartialValue::DateTime(_))
816 }
817
818 pub fn new_email_address_s(s: &str) -> Self {
819 PartialValue::EmailAddress(s.to_string())
820 }
821
822 pub fn is_email_address(&self) -> bool {
823 matches!(self, PartialValue::EmailAddress(_))
824 }
825
826 pub fn new_phonenumber_s(s: &str) -> Self {
827 PartialValue::PhoneNumber(s.to_string())
828 }
829
830 pub fn new_address(s: &str) -> Self {
831 PartialValue::Address(s.to_string())
832 }
833
834 pub fn new_url_s(s: &str) -> Option<Self> {
835 Url::parse(s).ok().map(PartialValue::Url)
836 }
837
838 pub fn is_url(&self) -> bool {
839 matches!(self, PartialValue::Url(_))
840 }
841
842 pub fn new_oauthscope(s: &str) -> Self {
843 PartialValue::OauthScope(s.to_string())
844 }
845
846 pub fn is_oauthscope(&self) -> bool {
847 matches!(self, PartialValue::OauthScope(_))
848 }
849
850 pub fn is_privatebinary(&self) -> bool {
868 matches!(self, PartialValue::PrivateBinary)
869 }
870
871 pub fn new_publicbinary_tag_s(s: &str) -> Self {
872 PartialValue::PublicBinary(s.to_string())
873 }
874
875 pub fn new_restrictedstring_s(s: &str) -> Self {
876 PartialValue::RestrictedString(s.to_string())
877 }
878
879 pub fn new_intenttoken_s(s: String) -> Option<Self> {
880 Some(PartialValue::IntentToken(s))
881 }
882
883 pub fn new_passkey_s(us: &str) -> Option<Self> {
884 Uuid::parse_str(us).map(PartialValue::Passkey).ok()
885 }
886
887 pub fn new_attested_passkey_s(us: &str) -> Option<Self> {
888 Uuid::parse_str(us).map(PartialValue::AttestedPasskey).ok()
889 }
890
891 pub fn new_hex_string_s(hexstr: &str) -> Option<Self> {
892 let hexstr_lower = hexstr.to_lowercase();
893 if HEXSTR_RE.is_match(&hexstr_lower) {
894 Some(PartialValue::HexString(hexstr_lower))
895 } else {
896 None
897 }
898 }
899
900 pub fn new_image(input: &str) -> Self {
901 PartialValue::Image(input.to_string())
902 }
903
904 pub fn to_str(&self) -> Option<&str> {
905 match self {
906 PartialValue::Utf8(s) => Some(s.as_str()),
907 PartialValue::Iutf8(s) => Some(s.as_str()),
908 PartialValue::Iname(s) => Some(s.as_str()),
909 _ => None,
910 }
911 }
912
913 pub fn to_url(&self) -> Option<&Url> {
914 match self {
915 PartialValue::Url(u) => Some(u),
916 _ => None,
917 }
918 }
919
920 pub fn get_idx_eq_key(&self) -> String {
921 match self {
922 PartialValue::Utf8(s)
923 | PartialValue::Iutf8(s)
924 | PartialValue::Iname(s)
925 | PartialValue::Nsuniqueid(s)
926 | PartialValue::EmailAddress(s)
927 | PartialValue::RestrictedString(s) => s.clone(),
928 PartialValue::Passkey(u)
929 | PartialValue::AttestedPasskey(u)
930 | PartialValue::Refer(u)
931 | PartialValue::Uuid(u) => u.as_hyphenated().to_string(),
932 PartialValue::Bool(b) => b.to_string(),
933 PartialValue::Syntax(syn) => syn.to_string(),
934 PartialValue::Index(it) => it.to_string(),
935 PartialValue::JsonFilt(s) =>
936 {
937 #[allow(clippy::expect_used)]
938 serde_json::to_string(s).expect("A json filter value was corrupted during run-time")
939 }
940 PartialValue::Cred(tag)
941 | PartialValue::PublicBinary(tag)
942 | PartialValue::SshKey(tag) => tag.to_string(),
943 PartialValue::SecretValue | PartialValue::PrivateBinary => "_".to_string(),
945 PartialValue::Spn(name, realm) => format!("{name}@{realm}"),
946 PartialValue::Uint32(u) => u.to_string(),
947 PartialValue::DateTime(odt) => {
948 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
949 #[allow(clippy::expect_used)]
950 odt.format(&Rfc3339)
951 .expect("Failed to format timestamp into RFC3339")
952 }
953 PartialValue::Url(u) => u.to_string(),
954 PartialValue::OauthScope(u) => u.to_string(),
955 PartialValue::Address(a) => a.to_string(),
956 PartialValue::PhoneNumber(a) => a.to_string(),
957 PartialValue::IntentToken(u) => u.clone(),
958 PartialValue::UiHint(u) => (*u as u16).to_string(),
959 PartialValue::Image(imagehash) => imagehash.to_owned(),
960 PartialValue::CredentialType(ct) => ct.to_string(),
961 PartialValue::Cid(_) => "_".to_string(),
963 PartialValue::OauthClaim(_, _) => "_".to_string(),
965 PartialValue::OauthClaimValue(_, _, _) => "_".to_string(),
966 PartialValue::HexString(hexstr) => hexstr.to_string(),
967 }
968 }
969
970 pub fn get_idx_sub_key(&self) -> Option<String> {
971 match self {
972 PartialValue::Utf8(s)
973 | PartialValue::Iutf8(s)
974 | PartialValue::Iname(s)
975 | PartialValue::EmailAddress(s)
977 | PartialValue::RestrictedString(s) => Some(s.to_lowercase()),
978
979 PartialValue::Cred(tag)
980 | PartialValue::PublicBinary(tag)
981 | PartialValue::SshKey(tag) => Some(tag.to_lowercase()),
982
983 _ => None,
985 }
986 }
987}
988
989#[derive(Clone, Copy, Debug, PartialEq, Eq)]
990pub enum ApiTokenScope {
991 ReadOnly,
992 ReadWrite,
993 Synchronise,
994}
995
996impl fmt::Display for ApiTokenScope {
997 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
998 match self {
999 ApiTokenScope::ReadOnly => write!(f, "read_only"),
1000 ApiTokenScope::ReadWrite => write!(f, "read_write"),
1001 ApiTokenScope::Synchronise => write!(f, "synchronise"),
1002 }
1003 }
1004}
1005
1006impl TryInto<ApiTokenPurpose> for ApiTokenScope {
1007 type Error = OperationError;
1008
1009 fn try_into(self: ApiTokenScope) -> Result<ApiTokenPurpose, OperationError> {
1010 match self {
1011 ApiTokenScope::ReadOnly => Ok(ApiTokenPurpose::ReadOnly),
1012 ApiTokenScope::ReadWrite => Ok(ApiTokenPurpose::ReadWrite),
1013 ApiTokenScope::Synchronise => Ok(ApiTokenPurpose::Synchronise),
1014 }
1015 }
1016}
1017
1018#[derive(Clone, Debug, PartialEq, Eq)]
1019pub struct ApiToken {
1020 pub label: String,
1021 pub expiry: Option<OffsetDateTime>,
1022 pub issued_at: OffsetDateTime,
1023 pub issued_by: IdentityId,
1024 pub scope: ApiTokenScope,
1025}
1026
1027#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1028pub enum SessionScope {
1029 ReadOnly,
1030 ReadWrite,
1031 PrivilegeCapable,
1032 Synchronise,
1034}
1035
1036impl fmt::Display for SessionScope {
1037 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1038 match self {
1039 SessionScope::ReadOnly => write!(f, "read_only"),
1040 SessionScope::ReadWrite => write!(f, "read_write"),
1041 SessionScope::PrivilegeCapable => write!(f, "privilege_capable"),
1042 SessionScope::Synchronise => write!(f, "synchronise"),
1043 }
1044 }
1045}
1046
1047impl TryInto<UatPurposeStatus> for SessionScope {
1048 type Error = OperationError;
1049
1050 fn try_into(self: SessionScope) -> Result<UatPurposeStatus, OperationError> {
1051 match self {
1052 SessionScope::ReadOnly => Ok(UatPurposeStatus::ReadOnly),
1053 SessionScope::ReadWrite => Ok(UatPurposeStatus::ReadWrite),
1054 SessionScope::PrivilegeCapable => Ok(UatPurposeStatus::PrivilegeCapable),
1055 SessionScope::Synchronise => Err(OperationError::InvalidEntryState),
1056 }
1057 }
1058}
1059
1060#[derive(Clone, Debug, PartialEq, Eq)]
1061pub enum SessionState {
1062 RevokedAt(Cid),
1066 ExpiresAt(OffsetDateTime),
1067 NeverExpires,
1068}
1069
1070impl PartialOrd for SessionState {
1071 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1072 Some(self.cmp(other))
1073 }
1074}
1075
1076impl Ord for SessionState {
1077 fn cmp(&self, other: &Self) -> Ordering {
1078 match (self, other) {
1081 (SessionState::RevokedAt(c_self), SessionState::RevokedAt(c_other)) => {
1083 c_other.cmp(c_self)
1086 }
1087 (SessionState::RevokedAt(_), _) => Ordering::Greater,
1088 (_, SessionState::RevokedAt(_)) => Ordering::Less,
1089 (SessionState::ExpiresAt(e_self), SessionState::ExpiresAt(e_other)) => {
1091 e_self.cmp(e_other)
1094 }
1095 (SessionState::ExpiresAt(_), _) => Ordering::Greater,
1096 (_, SessionState::ExpiresAt(_)) => Ordering::Less,
1097 (SessionState::NeverExpires, SessionState::NeverExpires) => Ordering::Equal,
1099 }
1100 }
1101}
1102
1103#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
1104pub enum AuthType {
1105 Anonymous,
1106 Password,
1107 GeneratedPassword,
1108 PasswordTotp,
1109 PasswordBackupCode,
1110 PasswordSecurityKey,
1111 Passkey,
1112 AttestedPasskey,
1113}
1114
1115impl fmt::Display for AuthType {
1116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1117 match self {
1118 AuthType::Anonymous => write!(f, "anonymous"),
1119 AuthType::Password => write!(f, "password"),
1120 AuthType::GeneratedPassword => write!(f, "generatedpassword"),
1121 AuthType::PasswordTotp => write!(f, "passwordtotp"),
1122 AuthType::PasswordBackupCode => write!(f, "passwordbackupcode"),
1123 AuthType::PasswordSecurityKey => write!(f, "passwordsecuritykey"),
1124 AuthType::Passkey => write!(f, "passkey"),
1125 AuthType::AttestedPasskey => write!(f, "attested_passkey"),
1126 }
1127 }
1128}
1129
1130#[derive(Clone, PartialEq, Eq)]
1131pub struct Session {
1132 pub label: String,
1133 pub state: SessionState,
1135 pub issued_at: OffsetDateTime,
1136 pub issued_by: IdentityId,
1137 pub cred_id: Uuid,
1138 pub scope: SessionScope,
1139 pub type_: AuthType,
1140}
1141
1142impl fmt::Debug for Session {
1143 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1144 let issuer = match self.issued_by {
1145 IdentityId::User(u) => format!("User - {}", uuid_to_proto_string(u)),
1146 IdentityId::Synch(u) => format!("Synch - {}", uuid_to_proto_string(u)),
1147 IdentityId::Internal => "Internal".to_string(),
1148 };
1149 let expiry = match self.state {
1150 SessionState::ExpiresAt(e) => e.to_string(),
1151 SessionState::NeverExpires => "never".to_string(),
1152 SessionState::RevokedAt(_) => "revoked".to_string(),
1153 };
1154 write!(
1155 f,
1156 "state: {}, issued at: {}, issued by: {}, credential id: {}, scope: {:?}",
1157 expiry, self.issued_at, issuer, self.cred_id, self.scope
1158 )
1159 }
1160}
1161
1162#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
1163pub enum OauthClaimMapJoin {
1164 CommaSeparatedValue,
1165 SpaceSeparatedValue,
1166 #[default]
1167 JsonArray,
1168}
1169
1170impl From<OauthClaimMapJoin> for ScimOauth2ClaimMapJoinChar {
1171 fn from(value: OauthClaimMapJoin) -> Self {
1172 match value {
1173 OauthClaimMapJoin::CommaSeparatedValue => {
1174 ScimOauth2ClaimMapJoinChar::CommaSeparatedValue
1175 }
1176 OauthClaimMapJoin::SpaceSeparatedValue => {
1177 ScimOauth2ClaimMapJoinChar::SpaceSeparatedValue
1178 }
1179 OauthClaimMapJoin::JsonArray => ScimOauth2ClaimMapJoinChar::JsonArray,
1180 }
1181 }
1182}
1183
1184impl From<ScimOauth2ClaimMapJoinChar> for OauthClaimMapJoin {
1185 fn from(value: ScimOauth2ClaimMapJoinChar) -> Self {
1186 match value {
1187 ScimOauth2ClaimMapJoinChar::CommaSeparatedValue => {
1188 OauthClaimMapJoin::CommaSeparatedValue
1189 }
1190 ScimOauth2ClaimMapJoinChar::SpaceSeparatedValue => {
1191 OauthClaimMapJoin::SpaceSeparatedValue
1192 }
1193 ScimOauth2ClaimMapJoinChar::JsonArray => OauthClaimMapJoin::JsonArray,
1194 }
1195 }
1196}
1197
1198impl OauthClaimMapJoin {
1199 pub(crate) fn to_str(self) -> &'static str {
1200 match self {
1201 OauthClaimMapJoin::CommaSeparatedValue => ",",
1202 OauthClaimMapJoin::SpaceSeparatedValue => " ",
1203 OauthClaimMapJoin::JsonArray => ";",
1205 }
1206 }
1207}
1208
1209impl From<DbValueOauthClaimMapJoinV1> for OauthClaimMapJoin {
1210 fn from(value: DbValueOauthClaimMapJoinV1) -> OauthClaimMapJoin {
1211 match value {
1212 DbValueOauthClaimMapJoinV1::CommaSeparatedValue => {
1213 OauthClaimMapJoin::CommaSeparatedValue
1214 }
1215 DbValueOauthClaimMapJoinV1::SpaceSeparatedValue => {
1216 OauthClaimMapJoin::SpaceSeparatedValue
1217 }
1218 DbValueOauthClaimMapJoinV1::JsonArray => OauthClaimMapJoin::JsonArray,
1219 }
1220 }
1221}
1222
1223impl From<OauthClaimMapJoin> for DbValueOauthClaimMapJoinV1 {
1224 fn from(value: OauthClaimMapJoin) -> DbValueOauthClaimMapJoinV1 {
1225 match value {
1226 OauthClaimMapJoin::CommaSeparatedValue => {
1227 DbValueOauthClaimMapJoinV1::CommaSeparatedValue
1228 }
1229 OauthClaimMapJoin::SpaceSeparatedValue => {
1230 DbValueOauthClaimMapJoinV1::SpaceSeparatedValue
1231 }
1232 OauthClaimMapJoin::JsonArray => DbValueOauthClaimMapJoinV1::JsonArray,
1233 }
1234 }
1235}
1236
1237#[derive(Clone, Debug, PartialEq, Eq)]
1238pub struct Oauth2Session {
1239 pub parent: Option<Uuid>,
1240 pub state: SessionState,
1241 pub issued_at: OffsetDateTime,
1242 pub rs_uuid: Uuid,
1243}
1244
1245#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1246pub enum KeyUsage {
1247 JwsEs256,
1248 JwsRs256,
1249 JweA128GCM,
1250}
1251
1252impl fmt::Display for KeyUsage {
1253 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1254 write!(
1255 f,
1256 "{}",
1257 match self {
1258 KeyUsage::JwsEs256 => "jws_es256",
1259 KeyUsage::JwsRs256 => "jws_rs256",
1260 KeyUsage::JweA128GCM => "jwe_a128gcm",
1261 }
1262 )
1263 }
1264}
1265
1266#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1267pub enum KeyStatus {
1268 Valid,
1269 Retained,
1270 Revoked,
1271}
1272
1273impl fmt::Display for KeyStatus {
1274 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1275 write!(
1276 f,
1277 "{}",
1278 match self {
1279 KeyStatus::Valid => "valid",
1280 KeyStatus::Retained => "retained",
1281 KeyStatus::Revoked => "revoked",
1282 }
1283 )
1284 }
1285}
1286
1287#[derive(Clone, Debug)]
1294pub enum Value {
1295 Utf8(String),
1296 Iutf8(String),
1298 Iname(String),
1300 Uuid(Uuid),
1301 Bool(bool),
1302 Syntax(SyntaxType),
1303 Index(IndexType),
1304 Refer(Uuid),
1305 JsonFilt(ProtoFilter),
1306 Cred(String, Credential),
1307 SshKey(String, SshPublicKey),
1308 SecretValue(String),
1309 Spn(String, String),
1310 Uint32(u32),
1311 Cid(Cid),
1312 Nsuniqueid(String),
1313 DateTime(OffsetDateTime),
1314 EmailAddress(String, bool),
1315 PhoneNumber(String, bool),
1316 Address(Address),
1317 Url(Url),
1318 OauthScope(String),
1319 OauthScopeMap(Uuid, BTreeSet<String>),
1320 PrivateBinary(Vec<u8>),
1321 PublicBinary(String, Vec<u8>),
1322 RestrictedString(String),
1323 IntentToken(String, IntentTokenState),
1324 Passkey(Uuid, String, PasskeyV4),
1325 AttestedPasskey(Uuid, String, AttestedPasskeyV4),
1326
1327 Session(Uuid, Session),
1328 ApiToken(Uuid, ApiToken),
1329 Oauth2Session(Uuid, Oauth2Session),
1330
1331 JwsKeyEs256(JwsEs256Signer),
1332 JwsKeyRs256(JwsRs256Signer),
1333 UiHint(UiHint),
1334
1335 TotpSecret(String, Totp),
1336 AuditLogString(Cid, String),
1337 EcKeyPrivate(EcKey<Private>),
1338
1339 Image(ImageValue),
1340 CredentialType(CredentialType),
1341 WebauthnAttestationCaList(AttestationCaList),
1342
1343 OauthClaimValue(String, Uuid, BTreeSet<String>),
1344 OauthClaimMap(String, OauthClaimMapJoin),
1345
1346 KeyInternal {
1347 id: KeyId,
1348 usage: KeyUsage,
1349 valid_from: u64,
1350 status: KeyStatus,
1351 status_cid: Cid,
1352 der: Vec<u8>,
1353 },
1354
1355 HexString(String),
1356
1357 Certificate(Box<Certificate>),
1358 ApplicationPassword(ApplicationPassword),
1359}
1360
1361impl PartialEq for Value {
1362 fn eq(&self, other: &Self) -> bool {
1363 match (self, other) {
1364 (Value::Utf8(a), Value::Utf8(b))
1365 | (Value::Iutf8(a), Value::Iutf8(b))
1366 | (Value::Iname(a), Value::Iname(b))
1367 | (Value::Cred(a, _), Value::Cred(b, _))
1368 | (Value::SshKey(a, _), Value::SshKey(b, _))
1369 | (Value::Nsuniqueid(a), Value::Nsuniqueid(b))
1370 | (Value::EmailAddress(a, _), Value::EmailAddress(b, _))
1371 | (Value::PhoneNumber(a, _), Value::PhoneNumber(b, _))
1372 | (Value::OauthScope(a), Value::OauthScope(b))
1373 | (Value::PublicBinary(a, _), Value::PublicBinary(b, _))
1374 | (Value::RestrictedString(a), Value::RestrictedString(b)) => a.eq(b),
1375 (Value::Spn(a, c), Value::Spn(b, d)) => a.eq(b) && c.eq(d),
1377 (Value::Uuid(a), Value::Uuid(b)) | (Value::Refer(a), Value::Refer(b)) => a.eq(b),
1379 (Value::Bool(a), Value::Bool(b)) => a.eq(b),
1381 (Value::Syntax(a), Value::Syntax(b)) => a.eq(b),
1383 (Value::Index(a), Value::Index(b)) => a.eq(b),
1385 (Value::JsonFilt(a), Value::JsonFilt(b)) => a.eq(b),
1387 (Value::Uint32(a), Value::Uint32(b)) => a.eq(b),
1389 (Value::Cid(a), Value::Cid(b)) => a.eq(b),
1391 (Value::DateTime(a), Value::DateTime(b)) => a.eq(b),
1393 (Value::Url(a), Value::Url(b)) => a.eq(b),
1395 (Value::OauthScopeMap(a, c), Value::OauthScopeMap(b, d)) => a.eq(b) && c.eq(d),
1397
1398 (Value::Image(image1), Value::Image(image2)) => {
1399 image1.hash_imagevalue().eq(&image2.hash_imagevalue())
1400 }
1401 (Value::Address(_), Value::Address(_))
1402 | (Value::PrivateBinary(_), Value::PrivateBinary(_))
1403 | (Value::SecretValue(_), Value::SecretValue(_)) => false,
1404 (Value::Iutf8(_), Value::Iname(_)) | (Value::Iname(_), Value::Iutf8(_)) => false,
1406 (Value::Uuid(_), Value::Iname(_))
1408 | (Value::Iname(_), Value::Spn(_, _))
1409 | (Value::Uuid(_), Value::Spn(_, _)) => false,
1410 (l, r) => {
1411 error!(?l, ?r, "mismatched value types");
1412 debug_assert!(false);
1413 false
1414 }
1415 }
1416 }
1417}
1418
1419impl Eq for Value {}
1420
1421impl From<bool> for Value {
1422 fn from(b: bool) -> Self {
1423 Value::Bool(b)
1424 }
1425}
1426
1427impl From<&bool> for Value {
1428 fn from(b: &bool) -> Self {
1429 Value::Bool(*b)
1430 }
1431}
1432
1433impl From<SyntaxType> for Value {
1434 fn from(s: SyntaxType) -> Self {
1435 Value::Syntax(s)
1436 }
1437}
1438
1439impl From<IndexType> for Value {
1440 fn from(i: IndexType) -> Self {
1441 Value::Index(i)
1442 }
1443}
1444
1445impl From<ProtoFilter> for Value {
1446 fn from(i: ProtoFilter) -> Self {
1447 Value::JsonFilt(i)
1448 }
1449}
1450
1451impl From<OffsetDateTime> for Value {
1452 fn from(i: OffsetDateTime) -> Self {
1453 Value::DateTime(i)
1454 }
1455}
1456
1457impl From<u32> for Value {
1458 fn from(i: u32) -> Self {
1459 Value::Uint32(i)
1460 }
1461}
1462
1463impl From<Url> for Value {
1464 fn from(i: Url) -> Self {
1465 Value::Url(i)
1466 }
1467}
1468
1469#[cfg(test)]
1472impl From<&str> for Value {
1473 fn from(s: &str) -> Self {
1474 match Uuid::parse_str(s) {
1476 Ok(u) => Value::Uuid(u),
1477 Err(_) => Value::Utf8(s.to_string()),
1478 }
1479 }
1480}
1481
1482#[cfg(test)]
1483impl From<&Uuid> for Value {
1484 fn from(u: &Uuid) -> Self {
1485 Value::Uuid(*u)
1486 }
1487}
1488
1489#[cfg(test)]
1490impl From<Uuid> for Value {
1491 fn from(u: Uuid) -> Self {
1492 Value::Uuid(u)
1493 }
1494}
1495
1496impl From<DbIdentSpn> for Value {
1497 fn from(dis: DbIdentSpn) -> Self {
1498 match dis {
1499 DbIdentSpn::Spn(n, r) => Value::Spn(n, r),
1500 DbIdentSpn::Iname(n) => Value::Iname(n),
1501 DbIdentSpn::Uuid(u) => Value::Uuid(u),
1502 }
1503 }
1504}
1505
1506impl Value {
1507 pub fn new_utf8(s: String) -> Self {
1509 Value::Utf8(s)
1510 }
1511
1512 pub fn new_utf8s(s: &str) -> Self {
1513 Value::Utf8(s.to_string())
1514 }
1515
1516 pub fn is_utf8(&self) -> bool {
1517 matches!(self, Value::Utf8(_))
1518 }
1519
1520 pub fn new_iutf8(s: &str) -> Self {
1521 Value::Iutf8(s.to_lowercase())
1522 }
1523
1524 pub fn is_iutf8(&self) -> bool {
1525 matches!(self, Value::Iutf8(_))
1526 }
1527
1528 pub fn new_attr(s: &str) -> Self {
1529 Value::Iutf8(s.to_lowercase())
1530 }
1531
1532 pub fn is_insensitive_utf8(&self) -> bool {
1533 matches!(self, Value::Iutf8(_))
1534 }
1535
1536 pub fn new_iname(s: &str) -> Self {
1537 Value::Iname(s.to_lowercase())
1538 }
1539
1540 pub fn is_iname(&self) -> bool {
1541 matches!(self, Value::Iname(_))
1542 }
1543
1544 pub fn new_uuid_s(s: &str) -> Option<Self> {
1545 Uuid::parse_str(s).map(Value::Uuid).ok()
1546 }
1547
1548 pub fn is_uuid(&self) -> bool {
1550 matches!(self, Value::Uuid(_))
1551 }
1552
1553 pub fn new_bool(b: bool) -> Self {
1554 Value::Bool(b)
1555 }
1556
1557 pub fn new_bools(s: &str) -> Option<Self> {
1558 bool::from_str(s).map(Value::Bool).ok()
1559 }
1560
1561 pub fn new_audit_log_string(e: (Cid, String)) -> Option<Self> {
1562 Some(Value::AuditLogString(e.0, e.1))
1563 }
1564
1565 #[inline]
1566 pub fn is_bool(&self) -> bool {
1567 matches!(self, Value::Bool(_))
1568 }
1569
1570 pub fn new_syntaxs(s: &str) -> Option<Self> {
1571 SyntaxType::try_from(s).map(Value::Syntax).ok()
1572 }
1573
1574 pub fn new_syntax(s: SyntaxType) -> Self {
1575 Value::Syntax(s)
1576 }
1577
1578 pub fn is_syntax(&self) -> bool {
1579 matches!(self, Value::Syntax(_))
1580 }
1581
1582 pub fn new_indexes(s: &str) -> Option<Self> {
1583 IndexType::try_from(s).map(Value::Index).ok()
1584 }
1585
1586 pub fn new_index(i: IndexType) -> Self {
1587 Value::Index(i)
1588 }
1589
1590 pub fn is_index(&self) -> bool {
1591 matches!(self, Value::Index(_))
1592 }
1593
1594 pub fn new_refer_s(us: &str) -> Option<Self> {
1595 Uuid::parse_str(us).map(Value::Refer).ok()
1596 }
1597
1598 pub fn is_refer(&self) -> bool {
1599 matches!(self, Value::Refer(_))
1600 }
1601
1602 pub fn new_json_filter_s(s: &str) -> Option<Self> {
1603 serde_json::from_str(s).map(Value::JsonFilt).ok()
1604 }
1605
1606 pub fn new_json_filter(f: ProtoFilter) -> Self {
1607 Value::JsonFilt(f)
1608 }
1609
1610 pub fn is_json_filter(&self) -> bool {
1611 matches!(self, Value::JsonFilt(_))
1612 }
1613
1614 pub fn as_json_filter(&self) -> Option<&ProtoFilter> {
1615 match &self {
1616 Value::JsonFilt(f) => Some(f),
1617 _ => None,
1618 }
1619 }
1620
1621 pub fn new_credential(tag: &str, cred: Credential) -> Self {
1622 Value::Cred(tag.to_string(), cred)
1623 }
1624
1625 pub fn is_credential(&self) -> bool {
1626 matches!(&self, Value::Cred(_, _))
1627 }
1628
1629 pub fn to_credential(&self) -> Option<&Credential> {
1630 match &self {
1631 Value::Cred(_, cred) => Some(cred),
1632 _ => None,
1633 }
1634 }
1635
1636 pub fn new_hex_string_s(hexstr: &str) -> Option<Self> {
1637 let hexstr_lower = hexstr.to_lowercase();
1638 if HEXSTR_RE.is_match(&hexstr_lower) {
1639 Some(Value::HexString(hexstr_lower))
1640 } else {
1641 None
1642 }
1643 }
1644
1645 pub fn new_certificate_s(cert_str: &str) -> Option<Self> {
1646 Certificate::from_pem(cert_str)
1647 .map(Box::new)
1648 .map(Value::Certificate)
1649 .ok()
1650 }
1651
1652 pub fn new_image(input: &str) -> Result<Self, OperationError> {
1654 serde_json::from_str::<ImageValue>(input)
1655 .map(Value::Image)
1656 .map_err(|_e| OperationError::InvalidValueState)
1657 }
1658
1659 pub fn new_secret_str(cleartext: &str) -> Self {
1660 Value::SecretValue(cleartext.to_string())
1661 }
1662
1663 pub fn is_secret_string(&self) -> bool {
1664 matches!(&self, Value::SecretValue(_))
1665 }
1666
1667 pub fn get_secret_str(&self) -> Option<&str> {
1668 match &self {
1669 Value::SecretValue(c) => Some(c.as_str()),
1670 _ => None,
1671 }
1672 }
1673
1674 pub fn new_sshkey_str(tag: &str, key: &str) -> Result<Self, OperationError> {
1675 SshPublicKey::from_string(key)
1676 .map(|pk| Value::SshKey(tag.to_string(), pk))
1677 .map_err(|err| {
1678 error!(?err, "value sshkey failed to parse string");
1679 OperationError::VL0001ValueSshPublicKeyString
1680 })
1681 }
1682
1683 pub fn is_sshkey(&self) -> bool {
1684 matches!(&self, Value::SshKey(_, _))
1685 }
1686
1687 pub fn get_sshkey(&self) -> Option<String> {
1688 match &self {
1689 Value::SshKey(_, key) => Some(key.to_string()),
1690 _ => None,
1691 }
1692 }
1693
1694 pub fn new_spn_parse(s: &str) -> Option<Self> {
1695 SPN_RE.captures(s).and_then(|caps| {
1696 let name = match caps.name(Attribute::Name.as_ref()) {
1697 Some(v) => v.as_str().to_string(),
1698 None => return None,
1699 };
1700 let realm = match caps.name("realm") {
1701 Some(v) => v.as_str().to_string(),
1702 None => return None,
1703 };
1704 Some(Value::Spn(name, realm))
1705 })
1706 }
1707
1708 pub fn new_spn_str(n: &str, r: &str) -> Self {
1709 Value::Spn(n.to_string(), r.to_string())
1710 }
1711
1712 pub fn is_spn(&self) -> bool {
1713 matches!(&self, Value::Spn(_, _))
1714 }
1715
1716 pub fn new_uint32(u: u32) -> Self {
1717 Value::Uint32(u)
1718 }
1719
1720 pub fn new_uint32_str(u: &str) -> Option<Self> {
1721 u.parse::<u32>().ok().map(Value::Uint32)
1722 }
1723
1724 pub fn is_uint32(&self) -> bool {
1725 matches!(&self, Value::Uint32(_))
1726 }
1727
1728 pub fn new_cid(c: Cid) -> Self {
1729 Value::Cid(c)
1730 }
1731
1732 pub fn is_cid(&self) -> bool {
1733 matches!(&self, Value::Cid(_))
1734 }
1735
1736 pub fn new_nsuniqueid_s(s: &str) -> Option<Self> {
1737 if NSUNIQUEID_RE.is_match(s) {
1738 Some(Value::Nsuniqueid(s.to_lowercase()))
1739 } else {
1740 None
1741 }
1742 }
1743
1744 pub fn is_nsuniqueid(&self) -> bool {
1745 matches!(&self, Value::Nsuniqueid(_))
1746 }
1747
1748 pub fn new_datetime_epoch(ts: Duration) -> Self {
1749 Value::DateTime(OffsetDateTime::UNIX_EPOCH + ts)
1750 }
1751
1752 pub fn new_datetime_s(s: &str) -> Option<Self> {
1753 OffsetDateTime::parse(s, &Rfc3339)
1754 .ok()
1755 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
1756 .map(Value::DateTime)
1757 }
1758
1759 pub fn new_datetime(dt: OffsetDateTime) -> Self {
1760 Value::DateTime(dt)
1761 }
1762
1763 pub fn to_datetime(&self) -> Option<OffsetDateTime> {
1764 match &self {
1765 Value::DateTime(odt) => {
1766 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
1767 Some(*odt)
1768 }
1769 _ => None,
1770 }
1771 }
1772
1773 pub fn is_datetime(&self) -> bool {
1774 matches!(&self, Value::DateTime(_))
1775 }
1776
1777 pub fn new_email_address_s(s: &str) -> Option<Self> {
1778 if VALIDATE_EMAIL_RE.is_match(s) {
1779 Some(Value::EmailAddress(s.to_string(), false))
1780 } else {
1781 None
1782 }
1783 }
1784
1785 pub fn new_email_address_primary_s(s: &str) -> Option<Self> {
1786 if VALIDATE_EMAIL_RE.is_match(s) {
1787 Some(Value::EmailAddress(s.to_string(), true))
1788 } else {
1789 None
1790 }
1791 }
1792
1793 pub fn is_email_address(&self) -> bool {
1794 matches!(&self, Value::EmailAddress(_, _))
1795 }
1796
1797 pub fn new_phonenumber_s(s: &str) -> Self {
1798 Value::PhoneNumber(s.to_string(), false)
1799 }
1800
1801 pub fn new_address(a: Address) -> Self {
1802 Value::Address(a)
1803 }
1804
1805 pub fn new_url_s(s: &str) -> Option<Self> {
1806 Url::parse(s).ok().map(Value::Url)
1807 }
1808
1809 pub fn new_url(u: Url) -> Self {
1810 Value::Url(u)
1811 }
1812
1813 pub fn is_url(&self) -> bool {
1814 matches!(&self, Value::Url(_))
1815 }
1816
1817 pub fn new_oauthscope(s: &str) -> Option<Self> {
1818 if OAUTHSCOPE_RE.is_match(s) {
1819 Some(Value::OauthScope(s.to_string()))
1820 } else {
1821 None
1822 }
1823 }
1824
1825 pub fn is_oauthscope(&self) -> bool {
1826 matches!(&self, Value::OauthScope(_))
1827 }
1828
1829 pub fn new_oauthscopemap(u: Uuid, m: BTreeSet<String>) -> Option<Self> {
1830 if m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1831 Some(Value::OauthScopeMap(u, m))
1832 } else {
1833 None
1834 }
1835 }
1836
1837 pub fn new_oauthclaimmap(n: String, u: Uuid, c: BTreeSet<String>) -> Option<Self> {
1838 if OAUTHSCOPE_RE.is_match(&n) && c.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1839 Some(Value::OauthClaimValue(n, u, c))
1840 } else {
1841 None
1842 }
1843 }
1844
1845 pub fn is_oauthscopemap(&self) -> bool {
1846 matches!(&self, Value::OauthScopeMap(_, _))
1847 }
1848
1849 #[cfg(test)]
1850 pub fn new_privatebinary_base64(der: &str) -> Self {
1851 let der = general_purpose::STANDARD
1852 .decode(der)
1853 .expect("Failed to decode base64 der value");
1854 Value::PrivateBinary(der)
1855 }
1856
1857 pub fn new_privatebinary(der: &[u8]) -> Self {
1858 Value::PrivateBinary(der.to_owned())
1859 }
1860
1861 pub fn to_privatebinary(&self) -> Option<&Vec<u8>> {
1862 match &self {
1863 Value::PrivateBinary(c) => Some(c),
1864 _ => None,
1865 }
1866 }
1867
1868 pub fn is_privatebinary(&self) -> bool {
1869 matches!(&self, Value::PrivateBinary(_))
1870 }
1871
1872 pub fn new_publicbinary(tag: String, der: Vec<u8>) -> Self {
1873 Value::PublicBinary(tag, der)
1874 }
1875
1876 pub fn new_restrictedstring(s: String) -> Self {
1877 Value::RestrictedString(s)
1878 }
1879
1880 pub fn new_webauthn_attestation_ca_list(s: &str) -> Option<Self> {
1881 serde_json::from_str(s)
1882 .map(Value::WebauthnAttestationCaList)
1883 .map_err(|err| {
1884 debug!(?err, ?s);
1885 })
1886 .ok()
1887 }
1888
1889 #[allow(clippy::unreachable)]
1890 pub(crate) fn to_db_ident_spn(&self) -> DbIdentSpn {
1891 match &self {
1893 Value::Spn(n, r) => DbIdentSpn::Spn(n.clone(), r.clone()),
1894 Value::Iname(s) => DbIdentSpn::Iname(s.clone()),
1895 Value::Uuid(u) => DbIdentSpn::Uuid(*u),
1896 v => unreachable!("-> {:?}", v),
1900 }
1901 }
1902
1903 pub fn to_str(&self) -> Option<&str> {
1904 match &self {
1905 Value::Utf8(s) => Some(s.as_str()),
1906 Value::Iutf8(s) => Some(s.as_str()),
1907 Value::Iname(s) => Some(s.as_str()),
1908 _ => None,
1909 }
1910 }
1911
1912 pub fn to_url(&self) -> Option<&Url> {
1913 match &self {
1914 Value::Url(u) => Some(u),
1915 _ => None,
1916 }
1917 }
1918
1919 pub fn as_string(&self) -> Option<&String> {
1920 match &self {
1921 Value::Utf8(s) => Some(s),
1922 Value::Iutf8(s) => Some(s),
1923 Value::Iname(s) => Some(s),
1924 _ => None,
1925 }
1926 }
1927
1928 pub fn to_ref_uuid(&self) -> Option<Uuid> {
1931 match &self {
1932 Value::Refer(u) => Some(*u),
1933 Value::OauthScopeMap(u, _) => Some(*u),
1934 Value::Oauth2Session(_, m) => Some(m.rs_uuid),
1936 _ => None,
1937 }
1938 }
1939
1940 pub fn to_uuid(&self) -> Option<&Uuid> {
1941 match &self {
1942 Value::Uuid(u) => Some(u),
1943 _ => None,
1944 }
1945 }
1946
1947 pub fn to_indextype(&self) -> Option<&IndexType> {
1948 match &self {
1949 Value::Index(i) => Some(i),
1950 _ => None,
1951 }
1952 }
1953
1954 pub fn to_syntaxtype(&self) -> Option<&SyntaxType> {
1955 match &self {
1956 Value::Syntax(s) => Some(s),
1957 _ => None,
1958 }
1959 }
1960
1961 pub fn to_bool(&self) -> Option<bool> {
1962 match self {
1963 Value::Bool(v) => Some(*v),
1965 _ => None,
1966 }
1967 }
1968
1969 pub fn to_uint32(&self) -> Option<u32> {
1970 match &self {
1971 Value::Uint32(v) => Some(*v),
1972 _ => None,
1973 }
1974 }
1975
1976 pub fn to_utf8(self) -> Option<String> {
1977 match self {
1978 Value::Utf8(s) => Some(s),
1979 _ => None,
1980 }
1981 }
1982
1983 pub fn to_iutf8(self) -> Option<String> {
1984 match self {
1985 Value::Iutf8(s) => Some(s),
1986 _ => None,
1987 }
1988 }
1989
1990 pub fn to_iname(self) -> Option<String> {
1991 match self {
1992 Value::Iname(s) => Some(s),
1993 _ => None,
1994 }
1995 }
1996
1997 pub fn to_jsonfilt(self) -> Option<ProtoFilter> {
1998 match self {
1999 Value::JsonFilt(f) => Some(f),
2000 _ => None,
2001 }
2002 }
2003
2004 pub fn to_cred(self) -> Option<(String, Credential)> {
2005 match self {
2006 Value::Cred(tag, c) => Some((tag, c)),
2007 _ => None,
2008 }
2009 }
2010
2011 pub fn to_spn(self) -> Option<(String, String)> {
2021 match self {
2022 Value::Spn(n, d) => Some((n, d)),
2023 _ => None,
2024 }
2025 }
2026
2027 pub fn to_cid(self) -> Option<Cid> {
2028 match self {
2029 Value::Cid(s) => Some(s),
2030 _ => None,
2031 }
2032 }
2033
2034 pub fn to_nsuniqueid(self) -> Option<String> {
2035 match self {
2036 Value::Nsuniqueid(s) => Some(s),
2037 _ => None,
2038 }
2039 }
2040
2041 pub fn to_emailaddress(self) -> Option<String> {
2042 match self {
2043 Value::EmailAddress(s, _) => Some(s),
2044 _ => None,
2045 }
2046 }
2047
2048 pub fn to_oauthscope(self) -> Option<String> {
2049 match self {
2050 Value::OauthScope(s) => Some(s),
2051 _ => None,
2052 }
2053 }
2054
2055 pub fn to_oauthscopemap(self) -> Option<(Uuid, BTreeSet<String>)> {
2056 match self {
2057 Value::OauthScopeMap(u, m) => Some((u, m)),
2058 _ => None,
2059 }
2060 }
2061
2062 pub fn to_restrictedstring(self) -> Option<String> {
2063 match self {
2064 Value::RestrictedString(s) => Some(s),
2065 _ => None,
2066 }
2067 }
2068
2069 pub fn to_phonenumber(self) -> Option<String> {
2070 match self {
2071 Value::PhoneNumber(p, _b) => Some(p),
2072 _ => None,
2073 }
2074 }
2075
2076 pub fn to_publicbinary(self) -> Option<(String, Vec<u8>)> {
2077 match self {
2078 Value::PublicBinary(t, d) => Some((t, d)),
2079 _ => None,
2080 }
2081 }
2082
2083 pub fn to_address(self) -> Option<Address> {
2084 match self {
2085 Value::Address(a) => Some(a),
2086 _ => None,
2087 }
2088 }
2089
2090 pub fn to_intenttoken(self) -> Option<(String, IntentTokenState)> {
2091 match self {
2092 Value::IntentToken(u, s) => Some((u, s)),
2093 _ => None,
2094 }
2095 }
2096
2097 pub fn to_session(self) -> Option<(Uuid, Session)> {
2098 match self {
2099 Value::Session(u, s) => Some((u, s)),
2100 _ => None,
2101 }
2102 }
2103
2104 pub fn migrate_iutf8_iname(self) -> Option<Self> {
2105 match self {
2106 Value::Iutf8(v) => Some(Value::Iname(v)),
2107 _ => None,
2108 }
2109 }
2110
2111 #[allow(clippy::unreachable)]
2113 pub(crate) fn to_proto_string_clone(&self) -> String {
2114 match &self {
2115 Value::Iname(s) => s.clone(),
2116 Value::Uuid(u) => u.as_hyphenated().to_string(),
2117 Value::SshKey(tag, key) => format!("{}: {}", tag, key),
2119 Value::Spn(n, r) => format!("{n}@{r}"),
2120 _ => unreachable!(
2121 "You've specified the wrong type for the attribute, got: {:?}",
2122 self
2123 ),
2124 }
2125 }
2126
2127 pub(crate) fn validate(&self) -> bool {
2128 match &self {
2132 Value::Utf8(s)
2134 | Value::Iutf8(s)
2135 | Value::Cred(s, _)
2136 | Value::PublicBinary(s, _)
2137 | Value::IntentToken(s, _)
2138 | Value::Passkey(_, s, _)
2139 | Value::AttestedPasskey(_, s, _)
2140 | Value::TotpSecret(s, _) => {
2141 Value::validate_str_escapes(s) && Value::validate_singleline(s)
2142 }
2143
2144 Value::Spn(a, b) => {
2145 Value::validate_str_escapes(a)
2146 && Value::validate_str_escapes(b)
2147 && Value::validate_singleline(a)
2148 && Value::validate_singleline(b)
2149 }
2150 Value::Image(image) => image.validate_image().is_ok(),
2151 Value::Iname(s) => {
2152 Value::validate_str_escapes(s)
2153 && Value::validate_iname(s)
2154 && Value::validate_singleline(s)
2155 }
2156
2157 Value::SshKey(s, _key) => {
2158 Value::validate_str_escapes(s)
2159 && Value::validate_singleline(s)
2161 }
2162
2163 Value::ApiToken(_, at) => {
2164 Value::validate_str_escapes(&at.label) && Value::validate_singleline(&at.label)
2165 }
2166 Value::AuditLogString(_, s) => {
2167 Value::validate_str_escapes(s) && Value::validate_singleline(s)
2168 }
2169 Value::ApplicationPassword(ap) => {
2170 Value::validate_str_escapes(&ap.label) && Value::validate_singleline(&ap.label)
2171 }
2172
2173 Value::Nsuniqueid(s) => NSUNIQUEID_RE.is_match(s),
2175 Value::DateTime(odt) => odt.offset() == time::UtcOffset::UTC,
2176 Value::EmailAddress(mail, _) => VALIDATE_EMAIL_RE.is_match(mail.as_str()),
2177 Value::OauthScope(s) => OAUTHSCOPE_RE.is_match(s),
2178 Value::OauthScopeMap(_, m) => m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)),
2179
2180 Value::OauthClaimMap(name, _) => OAUTHSCOPE_RE.is_match(name),
2181 Value::OauthClaimValue(name, _, value) => {
2182 OAUTHSCOPE_RE.is_match(name) && value.iter().all(|s| OAUTHSCOPE_RE.is_match(s))
2183 }
2184
2185 Value::HexString(id) | Value::KeyInternal { id, .. } => {
2186 Value::validate_str_escapes(id.as_str())
2187 && Value::validate_singleline(id.as_str())
2188 && Value::validate_hexstr(id.as_str())
2189 }
2190
2191 Value::PhoneNumber(_, _) => true,
2192 Value::Address(_) => true,
2193 Value::Certificate(_) => true,
2194
2195 Value::Uuid(_)
2196 | Value::Bool(_)
2197 | Value::Syntax(_)
2198 | Value::Index(_)
2199 | Value::Refer(_)
2200 | Value::JsonFilt(_)
2201 | Value::SecretValue(_)
2202 | Value::Uint32(_)
2203 | Value::Url(_)
2204 | Value::Cid(_)
2205 | Value::PrivateBinary(_)
2206 | Value::RestrictedString(_)
2207 | Value::JwsKeyEs256(_)
2208 | Value::Session(_, _)
2209 | Value::Oauth2Session(_, _)
2210 | Value::JwsKeyRs256(_)
2211 | Value::EcKeyPrivate(_)
2212 | Value::UiHint(_)
2213 | Value::CredentialType(_)
2214 | Value::WebauthnAttestationCaList(_) => true,
2215 }
2216 }
2217
2218 pub(crate) fn validate_iname(s: &str) -> bool {
2219 match Uuid::parse_str(s) {
2220 Ok(_) => {
2222 error!("iname values may not contain uuids");
2223 false
2224 }
2225 Err(_) => {
2227 if !INAME_RE.is_match(s) {
2228 error!("iname values may only contain limited characters - \"{}\" does not pass regex pattern \"{}\"", s, *INAME_RE);
2229 false
2230 } else if DISALLOWED_NAMES.contains(s) {
2231 error!("iname value \"{}\" is in denied list", s);
2232 false
2233 } else {
2234 true
2235 }
2236 }
2237 }
2238 }
2239
2240 pub(crate) fn validate_hexstr(s: &str) -> bool {
2241 if !HEXSTR_RE.is_match(s) {
2242 error!("hexstrings may only contain limited characters. - \"{}\" does not pass regex pattern \"{}\"", s, *HEXSTR_RE);
2243 false
2244 } else {
2245 true
2246 }
2247 }
2248
2249 pub(crate) fn validate_singleline(s: &str) -> bool {
2250 if !SINGLELINE_RE.is_match(s) {
2251 true
2252 } else {
2253 error!(
2254 "value contains invalid whitespace chars forbidden by \"{}\"",
2255 *SINGLELINE_RE
2256 );
2257 trace!(?s, "Invalid whitespace");
2259 false
2260 }
2261 }
2262
2263 pub(crate) fn validate_str_escapes(s: &str) -> bool {
2264 if UNICODE_CONTROL_RE.is_match(s) {
2266 error!("value contains invalid unicode control character",);
2267 trace!(?s, "Invalid Unicode Control");
2269 false
2270 } else {
2271 true
2272 }
2273 }
2274}
2275
2276#[cfg(test)]
2277mod tests {
2278 use crate::value::*;
2279
2280 #[test]
2281 fn test_value_index_tryfrom() {
2282 let r1 = IndexType::try_from("EQualiTY");
2283 assert_eq!(r1, Ok(IndexType::Equality));
2284
2285 let r2 = IndexType::try_from("PResenCE");
2286 assert_eq!(r2, Ok(IndexType::Presence));
2287
2288 let r3 = IndexType::try_from("SUbstrING");
2289 assert_eq!(r3, Ok(IndexType::SubString));
2290
2291 let r4 = IndexType::try_from("thaoeusaneuh");
2292 assert_eq!(r4, Err(()));
2293 }
2294
2295 #[test]
2296 fn test_value_syntax_tryfrom() {
2297 let r1 = SyntaxType::try_from("UTF8strinG");
2298 assert_eq!(r1, Ok(SyntaxType::Utf8String));
2299
2300 let r2 = SyntaxType::try_from("UTF8STRING_INSensitIVE");
2301 assert_eq!(r2, Ok(SyntaxType::Utf8StringInsensitive));
2302
2303 let r3 = SyntaxType::try_from("BOOLEAN");
2304 assert_eq!(r3, Ok(SyntaxType::Boolean));
2305
2306 let r4 = SyntaxType::try_from("SYNTAX_ID");
2307 assert_eq!(r4, Ok(SyntaxType::SyntaxId));
2308
2309 let r5 = SyntaxType::try_from("INDEX_ID");
2310 assert_eq!(r5, Ok(SyntaxType::IndexId));
2311
2312 let r6 = SyntaxType::try_from("zzzzantheou");
2313 assert_eq!(r6, Err(()));
2314 }
2315
2316 #[test]
2317 fn test_value_sshkey_validation_display() {
2318 let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
2319 "tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
2320 "zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
2321 "methyst");
2322 let ed25519 = concat!(
2323 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP",
2324 " william@amethyst"
2325 );
2326 let rsa = concat!("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTcXpclurQpyOHZBM/cDY9EvInSYkYSGe51by/wJP0Njgi",
2327 "GZUJ3HTaPqoGWux0PKd7KJki+onLYt4IwDV1RhV/GtMML2U9v94+pA8RIK4khCxvpUxlM7Kt/svjOzzzqiZfKdV37/",
2328 "OUXmM7bwVGOvm3EerDOwmO/QdzNGfkca12aWLoz97YrleXnCoAzr3IN7j3rwmfJGDyuUtGTdmyS/QWhK9FPr8Ic3eM",
2329 "QK1JSAQqVfGhA8lLbJHmnQ/b/KMl2lzzp7SXej0wPUfvI/IP3NGb8irLzq8+JssAzXGJ+HMql+mNHiSuPaktbFzZ6y",
2330 "ikMR6Rx/psU07nAkxKZDEYpNVv william@amethyst");
2331
2332 let sk1 = Value::new_sshkey_str("tag", ecdsa).expect("Invalid ssh key");
2333 assert!(sk1.validate());
2334 let psk1 = sk1.to_proto_string_clone();
2336 assert_eq!(psk1, format!("tag: {}", ecdsa));
2337
2338 let sk2 = Value::new_sshkey_str("tag", ed25519).expect("Invalid ssh key");
2339 assert!(sk2.validate());
2340 let psk2 = sk2.to_proto_string_clone();
2341 assert_eq!(psk2, format!("tag: {}", ed25519));
2342
2343 let sk3 = Value::new_sshkey_str("tag", rsa).expect("Invalid ssh key");
2344 assert!(sk3.validate());
2345 let psk3 = sk3.to_proto_string_clone();
2346 assert_eq!(psk3, format!("tag: {}", rsa));
2347
2348 let sk4 = Value::new_sshkey_str("tag", "ntaouhtnhtnuehtnuhotnuhtneouhtneouh");
2349 assert!(sk4.is_err());
2350
2351 let sk5 = Value::new_sshkey_str(
2352 "tag",
2353 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo",
2354 );
2355 assert!(sk5.is_err());
2356 }
2357
2358 #[test]
2394 fn test_value_cid() {
2395 assert!(PartialValue::new_cid_s("_").is_none());
2396 }
2397
2398 #[test]
2399 fn test_value_iname() {
2400 let inv1 = Value::new_iname("1234");
2411 let inv2 = Value::new_iname("bc23f637-4439-4c07-b95d-eaed0d9e4b8b");
2412 let inv3 = Value::new_iname("hello@test.com");
2413 let inv4 = Value::new_iname("_bad");
2414 let inv5 = Value::new_iname("no spaces I'm sorry :(");
2415 let inv6 = Value::new_iname("bad=equals");
2416 let inv7 = Value::new_iname("bad,comma");
2417 let inv8 = Value::new_iname("123_456");
2418 let inv9 = Value::new_iname("🍿");
2419
2420 let val1 = Value::new_iname("William");
2421 let val2 = Value::new_iname("this_is_okay");
2422 let val3 = Value::new_iname("a123_456");
2423
2424 assert!(!inv1.validate());
2425 assert!(!inv2.validate());
2426 assert!(!inv3.validate());
2427 assert!(!inv4.validate());
2428 assert!(!inv5.validate());
2429 assert!(!inv6.validate());
2430 assert!(!inv7.validate());
2431 assert!(!inv8.validate());
2432 assert!(!inv9.validate());
2433
2434 assert!(val1.validate());
2435 assert!(val2.validate());
2436 assert!(val3.validate());
2437 }
2438
2439 #[test]
2440 fn test_value_nsuniqueid() {
2441 let val1 = Value::new_nsuniqueid_s("d765e707-48e111e6-8c9ebed8-f7926cc3");
2446 let val2 = Value::new_nsuniqueid_s("D765E707-48E111E6-8C9EBED8-F7926CC3");
2447 let inv1 = Value::new_nsuniqueid_s("d765e707-48e1-11e6-8c9e-bed8f7926cc3");
2448 let inv2 = Value::new_nsuniqueid_s("xxxx");
2449
2450 assert!(inv1.is_none());
2451 assert!(inv2.is_none());
2452 assert!(val1.unwrap().validate());
2453 assert!(val2.unwrap().validate());
2454 }
2455
2456 #[test]
2457 fn test_value_datetime() {
2458 let val1 = Value::new_datetime_s("2020-09-25T11:22:02+10:00").expect("Must be valid");
2460 assert!(val1.validate());
2461 let val2 = Value::new_datetime_s("2020-09-25T01:22:02+00:00").expect("Must be valid");
2462 assert!(val2.validate());
2463 let val3 = Value::new_datetime_s("2020-09-25 01:22:02+00:00").expect("Must be valid");
2465 assert!(val3.validate());
2466
2467 assert!(Value::new_datetime_s("2020-09-25T01:22:02").is_none());
2468 assert!(Value::new_datetime_s("2020-09-25").is_none());
2469 assert!(Value::new_datetime_s("2020-09-25T01:22:02+10").is_none());
2470
2471 let inv1 = Value::DateTime(
2473 OffsetDateTime::now_utc()
2474 .to_offset(time::UtcOffset::from_whole_seconds(36000).unwrap()),
2475 );
2476 assert!(!inv1.validate());
2477
2478 let val3 = Value::DateTime(OffsetDateTime::now_utc());
2479 assert!(val3.validate());
2480 }
2481
2482 #[test]
2483 fn test_value_email_address() {
2484 let val1 = Value::new_email_address_s("william@blackhats.net.au");
2486 let val2 = Value::new_email_address_s("alice@idm.example.com");
2487 let val3 = Value::new_email_address_s("test+mailbox@foo.com");
2488 let inv1 = Value::new_email_address_s("william");
2489 let inv2 = Value::new_email_address_s("test~uuid");
2490
2491 assert!(inv1.is_none());
2492 assert!(inv2.is_none());
2493 assert!(val1.unwrap().validate());
2494 assert!(val2.unwrap().validate());
2495 assert!(val3.unwrap().validate());
2496 }
2497
2498 #[test]
2499 fn test_value_url() {
2500 let val1 = Value::new_url_s("https://localhost:8000/search?q=text#hello");
2502 let val2 = Value::new_url_s("https://github.com/kanidm/kanidm");
2503 let val3 = Value::new_url_s("ldap://foo.com");
2504 let inv1 = Value::new_url_s("127.0.");
2505 let inv2 = Value::new_url_s("🤔");
2506
2507 assert!(inv1.is_none());
2508 assert!(inv2.is_none());
2509 assert!(val1.is_some());
2510 assert!(val2.is_some());
2511 assert!(val3.is_some());
2512 }
2513
2514 #[test]
2515 fn test_singleline() {
2516 assert!(Value::validate_singleline("no new lines"));
2517
2518 assert!(!Value::validate_singleline("contains a \n new line"));
2519 assert!(!Value::validate_singleline("contains a \r return feed"));
2520 assert!(!Value::validate_singleline("contains a \t tab char"));
2521 }
2522
2523 #[test]
2524 fn test_str_escapes() {
2525 assert!(Value::validate_str_escapes("safe str"));
2526 assert!(Value::validate_str_escapes("🙃 emoji are 👍"));
2527
2528 assert!(!Value::validate_str_escapes("naughty \x1b[31mred"));
2529 }
2530
2531 #[test]
2532 fn test_value_key_internal_status_order() {
2533 assert!(KeyStatus::Valid < KeyStatus::Retained);
2534 assert!(KeyStatus::Retained < KeyStatus::Revoked);
2535 }
2536
2537 #[test]
2538 fn test_value_session_state_order() {
2539 assert!(
2540 SessionState::RevokedAt(Cid::new_zero()) > SessionState::RevokedAt(Cid::new_count(1))
2541 );
2542 assert!(
2543 SessionState::RevokedAt(Cid::new_zero())
2544 > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2545 );
2546 assert!(
2547 SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH + Duration::from_secs(1))
2548 > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2549 );
2550 assert!(SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH) > SessionState::NeverExpires);
2551 }
2552}