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