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