Resolve incorrect SCIM Sync serialisation (#3047)

This commit is contained in:
Firstyear 2024-09-17 16:27:41 +10:00 committed by GitHub
parent 004e263f90
commit fb3e7a01bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 80 additions and 4 deletions

1
Cargo.lock generated
View file

@ -5375,6 +5375,7 @@ dependencies = [
"peg", "peg",
"serde", "serde",
"serde_json", "serde_json",
"serde_with",
"time", "time",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",

View file

@ -19,6 +19,7 @@ doctest = false
base64urlsafedata = { workspace = true } base64urlsafedata = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
serde_with = { workspace = true }
peg = { workspace = true } peg = { workspace = true }
time = { workspace = true, features = [ time = { workspace = true, features = [
"local-offset", "local-offset",

View file

@ -5,7 +5,9 @@ use url::Url;
use uuid::Uuid; use uuid::Uuid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Name { pub struct Name {
@ -73,7 +75,8 @@ impl fmt::Display for Timezone {
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MultiValueAttr { pub struct MultiValueAttr {
#[serde(rename = "type")] #[serde(rename = "type")]
@ -85,6 +88,7 @@ pub struct MultiValueAttr {
pub value: String, pub value: String,
} }
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Photo { pub struct Photo {
@ -97,6 +101,7 @@ pub struct Photo {
value: Url, value: Url,
} }
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Binary { pub struct Binary {
#[serde(rename = "type")] #[serde(rename = "type")]
@ -108,6 +113,7 @@ pub struct Binary {
value: Base64UrlSafeData, value: Base64UrlSafeData,
} }
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Address { pub struct Address {
@ -130,6 +136,7 @@ enum Membership {
} }
*/ */
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Group { pub struct Group {
@ -141,6 +148,7 @@ pub struct Group {
display: String, display: String,
} }
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct User { pub struct User {
@ -163,17 +171,23 @@ pub struct User {
timezone: Option<Timezone>, timezone: Option<Timezone>,
active: bool, active: bool,
password: Option<String>, password: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
emails: Vec<MultiValueAttr>, emails: Vec<MultiValueAttr>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
phone_numbers: Vec<MultiValueAttr>, phone_numbers: Vec<MultiValueAttr>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
ims: Vec<MultiValueAttr>, ims: Vec<MultiValueAttr>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
photos: Vec<Photo>, photos: Vec<Photo>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
addresses: Vec<Address>, addresses: Vec<Address>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
groups: Vec<Group>, groups: Vec<Group>,
#[serde(default)] #[serde(default, skip_serializing_if = "Vec::is_empty")]
entitlements: Vec<MultiValueAttr>, entitlements: Vec<MultiValueAttr>,
#[serde(default)] #[serde(default, skip_serializing_if = "Vec::is_empty")]
roles: Vec<MultiValueAttr>, roles: Vec<MultiValueAttr>,
#[serde(default)] #[serde(default, skip_serializing_if = "Vec::is_empty")]
x509certificates: Vec<Binary>, x509certificates: Vec<Binary>,
} }

View file

@ -59,4 +59,59 @@ mod tests {
fn test_scim_kani_to_rfc() { fn test_scim_kani_to_rfc() {
// Assert that a kanidm strong entry can convert to rfc. // Assert that a kanidm strong entry can convert to rfc.
} }
#[test]
fn test_scim_sync_kani_to_rfc() {
use super::*;
// Group
let group_uuid = uuid::uuid!("2d0a9e7c-cc08-4ca2-8d7f-114f9abcfc8a");
let group = ScimSyncGroup::builder("testgroup".to_string(), group_uuid)
.set_description(Some("test desc".to_string()))
.set_gidnumber(Some(12345))
.set_members(vec!["member_a".to_string(), "member_a".to_string()].into_iter())
.set_external_id(Some("cn=testgroup".to_string()))
.build();
let entry: Result<ScimEntry, _> = group.try_into();
assert!(entry.is_ok());
// User
let user_uuid = uuid::uuid!("cb3de098-33fd-4565-9d80-4f7ed6a664e9");
let user_sshkey = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== testuser@fidokey";
let person =
ScimSyncPerson::builder(user_uuid, "testuser".to_string(), "Test User".to_string())
.set_password_import(Some("new_password".to_string()))
.set_unix_password_import(Some("new_password".to_string()))
.set_totp_import(vec![ScimTotp {
external_id: "Totp".to_string(),
secret: "abcd".to_string(),
algo: "SHA3".to_string(),
step: 60,
digits: 8,
}])
.set_mail(vec![MultiValueAttr {
primary: Some(true),
value: "testuser@example.com".to_string(),
..Default::default()
}])
.set_ssh_publickey(vec![ScimSshPubKey {
label: "Key McKeyface".to_string(),
value: user_sshkey.to_string(),
}])
.set_login_shell(Some("/bin/false".to_string()))
.set_account_valid_from(Some("2023-11-28T04:57:55Z".to_string()))
.set_account_expire(Some("2023-11-28T04:57:55Z".to_string()))
.set_gidnumber(Some(54321))
.set_external_id(Some("cn=testuser".to_string()))
.build();
let entry: Result<ScimEntry, _> = person.try_into();
assert!(entry.is_ok());
}
} }

View file

@ -5,6 +5,7 @@ use uuid::Uuid;
use scim_proto::user::MultiValueAttr; use scim_proto::user::MultiValueAttr;
use scim_proto::{ScimEntry, ScimEntryHeader}; use scim_proto::{ScimEntry, ScimEntryHeader};
use serde_with::skip_serializing_none;
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
@ -82,6 +83,7 @@ pub struct ScimSshPubKey {
pub value: String, pub value: String,
} }
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ScimSyncPerson { pub struct ScimSyncPerson {
@ -93,9 +95,12 @@ pub struct ScimSyncPerson {
pub gidnumber: Option<u32>, pub gidnumber: Option<u32>,
pub password_import: Option<String>, pub password_import: Option<String>,
pub unix_password_import: Option<String>, pub unix_password_import: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub totp_import: Vec<ScimTotp>, pub totp_import: Vec<ScimTotp>,
pub login_shell: Option<String>, pub login_shell: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mail: Vec<MultiValueAttr>, pub mail: Vec<MultiValueAttr>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ssh_publickey: Vec<ScimSshPubKey>, pub ssh_publickey: Vec<ScimSshPubKey>,
pub account_valid_from: Option<String>, pub account_valid_from: Option<String>,
pub account_expire: Option<String>, pub account_expire: Option<String>,