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