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