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