kanidmd_lib/
value.rs

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