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