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