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