Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

required changes for famedly.de #82

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ pub enum FeatureFlag {
DryRun,
/// If only deactivated users should be synced
DeactivateOnly,
/// Use plain localpart
PlainLocalpart,
}

#[derive(Debug, Clone, Deserialize, PartialEq, Default)]
Expand Down Expand Up @@ -356,7 +358,10 @@ mod tests {
let file_path = create_config_file(tempdir.path());

let env_var_name = format!("{ENV_VAR_CONFIG_PREFIX}__FEATURE_FLAGS");
env::set_var(&env_var_name, "sso_login verify_email verify_phone dry_run deactivate_only");
env::set_var(
&env_var_name,
"sso_login verify_email verify_phone dry_run deactivate_only plain_localpart",
);

let loaded_config =
Config::new(file_path.as_path()).expect("Failed to create config object");
Expand All @@ -367,6 +372,7 @@ mod tests {
sample_config.feature_flags.push(FeatureFlag::VerifyPhone);
sample_config.feature_flags.push(FeatureFlag::DryRun);
sample_config.feature_flags.push(FeatureFlag::DeactivateOnly);
sample_config.feature_flags.push(FeatureFlag::PlainLocalpart);

env::remove_var(env_var_name);

Expand Down
63 changes: 50 additions & 13 deletions src/sources/ldap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,32 @@

/// Construct a user from an LDAP SearchEntry
pub(crate) fn parse_user(&self, entry: SearchEntry) -> Result<User> {
let status_as_int = match read_search_entry(&entry, &self.ldap_config.attributes.status)? {
StringOrBytes::String(status) => status.parse::<i32>()?,
StringOrBytes::Bytes(status) => {
i32::from_be_bytes(status.try_into().map_err(|err: Vec<u8>| {
let err_string = String::from_utf8_lossy(&err).to_string();
anyhow!(err_string).context("failed to convert to i32 flag")
})?)
let disable_bitmask = {
use std::ops::BitOr;
self.ldap_config.attributes.disable_bitmasks.iter().fold(0, i32::bitor)
};

let status = read_search_entry(&entry, &self.ldap_config.attributes.status)?;
let enabled = if disable_bitmask != 0 {
disable_bitmask
& match status {
StringOrBytes::String(status) => status.parse::<i32>()?,
StringOrBytes::Bytes(status) => {
i32::from_be_bytes(status.try_into().map_err(|err: Vec<u8>| {
let err_string = String::from_utf8_lossy(&err).to_string();
anyhow!(err_string).context("failed to convert to i32 flag")
})?)

Check warning on line 127 in src/sources/ldap.rs

View check run for this annotation

Codecov / codecov/patch

src/sources/ldap.rs#L123-L127

Added lines #L123 - L127 were not covered by tests
}
} == 0
} else if let StringOrBytes::String(status) = status {
match &status[..] {
"TRUE" => true,
"FALSE" => false,
_ => bail!("Cannot parse status without disable_bitmasks: {:?}", status),

Check warning on line 134 in src/sources/ldap.rs

View check run for this annotation

Codecov / codecov/patch

src/sources/ldap.rs#L134

Added line #L134 was not covered by tests
tlater-famedly marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
bail!("Binary status without disable_bitmasks");

Check warning on line 137 in src/sources/ldap.rs

View check run for this annotation

Codecov / codecov/patch

src/sources/ldap.rs#L137

Added line #L137 was not covered by tests
};
let enabled = !self
.ldap_config
.attributes
.disable_bitmasks
.iter()
.any(|flag| status_as_int & flag != 0);

let first_name = read_search_entry(&entry, &self.ldap_config.attributes.first_name)?;
let last_name = read_search_entry(&entry, &self.ldap_config.attributes.last_name)?;
Expand Down Expand Up @@ -571,4 +582,30 @@
assert_eq!(user.external_user_id, StringOrBytes::String("testuser".to_owned()));
assert!(user.enabled);
}

#[tokio::test]
async fn test_text_enabled() {
let mut config = load_config();
config.sources.ldap.as_mut().unwrap().attributes.disable_bitmasks =
serde_yaml::from_str("[0]").expect("invalid config fragment");
let ldap_source =
LdapSource { ldap_config: config.sources.ldap.unwrap(), is_dry_run: false };

for (attr, parsed) in [("TRUE", true), ("FALSE", false)] {
let entry = SearchEntry {
dn: "uid=testuser,ou=testorg,dc=example,dc=org".to_owned(),
attrs: {
let mut user = new_user();
user.insert("shadowFlag".to_owned(), vec![attr.to_owned()]);
user
},
bin_attrs: HashMap::new(),
};

let result = ldap_source.parse_user(entry);
assert!(result.is_ok(), "Failed to parse user: {:?}", result);
let user = result.unwrap();
assert_eq!(user.enabled, parsed);
}
}
}
28 changes: 22 additions & 6 deletions src/zitadel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,27 @@
return Ok(());
}

let id = match &user.user_data.external_user_id {
StringOrBytes::String(value) => value.as_bytes(),
StringOrBytes::Bytes(value) => value,

Check warning on line 413 in src/zitadel.rs

View check run for this annotation

Codecov / codecov/patch

src/zitadel.rs#L413

Added line #L413 was not covered by tests
};

let uuid;
let localpart = if self.feature_flags.contains(&FeatureFlag::PlainLocalpart) {
match &user.user_data.external_user_id {
StringOrBytes::String(value) => value,

Check warning on line 419 in src/zitadel.rs

View check run for this annotation

Codecov / codecov/patch

src/zitadel.rs#L418-L419

Added lines #L418 - L419 were not covered by tests
StringOrBytes::Bytes(_) => {
bail!(
"Unsupported binary external ID for user using plain localparts: {:?}",
user
);

Check warning on line 424 in src/zitadel.rs

View check run for this annotation

Codecov / codecov/patch

src/zitadel.rs#L421-L424

Added lines #L421 - L424 were not covered by tests
}
}
} else {
uuid = Uuid::new_v5(&FAMEDLY_NAMESPACE, id).to_string();
&uuid
};

let new_user_id = self
.zitadel_client
.create_human_user(&self.zitadel_config.organization_id, user.clone().into())
Expand All @@ -422,17 +443,12 @@
)
.await?;

let id = match &user.user_data.external_user_id {
StringOrBytes::String(value) => value.as_bytes(),
StringOrBytes::Bytes(value) => value,
};

self.zitadel_client
.set_user_metadata(
Some(&self.zitadel_config.organization_id),
new_user_id.clone(),
"localpart".to_owned(),
&Uuid::new_v5(&FAMEDLY_NAMESPACE, id).to_string(),
localpart,
)
.await?;

Expand Down
Loading