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