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