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    JweA128GCM,
1255}
1256
1257impl fmt::Display for KeyUsage {
1258    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1259        write!(
1260            f,
1261            "{}",
1262            match self {
1263                KeyUsage::JwsEs256 => "jws_es256",
1264                KeyUsage::JweA128GCM => "jwe_a128gcm",
1265            }
1266        )
1267    }
1268}
1269
1270#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1271pub enum KeyStatus {
1272    Valid,
1273    Retained,
1274    Revoked,
1275}
1276
1277impl fmt::Display for KeyStatus {
1278    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1279        write!(
1280            f,
1281            "{}",
1282            match self {
1283                KeyStatus::Valid => "valid",
1284                KeyStatus::Retained => "retained",
1285                KeyStatus::Revoked => "revoked",
1286            }
1287        )
1288    }
1289}
1290
1291/// A value is a complete unit of data for an attribute. It is made up of a PartialValue, which is
1292/// used for selection, filtering, searching, matching etc. It also contains supplemental data
1293/// which may be stored inside of the Value, such as credential secrets, blobs etc.
1294///
1295/// This type is used when you need the "full data" of an attribute. Typically this is in a create
1296/// or modification operation where you are applying a set of complete values into an entry.
1297#[derive(Clone, Debug)]
1298pub enum Value {
1299    Utf8(String),
1300    /// Case insensitive string
1301    Iutf8(String),
1302    /// Case insensitive Name for a thing
1303    Iname(String),
1304    Uuid(Uuid),
1305    Bool(bool),
1306    Syntax(SyntaxType),
1307    Index(IndexType),
1308    Refer(Uuid),
1309    JsonFilt(ProtoFilter),
1310    Cred(String, Credential),
1311    SshKey(String, SshPublicKey),
1312    SecretValue(String),
1313    Spn(String, String),
1314    Uint32(u32),
1315    Cid(Cid),
1316    Nsuniqueid(String),
1317    DateTime(OffsetDateTime),
1318    EmailAddress(String, bool),
1319    PhoneNumber(String, bool),
1320    Address(Address),
1321    Url(Url),
1322    OauthScope(String),
1323    OauthScopeMap(Uuid, BTreeSet<String>),
1324    PrivateBinary(Vec<u8>),
1325    PublicBinary(String, Vec<u8>),
1326    RestrictedString(String),
1327    IntentToken(String, IntentTokenState),
1328    Passkey(Uuid, String, PasskeyV4),
1329    AttestedPasskey(Uuid, String, AttestedPasskeyV4),
1330
1331    Session(Uuid, Session),
1332    ApiToken(Uuid, ApiToken),
1333    Oauth2Session(Uuid, Oauth2Session),
1334
1335    JwsKeyEs256(JwsEs256Signer),
1336    JwsKeyRs256(JwsRs256Signer),
1337    UiHint(UiHint),
1338
1339    TotpSecret(String, Totp),
1340    AuditLogString(Cid, String),
1341    EcKeyPrivate(EcKey<Private>),
1342
1343    Image(ImageValue),
1344    CredentialType(CredentialType),
1345    WebauthnAttestationCaList(AttestationCaList),
1346
1347    OauthClaimValue(String, Uuid, BTreeSet<String>),
1348    OauthClaimMap(String, OauthClaimMapJoin),
1349
1350    KeyInternal {
1351        id: KeyId,
1352        usage: KeyUsage,
1353        valid_from: u64,
1354        status: KeyStatus,
1355        status_cid: Cid,
1356        der: Vec<u8>,
1357    },
1358
1359    HexString(String),
1360
1361    Certificate(Box<Certificate>),
1362    ApplicationPassword(ApplicationPassword),
1363}
1364
1365impl PartialEq for Value {
1366    fn eq(&self, other: &Self) -> bool {
1367        match (self, other) {
1368            (Value::Utf8(a), Value::Utf8(b))
1369            | (Value::Iutf8(a), Value::Iutf8(b))
1370            | (Value::Iname(a), Value::Iname(b))
1371            | (Value::Cred(a, _), Value::Cred(b, _))
1372            | (Value::SshKey(a, _), Value::SshKey(b, _))
1373            | (Value::Nsuniqueid(a), Value::Nsuniqueid(b))
1374            | (Value::EmailAddress(a, _), Value::EmailAddress(b, _))
1375            | (Value::PhoneNumber(a, _), Value::PhoneNumber(b, _))
1376            | (Value::OauthScope(a), Value::OauthScope(b))
1377            | (Value::PublicBinary(a, _), Value::PublicBinary(b, _))
1378            | (Value::RestrictedString(a), Value::RestrictedString(b)) => a.eq(b),
1379            // Spn - need to check both name and domain.
1380            (Value::Spn(a, c), Value::Spn(b, d)) => a.eq(b) && c.eq(d),
1381            // Uuid, Refer
1382            (Value::Uuid(a), Value::Uuid(b)) | (Value::Refer(a), Value::Refer(b)) => a.eq(b),
1383            // Bool
1384            (Value::Bool(a), Value::Bool(b)) => a.eq(b),
1385            // Syntax
1386            (Value::Syntax(a), Value::Syntax(b)) => a.eq(b),
1387            // Index
1388            (Value::Index(a), Value::Index(b)) => a.eq(b),
1389            // JsonFilt
1390            (Value::JsonFilt(a), Value::JsonFilt(b)) => a.eq(b),
1391            // Uint32
1392            (Value::Uint32(a), Value::Uint32(b)) => a.eq(b),
1393            // Cid
1394            (Value::Cid(a), Value::Cid(b)) => a.eq(b),
1395            // DateTime
1396            (Value::DateTime(a), Value::DateTime(b)) => a.eq(b),
1397            // Url
1398            (Value::Url(a), Value::Url(b)) => a.eq(b),
1399            // OauthScopeMap
1400            (Value::OauthScopeMap(a, c), Value::OauthScopeMap(b, d)) => a.eq(b) && c.eq(d),
1401
1402            (Value::Image(image1), Value::Image(image2)) => {
1403                image1.hash_imagevalue().eq(&image2.hash_imagevalue())
1404            }
1405            (Value::Address(_), Value::Address(_))
1406            | (Value::PrivateBinary(_), Value::PrivateBinary(_))
1407            | (Value::SecretValue(_), Value::SecretValue(_)) => false,
1408            // Specifically related to migrations, we allow the invalid comparison.
1409            (Value::Iutf8(_), Value::Iname(_)) | (Value::Iname(_), Value::Iutf8(_)) => false,
1410            // When upgrading between uuid -> name -> spn we have to allow some invalid types.
1411            (Value::Uuid(_), Value::Iname(_))
1412            | (Value::Iname(_), Value::Spn(_, _))
1413            | (Value::Uuid(_), Value::Spn(_, _)) => false,
1414            (l, r) => {
1415                error!(?l, ?r, "mismatched value types");
1416                debug_assert!(false);
1417                false
1418            }
1419        }
1420    }
1421}
1422
1423impl Eq for Value {}
1424
1425impl From<bool> for Value {
1426    fn from(b: bool) -> Self {
1427        Value::Bool(b)
1428    }
1429}
1430
1431impl From<&bool> for Value {
1432    fn from(b: &bool) -> Self {
1433        Value::Bool(*b)
1434    }
1435}
1436
1437impl From<SyntaxType> for Value {
1438    fn from(s: SyntaxType) -> Self {
1439        Value::Syntax(s)
1440    }
1441}
1442
1443impl From<IndexType> for Value {
1444    fn from(i: IndexType) -> Self {
1445        Value::Index(i)
1446    }
1447}
1448
1449impl From<ProtoFilter> for Value {
1450    fn from(i: ProtoFilter) -> Self {
1451        Value::JsonFilt(i)
1452    }
1453}
1454
1455impl From<OffsetDateTime> for Value {
1456    fn from(i: OffsetDateTime) -> Self {
1457        Value::DateTime(i)
1458    }
1459}
1460
1461impl From<u32> for Value {
1462    fn from(i: u32) -> Self {
1463        Value::Uint32(i)
1464    }
1465}
1466
1467impl From<Url> for Value {
1468    fn from(i: Url) -> Self {
1469        Value::Url(i)
1470    }
1471}
1472
1473// Because these are potentially ambiguous, we limit them to tests where we can contain
1474// any....mistakes.
1475#[cfg(test)]
1476impl From<&str> for Value {
1477    fn from(s: &str) -> Self {
1478        // Fuzzy match for uuid's
1479        match Uuid::parse_str(s) {
1480            Ok(u) => Value::Uuid(u),
1481            Err(_) => Value::Utf8(s.to_string()),
1482        }
1483    }
1484}
1485
1486#[cfg(test)]
1487impl From<&Uuid> for Value {
1488    fn from(u: &Uuid) -> Self {
1489        Value::Uuid(*u)
1490    }
1491}
1492
1493#[cfg(test)]
1494impl From<Uuid> for Value {
1495    fn from(u: Uuid) -> Self {
1496        Value::Uuid(u)
1497    }
1498}
1499
1500impl From<DbIdentSpn> for Value {
1501    fn from(dis: DbIdentSpn) -> Self {
1502        match dis {
1503            DbIdentSpn::Spn(n, r) => Value::Spn(n, r),
1504            DbIdentSpn::Iname(n) => Value::Iname(n),
1505            DbIdentSpn::Uuid(u) => Value::Uuid(u),
1506        }
1507    }
1508}
1509
1510impl Value {
1511    // I get the feeling this will have a lot of matching ... sigh.
1512    pub fn new_utf8(s: String) -> Self {
1513        Value::Utf8(s)
1514    }
1515
1516    pub fn new_utf8s(s: &str) -> Self {
1517        Value::Utf8(s.to_string())
1518    }
1519
1520    pub fn is_utf8(&self) -> bool {
1521        matches!(self, Value::Utf8(_))
1522    }
1523
1524    pub fn new_iutf8(s: &str) -> Self {
1525        Value::Iutf8(s.to_lowercase())
1526    }
1527
1528    pub fn is_iutf8(&self) -> bool {
1529        matches!(self, Value::Iutf8(_))
1530    }
1531
1532    #[inline(always)]
1533    pub fn new_class(s: &str) -> Self {
1534        Value::Iutf8(s.to_lowercase())
1535    }
1536
1537    pub fn new_attr(s: &str) -> Self {
1538        Value::Iutf8(s.to_lowercase())
1539    }
1540
1541    pub fn is_insensitive_utf8(&self) -> bool {
1542        matches!(self, Value::Iutf8(_))
1543    }
1544
1545    pub fn new_iname(s: &str) -> Self {
1546        Value::Iname(s.to_lowercase())
1547    }
1548
1549    pub fn is_iname(&self) -> bool {
1550        matches!(self, Value::Iname(_))
1551    }
1552
1553    pub fn new_uuid_s(s: &str) -> Option<Self> {
1554        Uuid::parse_str(s).map(Value::Uuid).ok()
1555    }
1556
1557    // Is this correct? Should ref be separate?
1558    pub fn is_uuid(&self) -> bool {
1559        matches!(self, Value::Uuid(_))
1560    }
1561
1562    pub fn new_bool(b: bool) -> Self {
1563        Value::Bool(b)
1564    }
1565
1566    pub fn new_bools(s: &str) -> Option<Self> {
1567        bool::from_str(s).map(Value::Bool).ok()
1568    }
1569
1570    pub fn new_audit_log_string(e: (Cid, String)) -> Option<Self> {
1571        Some(Value::AuditLogString(e.0, e.1))
1572    }
1573
1574    #[inline]
1575    pub fn is_bool(&self) -> bool {
1576        matches!(self, Value::Bool(_))
1577    }
1578
1579    pub fn new_syntaxs(s: &str) -> Option<Self> {
1580        SyntaxType::try_from(s).map(Value::Syntax).ok()
1581    }
1582
1583    pub fn new_syntax(s: SyntaxType) -> Self {
1584        Value::Syntax(s)
1585    }
1586
1587    pub fn is_syntax(&self) -> bool {
1588        matches!(self, Value::Syntax(_))
1589    }
1590
1591    pub fn new_indexes(s: &str) -> Option<Self> {
1592        IndexType::try_from(s).map(Value::Index).ok()
1593    }
1594
1595    pub fn new_index(i: IndexType) -> Self {
1596        Value::Index(i)
1597    }
1598
1599    pub fn is_index(&self) -> bool {
1600        matches!(self, Value::Index(_))
1601    }
1602
1603    pub fn new_refer_s(us: &str) -> Option<Self> {
1604        Uuid::parse_str(us).map(Value::Refer).ok()
1605    }
1606
1607    pub fn is_refer(&self) -> bool {
1608        matches!(self, Value::Refer(_))
1609    }
1610
1611    pub fn new_json_filter_s(s: &str) -> Option<Self> {
1612        serde_json::from_str(s).map(Value::JsonFilt).ok()
1613    }
1614
1615    pub fn new_json_filter(f: ProtoFilter) -> Self {
1616        Value::JsonFilt(f)
1617    }
1618
1619    pub fn is_json_filter(&self) -> bool {
1620        matches!(self, Value::JsonFilt(_))
1621    }
1622
1623    pub fn as_json_filter(&self) -> Option<&ProtoFilter> {
1624        match &self {
1625            Value::JsonFilt(f) => Some(f),
1626            _ => None,
1627        }
1628    }
1629
1630    pub fn new_credential(tag: &str, cred: Credential) -> Self {
1631        Value::Cred(tag.to_string(), cred)
1632    }
1633
1634    pub fn is_credential(&self) -> bool {
1635        matches!(&self, Value::Cred(_, _))
1636    }
1637
1638    pub fn to_credential(&self) -> Option<&Credential> {
1639        match &self {
1640            Value::Cred(_, cred) => Some(cred),
1641            _ => None,
1642        }
1643    }
1644
1645    pub fn new_hex_string_s(hexstr: &str) -> Option<Self> {
1646        let hexstr_lower = hexstr.to_lowercase();
1647        if HEXSTR_RE.is_match(&hexstr_lower) {
1648            Some(Value::HexString(hexstr_lower))
1649        } else {
1650            None
1651        }
1652    }
1653
1654    pub fn new_certificate_s(cert_str: &str) -> Option<Self> {
1655        Certificate::from_pem(cert_str)
1656            .map(Box::new)
1657            .map(Value::Certificate)
1658            .ok()
1659    }
1660
1661    /// Want a `Value::Image`? use this!
1662    pub fn new_image(input: &str) -> Result<Self, OperationError> {
1663        serde_json::from_str::<ImageValue>(input)
1664            .map(Value::Image)
1665            .map_err(|_e| OperationError::InvalidValueState)
1666    }
1667
1668    pub fn new_secret_str(cleartext: &str) -> Self {
1669        Value::SecretValue(cleartext.to_string())
1670    }
1671
1672    pub fn is_secret_string(&self) -> bool {
1673        matches!(&self, Value::SecretValue(_))
1674    }
1675
1676    pub fn get_secret_str(&self) -> Option<&str> {
1677        match &self {
1678            Value::SecretValue(c) => Some(c.as_str()),
1679            _ => None,
1680        }
1681    }
1682
1683    pub fn new_sshkey_str(tag: &str, key: &str) -> Result<Self, OperationError> {
1684        SshPublicKey::from_string(key)
1685            .map(|pk| Value::SshKey(tag.to_string(), pk))
1686            .map_err(|err| {
1687                error!(?err, "value sshkey failed to parse string");
1688                OperationError::VL0001ValueSshPublicKeyString
1689            })
1690    }
1691
1692    pub fn is_sshkey(&self) -> bool {
1693        matches!(&self, Value::SshKey(_, _))
1694    }
1695
1696    pub fn get_sshkey(&self) -> Option<String> {
1697        match &self {
1698            Value::SshKey(_, key) => Some(key.to_string()),
1699            _ => None,
1700        }
1701    }
1702
1703    pub fn new_spn_parse(s: &str) -> Option<Self> {
1704        SPN_RE.captures(s).and_then(|caps| {
1705            let name = match caps.name(Attribute::Name.as_ref()) {
1706                Some(v) => v.as_str().to_string(),
1707                None => return None,
1708            };
1709            let realm = match caps.name("realm") {
1710                Some(v) => v.as_str().to_string(),
1711                None => return None,
1712            };
1713            Some(Value::Spn(name, realm))
1714        })
1715    }
1716
1717    pub fn new_spn_str(n: &str, r: &str) -> Self {
1718        Value::Spn(n.to_string(), r.to_string())
1719    }
1720
1721    pub fn is_spn(&self) -> bool {
1722        matches!(&self, Value::Spn(_, _))
1723    }
1724
1725    pub fn new_uint32(u: u32) -> Self {
1726        Value::Uint32(u)
1727    }
1728
1729    pub fn new_uint32_str(u: &str) -> Option<Self> {
1730        u.parse::<u32>().ok().map(Value::Uint32)
1731    }
1732
1733    pub fn is_uint32(&self) -> bool {
1734        matches!(&self, Value::Uint32(_))
1735    }
1736
1737    pub fn new_cid(c: Cid) -> Self {
1738        Value::Cid(c)
1739    }
1740
1741    pub fn is_cid(&self) -> bool {
1742        matches!(&self, Value::Cid(_))
1743    }
1744
1745    pub fn new_nsuniqueid_s(s: &str) -> Option<Self> {
1746        if NSUNIQUEID_RE.is_match(s) {
1747            Some(Value::Nsuniqueid(s.to_lowercase()))
1748        } else {
1749            None
1750        }
1751    }
1752
1753    pub fn is_nsuniqueid(&self) -> bool {
1754        matches!(&self, Value::Nsuniqueid(_))
1755    }
1756
1757    pub fn new_datetime_epoch(ts: Duration) -> Self {
1758        Value::DateTime(OffsetDateTime::UNIX_EPOCH + ts)
1759    }
1760
1761    pub fn new_datetime_s(s: &str) -> Option<Self> {
1762        OffsetDateTime::parse(s, &Rfc3339)
1763            .ok()
1764            .map(|odt| odt.to_offset(time::UtcOffset::UTC))
1765            .map(Value::DateTime)
1766    }
1767
1768    pub fn new_datetime(dt: OffsetDateTime) -> Self {
1769        Value::DateTime(dt)
1770    }
1771
1772    pub fn to_datetime(&self) -> Option<OffsetDateTime> {
1773        match &self {
1774            Value::DateTime(odt) => {
1775                debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
1776                Some(*odt)
1777            }
1778            _ => None,
1779        }
1780    }
1781
1782    pub fn is_datetime(&self) -> bool {
1783        matches!(&self, Value::DateTime(_))
1784    }
1785
1786    pub fn new_email_address_s(s: &str) -> Option<Self> {
1787        if VALIDATE_EMAIL_RE.is_match(s) {
1788            Some(Value::EmailAddress(s.to_string(), false))
1789        } else {
1790            None
1791        }
1792    }
1793
1794    pub fn new_email_address_primary_s(s: &str) -> Option<Self> {
1795        if VALIDATE_EMAIL_RE.is_match(s) {
1796            Some(Value::EmailAddress(s.to_string(), true))
1797        } else {
1798            None
1799        }
1800    }
1801
1802    pub fn is_email_address(&self) -> bool {
1803        matches!(&self, Value::EmailAddress(_, _))
1804    }
1805
1806    pub fn new_phonenumber_s(s: &str) -> Self {
1807        Value::PhoneNumber(s.to_string(), false)
1808    }
1809
1810    pub fn new_address(a: Address) -> Self {
1811        Value::Address(a)
1812    }
1813
1814    pub fn new_url_s(s: &str) -> Option<Self> {
1815        Url::parse(s).ok().map(Value::Url)
1816    }
1817
1818    pub fn new_url(u: Url) -> Self {
1819        Value::Url(u)
1820    }
1821
1822    pub fn is_url(&self) -> bool {
1823        matches!(&self, Value::Url(_))
1824    }
1825
1826    pub fn new_oauthscope(s: &str) -> Option<Self> {
1827        if OAUTHSCOPE_RE.is_match(s) {
1828            Some(Value::OauthScope(s.to_string()))
1829        } else {
1830            None
1831        }
1832    }
1833
1834    pub fn is_oauthscope(&self) -> bool {
1835        matches!(&self, Value::OauthScope(_))
1836    }
1837
1838    pub fn new_oauthscopemap(u: Uuid, m: BTreeSet<String>) -> Option<Self> {
1839        if m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1840            Some(Value::OauthScopeMap(u, m))
1841        } else {
1842            None
1843        }
1844    }
1845
1846    pub fn new_oauthclaimmap(n: String, u: Uuid, c: BTreeSet<String>) -> Option<Self> {
1847        if OAUTHSCOPE_RE.is_match(&n) && c.iter().all(|s| OAUTHSCOPE_RE.is_match(s)) {
1848            Some(Value::OauthClaimValue(n, u, c))
1849        } else {
1850            None
1851        }
1852    }
1853
1854    pub fn is_oauthscopemap(&self) -> bool {
1855        matches!(&self, Value::OauthScopeMap(_, _))
1856    }
1857
1858    #[cfg(test)]
1859    pub fn new_privatebinary_base64(der: &str) -> Self {
1860        let der = general_purpose::STANDARD
1861            .decode(der)
1862            .expect("Failed to decode base64 der value");
1863        Value::PrivateBinary(der)
1864    }
1865
1866    pub fn new_privatebinary(der: &[u8]) -> Self {
1867        Value::PrivateBinary(der.to_owned())
1868    }
1869
1870    pub fn to_privatebinary(&self) -> Option<&Vec<u8>> {
1871        match &self {
1872            Value::PrivateBinary(c) => Some(c),
1873            _ => None,
1874        }
1875    }
1876
1877    pub fn is_privatebinary(&self) -> bool {
1878        matches!(&self, Value::PrivateBinary(_))
1879    }
1880
1881    pub fn new_publicbinary(tag: String, der: Vec<u8>) -> Self {
1882        Value::PublicBinary(tag, der)
1883    }
1884
1885    pub fn new_restrictedstring(s: String) -> Self {
1886        Value::RestrictedString(s)
1887    }
1888
1889    pub fn new_webauthn_attestation_ca_list(s: &str) -> Option<Self> {
1890        serde_json::from_str(s)
1891            .map(Value::WebauthnAttestationCaList)
1892            .map_err(|err| {
1893                debug!(?err, ?s);
1894            })
1895            .ok()
1896    }
1897
1898    #[allow(clippy::unreachable)]
1899    pub(crate) fn to_db_ident_spn(&self) -> DbIdentSpn {
1900        // This has to clone due to how the backend works.
1901        match &self {
1902            Value::Spn(n, r) => DbIdentSpn::Spn(n.clone(), r.clone()),
1903            Value::Iname(s) => DbIdentSpn::Iname(s.clone()),
1904            Value::Uuid(u) => DbIdentSpn::Uuid(*u),
1905            // Value::Iutf8(s) => DbValueV1::Iutf8(s.clone()),
1906            // Value::Utf8(s) => DbValueV1::Utf8(s.clone()),
1907            // Value::Nsuniqueid(s) => DbValueV1::NsUniqueId(s.clone()),
1908            v => unreachable!("-> {:?}", v),
1909        }
1910    }
1911
1912    pub fn to_str(&self) -> Option<&str> {
1913        match &self {
1914            Value::Utf8(s) => Some(s.as_str()),
1915            Value::Iutf8(s) => Some(s.as_str()),
1916            Value::Iname(s) => Some(s.as_str()),
1917            _ => None,
1918        }
1919    }
1920
1921    pub fn to_url(&self) -> Option<&Url> {
1922        match &self {
1923            Value::Url(u) => Some(u),
1924            _ => None,
1925        }
1926    }
1927
1928    pub fn as_string(&self) -> Option<&String> {
1929        match &self {
1930            Value::Utf8(s) => Some(s),
1931            Value::Iutf8(s) => Some(s),
1932            Value::Iname(s) => Some(s),
1933            _ => None,
1934        }
1935    }
1936
1937    // We need a separate to-ref_uuid to distinguish from normal uuids
1938    // in refint plugin.
1939    pub fn to_ref_uuid(&self) -> Option<Uuid> {
1940        match &self {
1941            Value::Refer(u) => Some(*u),
1942            Value::OauthScopeMap(u, _) => Some(*u),
1943            // We need to assert that our reference to our rs exists.
1944            Value::Oauth2Session(_, m) => Some(m.rs_uuid),
1945            _ => None,
1946        }
1947    }
1948
1949    pub fn to_uuid(&self) -> Option<&Uuid> {
1950        match &self {
1951            Value::Uuid(u) => Some(u),
1952            _ => None,
1953        }
1954    }
1955
1956    pub fn to_indextype(&self) -> Option<&IndexType> {
1957        match &self {
1958            Value::Index(i) => Some(i),
1959            _ => None,
1960        }
1961    }
1962
1963    pub fn to_syntaxtype(&self) -> Option<&SyntaxType> {
1964        match &self {
1965            Value::Syntax(s) => Some(s),
1966            _ => None,
1967        }
1968    }
1969
1970    pub fn to_bool(&self) -> Option<bool> {
1971        match self {
1972            // *v is to invoke a copy, but this is cheap af
1973            Value::Bool(v) => Some(*v),
1974            _ => None,
1975        }
1976    }
1977
1978    pub fn to_uint32(&self) -> Option<u32> {
1979        match &self {
1980            Value::Uint32(v) => Some(*v),
1981            _ => None,
1982        }
1983    }
1984
1985    pub fn to_utf8(self) -> Option<String> {
1986        match self {
1987            Value::Utf8(s) => Some(s),
1988            _ => None,
1989        }
1990    }
1991
1992    pub fn to_iutf8(self) -> Option<String> {
1993        match self {
1994            Value::Iutf8(s) => Some(s),
1995            _ => None,
1996        }
1997    }
1998
1999    pub fn to_iname(self) -> Option<String> {
2000        match self {
2001            Value::Iname(s) => Some(s),
2002            _ => None,
2003        }
2004    }
2005
2006    pub fn to_jsonfilt(self) -> Option<ProtoFilter> {
2007        match self {
2008            Value::JsonFilt(f) => Some(f),
2009            _ => None,
2010        }
2011    }
2012
2013    pub fn to_cred(self) -> Option<(String, Credential)> {
2014        match self {
2015            Value::Cred(tag, c) => Some((tag, c)),
2016            _ => None,
2017        }
2018    }
2019
2020    /*
2021    pub(crate) fn to_sshkey(self) -> Option<(String, SshPublicKey)> {
2022        match self {
2023            Value::SshKey(tag, k) => Some((tag, k)),
2024            _ => None,
2025        }
2026    }
2027    */
2028
2029    pub fn to_spn(self) -> Option<(String, String)> {
2030        match self {
2031            Value::Spn(n, d) => Some((n, d)),
2032            _ => None,
2033        }
2034    }
2035
2036    pub fn to_cid(self) -> Option<Cid> {
2037        match self {
2038            Value::Cid(s) => Some(s),
2039            _ => None,
2040        }
2041    }
2042
2043    pub fn to_nsuniqueid(self) -> Option<String> {
2044        match self {
2045            Value::Nsuniqueid(s) => Some(s),
2046            _ => None,
2047        }
2048    }
2049
2050    pub fn to_emailaddress(self) -> Option<String> {
2051        match self {
2052            Value::EmailAddress(s, _) => Some(s),
2053            _ => None,
2054        }
2055    }
2056
2057    pub fn to_oauthscope(self) -> Option<String> {
2058        match self {
2059            Value::OauthScope(s) => Some(s),
2060            _ => None,
2061        }
2062    }
2063
2064    pub fn to_oauthscopemap(self) -> Option<(Uuid, BTreeSet<String>)> {
2065        match self {
2066            Value::OauthScopeMap(u, m) => Some((u, m)),
2067            _ => None,
2068        }
2069    }
2070
2071    pub fn to_restrictedstring(self) -> Option<String> {
2072        match self {
2073            Value::RestrictedString(s) => Some(s),
2074            _ => None,
2075        }
2076    }
2077
2078    pub fn to_phonenumber(self) -> Option<String> {
2079        match self {
2080            Value::PhoneNumber(p, _b) => Some(p),
2081            _ => None,
2082        }
2083    }
2084
2085    pub fn to_publicbinary(self) -> Option<(String, Vec<u8>)> {
2086        match self {
2087            Value::PublicBinary(t, d) => Some((t, d)),
2088            _ => None,
2089        }
2090    }
2091
2092    pub fn to_address(self) -> Option<Address> {
2093        match self {
2094            Value::Address(a) => Some(a),
2095            _ => None,
2096        }
2097    }
2098
2099    pub fn to_intenttoken(self) -> Option<(String, IntentTokenState)> {
2100        match self {
2101            Value::IntentToken(u, s) => Some((u, s)),
2102            _ => None,
2103        }
2104    }
2105
2106    pub fn to_session(self) -> Option<(Uuid, Session)> {
2107        match self {
2108            Value::Session(u, s) => Some((u, s)),
2109            _ => None,
2110        }
2111    }
2112
2113    pub fn migrate_iutf8_iname(self) -> Option<Self> {
2114        match self {
2115            Value::Iutf8(v) => Some(Value::Iname(v)),
2116            _ => None,
2117        }
2118    }
2119
2120    // !!!! This function is being phased out !!!
2121    #[allow(clippy::unreachable)]
2122    pub(crate) fn to_proto_string_clone(&self) -> String {
2123        match &self {
2124            Value::Iname(s) => s.clone(),
2125            Value::Uuid(u) => u.as_hyphenated().to_string(),
2126            // We display the tag and fingerprint.
2127            Value::SshKey(tag, key) => format!("{}: {}", tag, key),
2128            Value::Spn(n, r) => format!("{n}@{r}"),
2129            _ => unreachable!(
2130                "You've specified the wrong type for the attribute, got: {:?}",
2131                self
2132            ),
2133        }
2134    }
2135
2136    pub(crate) fn validate(&self) -> bool {
2137        // Validate that extra-data constraints on the type exist and are
2138        // valid. IE json filter is really a filter, or cred types have supplemental
2139        // data.
2140        match &self {
2141            // String security is required here
2142            Value::Utf8(s)
2143            | Value::Iutf8(s)
2144            | Value::Cred(s, _)
2145            | Value::PublicBinary(s, _)
2146            | Value::IntentToken(s, _)
2147            | Value::Passkey(_, s, _)
2148            | Value::AttestedPasskey(_, s, _)
2149            | Value::TotpSecret(s, _) => {
2150                Value::validate_str_escapes(s) && Value::validate_singleline(s)
2151            }
2152
2153            Value::Spn(a, b) => {
2154                Value::validate_str_escapes(a)
2155                    && Value::validate_str_escapes(b)
2156                    && Value::validate_singleline(a)
2157                    && Value::validate_singleline(b)
2158            }
2159            Value::Image(image) => image.validate_image().is_ok(),
2160            Value::Iname(s) => {
2161                Value::validate_str_escapes(s)
2162                    && Value::validate_iname(s)
2163                    && Value::validate_singleline(s)
2164            }
2165
2166            Value::SshKey(s, _key) => {
2167                Value::validate_str_escapes(s)
2168                    // && Value::validate_iname(s)
2169                    && Value::validate_singleline(s)
2170            }
2171
2172            Value::ApiToken(_, at) => {
2173                Value::validate_str_escapes(&at.label) && Value::validate_singleline(&at.label)
2174            }
2175            Value::AuditLogString(_, s) => {
2176                Value::validate_str_escapes(s) && Value::validate_singleline(s)
2177            }
2178            Value::ApplicationPassword(ap) => {
2179                Value::validate_str_escapes(&ap.label) && Value::validate_singleline(&ap.label)
2180            }
2181
2182            // These have stricter validators so not needed.
2183            Value::Nsuniqueid(s) => NSUNIQUEID_RE.is_match(s),
2184            Value::DateTime(odt) => odt.offset() == time::UtcOffset::UTC,
2185            Value::EmailAddress(mail, _) => VALIDATE_EMAIL_RE.is_match(mail.as_str()),
2186            Value::OauthScope(s) => OAUTHSCOPE_RE.is_match(s),
2187            Value::OauthScopeMap(_, m) => m.iter().all(|s| OAUTHSCOPE_RE.is_match(s)),
2188
2189            Value::OauthClaimMap(name, _) => OAUTHSCOPE_RE.is_match(name),
2190            Value::OauthClaimValue(name, _, value) => {
2191                OAUTHSCOPE_RE.is_match(name) && value.iter().all(|s| OAUTHSCOPE_RE.is_match(s))
2192            }
2193
2194            Value::HexString(id) | Value::KeyInternal { id, .. } => {
2195                Value::validate_str_escapes(id.as_str())
2196                    && Value::validate_singleline(id.as_str())
2197                    && Value::validate_hexstr(id.as_str())
2198            }
2199
2200            Value::PhoneNumber(_, _) => true,
2201            Value::Address(_) => true,
2202            Value::Certificate(_) => true,
2203
2204            Value::Uuid(_)
2205            | Value::Bool(_)
2206            | Value::Syntax(_)
2207            | Value::Index(_)
2208            | Value::Refer(_)
2209            | Value::JsonFilt(_)
2210            | Value::SecretValue(_)
2211            | Value::Uint32(_)
2212            | Value::Url(_)
2213            | Value::Cid(_)
2214            | Value::PrivateBinary(_)
2215            | Value::RestrictedString(_)
2216            | Value::JwsKeyEs256(_)
2217            | Value::Session(_, _)
2218            | Value::Oauth2Session(_, _)
2219            | Value::JwsKeyRs256(_)
2220            | Value::EcKeyPrivate(_)
2221            | Value::UiHint(_)
2222            | Value::CredentialType(_)
2223            | Value::WebauthnAttestationCaList(_) => true,
2224        }
2225    }
2226
2227    pub(crate) fn validate_iname(s: &str) -> bool {
2228        match Uuid::parse_str(s) {
2229            // It is a uuid, disallow.
2230            Ok(_) => {
2231                error!("iname values may not contain uuids");
2232                false
2233            }
2234            // Not a uuid, check it against the re.
2235            Err(_) => {
2236                if !INAME_RE.is_match(s) {
2237                    error!("iname values may only contain limited characters - \"{}\" does not pass regex pattern \"{}\"", s, *INAME_RE);
2238                    false
2239                } else if DISALLOWED_NAMES.contains(s) {
2240                    error!("iname value \"{}\" is in denied list", s);
2241                    false
2242                } else {
2243                    true
2244                }
2245            }
2246        }
2247    }
2248
2249    pub(crate) fn validate_hexstr(s: &str) -> bool {
2250        if !HEXSTR_RE.is_match(s) {
2251            error!("hexstrings may only contain limited characters. - \"{}\" does not pass regex pattern \"{}\"", s, *HEXSTR_RE);
2252            false
2253        } else {
2254            true
2255        }
2256    }
2257
2258    pub(crate) fn validate_singleline(s: &str) -> bool {
2259        if !SINGLELINE_RE.is_match(s) {
2260            true
2261        } else {
2262            error!(
2263                "value contains invalid whitespace chars forbidden by \"{}\"",
2264                *SINGLELINE_RE
2265            );
2266            // Trace only, could be an injection attack of some kind.
2267            trace!(?s, "Invalid whitespace");
2268            false
2269        }
2270    }
2271
2272    pub(crate) fn validate_str_escapes(s: &str) -> bool {
2273        // Look for and prevent certain types of string escapes and injections.
2274        if UNICODE_CONTROL_RE.is_match(s) {
2275            error!("value contains invalid unicode control character",);
2276            // Trace only, could be an injection attack of some kind.
2277            trace!(?s, "Invalid Unicode Control");
2278            false
2279        } else {
2280            true
2281        }
2282    }
2283}
2284
2285#[cfg(test)]
2286mod tests {
2287    use crate::value::*;
2288
2289    #[test]
2290    fn test_value_index_tryfrom() {
2291        let r1 = IndexType::try_from("EQualiTY");
2292        assert_eq!(r1, Ok(IndexType::Equality));
2293
2294        let r2 = IndexType::try_from("PResenCE");
2295        assert_eq!(r2, Ok(IndexType::Presence));
2296
2297        let r3 = IndexType::try_from("SUbstrING");
2298        assert_eq!(r3, Ok(IndexType::SubString));
2299
2300        let r4 = IndexType::try_from("thaoeusaneuh");
2301        assert_eq!(r4, Err(()));
2302    }
2303
2304    #[test]
2305    fn test_value_syntax_tryfrom() {
2306        let r1 = SyntaxType::try_from("UTF8strinG");
2307        assert_eq!(r1, Ok(SyntaxType::Utf8String));
2308
2309        let r2 = SyntaxType::try_from("UTF8STRING_INSensitIVE");
2310        assert_eq!(r2, Ok(SyntaxType::Utf8StringInsensitive));
2311
2312        let r3 = SyntaxType::try_from("BOOLEAN");
2313        assert_eq!(r3, Ok(SyntaxType::Boolean));
2314
2315        let r4 = SyntaxType::try_from("SYNTAX_ID");
2316        assert_eq!(r4, Ok(SyntaxType::SyntaxId));
2317
2318        let r5 = SyntaxType::try_from("INDEX_ID");
2319        assert_eq!(r5, Ok(SyntaxType::IndexId));
2320
2321        let r6 = SyntaxType::try_from("zzzzantheou");
2322        assert_eq!(r6, Err(()));
2323    }
2324
2325    #[test]
2326    fn test_value_sshkey_validation_display() {
2327        let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
2328        "tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
2329        "zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
2330        "methyst");
2331        let ed25519 = concat!(
2332            "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP",
2333            " william@amethyst"
2334        );
2335        let rsa = concat!("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTcXpclurQpyOHZBM/cDY9EvInSYkYSGe51by/wJP0Njgi",
2336        "GZUJ3HTaPqoGWux0PKd7KJki+onLYt4IwDV1RhV/GtMML2U9v94+pA8RIK4khCxvpUxlM7Kt/svjOzzzqiZfKdV37/",
2337        "OUXmM7bwVGOvm3EerDOwmO/QdzNGfkca12aWLoz97YrleXnCoAzr3IN7j3rwmfJGDyuUtGTdmyS/QWhK9FPr8Ic3eM",
2338        "QK1JSAQqVfGhA8lLbJHmnQ/b/KMl2lzzp7SXej0wPUfvI/IP3NGb8irLzq8+JssAzXGJ+HMql+mNHiSuPaktbFzZ6y",
2339        "ikMR6Rx/psU07nAkxKZDEYpNVv william@amethyst");
2340
2341        let sk1 = Value::new_sshkey_str("tag", ecdsa).expect("Invalid ssh key");
2342        assert!(sk1.validate());
2343        // to proto them
2344        let psk1 = sk1.to_proto_string_clone();
2345        assert_eq!(psk1, format!("tag: {}", ecdsa));
2346
2347        let sk2 = Value::new_sshkey_str("tag", ed25519).expect("Invalid ssh key");
2348        assert!(sk2.validate());
2349        let psk2 = sk2.to_proto_string_clone();
2350        assert_eq!(psk2, format!("tag: {}", ed25519));
2351
2352        let sk3 = Value::new_sshkey_str("tag", rsa).expect("Invalid ssh key");
2353        assert!(sk3.validate());
2354        let psk3 = sk3.to_proto_string_clone();
2355        assert_eq!(psk3, format!("tag: {}", rsa));
2356
2357        let sk4 = Value::new_sshkey_str("tag", "ntaouhtnhtnuehtnuhotnuhtneouhtneouh");
2358        assert!(sk4.is_err());
2359
2360        let sk5 = Value::new_sshkey_str(
2361            "tag",
2362            "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo",
2363        );
2364        assert!(sk5.is_err());
2365    }
2366
2367    /*
2368    #[test]
2369    fn test_value_spn() {
2370        // Create an spn vale
2371        let spnv = Value::new_spn_str("claire", "example.net.au");
2372        // create an spn pv
2373        let spnp = PartialValue::new_spn_nrs("claire", "example.net.au");
2374        // check it's indexing output
2375        let vidx_key = spnv.generate_idx_eq_keys().pop().unwrap();
2376        let idx_key = spnp.get_idx_eq_key();
2377        assert_eq!(idx_key,vidx_key);
2378        // check it can parse from name@realm
2379        let spn_parse = PartialValue::new_spn_s("claire@example.net.au").unwrap();
2380        assert_eq!(spn_parse,spnp);
2381        // check it can produce name@realm as str from the pv.
2382        assert_eq!("claire@example.net.au",spnv.to_proto_string_clone());
2383    }
2384    */
2385
2386    /*
2387    #[test]
2388    fn test_value_uint32() {
2389        assert!(Value::new_uint32_str("test").is_none());
2390        assert!(Value::new_uint32_str("18446744073709551615").is_none());
2391
2392        let u32v = Value::new_uint32_str("4000").unwrap();
2393        let u32pv = PartialValue::new_uint32_str("4000").unwrap();
2394
2395        let idx_key = u32pv.get_idx_eq_key();
2396        let vidx_key = u32v.generate_idx_eq_keys().pop().unwrap();
2397
2398        assert_eq!(idx_key,vidx_key);
2399    }
2400    */
2401
2402    #[test]
2403    fn test_value_cid() {
2404        assert!(PartialValue::new_cid_s("_").is_none());
2405    }
2406
2407    #[test]
2408    fn test_value_iname() {
2409        /*
2410         * name MUST NOT:
2411         * - be a pure int (confusion to gid/uid/linux)
2412         * - a uuid (confuses our name mapper)
2413         * - contain an @ (confuses SPN)
2414         * - can not start with _ (... api end points have _ as a magic char)
2415         * - can not have spaces (confuses too many systems :()
2416         * - can not have = or , (confuses ldap)
2417         * - can not have ., /, \ (path injection attacks)
2418         */
2419        let inv1 = Value::new_iname("1234");
2420        let inv2 = Value::new_iname("bc23f637-4439-4c07-b95d-eaed0d9e4b8b");
2421        let inv3 = Value::new_iname("hello@test.com");
2422        let inv4 = Value::new_iname("_bad");
2423        let inv5 = Value::new_iname("no spaces I'm sorry :(");
2424        let inv6 = Value::new_iname("bad=equals");
2425        let inv7 = Value::new_iname("bad,comma");
2426        let inv8 = Value::new_iname("123_456");
2427        let inv9 = Value::new_iname("🍿");
2428
2429        let val1 = Value::new_iname("William");
2430        let val2 = Value::new_iname("this_is_okay");
2431        let val3 = Value::new_iname("a123_456");
2432
2433        assert!(!inv1.validate());
2434        assert!(!inv2.validate());
2435        assert!(!inv3.validate());
2436        assert!(!inv4.validate());
2437        assert!(!inv5.validate());
2438        assert!(!inv6.validate());
2439        assert!(!inv7.validate());
2440        assert!(!inv8.validate());
2441        assert!(!inv9.validate());
2442
2443        assert!(val1.validate());
2444        assert!(val2.validate());
2445        assert!(val3.validate());
2446    }
2447
2448    #[test]
2449    fn test_value_nsuniqueid() {
2450        // nsunique
2451        // d765e707-48e111e6-8c9ebed8-f7926cc3
2452        // uuid
2453        // d765e707-48e1-11e6-8c9e-bed8f7926cc3
2454        let val1 = Value::new_nsuniqueid_s("d765e707-48e111e6-8c9ebed8-f7926cc3");
2455        let val2 = Value::new_nsuniqueid_s("D765E707-48E111E6-8C9EBED8-F7926CC3");
2456        let inv1 = Value::new_nsuniqueid_s("d765e707-48e1-11e6-8c9e-bed8f7926cc3");
2457        let inv2 = Value::new_nsuniqueid_s("xxxx");
2458
2459        assert!(inv1.is_none());
2460        assert!(inv2.is_none());
2461        assert!(val1.unwrap().validate());
2462        assert!(val2.unwrap().validate());
2463    }
2464
2465    #[test]
2466    fn test_value_datetime() {
2467        // Datetimes must always convert to UTC, and must always be rfc3339
2468        let val1 = Value::new_datetime_s("2020-09-25T11:22:02+10:00").expect("Must be valid");
2469        assert!(val1.validate());
2470        let val2 = Value::new_datetime_s("2020-09-25T01:22:02+00:00").expect("Must be valid");
2471        assert!(val2.validate());
2472        // Spaces are now valid in rfc3339 for parsing.
2473        let val3 = Value::new_datetime_s("2020-09-25 01:22:02+00:00").expect("Must be valid");
2474        assert!(val3.validate());
2475
2476        assert!(Value::new_datetime_s("2020-09-25T01:22:02").is_none());
2477        assert!(Value::new_datetime_s("2020-09-25").is_none());
2478        assert!(Value::new_datetime_s("2020-09-25T01:22:02+10").is_none());
2479
2480        // Manually craft
2481        let inv1 = Value::DateTime(
2482            OffsetDateTime::now_utc()
2483                .to_offset(time::UtcOffset::from_whole_seconds(36000).unwrap()),
2484        );
2485        assert!(!inv1.validate());
2486
2487        let val3 = Value::DateTime(OffsetDateTime::now_utc());
2488        assert!(val3.validate());
2489    }
2490
2491    #[test]
2492    fn test_value_email_address() {
2493        // https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
2494        let val1 = Value::new_email_address_s("william@blackhats.net.au");
2495        let val2 = Value::new_email_address_s("alice@idm.example.com");
2496        let val3 = Value::new_email_address_s("test+mailbox@foo.com");
2497        let inv1 = Value::new_email_address_s("william");
2498        let inv2 = Value::new_email_address_s("test~uuid");
2499
2500        assert!(inv1.is_none());
2501        assert!(inv2.is_none());
2502        assert!(val1.unwrap().validate());
2503        assert!(val2.unwrap().validate());
2504        assert!(val3.unwrap().validate());
2505    }
2506
2507    #[test]
2508    fn test_value_url() {
2509        // https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
2510        let val1 = Value::new_url_s("https://localhost:8000/search?q=text#hello");
2511        let val2 = Value::new_url_s("https://github.com/kanidm/kanidm");
2512        let val3 = Value::new_url_s("ldap://foo.com");
2513        let inv1 = Value::new_url_s("127.0.");
2514        let inv2 = Value::new_url_s("🤔");
2515
2516        assert!(inv1.is_none());
2517        assert!(inv2.is_none());
2518        assert!(val1.is_some());
2519        assert!(val2.is_some());
2520        assert!(val3.is_some());
2521    }
2522
2523    #[test]
2524    fn test_singleline() {
2525        assert!(Value::validate_singleline("no new lines"));
2526
2527        assert!(!Value::validate_singleline("contains a \n new line"));
2528        assert!(!Value::validate_singleline("contains a \r return feed"));
2529        assert!(!Value::validate_singleline("contains a \t tab char"));
2530    }
2531
2532    #[test]
2533    fn test_str_escapes() {
2534        assert!(Value::validate_str_escapes("safe str"));
2535        assert!(Value::validate_str_escapes("🙃 emoji are 👍"));
2536
2537        assert!(!Value::validate_str_escapes("naughty \x1b[31mred"));
2538    }
2539
2540    #[test]
2541    fn test_value_key_internal_status_order() {
2542        assert!(KeyStatus::Valid < KeyStatus::Retained);
2543        assert!(KeyStatus::Retained < KeyStatus::Revoked);
2544    }
2545
2546    #[test]
2547    fn test_value_session_state_order() {
2548        assert!(
2549            SessionState::RevokedAt(Cid::new_zero()) > SessionState::RevokedAt(Cid::new_count(1))
2550        );
2551        assert!(
2552            SessionState::RevokedAt(Cid::new_zero())
2553                > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2554        );
2555        assert!(
2556            SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH + Duration::from_secs(1))
2557                > SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH)
2558        );
2559        assert!(SessionState::ExpiresAt(OffsetDateTime::UNIX_EPOCH) > SessionState::NeverExpires);
2560    }
2561}