mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-18 23:13:56 +02:00
20240820 SCIM value (#2992)
Add the basics of scim value serialisation to entries.
This commit is contained in:
parent
413ef9210a
commit
0fac1f301e
Cargo.lockCargo.toml
libs/scim_proto/src
proto
server
core/src/https/apidocs
lib
tools/iam_migrations
116
Cargo.lock
generated
116
Cargo.lock
generated
|
@ -17,6 +17,12 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
|
@ -540,7 +546,7 @@ dependencies = [
|
|||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.7.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
@ -628,7 +634,7 @@ dependencies = [
|
|||
"lazycell",
|
||||
"log",
|
||||
"peeking_take_while",
|
||||
"prettyplease 0.2.20",
|
||||
"prettyplease 0.2.22",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
@ -651,7 +657,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"prettyplease 0.2.20",
|
||||
"prettyplease 0.2.22",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
@ -743,9 +749,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.17.0"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31"
|
||||
checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
@ -767,9 +773,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.13"
|
||||
version = "1.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
|
||||
checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
@ -878,9 +884,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.23"
|
||||
version = "4.5.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531d7959c5bbb6e266cecdd0f20213639c3a5c3e4d615f97db87661745f781ff"
|
||||
checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
@ -1658,9 +1664,9 @@ checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
|
|||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
|
||||
[[package]]
|
||||
name = "fernet"
|
||||
|
@ -1686,9 +1692,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.24"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550"
|
||||
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
@ -1710,12 +1716,12 @@ checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
|
|||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.31"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
|
||||
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2025,9 +2031,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gix-config-value"
|
||||
version = "0.14.7"
|
||||
version = "0.14.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b328997d74dd15dc71b2773b162cb4af9a25c424105e4876e6d0686ab41c383e"
|
||||
checksum = "03f76169faa0dec598eac60f83d7fcdd739ec16596eca8fb144c88973dbe6f8c"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
|
@ -2097,9 +2103,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gix-fs"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6adf99c27cdf17b1c4d77680c917e0d94d8783d4e1c73d3be0d1d63107163d7a"
|
||||
checksum = "f2bfe6249cfea6d0c0e0990d5226a4cb36f030444ba9e35e0639275db8f98575"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"gix-features",
|
||||
|
@ -2108,9 +2114,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gix-glob"
|
||||
version = "0.16.4"
|
||||
version = "0.16.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7df15afa265cc8abe92813cd354d522f1ac06b29ec6dfa163ad320575cb447"
|
||||
checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
|
@ -2220,9 +2226,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gix-path"
|
||||
version = "0.10.9"
|
||||
version = "0.10.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d23d5bbda31344d8abc8de7c075b3cf26e5873feba7c4a15d916bce67382bd9"
|
||||
checksum = "38d5b8722112fa2fa87135298780bc833b0e9f6c56cc82795d209804b3a03484"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-trace",
|
||||
|
@ -2308,9 +2314,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gix-sec"
|
||||
version = "0.10.7"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1547d26fa5693a7f34f05b4a3b59a90890972922172653bcb891ab3f09f436df"
|
||||
checksum = "0fe4d52f30a737bbece5276fab5d3a8b276dc2650df963e293d0673be34e7a5f"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"gix-path",
|
||||
|
@ -2320,9 +2326,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gix-tempfile"
|
||||
version = "14.0.1"
|
||||
version = "14.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "006acf5a613e0b5cf095d8e4b3f48c12a60d9062aa2b2dd105afaf8344a5600c"
|
||||
checksum = "046b4927969fa816a150a0cda2e62c80016fe11fb3c3184e4dddf4e542f108aa"
|
||||
dependencies = [
|
||||
"gix-fs",
|
||||
"libc",
|
||||
|
@ -2356,9 +2362,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gix-url"
|
||||
version = "0.27.4"
|
||||
version = "0.27.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2eb9b35bba92ea8f0b5ab406fad3cf6b87f7929aa677ff10aa042c6da621156"
|
||||
checksum = "fd280c5e84fb22e128ed2a053a0daeacb6379469be6a85e3d518a0636e160c89"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-features",
|
||||
|
@ -2607,9 +2613,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
|
||||
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
|
@ -2827,7 +2833,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.5",
|
||||
"h2 0.4.6",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
|
@ -3293,12 +3299,13 @@ name = "kanidm_proto"
|
|||
version = "1.4.0-dev"
|
||||
dependencies = [
|
||||
"base32",
|
||||
"base64urlsafedata 0.5.0",
|
||||
"enum-iterator",
|
||||
"num_enum",
|
||||
"scim_proto",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"smartstring",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
|
@ -3703,16 +3710,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ldap3_client"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b070f5b1cbe8f7470e341057513fb4936d96b7409b0cb759748d36b52ecbf38f"
|
||||
checksum = "c6027fc899bda353fe645cdcab9de93b0d2fa4731c105ad449fed22c455b61ff"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"base64urlsafedata 0.5.0",
|
||||
"futures-util",
|
||||
"ldap3_proto",
|
||||
"openssl",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"tokio",
|
||||
"tokio-openssl",
|
||||
"tokio-util",
|
||||
|
@ -3723,9 +3730,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ldap3_proto"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86e00102210e670682f609534b688570ad69820c50608e88a33c17d57356d77a"
|
||||
checksum = "e9a047c1b49d3b4da70f52ac54310dcd879c9b7fef658615ff17f6212ae7411e"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
|
@ -3971,6 +3978,15 @@ dependencies = [
|
|||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mintex"
|
||||
version = "0.1.3"
|
||||
|
@ -4826,9 +4842,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.20"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
|
||||
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.76",
|
||||
|
@ -5043,9 +5059,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
|
@ -5116,7 +5132,7 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.5",
|
||||
"h2 0.4.6",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
|
@ -5287,9 +5303,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
version = "0.38.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
|
@ -5329,9 +5345,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
|
|||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.102.6"
|
||||
version = "0.102.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
|
||||
checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
|
@ -6432,9 +6448,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
|
|
|
@ -202,8 +202,8 @@ js-sys = "^0.3.70"
|
|||
kanidmd_web_ui_shared = { path = "./server/web_ui/shared" }
|
||||
# REMOVE this
|
||||
lazy_static = "^1.5.0"
|
||||
ldap3_client = "^0.5"
|
||||
ldap3_proto = { version = "^0.5", features = ["serde"] }
|
||||
ldap3_client = "^0.5.2"
|
||||
ldap3_proto = { version = "^0.5.2", features = ["serde"] }
|
||||
|
||||
libc = "^0.2.158"
|
||||
libnss = "^0.8.0"
|
||||
|
|
|
@ -26,6 +26,26 @@ pub enum ScimFilter {
|
|||
Less(AttrPath, Value),
|
||||
GreaterOrEqual(AttrPath, Value),
|
||||
LessOrEqual(AttrPath, Value),
|
||||
|
||||
Complex(String, Box<ScimComplexFilter>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ScimComplexFilter {
|
||||
Or(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
|
||||
And(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
|
||||
Not(Box<ScimComplexFilter>),
|
||||
|
||||
Present(String),
|
||||
Equal(String, Value),
|
||||
NotEqual(String, Value),
|
||||
Contains(String, Value),
|
||||
StartsWith(String, Value),
|
||||
EndsWith(String, Value),
|
||||
Greater(String, Value),
|
||||
Less(String, Value),
|
||||
GreaterOrEqual(String, Value),
|
||||
LessOrEqual(String, Value),
|
||||
}
|
||||
|
||||
// separator()* "(" e:term() ")" separator()* { e }
|
||||
|
@ -52,9 +72,38 @@ peg::parser! {
|
|||
ScimFilter::Not(Box::new(e))
|
||||
}
|
||||
--
|
||||
// separator()* e:parse() separator()* { e }
|
||||
"(" e:parse() ")" { e }
|
||||
a:attrname()"[" e:parse_complex() "]" {
|
||||
ScimFilter::Complex(
|
||||
a,
|
||||
Box::new(e)
|
||||
)
|
||||
}
|
||||
--
|
||||
a:attrexp() { a }
|
||||
"(" e:parse() ")" { e }
|
||||
}
|
||||
|
||||
pub rule parse_complex() -> ScimComplexFilter = precedence!{
|
||||
a:(@) separator()+ "or" separator()+ b:@ {
|
||||
ScimComplexFilter::Or(
|
||||
Box::new(a),
|
||||
Box::new(b)
|
||||
)
|
||||
}
|
||||
--
|
||||
a:(@) separator()+ "and" separator()+ b:@ {
|
||||
ScimComplexFilter::And(
|
||||
Box::new(a),
|
||||
Box::new(b)
|
||||
)
|
||||
}
|
||||
--
|
||||
"not" separator()+ "(" e:parse_complex() ")" {
|
||||
ScimComplexFilter::Not(Box::new(e))
|
||||
}
|
||||
--
|
||||
a:complex_attrexp() { a }
|
||||
"(" e:parse_complex() ")" { e }
|
||||
}
|
||||
|
||||
pub(crate) rule attrexp() -> ScimFilter =
|
||||
|
@ -99,17 +148,59 @@ peg::parser! {
|
|||
pub(crate) rule le() -> ScimFilter =
|
||||
a:attrpath() separator()+ "le" separator()+ v:value() { ScimFilter::LessOrEqual(a, v) }
|
||||
|
||||
pub(crate) rule complex_attrexp() -> ScimComplexFilter =
|
||||
c_pres()
|
||||
/ c_eq()
|
||||
/ c_ne()
|
||||
/ c_co()
|
||||
/ c_sw()
|
||||
/ c_ew()
|
||||
/ c_gt()
|
||||
/ c_lt()
|
||||
/ c_ge()
|
||||
/ c_le()
|
||||
|
||||
pub(crate) rule c_pres() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "pr" { ScimComplexFilter::Present(a) }
|
||||
|
||||
pub(crate) rule c_eq() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "eq" separator()+ v:value() { ScimComplexFilter::Equal(a, v) }
|
||||
|
||||
pub(crate) rule c_ne() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "ne" separator()+ v:value() { ScimComplexFilter::NotEqual(a, v) }
|
||||
|
||||
pub(crate) rule c_co() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "co" separator()+ v:value() { ScimComplexFilter::Contains(a, v) }
|
||||
|
||||
pub(crate) rule c_sw() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "sw" separator()+ v:value() { ScimComplexFilter::StartsWith(a, v) }
|
||||
|
||||
pub(crate) rule c_ew() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "ew" separator()+ v:value() { ScimComplexFilter::EndsWith(a, v) }
|
||||
|
||||
pub(crate) rule c_gt() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "gt" separator()+ v:value() { ScimComplexFilter::Greater(a, v) }
|
||||
|
||||
pub(crate) rule c_lt() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "lt" separator()+ v:value() { ScimComplexFilter::Less(a, v) }
|
||||
|
||||
pub(crate) rule c_ge() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "ge" separator()+ v:value() { ScimComplexFilter::GreaterOrEqual(a, v) }
|
||||
|
||||
pub(crate) rule c_le() -> ScimComplexFilter =
|
||||
a:attrname() separator()+ "le" separator()+ v:value() { ScimComplexFilter::LessOrEqual(a, v) }
|
||||
|
||||
rule separator() =
|
||||
['\n' | ' ' | '\t' ]
|
||||
|
||||
rule operator() =
|
||||
['\n' | ' ' | '\t' | '(' | ')' ]
|
||||
['\n' | ' ' | '\t' | '(' | ')' | '[' | ']' ]
|
||||
|
||||
rule value() -> Value =
|
||||
barevalue()
|
||||
|
||||
rule barevalue() -> Value =
|
||||
s:$((!operator()[_])*) {? serde_json::from_str(s).map_err(|_| "invalid json value" ) }
|
||||
s:$((!operator()[_])*) {? eprintln!("--> {}", s); serde_json::from_str(s).map_err(|_| "invalid json value" ) }
|
||||
|
||||
pub(crate) rule attrpath() -> AttrPath =
|
||||
a:attrname() s:subattr()? { AttrPath { a, s } }
|
||||
|
@ -381,6 +472,48 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scimfilter_complex() {
|
||||
let f = scimfilter::parse("emails[type eq \"work\"]");
|
||||
eprintln!("-- {:?}", f);
|
||||
assert!(f.is_ok());
|
||||
|
||||
let f = scimfilter::parse("emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co \"@foo.com\"]");
|
||||
eprintln!("{:?}", f);
|
||||
|
||||
assert_eq!(
|
||||
f,
|
||||
Ok(ScimFilter::Or(
|
||||
Box::new(ScimFilter::Complex(
|
||||
"emails".to_string(),
|
||||
Box::new(ScimComplexFilter::And(
|
||||
Box::new(ScimComplexFilter::Equal(
|
||||
"type".to_string(),
|
||||
Value::String("work".to_string())
|
||||
)),
|
||||
Box::new(ScimComplexFilter::Contains(
|
||||
"value".to_string(),
|
||||
Value::String("@example.com".to_string())
|
||||
))
|
||||
))
|
||||
)),
|
||||
Box::new(ScimFilter::Complex(
|
||||
"ims".to_string(),
|
||||
Box::new(ScimComplexFilter::And(
|
||||
Box::new(ScimComplexFilter::Equal(
|
||||
"type".to_string(),
|
||||
Value::String("xmpp".to_string())
|
||||
)),
|
||||
Box::new(ScimComplexFilter::Contains(
|
||||
"value".to_string(),
|
||||
Value::String("@foo.com".to_string())
|
||||
))
|
||||
))
|
||||
))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scimfilter_precedence_1() {
|
||||
let f = scimfilter::parse("a pr or b pr and c pr or d pr");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::ScimEntry;
|
||||
use crate::ScimEntryHeader;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
@ -15,7 +15,7 @@ pub struct Member {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Group {
|
||||
#[serde(flatten)]
|
||||
entry: ScimEntry,
|
||||
entry: ScimEntryHeader,
|
||||
|
||||
display_name: String,
|
||||
members: Vec<Member>,
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
#![deny(clippy::needless_pass_by_value)]
|
||||
#![deny(clippy::trivially_copy_pass_by_ref)]
|
||||
|
||||
use base64urlsafedata::Base64UrlSafeData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use time::OffsetDateTime;
|
||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
use url::Url;
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
@ -25,7 +26,7 @@ pub mod user;
|
|||
pub mod prelude {
|
||||
pub use crate::constants::*;
|
||||
pub use crate::user::MultiValueAttr;
|
||||
pub use crate::{ScimAttr, ScimComplexAttr, ScimEntry, ScimEntryGeneric, ScimMeta, ScimValue};
|
||||
pub use crate::{ScimAttr, ScimComplexAttr, ScimEntry, ScimEntryHeader, ScimMeta, ScimValue};
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
|
||||
|
@ -38,11 +39,56 @@ pub enum ScimAttr {
|
|||
// These can't be implicitly decoded because we may not know the intent, but we can *encode* them.
|
||||
// That's why "String" is above this because it catches anything during deserialization before
|
||||
// this point.
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
DateTime(OffsetDateTime),
|
||||
Binary(Vec<u8>),
|
||||
|
||||
Binary(Base64UrlSafeData),
|
||||
Reference(Url),
|
||||
}
|
||||
|
||||
impl ScimAttr {
|
||||
pub fn parse_as_datetime(&self) -> Option<Self> {
|
||||
let s = match self {
|
||||
ScimAttr::String(s) => s,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
OffsetDateTime::parse(s, &Rfc3339)
|
||||
.map(ScimAttr::DateTime)
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ScimAttr {
|
||||
fn from(s: String) -> Self {
|
||||
ScimAttr::String(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for ScimAttr {
|
||||
fn from(b: bool) -> Self {
|
||||
ScimAttr::Bool(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for ScimAttr {
|
||||
fn from(i: u32) -> Self {
|
||||
ScimAttr::Integer(i as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ScimAttr {
|
||||
fn from(data: Vec<u8>) -> Self {
|
||||
ScimAttr::Binary(data.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OffsetDateTime> for ScimAttr {
|
||||
fn from(odt: OffsetDateTime) -> Self {
|
||||
ScimAttr::DateTime(odt)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScimAttr> for ScimValue {
|
||||
fn from(sa: ScimAttr) -> Self {
|
||||
ScimValue::Simple(sa)
|
||||
|
@ -105,7 +151,7 @@ pub struct ScimMeta {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimEntry {
|
||||
pub struct ScimEntryHeader {
|
||||
pub schemas: Vec<String>,
|
||||
pub id: Uuid,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -116,7 +162,7 @@ pub struct ScimEntry {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimEntryGeneric {
|
||||
pub struct ScimEntry {
|
||||
pub schemas: Vec<String>,
|
||||
pub id: Uuid,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -136,7 +182,7 @@ mod tests {
|
|||
fn parse_scim_entry() {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let u: ScimEntryGeneric =
|
||||
let u: ScimEntry =
|
||||
serde_json::from_str(RFC7643_USER).expect("Failed to parse RFC7643_USER");
|
||||
|
||||
tracing::trace!(?u);
|
||||
|
@ -144,4 +190,136 @@ mod tests {
|
|||
let s = serde_json::to_string_pretty(&u).expect("Failed to serialise RFC7643_USER");
|
||||
eprintln!("{}", s);
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// asymmetric serde tests
|
||||
|
||||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||
use std::fmt;
|
||||
use uuid::Uuid;
|
||||
|
||||
// -> For values, we need to be able to capture and handle "what if it's X" type? But
|
||||
// we can't know the "intent" until we hit schema, so we have to preserve the string
|
||||
// types as well. In this type, we make this *asymmetric*. When we parse we use
|
||||
// this type which has the "maybes" but when we serialise, we use concrete types
|
||||
// instead.
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum TestB {
|
||||
Integer(i64),
|
||||
Decimal(f64),
|
||||
MaybeUuid(Uuid, String),
|
||||
String(String),
|
||||
}
|
||||
|
||||
struct TestBVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for TestBVisitor {
|
||||
type Value = TestB;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("cheese")
|
||||
}
|
||||
|
||||
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(TestB::Decimal(v))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(TestB::Integer(v as i64))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(TestB::Integer(v))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(if let Ok(u) = Uuid::parse_str(v) {
|
||||
TestB::MaybeUuid(u, v.to_string())
|
||||
} else {
|
||||
TestB::String(v.to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TestB {
|
||||
fn deserialize<D>(deserializer: D) -> Result<TestB, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(TestBVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_enum_b() {
|
||||
let x: TestB = serde_json::from_str("10").unwrap();
|
||||
eprintln!("{:?}", x);
|
||||
|
||||
let x: TestB = serde_json::from_str("10.5").unwrap();
|
||||
eprintln!("{:?}", x);
|
||||
|
||||
let x: TestB = serde_json::from_str(r#""550e8400-e29b-41d4-a716-446655440000""#).unwrap();
|
||||
eprintln!("{:?}", x);
|
||||
|
||||
let x: TestB = serde_json::from_str(r#""Value""#).unwrap();
|
||||
eprintln!("{:?}", x);
|
||||
}
|
||||
|
||||
// In reverse when we serialise, we can simply use untagged on an enum.
|
||||
// Potentially this lets us have more "scim" types for dedicated serialisations
|
||||
// over the generic ones.
|
||||
|
||||
#[derive(Serialize, Debug, Deserialize, Clone)]
|
||||
#[serde(rename_all = "lowercase", from = "&str", into = "String")]
|
||||
enum TestC {
|
||||
A,
|
||||
B,
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl From<TestC> for String {
|
||||
fn from(v: TestC) -> String {
|
||||
match v {
|
||||
TestC::A => "A".to_string(),
|
||||
TestC::B => "B".to_string(),
|
||||
TestC::Unknown(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for TestC {
|
||||
fn from(v: &str) -> TestC {
|
||||
match v {
|
||||
"A" => TestC::A,
|
||||
"B" => TestC::B,
|
||||
_ => TestC::Unknown(v.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_enum_c() {
|
||||
let x = serde_json::to_string(&TestC::A).unwrap();
|
||||
eprintln!("{:?}", x);
|
||||
|
||||
let x = serde_json::to_string(&TestC::B).unwrap();
|
||||
eprintln!("{:?}", x);
|
||||
|
||||
let x = serde_json::to_string(&TestC::Unknown("X".to_string())).unwrap();
|
||||
eprintln!("{:?}", x);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::ScimEntry;
|
||||
use crate::ScimEntryHeader;
|
||||
use base64urlsafedata::Base64UrlSafeData;
|
||||
use std::fmt;
|
||||
use url::Url;
|
||||
|
@ -145,7 +145,7 @@ pub struct Group {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct User {
|
||||
#[serde(flatten)]
|
||||
entry: ScimEntry,
|
||||
entry: ScimEntryHeader,
|
||||
// required, must be unique, string.
|
||||
user_name: String,
|
||||
// Components of the users name.
|
||||
|
|
|
@ -20,12 +20,13 @@ wasm = ["webauthn-rs-proto/wasm"]
|
|||
|
||||
[dependencies]
|
||||
base32 = { workspace = true }
|
||||
base64urlsafedata = { workspace = true }
|
||||
enum-iterator = { workspace = true }
|
||||
num_enum = { workspace = true }
|
||||
scim_proto = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
serde_with = { workspace = true, features = ["time_0_3", "base64", "hex"] }
|
||||
smartstring = { workspace = true, features = ["serde"] }
|
||||
time = { workspace = true, features = ["serde", "std"] }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
|
|
593
proto/src/attribute.rs
Normal file
593
proto/src/attribute.rs
Normal file
|
@ -0,0 +1,593 @@
|
|||
use enum_iterator::Sequence;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::internal::OperationError;
|
||||
use std::fmt;
|
||||
use tracing::trace;
|
||||
|
||||
pub use smartstring::alias::String as AttrString;
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, Hash,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase", try_from = "&str", into = "AttrString")]
|
||||
pub enum Attribute {
|
||||
Account,
|
||||
AccountExpire,
|
||||
AccountValidFrom,
|
||||
AcpCreateAttr,
|
||||
AcpCreateClass,
|
||||
AcpEnable,
|
||||
AcpModifyClass,
|
||||
AcpModifyPresentAttr,
|
||||
AcpModifyRemovedAttr,
|
||||
AcpReceiver,
|
||||
AcpReceiverGroup,
|
||||
AcpSearchAttr,
|
||||
AcpTargetScope,
|
||||
ApiTokenSession,
|
||||
ApplicationPassword,
|
||||
AttestedPasskeys,
|
||||
Attr,
|
||||
AttributeName,
|
||||
AttributeType,
|
||||
AuthSessionExpiry,
|
||||
AuthPasswordMinimumLength,
|
||||
BadlistPassword,
|
||||
Certificate,
|
||||
Claim,
|
||||
Class,
|
||||
ClassName,
|
||||
Cn,
|
||||
CookiePrivateKey,
|
||||
CredentialUpdateIntentToken,
|
||||
CredentialTypeMinimum,
|
||||
DeniedName,
|
||||
Description,
|
||||
DirectMemberOf,
|
||||
DisplayName,
|
||||
Dn,
|
||||
Domain,
|
||||
DomainDevelopmentTaint,
|
||||
DomainDisplayName,
|
||||
DomainLdapBasedn,
|
||||
DomainName,
|
||||
DomainSsid,
|
||||
DomainTokenKey,
|
||||
DomainUuid,
|
||||
DynGroup,
|
||||
DynGroupFilter,
|
||||
DynMember,
|
||||
Email,
|
||||
EmailAlternative,
|
||||
EmailPrimary,
|
||||
EntryDn,
|
||||
EntryManagedBy,
|
||||
EntryUuid,
|
||||
Es256PrivateKeyDer,
|
||||
Excludes,
|
||||
FernetPrivateKeyStr,
|
||||
Gecos,
|
||||
GidNumber,
|
||||
GrantUiHint,
|
||||
Group,
|
||||
IdVerificationEcKey,
|
||||
Image,
|
||||
Index,
|
||||
IpaNtHash,
|
||||
IpaSshPubKey,
|
||||
JwsEs256PrivateKey,
|
||||
KeyActionRotate,
|
||||
KeyActionRevoke,
|
||||
KeyActionImportJwsEs256,
|
||||
KeyInternalData,
|
||||
KeyProvider,
|
||||
LastModifiedCid,
|
||||
LdapAllowUnixPwBind,
|
||||
/// An LDAP Compatible emailAddress
|
||||
LdapEmailAddress,
|
||||
/// An LDAP Compatible sshkeys virtual attribute
|
||||
LdapKeys,
|
||||
LegalName,
|
||||
LimitSearchMaxResults,
|
||||
LimitSearchMaxFilterTest,
|
||||
LinkedGroup,
|
||||
LoginShell,
|
||||
Mail,
|
||||
May,
|
||||
Member,
|
||||
MemberOf,
|
||||
MultiValue,
|
||||
Must,
|
||||
Name,
|
||||
NameHistory,
|
||||
NoIndex,
|
||||
NsUniqueId,
|
||||
NsAccountLock,
|
||||
OAuth2AllowInsecureClientDisablePkce,
|
||||
OAuth2AllowLocalhostRedirect,
|
||||
OAuth2ConsentScopeMap,
|
||||
OAuth2JwtLegacyCryptoEnable,
|
||||
OAuth2PreferShortUsername,
|
||||
OAuth2RsBasicSecret,
|
||||
OAuth2RsClaimMap,
|
||||
OAuth2RsImplicitScopes,
|
||||
OAuth2RsName,
|
||||
OAuth2RsOrigin,
|
||||
OAuth2RsOriginLanding,
|
||||
OAuth2RsScopeMap,
|
||||
OAuth2RsSupScopeMap,
|
||||
OAuth2RsTokenKey,
|
||||
OAuth2Session,
|
||||
OAuth2StrictRedirectUri,
|
||||
ObjectClass,
|
||||
OtherNoIndex,
|
||||
PassKeys,
|
||||
PasswordImport,
|
||||
PatchLevel,
|
||||
Phantom,
|
||||
PrimaryCredential,
|
||||
PrivateCookieKey,
|
||||
PrivilegeExpiry,
|
||||
RadiusSecret,
|
||||
RecycledDirectMemberOf,
|
||||
Refers,
|
||||
Replicated,
|
||||
Rs256PrivateKeyDer,
|
||||
Scope,
|
||||
SourceUuid,
|
||||
Spn,
|
||||
/// An LDAP-compatible sshpublickey
|
||||
LdapSshPublicKey,
|
||||
/// The Kanidm-local ssh_publickey
|
||||
SshPublicKey,
|
||||
SudoHost,
|
||||
Supplements,
|
||||
SystemSupplements,
|
||||
SyncAllowed,
|
||||
SyncClass,
|
||||
SyncCookie,
|
||||
SyncCredentialPortal,
|
||||
SyncExternalId,
|
||||
SyncParentUuid,
|
||||
SyncTokenSession,
|
||||
SyncYieldAuthority,
|
||||
Syntax,
|
||||
SystemExcludes,
|
||||
SystemMay,
|
||||
SystemMust,
|
||||
Term,
|
||||
TotpImport,
|
||||
Uid,
|
||||
UidNumber,
|
||||
Unique,
|
||||
UnixPassword,
|
||||
UnixPasswordImport,
|
||||
UserAuthTokenSession,
|
||||
UserId,
|
||||
UserPassword,
|
||||
Uuid,
|
||||
Version,
|
||||
WebauthnAttestationCaList,
|
||||
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
NonExist,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TestAttr,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TestNumber,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Extra,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TestNotAllowed,
|
||||
// Custom(AttrString),
|
||||
}
|
||||
|
||||
impl AsRef<str> for Attribute {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Attribute> for &'static str {
|
||||
fn from(value: &Attribute) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AttrString> for Attribute {
|
||||
type Error = OperationError;
|
||||
|
||||
fn try_from(value: &AttrString) -> Result<Self, Self::Error> {
|
||||
Attribute::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Attribute {
|
||||
type Error = OperationError;
|
||||
fn try_from(val: &'a str) -> Result<Self, OperationError> {
|
||||
let res = match val {
|
||||
ATTR_ACCOUNT => Attribute::Account,
|
||||
ATTR_ACCOUNT_EXPIRE => Attribute::AccountExpire,
|
||||
ATTR_ACCOUNT_VALID_FROM => Attribute::AccountValidFrom,
|
||||
ATTR_ACP_CREATE_ATTR => Attribute::AcpCreateAttr,
|
||||
ATTR_ACP_CREATE_CLASS => Attribute::AcpCreateClass,
|
||||
ATTR_ACP_ENABLE => Attribute::AcpEnable,
|
||||
ATTR_ACP_MODIFY_CLASS => Attribute::AcpModifyClass,
|
||||
ATTR_ACP_MODIFY_PRESENTATTR => Attribute::AcpModifyPresentAttr,
|
||||
ATTR_ACP_MODIFY_REMOVEDATTR => Attribute::AcpModifyRemovedAttr,
|
||||
ATTR_ACP_RECEIVER => Attribute::AcpReceiver,
|
||||
ATTR_ACP_RECEIVER_GROUP => Attribute::AcpReceiverGroup,
|
||||
ATTR_ACP_SEARCH_ATTR => Attribute::AcpSearchAttr,
|
||||
ATTR_ACP_TARGET_SCOPE => Attribute::AcpTargetScope,
|
||||
ATTR_API_TOKEN_SESSION => Attribute::ApiTokenSession,
|
||||
ATTR_APPLICATION_PASSWORD => Attribute::ApplicationPassword,
|
||||
ATTR_ATTESTED_PASSKEYS => Attribute::AttestedPasskeys,
|
||||
ATTR_ATTR => Attribute::Attr,
|
||||
ATTR_ATTRIBUTENAME => Attribute::AttributeName,
|
||||
ATTR_ATTRIBUTETYPE => Attribute::AttributeType,
|
||||
ATTR_AUTH_SESSION_EXPIRY => Attribute::AuthSessionExpiry,
|
||||
ATTR_AUTH_PASSWORD_MINIMUM_LENGTH => Attribute::AuthPasswordMinimumLength,
|
||||
ATTR_BADLIST_PASSWORD => Attribute::BadlistPassword,
|
||||
ATTR_CERTIFICATE => Attribute::Certificate,
|
||||
ATTR_CLAIM => Attribute::Claim,
|
||||
ATTR_CLASS => Attribute::Class,
|
||||
ATTR_CLASSNAME => Attribute::ClassName,
|
||||
ATTR_CN => Attribute::Cn,
|
||||
ATTR_COOKIE_PRIVATE_KEY => Attribute::CookiePrivateKey,
|
||||
ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN => Attribute::CredentialUpdateIntentToken,
|
||||
ATTR_CREDENTIAL_TYPE_MINIMUM => Attribute::CredentialTypeMinimum,
|
||||
ATTR_DENIED_NAME => Attribute::DeniedName,
|
||||
ATTR_DESCRIPTION => Attribute::Description,
|
||||
ATTR_DIRECTMEMBEROF => Attribute::DirectMemberOf,
|
||||
ATTR_DISPLAYNAME => Attribute::DisplayName,
|
||||
ATTR_DN => Attribute::Dn,
|
||||
ATTR_DOMAIN => Attribute::Domain,
|
||||
ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName,
|
||||
ATTR_DOMAIN_DEVELOPMENT_TAINT => Attribute::DomainDevelopmentTaint,
|
||||
ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn,
|
||||
ATTR_DOMAIN_NAME => Attribute::DomainName,
|
||||
ATTR_DOMAIN_SSID => Attribute::DomainSsid,
|
||||
ATTR_DOMAIN_TOKEN_KEY => Attribute::DomainTokenKey,
|
||||
ATTR_DOMAIN_UUID => Attribute::DomainUuid,
|
||||
ATTR_DYNGROUP => Attribute::DynGroup,
|
||||
ATTR_DYNGROUP_FILTER => Attribute::DynGroupFilter,
|
||||
ATTR_DYNMEMBER => Attribute::DynMember,
|
||||
ATTR_EMAIL => Attribute::Email,
|
||||
ATTR_EMAIL_ALTERNATIVE => Attribute::EmailAlternative,
|
||||
ATTR_EMAIL_PRIMARY => Attribute::EmailPrimary,
|
||||
ATTR_ENTRYDN => Attribute::EntryDn,
|
||||
ATTR_ENTRY_MANAGED_BY => Attribute::EntryManagedBy,
|
||||
ATTR_ENTRYUUID => Attribute::EntryUuid,
|
||||
ATTR_ES256_PRIVATE_KEY_DER => Attribute::Es256PrivateKeyDer,
|
||||
ATTR_EXCLUDES => Attribute::Excludes,
|
||||
ATTR_FERNET_PRIVATE_KEY_STR => Attribute::FernetPrivateKeyStr,
|
||||
ATTR_GECOS => Attribute::Gecos,
|
||||
ATTR_GIDNUMBER => Attribute::GidNumber,
|
||||
ATTR_GRANT_UI_HINT => Attribute::GrantUiHint,
|
||||
ATTR_GROUP => Attribute::Group,
|
||||
ATTR_ID_VERIFICATION_ECKEY => Attribute::IdVerificationEcKey,
|
||||
ATTR_IMAGE => Attribute::Image,
|
||||
ATTR_INDEX => Attribute::Index,
|
||||
ATTR_IPANTHASH => Attribute::IpaNtHash,
|
||||
ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey,
|
||||
ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey,
|
||||
ATTR_KEY_ACTION_ROTATE => Attribute::KeyActionRotate,
|
||||
ATTR_KEY_ACTION_REVOKE => Attribute::KeyActionRevoke,
|
||||
ATTR_KEY_ACTION_IMPORT_JWS_ES256 => Attribute::KeyActionImportJwsEs256,
|
||||
ATTR_KEY_INTERNAL_DATA => Attribute::KeyInternalData,
|
||||
ATTR_KEY_PROVIDER => Attribute::KeyProvider,
|
||||
ATTR_LAST_MODIFIED_CID => Attribute::LastModifiedCid,
|
||||
ATTR_LDAP_ALLOW_UNIX_PW_BIND => Attribute::LdapAllowUnixPwBind,
|
||||
ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress,
|
||||
ATTR_LDAP_KEYS => Attribute::LdapKeys,
|
||||
ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey,
|
||||
ATTR_LEGALNAME => Attribute::LegalName,
|
||||
ATTR_LINKEDGROUP => Attribute::LinkedGroup,
|
||||
ATTR_LOGINSHELL => Attribute::LoginShell,
|
||||
ATTR_LIMIT_SEARCH_MAX_RESULTS => Attribute::LimitSearchMaxResults,
|
||||
ATTR_LIMIT_SEARCH_MAX_FILTER_TEST => Attribute::LimitSearchMaxFilterTest,
|
||||
ATTR_MAIL => Attribute::Mail,
|
||||
ATTR_MAY => Attribute::May,
|
||||
ATTR_MEMBER => Attribute::Member,
|
||||
ATTR_MEMBEROF => Attribute::MemberOf,
|
||||
ATTR_MULTIVALUE => Attribute::MultiValue,
|
||||
ATTR_MUST => Attribute::Must,
|
||||
ATTR_NAME => Attribute::Name,
|
||||
ATTR_NAME_HISTORY => Attribute::NameHistory,
|
||||
ATTR_NO_INDEX => Attribute::NoIndex,
|
||||
ATTR_NSUNIQUEID => Attribute::NsUniqueId,
|
||||
ATTR_NSACCOUNTLOCK => Attribute::NsAccountLock,
|
||||
ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE => {
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce
|
||||
}
|
||||
ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT => Attribute::OAuth2AllowLocalhostRedirect,
|
||||
ATTR_OAUTH2_CONSENT_SCOPE_MAP => Attribute::OAuth2ConsentScopeMap,
|
||||
ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE => Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
ATTR_OAUTH2_PREFER_SHORT_USERNAME => Attribute::OAuth2PreferShortUsername,
|
||||
ATTR_OAUTH2_RS_BASIC_SECRET => Attribute::OAuth2RsBasicSecret,
|
||||
ATTR_OAUTH2_RS_CLAIM_MAP => Attribute::OAuth2RsClaimMap,
|
||||
ATTR_OAUTH2_RS_IMPLICIT_SCOPES => Attribute::OAuth2RsImplicitScopes,
|
||||
ATTR_OAUTH2_RS_NAME => Attribute::OAuth2RsName,
|
||||
ATTR_OAUTH2_RS_ORIGIN => Attribute::OAuth2RsOrigin,
|
||||
ATTR_OAUTH2_RS_ORIGIN_LANDING => Attribute::OAuth2RsOriginLanding,
|
||||
ATTR_OAUTH2_RS_SCOPE_MAP => Attribute::OAuth2RsScopeMap,
|
||||
ATTR_OAUTH2_RS_SUP_SCOPE_MAP => Attribute::OAuth2RsSupScopeMap,
|
||||
ATTR_OAUTH2_RS_TOKEN_KEY => Attribute::OAuth2RsTokenKey,
|
||||
ATTR_OAUTH2_SESSION => Attribute::OAuth2Session,
|
||||
ATTR_OAUTH2_STRICT_REDIRECT_URI => Attribute::OAuth2StrictRedirectUri,
|
||||
ATTR_OBJECTCLASS => Attribute::ObjectClass,
|
||||
ATTR_OTHER_NO_INDEX => Attribute::OtherNoIndex,
|
||||
ATTR_PASSKEYS => Attribute::PassKeys,
|
||||
ATTR_PASSWORD_IMPORT => Attribute::PasswordImport,
|
||||
ATTR_PATCH_LEVEL => Attribute::PatchLevel,
|
||||
ATTR_PHANTOM => Attribute::Phantom,
|
||||
ATTR_PRIMARY_CREDENTIAL => Attribute::PrimaryCredential,
|
||||
ATTR_PRIVATE_COOKIE_KEY => Attribute::PrivateCookieKey,
|
||||
ATTR_PRIVILEGE_EXPIRY => Attribute::PrivilegeExpiry,
|
||||
ATTR_RADIUS_SECRET => Attribute::RadiusSecret,
|
||||
ATTR_RECYCLEDDIRECTMEMBEROF => Attribute::RecycledDirectMemberOf,
|
||||
ATTR_REFERS => Attribute::Refers,
|
||||
ATTR_REPLICATED => Attribute::Replicated,
|
||||
ATTR_RS256_PRIVATE_KEY_DER => Attribute::Rs256PrivateKeyDer,
|
||||
ATTR_SCOPE => Attribute::Scope,
|
||||
ATTR_SOURCE_UUID => Attribute::SourceUuid,
|
||||
ATTR_SPN => Attribute::Spn,
|
||||
ATTR_LDAP_SSHPUBLICKEY => Attribute::LdapSshPublicKey,
|
||||
ATTR_SUDOHOST => Attribute::SudoHost,
|
||||
ATTR_SUPPLEMENTS => Attribute::Supplements,
|
||||
ATTR_SYNC_ALLOWED => Attribute::SyncAllowed,
|
||||
ATTR_SYNC_CLASS => Attribute::SyncClass,
|
||||
ATTR_SYNC_COOKIE => Attribute::SyncCookie,
|
||||
ATTR_SYNC_CREDENTIAL_PORTAL => Attribute::SyncCredentialPortal,
|
||||
ATTR_SYNC_EXTERNAL_ID => Attribute::SyncExternalId,
|
||||
ATTR_SYNC_PARENT_UUID => Attribute::SyncParentUuid,
|
||||
ATTR_SYNC_TOKEN_SESSION => Attribute::SyncTokenSession,
|
||||
ATTR_SYNC_YIELD_AUTHORITY => Attribute::SyncYieldAuthority,
|
||||
ATTR_SYNTAX => Attribute::Syntax,
|
||||
ATTR_SYSTEMEXCLUDES => Attribute::SystemExcludes,
|
||||
ATTR_SYSTEMMAY => Attribute::SystemMay,
|
||||
ATTR_SYSTEMMUST => Attribute::SystemMust,
|
||||
ATTR_SYSTEMSUPPLEMENTS => Attribute::SystemSupplements,
|
||||
ATTR_TERM => Attribute::Term,
|
||||
ATTR_TOTP_IMPORT => Attribute::TotpImport,
|
||||
ATTR_UID => Attribute::Uid,
|
||||
ATTR_UIDNUMBER => Attribute::UidNumber,
|
||||
ATTR_UNIQUE => Attribute::Unique,
|
||||
ATTR_UNIX_PASSWORD => Attribute::UnixPassword,
|
||||
ATTR_UNIX_PASSWORD_IMPORT => Attribute::UnixPasswordImport,
|
||||
ATTR_USER_AUTH_TOKEN_SESSION => Attribute::UserAuthTokenSession,
|
||||
ATTR_USERID => Attribute::UserId,
|
||||
ATTR_USERPASSWORD => Attribute::UserPassword,
|
||||
ATTR_UUID => Attribute::Uuid,
|
||||
ATTR_VERSION => Attribute::Version,
|
||||
ATTR_WEBAUTHN_ATTESTATION_CA_LIST => Attribute::WebauthnAttestationCaList,
|
||||
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_NON_EXIST => Attribute::NonExist,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_TEST_ATTR => Attribute::TestAttr,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_EXTRA => Attribute::Extra,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_NUMBER => Attribute::TestNumber,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_NOTALLOWED => Attribute::TestNotAllowed,
|
||||
_ => {
|
||||
trace!("Failed to convert {} to Attribute", val);
|
||||
return Err(OperationError::InvalidAttributeName(val.to_string()));
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Attribute> for &'static str {
|
||||
fn from(val: Attribute) -> Self {
|
||||
match val {
|
||||
Attribute::Account => ATTR_ACCOUNT,
|
||||
Attribute::AccountExpire => ATTR_ACCOUNT_EXPIRE,
|
||||
Attribute::AccountValidFrom => ATTR_ACCOUNT_VALID_FROM,
|
||||
Attribute::AcpCreateAttr => ATTR_ACP_CREATE_ATTR,
|
||||
Attribute::AcpCreateClass => ATTR_ACP_CREATE_CLASS,
|
||||
Attribute::AcpEnable => ATTR_ACP_ENABLE,
|
||||
Attribute::AcpModifyClass => ATTR_ACP_MODIFY_CLASS,
|
||||
Attribute::AcpModifyPresentAttr => ATTR_ACP_MODIFY_PRESENTATTR,
|
||||
Attribute::AcpModifyRemovedAttr => ATTR_ACP_MODIFY_REMOVEDATTR,
|
||||
Attribute::AcpReceiver => ATTR_ACP_RECEIVER,
|
||||
Attribute::AcpReceiverGroup => ATTR_ACP_RECEIVER_GROUP,
|
||||
Attribute::AcpSearchAttr => ATTR_ACP_SEARCH_ATTR,
|
||||
Attribute::AcpTargetScope => ATTR_ACP_TARGET_SCOPE,
|
||||
Attribute::ApiTokenSession => ATTR_API_TOKEN_SESSION,
|
||||
Attribute::ApplicationPassword => ATTR_APPLICATION_PASSWORD,
|
||||
Attribute::AttestedPasskeys => ATTR_ATTESTED_PASSKEYS,
|
||||
Attribute::Attr => ATTR_ATTR,
|
||||
Attribute::AttributeName => ATTR_ATTRIBUTENAME,
|
||||
Attribute::AttributeType => ATTR_ATTRIBUTETYPE,
|
||||
Attribute::AuthSessionExpiry => ATTR_AUTH_SESSION_EXPIRY,
|
||||
Attribute::AuthPasswordMinimumLength => ATTR_AUTH_PASSWORD_MINIMUM_LENGTH,
|
||||
Attribute::BadlistPassword => ATTR_BADLIST_PASSWORD,
|
||||
Attribute::Certificate => ATTR_CERTIFICATE,
|
||||
Attribute::Claim => ATTR_CLAIM,
|
||||
Attribute::Class => ATTR_CLASS,
|
||||
Attribute::ClassName => ATTR_CLASSNAME,
|
||||
Attribute::Cn => ATTR_CN,
|
||||
Attribute::CookiePrivateKey => ATTR_COOKIE_PRIVATE_KEY,
|
||||
Attribute::CredentialUpdateIntentToken => ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||
Attribute::CredentialTypeMinimum => ATTR_CREDENTIAL_TYPE_MINIMUM,
|
||||
Attribute::DeniedName => ATTR_DENIED_NAME,
|
||||
Attribute::Description => ATTR_DESCRIPTION,
|
||||
Attribute::DirectMemberOf => ATTR_DIRECTMEMBEROF,
|
||||
Attribute::DisplayName => ATTR_DISPLAYNAME,
|
||||
Attribute::Dn => ATTR_DN,
|
||||
Attribute::Domain => ATTR_DOMAIN,
|
||||
Attribute::DomainDevelopmentTaint => ATTR_DOMAIN_DEVELOPMENT_TAINT,
|
||||
Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME,
|
||||
Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN,
|
||||
Attribute::DomainName => ATTR_DOMAIN_NAME,
|
||||
Attribute::DomainSsid => ATTR_DOMAIN_SSID,
|
||||
Attribute::DomainTokenKey => ATTR_DOMAIN_TOKEN_KEY,
|
||||
Attribute::DomainUuid => ATTR_DOMAIN_UUID,
|
||||
Attribute::DynGroup => ATTR_DYNGROUP,
|
||||
Attribute::DynGroupFilter => ATTR_DYNGROUP_FILTER,
|
||||
Attribute::DynMember => ATTR_DYNMEMBER,
|
||||
Attribute::Email => ATTR_EMAIL,
|
||||
Attribute::EmailAlternative => ATTR_EMAIL_ALTERNATIVE,
|
||||
Attribute::EmailPrimary => ATTR_EMAIL_PRIMARY,
|
||||
Attribute::EntryDn => ATTR_ENTRYDN,
|
||||
Attribute::EntryManagedBy => ATTR_ENTRY_MANAGED_BY,
|
||||
Attribute::EntryUuid => ATTR_ENTRYUUID,
|
||||
Attribute::Es256PrivateKeyDer => ATTR_ES256_PRIVATE_KEY_DER,
|
||||
Attribute::Excludes => ATTR_EXCLUDES,
|
||||
Attribute::FernetPrivateKeyStr => ATTR_FERNET_PRIVATE_KEY_STR,
|
||||
Attribute::Gecos => ATTR_GECOS,
|
||||
Attribute::GidNumber => ATTR_GIDNUMBER,
|
||||
Attribute::GrantUiHint => ATTR_GRANT_UI_HINT,
|
||||
Attribute::Group => ATTR_GROUP,
|
||||
Attribute::IdVerificationEcKey => ATTR_ID_VERIFICATION_ECKEY,
|
||||
Attribute::Image => ATTR_IMAGE,
|
||||
Attribute::Index => ATTR_INDEX,
|
||||
Attribute::IpaNtHash => ATTR_IPANTHASH,
|
||||
Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY,
|
||||
Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
Attribute::KeyActionRotate => ATTR_KEY_ACTION_ROTATE,
|
||||
Attribute::KeyActionRevoke => ATTR_KEY_ACTION_REVOKE,
|
||||
Attribute::KeyActionImportJwsEs256 => ATTR_KEY_ACTION_IMPORT_JWS_ES256,
|
||||
Attribute::KeyInternalData => ATTR_KEY_INTERNAL_DATA,
|
||||
Attribute::KeyProvider => ATTR_KEY_PROVIDER,
|
||||
Attribute::LastModifiedCid => ATTR_LAST_MODIFIED_CID,
|
||||
Attribute::LdapAllowUnixPwBind => ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
||||
Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS,
|
||||
Attribute::LdapKeys => ATTR_LDAP_KEYS,
|
||||
Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY,
|
||||
Attribute::LegalName => ATTR_LEGALNAME,
|
||||
Attribute::LimitSearchMaxResults => ATTR_LIMIT_SEARCH_MAX_RESULTS,
|
||||
Attribute::LimitSearchMaxFilterTest => ATTR_LIMIT_SEARCH_MAX_FILTER_TEST,
|
||||
Attribute::LinkedGroup => ATTR_LINKEDGROUP,
|
||||
Attribute::LoginShell => ATTR_LOGINSHELL,
|
||||
Attribute::Mail => ATTR_MAIL,
|
||||
Attribute::May => ATTR_MAY,
|
||||
Attribute::Member => ATTR_MEMBER,
|
||||
Attribute::MemberOf => ATTR_MEMBEROF,
|
||||
Attribute::MultiValue => ATTR_MULTIVALUE,
|
||||
Attribute::Must => ATTR_MUST,
|
||||
Attribute::Name => ATTR_NAME,
|
||||
Attribute::NameHistory => ATTR_NAME_HISTORY,
|
||||
Attribute::NoIndex => ATTR_NO_INDEX,
|
||||
Attribute::NsUniqueId => ATTR_NSUNIQUEID,
|
||||
Attribute::NsAccountLock => ATTR_NSACCOUNTLOCK,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce => {
|
||||
ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
|
||||
}
|
||||
Attribute::OAuth2AllowLocalhostRedirect => ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT,
|
||||
Attribute::OAuth2ConsentScopeMap => ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable => ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE,
|
||||
Attribute::OAuth2PreferShortUsername => ATTR_OAUTH2_PREFER_SHORT_USERNAME,
|
||||
Attribute::OAuth2RsBasicSecret => ATTR_OAUTH2_RS_BASIC_SECRET,
|
||||
Attribute::OAuth2RsClaimMap => ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||
Attribute::OAuth2RsImplicitScopes => ATTR_OAUTH2_RS_IMPLICIT_SCOPES,
|
||||
Attribute::OAuth2RsName => ATTR_OAUTH2_RS_NAME,
|
||||
Attribute::OAuth2RsOrigin => ATTR_OAUTH2_RS_ORIGIN,
|
||||
Attribute::OAuth2RsOriginLanding => ATTR_OAUTH2_RS_ORIGIN_LANDING,
|
||||
Attribute::OAuth2RsScopeMap => ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||
Attribute::OAuth2RsSupScopeMap => ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
Attribute::OAuth2RsTokenKey => ATTR_OAUTH2_RS_TOKEN_KEY,
|
||||
Attribute::OAuth2Session => ATTR_OAUTH2_SESSION,
|
||||
Attribute::OAuth2StrictRedirectUri => ATTR_OAUTH2_STRICT_REDIRECT_URI,
|
||||
Attribute::ObjectClass => ATTR_OBJECTCLASS,
|
||||
Attribute::OtherNoIndex => ATTR_OTHER_NO_INDEX,
|
||||
Attribute::PassKeys => ATTR_PASSKEYS,
|
||||
Attribute::PasswordImport => ATTR_PASSWORD_IMPORT,
|
||||
Attribute::PatchLevel => ATTR_PATCH_LEVEL,
|
||||
Attribute::Phantom => ATTR_PHANTOM,
|
||||
Attribute::PrimaryCredential => ATTR_PRIMARY_CREDENTIAL,
|
||||
Attribute::PrivateCookieKey => ATTR_PRIVATE_COOKIE_KEY,
|
||||
Attribute::PrivilegeExpiry => ATTR_PRIVILEGE_EXPIRY,
|
||||
Attribute::RadiusSecret => ATTR_RADIUS_SECRET,
|
||||
Attribute::RecycledDirectMemberOf => ATTR_RECYCLEDDIRECTMEMBEROF,
|
||||
Attribute::Refers => ATTR_REFERS,
|
||||
Attribute::Replicated => ATTR_REPLICATED,
|
||||
Attribute::Rs256PrivateKeyDer => ATTR_RS256_PRIVATE_KEY_DER,
|
||||
Attribute::Scope => ATTR_SCOPE,
|
||||
Attribute::SourceUuid => ATTR_SOURCE_UUID,
|
||||
Attribute::Spn => ATTR_SPN,
|
||||
Attribute::SshPublicKey => ATTR_SSH_PUBLICKEY,
|
||||
Attribute::SudoHost => ATTR_SUDOHOST,
|
||||
Attribute::Supplements => ATTR_SUPPLEMENTS,
|
||||
Attribute::SyncAllowed => ATTR_SYNC_ALLOWED,
|
||||
Attribute::SyncClass => ATTR_SYNC_CLASS,
|
||||
Attribute::SyncCookie => ATTR_SYNC_COOKIE,
|
||||
Attribute::SyncCredentialPortal => ATTR_SYNC_CREDENTIAL_PORTAL,
|
||||
Attribute::SyncExternalId => ATTR_SYNC_EXTERNAL_ID,
|
||||
Attribute::SyncParentUuid => ATTR_SYNC_PARENT_UUID,
|
||||
Attribute::SyncTokenSession => ATTR_SYNC_TOKEN_SESSION,
|
||||
Attribute::SyncYieldAuthority => ATTR_SYNC_YIELD_AUTHORITY,
|
||||
Attribute::Syntax => ATTR_SYNTAX,
|
||||
Attribute::SystemExcludes => ATTR_SYSTEMEXCLUDES,
|
||||
Attribute::SystemMay => ATTR_SYSTEMMAY,
|
||||
Attribute::SystemMust => ATTR_SYSTEMMUST,
|
||||
Attribute::SystemSupplements => ATTR_SYSTEMSUPPLEMENTS,
|
||||
Attribute::Term => ATTR_TERM,
|
||||
Attribute::TotpImport => ATTR_TOTP_IMPORT,
|
||||
Attribute::Uid => ATTR_UID,
|
||||
Attribute::UidNumber => ATTR_UIDNUMBER,
|
||||
Attribute::Unique => ATTR_UNIQUE,
|
||||
Attribute::UnixPassword => ATTR_UNIX_PASSWORD,
|
||||
Attribute::UnixPasswordImport => ATTR_UNIX_PASSWORD_IMPORT,
|
||||
Attribute::UserAuthTokenSession => ATTR_USER_AUTH_TOKEN_SESSION,
|
||||
Attribute::UserId => ATTR_USERID,
|
||||
Attribute::UserPassword => ATTR_USERPASSWORD,
|
||||
Attribute::Uuid => ATTR_UUID,
|
||||
Attribute::Version => ATTR_VERSION,
|
||||
Attribute::WebauthnAttestationCaList => ATTR_WEBAUTHN_ATTESTATION_CA_LIST,
|
||||
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::NonExist => TEST_ATTR_NON_EXIST,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::TestAttr => TEST_ATTR_TEST_ATTR,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::Extra => TEST_ATTR_EXTRA,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::TestNumber => TEST_ATTR_NUMBER,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::TestNotAllowed => TEST_ATTR_NOTALLOWED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Attribute> for AttrString {
|
||||
fn from(val: Attribute) -> Self {
|
||||
AttrString::from(val.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Attribute {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s: &'static str = (*self).into();
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Attribute;
|
||||
|
||||
#[test]
|
||||
fn test_valueattribute_as_str() {
|
||||
assert!(Attribute::Class.as_ref() == "class");
|
||||
assert!(Attribute::Class.to_string() == *"class");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// this ensures we cover both ends of the conversion to/from string-types
|
||||
fn test_valueattribute_round_trip() {
|
||||
use enum_iterator::all;
|
||||
let the_list = all::<Attribute>().collect::<Vec<_>>();
|
||||
for attr in the_list {
|
||||
let s: &'static str = attr.into();
|
||||
let attr2 = Attribute::try_from(s).unwrap();
|
||||
assert!(attr == attr2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,4 +24,6 @@ pub mod oauth2;
|
|||
pub mod scim_v1;
|
||||
pub mod v1;
|
||||
|
||||
pub mod attribute;
|
||||
|
||||
pub use webauthn_rs_proto as webauthn;
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use base64urlsafedata::Base64UrlSafeData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::formats::SpaceSeparator;
|
||||
use serde_with::{serde_as, skip_serializing_none, StringWithSeparator};
|
||||
use serde_with::{base64, formats, serde_as, skip_serializing_none, StringWithSeparator};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -17,9 +16,11 @@ pub enum CodeChallengeMethod {
|
|||
S256,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PkceRequest {
|
||||
pub code_challenge: Base64UrlSafeData,
|
||||
#[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
|
||||
pub code_challenge: Vec<u8>,
|
||||
pub code_challenge_method: CodeChallengeMethod,
|
||||
}
|
||||
|
||||
|
|
1
proto/src/scim_v1/client.rs
Normal file
1
proto/src/scim_v1/client.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,5 +1,48 @@
|
|||
//! These represent Kanidm's view of SCIM resources that a client will serialise
|
||||
//! for transmission, and the server will deserialise to process them. In reverse
|
||||
//! Kanidm will send responses that a client can then process and use.
|
||||
//!
|
||||
//! A challenge of this is that it creates an asymmetry between the client and server
|
||||
//! as SCIM contains very few strong types. Without awareness of what the client
|
||||
//! or server intended it's not possible to directly deserialise into a rust
|
||||
//! strong type on the receiver. To resolve this, this library divides the elements
|
||||
//! into multiple parts.
|
||||
//!
|
||||
//! The [scim_proto] library, which is generic over all scim implementations.
|
||||
//!
|
||||
//! The client module, which describes how a client should transmit entries, and
|
||||
//! how it should parse them when it recieves them.
|
||||
//!
|
||||
//! The server module, which describes how a server should transmit entries and
|
||||
//! how it should recieve them.
|
||||
|
||||
mod client;
|
||||
pub mod server;
|
||||
mod synch;
|
||||
|
||||
pub use scim_proto::prelude::*;
|
||||
|
||||
pub use self::synch::*;
|
||||
|
||||
//
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_scim_rfc_to_generic() {
|
||||
// Assert that we can transition from the rfc generic entries to the
|
||||
// kanidm types.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_kani_to_generic() {
|
||||
// Assert that a kanidm strong entry can convert to generic.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_kani_to_rfc() {
|
||||
// Assert that a kanidm strong entry can convert to rfc.
|
||||
}
|
||||
}
|
||||
|
|
360
proto/src/scim_v1/server.rs
Normal file
360
proto/src/scim_v1/server.rs
Normal file
|
@ -0,0 +1,360 @@
|
|||
use crate::attribute::Attribute;
|
||||
use scim_proto::ScimEntryHeader;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_with::{base64, formats, hex::Hex, serde_as, skip_serializing_none, StringWithSeparator};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A generic ScimEntry that we receive from a client. This retains attribute
|
||||
/// values in a generic state awaiting processing by schema aware transforms
|
||||
#[derive(Deserialize, Debug, Clone, ToSchema)]
|
||||
pub struct ScimEntryGeneric {
|
||||
#[serde(flatten)]
|
||||
pub header: ScimEntryHeader,
|
||||
#[serde(flatten)]
|
||||
pub attrs: BTreeMap<Attribute, JsonValue>,
|
||||
}
|
||||
|
||||
/// A strongly typed ScimEntry that is for transmission to clients. This uses
|
||||
/// Kanidm internal strong types for values allowing direct serialisation and
|
||||
/// transmission.
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
pub struct ScimEntryKanidm {
|
||||
#[serde(flatten)]
|
||||
pub header: ScimEntryHeader,
|
||||
#[serde(flatten)]
|
||||
pub attrs: BTreeMap<Attribute, ScimValueKanidm>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimAddress {
|
||||
pub formatted: String,
|
||||
pub street_address: String,
|
||||
pub locality: String,
|
||||
pub region: String,
|
||||
pub postal_code: String,
|
||||
pub country: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimMail {
|
||||
pub primary: bool,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimApplicationPassword {
|
||||
pub uuid: Uuid,
|
||||
pub application_uuid: Uuid,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimBinary {
|
||||
pub label: String,
|
||||
#[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimCertificate {
|
||||
#[serde_as(as = "Hex")]
|
||||
pub s256: Vec<u8>,
|
||||
#[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
|
||||
pub der: Vec<u8>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimAuditString {
|
||||
#[serde_as(as = "Rfc3339")]
|
||||
pub date_time: OffsetDateTime,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimSshPublicKey {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ScimIntentTokenState {
|
||||
Valid,
|
||||
InProgress,
|
||||
Consumed,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimIntentToken {
|
||||
pub token_id: String,
|
||||
pub state: ScimIntentTokenState,
|
||||
#[serde_as(as = "Rfc3339")]
|
||||
pub expires: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimKeyInternal {
|
||||
pub key_id: String,
|
||||
pub status: String,
|
||||
pub usage: String,
|
||||
#[serde_as(as = "Rfc3339")]
|
||||
pub valid_from: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimAuthSession {
|
||||
pub id: Uuid,
|
||||
#[serde_as(as = "Option<Rfc3339>")]
|
||||
pub expires: Option<OffsetDateTime>,
|
||||
#[serde_as(as = "Option<Rfc3339>")]
|
||||
pub revoked: Option<OffsetDateTime>,
|
||||
#[serde_as(as = "Rfc3339")]
|
||||
pub issued_at: OffsetDateTime,
|
||||
pub issued_by: Uuid,
|
||||
pub credential_id: Uuid,
|
||||
pub auth_type: String,
|
||||
pub session_scope: String,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimOAuth2Session {
|
||||
pub id: Uuid,
|
||||
pub parent_id: Option<Uuid>,
|
||||
pub client_id: Uuid,
|
||||
#[serde_as(as = "Rfc3339")]
|
||||
pub issued_at: OffsetDateTime,
|
||||
#[serde_as(as = "Option<Rfc3339>")]
|
||||
pub expires: Option<OffsetDateTime>,
|
||||
#[serde_as(as = "Option<Rfc3339>")]
|
||||
pub revoked: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimApiToken {
|
||||
pub id: Uuid,
|
||||
pub label: String,
|
||||
#[serde_as(as = "Option<Rfc3339>")]
|
||||
pub expires: Option<OffsetDateTime>,
|
||||
#[serde_as(as = "Rfc3339")]
|
||||
pub issued_at: OffsetDateTime,
|
||||
pub issued_by: Uuid,
|
||||
pub scope: String,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimOAuth2ScopeMap {
|
||||
pub uuid: Uuid,
|
||||
#[serde_as(as = "StringWithSeparator::<formats::SpaceSeparator, String>")]
|
||||
pub scopes: BTreeSet<String>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimOAuth2ClaimMap {
|
||||
pub group: Uuid,
|
||||
pub claim: String,
|
||||
pub join_char: String,
|
||||
#[serde_as(as = "StringWithSeparator::<formats::SpaceSeparator, String>")]
|
||||
pub values: BTreeSet<String>,
|
||||
}
|
||||
|
||||
/// This is a strongly typed ScimValue for Kanidm. It is for serialisation only
|
||||
/// since on a deserialisation path we can not know the intent of the sender
|
||||
/// to how we deserialise strings. Additionally during deserialisation we need
|
||||
/// to accept optional or partial types too.
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum ScimValueKanidm {
|
||||
Bool(bool),
|
||||
Uint32(u32),
|
||||
Integer(i64),
|
||||
Decimal(f64),
|
||||
String(String),
|
||||
DateTime(#[serde_as(as = "Rfc3339")] OffsetDateTime),
|
||||
Reference(Url),
|
||||
Uuid(Uuid),
|
||||
// Other strong outbound types.
|
||||
ArrayString(Vec<String>),
|
||||
ArrayDateTime(#[serde_as(as = "Vec<Rfc3339>")] Vec<OffsetDateTime>),
|
||||
ArrayUuid(Vec<Uuid>),
|
||||
ArrayBinary(Vec<ScimBinary>),
|
||||
ArrayCertificate(Vec<ScimCertificate>),
|
||||
|
||||
Address(Vec<ScimAddress>),
|
||||
Mail(Vec<ScimMail>),
|
||||
ApplicationPassword(Vec<ScimApplicationPassword>),
|
||||
AuditString(Vec<ScimAuditString>),
|
||||
SshPublicKey(Vec<ScimSshPublicKey>),
|
||||
AuthSession(Vec<ScimAuthSession>),
|
||||
OAuth2Session(Vec<ScimOAuth2Session>),
|
||||
ApiToken(Vec<ScimApiToken>),
|
||||
IntentToken(Vec<ScimIntentToken>),
|
||||
OAuth2ScopeMap(Vec<ScimOAuth2ScopeMap>),
|
||||
OAuth2ClaimMap(Vec<ScimOAuth2ClaimMap>),
|
||||
KeyInternal(Vec<ScimKeyInternal>),
|
||||
}
|
||||
|
||||
impl From<bool> for ScimValueKanidm {
|
||||
fn from(b: bool) -> Self {
|
||||
Self::Bool(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OffsetDateTime> for ScimValueKanidm {
|
||||
fn from(odt: OffsetDateTime) -> Self {
|
||||
Self::DateTime(odt)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<OffsetDateTime>> for ScimValueKanidm {
|
||||
fn from(set: Vec<OffsetDateTime>) -> Self {
|
||||
Self::ArrayDateTime(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ScimValueKanidm {
|
||||
fn from(s: String) -> Self {
|
||||
Self::String(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<String>> for ScimValueKanidm {
|
||||
fn from(set: Vec<String>) -> Self {
|
||||
Self::ArrayString(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for ScimValueKanidm {
|
||||
fn from(u: Uuid) -> Self {
|
||||
Self::Uuid(u)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Uuid>> for ScimValueKanidm {
|
||||
fn from(set: Vec<Uuid>) -> Self {
|
||||
Self::ArrayUuid(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for ScimValueKanidm {
|
||||
fn from(u: u32) -> Self {
|
||||
Self::Uint32(u)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimAddress>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimAddress>) -> Self {
|
||||
Self::Address(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimMail>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimMail>) -> Self {
|
||||
Self::Mail(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimApplicationPassword>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimApplicationPassword>) -> Self {
|
||||
Self::ApplicationPassword(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimAuditString>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimAuditString>) -> Self {
|
||||
Self::AuditString(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimBinary>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimBinary>) -> Self {
|
||||
Self::ArrayBinary(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimCertificate>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimCertificate>) -> Self {
|
||||
Self::ArrayCertificate(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimSshPublicKey>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimSshPublicKey>) -> Self {
|
||||
Self::SshPublicKey(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimAuthSession>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimAuthSession>) -> Self {
|
||||
Self::AuthSession(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimOAuth2Session>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimOAuth2Session>) -> Self {
|
||||
Self::OAuth2Session(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimApiToken>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimApiToken>) -> Self {
|
||||
Self::ApiToken(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimIntentToken>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimIntentToken>) -> Self {
|
||||
Self::IntentToken(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimOAuth2ScopeMap>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimOAuth2ScopeMap>) -> Self {
|
||||
Self::OAuth2ScopeMap(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimOAuth2ClaimMap>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimOAuth2ClaimMap>) -> Self {
|
||||
Self::OAuth2ClaimMap(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ScimKeyInternal>> for ScimValueKanidm {
|
||||
fn from(set: Vec<ScimKeyInternal>) -> Self {
|
||||
Self::KeyInternal(set)
|
||||
}
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
use base64urlsafedata::Base64UrlSafeData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{base64, formats, serde_as};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
use scim_proto::user::MultiValueAttr;
|
||||
use scim_proto::{ScimEntry, ScimEntryGeneric};
|
||||
use scim_proto::{ScimEntry, ScimEntryHeader};
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
|
||||
pub enum ScimSyncState {
|
||||
Refresh,
|
||||
Active { cookie: Base64UrlSafeData },
|
||||
Active {
|
||||
#[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
|
||||
cookie: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
|
||||
|
@ -32,7 +36,7 @@ pub struct ScimSyncRequest {
|
|||
|
||||
// These entries are created with serde_json::to_value(ScimSyncGroup) for
|
||||
// example. This is how we can mix/match the different types.
|
||||
pub entries: Vec<ScimEntryGeneric>,
|
||||
pub entries: Vec<ScimEntry>,
|
||||
|
||||
pub retain: ScimSyncRetentionMode,
|
||||
}
|
||||
|
@ -82,7 +86,7 @@ pub struct ScimSshPubKey {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimSyncPerson {
|
||||
#[serde(flatten)]
|
||||
pub entry: ScimEntry,
|
||||
pub entry: ScimEntryHeader,
|
||||
|
||||
pub user_name: String,
|
||||
pub display_name: String,
|
||||
|
@ -97,10 +101,10 @@ pub struct ScimSyncPerson {
|
|||
pub account_expire: Option<String>,
|
||||
}
|
||||
|
||||
impl TryInto<ScimEntryGeneric> for ScimSyncPerson {
|
||||
impl TryInto<ScimEntry> for ScimSyncPerson {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<ScimEntryGeneric, Self::Error> {
|
||||
fn try_into(self) -> Result<ScimEntry, Self::Error> {
|
||||
serde_json::to_value(self).and_then(serde_json::from_value)
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +117,7 @@ impl ScimSyncPerson {
|
|||
pub fn builder(id: Uuid, user_name: String, display_name: String) -> ScimSyncPersonBuilder {
|
||||
ScimSyncPersonBuilder {
|
||||
inner: ScimSyncPerson {
|
||||
entry: ScimEntry {
|
||||
entry: ScimEntryHeader {
|
||||
schemas: vec![
|
||||
SCIM_SCHEMA_SYNC_ACCOUNT.to_string(),
|
||||
SCIM_SCHEMA_SYNC_PERSON.to_string(),
|
||||
|
@ -215,7 +219,7 @@ pub struct ScimExternalMember {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimSyncGroup {
|
||||
#[serde(flatten)]
|
||||
pub entry: ScimEntry,
|
||||
pub entry: ScimEntryHeader,
|
||||
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
|
@ -223,10 +227,10 @@ pub struct ScimSyncGroup {
|
|||
pub members: Vec<ScimExternalMember>,
|
||||
}
|
||||
|
||||
impl TryInto<ScimEntryGeneric> for ScimSyncGroup {
|
||||
impl TryInto<ScimEntry> for ScimSyncGroup {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<ScimEntryGeneric, Self::Error> {
|
||||
fn try_into(self) -> Result<ScimEntry, Self::Error> {
|
||||
serde_json::to_value(self).and_then(serde_json::from_value)
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +245,7 @@ impl ScimSyncGroup {
|
|||
pub fn builder(name: String, id: Uuid) -> ScimSyncGroupBuilder {
|
||||
ScimSyncGroupBuilder {
|
||||
inner: ScimSyncGroup {
|
||||
entry: ScimEntry {
|
||||
entry: ScimEntryHeader {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id,
|
||||
external_id: None,
|
||||
|
|
|
@ -206,7 +206,7 @@ impl Modify for SecurityAddon {
|
|||
scim_v1::ScimSyncState,
|
||||
scim_v1::ScimSyncRequest,
|
||||
scim_v1::ScimSyncRetentionMode,
|
||||
scim_v1::ScimEntryGeneric,
|
||||
scim_v1::ScimEntry,
|
||||
scim_v1::ScimValue,
|
||||
scim_v1::ScimMeta,
|
||||
scim_v1::ScimAttr,
|
||||
|
|
|
@ -90,7 +90,7 @@ webauthn-rs = { workspace = true, features = [
|
|||
] }
|
||||
webauthn-rs-core = { workspace = true }
|
||||
zxcvbn = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
serde_with = { workspace = true, features = ["time_0_3", "base64"] }
|
||||
hex.workspace = true
|
||||
lodepng = { workspace = true }
|
||||
image = { workspace = true, default-features = false, features = [
|
||||
|
|
|
@ -150,23 +150,23 @@ impl From<BuiltinAcp> for EntryInitNew {
|
|||
value
|
||||
.search_attrs
|
||||
.into_iter()
|
||||
.map(|sa| sa.to_value())
|
||||
.map(Value::from)
|
||||
.collect::<Vec<Value>>(),
|
||||
);
|
||||
value.modify_present_attrs.into_iter().for_each(|attr| {
|
||||
entry.add_ava(Attribute::AcpModifyPresentAttr, attr.to_value());
|
||||
entry.add_ava(Attribute::AcpModifyPresentAttr, Value::from(attr));
|
||||
});
|
||||
value.modify_removed_attrs.into_iter().for_each(|attr| {
|
||||
entry.add_ava(Attribute::AcpModifyRemovedAttr, attr.to_value());
|
||||
entry.add_ava(Attribute::AcpModifyRemovedAttr, Value::from(attr));
|
||||
});
|
||||
value.modify_classes.into_iter().for_each(|class| {
|
||||
entry.add_ava(Attribute::AcpModifyClass, class.to_value());
|
||||
entry.add_ava(Attribute::AcpModifyClass, Value::from(class));
|
||||
});
|
||||
value.create_classes.into_iter().for_each(|class| {
|
||||
entry.add_ava(Attribute::AcpCreateClass, class.to_value());
|
||||
entry.add_ava(Attribute::AcpCreateClass, Value::from(class));
|
||||
});
|
||||
value.create_attrs.into_iter().for_each(|attr| {
|
||||
entry.add_ava(Attribute::AcpCreateAttr, attr.to_value());
|
||||
entry.add_ava(Attribute::AcpCreateAttr, Value::from(attr));
|
||||
});
|
||||
entry
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
//! Constant Entries for the IDM
|
||||
use crate::prelude::AttrString;
|
||||
use enum_iterator::Sequence;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::constants::groups::idm_builtin_admin_groups;
|
||||
|
@ -9,612 +7,13 @@ use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
|
|||
use crate::idm::account::Account;
|
||||
use crate::value::PartialValue;
|
||||
use crate::value::Value;
|
||||
pub use kanidm_proto::attribute::Attribute;
|
||||
use kanidm_proto::constants::*;
|
||||
use kanidm_proto::internal::OperationError;
|
||||
use kanidm_proto::v1::AccountType;
|
||||
|
||||
#[cfg(test)]
|
||||
use uuid::uuid;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn test_valueattribute_as_str() {
|
||||
assert_eq!(Attribute::Class.as_ref(), "class");
|
||||
assert_eq!(Attribute::Class.to_string(), *"class");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// this ensures we cover both ends of the conversion to/from string-types
|
||||
fn test_valueattribute_round_trip() {
|
||||
use enum_iterator::all;
|
||||
let the_list = all::<Attribute>().collect::<Vec<_>>();
|
||||
for attr in the_list {
|
||||
let s: &'static str = attr.into();
|
||||
let attr2 = Attribute::try_from(s).unwrap();
|
||||
assert_eq!(attr, attr2);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, Hash)]
|
||||
pub enum Attribute {
|
||||
Account,
|
||||
AccountExpire,
|
||||
AccountValidFrom,
|
||||
AcpCreateAttr,
|
||||
AcpCreateClass,
|
||||
AcpEnable,
|
||||
AcpModifyClass,
|
||||
AcpModifyPresentAttr,
|
||||
AcpModifyRemovedAttr,
|
||||
AcpReceiver,
|
||||
AcpReceiverGroup,
|
||||
AcpSearchAttr,
|
||||
AcpTargetScope,
|
||||
ApiTokenSession,
|
||||
ApplicationPassword,
|
||||
AttestedPasskeys,
|
||||
Attr,
|
||||
AttributeName,
|
||||
AttributeType,
|
||||
AuthSessionExpiry,
|
||||
AuthPasswordMinimumLength,
|
||||
BadlistPassword,
|
||||
Certificate,
|
||||
Claim,
|
||||
Class,
|
||||
ClassName,
|
||||
Cn,
|
||||
CookiePrivateKey,
|
||||
CredentialUpdateIntentToken,
|
||||
CredentialTypeMinimum,
|
||||
DeniedName,
|
||||
Description,
|
||||
DirectMemberOf,
|
||||
DisplayName,
|
||||
Dn,
|
||||
Domain,
|
||||
DomainDevelopmentTaint,
|
||||
DomainDisplayName,
|
||||
DomainLdapBasedn,
|
||||
DomainName,
|
||||
DomainSsid,
|
||||
DomainTokenKey,
|
||||
DomainUuid,
|
||||
DynGroup,
|
||||
DynGroupFilter,
|
||||
DynMember,
|
||||
Email,
|
||||
EmailAlternative,
|
||||
EmailPrimary,
|
||||
EntryDn,
|
||||
EntryManagedBy,
|
||||
EntryUuid,
|
||||
Es256PrivateKeyDer,
|
||||
Excludes,
|
||||
FernetPrivateKeyStr,
|
||||
Gecos,
|
||||
GidNumber,
|
||||
GrantUiHint,
|
||||
Group,
|
||||
IdVerificationEcKey,
|
||||
Image,
|
||||
Index,
|
||||
IpaNtHash,
|
||||
IpaSshPubKey,
|
||||
JwsEs256PrivateKey,
|
||||
KeyActionRotate,
|
||||
KeyActionRevoke,
|
||||
KeyActionImportJwsEs256,
|
||||
KeyInternalData,
|
||||
KeyProvider,
|
||||
LastModifiedCid,
|
||||
LdapAllowUnixPwBind,
|
||||
/// An LDAP Compatible emailAddress
|
||||
LdapEmailAddress,
|
||||
/// An LDAP Compatible sshkeys virtual attribute
|
||||
LdapKeys,
|
||||
LegalName,
|
||||
LimitSearchMaxResults,
|
||||
LimitSearchMaxFilterTest,
|
||||
LinkedGroup,
|
||||
LoginShell,
|
||||
Mail,
|
||||
May,
|
||||
Member,
|
||||
MemberOf,
|
||||
MultiValue,
|
||||
Must,
|
||||
Name,
|
||||
NameHistory,
|
||||
NoIndex,
|
||||
NsUniqueId,
|
||||
NsAccountLock,
|
||||
OAuth2AllowInsecureClientDisablePkce,
|
||||
OAuth2AllowLocalhostRedirect,
|
||||
OAuth2ConsentScopeMap,
|
||||
OAuth2JwtLegacyCryptoEnable,
|
||||
OAuth2PreferShortUsername,
|
||||
OAuth2RsBasicSecret,
|
||||
OAuth2RsClaimMap,
|
||||
OAuth2RsImplicitScopes,
|
||||
OAuth2RsName,
|
||||
OAuth2RsOrigin,
|
||||
OAuth2RsOriginLanding,
|
||||
OAuth2RsScopeMap,
|
||||
OAuth2RsSupScopeMap,
|
||||
OAuth2RsTokenKey,
|
||||
OAuth2Session,
|
||||
OAuth2StrictRedirectUri,
|
||||
ObjectClass,
|
||||
OtherNoIndex,
|
||||
PassKeys,
|
||||
PasswordImport,
|
||||
PatchLevel,
|
||||
Phantom,
|
||||
PrimaryCredential,
|
||||
PrivateCookieKey,
|
||||
PrivilegeExpiry,
|
||||
RadiusSecret,
|
||||
RecycledDirectMemberOf,
|
||||
Refers,
|
||||
Replicated,
|
||||
Rs256PrivateKeyDer,
|
||||
Scope,
|
||||
SourceUuid,
|
||||
Spn,
|
||||
/// An LDAP-compatible sshpublickey
|
||||
LdapSshPublicKey,
|
||||
/// The Kanidm-local ssh_publickey
|
||||
SshPublicKey,
|
||||
SudoHost,
|
||||
Supplements,
|
||||
SystemSupplements,
|
||||
SyncAllowed,
|
||||
SyncClass,
|
||||
SyncCookie,
|
||||
SyncCredentialPortal,
|
||||
SyncExternalId,
|
||||
SyncParentUuid,
|
||||
SyncTokenSession,
|
||||
SyncYieldAuthority,
|
||||
Syntax,
|
||||
SystemExcludes,
|
||||
SystemMay,
|
||||
SystemMust,
|
||||
Term,
|
||||
TotpImport,
|
||||
Uid,
|
||||
UidNumber,
|
||||
Unique,
|
||||
UnixPassword,
|
||||
UnixPasswordImport,
|
||||
UserAuthTokenSession,
|
||||
UserId,
|
||||
UserPassword,
|
||||
Uuid,
|
||||
Version,
|
||||
WebauthnAttestationCaList,
|
||||
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
NonExist,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TestAttr,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TestNumber,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Extra,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TestNotAllowed,
|
||||
}
|
||||
|
||||
impl AsRef<str> for Attribute {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Attribute> for &'static str {
|
||||
fn from(value: &Attribute) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AttrString> for Attribute {
|
||||
type Error = OperationError;
|
||||
|
||||
fn try_from(value: &AttrString) -> Result<Self, Self::Error> {
|
||||
Attribute::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Attribute {
|
||||
type Error = OperationError;
|
||||
fn try_from(val: &'a str) -> Result<Self, OperationError> {
|
||||
let res = match val {
|
||||
ATTR_ACCOUNT => Attribute::Account,
|
||||
ATTR_ACCOUNT_EXPIRE => Attribute::AccountExpire,
|
||||
ATTR_ACCOUNT_VALID_FROM => Attribute::AccountValidFrom,
|
||||
ATTR_ACP_CREATE_ATTR => Attribute::AcpCreateAttr,
|
||||
ATTR_ACP_CREATE_CLASS => Attribute::AcpCreateClass,
|
||||
ATTR_ACP_ENABLE => Attribute::AcpEnable,
|
||||
ATTR_ACP_MODIFY_CLASS => Attribute::AcpModifyClass,
|
||||
ATTR_ACP_MODIFY_PRESENTATTR => Attribute::AcpModifyPresentAttr,
|
||||
ATTR_ACP_MODIFY_REMOVEDATTR => Attribute::AcpModifyRemovedAttr,
|
||||
ATTR_ACP_RECEIVER => Attribute::AcpReceiver,
|
||||
ATTR_ACP_RECEIVER_GROUP => Attribute::AcpReceiverGroup,
|
||||
ATTR_ACP_SEARCH_ATTR => Attribute::AcpSearchAttr,
|
||||
ATTR_ACP_TARGET_SCOPE => Attribute::AcpTargetScope,
|
||||
ATTR_API_TOKEN_SESSION => Attribute::ApiTokenSession,
|
||||
ATTR_APPLICATION_PASSWORD => Attribute::ApplicationPassword,
|
||||
ATTR_ATTESTED_PASSKEYS => Attribute::AttestedPasskeys,
|
||||
ATTR_ATTR => Attribute::Attr,
|
||||
ATTR_ATTRIBUTENAME => Attribute::AttributeName,
|
||||
ATTR_ATTRIBUTETYPE => Attribute::AttributeType,
|
||||
ATTR_AUTH_SESSION_EXPIRY => Attribute::AuthSessionExpiry,
|
||||
ATTR_AUTH_PASSWORD_MINIMUM_LENGTH => Attribute::AuthPasswordMinimumLength,
|
||||
ATTR_BADLIST_PASSWORD => Attribute::BadlistPassword,
|
||||
ATTR_CERTIFICATE => Attribute::Certificate,
|
||||
ATTR_CLAIM => Attribute::Claim,
|
||||
ATTR_CLASS => Attribute::Class,
|
||||
ATTR_CLASSNAME => Attribute::ClassName,
|
||||
ATTR_CN => Attribute::Cn,
|
||||
ATTR_COOKIE_PRIVATE_KEY => Attribute::CookiePrivateKey,
|
||||
ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN => Attribute::CredentialUpdateIntentToken,
|
||||
ATTR_CREDENTIAL_TYPE_MINIMUM => Attribute::CredentialTypeMinimum,
|
||||
ATTR_DENIED_NAME => Attribute::DeniedName,
|
||||
ATTR_DESCRIPTION => Attribute::Description,
|
||||
ATTR_DIRECTMEMBEROF => Attribute::DirectMemberOf,
|
||||
ATTR_DISPLAYNAME => Attribute::DisplayName,
|
||||
ATTR_DN => Attribute::Dn,
|
||||
ATTR_DOMAIN => Attribute::Domain,
|
||||
ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName,
|
||||
ATTR_DOMAIN_DEVELOPMENT_TAINT => Attribute::DomainDevelopmentTaint,
|
||||
ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn,
|
||||
ATTR_DOMAIN_NAME => Attribute::DomainName,
|
||||
ATTR_DOMAIN_SSID => Attribute::DomainSsid,
|
||||
ATTR_DOMAIN_TOKEN_KEY => Attribute::DomainTokenKey,
|
||||
ATTR_DOMAIN_UUID => Attribute::DomainUuid,
|
||||
ATTR_DYNGROUP => Attribute::DynGroup,
|
||||
ATTR_DYNGROUP_FILTER => Attribute::DynGroupFilter,
|
||||
ATTR_DYNMEMBER => Attribute::DynMember,
|
||||
ATTR_EMAIL => Attribute::Email,
|
||||
ATTR_EMAIL_ALTERNATIVE => Attribute::EmailAlternative,
|
||||
ATTR_EMAIL_PRIMARY => Attribute::EmailPrimary,
|
||||
ATTR_ENTRYDN => Attribute::EntryDn,
|
||||
ATTR_ENTRY_MANAGED_BY => Attribute::EntryManagedBy,
|
||||
ATTR_ENTRYUUID => Attribute::EntryUuid,
|
||||
ATTR_ES256_PRIVATE_KEY_DER => Attribute::Es256PrivateKeyDer,
|
||||
ATTR_EXCLUDES => Attribute::Excludes,
|
||||
ATTR_FERNET_PRIVATE_KEY_STR => Attribute::FernetPrivateKeyStr,
|
||||
ATTR_GECOS => Attribute::Gecos,
|
||||
ATTR_GIDNUMBER => Attribute::GidNumber,
|
||||
ATTR_GRANT_UI_HINT => Attribute::GrantUiHint,
|
||||
ATTR_GROUP => Attribute::Group,
|
||||
ATTR_ID_VERIFICATION_ECKEY => Attribute::IdVerificationEcKey,
|
||||
ATTR_IMAGE => Attribute::Image,
|
||||
ATTR_INDEX => Attribute::Index,
|
||||
ATTR_IPANTHASH => Attribute::IpaNtHash,
|
||||
ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey,
|
||||
ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey,
|
||||
ATTR_KEY_ACTION_ROTATE => Attribute::KeyActionRotate,
|
||||
ATTR_KEY_ACTION_REVOKE => Attribute::KeyActionRevoke,
|
||||
ATTR_KEY_ACTION_IMPORT_JWS_ES256 => Attribute::KeyActionImportJwsEs256,
|
||||
ATTR_KEY_INTERNAL_DATA => Attribute::KeyInternalData,
|
||||
ATTR_KEY_PROVIDER => Attribute::KeyProvider,
|
||||
ATTR_LAST_MODIFIED_CID => Attribute::LastModifiedCid,
|
||||
ATTR_LDAP_ALLOW_UNIX_PW_BIND => Attribute::LdapAllowUnixPwBind,
|
||||
ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress,
|
||||
ATTR_LDAP_KEYS => Attribute::LdapKeys,
|
||||
ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey,
|
||||
ATTR_LEGALNAME => Attribute::LegalName,
|
||||
ATTR_LINKEDGROUP => Attribute::LinkedGroup,
|
||||
ATTR_LOGINSHELL => Attribute::LoginShell,
|
||||
ATTR_LIMIT_SEARCH_MAX_RESULTS => Attribute::LimitSearchMaxResults,
|
||||
ATTR_LIMIT_SEARCH_MAX_FILTER_TEST => Attribute::LimitSearchMaxFilterTest,
|
||||
ATTR_MAIL => Attribute::Mail,
|
||||
ATTR_MAY => Attribute::May,
|
||||
ATTR_MEMBER => Attribute::Member,
|
||||
ATTR_MEMBEROF => Attribute::MemberOf,
|
||||
ATTR_MULTIVALUE => Attribute::MultiValue,
|
||||
ATTR_MUST => Attribute::Must,
|
||||
ATTR_NAME => Attribute::Name,
|
||||
ATTR_NAME_HISTORY => Attribute::NameHistory,
|
||||
ATTR_NO_INDEX => Attribute::NoIndex,
|
||||
ATTR_NSUNIQUEID => Attribute::NsUniqueId,
|
||||
ATTR_NSACCOUNTLOCK => Attribute::NsAccountLock,
|
||||
ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE => {
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce
|
||||
}
|
||||
ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT => Attribute::OAuth2AllowLocalhostRedirect,
|
||||
ATTR_OAUTH2_CONSENT_SCOPE_MAP => Attribute::OAuth2ConsentScopeMap,
|
||||
ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE => Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
ATTR_OAUTH2_PREFER_SHORT_USERNAME => Attribute::OAuth2PreferShortUsername,
|
||||
ATTR_OAUTH2_RS_BASIC_SECRET => Attribute::OAuth2RsBasicSecret,
|
||||
ATTR_OAUTH2_RS_CLAIM_MAP => Attribute::OAuth2RsClaimMap,
|
||||
ATTR_OAUTH2_RS_IMPLICIT_SCOPES => Attribute::OAuth2RsImplicitScopes,
|
||||
ATTR_OAUTH2_RS_NAME => Attribute::OAuth2RsName,
|
||||
ATTR_OAUTH2_RS_ORIGIN => Attribute::OAuth2RsOrigin,
|
||||
ATTR_OAUTH2_RS_ORIGIN_LANDING => Attribute::OAuth2RsOriginLanding,
|
||||
ATTR_OAUTH2_RS_SCOPE_MAP => Attribute::OAuth2RsScopeMap,
|
||||
ATTR_OAUTH2_RS_SUP_SCOPE_MAP => Attribute::OAuth2RsSupScopeMap,
|
||||
ATTR_OAUTH2_RS_TOKEN_KEY => Attribute::OAuth2RsTokenKey,
|
||||
ATTR_OAUTH2_SESSION => Attribute::OAuth2Session,
|
||||
ATTR_OAUTH2_STRICT_REDIRECT_URI => Attribute::OAuth2StrictRedirectUri,
|
||||
ATTR_OBJECTCLASS => Attribute::ObjectClass,
|
||||
ATTR_OTHER_NO_INDEX => Attribute::OtherNoIndex,
|
||||
ATTR_PASSKEYS => Attribute::PassKeys,
|
||||
ATTR_PASSWORD_IMPORT => Attribute::PasswordImport,
|
||||
ATTR_PATCH_LEVEL => Attribute::PatchLevel,
|
||||
ATTR_PHANTOM => Attribute::Phantom,
|
||||
ATTR_PRIMARY_CREDENTIAL => Attribute::PrimaryCredential,
|
||||
ATTR_PRIVATE_COOKIE_KEY => Attribute::PrivateCookieKey,
|
||||
ATTR_PRIVILEGE_EXPIRY => Attribute::PrivilegeExpiry,
|
||||
ATTR_RADIUS_SECRET => Attribute::RadiusSecret,
|
||||
ATTR_RECYCLEDDIRECTMEMBEROF => Attribute::RecycledDirectMemberOf,
|
||||
ATTR_REFERS => Attribute::Refers,
|
||||
ATTR_REPLICATED => Attribute::Replicated,
|
||||
ATTR_RS256_PRIVATE_KEY_DER => Attribute::Rs256PrivateKeyDer,
|
||||
ATTR_SCOPE => Attribute::Scope,
|
||||
ATTR_SOURCE_UUID => Attribute::SourceUuid,
|
||||
ATTR_SPN => Attribute::Spn,
|
||||
ATTR_LDAP_SSHPUBLICKEY => Attribute::LdapSshPublicKey,
|
||||
ATTR_SUDOHOST => Attribute::SudoHost,
|
||||
ATTR_SUPPLEMENTS => Attribute::Supplements,
|
||||
ATTR_SYNC_ALLOWED => Attribute::SyncAllowed,
|
||||
ATTR_SYNC_CLASS => Attribute::SyncClass,
|
||||
ATTR_SYNC_COOKIE => Attribute::SyncCookie,
|
||||
ATTR_SYNC_CREDENTIAL_PORTAL => Attribute::SyncCredentialPortal,
|
||||
ATTR_SYNC_EXTERNAL_ID => Attribute::SyncExternalId,
|
||||
ATTR_SYNC_PARENT_UUID => Attribute::SyncParentUuid,
|
||||
ATTR_SYNC_TOKEN_SESSION => Attribute::SyncTokenSession,
|
||||
ATTR_SYNC_YIELD_AUTHORITY => Attribute::SyncYieldAuthority,
|
||||
ATTR_SYNTAX => Attribute::Syntax,
|
||||
ATTR_SYSTEMEXCLUDES => Attribute::SystemExcludes,
|
||||
ATTR_SYSTEMMAY => Attribute::SystemMay,
|
||||
ATTR_SYSTEMMUST => Attribute::SystemMust,
|
||||
ATTR_SYSTEMSUPPLEMENTS => Attribute::SystemSupplements,
|
||||
ATTR_TERM => Attribute::Term,
|
||||
ATTR_TOTP_IMPORT => Attribute::TotpImport,
|
||||
ATTR_UID => Attribute::Uid,
|
||||
ATTR_UIDNUMBER => Attribute::UidNumber,
|
||||
ATTR_UNIQUE => Attribute::Unique,
|
||||
ATTR_UNIX_PASSWORD => Attribute::UnixPassword,
|
||||
ATTR_UNIX_PASSWORD_IMPORT => Attribute::UnixPasswordImport,
|
||||
ATTR_USER_AUTH_TOKEN_SESSION => Attribute::UserAuthTokenSession,
|
||||
ATTR_USERID => Attribute::UserId,
|
||||
ATTR_USERPASSWORD => Attribute::UserPassword,
|
||||
ATTR_UUID => Attribute::Uuid,
|
||||
ATTR_VERSION => Attribute::Version,
|
||||
ATTR_WEBAUTHN_ATTESTATION_CA_LIST => Attribute::WebauthnAttestationCaList,
|
||||
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_NON_EXIST => Attribute::NonExist,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_TEST_ATTR => Attribute::TestAttr,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_EXTRA => Attribute::Extra,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_NUMBER => Attribute::TestNumber,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
TEST_ATTR_NOTALLOWED => Attribute::TestNotAllowed,
|
||||
_ => {
|
||||
trace!("Failed to convert {} to Attribute", val);
|
||||
return Err(OperationError::InvalidAttributeName(val.to_string()));
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Attribute> for &'static str {
|
||||
fn from(val: Attribute) -> Self {
|
||||
match val {
|
||||
Attribute::Account => ATTR_ACCOUNT,
|
||||
Attribute::AccountExpire => ATTR_ACCOUNT_EXPIRE,
|
||||
Attribute::AccountValidFrom => ATTR_ACCOUNT_VALID_FROM,
|
||||
Attribute::AcpCreateAttr => ATTR_ACP_CREATE_ATTR,
|
||||
Attribute::AcpCreateClass => ATTR_ACP_CREATE_CLASS,
|
||||
Attribute::AcpEnable => ATTR_ACP_ENABLE,
|
||||
Attribute::AcpModifyClass => ATTR_ACP_MODIFY_CLASS,
|
||||
Attribute::AcpModifyPresentAttr => ATTR_ACP_MODIFY_PRESENTATTR,
|
||||
Attribute::AcpModifyRemovedAttr => ATTR_ACP_MODIFY_REMOVEDATTR,
|
||||
Attribute::AcpReceiver => ATTR_ACP_RECEIVER,
|
||||
Attribute::AcpReceiverGroup => ATTR_ACP_RECEIVER_GROUP,
|
||||
Attribute::AcpSearchAttr => ATTR_ACP_SEARCH_ATTR,
|
||||
Attribute::AcpTargetScope => ATTR_ACP_TARGET_SCOPE,
|
||||
Attribute::ApiTokenSession => ATTR_API_TOKEN_SESSION,
|
||||
Attribute::ApplicationPassword => ATTR_APPLICATION_PASSWORD,
|
||||
Attribute::AttestedPasskeys => ATTR_ATTESTED_PASSKEYS,
|
||||
Attribute::Attr => ATTR_ATTR,
|
||||
Attribute::AttributeName => ATTR_ATTRIBUTENAME,
|
||||
Attribute::AttributeType => ATTR_ATTRIBUTETYPE,
|
||||
Attribute::AuthSessionExpiry => ATTR_AUTH_SESSION_EXPIRY,
|
||||
Attribute::AuthPasswordMinimumLength => ATTR_AUTH_PASSWORD_MINIMUM_LENGTH,
|
||||
Attribute::BadlistPassword => ATTR_BADLIST_PASSWORD,
|
||||
Attribute::Certificate => ATTR_CERTIFICATE,
|
||||
Attribute::Claim => ATTR_CLAIM,
|
||||
Attribute::Class => ATTR_CLASS,
|
||||
Attribute::ClassName => ATTR_CLASSNAME,
|
||||
Attribute::Cn => ATTR_CN,
|
||||
Attribute::CookiePrivateKey => ATTR_COOKIE_PRIVATE_KEY,
|
||||
Attribute::CredentialUpdateIntentToken => ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||
Attribute::CredentialTypeMinimum => ATTR_CREDENTIAL_TYPE_MINIMUM,
|
||||
Attribute::DeniedName => ATTR_DENIED_NAME,
|
||||
Attribute::Description => ATTR_DESCRIPTION,
|
||||
Attribute::DirectMemberOf => ATTR_DIRECTMEMBEROF,
|
||||
Attribute::DisplayName => ATTR_DISPLAYNAME,
|
||||
Attribute::Dn => ATTR_DN,
|
||||
Attribute::Domain => ATTR_DOMAIN,
|
||||
Attribute::DomainDevelopmentTaint => ATTR_DOMAIN_DEVELOPMENT_TAINT,
|
||||
Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME,
|
||||
Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN,
|
||||
Attribute::DomainName => ATTR_DOMAIN_NAME,
|
||||
Attribute::DomainSsid => ATTR_DOMAIN_SSID,
|
||||
Attribute::DomainTokenKey => ATTR_DOMAIN_TOKEN_KEY,
|
||||
Attribute::DomainUuid => ATTR_DOMAIN_UUID,
|
||||
Attribute::DynGroup => ATTR_DYNGROUP,
|
||||
Attribute::DynGroupFilter => ATTR_DYNGROUP_FILTER,
|
||||
Attribute::DynMember => ATTR_DYNMEMBER,
|
||||
Attribute::Email => ATTR_EMAIL,
|
||||
Attribute::EmailAlternative => ATTR_EMAIL_ALTERNATIVE,
|
||||
Attribute::EmailPrimary => ATTR_EMAIL_PRIMARY,
|
||||
Attribute::EntryDn => ATTR_ENTRYDN,
|
||||
Attribute::EntryManagedBy => ATTR_ENTRY_MANAGED_BY,
|
||||
Attribute::EntryUuid => ATTR_ENTRYUUID,
|
||||
Attribute::Es256PrivateKeyDer => ATTR_ES256_PRIVATE_KEY_DER,
|
||||
Attribute::Excludes => ATTR_EXCLUDES,
|
||||
Attribute::FernetPrivateKeyStr => ATTR_FERNET_PRIVATE_KEY_STR,
|
||||
Attribute::Gecos => ATTR_GECOS,
|
||||
Attribute::GidNumber => ATTR_GIDNUMBER,
|
||||
Attribute::GrantUiHint => ATTR_GRANT_UI_HINT,
|
||||
Attribute::Group => ATTR_GROUP,
|
||||
Attribute::IdVerificationEcKey => ATTR_ID_VERIFICATION_ECKEY,
|
||||
Attribute::Image => ATTR_IMAGE,
|
||||
Attribute::Index => ATTR_INDEX,
|
||||
Attribute::IpaNtHash => ATTR_IPANTHASH,
|
||||
Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY,
|
||||
Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
Attribute::KeyActionRotate => ATTR_KEY_ACTION_ROTATE,
|
||||
Attribute::KeyActionRevoke => ATTR_KEY_ACTION_REVOKE,
|
||||
Attribute::KeyActionImportJwsEs256 => ATTR_KEY_ACTION_IMPORT_JWS_ES256,
|
||||
Attribute::KeyInternalData => ATTR_KEY_INTERNAL_DATA,
|
||||
Attribute::KeyProvider => ATTR_KEY_PROVIDER,
|
||||
Attribute::LastModifiedCid => ATTR_LAST_MODIFIED_CID,
|
||||
Attribute::LdapAllowUnixPwBind => ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
||||
Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS,
|
||||
Attribute::LdapKeys => ATTR_LDAP_KEYS,
|
||||
Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY,
|
||||
Attribute::LegalName => ATTR_LEGALNAME,
|
||||
Attribute::LimitSearchMaxResults => ATTR_LIMIT_SEARCH_MAX_RESULTS,
|
||||
Attribute::LimitSearchMaxFilterTest => ATTR_LIMIT_SEARCH_MAX_FILTER_TEST,
|
||||
Attribute::LinkedGroup => ATTR_LINKEDGROUP,
|
||||
Attribute::LoginShell => ATTR_LOGINSHELL,
|
||||
Attribute::Mail => ATTR_MAIL,
|
||||
Attribute::May => ATTR_MAY,
|
||||
Attribute::Member => ATTR_MEMBER,
|
||||
Attribute::MemberOf => ATTR_MEMBEROF,
|
||||
Attribute::MultiValue => ATTR_MULTIVALUE,
|
||||
Attribute::Must => ATTR_MUST,
|
||||
Attribute::Name => ATTR_NAME,
|
||||
Attribute::NameHistory => ATTR_NAME_HISTORY,
|
||||
Attribute::NoIndex => ATTR_NO_INDEX,
|
||||
Attribute::NsUniqueId => ATTR_NSUNIQUEID,
|
||||
Attribute::NsAccountLock => ATTR_NSACCOUNTLOCK,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce => {
|
||||
ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
|
||||
}
|
||||
Attribute::OAuth2AllowLocalhostRedirect => ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT,
|
||||
Attribute::OAuth2ConsentScopeMap => ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable => ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE,
|
||||
Attribute::OAuth2PreferShortUsername => ATTR_OAUTH2_PREFER_SHORT_USERNAME,
|
||||
Attribute::OAuth2RsBasicSecret => ATTR_OAUTH2_RS_BASIC_SECRET,
|
||||
Attribute::OAuth2RsClaimMap => ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||
Attribute::OAuth2RsImplicitScopes => ATTR_OAUTH2_RS_IMPLICIT_SCOPES,
|
||||
Attribute::OAuth2RsName => ATTR_OAUTH2_RS_NAME,
|
||||
Attribute::OAuth2RsOrigin => ATTR_OAUTH2_RS_ORIGIN,
|
||||
Attribute::OAuth2RsOriginLanding => ATTR_OAUTH2_RS_ORIGIN_LANDING,
|
||||
Attribute::OAuth2RsScopeMap => ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||
Attribute::OAuth2RsSupScopeMap => ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
Attribute::OAuth2RsTokenKey => ATTR_OAUTH2_RS_TOKEN_KEY,
|
||||
Attribute::OAuth2Session => ATTR_OAUTH2_SESSION,
|
||||
Attribute::OAuth2StrictRedirectUri => ATTR_OAUTH2_STRICT_REDIRECT_URI,
|
||||
Attribute::ObjectClass => ATTR_OBJECTCLASS,
|
||||
Attribute::OtherNoIndex => ATTR_OTHER_NO_INDEX,
|
||||
Attribute::PassKeys => ATTR_PASSKEYS,
|
||||
Attribute::PasswordImport => ATTR_PASSWORD_IMPORT,
|
||||
Attribute::PatchLevel => ATTR_PATCH_LEVEL,
|
||||
Attribute::Phantom => ATTR_PHANTOM,
|
||||
Attribute::PrimaryCredential => ATTR_PRIMARY_CREDENTIAL,
|
||||
Attribute::PrivateCookieKey => ATTR_PRIVATE_COOKIE_KEY,
|
||||
Attribute::PrivilegeExpiry => ATTR_PRIVILEGE_EXPIRY,
|
||||
Attribute::RadiusSecret => ATTR_RADIUS_SECRET,
|
||||
Attribute::RecycledDirectMemberOf => ATTR_RECYCLEDDIRECTMEMBEROF,
|
||||
Attribute::Refers => ATTR_REFERS,
|
||||
Attribute::Replicated => ATTR_REPLICATED,
|
||||
Attribute::Rs256PrivateKeyDer => ATTR_RS256_PRIVATE_KEY_DER,
|
||||
Attribute::Scope => ATTR_SCOPE,
|
||||
Attribute::SourceUuid => ATTR_SOURCE_UUID,
|
||||
Attribute::Spn => ATTR_SPN,
|
||||
Attribute::SshPublicKey => ATTR_SSH_PUBLICKEY,
|
||||
Attribute::SudoHost => ATTR_SUDOHOST,
|
||||
Attribute::Supplements => ATTR_SUPPLEMENTS,
|
||||
Attribute::SyncAllowed => ATTR_SYNC_ALLOWED,
|
||||
Attribute::SyncClass => ATTR_SYNC_CLASS,
|
||||
Attribute::SyncCookie => ATTR_SYNC_COOKIE,
|
||||
Attribute::SyncCredentialPortal => ATTR_SYNC_CREDENTIAL_PORTAL,
|
||||
Attribute::SyncExternalId => ATTR_SYNC_EXTERNAL_ID,
|
||||
Attribute::SyncParentUuid => ATTR_SYNC_PARENT_UUID,
|
||||
Attribute::SyncTokenSession => ATTR_SYNC_TOKEN_SESSION,
|
||||
Attribute::SyncYieldAuthority => ATTR_SYNC_YIELD_AUTHORITY,
|
||||
Attribute::Syntax => ATTR_SYNTAX,
|
||||
Attribute::SystemExcludes => ATTR_SYSTEMEXCLUDES,
|
||||
Attribute::SystemMay => ATTR_SYSTEMMAY,
|
||||
Attribute::SystemMust => ATTR_SYSTEMMUST,
|
||||
Attribute::SystemSupplements => ATTR_SYSTEMSUPPLEMENTS,
|
||||
Attribute::Term => ATTR_TERM,
|
||||
Attribute::TotpImport => ATTR_TOTP_IMPORT,
|
||||
Attribute::Uid => ATTR_UID,
|
||||
Attribute::UidNumber => ATTR_UIDNUMBER,
|
||||
Attribute::Unique => ATTR_UNIQUE,
|
||||
Attribute::UnixPassword => ATTR_UNIX_PASSWORD,
|
||||
Attribute::UnixPasswordImport => ATTR_UNIX_PASSWORD_IMPORT,
|
||||
Attribute::UserAuthTokenSession => ATTR_USER_AUTH_TOKEN_SESSION,
|
||||
Attribute::UserId => ATTR_USERID,
|
||||
Attribute::UserPassword => ATTR_USERPASSWORD,
|
||||
Attribute::Uuid => ATTR_UUID,
|
||||
Attribute::Version => ATTR_VERSION,
|
||||
Attribute::WebauthnAttestationCaList => ATTR_WEBAUTHN_ATTESTATION_CA_LIST,
|
||||
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::NonExist => TEST_ATTR_NON_EXIST,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::TestAttr => TEST_ATTR_TEST_ATTR,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::Extra => TEST_ATTR_EXTRA,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::TestNumber => TEST_ATTR_NUMBER,
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
Attribute::TestNotAllowed => TEST_ATTR_NOTALLOWED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Attribute> for AttrString {
|
||||
fn from(val: Attribute) -> Self {
|
||||
AttrString::from(val.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Attribute {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s: &'static str = (*self).into();
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
pub fn to_value(self) -> Value {
|
||||
let s: &'static str = self.into();
|
||||
Value::new_iutf8(s)
|
||||
}
|
||||
|
||||
pub fn to_partialvalue(self) -> PartialValue {
|
||||
let s: &'static str = self.into();
|
||||
PartialValue::new_iutf8(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::Deserialize<'a> for Attribute {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'a>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Attribute::try_from(s.as_str()).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EntryClass {
|
||||
AccessControlCreate,
|
||||
|
@ -933,10 +332,10 @@ pub fn builtin_accounts() -> Vec<&'static BuiltinAccount> {
|
|||
|
||||
// ============ TEST DATA ============
|
||||
#[cfg(test)]
|
||||
pub const UUID_TESTPERSON_1: Uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
||||
pub const UUID_TESTPERSON_1: Uuid = ::uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
||||
|
||||
#[cfg(test)]
|
||||
pub const UUID_TESTPERSON_2: Uuid = uuid!("538faac7-4d29-473b-a59d-23023ac19955");
|
||||
pub const UUID_TESTPERSON_2: Uuid = ::uuid::uuid!("538faac7-4d29-473b-a59d-23023ac19955");
|
||||
|
||||
#[cfg(test)]
|
||||
lazy_static! {
|
||||
|
|
|
@ -321,6 +321,7 @@ pub const UUID_SCHEMA_ATTR_APPLICATION_PASSWORD: Uuid =
|
|||
|
||||
// System and domain infos
|
||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||
pub const UUID_SYSTEM: Uuid = uuid!("00000000-0000-0000-0000-ffffff000000");
|
||||
pub const UUID_SYSTEM_INFO: Uuid = uuid!("00000000-0000-0000-0000-ffffff000001");
|
||||
pub const STR_UUID_DOMAIN_INFO: &str = "00000000-0000-0000-0000-ffffff000025";
|
||||
pub const UUID_DOMAIN_INFO: Uuid = uuid!("00000000-0000-0000-0000-ffffff000025");
|
||||
|
|
|
@ -2410,6 +2410,39 @@ impl Entry<EntryReduced, EntryCommitted> {
|
|||
Ok(ProtoEntry { attrs: attrs? })
|
||||
}
|
||||
|
||||
pub fn to_scim_kanidm(&self) -> Result<ScimEntryKanidm, OperationError> {
|
||||
let attrs = Default::default();
|
||||
/*
|
||||
let attrs = self
|
||||
.attrs
|
||||
.iter()
|
||||
.filter_map(|(k, vs)| {
|
||||
vs.to_scim_value()
|
||||
.map(|scim_value| (k, scim_value))
|
||||
})
|
||||
.collect();
|
||||
*/
|
||||
|
||||
let id = self.get_uuid();
|
||||
|
||||
// Not sure how I want to handle this yet, I think we need some schema changes
|
||||
// to achieve this.
|
||||
let schemas = Vec::with_capacity(0);
|
||||
|
||||
Ok(ScimEntryKanidm {
|
||||
header: ScimEntryHeader {
|
||||
schemas,
|
||||
id,
|
||||
// TODO: Should be spn / name or uuid.
|
||||
external_id: None,
|
||||
// TODO - this one will be useful in future, but we need to change
|
||||
// entry to store some extra metadata.
|
||||
meta: None,
|
||||
},
|
||||
attrs,
|
||||
})
|
||||
}
|
||||
|
||||
/// Transform this reduced entry into an LDAP form that can be sent to clients.
|
||||
pub fn to_ldap(
|
||||
&self,
|
||||
|
|
|
@ -14,9 +14,8 @@ use std::time::Duration;
|
|||
|
||||
use hashbrown::HashSet;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use ::base64::{engine::general_purpose, Engine as _};
|
||||
|
||||
use base64urlsafedata::Base64UrlSafeData;
|
||||
pub use compact_jwt::{compact::JwkKeySet, OidcToken};
|
||||
use compact_jwt::{
|
||||
crypto::JwsRs256Signer, jws::JwsBuilder, JwsCompact, JwsEs256Signer, JwsSigner,
|
||||
|
@ -39,6 +38,7 @@ use kanidm_proto::oauth2::{
|
|||
};
|
||||
use openssl::sha;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{base64, formats, serde_as};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::trace;
|
||||
use url::{Origin, Url};
|
||||
|
@ -48,6 +48,7 @@ use crate::idm::server::{
|
|||
IdmServerProxyReadTransaction, IdmServerProxyWriteTransaction, IdmServerTransaction,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::utils::str_join;
|
||||
use crate::value::{Oauth2Session, OauthClaimMapJoin, SessionState, OAUTHSCOPE_RE};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
|
@ -96,6 +97,7 @@ impl std::fmt::Display for Oauth2Error {
|
|||
|
||||
// == internal state formats that we encrypt and send.
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct ConsentToken {
|
||||
pub client_id: String,
|
||||
|
@ -106,7 +108,8 @@ struct ConsentToken {
|
|||
// CSRF
|
||||
pub state: String,
|
||||
// The S256 code challenge.
|
||||
pub code_challenge: Option<Base64UrlSafeData>,
|
||||
#[serde_as(as = "Option<base64::Base64<base64::UrlSafe, formats::Unpadded>>")]
|
||||
pub code_challenge: Option<Vec<u8>>,
|
||||
// Where the RS wants us to go back to.
|
||||
pub redirect_uri: Url,
|
||||
// The scopes being granted
|
||||
|
@ -115,6 +118,7 @@ struct ConsentToken {
|
|||
pub nonce: Option<String>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct TokenExchangeCode {
|
||||
// We don't need the client_id here, because it's signed with an RS specific
|
||||
|
@ -124,7 +128,8 @@ struct TokenExchangeCode {
|
|||
pub session_id: Uuid,
|
||||
|
||||
// The S256 code challenge.
|
||||
pub code_challenge: Option<Base64UrlSafeData>,
|
||||
#[serde_as(as = "Option<base64::Base64<base64::UrlSafe, formats::Unpadded>>")]
|
||||
pub code_challenge: Option<Vec<u8>>,
|
||||
// The original redirect uri
|
||||
pub redirect_uri: Url,
|
||||
// The scopes being granted
|
||||
|
@ -2653,20 +2658,6 @@ fn extra_claims_for_account(
|
|||
extra_claims
|
||||
}
|
||||
|
||||
fn str_join(set: &BTreeSet<String>) -> String {
|
||||
let alloc_len = set.iter().fold(0, |acc, s| acc + s.len() + 1);
|
||||
let mut buf = String::with_capacity(alloc_len);
|
||||
set.iter().for_each(|s| {
|
||||
buf.push_str(s);
|
||||
buf.push(' ');
|
||||
});
|
||||
|
||||
// Remove the excess trailing space.
|
||||
let _ = buf.pop();
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn validate_scopes(req_scopes: &BTreeSet<String>) -> Result<(), Oauth2Error> {
|
||||
let failed_scopes = req_scopes
|
||||
.iter()
|
||||
|
|
|
@ -546,15 +546,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
&mut self,
|
||||
sse: &'b ScimSyncUpdateEvent,
|
||||
changes: &'b ScimSyncRequest,
|
||||
) -> Result<
|
||||
(
|
||||
Uuid,
|
||||
BTreeSet<String>,
|
||||
BTreeMap<Uuid, &'b ScimEntryGeneric>,
|
||||
bool,
|
||||
),
|
||||
OperationError,
|
||||
> {
|
||||
) -> Result<(Uuid, BTreeSet<String>, BTreeMap<Uuid, &'b ScimEntry>, bool), OperationError> {
|
||||
// Assert the token is valid.
|
||||
let sync_uuid = match &sse.ident.origin {
|
||||
IdentType::User(_) | IdentType::Internal => {
|
||||
|
@ -622,7 +614,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
.unwrap_or_default();
|
||||
|
||||
// Transform the changes into something that supports lookups.
|
||||
let change_entries: BTreeMap<Uuid, &ScimEntryGeneric> = changes
|
||||
let change_entries: BTreeMap<Uuid, &ScimEntry> = changes
|
||||
.entries
|
||||
.iter()
|
||||
.map(|scim_entry| (scim_entry.id, scim_entry))
|
||||
|
@ -634,7 +626,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
#[instrument(level = "debug", skip_all)]
|
||||
pub(crate) fn scim_sync_apply_phase_2(
|
||||
&mut self,
|
||||
change_entries: &BTreeMap<Uuid, &ScimEntryGeneric>,
|
||||
change_entries: &BTreeMap<Uuid, &ScimEntry>,
|
||||
sync_uuid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
if change_entries.is_empty() {
|
||||
|
@ -757,7 +749,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
#[instrument(level = "debug", skip_all)]
|
||||
pub(crate) fn scim_sync_apply_phase_refresh_cleanup(
|
||||
&mut self,
|
||||
change_entries: &BTreeMap<Uuid, &ScimEntryGeneric>,
|
||||
change_entries: &BTreeMap<Uuid, &ScimEntry>,
|
||||
sync_uuid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
// If this is a refresh, then the providing server is sending a full state of entries
|
||||
|
@ -1124,7 +1116,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
|
||||
fn scim_entry_to_mod(
|
||||
&mut self,
|
||||
scim_ent: &ScimEntryGeneric,
|
||||
scim_ent: &ScimEntry,
|
||||
sync_uuid: Uuid,
|
||||
sync_allow_class_set: &BTreeMap<String, SchemaClass>,
|
||||
sync_allow_attr_set: &BTreeSet<String>,
|
||||
|
@ -1254,7 +1246,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
#[instrument(level = "debug", skip_all)]
|
||||
pub(crate) fn scim_sync_apply_phase_3(
|
||||
&mut self,
|
||||
change_entries: &BTreeMap<Uuid, &ScimEntryGeneric>,
|
||||
change_entries: &BTreeMap<Uuid, &ScimEntry>,
|
||||
sync_uuid: Uuid,
|
||||
sync_authority_set: &BTreeSet<String>,
|
||||
) -> Result<(), OperationError> {
|
||||
|
@ -1506,9 +1498,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
|||
|
||||
Ok(
|
||||
match sync_entry.get_ava_single_private_binary(Attribute::SyncCookie) {
|
||||
Some(b) => ScimSyncState::Active {
|
||||
cookie: b.to_vec().into(),
|
||||
},
|
||||
Some(b) => ScimSyncState::Active { cookie: b.to_vec() },
|
||||
None => ScimSyncState::Refresh,
|
||||
},
|
||||
)
|
||||
|
@ -1803,7 +1793,7 @@ mod tests {
|
|||
to_state: ScimSyncState::Active {
|
||||
cookie: vec![1, 2, 3, 4].into(),
|
||||
},
|
||||
entries: vec![ScimEntryGeneric {
|
||||
entries: vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_PERSON.to_string()],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("dn=william,ou=people,dc=test".to_string()),
|
||||
|
@ -1871,7 +1861,7 @@ mod tests {
|
|||
to_state: ScimSyncState::Active {
|
||||
cookie: vec![1, 2, 3, 4].into(),
|
||||
},
|
||||
entries: vec![ScimEntryGeneric {
|
||||
entries: vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_PERSON.to_string()],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("dn=william,ou=people,dc=test".to_string()),
|
||||
|
@ -1899,7 +1889,7 @@ mod tests {
|
|||
|
||||
async fn apply_phase_3_test(
|
||||
idms: &IdmServer,
|
||||
entries: Vec<ScimEntryGeneric>,
|
||||
entries: Vec<ScimEntry>,
|
||||
) -> Result<(), OperationError> {
|
||||
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||
let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
|
||||
|
@ -1937,7 +1927,7 @@ mod tests {
|
|||
|
||||
assert!(apply_phase_3_test(
|
||||
idms,
|
||||
vec![ScimEntryGeneric {
|
||||
vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -1978,7 +1968,7 @@ mod tests {
|
|||
|
||||
assert!(apply_phase_3_test(
|
||||
idms,
|
||||
vec![ScimEntryGeneric {
|
||||
vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2011,7 +2001,7 @@ mod tests {
|
|||
|
||||
assert!(apply_phase_3_test(
|
||||
idms,
|
||||
vec![ScimEntryGeneric {
|
||||
vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2044,7 +2034,7 @@ mod tests {
|
|||
|
||||
assert!(apply_phase_3_test(
|
||||
idms,
|
||||
vec![ScimEntryGeneric {
|
||||
vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2076,7 +2066,7 @@ mod tests {
|
|||
|
||||
assert!(apply_phase_3_test(
|
||||
idms,
|
||||
vec![ScimEntryGeneric {
|
||||
vec![ScimEntry {
|
||||
schemas: vec![format!("{SCIM_SCHEMA_SYNC_1}system")],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2114,7 +2104,7 @@ mod tests {
|
|||
to_state: ScimSyncState::Active {
|
||||
cookie: vec![1, 2, 3, 4].into(),
|
||||
},
|
||||
entries: vec![ScimEntryGeneric {
|
||||
entries: vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: user_sync_uuid,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2298,7 +2288,7 @@ mod tests {
|
|||
cookie: vec![1, 2, 3, 4].into(),
|
||||
},
|
||||
entries: vec![
|
||||
ScimEntryGeneric {
|
||||
ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: sync_uuid_a,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2308,7 +2298,7 @@ mod tests {
|
|||
ScimValue::Simple(ScimAttr::String("testgroup".to_string()))
|
||||
),),
|
||||
},
|
||||
ScimEntryGeneric {
|
||||
ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: sync_uuid_b,
|
||||
external_id: Some("cn=anothergroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2382,7 +2372,7 @@ mod tests {
|
|||
cookie: vec![1, 2, 3, 4].into(),
|
||||
},
|
||||
entries: vec![
|
||||
ScimEntryGeneric {
|
||||
ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: sync_uuid_a,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2392,7 +2382,7 @@ mod tests {
|
|||
ScimValue::Simple(ScimAttr::String("testgroup".to_string()))
|
||||
),),
|
||||
},
|
||||
ScimEntryGeneric {
|
||||
ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: sync_uuid_b,
|
||||
external_id: Some("cn=anothergroup,ou=people,dc=test".to_string()),
|
||||
|
@ -2479,7 +2469,7 @@ mod tests {
|
|||
to_state: ScimSyncState::Active {
|
||||
cookie: vec![1, 2, 3, 4].into(),
|
||||
},
|
||||
entries: vec![ScimEntryGeneric {
|
||||
entries: vec![ScimEntry {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id: sync_uuid_a,
|
||||
external_id: Some("cn=testgroup,ou=people,dc=test".to_string()),
|
||||
|
|
|
@ -67,6 +67,7 @@ pub mod testkit;
|
|||
/// A prelude of imports that should be imported by all other Kanidm modules to
|
||||
/// help make imports cleaner.
|
||||
pub mod prelude {
|
||||
pub use kanidm_proto::attribute::{AttrString, Attribute};
|
||||
pub use kanidm_proto::constants::*;
|
||||
pub use kanidm_proto::internal::{ConsistencyError, OperationError, PluginError, SchemaError};
|
||||
pub use sketching::{
|
||||
|
@ -75,7 +76,6 @@ pub mod prelude {
|
|||
security_access, security_critical, security_debug, security_error, security_info,
|
||||
tagged_event, EventTag,
|
||||
};
|
||||
pub use smartstring::alias::String as AttrString;
|
||||
pub use std::time::Duration;
|
||||
pub use url::Url;
|
||||
pub use uuid::{uuid, Uuid};
|
||||
|
@ -118,6 +118,13 @@ pub mod prelude {
|
|||
ValueSetSyntax, ValueSetT, ValueSetUtf8, ValueSetUuid,
|
||||
};
|
||||
|
||||
pub(crate) use kanidm_proto::scim_v1::{
|
||||
server::{ScimEntryKanidm, ScimValueKanidm},
|
||||
ScimEntryHeader,
|
||||
};
|
||||
|
||||
// pub(crate) use serde_json::Value as JsonValue;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use kanidmd_lib_macros::*;
|
||||
|
||||
|
|
|
@ -294,32 +294,50 @@ mod tests {
|
|||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"pres\":\"class\"}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpSearchAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpSearchAttr, Attribute::Class.to_value()),
|
||||
(Attribute::AcpSearchAttr, Attribute::Uuid.to_value()),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Class)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Uuid)),
|
||||
(Attribute::AcpModifyClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::Class.to_value()),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::DisplayName.to_value()
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyRemovedAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::May.to_value()),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::Must.to_value()),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::Class.to_value()),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::DisplayName.to_value()
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyPresentAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::May.to_value()),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::Must.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Object.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Person.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::Class.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::Description.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::DisplayName.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::Uuid.to_value())
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Class)),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::Description)
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Uuid))
|
||||
);
|
||||
pub static ref PRELOAD: Vec<EntryInitNew> =
|
||||
vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()];
|
||||
|
|
|
@ -324,9 +324,9 @@ mod tests {
|
|||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"pres\":\"class\"}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpSearchAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpSearchAttr, Attribute::Class.to_value()),
|
||||
(Attribute::AcpSearchAttr, Attribute::Uuid.to_value()),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Class)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Uuid)),
|
||||
(Attribute::AcpSearchAttr, Value::new_iutf8("classname")),
|
||||
(
|
||||
Attribute::AcpSearchAttr,
|
||||
|
@ -334,106 +334,124 @@ mod tests {
|
|||
),
|
||||
(Attribute::AcpModifyClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpModifyClass, Value::new_iutf8("domain_info")),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::Class.to_value()),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::DisplayName.to_value()
|
||||
),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::May.to_value()),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::Must.to_value()),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::DomainName.to_value()
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::DomainDisplayName.to_value()
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyRemovedAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::DomainUuid.to_value()
|
||||
Value::from(Attribute::DomainName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::DomainSsid.to_value()
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::FernetPrivateKeyStr.to_value()
|
||||
Value::from(Attribute::DomainUuid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::Es256PrivateKeyDer.to_value()
|
||||
Value::from(Attribute::DomainSsid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Attribute::PrivateCookieKey.to_value()
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::Class.to_value()),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::DisplayName.to_value()
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::May.to_value()),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::Must.to_value()),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::DomainName.to_value()
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::DomainDisplayName.to_value()
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::DomainUuid.to_value()
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyPresentAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::DomainSsid.to_value()
|
||||
Value::from(Attribute::DomainName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::FernetPrivateKeyStr.to_value()
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::Es256PrivateKeyDer.to_value()
|
||||
Value::from(Attribute::DomainUuid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Attribute::PrivateCookieKey.to_value()
|
||||
Value::from(Attribute::DomainSsid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(Attribute::AcpCreateClass, EntryClass::Object.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Account.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Person.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpCreateAttr, EntryClass::Class.to_value(),),
|
||||
(Attribute::AcpCreateAttr, Attribute::Description.to_value(),),
|
||||
(Attribute::AcpCreateAttr, Attribute::DisplayName.to_value(),),
|
||||
(Attribute::AcpCreateAttr, Attribute::DomainName.to_value(),),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Attribute::DomainDisplayName.to_value()
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Attribute::DomainUuid.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::DomainSsid.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::Uuid.to_value()),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Attribute::FernetPrivateKeyStr.to_value()
|
||||
Value::from(Attribute::Description),
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Attribute::Es256PrivateKeyDer.to_value()
|
||||
Value::from(Attribute::DisplayName),
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainName),),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainUuid)),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainSsid)),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Uuid)),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Attribute::PrivateCookieKey.to_value()
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Attribute::Version.to_value())
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Version))
|
||||
);
|
||||
pub static ref PRELOAD: Vec<EntryInitNew> =
|
||||
vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()];
|
||||
|
@ -520,8 +538,8 @@ mod tests {
|
|||
preload,
|
||||
filter!(f_eq(Attribute::ClassName, EntryClass::TestClass.into())),
|
||||
modlist!([
|
||||
m_pres(Attribute::May, &Attribute::Name.to_value()),
|
||||
m_pres(Attribute::Must, &Attribute::Name.to_value()),
|
||||
m_pres(Attribute::May, &Value::from(Attribute::Name)),
|
||||
m_pres(Attribute::Must, &Value::from(Attribute::Name)),
|
||||
]),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::be::dbvalue::DbCidV1;
|
||||
use crate::prelude::*;
|
||||
|
@ -38,6 +39,12 @@ impl fmt::Display for Cid {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&Cid> for OffsetDateTime {
|
||||
fn from(cid: &Cid) -> Self {
|
||||
OffsetDateTime::UNIX_EPOCH + cid.ts
|
||||
}
|
||||
}
|
||||
|
||||
impl Cid {
|
||||
pub(crate) fn new(s_uuid: Uuid, ts: Duration) -> Self {
|
||||
Cid { s_uuid, ts }
|
||||
|
|
|
@ -2280,7 +2280,7 @@ async fn test_repl_increment_schema_dynamic(server_a: &QueryServer, server_b: &Q
|
|||
(Attribute::ClassName, EntryClass::TestClass.to_value()),
|
||||
(Attribute::Uuid, Value::Uuid(s_uuid)),
|
||||
(Attribute::Description, Value::new_utf8s("Test Class")),
|
||||
(Attribute::May, Attribute::Name.to_value())
|
||||
(Attribute::May, Value::from(Attribute::Name))
|
||||
)])
|
||||
.is_ok());
|
||||
// Schema doesn't take effect til after a commit.
|
||||
|
|
|
@ -1316,7 +1316,7 @@ mod tests {
|
|||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpSearchAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpSearchAttr, Value::new_iutf8("class"))
|
||||
),
|
||||
AccessControlSearch
|
||||
|
@ -1395,8 +1395,14 @@ mod tests {
|
|||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::Name.to_value()),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Name)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Name)
|
||||
),
|
||||
(Attribute::AcpModifyClass, EntryClass::Object.to_value())
|
||||
),
|
||||
AccessControlModify
|
||||
|
@ -1474,7 +1480,7 @@ mod tests {
|
|||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpCreateClass, EntryClass::Object.to_value())
|
||||
),
|
||||
AccessControlCreate
|
||||
|
@ -1512,11 +1518,17 @@ mod tests {
|
|||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"eq\":[\"name\",\"a\"]}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpSearchAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpCreateClass, EntryClass::Class.to_value()),
|
||||
(Attribute::AcpCreateAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpModifyRemovedAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpModifyPresentAttr, Attribute::Name.to_value()),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Name)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Name)
|
||||
),
|
||||
(Attribute::AcpModifyClass, EntryClass::Object.to_value())
|
||||
);
|
||||
|
||||
|
|
|
@ -87,6 +87,15 @@ pub enum IdentityId {
|
|||
Internal,
|
||||
}
|
||||
|
||||
impl From<&IdentityId> for Uuid {
|
||||
fn from(ident: &IdentityId) -> Uuid {
|
||||
match ident {
|
||||
IdentityId::User(uuid) | IdentityId::Synch(uuid) => *uuid,
|
||||
IdentityId::Internal => UUID_SYSTEM,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&IdentType> for IdentityId {
|
||||
fn from(idt: &IdentType) -> Self {
|
||||
match idt {
|
||||
|
|
|
@ -2447,7 +2447,7 @@ mod tests {
|
|||
Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
|
||||
),
|
||||
(Attribute::Description, Value::new_utf8s("Test Class")),
|
||||
(Attribute::May, Attribute::Name.to_value())
|
||||
(Attribute::May, Value::from(Attribute::Name))
|
||||
);
|
||||
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
|
||||
// Add a new class.
|
||||
|
@ -2518,7 +2518,7 @@ mod tests {
|
|||
Attribute::Uuid,
|
||||
Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3"))
|
||||
),
|
||||
(Attribute::AttributeName, Attribute::TestAttr.to_value()),
|
||||
(Attribute::AttributeName, Value::from(Attribute::TestAttr)),
|
||||
(Attribute::Description, Value::new_utf8s("Test Attribute")),
|
||||
(Attribute::MultiValue, Value::new_bool(false)),
|
||||
(Attribute::Unique, Value::new_bool(false)),
|
||||
|
@ -2554,7 +2554,7 @@ mod tests {
|
|||
// delete the attr
|
||||
let de_attr = DeleteEvent::new_internal_invalid(filter!(f_eq(
|
||||
Attribute::AttributeName,
|
||||
Attribute::TestAttr.to_partialvalue()
|
||||
PartialValue::from(Attribute::TestAttr)
|
||||
)));
|
||||
assert!(server_txn.delete(&de_attr).is_ok());
|
||||
// Commit
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::prelude::*;
|
|||
use hashbrown::HashSet;
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::collections::BTreeSet;
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -74,6 +75,20 @@ pub fn readable_password_from_random() -> String {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn str_join(set: &BTreeSet<String>) -> String {
|
||||
let alloc_len = set.iter().fold(0, |acc, s| acc + s.len() + 1);
|
||||
let mut buf = String::with_capacity(alloc_len);
|
||||
set.iter().for_each(|s| {
|
||||
buf.push_str(s);
|
||||
buf.push(' ');
|
||||
});
|
||||
|
||||
// Remove the excess trailing space.
|
||||
let _ = buf.pop();
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
impl Distribution<char> for DistinctAlpha {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> char {
|
||||
const RANGE: u32 = 55;
|
||||
|
|
|
@ -447,6 +447,20 @@ impl From<CredentialType> for PartialValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Attribute> for Value {
|
||||
fn from(attr: Attribute) -> Value {
|
||||
let s: &str = attr.into();
|
||||
Value::new_iutf8(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Attribute> for PartialValue {
|
||||
fn from(attr: Attribute) -> PartialValue {
|
||||
let s: &str = attr.into();
|
||||
PartialValue::new_iutf8(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A partial value is a key or key subset that can be used to match for equality or substring
|
||||
/// against a complete Value within a set in an Entry.
|
||||
///
|
||||
|
@ -919,6 +933,16 @@ pub enum ApiTokenScope {
|
|||
Synchronise,
|
||||
}
|
||||
|
||||
impl fmt::Display for ApiTokenScope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ApiTokenScope::ReadOnly => write!(f, "read_only"),
|
||||
ApiTokenScope::ReadWrite => write!(f, "read_write"),
|
||||
ApiTokenScope::Synchronise => write!(f, "synchronise"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<ApiTokenPurpose> for ApiTokenScope {
|
||||
type Error = OperationError;
|
||||
|
||||
|
@ -949,6 +973,17 @@ pub enum SessionScope {
|
|||
Synchronise,
|
||||
}
|
||||
|
||||
impl fmt::Display for SessionScope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SessionScope::ReadOnly => write!(f, "read_only"),
|
||||
SessionScope::ReadWrite => write!(f, "read_write"),
|
||||
SessionScope::PrivilegeCapable => write!(f, "privilege_capable"),
|
||||
SessionScope::Synchronise => write!(f, "synchronise"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<UatPurposeStatus> for SessionScope {
|
||||
type Error = OperationError;
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ use crate::utils::trigraph_iter;
|
|||
use crate::value::{Address, VALIDATE_EMAIL_RE};
|
||||
use crate::valueset::{DbValueSetV2, ValueSet};
|
||||
|
||||
use kanidm_proto::scim_v1::server::{ScimAddress, ScimMail};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueSetAddress {
|
||||
set: SmolSet<[Address; 1]>,
|
||||
|
@ -163,6 +165,22 @@ impl ValueSetT for ValueSetAddress {
|
|||
Box::new(self.set.iter().map(|a| a.formatted.clone()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set
|
||||
.iter()
|
||||
.map(|a| ScimAddress {
|
||||
formatted: a.formatted.clone(),
|
||||
street_address: a.street_address.clone(),
|
||||
locality: a.locality.clone(),
|
||||
region: a.region.clone(),
|
||||
postal_code: a.postal_code.clone(),
|
||||
country: a.country.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Address(
|
||||
self.set
|
||||
|
@ -454,6 +472,21 @@ impl ValueSetT for ValueSetEmailAddress {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set
|
||||
.iter()
|
||||
.map(|mail| {
|
||||
let primary = **mail == self.primary;
|
||||
ScimMail {
|
||||
primary,
|
||||
value: mail.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::EmailAddress(self.primary.clone(), self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -525,9 +558,9 @@ pub struct ValueSetPhoneNumber {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetEmailAddress;
|
||||
use super::{ValueSetAddress, ValueSetEmailAddress};
|
||||
use crate::repl::cid::Cid;
|
||||
use crate::value::{PartialValue, Value};
|
||||
use crate::value::{Address, PartialValue, Value};
|
||||
use crate::valueset::{self, ValueSet};
|
||||
|
||||
#[test]
|
||||
|
@ -605,4 +638,52 @@ mod tests {
|
|||
assert_eq!(vs.len(), 0);
|
||||
assert!(vs.to_email_address_primary_str().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_emailaddress() {
|
||||
let mut vs: ValueSet = ValueSetEmailAddress::new("claire@example.com".to_string());
|
||||
// Add another, still not primary.
|
||||
assert!(
|
||||
vs.insert_checked(
|
||||
Value::new_email_address_s("alice@example.com").expect("Invalid Email")
|
||||
) == Ok(true)
|
||||
);
|
||||
|
||||
let data = r#"[
|
||||
{
|
||||
"primary": false,
|
||||
"value": "alice@example.com"
|
||||
},
|
||||
{
|
||||
"primary": true,
|
||||
"value": "claire@example.com"
|
||||
}
|
||||
]"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_address() {
|
||||
let vs: ValueSet = ValueSetAddress::new(Address {
|
||||
formatted: "1 No Where Lane, Doesn't Exist, Brisbane, 0420, Australia".to_string(),
|
||||
street_address: "1 No Where Lane".to_string(),
|
||||
locality: "Doesn't Exist".to_string(),
|
||||
region: "Brisbane".to_string(),
|
||||
postal_code: "0420".to_string(),
|
||||
country: "Australia".to_string(),
|
||||
});
|
||||
|
||||
let data = r#"[
|
||||
{
|
||||
"country": "Australia",
|
||||
"formatted": "1 No Where Lane, Doesn't Exist, Brisbane, 0420, Australia",
|
||||
"locality": "Doesn't Exist",
|
||||
"postalCode": "0420",
|
||||
"region": "Brisbane",
|
||||
"streetAddress": "1 No Where Lane"
|
||||
}
|
||||
]"#;
|
||||
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ use crate::repl::proto::ReplAttrV1;
|
|||
use crate::schema::SchemaAttribute;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use kanidm_proto::scim_v1::server::ScimApplicationPassword;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueSetApplicationPassword {
|
||||
// The map key is application's UUID
|
||||
|
@ -186,6 +188,20 @@ impl ValueSetT for ValueSetApplicationPassword {
|
|||
}))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.values()
|
||||
.flatten()
|
||||
.map(|app_pwd| ScimApplicationPassword {
|
||||
uuid: app_pwd.uuid,
|
||||
application_uuid: app_pwd.application,
|
||||
label: app_pwd.label.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
let data = self.to_vec_dbvs();
|
||||
DbValueSetV2::ApplicationPassword(data)
|
||||
|
@ -316,4 +332,64 @@ mod tests {
|
|||
let res = vs.as_application_password_map().unwrap();
|
||||
assert_eq!(res.keys().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_application_password() {
|
||||
let app1_uuid = uuid::uuid!("7c3cd2b4-dc0d-43f5-999c-4912c2412405");
|
||||
let app2_uuid = uuid::uuid!("82eaeca8-4250-4b63-a94b-75a3764a9327");
|
||||
let ap1_uuid = uuid::uuid!("f36434ba-087a-4774-90ea-ebcda7f8c549");
|
||||
let ap2_uuid = uuid::uuid!("b78506c7-eb7a-45d8-a994-34e868ee1a9e");
|
||||
let ap3_uuid = uuid::uuid!("740a9d06-1188-4c48-9c5c-dbf863712c66");
|
||||
|
||||
let ap1: ApplicationPassword = ApplicationPassword {
|
||||
uuid: ap1_uuid,
|
||||
application: app1_uuid,
|
||||
label: "apppwd1".to_string(),
|
||||
password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd1")
|
||||
.expect("Failed to create password"),
|
||||
};
|
||||
|
||||
let ap2: ApplicationPassword = ApplicationPassword {
|
||||
uuid: ap2_uuid,
|
||||
application: app1_uuid,
|
||||
label: "apppwd2".to_string(),
|
||||
password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd2")
|
||||
.expect("Failed to create password"),
|
||||
};
|
||||
|
||||
let ap3: ApplicationPassword = ApplicationPassword {
|
||||
uuid: ap3_uuid,
|
||||
application: app2_uuid,
|
||||
label: "apppwd3".to_string(),
|
||||
password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd3")
|
||||
.expect("Failed to create password"),
|
||||
};
|
||||
|
||||
let mut vs: ValueSet = ValueSetApplicationPassword::new(ap1);
|
||||
vs.insert_checked(Value::ApplicationPassword(ap2))
|
||||
.expect("Failed to insert");
|
||||
vs.insert_checked(Value::ApplicationPassword(ap3))
|
||||
.expect("Failed to insert");
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"applicationUuid": "7c3cd2b4-dc0d-43f5-999c-4912c2412405",
|
||||
"label": "apppwd1",
|
||||
"uuid": "f36434ba-087a-4774-90ea-ebcda7f8c549"
|
||||
},
|
||||
{
|
||||
"applicationUuid": "7c3cd2b4-dc0d-43f5-999c-4912c2412405",
|
||||
"label": "apppwd2",
|
||||
"uuid": "b78506c7-eb7a-45d8-a994-34e868ee1a9e"
|
||||
},
|
||||
{
|
||||
"applicationUuid": "82eaeca8-4250-4b63-a94b-75a3764a9327",
|
||||
"label": "apppwd3",
|
||||
"uuid": "740a9d06-1188-4c48-9c5c-dbf863712c66"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ use crate::repl::cid::Cid;
|
|||
use crate::repl::proto::ReplAttrV1;
|
||||
use crate::schema::SchemaAttribute;
|
||||
use crate::valueset::{DbValueSetV2, ValueSet};
|
||||
use kanidm_proto::scim_v1::server::ScimAuditString;
|
||||
use std::collections::BTreeMap;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
type AuditLogStringType = (Cid, String);
|
||||
|
||||
|
@ -119,6 +121,21 @@ impl ValueSetT for ValueSetAuditLogString {
|
|||
Box::new(self.map.iter().map(|(d, s)| format!("{d}-{s}")))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(cid, strdata)| {
|
||||
let odt: OffsetDateTime = cid.into();
|
||||
ScimAuditString {
|
||||
date_time: odt,
|
||||
value: strdata.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::AuditLogString(
|
||||
self.map
|
||||
|
@ -350,4 +367,60 @@ mod tests {
|
|||
assert_eq!(c.ts, Duration::from_secs(2));
|
||||
drop(v_iter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_auditlog_string() {
|
||||
let mut vs: ValueSet = ValueSetAuditLogString::new((Cid::new_count(0), "A".to_string()));
|
||||
assert!(vs.len() == 1);
|
||||
|
||||
for i in 1..AUDIT_LOG_STRING_CAPACITY {
|
||||
vs.insert_checked(Value::AuditLogString(
|
||||
Cid::new_count(i as u64),
|
||||
"A".to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:00Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:01Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:02Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:03Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:04Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:05Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:06Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:07Z",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"dateTime": "1970-01-01T00:00:08Z",
|
||||
"value": "A"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::repl::proto::ReplAttrV1;
|
|||
use crate::schema::SchemaAttribute;
|
||||
use crate::utils::trigraph_iter;
|
||||
use crate::valueset::{DbValueSetV2, ValueSet};
|
||||
use kanidm_proto::scim_v1::server::ScimBinary;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueSetPrivateBinary {
|
||||
|
@ -107,6 +108,10 @@ impl ValueSetT for ValueSetPrivateBinary {
|
|||
Box::new(self.set.iter().map(|_| "private_binary".to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::PrivateBinary(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -267,8 +272,11 @@ impl ValueSetT for ValueSetPublicBinary {
|
|||
}
|
||||
|
||||
fn syntax(&self) -> SyntaxType {
|
||||
unreachable!();
|
||||
// Apparently I never actually implemented this type in ... anything?
|
||||
// We should probably clean up syntax soon .....
|
||||
//
|
||||
// SyntaxType::PublicBinary
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||
|
@ -281,6 +289,18 @@ impl ValueSetT for ValueSetPublicBinary {
|
|||
Box::new(self.map.keys().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(tag, bin)| ScimBinary {
|
||||
label: tag.clone(),
|
||||
value: bin.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::PublicBinary(
|
||||
self.map
|
||||
|
@ -334,3 +354,16 @@ impl ValueSetT for ValueSetPublicBinary {
|
|||
Some(&self.map)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetPrivateBinary;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_private_binary() {
|
||||
let vs: ValueSet = ValueSetPrivateBinary::new(vec![0x00]);
|
||||
|
||||
assert!(vs.to_scim_value().is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,18 @@ impl ValueSetT for ValueSetBool {
|
|||
Box::new(self.set.iter().map(|b| b.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
if self.len() == 1 {
|
||||
// Because self.len == 1 we know this has to yield a value.
|
||||
let b = self.set.iter().copied().next().unwrap_or_default();
|
||||
|
||||
Some(b.into())
|
||||
} else {
|
||||
// Makes no sense for more than 1 value.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Bool(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -159,3 +171,15 @@ impl ValueSetT for ValueSetBool {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetBool;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_boolean() {
|
||||
let vs: ValueSet = ValueSetBool::new(true);
|
||||
crate::valueset::scim_json_reflexive(vs, "true");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::prelude::*;
|
|||
use crate::repl::proto::ReplAttrV1;
|
||||
use crate::schema::SchemaAttribute;
|
||||
use crate::valueset::{DbValueSetV2, ValueSet};
|
||||
use kanidm_proto::scim_v1::server::ScimCertificate;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use kanidm_lib_crypto::{
|
||||
|
@ -195,6 +196,35 @@ impl ValueSetT for ValueSetCertificate {
|
|||
}))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let vals: Vec<ScimCertificate> = self
|
||||
.map
|
||||
.iter()
|
||||
.filter_map(|(s256, cert)| {
|
||||
cert.to_der()
|
||||
.map_err(|der_err| {
|
||||
error!(
|
||||
?s256,
|
||||
?der_err,
|
||||
"Failed to serialise certificate to der. This value will be dropped!"
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
.map(|der| (s256, der))
|
||||
})
|
||||
.map(|(s256, cert_der)| ScimCertificate {
|
||||
s256: s256.to_vec(),
|
||||
der: cert_der,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if vals.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ScimValueKanidm::from(vals))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
let data = self.to_vec_dbvs();
|
||||
DbValueSetV2::Certificate(data)
|
||||
|
@ -248,3 +278,49 @@ impl ValueSetT for ValueSetCertificate {
|
|||
Some(&self.map)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetCertificate;
|
||||
use crate::prelude::{ScimValueKanidm, ValueSet};
|
||||
use kanidm_lib_crypto::x509_cert::der::DecodePem;
|
||||
use kanidm_lib_crypto::x509_cert::Certificate;
|
||||
|
||||
// Generated with:
|
||||
//
|
||||
// openssl ecparam -out ec_key.pem -name secp256r1 -genkey
|
||||
// openssl req -new -key ec_key.pem -x509 -nodes -days 365 -out cert.pem
|
||||
const PEM_DATA: &str = r#"-----BEGIN CERTIFICATE-----
|
||||
MIIB3zCCAYWgAwIBAgIUdJ6IWvI+8M6nwK7ykUK7/iBq7yQwCgYIKoZIzj0EAwIw
|
||||
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
|
||||
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA4MjEwNjQ2MzBaFw0yNTA4MjEw
|
||||
NjQ2MzBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
|
||||
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO
|
||||
PQMBBwNCAAS2Szn4NPmgxawC1+MRC41jqobemNkXkRZ9AgozK0zRDFc6k1IHUZ++
|
||||
wN0USpXDQYDnJfATqvlpKPebnHxTytt6o1MwUTAdBgNVHQ4EFgQU1oR1x2CnoPap
|
||||
JMKPCVVzqWf2ANYwHwYDVR0jBBgwFoAU1oR1x2CnoPapJMKPCVVzqWf2ANYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBpy0o2CY97MIxeQ0HgG44Y
|
||||
raBy6edj7W0EIH+yQxkDEwIhAI0nVKaI6duHLAvtKW6CfEQFG6jKg7dyk37YYiRD
|
||||
2jS0
|
||||
-----END CERTIFICATE-----"#;
|
||||
|
||||
#[test]
|
||||
fn test_scim_certificate() {
|
||||
let cert = Certificate::from_pem(PEM_DATA).unwrap();
|
||||
|
||||
let vs: ValueSet = ValueSetCertificate::new(Box::new(cert)).unwrap();
|
||||
|
||||
let scim_value = vs.to_scim_value().unwrap();
|
||||
|
||||
let cert = match scim_value {
|
||||
ScimValueKanidm::ArrayCertificate(mut set) => set.pop().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let expect_s256 =
|
||||
hex::decode("8c98a09d0a50db92ccb6d05be846d9b9315015520d19a3e1739aeb8d84ebc28d")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(cert.s256, expect_s256);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,18 @@ impl ValueSetT for ValueSetCid {
|
|||
}
|
||||
|
||||
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
|
||||
Box::new(self.set.iter().map(|c| format!("{:?}_{}", c.ts, c.s_uuid)))
|
||||
Box::new(self.set.iter().map(|c| c.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().map(|cid| cid.to_string());
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
|
@ -183,3 +194,17 @@ impl ValueSetT for ValueSetCid {
|
|||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetCid;
|
||||
use crate::prelude::{Cid, ValueSet};
|
||||
|
||||
#[test]
|
||||
fn test_scim_cid() {
|
||||
let vs: ValueSet = ValueSetCid::new(Cid::new_zero());
|
||||
|
||||
let data = r#""00000000000000000000000000000000-00000000-0000-0000-0000-000000000000""#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use smolset::SmolSet;
|
||||
use std::collections::btree_map::Entry as BTreeEntry;
|
||||
use std::collections::BTreeMap;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use webauthn_rs::prelude::{
|
||||
AttestationCaList, AttestedPasskey as AttestedPasskeyV4, Passkey as PasskeyV4,
|
||||
|
@ -19,6 +20,8 @@ use crate::utils::trigraph_iter;
|
|||
use crate::value::{CredUpdateSessionPerms, CredentialType, IntentTokenState};
|
||||
use crate::valueset::{DbValueSetV2, ValueSet};
|
||||
|
||||
use kanidm_proto::scim_v1::server::{ScimIntentToken, ScimIntentTokenState};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueSetCredential {
|
||||
map: BTreeMap<String, Credential>,
|
||||
|
@ -149,6 +152,12 @@ impl ValueSetT for ValueSetCredential {
|
|||
Box::new(self.map.keys().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
// Currently I think we don't need to yield cred info as that's part of the
|
||||
// cred update session instead.
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Credential(
|
||||
self.map
|
||||
|
@ -441,6 +450,35 @@ impl ValueSetT for ValueSetIntentToken {
|
|||
Box::new(self.map.keys().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(token_id, intent_token_state)| {
|
||||
let (state, max_ttl) = match intent_token_state {
|
||||
IntentTokenState::Valid { max_ttl, .. } => {
|
||||
(ScimIntentTokenState::Valid, *max_ttl)
|
||||
}
|
||||
IntentTokenState::InProgress { max_ttl, .. } => {
|
||||
(ScimIntentTokenState::InProgress, *max_ttl)
|
||||
}
|
||||
IntentTokenState::Consumed { max_ttl } => {
|
||||
(ScimIntentTokenState::Consumed, *max_ttl)
|
||||
}
|
||||
};
|
||||
|
||||
let odt: OffsetDateTime = OffsetDateTime::UNIX_EPOCH + max_ttl;
|
||||
|
||||
ScimIntentToken {
|
||||
token_id: token_id.clone(),
|
||||
state,
|
||||
expires: odt,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::IntentToken(
|
||||
self.map
|
||||
|
@ -725,6 +763,10 @@ impl ValueSetT for ValueSetPasskey {
|
|||
Box::new(self.map.values().map(|(t, _)| t).cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Passkey(
|
||||
self.map
|
||||
|
@ -917,6 +959,10 @@ impl ValueSetT for ValueSetAttestedPasskey {
|
|||
Box::new(self.map.values().map(|(t, _)| t).cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::AttestedPasskey(
|
||||
self.map
|
||||
|
@ -1098,6 +1144,12 @@ impl ValueSetT for ValueSetCredentialType {
|
|||
Box::new(self.set.iter().map(|ct| ct.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set.iter().map(|ct| ct.to_string()).collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::CredentialType(self.set.iter().map(|s| *s as u16).collect())
|
||||
}
|
||||
|
@ -1279,6 +1331,17 @@ impl ValueSetT for ValueSetWebauthnAttestationCaList {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.ca_list
|
||||
.cas()
|
||||
.values()
|
||||
.flat_map(|att_ca| att_ca.aaguids().values())
|
||||
.map(|device| device.description_en().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_repl_v1(&self) -> ReplAttrV1 {
|
||||
ReplAttrV1::WebauthnAttestationCaList {
|
||||
ca_list: self.ca_list.clone(),
|
||||
|
@ -1318,3 +1381,38 @@ impl ValueSetT for ValueSetWebauthnAttestationCaList {
|
|||
Some(&self.ca_list)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CredentialType, IntentTokenState, ValueSetCredentialType, ValueSetIntentToken};
|
||||
use crate::prelude::ValueSet;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_scim_intent_token() {
|
||||
// I seem to recall this shouldn't have a value returned?
|
||||
let vs: ValueSet = ValueSetIntentToken::new(
|
||||
"ca6f29d1-034b-41fb-abc1-4bb9f0548e67".to_string(),
|
||||
IntentTokenState::Consumed {
|
||||
max_ttl: Duration::from_secs(300),
|
||||
},
|
||||
);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"expires": "1970-01-01T00:05:00Z",
|
||||
"state": "consumed",
|
||||
"tokenId": "ca6f29d1-034b-41fb-abc1-4bb9f0548e67"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_credential_type() {
|
||||
let vs: ValueSet = ValueSetCredentialType::new(CredentialType::Mfa);
|
||||
crate::valueset::scim_json_reflexive(vs, r#"["mfa"]"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,6 +136,17 @@ impl ValueSetT for ValueSetDateTime {
|
|||
}))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().copied();
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or(OffsetDateTime::UNIX_EPOCH);
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::DateTime(
|
||||
self.set
|
||||
|
@ -203,3 +214,19 @@ impl ValueSetT for ValueSetDateTime {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetDateTime;
|
||||
use crate::prelude::ValueSet;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[test]
|
||||
fn test_scim_datetime() {
|
||||
let odt = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(69_420);
|
||||
let vs: ValueSet = ValueSetDateTime::new(odt);
|
||||
|
||||
crate::valueset::scim_json_reflexive(vs, r#""1970-01-01T19:17:00Z""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,6 +133,10 @@ impl ValueSetT for ValueSetEcKeyPrivate {
|
|||
Box::new(iter::once(String::from("hidden")))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
#[allow(clippy::expect_used)]
|
||||
let key_der = self
|
||||
|
|
|
@ -133,6 +133,17 @@ impl ValueSetT for ValueSetHexString {
|
|||
Box::new(self.set.iter().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().cloned();
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::HexString(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -173,3 +184,16 @@ impl ValueSetT for ValueSetHexString {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetHexString;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_hexstring() {
|
||||
let vs: ValueSet =
|
||||
ValueSetHexString::new("D68475C760A7A0F6A924C28F095573A967F600D6".to_string());
|
||||
crate::valueset::scim_json_reflexive(vs, r#""D68475C760A7A0F6A924C28F095573A967F600D6""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -417,6 +417,24 @@ impl ValueSetT for ValueSetImage {
|
|||
Box::new(self.set.iter().map(|image| image.hash_imagevalue()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
// TODO: This should be a reference to the image URL, not the image itself!
|
||||
// Does this mean we need to pass in the domain / origin so we can render
|
||||
// these URL's correctly?
|
||||
//
|
||||
// TODO: Currently we don't have a generic way to reference images, we need
|
||||
// to add one.
|
||||
//
|
||||
// TODO: Scim supports a "type" field here, but do we care?
|
||||
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set
|
||||
.iter()
|
||||
.map(|image| image.hash_imagevalue())
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Image(
|
||||
self.set
|
||||
|
@ -477,60 +495,87 @@ impl ValueSetT for ValueSetImage {
|
|||
}
|
||||
}
|
||||
|
||||
// this seems dumb
|
||||
fn as_imageset(&self) -> Option<&HashSet<ImageValue>> {
|
||||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// tests that we can load a bunch of test images and it'll throw errors in a way we expect
|
||||
fn test_imagevalue_things() {
|
||||
["gif", "png", "jpg", "webp"]
|
||||
.into_iter()
|
||||
.for_each(|extension| {
|
||||
// test should-be-bad images
|
||||
let filename = format!(
|
||||
"{}/src/valueset/image/test_images/oversize_dimensions.{extension}",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
trace!("testing {}", &filename);
|
||||
let image = ImageValue {
|
||||
filename: format!("oversize_dimensions.{extension}"),
|
||||
filetype: ImageType::try_from(extension).unwrap(),
|
||||
contents: std::fs::read(filename).unwrap(),
|
||||
};
|
||||
let res = image.validate_image();
|
||||
trace!("{:?}", &res);
|
||||
assert!(res.is_err());
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ImageType, ImageValue, ImageValueThings};
|
||||
|
||||
// test should-be-good images
|
||||
let filename = format!(
|
||||
"{}/src/valueset/image/test_images/ok.{extension}",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
trace!("testing {}", &filename);
|
||||
let image = ImageValue {
|
||||
filename: filename.clone(),
|
||||
filetype: ImageType::try_from(extension).unwrap(),
|
||||
contents: std::fs::read(filename).unwrap(),
|
||||
};
|
||||
let res = image.validate_image();
|
||||
trace!("validation result of {}: {:?}", image.filename, &res);
|
||||
assert!(res.is_ok());
|
||||
#[test]
|
||||
/// tests that we can load a bunch of test images and it'll throw errors in a way we expect
|
||||
fn test_imagevalue_loading() {
|
||||
["gif", "png", "jpg", "webp"]
|
||||
.into_iter()
|
||||
.for_each(|extension| {
|
||||
// test should-be-bad images
|
||||
let filename = format!(
|
||||
"{}/src/valueset/image/test_images/oversize_dimensions.{extension}",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
trace!("testing {}", &filename);
|
||||
let image = ImageValue {
|
||||
filename: format!("oversize_dimensions.{extension}"),
|
||||
filetype: ImageType::try_from(extension).unwrap(),
|
||||
contents: std::fs::read(filename).unwrap(),
|
||||
};
|
||||
let res = image.validate_image();
|
||||
trace!("{:?}", &res);
|
||||
assert!(res.is_err());
|
||||
|
||||
let filename = format!(
|
||||
"{}/src/valueset/image/test_images/ok.svg",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
let image = ImageValue {
|
||||
filename: filename.clone(),
|
||||
filetype: ImageType::Svg,
|
||||
contents: std::fs::read(&filename).unwrap(),
|
||||
};
|
||||
let res = image.validate_image();
|
||||
trace!("SVG Validation result of {}: {:?}", filename, &res);
|
||||
assert!(res.is_ok());
|
||||
assert!(!image.hash_imagevalue().is_empty());
|
||||
})
|
||||
let filename = format!(
|
||||
"{}/src/valueset/image/test_images/ok.svg",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
let image = ImageValue {
|
||||
filename: filename.clone(),
|
||||
filetype: ImageType::Svg,
|
||||
contents: std::fs::read(&filename).unwrap(),
|
||||
};
|
||||
let res = image.validate_image();
|
||||
trace!("SVG Validation result of {}: {:?}", filename, &res);
|
||||
assert!(res.is_ok());
|
||||
assert!(!image.hash_imagevalue().is_empty());
|
||||
});
|
||||
|
||||
let filename = format!(
|
||||
"{}/src/valueset/image/test_images/ok.svg",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
let image = ImageValue {
|
||||
filename: filename.clone(),
|
||||
filetype: ImageType::Svg,
|
||||
contents: std::fs::read(&filename).unwrap(),
|
||||
};
|
||||
let res = image.validate_image();
|
||||
trace!("SVG Validation result of {}: {:?}", filename, &res);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(image.hash_imagevalue().is_empty(), false);
|
||||
}
|
||||
|
||||
/*
|
||||
// This test is broken on github as it appears to be changing the binary image hash.
|
||||
#[test]
|
||||
fn test_scim_imagevalue() {
|
||||
let filename = format!(
|
||||
"{}/src/valueset/image/test_images/ok.jpg",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
let image = ImageValue {
|
||||
filename: filename.clone(),
|
||||
filetype: ImageType::Jpg,
|
||||
contents: std::fs::read(&filename).unwrap(),
|
||||
};
|
||||
|
||||
let vs = ValueSetImage::new(image);
|
||||
|
||||
let data = r#"[
|
||||
"142dc7984dd548dd5dacfe2ad30f8473e3217e39b3b6c8d17a0cf6e4e24b02e0"
|
||||
]"#;
|
||||
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -144,6 +144,17 @@ impl ValueSetT for ValueSetIname {
|
|||
Box::new(self.set.iter().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().cloned();
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Iname(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -200,3 +211,15 @@ impl ValueSetT for ValueSetIname {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetIname;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_iname() {
|
||||
let vs: ValueSet = ValueSetIname::new("stevo");
|
||||
crate::valueset::scim_json_reflexive(vs, r#""stevo""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,12 @@ impl ValueSetT for ValueSetIndex {
|
|||
Box::new(self.set.iter().map(|b| b.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set.iter().map(|u| u.to_string()).collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::IndexType(self.set.iter().map(|s| *s as u16).collect())
|
||||
}
|
||||
|
@ -156,3 +162,15 @@ impl ValueSetT for ValueSetIndex {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetIndex;
|
||||
use crate::prelude::{IndexType, ValueSet};
|
||||
|
||||
#[test]
|
||||
fn test_scim_index() {
|
||||
let vs: ValueSet = ValueSetIndex::new(IndexType::Equality);
|
||||
crate::valueset::scim_json_reflexive(vs, r#"["EQUALITY"]"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,6 +142,17 @@ impl ValueSetT for ValueSetIutf8 {
|
|||
Box::new(self.set.iter().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().cloned();
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Iutf8(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -200,3 +211,15 @@ impl ValueSetT for ValueSetIutf8 {
|
|||
Ok(vsi)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetIutf8;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_iutf8() {
|
||||
let vs: ValueSet = ValueSetIutf8::new("lowercase string");
|
||||
crate::valueset::scim_json_reflexive(vs, r#""lowercase string""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,20 +121,40 @@ impl ValueSetT for ValueSetJsonFilter {
|
|||
}
|
||||
|
||||
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
|
||||
Box::new(self.set.iter().map(|i| {
|
||||
#[allow(clippy::expect_used)]
|
||||
serde_json::to_string(i).expect("A json filter value was corrupted during run-time")
|
||||
Box::new(self.set.iter().filter_map(|i| {
|
||||
serde_json::to_string(i)
|
||||
.inspect_err(|err| {
|
||||
error!(?err, "A json filter value was corrupted during run-time")
|
||||
})
|
||||
.ok()
|
||||
}))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set
|
||||
.iter()
|
||||
.filter_map(|s| {
|
||||
serde_json::to_string(s)
|
||||
.inspect_err(|err| {
|
||||
error!(?err, "A json filter value was corrupted during run-time")
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::JsonFilter(
|
||||
self.set
|
||||
.iter()
|
||||
.map(|s| {
|
||||
#[allow(clippy::expect_used)]
|
||||
.filter_map(|s| {
|
||||
serde_json::to_string(s)
|
||||
.expect("A json filter value was corrupted during run-time")
|
||||
.inspect_err(|err| {
|
||||
error!(?err, "A json filter value was corrupted during run-time")
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
|
@ -145,10 +165,12 @@ impl ValueSetT for ValueSetJsonFilter {
|
|||
set: self
|
||||
.set
|
||||
.iter()
|
||||
.map(|s| {
|
||||
#[allow(clippy::expect_used)]
|
||||
.filter_map(|s| {
|
||||
serde_json::to_string(s)
|
||||
.expect("A json filter value was corrupted during run-time")
|
||||
.inspect_err(|err| {
|
||||
error!(?err, "A json filter value was corrupted during run-time")
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
|
@ -192,3 +214,22 @@ impl ValueSetT for ValueSetJsonFilter {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ProtoFilter, ValueSetJsonFilter};
|
||||
use crate::prelude::{Attribute, ValueSet};
|
||||
|
||||
#[test]
|
||||
fn test_scim_json_filter() {
|
||||
let filter = ProtoFilter::Pres(Attribute::Class.to_string());
|
||||
let vs: ValueSet = ValueSetJsonFilter::new(filter);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
"{\"pres\":\"class\"}"
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,10 @@ impl ValueSetT for ValueSetJwsKeyEs256 {
|
|||
Box::new(self.set.iter().map(|k| k.get_kid().to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::JwsKeyEs256(self.set.iter()
|
||||
.map(|k| {
|
||||
|
@ -316,6 +320,10 @@ impl ValueSetT for ValueSetJwsKeyRs256 {
|
|||
Box::new(self.set.iter().map(|k| k.get_kid().to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::JwsKeyRs256(self.set.iter()
|
||||
.map(|k| {
|
||||
|
|
|
@ -9,6 +9,9 @@ use crate::valueset::{DbValueSetV2, ValueSet};
|
|||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use kanidm_proto::scim_v1::server::ScimKeyInternal;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct KeyInternalData {
|
||||
|
@ -284,6 +287,25 @@ impl ValueSetT for ValueSetKeyInternal {
|
|||
}))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(kid, key_object)| {
|
||||
let odt: OffsetDateTime =
|
||||
OffsetDateTime::UNIX_EPOCH + Duration::from_secs(key_object.valid_from);
|
||||
|
||||
ScimKeyInternal {
|
||||
key_id: kid.clone(),
|
||||
status: key_object.status.to_string(),
|
||||
usage: key_object.usage.to_string(),
|
||||
valid_from: odt,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
let keys = self.to_vec_dbvs();
|
||||
DbValueSetV2::KeyInternal(keys)
|
||||
|
@ -617,4 +639,29 @@ mod tests {
|
|||
// Assert the item was trimmed
|
||||
assert!(!key_internal_map.contains_key(&kid_2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_key_internal() {
|
||||
let kid = "test".to_string();
|
||||
let usage = KeyUsage::JwsEs256;
|
||||
let valid_from = 0;
|
||||
let status = KeyStatus::Valid;
|
||||
let status_cid = Cid::new_zero();
|
||||
let der = Vec::with_capacity(0);
|
||||
|
||||
let vs: ValueSet =
|
||||
ValueSetKeyInternal::new(kid.clone(), usage, valid_from, status, status_cid, der);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"keyId": "test",
|
||||
"status": "valid",
|
||||
"usage": "jws_es256",
|
||||
"validFrom": "1970-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,6 +144,8 @@ pub trait ValueSetT: std::fmt::Debug + DynClone {
|
|||
|
||||
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_>;
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm>;
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2;
|
||||
|
||||
fn to_repl_v1(&self) -> ReplAttrV1;
|
||||
|
@ -928,3 +930,17 @@ pub fn from_repl_v1(rv1: &ReplAttrV1) -> Result<ValueSet, OperationError> {
|
|||
ReplAttrV1::ApplicationPassword { set } => ValueSetApplicationPassword::from_repl_v1(set),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn scim_json_reflexive(vs: ValueSet, data: &str) {
|
||||
let scim_value = vs.to_scim_value().unwrap();
|
||||
|
||||
let strout = serde_json::to_string_pretty(&scim_value).unwrap();
|
||||
eprintln!("{}", strout);
|
||||
|
||||
let json_value: serde_json::Value = serde_json::to_value(&scim_value).unwrap();
|
||||
|
||||
let expect: serde_json::Value = serde_json::from_str(data).unwrap();
|
||||
|
||||
assert_eq!(json_value, expect);
|
||||
}
|
||||
|
|
|
@ -112,6 +112,17 @@ impl ValueSetT for ValueSetNsUniqueId {
|
|||
Box::new(self.set.iter().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().cloned();
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::NsUniqueId(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -162,3 +173,16 @@ impl ValueSetT for ValueSetNsUniqueId {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetNsUniqueId;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_nsuniqueid() {
|
||||
let vs: ValueSet =
|
||||
ValueSetNsUniqueId::new("3a163ca0-47624620-a18806b7-50c84c86".to_string());
|
||||
crate::valueset::scim_json_reflexive(vs, r#""3a163ca0-47624620-a18806b7-50c84c86""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,13 @@ use crate::be::dbvalue::{DbValueOauthClaimMap, DbValueOauthScopeMapV1};
|
|||
use crate::prelude::*;
|
||||
use crate::repl::proto::{ReplAttrV1, ReplOauthClaimMapV1, ReplOauthScopeMapV1};
|
||||
use crate::schema::SchemaAttribute;
|
||||
use crate::utils::str_join;
|
||||
use crate::value::{OauthClaimMapJoin, OAUTHSCOPE_RE};
|
||||
use crate::valueset::{uuid_to_proto_string, DbValueSetV2, ValueSet};
|
||||
|
||||
use kanidm_proto::scim_v1::server::ScimOAuth2ClaimMap;
|
||||
use kanidm_proto::scim_v1::server::ScimOAuth2ScopeMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueSetOauthScope {
|
||||
set: BTreeSet<String>,
|
||||
|
@ -114,6 +118,10 @@ impl ValueSetT for ValueSetOauthScope {
|
|||
Box::new(self.set.iter().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(str_join(&self.set).into())
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::OauthScope(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -301,6 +309,21 @@ impl ValueSetT for ValueSetOauthScopeMap {
|
|||
)
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(uuid, scopes)| {
|
||||
ScimOAuth2ScopeMap {
|
||||
uuid: *uuid,
|
||||
// Flattened to a space separated list.
|
||||
scopes: scopes.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::OauthScopeMap(
|
||||
self.map
|
||||
|
@ -665,6 +688,25 @@ impl ValueSetT for ValueSetOauthClaimMap {
|
|||
}))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.flat_map(|(claim_name, mappings)| {
|
||||
mappings
|
||||
.values
|
||||
.iter()
|
||||
.map(|(group_uuid, claim_values)| ScimOAuth2ClaimMap {
|
||||
group: *group_uuid,
|
||||
claim: claim_name.to_string(),
|
||||
join_char: mappings.join.to_str().to_string(),
|
||||
values: claim_values.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::OauthClaimMap(
|
||||
self.map
|
||||
|
@ -742,7 +784,8 @@ impl ValueSetT for ValueSetOauthClaimMap {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetOauthClaimMap;
|
||||
use super::{ValueSetOauthClaimMap, ValueSetOauthScope, ValueSetOauthScopeMap};
|
||||
use crate::prelude::ValueSet;
|
||||
use crate::valueset::ValueSetT;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
|
@ -760,4 +803,47 @@ mod tests {
|
|||
"claim: 5a6b8783-3f67-4ebb-b6aa-77fd6e66589f \"\"\"\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_oauth2_scope() {
|
||||
let vs: ValueSet = ValueSetOauthScope::new("fully_sick_scope_m8".to_string());
|
||||
let data = r#""fully_sick_scope_m8""#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_oauth2_scope_map() {
|
||||
let u = uuid::uuid!("3a163ca0-4762-4620-a188-06b750c84c86");
|
||||
let set = ["read".to_string(), "write".to_string()].into();
|
||||
let vs: ValueSet = ValueSetOauthScopeMap::new(u, set);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"scopes": "read write",
|
||||
"uuid": "3a163ca0-4762-4620-a188-06b750c84c86"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_oauth2_claim_map() {
|
||||
let u = uuid::uuid!("3a163ca0-4762-4620-a188-06b750c84c86");
|
||||
let set = ["read".to_string(), "write".to_string()].into();
|
||||
let vs: ValueSet = ValueSetOauthClaimMap::new_value("claim".to_string(), u, set);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"claim": "claim",
|
||||
"group": "3a163ca0-4762-4620-a188-06b750c84c86",
|
||||
"joinChar": ";",
|
||||
"values": "read write"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,6 +144,17 @@ impl ValueSetT for ValueSetRestricted {
|
|||
Box::new(self.set.iter().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().cloned();
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::RestrictedString(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -196,3 +207,15 @@ impl ValueSetT for ValueSetRestricted {
|
|||
Some(Box::new(self.set.iter().map(|s| s.as_str())))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetRestricted;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_restricted() {
|
||||
let vs: ValueSet = ValueSetRestricted::new("Test".to_string());
|
||||
crate::valueset::scim_json_reflexive(vs, r#""Test""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,10 @@ impl ValueSetT for ValueSetSecret {
|
|||
Box::new(self.set.iter().map(|_| "hidden".to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::SecretValue(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -150,3 +154,16 @@ impl ValueSetT for ValueSetSecret {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetSecret;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_secret() {
|
||||
let vs: ValueSet = ValueSetSecret::new("super secret special awesome value".to_string());
|
||||
|
||||
assert!(vs.to_scim_value().is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ use crate::value::{
|
|||
};
|
||||
use crate::valueset::{uuid_to_proto_string, DbValueSetV2, ValueSet};
|
||||
|
||||
use kanidm_proto::scim_v1::server::ScimApiToken;
|
||||
use kanidm_proto::scim_v1::server::ScimAuthSession;
|
||||
use kanidm_proto::scim_v1::server::ScimOAuth2Session;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueSetSession {
|
||||
map: BTreeMap<Uuid, Session>,
|
||||
|
@ -358,6 +362,36 @@ impl ValueSetT for ValueSetSession {
|
|||
)
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(session_id, session)| {
|
||||
let (expires, revoked) = match &session.state {
|
||||
SessionState::ExpiresAt(odt) => (Some(*odt), None),
|
||||
SessionState::NeverExpires => (None, None),
|
||||
SessionState::RevokedAt(cid) => {
|
||||
let odt: OffsetDateTime = cid.into();
|
||||
(None, Some(odt))
|
||||
}
|
||||
};
|
||||
|
||||
ScimAuthSession {
|
||||
id: *session_id,
|
||||
expires,
|
||||
revoked,
|
||||
|
||||
issued_at: session.issued_at,
|
||||
issued_by: Uuid::from(&session.issued_by),
|
||||
credential_id: session.cred_id,
|
||||
auth_type: session.type_.to_string(),
|
||||
session_scope: session.scope.to_string(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Session(self.to_vec_dbvs())
|
||||
}
|
||||
|
@ -898,6 +932,33 @@ impl ValueSetT for ValueSetOauth2Session {
|
|||
)
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(session_id, session)| {
|
||||
let (expires, revoked) = match &session.state {
|
||||
SessionState::ExpiresAt(odt) => (Some(*odt), None),
|
||||
SessionState::NeverExpires => (None, None),
|
||||
SessionState::RevokedAt(cid) => {
|
||||
let odt: OffsetDateTime = cid.into();
|
||||
(None, Some(odt))
|
||||
}
|
||||
};
|
||||
|
||||
ScimOAuth2Session {
|
||||
id: *session_id,
|
||||
parent_id: session.parent,
|
||||
client_id: session.rs_uuid,
|
||||
issued_at: session.issued_at,
|
||||
expires,
|
||||
revoked,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Oauth2Session(
|
||||
self.map
|
||||
|
@ -1320,6 +1381,22 @@ impl ValueSetT for ValueSetApiToken {
|
|||
)
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(token_id, token)| ScimApiToken {
|
||||
id: *token_id,
|
||||
label: token.label.clone(),
|
||||
issued_by: Uuid::from(&token.issued_by),
|
||||
issued_at: token.issued_at,
|
||||
expires: token.expiry,
|
||||
scope: token.scope.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::ApiToken(
|
||||
self.map
|
||||
|
@ -1431,10 +1508,10 @@ impl ValueSetT for ValueSetApiToken {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ValueSetOauth2Session, ValueSetSession, SESSION_MAXIMUM};
|
||||
use crate::prelude::ValueSet;
|
||||
use crate::prelude::{IdentityId, SessionScope, Uuid};
|
||||
use crate::repl::cid::Cid;
|
||||
use crate::value::{AuthType, Oauth2Session, Session, SessionState};
|
||||
use crate::valueset::ValueSet;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[test]
|
||||
|
@ -2038,4 +2115,64 @@ mod tests {
|
|||
assert!(!sessions.contains_key(&one_uuid));
|
||||
assert!(sessions.contains_key(&two_uuid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_session() {
|
||||
let s_uuid = uuid::uuid!("3a163ca0-4762-4620-a188-06b750c84c86");
|
||||
|
||||
let vs: ValueSet = ValueSetSession::new(
|
||||
s_uuid,
|
||||
Session {
|
||||
label: "hacks".to_string(),
|
||||
state: SessionState::NeverExpires,
|
||||
issued_at: OffsetDateTime::UNIX_EPOCH,
|
||||
issued_by: IdentityId::Internal,
|
||||
cred_id: s_uuid,
|
||||
scope: SessionScope::ReadOnly,
|
||||
type_: AuthType::Passkey,
|
||||
},
|
||||
);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"authType": "passkey",
|
||||
"credentialId": "3a163ca0-4762-4620-a188-06b750c84c86",
|
||||
"issuedAt": "1970-01-01T00:00:00Z",
|
||||
"issuedBy": "00000000-0000-0000-0000-ffffff000000",
|
||||
"id": "3a163ca0-4762-4620-a188-06b750c84c86",
|
||||
"sessionScope": "read_only"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_oauth2_session() {
|
||||
let s_uuid = uuid::uuid!("3a163ca0-4762-4620-a188-06b750c84c86");
|
||||
|
||||
let vs: ValueSet = ValueSetOauth2Session::new(
|
||||
s_uuid,
|
||||
Oauth2Session {
|
||||
state: SessionState::NeverExpires,
|
||||
issued_at: OffsetDateTime::UNIX_EPOCH,
|
||||
parent: Some(s_uuid),
|
||||
rs_uuid: s_uuid,
|
||||
},
|
||||
);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"clientId": "3a163ca0-4762-4620-a188-06b750c84c86",
|
||||
"issuedAt": "1970-01-01T00:00:00Z",
|
||||
"parentId": "3a163ca0-4762-4620-a188-06b750c84c86",
|
||||
"id": "3a163ca0-4762-4620-a188-06b750c84c86"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,17 @@ impl ValueSetT for ValueSetSpn {
|
|||
Box::new(self.set.iter().map(|(n, d)| format!("{n}@{d}")))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().map(|(n, d)| format!("{n}@{d}"));
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Spn(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -180,3 +191,15 @@ impl ValueSetT for ValueSetSpn {
|
|||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetSpn;
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_spn() {
|
||||
let vs: ValueSet = ValueSetSpn::new(("claire".to_string(), "example.com".to_string()));
|
||||
crate::valueset::scim_json_reflexive(vs, r#""claire@example.com""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ use crate::valueset::{DbValueSetV2, ValueSet};
|
|||
|
||||
use sshkey_attest::proto::PublicKey as SshPublicKey;
|
||||
|
||||
use kanidm_proto::scim_v1::server::ScimSshPublicKey;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueSetSshKey {
|
||||
map: BTreeMap<String, SshPublicKey>,
|
||||
|
@ -152,6 +154,18 @@ impl ValueSetT for ValueSetSshKey {
|
|||
Box::new(self.map.iter().map(|(tag, pk)| format!("{}: {}", tag, pk)))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(label, pk)| ScimSshPublicKey {
|
||||
label: label.clone(),
|
||||
value: pk.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::SshKey(
|
||||
self.map
|
||||
|
@ -216,3 +230,32 @@ impl ValueSetT for ValueSetSshKey {
|
|||
Some(Box::new(self.map.values().map(|pk| pk.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{SshPublicKey, ValueSetSshKey};
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_ssh_public_key() {
|
||||
let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
|
||||
"tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
|
||||
"zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
|
||||
"methyst");
|
||||
|
||||
let vs: ValueSet = ValueSetSshKey::new(
|
||||
"label".to_string(),
|
||||
SshPublicKey::from_string(ecdsa).unwrap(),
|
||||
);
|
||||
|
||||
let data = r#"
|
||||
[
|
||||
{
|
||||
"label": "label",
|
||||
"value": "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3BtOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81lzPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@amethyst"
|
||||
}
|
||||
]
|
||||
"#;
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,12 @@ impl ValueSetT for ValueSetSyntax {
|
|||
Box::new(self.set.iter().map(|b| b.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set.iter().map(|u| u.to_string()).collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::SyntaxType(self.set.iter().map(|s| *s as u16).collect())
|
||||
}
|
||||
|
@ -160,3 +166,15 @@ impl ValueSetT for ValueSetSyntax {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetSyntax;
|
||||
use crate::prelude::{SyntaxType, ValueSet};
|
||||
|
||||
#[test]
|
||||
fn test_scim_syntax() {
|
||||
let vs: ValueSet = ValueSetSyntax::new(SyntaxType::Uuid);
|
||||
crate::valueset::scim_json_reflexive(vs, r#"["UUID"]"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,10 @@ impl ValueSetT for ValueSetTotpSecret {
|
|||
Box::new(self.map.keys().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::TotpSecret(
|
||||
self.map
|
||||
|
|
|
@ -101,6 +101,12 @@ impl ValueSetT for ValueSetUiHint {
|
|||
Box::new(self.set.iter().map(|u| u.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(ScimValueKanidm::from(
|
||||
self.set.iter().map(|u| u.to_string()).collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::UiHint(self.set.iter().map(|u| *u as u16).collect())
|
||||
}
|
||||
|
@ -145,3 +151,15 @@ impl ValueSetT for ValueSetUiHint {
|
|||
Some(Box::new(self.set.iter().copied()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{UiHint, ValueSetUiHint};
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_uihint() {
|
||||
let vs: ValueSet = ValueSetUiHint::new(UiHint::PosixAccount);
|
||||
crate::valueset::scim_json_reflexive(vs, r#"["PosixAccount"]"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,17 @@ impl ValueSetT for ValueSetUint32 {
|
|||
Box::new(self.set.iter().map(|b| b.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
if self.len() == 1 {
|
||||
// Because self.len == 1 we know this has to yield a value.
|
||||
let b = self.set.iter().copied().next().unwrap_or_default();
|
||||
Some(b.into())
|
||||
} else {
|
||||
// Nothing is MV for this today
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Uint32(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -175,4 +186,10 @@ mod tests {
|
|||
assert_eq!(vs.insert_checked(Value::new_uint32(1)), Ok(true));
|
||||
assert_eq!(vs.insert_checked(Value::new_uint32(1)), Ok(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_uint32() {
|
||||
let vs: ValueSet = ValueSetUint32::new(69);
|
||||
crate::valueset::scim_json_reflexive(vs, "69");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,17 @@ impl ValueSetT for ValueSetUrl {
|
|||
Box::new(self.set.iter().map(|i| i.to_string()))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().map(|url| url.to_string());
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Url(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -156,3 +167,16 @@ impl ValueSetT for ValueSetUrl {
|
|||
Some(&self.set)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetUrl;
|
||||
use crate::prelude::{Url, ValueSet};
|
||||
|
||||
#[test]
|
||||
fn test_scim_url() {
|
||||
let u = Url::parse("https://idm.example.com").unwrap();
|
||||
let vs: ValueSet = ValueSetUrl::new(u);
|
||||
crate::valueset::scim_json_reflexive(vs, r#""https://idm.example.com/""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,17 @@ impl ValueSetT for ValueSetUtf8 {
|
|||
Box::new(self.set.iter().cloned())
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
let mut iter = self.set.iter().cloned();
|
||||
if self.len() == 1 {
|
||||
let v = iter.next().unwrap_or_default();
|
||||
Some(v.into())
|
||||
} else {
|
||||
let arr = iter.collect::<Vec<_>>();
|
||||
Some(arr.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Utf8(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -202,8 +213,7 @@ impl ValueSetT for ValueSetUtf8 {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ValueSetUtf8;
|
||||
use crate::prelude::PartialValue;
|
||||
use crate::valueset::ValueSetT;
|
||||
use crate::prelude::{PartialValue, ValueSet, ValueSetT};
|
||||
|
||||
#[test]
|
||||
fn test_utf8_substring_insensitive() {
|
||||
|
@ -225,4 +235,10 @@ mod tests {
|
|||
assert!(!vs.endswith(&pv_test));
|
||||
assert!(vs.endswith(&pv_user));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_utf8() {
|
||||
let vs: ValueSet = ValueSetUtf8::new("Test".to_string());
|
||||
crate::valueset::scim_json_reflexive(vs, r#""Test""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,6 +119,10 @@ impl ValueSetT for ValueSetUuid {
|
|||
Box::new(self.set.iter().copied().map(uuid_to_proto_string))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
self.set.iter().next().copied().map(ScimValueKanidm::Uuid)
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Uuid(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -290,6 +294,10 @@ impl ValueSetT for ValueSetRefer {
|
|||
Box::new(self.set.iter().copied().map(uuid_to_proto_string))
|
||||
}
|
||||
|
||||
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
|
||||
Some(self.set.iter().copied().collect::<Vec<_>>().into())
|
||||
}
|
||||
|
||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||
DbValueSetV2::Reference(self.set.iter().cloned().collect())
|
||||
}
|
||||
|
@ -346,3 +354,27 @@ impl ValueSetT for ValueSetRefer {
|
|||
Some(Box::new(self.set.iter().copied()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ValueSetRefer, ValueSetUuid};
|
||||
use crate::prelude::ValueSet;
|
||||
|
||||
#[test]
|
||||
fn test_scim_uuid() {
|
||||
let vs: ValueSet = ValueSetUuid::new(uuid::uuid!("4d21d04a-dc0e-42eb-b850-34dd180b107f"));
|
||||
|
||||
let data = r#""4d21d04a-dc0e-42eb-b850-34dd180b107f""#;
|
||||
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scim_refer() {
|
||||
let vs: ValueSet = ValueSetRefer::new(uuid::uuid!("4d21d04a-dc0e-42eb-b850-34dd180b107f"));
|
||||
|
||||
let data = r#"["4d21d04a-dc0e-42eb-b850-34dd180b107f"]"#;
|
||||
|
||||
crate::valueset::scim_json_reflexive(vs, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,8 +52,8 @@ use uuid::Uuid;
|
|||
|
||||
use kanidm_client::KanidmClientBuilder;
|
||||
use kanidm_proto::scim_v1::{
|
||||
MultiValueAttr, ScimEntryGeneric, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson,
|
||||
ScimSyncRequest, ScimSyncRetentionMode, ScimSyncState, ScimTotp,
|
||||
MultiValueAttr, ScimEntry, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
|
||||
ScimSyncRetentionMode, ScimSyncState, ScimTotp,
|
||||
};
|
||||
|
||||
use kanidm_lib_file_permissions::readonly as file_permissions_readonly;
|
||||
|
@ -524,7 +524,7 @@ async fn process_ipa_sync_result(
|
|||
entry_config_map: &BTreeMap<Uuid, EntryConfig>,
|
||||
is_initialise: bool,
|
||||
sync_password_as_unix_password: bool,
|
||||
) -> Result<Vec<ScimEntryGeneric>, ()> {
|
||||
) -> Result<Vec<ScimEntry>, ()> {
|
||||
// Because of how TOTP works with freeipa it's a soft referral from
|
||||
// the totp toward the user. This means if a TOTP is added or removed
|
||||
// we see those as unique entries in the syncrepl but we are missing
|
||||
|
@ -775,7 +775,7 @@ fn ipa_to_scim_entry(
|
|||
entry_config: &EntryConfig,
|
||||
totp: &[LdapSyncReplEntry],
|
||||
sync_password_as_unix_password: bool,
|
||||
) -> Result<Option<ScimEntryGeneric>, ()> {
|
||||
) -> Result<Option<ScimEntry>, ()> {
|
||||
debug!("{:#?}", sync_entry);
|
||||
|
||||
// check the sync_entry state?
|
||||
|
@ -941,10 +941,9 @@ fn ipa_to_scim_entry(
|
|||
.set_external_id(external_id)
|
||||
.build();
|
||||
|
||||
let scim_entry_generic: ScimEntryGeneric =
|
||||
scim_sync_person.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
let scim_entry_generic: ScimEntry = scim_sync_person.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
|
||||
Ok(Some(scim_entry_generic))
|
||||
} else if oc.contains(LDAP_CLASS_GROUPOFNAMES) {
|
||||
|
@ -993,10 +992,9 @@ fn ipa_to_scim_entry(
|
|||
.set_external_id(external_id)
|
||||
.build();
|
||||
|
||||
let scim_entry_generic: ScimEntryGeneric =
|
||||
scim_sync_group.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
let scim_entry_generic: ScimEntry = scim_sync_group.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
|
||||
Ok(Some(scim_entry_generic))
|
||||
} else if oc.contains("ipatokentotp") {
|
||||
|
|
|
@ -46,8 +46,8 @@ use tracing_subscriber::{fmt, EnvFilter};
|
|||
use kanidm_client::KanidmClientBuilder;
|
||||
use kanidm_lib_file_permissions::readonly as file_permissions_readonly;
|
||||
use kanidm_proto::scim_v1::{
|
||||
MultiValueAttr, ScimEntryGeneric, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson,
|
||||
ScimSyncRequest, ScimSyncRetentionMode, ScimSyncState,
|
||||
MultiValueAttr, ScimEntry, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
|
||||
ScimSyncRetentionMode, ScimSyncState,
|
||||
};
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
|
@ -447,7 +447,7 @@ async fn run_sync(
|
|||
async fn process_ldap_sync_result(
|
||||
ldap_entries: Vec<LdapSyncReplEntry>,
|
||||
sync_config: &Config,
|
||||
) -> Result<Vec<ScimEntryGeneric>, ()> {
|
||||
) -> Result<Vec<ScimEntry>, ()> {
|
||||
// Future - make this par-map
|
||||
ldap_entries
|
||||
.into_iter()
|
||||
|
@ -471,7 +471,7 @@ fn ldap_to_scim_entry(
|
|||
sync_entry: LdapSyncReplEntry,
|
||||
entry_config: &EntryConfig,
|
||||
sync_config: &Config,
|
||||
) -> Result<Option<ScimEntryGeneric>, ()> {
|
||||
) -> Result<Option<ScimEntry>, ()> {
|
||||
debug!("{:#?}", sync_entry);
|
||||
|
||||
// check the sync_entry state?
|
||||
|
@ -632,10 +632,9 @@ fn ldap_to_scim_entry(
|
|||
.set_external_id(external_id)
|
||||
.build();
|
||||
|
||||
let scim_entry_generic: ScimEntryGeneric =
|
||||
scim_sync_person.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
let scim_entry_generic: ScimEntry = scim_sync_person.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
|
||||
Ok(Some(scim_entry_generic))
|
||||
} else if oc.contains(&sync_config.group_objectclass) {
|
||||
|
@ -688,10 +687,9 @@ fn ldap_to_scim_entry(
|
|||
.set_external_id(external_id)
|
||||
.build();
|
||||
|
||||
let scim_entry_generic: ScimEntryGeneric =
|
||||
scim_sync_group.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
let scim_entry_generic: ScimEntry = scim_sync_group.try_into().map_err(|json_err| {
|
||||
error!(?json_err, "Unable to convert group to scim_sync_group");
|
||||
})?;
|
||||
|
||||
Ok(Some(scim_entry_generic))
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue