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