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