mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Chasing yaks down dark alleyways (#2207)
* adding some test coverage because there was some rando panic-inducing thing * ldap constants * documenting a macro * helpful weird errors * the war on strings continues * less json more better * testing things fixing bugs * idm_domain_reset_token_key wasn't working, added a test and fixed it (we weren't testing it) * idm_domain_set_ldap_basedn - adding tests * adding testing for idm_account_credential_update_cancel_mfareg * warning of deprecation
This commit is contained in:
parent
c66a401b31
commit
d9da1eeca0
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2872,6 +2872,7 @@ dependencies = [
|
||||||
"ldap3_client",
|
"ldap3_client",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sketching",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
11
examples/iam_migration_ldap.toml
Normal file
11
examples/iam_migration_ldap.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
debug = false
|
||||||
|
dry_run = true
|
||||||
|
schedule = "* * * * *"
|
||||||
|
skip_root_check = false
|
||||||
|
sync_token="cheesemonkey"
|
||||||
|
ldap_uri="ldaps://example.com:636"
|
||||||
|
ldap_ca=""
|
||||||
|
ldap_sync_dn=""
|
||||||
|
ldap_sync_pw=""
|
||||||
|
ldap_sync_base_dn=""
|
||||||
|
ldap_filter="(objectClass=posixAccount)"
|
|
@ -32,6 +32,7 @@ pub use reqwest::StatusCode;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::error::Error as SerdeJsonError;
|
use serde_json::error::Error as SerdeJsonError;
|
||||||
|
use serde_json::json;
|
||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::{Mutex, RwLock};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -108,6 +109,32 @@ impl Display for KanidmClientBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_kanidmclientbuilder_display() {
|
||||||
|
let foo = KanidmClientBuilder::default();
|
||||||
|
println!("{}", foo.to_string());
|
||||||
|
assert!(foo.to_string().contains("verify_ca"));
|
||||||
|
|
||||||
|
let foo = KanidmClientBuilder {
|
||||||
|
address: Some("https://example.com".to_string()),
|
||||||
|
verify_ca: true,
|
||||||
|
verify_hostnames: true,
|
||||||
|
ca: None,
|
||||||
|
connect_timeout: Some(420),
|
||||||
|
use_system_proxies: true,
|
||||||
|
};
|
||||||
|
println!("foo {}", foo.to_string());
|
||||||
|
assert!(foo.to_string().contains("verify_ca: true"));
|
||||||
|
assert!(foo.to_string().contains("verify_hostnames: true"));
|
||||||
|
|
||||||
|
let badness = foo.danger_accept_invalid_hostnames(true);
|
||||||
|
let badness = badness.danger_accept_invalid_certs(true);
|
||||||
|
println!("badness: {}", badness.to_string());
|
||||||
|
assert!(badness.to_string().contains("verify_ca: false"));
|
||||||
|
assert!(badness.to_string().contains("verify_hostnames: false"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct KanidmClient {
|
pub struct KanidmClient {
|
||||||
pub(crate) client: reqwest::Client,
|
pub(crate) client: reqwest::Client,
|
||||||
|
@ -890,7 +917,8 @@ impl KanidmClient {
|
||||||
let response = self
|
let response = self
|
||||||
.client
|
.client
|
||||||
.delete(self.make_url(dest))
|
.delete(self.make_url(dest))
|
||||||
.header(CONTENT_TYPE, APPLICATION_JSON);
|
// empty-ish body that makes the parser happy
|
||||||
|
.json(&json!([]));
|
||||||
|
|
||||||
let response = {
|
let response = {
|
||||||
let tguard = self.bearer_token.read().await;
|
let tguard = self.bearer_token.read().await;
|
||||||
|
@ -932,12 +960,10 @@ impl KanidmClient {
|
||||||
dest: &str,
|
dest: &str,
|
||||||
request: R,
|
request: R,
|
||||||
) -> Result<(), ClientError> {
|
) -> Result<(), ClientError> {
|
||||||
let req_string = serde_json::to_string(&request).map_err(ClientError::JsonEncode)?;
|
|
||||||
let response = self
|
let response = self
|
||||||
.client
|
.client
|
||||||
.delete(self.make_url(dest))
|
.delete(self.make_url(dest))
|
||||||
.body(req_string)
|
.json(&request);
|
||||||
.header(CONTENT_TYPE, APPLICATION_JSON);
|
|
||||||
|
|
||||||
let response = {
|
let response = {
|
||||||
let tguard = self.bearer_token.read().await;
|
let tguard = self.bearer_token.read().await;
|
||||||
|
@ -1647,6 +1673,7 @@ impl KanidmClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add test coverage
|
||||||
pub async fn idm_account_credential_update_accept_sha1_totp(
|
pub async fn idm_account_credential_update_accept_sha1_totp(
|
||||||
&self,
|
&self,
|
||||||
session_token: &CUSessionToken,
|
session_token: &CUSessionToken,
|
||||||
|
@ -1666,6 +1693,7 @@ impl KanidmClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add test coverage
|
||||||
pub async fn idm_account_credential_update_backup_codes_generate(
|
pub async fn idm_account_credential_update_backup_codes_generate(
|
||||||
&self,
|
&self,
|
||||||
session_token: &CUSessionToken,
|
session_token: &CUSessionToken,
|
||||||
|
@ -1675,6 +1703,7 @@ impl KanidmClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add test coverage
|
||||||
pub async fn idm_account_credential_update_primary_remove(
|
pub async fn idm_account_credential_update_primary_remove(
|
||||||
&self,
|
&self,
|
||||||
session_token: &CUSessionToken,
|
session_token: &CUSessionToken,
|
||||||
|
@ -1704,6 +1733,7 @@ impl KanidmClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add test coverage
|
||||||
pub async fn idm_account_credential_update_passkey_remove(
|
pub async fn idm_account_credential_update_passkey_remove(
|
||||||
&self,
|
&self,
|
||||||
session_token: &CUSessionToken,
|
session_token: &CUSessionToken,
|
||||||
|
|
|
@ -2,10 +2,12 @@ use crate::{ClientError, KanidmClient};
|
||||||
use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState};
|
use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState};
|
||||||
|
|
||||||
impl KanidmClient {
|
impl KanidmClient {
|
||||||
|
// TODO: testing for this
|
||||||
pub async fn scim_v1_sync_status(&self) -> Result<ScimSyncState, ClientError> {
|
pub async fn scim_v1_sync_status(&self) -> Result<ScimSyncState, ClientError> {
|
||||||
self.perform_get_request("/scim/v1/Sync").await
|
self.perform_get_request("/scim/v1/Sync").await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: testing for this
|
||||||
pub async fn scim_v1_sync_update(
|
pub async fn scim_v1_sync_update(
|
||||||
&self,
|
&self,
|
||||||
scim_sync_request: &ScimSyncRequest,
|
scim_sync_request: &ScimSyncRequest,
|
||||||
|
|
|
@ -146,8 +146,11 @@ impl KanidmClient {
|
||||||
|
|
||||||
pub async fn idm_service_account_unix_extend(
|
pub async fn idm_service_account_unix_extend(
|
||||||
&self,
|
&self,
|
||||||
|
// The username or uuid of the account
|
||||||
id: &str,
|
id: &str,
|
||||||
|
// The GID number to set for the account
|
||||||
gidnumber: Option<u32>,
|
gidnumber: Option<u32>,
|
||||||
|
// Set a default login shell
|
||||||
shell: Option<&str>,
|
shell: Option<&str>,
|
||||||
) -> Result<(), ClientError> {
|
) -> Result<(), ClientError> {
|
||||||
let ux = AccountUnixExtend {
|
let ux = AccountUnixExtend {
|
||||||
|
@ -158,6 +161,7 @@ impl KanidmClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: test coverage for this, but there's a weird issue with ACPs on apply
|
||||||
pub async fn idm_service_account_into_person(&self, id: &str) -> Result<(), ClientError> {
|
pub async fn idm_service_account_into_person(&self, id: &str) -> Result<(), ClientError> {
|
||||||
self.perform_post_request(
|
self.perform_post_request(
|
||||||
format!("/v1/service_account/{}/_into_person", id).as_str(),
|
format!("/v1/service_account/{}/_into_person", id).as_str(),
|
||||||
|
|
|
@ -78,6 +78,7 @@ impl KanidmClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a sync token for a given sync account
|
||||||
pub async fn idm_sync_account_generate_token(
|
pub async fn idm_sync_account_generate_token(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
|
|
|
@ -168,6 +168,7 @@ pub const OAUTH2_SCOPE_READ: &str = "read";
|
||||||
pub const OAUTH2_SCOPE_SUPPLEMENT: &str = "supplement";
|
pub const OAUTH2_SCOPE_SUPPLEMENT: &str = "supplement";
|
||||||
|
|
||||||
pub const LDAP_ATTR_CN: &str = "cn";
|
pub const LDAP_ATTR_CN: &str = "cn";
|
||||||
|
pub const LDAP_ATTR_DISPLAY_NAME: &str = "displayName";
|
||||||
pub const LDAP_ATTR_EMAIL_ALTERNATIVE: &str = "emailalternative";
|
pub const LDAP_ATTR_EMAIL_ALTERNATIVE: &str = "emailalternative";
|
||||||
pub const LDAP_ATTR_EMAIL_PRIMARY: &str = "emailprimary";
|
pub const LDAP_ATTR_EMAIL_PRIMARY: &str = "emailprimary";
|
||||||
pub const LDAP_ATTR_ENTRYDN: &str = "entrydn";
|
pub const LDAP_ATTR_ENTRYDN: &str = "entrydn";
|
||||||
|
@ -181,6 +182,7 @@ pub const LDAP_ATTR_MEMBER: &str = "member";
|
||||||
pub const LDAP_ATTR_NAME: &str = "name";
|
pub const LDAP_ATTR_NAME: &str = "name";
|
||||||
pub const LDAP_ATTR_OBJECTCLASS: &str = "objectClass";
|
pub const LDAP_ATTR_OBJECTCLASS: &str = "objectClass";
|
||||||
pub const LDAP_ATTR_OU: &str = "ou";
|
pub const LDAP_ATTR_OU: &str = "ou";
|
||||||
|
pub const LDAP_CLASS_GROUPOFNAMES: &str = "groupofnames";
|
||||||
|
|
||||||
// rust can't deal with this being compiled out, don't try and #[cfg()] them
|
// rust can't deal with this being compiled out, don't try and #[cfg()] them
|
||||||
pub const TEST_ATTR_NON_EXIST: &str = "non-exist";
|
pub const TEST_ATTR_NON_EXIST: &str = "non-exist";
|
||||||
|
|
|
@ -273,6 +273,8 @@ pub enum OperationError {
|
||||||
ReplDomainLevelUnsatisfiable,
|
ReplDomainLevelUnsatisfiable,
|
||||||
ReplDomainUuidMismatch,
|
ReplDomainUuidMismatch,
|
||||||
TransactionAlreadyCommitted,
|
TransactionAlreadyCommitted,
|
||||||
|
/// when you ask for a gid that's lower than a safe minimum
|
||||||
|
GidOverlapsSystemMin(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for OperationError {
|
impl PartialEq for OperationError {
|
||||||
|
|
|
@ -896,6 +896,7 @@ pub async fn account_id_radius_token(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expects an `AccountUnixExtend` object
|
/// Expects an `AccountUnixExtend` object
|
||||||
|
///
|
||||||
#[instrument(name = "account_post_id_unix", level = "INFO", skip(id, state, kopid))]
|
#[instrument(name = "account_post_id_unix", level = "INFO", skip(id, state, kopid))]
|
||||||
pub async fn account_post_id_unix(
|
pub async fn account_post_id_unix(
|
||||||
State(state): State<ServerState>,
|
State(state): State<ServerState>,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant, SystemTime};
|
||||||
|
|
||||||
use criterion::{
|
use criterion::{
|
||||||
criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput,
|
criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput,
|
||||||
|
@ -7,9 +7,15 @@ use criterion::{
|
||||||
use kanidmd_lib::entry::{Entry, EntryInit, EntryNew};
|
use kanidmd_lib::entry::{Entry, EntryInit, EntryNew};
|
||||||
use kanidmd_lib::entry_init;
|
use kanidmd_lib::entry_init;
|
||||||
use kanidmd_lib::prelude::{Attribute, EntryClass};
|
use kanidmd_lib::prelude::{Attribute, EntryClass};
|
||||||
use kanidmd_lib::utils::duration_from_epoch_now;
|
|
||||||
use kanidmd_lib::value::Value;
|
use kanidmd_lib::value::Value;
|
||||||
|
|
||||||
|
pub fn duration_from_epoch_now() -> Duration {
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.expect("invalid duration from epoch now")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scaling_user_create_single(c: &mut Criterion) {
|
pub fn scaling_user_create_single(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("user_create_single");
|
let mut group = c.benchmark_group("user_create_single");
|
||||||
group.sample_size(10);
|
group.sample_size(10);
|
||||||
|
|
|
@ -621,6 +621,18 @@ impl From<EntryClass> for &'static str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for EntryClass {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&EntryClass> for &'static str {
|
||||||
|
fn from(value: &EntryClass) -> Self {
|
||||||
|
(*value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<EntryClass> for String {
|
impl From<EntryClass> for String {
|
||||||
fn from(val: EntryClass) -> Self {
|
fn from(val: EntryClass) -> Self {
|
||||||
let s: &'static str = val.into();
|
let s: &'static str = val.into();
|
||||||
|
|
|
@ -897,10 +897,10 @@ mod tests {
|
||||||
assert_entry_contains!(
|
assert_entry_contains!(
|
||||||
lsre,
|
lsre,
|
||||||
"spn=testperson1@example.com,dc=example,dc=com",
|
"spn=testperson1@example.com,dc=example,dc=com",
|
||||||
(Attribute::ObjectClass, "object"),
|
(Attribute::ObjectClass, EntryClass::Object.as_ref()),
|
||||||
(Attribute::ObjectClass, "person"),
|
(Attribute::ObjectClass, EntryClass::Person.as_ref()),
|
||||||
(Attribute::ObjectClass, "account"),
|
(Attribute::ObjectClass, EntryClass::Account.as_ref()),
|
||||||
(Attribute::ObjectClass, "posixaccount"),
|
(Attribute::ObjectClass, EntryClass::PosixAccount.as_ref()),
|
||||||
(Attribute::DisplayName, "testperson1"),
|
(Attribute::DisplayName, "testperson1"),
|
||||||
(Attribute::Name, "testperson1"),
|
(Attribute::Name, "testperson1"),
|
||||||
(Attribute::GidNumber, "12345678"),
|
(Attribute::GidNumber, "12345678"),
|
||||||
|
|
|
@ -119,16 +119,22 @@ macro_rules! run_create_test {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[macro_export]
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
/// Runs a test with preloaded entries, then modifies based on a filter/list, then runs a given check
|
||||||
macro_rules! run_modify_test {
|
macro_rules! run_modify_test {
|
||||||
(
|
(
|
||||||
|
// expected outcome
|
||||||
$expect:expr,
|
$expect:expr,
|
||||||
|
// things to preload
|
||||||
$preload_entries:ident,
|
$preload_entries:ident,
|
||||||
|
// the targets to modify
|
||||||
$modify_filter:expr,
|
$modify_filter:expr,
|
||||||
|
// changes to make
|
||||||
$modify_list:expr,
|
$modify_list:expr,
|
||||||
$internal:expr,
|
$internal:expr,
|
||||||
|
// something to run after the preload but before the modification, takes `&mut qs_write`
|
||||||
$pre_hook:expr,
|
$pre_hook:expr,
|
||||||
|
// the result we expect
|
||||||
$check:expr
|
$check:expr
|
||||||
) => {{
|
) => {{
|
||||||
use crate::be::{Backend, BackendConfig};
|
use crate::be::{Backend, BackendConfig};
|
||||||
|
|
|
@ -498,15 +498,12 @@ mod tests {
|
||||||
// Test entry in db, and same name, reject.
|
// Test entry in db, and same name, reject.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pre_create_name_unique() {
|
fn test_pre_create_name_unique() {
|
||||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
|
||||||
r#"{
|
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
"attrs": {
|
(Attribute::Class, EntryClass::Person.to_value()),
|
||||||
"class": ["person"],
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
"name": ["testperson"],
|
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||||
"description": ["testperson"],
|
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||||
"displayname": ["testperson"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let create = vec![e.clone()];
|
let create = vec![e.clone()];
|
||||||
|
@ -526,15 +523,11 @@ mod tests {
|
||||||
// Test two entries in create that would have same name, reject.
|
// Test two entries in create that would have same name, reject.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pre_create_name_unique_2() {
|
fn test_pre_create_name_unique_2() {
|
||||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Person.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
"class": ["person"],
|
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||||
"name": ["testperson"],
|
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||||
"description": ["testperson"],
|
|
||||||
"displayname": ["testperson"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let create = vec![e.clone(), e];
|
let create = vec![e.clone(), e];
|
||||||
|
@ -557,24 +550,15 @@ mod tests {
|
||||||
// A mod to something that exists, reject.
|
// A mod to something that exists, reject.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pre_modify_name_unique() {
|
fn test_pre_modify_name_unique() {
|
||||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let ea: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||||
"class": ["group"],
|
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||||
"name": ["testgroup_a"],
|
|
||||||
"description": ["testgroup"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
let eb: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
r#"{
|
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||||
"attrs": {
|
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||||
"class": ["group"],
|
|
||||||
"name": ["testgroup_b"],
|
|
||||||
"description": ["testgroup"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![ea, eb];
|
let preload = vec![ea, eb];
|
||||||
|
@ -601,24 +585,15 @@ mod tests {
|
||||||
// Two items modded to have the same value, reject.
|
// Two items modded to have the same value, reject.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pre_modify_name_unique_2() {
|
fn test_pre_modify_name_unique_2() {
|
||||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let ea: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||||
"class": ["group"],
|
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||||
"name": ["testgroup_a"],
|
|
||||||
"description": ["testgroup"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
let eb: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
r#"{
|
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||||
"attrs": {
|
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||||
"class": ["group"],
|
|
||||||
"name": ["testgroup_b"],
|
|
||||||
"description": ["testgroup"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![ea, eb];
|
let preload = vec![ea, eb];
|
||||||
|
|
|
@ -35,12 +35,8 @@ fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), Opera
|
||||||
let gid = uuid_to_gid_u32(u_ref);
|
let gid = uuid_to_gid_u32(u_ref);
|
||||||
// assert the value is greater than the system range.
|
// assert the value is greater than the system range.
|
||||||
if gid < GID_SYSTEM_NUMBER_MIN {
|
if gid < GID_SYSTEM_NUMBER_MIN {
|
||||||
return Err(OperationError::InvalidAttribute(format!(
|
admin_error!("Requested GID {} is lower than system minimum {}", gid, GID_SYSTEM_NUMBER_MIN);
|
||||||
"{} {} may overlap with system range {}",
|
return Err(OperationError::GidOverlapsSystemMin(GID_SYSTEM_NUMBER_MIN));
|
||||||
Attribute::GidNumber,
|
|
||||||
gid,
|
|
||||||
GID_SYSTEM_NUMBER_MIN
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let gid_v = Value::new_uint32(gid);
|
let gid_v = Value::new_uint32(gid);
|
||||||
|
@ -50,12 +46,8 @@ fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), Opera
|
||||||
} else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) {
|
} else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) {
|
||||||
// If they provided us with a gid number, ensure it's in a safe range.
|
// If they provided us with a gid number, ensure it's in a safe range.
|
||||||
if gid <= GID_SAFETY_NUMBER_MIN {
|
if gid <= GID_SAFETY_NUMBER_MIN {
|
||||||
Err(OperationError::InvalidAttribute(format!(
|
admin_error!("Requested GID {} is lower or equal to a safe value {}", gid, GID_SAFETY_NUMBER_MIN);
|
||||||
"{} {} overlaps into system secure range {}",
|
Err(OperationError::GidOverlapsSystemMin(GID_SAFETY_NUMBER_MIN))
|
||||||
Attribute::GidNumber,
|
|
||||||
gid,
|
|
||||||
GID_SAFETY_NUMBER_MIN
|
|
||||||
)))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -290,9 +282,7 @@ mod tests {
|
||||||
let preload = Vec::new();
|
let preload = Vec::new();
|
||||||
|
|
||||||
run_create_test!(
|
run_create_test!(
|
||||||
Err(OperationError::InvalidAttribute(
|
Err(OperationError::GidOverlapsSystemMin(65536)),
|
||||||
"gidnumber 580 may overlap with system range 65536".to_string()
|
|
||||||
)),
|
|
||||||
preload,
|
preload,
|
||||||
create,
|
create,
|
||||||
None,
|
None,
|
||||||
|
@ -315,9 +305,7 @@ mod tests {
|
||||||
let preload = Vec::new();
|
let preload = Vec::new();
|
||||||
|
|
||||||
run_create_test!(
|
run_create_test!(
|
||||||
Err(OperationError::InvalidAttribute(
|
Err(OperationError::GidOverlapsSystemMin(1000)),
|
||||||
"gidnumber 500 overlaps into system secure range 1000".to_string()
|
|
||||||
)),
|
|
||||||
preload,
|
preload,
|
||||||
create,
|
create,
|
||||||
None,
|
None,
|
||||||
|
@ -340,9 +328,7 @@ mod tests {
|
||||||
let preload = Vec::new();
|
let preload = Vec::new();
|
||||||
|
|
||||||
run_create_test!(
|
run_create_test!(
|
||||||
Err(OperationError::InvalidAttribute(
|
Err(OperationError::GidOverlapsSystemMin(1000)),
|
||||||
"gidnumber 0 overlaps into system secure range 1000".to_string()
|
|
||||||
)),
|
|
||||||
preload,
|
preload,
|
||||||
create,
|
create,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -456,23 +456,24 @@ mod tests {
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::value::{Oauth2Session, Session, SessionState};
|
use crate::value::{Oauth2Session, Session, SessionState};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use uuid::uuid;
|
|
||||||
|
|
||||||
use crate::credential::Credential;
|
use crate::credential::Credential;
|
||||||
use kanidm_lib_crypto::CryptoPolicy;
|
use kanidm_lib_crypto::CryptoPolicy;
|
||||||
|
|
||||||
|
const TEST_TESTGROUP_A_UUID: &str = "d2b496bd-8493-47b7-8142-f568b5cf47ee";
|
||||||
|
const TEST_TESTGROUP_B_UUID: &str = "8cef42bc-2cac-43e4-96b3-8f54561885ca";
|
||||||
|
|
||||||
// The create references a uuid that doesn't exist - reject
|
// The create references a uuid that doesn't exist - reject
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_uuid_reference_not_exist() {
|
fn test_create_uuid_reference_not_exist() {
|
||||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let e = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testgroup")),
|
||||||
"class": ["group"],
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"name": ["testgroup"],
|
(
|
||||||
"description": ["testperson"],
|
Attribute::Member,
|
||||||
"member": ["ca85168c-91b7-49a8-b7bb-a3d5bb40e97e"]
|
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap())
|
||||||
}
|
)
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let create = vec![e];
|
let create = vec![e];
|
||||||
|
@ -491,26 +492,23 @@ mod tests {
|
||||||
// The create references a uuid that does exist - validate
|
// The create references a uuid that does exist - validate
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_uuid_reference_exist() {
|
fn test_create_uuid_reference_exist() {
|
||||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let ea = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||||
"class": ["group"],
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"name": ["testgroup_a"],
|
(
|
||||||
"description": ["testgroup"],
|
Attribute::Uuid,
|
||||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
}
|
)
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
let eb = entry_init!(
|
||||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
r#"{
|
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||||
"attrs": {
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"class": ["group"],
|
(
|
||||||
"name": ["testgroup_b"],
|
Attribute::Member,
|
||||||
"description": ["testgroup"],
|
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
)
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![ea];
|
let preload = vec![ea];
|
||||||
|
@ -604,7 +602,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
ModifyList::new_list(vec![Modify::Present(
|
ModifyList::new_list(vec![Modify::Present(
|
||||||
Attribute::Member.into(),
|
Attribute::Member.into(),
|
||||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||||
)]),
|
)]),
|
||||||
None,
|
None,
|
||||||
|_| {},
|
|_| {},
|
||||||
|
@ -638,7 +636,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
ModifyList::new_list(vec![Modify::Present(
|
ModifyList::new_list(vec![Modify::Present(
|
||||||
Attribute::Member.into(),
|
Attribute::Member.into(),
|
||||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||||
)]),
|
)]),
|
||||||
None,
|
None,
|
||||||
|_| {},
|
|_| {},
|
||||||
|
@ -685,7 +683,7 @@ mod tests {
|
||||||
ModifyList::new_list(vec![
|
ModifyList::new_list(vec![
|
||||||
Modify::Present(
|
Modify::Present(
|
||||||
Attribute::Member.into(),
|
Attribute::Member.into(),
|
||||||
Value::Refer(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
|
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
),
|
),
|
||||||
Modify::Present(Attribute::Member.into(), Value::Refer(UUID_DOES_NOT_EXIST)),
|
Modify::Present(Attribute::Member.into(), Value::Refer(UUID_DOES_NOT_EXIST)),
|
||||||
]),
|
]),
|
||||||
|
@ -761,7 +759,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
ModifyList::new_list(vec![Modify::Present(
|
ModifyList::new_list(vec![Modify::Present(
|
||||||
Attribute::Member.into(),
|
Attribute::Member.into(),
|
||||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||||
)]),
|
)]),
|
||||||
None,
|
None,
|
||||||
|_| {},
|
|_| {},
|
||||||
|
@ -806,7 +804,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
ModifyList::new_list(vec![Modify::Present(
|
ModifyList::new_list(vec![Modify::Present(
|
||||||
Attribute::Member.into(),
|
Attribute::Member.into(),
|
||||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||||
)]),
|
)]),
|
||||||
None,
|
None,
|
||||||
|qs: &mut QueryServerWriteTransaction| {
|
|qs: &mut QueryServerWriteTransaction| {
|
||||||
|
@ -827,26 +825,23 @@ mod tests {
|
||||||
// This is the valid case, where the reference is MAY.
|
// This is the valid case, where the reference is MAY.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_remove_referent_valid() {
|
fn test_delete_remove_referent_valid() {
|
||||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let ea: Entry<EntryInit, EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||||
"class": ["group"],
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"name": ["testgroup_a"],
|
(
|
||||||
"description": ["testgroup"],
|
Attribute::Uuid,
|
||||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
}
|
)
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
let eb: Entry<EntryInit, EntryNew> = entry_init!(
|
||||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
r#"{
|
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||||
"attrs": {
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"class": ["group"],
|
(
|
||||||
"name": ["testgroup_b"],
|
Attribute::Member,
|
||||||
"description": ["testgroup"],
|
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
)
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![ea, eb];
|
let preload = vec![ea, eb];
|
||||||
|
@ -867,31 +862,30 @@ mod tests {
|
||||||
//
|
//
|
||||||
// this is the invalid case, where the reference is MUST.
|
// this is the invalid case, where the reference is MUST.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_remove_referent_invalid() {}
|
fn test_delete_remove_referent_invalid() {
|
||||||
|
// TODO: uh.. wot
|
||||||
|
}
|
||||||
|
|
||||||
// Delete of something that holds references.
|
// Delete of something that holds references.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_remove_referee() {
|
fn test_delete_remove_referee() {
|
||||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let ea: Entry<EntryInit, EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||||
"class": ["group"],
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"name": ["testgroup_a"],
|
(
|
||||||
"description": ["testgroup"],
|
Attribute::Uuid,
|
||||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
}
|
)
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
let eb: Entry<EntryInit, EntryNew> = entry_init!(
|
||||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
r#"{
|
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||||
"attrs": {
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"class": ["group"],
|
(
|
||||||
"name": ["testgroup_b"],
|
Attribute::Member,
|
||||||
"description": ["testgroup"],
|
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
)
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![ea, eb];
|
let preload = vec![ea, eb];
|
||||||
|
@ -911,18 +905,19 @@ mod tests {
|
||||||
// Delete something that has a self reference.
|
// Delete something that has a self reference.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_remove_reference_self() {
|
fn test_delete_remove_reference_self() {
|
||||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let eb: Entry<EntryInit, EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
"attrs": {
|
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||||
"class": ["group"],
|
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||||
"name": ["testgroup_b"],
|
(
|
||||||
"description": ["testgroup"],
|
Attribute::Uuid,
|
||||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"],
|
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
),
|
||||||
}
|
(
|
||||||
}"#,
|
Attribute::Member,
|
||||||
|
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![eb];
|
let preload = vec![eb];
|
||||||
|
|
||||||
run_delete_test!(
|
run_delete_test!(
|
||||||
|
@ -964,7 +959,7 @@ mod tests {
|
||||||
(
|
(
|
||||||
Attribute::OAuth2RsScopeMap,
|
Attribute::OAuth2RsScopeMap,
|
||||||
Value::new_oauthscopemap(
|
Value::new_oauthscopemap(
|
||||||
uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
|
Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap(),
|
||||||
btreeset![OAUTH2_SCOPE_READ.to_string()]
|
btreeset![OAUTH2_SCOPE_READ.to_string()]
|
||||||
)
|
)
|
||||||
.expect("Invalid scope")
|
.expect("Invalid scope")
|
||||||
|
@ -976,7 +971,7 @@ mod tests {
|
||||||
(Attribute::Name, Value::new_iname("testgroup")),
|
(Attribute::Name, Value::new_iname("testgroup")),
|
||||||
(
|
(
|
||||||
Attribute::Uuid,
|
Attribute::Uuid,
|
||||||
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
|
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap())
|
||||||
),
|
),
|
||||||
(Attribute::Description, Value::new_utf8s("testgroup"))
|
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||||
);
|
);
|
||||||
|
@ -1015,7 +1010,7 @@ mod tests {
|
||||||
// Create a user
|
// Create a user
|
||||||
let mut server_txn = server.write(curtime).await;
|
let mut server_txn = server.write(curtime).await;
|
||||||
|
|
||||||
let tuuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
let tuuid = Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap();
|
||||||
let rs_uuid = Uuid::new_v4();
|
let rs_uuid = Uuid::new_v4();
|
||||||
|
|
||||||
let e1 = entry_init!(
|
let e1 = entry_init!(
|
||||||
|
|
|
@ -225,16 +225,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spn_generate_create() {
|
fn test_spn_generate_create() {
|
||||||
// on create don't provide
|
// on create don't provide the spn, we generate it.
|
||||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
"attrs": {
|
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||||
"class": ["account", "service_account"],
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
"name": ["testperson"],
|
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||||
"description": ["testperson"],
|
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||||
"displayname": ["testperson"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let create = vec![e];
|
let create = vec![e];
|
||||||
|
@ -252,16 +249,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spn_generate_modify() {
|
fn test_spn_generate_modify() {
|
||||||
// on a purge of the spen, generate it.
|
// on a purge of the spn, generate it.
|
||||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
r#"{
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
"attrs": {
|
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||||
"class": ["account", "service_account"],
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
"name": ["testperson"],
|
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||||
"description": ["testperson"],
|
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||||
"displayname": ["testperson"]
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![e];
|
let preload = vec![e];
|
||||||
|
@ -280,16 +274,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spn_validate_create() {
|
fn test_spn_validate_create() {
|
||||||
// on create providing invalid spn, we over-write it.
|
// on create providing invalid spn, we over-write it.
|
||||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
|
||||||
r#"{
|
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
"attrs": {
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
"class": ["account", "service_account"],
|
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||||
"spn": ["testperson@invalid_domain.com"],
|
(Attribute::Spn, Value::new_utf8s("testperson@invalid_domain.com")),
|
||||||
"name": ["testperson"],
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
"description": ["testperson"],
|
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||||
"displayname": ["testperson"]
|
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let create = vec![e];
|
let create = vec![e];
|
||||||
|
@ -307,15 +299,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spn_validate_modify() {
|
fn test_spn_validate_modify() {
|
||||||
// On modify (removed/present) of the spn, just regenerate it.
|
// On modify (removed/present) of the spn, just regenerate it.
|
||||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
|
||||||
r#"{
|
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||||
"attrs": {
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
"class": ["account", "service_account"],
|
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||||
"name": ["testperson"],
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
"description": ["testperson"],
|
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||||
"displayname": ["testperson"]
|
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let preload = vec![e];
|
let preload = vec![e];
|
||||||
|
|
|
@ -2903,7 +2903,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
e_service_person.validate(&schema),
|
e_service_person.validate(&schema),
|
||||||
Err(SchemaError::ExcludesNotSatisfied(
|
Err(SchemaError::ExcludesNotSatisfied(
|
||||||
vec!["person".to_string()]
|
vec![EntryClass::Person.to_string()]
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1703,7 +1703,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Spn(n, r) => format!("{n}@{r}"),
|
Value::Spn(n, r) => format!("{n}@{r}"),
|
||||||
_ => unreachable!(),
|
_ => unreachable!("You've specified the wrong type for the attribute, got: {:?}", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
//! Integration tests using browser automation
|
//! Integration tests using browser automation
|
||||||
|
|
||||||
|
use kanidm_client::KanidmClient;
|
||||||
|
use kanidmd_testkit::login_put_admin_idm_admins;
|
||||||
|
|
||||||
/// Tries to handle closing the webdriver session if there's an error
|
/// Tries to handle closing the webdriver session if there's an error
|
||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
macro_rules! handle_error {
|
macro_rules! handle_error {
|
||||||
|
@ -197,3 +200,22 @@ async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) {
|
||||||
}
|
}
|
||||||
// tokio::time::sleep(Duration::from_millis(3000)).await;
|
// tokio::time::sleep(Duration::from_millis(3000)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[kanidmd_testkit::test]
|
||||||
|
async fn test_domain_reset_token_key(rsclient: KanidmClient) {
|
||||||
|
login_put_admin_idm_admins(&rsclient).await;
|
||||||
|
assert!(rsclient.idm_domain_reset_token_key().await.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kanidmd_testkit::test]
|
||||||
|
async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
|
||||||
|
login_put_admin_idm_admins(&rsclient).await;
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_domain_set_ldap_basedn("dc=krabsarekool,dc=example,dc=com")
|
||||||
|
.await
|
||||||
|
.is_ok());
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_domain_set_ldap_basedn("krabsarekool")
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
||||||
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
||||||
|
|
||||||
use kanidm_client::{ClientError, KanidmClient};
|
use kanidm_client::{ClientError, KanidmClient};
|
||||||
use kanidmd_testkit::ADMIN_TEST_PASSWORD;
|
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||||
|
|
||||||
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
||||||
|
|
||||||
|
@ -1410,29 +1410,36 @@ async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
let test_service_account_username = "test_service";
|
||||||
|
|
||||||
// Not recommended in production!
|
// Not recommended in production!
|
||||||
rsclient
|
rsclient
|
||||||
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
|
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &[ADMIN_TEST_USER])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
rsclient
|
rsclient
|
||||||
.idm_service_account_create("test_service", "Test Service")
|
.idm_service_account_create(test_service_account_username, "Test Service")
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create service account");
|
.expect("Failed to create service account");
|
||||||
|
|
||||||
let tokens = rsclient
|
let tokens = rsclient
|
||||||
.idm_service_account_list_api_token("test_service")
|
.idm_service_account_list_api_token(test_service_account_username)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to list service account api tokens");
|
.expect("Failed to list service account api tokens");
|
||||||
assert!(tokens.is_empty());
|
assert!(tokens.is_empty());
|
||||||
|
|
||||||
let token = rsclient
|
let token = rsclient
|
||||||
.idm_service_account_generate_api_token("test_service", "test token", None, false)
|
.idm_service_account_generate_api_token(
|
||||||
|
test_service_account_username,
|
||||||
|
"test token",
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create service account api token");
|
.expect("Failed to create service account api token");
|
||||||
|
|
||||||
|
@ -1445,7 +1452,7 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
||||||
.expect("Embedded jwk not found");
|
.expect("Embedded jwk not found");
|
||||||
|
|
||||||
let tokens = rsclient
|
let tokens = rsclient
|
||||||
.idm_service_account_list_api_token("test_service")
|
.idm_service_account_list_api_token(test_service_account_username)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to list service account api tokens");
|
.expect("Failed to list service account api tokens");
|
||||||
|
|
||||||
|
@ -1457,24 +1464,134 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
||||||
.expect("Failed to destroy service account api token");
|
.expect("Failed to destroy service account api token");
|
||||||
|
|
||||||
let tokens = rsclient
|
let tokens = rsclient
|
||||||
.idm_service_account_list_api_token("test_service")
|
.idm_service_account_list_api_token(test_service_account_username)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to list service account api tokens");
|
.expect("Failed to list service account api tokens");
|
||||||
assert!(tokens.is_empty());
|
assert!(tokens.is_empty());
|
||||||
|
|
||||||
|
// test we can add an attribute
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_add_attr(
|
||||||
|
test_service_account_username,
|
||||||
|
Attribute::Mail.as_ref(),
|
||||||
|
&vec!["test@example.com"]
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
// test we can overwrite an attribute
|
||||||
|
let new_displayname = vec!["testing displayname 1235"];
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_set_attr(
|
||||||
|
test_service_account_username,
|
||||||
|
Attribute::DisplayName.as_ref(),
|
||||||
|
&new_displayname
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_ok());
|
||||||
|
// check it actually set
|
||||||
|
let displayname = rsclient
|
||||||
|
.idm_service_account_get_attr(
|
||||||
|
test_service_account_username,
|
||||||
|
Attribute::DisplayName.as_ref(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to get displayname")
|
||||||
|
.expect("Failed to unwrap displayname");
|
||||||
|
assert!(new_displayname == displayname);
|
||||||
|
|
||||||
|
rsclient
|
||||||
|
.idm_service_account_purge_attr(test_service_account_username, Attribute::Mail.as_ref())
|
||||||
|
.await
|
||||||
|
.expect("Failed to purge displayname");
|
||||||
|
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_get_attr(test_service_account_username, Attribute::Mail.as_ref(),)
|
||||||
|
.await
|
||||||
|
.expect("Failed to check mail attr")
|
||||||
|
.is_none());
|
||||||
|
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_unix_extend(
|
||||||
|
test_service_account_username,
|
||||||
|
Some(58008),
|
||||||
|
Some("/bin/vim")
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_unix_extend(
|
||||||
|
test_service_account_username,
|
||||||
|
Some(1000),
|
||||||
|
Some("/bin/vim")
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
// because you have to set *something*
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_update(test_service_account_username, None, None, None)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_update(
|
||||||
|
test_service_account_username,
|
||||||
|
Some(&format!("{}lol", test_service_account_username)),
|
||||||
|
Some(&format!("{}displayzzzz", test_service_account_username)),
|
||||||
|
Some(&[format!("{}@example.crabs", test_service_account_username)]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
let pw = rsclient.idm_service_account_generate_password(test_service_account_username).await.expect("Failed to get a pw for the service account");
|
||||||
|
|
||||||
|
assert!(!pw.is_empty());
|
||||||
|
assert!(pw.is_ascii());
|
||||||
|
|
||||||
|
let res = rsclient.idm_service_account_get_credential_status(test_service_account_username).await;
|
||||||
|
dbg!(&res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"testing deletion of service account {}",
|
||||||
|
test_service_account_username
|
||||||
|
);
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_service_account_delete(test_service_account_username)
|
||||||
|
.await
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// let's create one and just yolo it into a person
|
||||||
|
// TODO: Turns out this doesn't work because admin doesn't have the right perms to remove `jws_es256_private_key` from the account?
|
||||||
|
// rsclient
|
||||||
|
// .idm_service_account_create(test_service_account_username, "Test Service")
|
||||||
|
// .await
|
||||||
|
// .expect("Failed to create service account");
|
||||||
|
|
||||||
|
// rsclient.idm_service_account_into_person(test_service_account_username).await.expect("Failed to convert service account into person");
|
||||||
|
|
||||||
|
// assert!(rsclient
|
||||||
|
// .idm_person_account_delete(test_service_account_username)
|
||||||
|
// .await
|
||||||
|
// .is_ok());
|
||||||
|
|
||||||
// No need to test expiry, that's validated in the server internal tests.
|
// No need to test expiry, that's validated in the server internal tests.
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
// Not recommended in production!
|
// Not recommended in production!
|
||||||
rsclient
|
rsclient
|
||||||
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
|
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &[ADMIN_TEST_USER])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1553,7 +1670,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
||||||
|
|
||||||
// Since the session is revoked, check with the admin.
|
// Since the session is revoked, check with the admin.
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
@ -1564,6 +1681,18 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
||||||
assert!(tokens.is_empty());
|
assert!(tokens.is_empty());
|
||||||
|
|
||||||
// No need to test expiry, that's validated in the server internal tests.
|
// No need to test expiry, that's validated in the server internal tests.
|
||||||
|
|
||||||
|
// testing idm_account_credential_update_cancel_mfareg
|
||||||
|
let (token, _status) = rsclient
|
||||||
|
.idm_account_credential_update_begin("demo_account")
|
||||||
|
.await
|
||||||
|
.expect("Failed to get token for demo_account");
|
||||||
|
|
||||||
|
println!("trying to cancel the token we just got");
|
||||||
|
assert!(rsclient
|
||||||
|
.idm_account_credential_update_cancel_mfareg(&token)
|
||||||
|
.await
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
|
@ -1793,3 +1922,21 @@ async fn test_server_user_auth_privileged_shortcut(rsclient: KanidmClient) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wanna test how long it takes for testkit to start up? here's your biz.
|
||||||
|
// turns out as of 2023-10-11 on my M2 Max, it's about 1.0 seconds per iteration
|
||||||
|
// #[kanidmd_testkit::test]
|
||||||
|
// fn test_teskit_test_test() {
|
||||||
|
// #[allow(unnameable_test_items)]
|
||||||
|
|
||||||
|
// for _ in 0..15 {
|
||||||
|
// #[kanidmd_testkit::test]
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// async fn test_teskit_test(rsclient: KanidmClient){
|
||||||
|
// assert!(rsclient.auth_anonymous().await.is_ok());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// tk_test_teskit_test();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use compact_jwt::JwsUnverified;
|
use compact_jwt::JwsUnverified;
|
||||||
use kanidm_client::KanidmClient;
|
use kanidm_client::KanidmClient;
|
||||||
use kanidm_proto::internal::ScimSyncToken;
|
use kanidm_proto::internal::ScimSyncToken;
|
||||||
use kanidmd_testkit::ADMIN_TEST_PASSWORD;
|
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||||
use reqwest::header::HeaderValue;
|
use reqwest::header::HeaderValue;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -9,7 +9,7 @@ use url::Url;
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
|
async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
|
||||||
let a_res = rsclient
|
let a_res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
assert!(a_res.is_ok());
|
assert!(a_res.is_ok());
|
||||||
|
|
||||||
|
|
|
@ -497,6 +497,7 @@ impl ServiceAccountOpt {
|
||||||
}
|
}
|
||||||
}, // end ServiceAccountOpt::Validity
|
}, // end ServiceAccountOpt::Validity
|
||||||
ServiceAccountOpt::IntoPerson(aopt) => {
|
ServiceAccountOpt::IntoPerson(aopt) => {
|
||||||
|
warn!("This command is deprecated and will be removed in a future release");
|
||||||
let client = aopt.copt.to_client(OpType::Write).await;
|
let client = aopt.copt.to_client(OpType::Write).await;
|
||||||
match client
|
match client
|
||||||
.idm_service_account_into_person(aopt.aopts.account_id.as_str())
|
.idm_service_account_into_person(aopt.aopts.account_id.as_str())
|
||||||
|
|
|
@ -515,7 +515,7 @@ pub enum ServiceAccountOpt {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
commands: AccountValidity,
|
commands: AccountValidity,
|
||||||
},
|
},
|
||||||
/// Convert a service account into a person. This is used during the alpha.9
|
/// (Deprecated - due for removal in v1.1.0-15) - Convert a service account into a person. This is used during the alpha.9
|
||||||
/// to alpha.10 migration to "fix up" accounts that were not previously marked
|
/// to alpha.10 migration to "fix up" accounts that were not previously marked
|
||||||
/// as persons.
|
/// as persons.
|
||||||
#[clap(name = "into-person")]
|
#[clap(name = "into-person")]
|
||||||
|
|
|
@ -23,8 +23,8 @@ use base64urlsafedata::Base64UrlSafeData;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cron::Schedule;
|
use cron::Schedule;
|
||||||
use kanidm_proto::constants::{LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS};
|
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_CLASS_GROUPOFNAMES};
|
||||||
use kanidmd_lib::prelude::Attribute;
|
use kanidmd_lib::prelude::{Attribute, EntryClass};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs::metadata;
|
use std::fs::metadata;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -583,7 +583,7 @@ async fn process_ipa_sync_result(
|
||||||
.entry
|
.entry
|
||||||
.attrs
|
.attrs
|
||||||
.get(LDAP_ATTR_OBJECTCLASS)
|
.get(LDAP_ATTR_OBJECTCLASS)
|
||||||
.map(|oc| oc.contains("person"))
|
.map(|oc| oc.contains(EntryClass::Person.as_ref()))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
{
|
{
|
||||||
user_dns.push(dn.clone());
|
user_dns.push(dn.clone());
|
||||||
|
@ -638,7 +638,7 @@ async fn process_ipa_sync_result(
|
||||||
// We have to split the DN to it's RDN because lol.
|
// We have to split the DN to it's RDN because lol.
|
||||||
dn.split_once(',')
|
dn.split_once(',')
|
||||||
.and_then(|(rdn, _)| rdn.split_once('='))
|
.and_then(|(rdn, _)| rdn.split_once('='))
|
||||||
.map(|(_, uid)| LdapFilter::Equality("uid".to_string(), uid.to_string()))
|
.map(|(_, uid)| LdapFilter::Equality(ATTR_UID.to_string(), uid.to_string()))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -903,7 +903,7 @@ fn ipa_to_scim_entry(
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
))
|
))
|
||||||
} else if oc.contains("groupofnames") {
|
} else if oc.contains(LDAP_CLASS_GROUPOFNAMES) {
|
||||||
let LdapSyncReplEntry {
|
let LdapSyncReplEntry {
|
||||||
entry_uuid,
|
entry_uuid,
|
||||||
state: _,
|
state: _,
|
||||||
|
|
|
@ -40,3 +40,6 @@ kanidm_utils_users = { workspace = true }
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
clap = { workspace = true, features = ["derive"] }
|
clap = { workspace = true, features = ["derive"] }
|
||||||
clap_complete = { workspace = true }
|
clap_complete = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
sketching = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use kanidmd_lib::prelude::Attribute;
|
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_CN, LDAP_CLASS_GROUPOFNAMES};
|
||||||
|
use kanidmd_lib::prelude::{Attribute, EntryClass};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -7,15 +8,15 @@ use uuid::Uuid;
|
||||||
use ldap3_client::proto::LdapFilter;
|
use ldap3_client::proto::LdapFilter;
|
||||||
|
|
||||||
fn person_objectclass() -> String {
|
fn person_objectclass() -> String {
|
||||||
"person".to_string()
|
EntryClass::Person.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn person_attr_user_name() -> String {
|
fn person_attr_user_name() -> String {
|
||||||
"uid".to_string()
|
ATTR_UID.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn person_attr_display_name() -> String {
|
fn person_attr_display_name() -> String {
|
||||||
"cn".to_string()
|
LDAP_ATTR_CN.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn person_attr_gidnumber() -> String {
|
fn person_attr_gidnumber() -> String {
|
||||||
|
@ -39,7 +40,7 @@ fn person_attr_ssh_public_key() -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group_objectclass() -> String {
|
fn group_objectclass() -> String {
|
||||||
"groupofnames".to_string()
|
LDAP_CLASS_GROUPOFNAMES.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group_attr_name() -> String {
|
fn group_attr_name() -> String {
|
||||||
|
|
|
@ -62,21 +62,21 @@ async fn driver_main(opt: Opt) {
|
||||||
let mut f = match File::open(&opt.ldap_sync_config) {
|
let mut f = match File::open(&opt.ldap_sync_config) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to open profile file [{:?}] 🥺", e);
|
error!("Unable to open ldap sync config from '{}' [{:?}] 🥺", &opt.ldap_sync_config.display(), e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
if let Err(e) = f.read_to_string(&mut contents) {
|
if let Err(e) = f.read_to_string(&mut contents) {
|
||||||
error!("unable to read profile contents {:?}", e);
|
error!("unable to read file '{}': {:?}", &opt.ldap_sync_config.display(), e);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let sync_config: Config = match toml::from_str(contents.as_str()) {
|
let sync_config: Config = match toml::from_str(contents.as_str()) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("unable to parse config {:?}", e);
|
eprintln!("Unable to parse config from '{}' error: {:?}", &opt.ldap_sync_config.display(), e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -781,3 +781,41 @@ fn main() {
|
||||||
|
|
||||||
rt.block_on(async move { driver_main(opt).await });
|
rt.block_on(async move { driver_main(opt).await });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_driver_main() {
|
||||||
|
let testopt = Opt {
|
||||||
|
client_config: PathBuf::from("test"),
|
||||||
|
ldap_sync_config: PathBuf::from("test"),
|
||||||
|
debug: false,
|
||||||
|
schedule: false,
|
||||||
|
proto_dump: false,
|
||||||
|
dry_run: false,
|
||||||
|
skip_root_check: true,
|
||||||
|
};
|
||||||
|
let _ = sketching::test_init();
|
||||||
|
|
||||||
|
println!("testing config");
|
||||||
|
// because it can't find the profile file it'll just stop
|
||||||
|
assert_eq!(driver_main(testopt.clone()).await, ());
|
||||||
|
println!("done testing missing config");
|
||||||
|
|
||||||
|
let testopt = Opt{
|
||||||
|
client_config: PathBuf::from(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))),
|
||||||
|
ldap_sync_config: PathBuf::from(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))),
|
||||||
|
..testopt
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("valid file path, invalid contents");
|
||||||
|
assert_eq!(driver_main(testopt.clone()).await, ());
|
||||||
|
println!("done with valid file path, invalid contents");
|
||||||
|
let testopt = Opt{
|
||||||
|
client_config: PathBuf::from(format!("{}/../../../examples/iam_migration_ldap.toml", env!("CARGO_MANIFEST_DIR"))),
|
||||||
|
ldap_sync_config: PathBuf::from(format!("{}/../../../examples/iam_migration_ldap.toml", env!("CARGO_MANIFEST_DIR"))),
|
||||||
|
..testopt
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("valid file path, invalid contents");
|
||||||
|
assert_eq!(driver_main(testopt).await, ());
|
||||||
|
println!("done with valid file path, valid contents");
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
|
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
|
||||||
pub const DEFAULT_LDAP_CONFIG_PATH: &str = "/etc/kanidm/ldap-sync";
|
pub const DEFAULT_LDAP_CONFIG_PATH: &str = "/etc/kanidm/ldap-sync";
|
||||||
|
|
||||||
#[derive(Debug, clap::Parser)]
|
#[derive(Debug, clap::Parser, Clone)]
|
||||||
#[clap(about = "Kanidm LDAP Sync Driver")]
|
#[clap(about = "Kanidm LDAP Sync Driver")]
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
/// Enable debugging of the sync driver
|
/// Enable debugging of the sync driver
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_DISPLAY_NAME, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_ATTR_OU, LDAP_ATTR_GROUPS};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use ldap3_proto::proto::*;
|
use ldap3_proto::proto::*;
|
||||||
|
@ -59,7 +60,7 @@ impl DirectoryServer {
|
||||||
let filter = LdapFilter::Or(
|
let filter = LdapFilter::Or(
|
||||||
targets
|
targets
|
||||||
.iter()
|
.iter()
|
||||||
.map(|u| LdapFilter::Equality("cn".to_string(), u.to_string()))
|
.map(|u| LdapFilter::Equality(LDAP_ATTR_CN.to_string(), u.to_string()))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ impl DirectoryServer {
|
||||||
// Check if ou=people and ou=group exist
|
// Check if ou=people and ou=group exist
|
||||||
let res = self
|
let res = self
|
||||||
.ldap
|
.ldap
|
||||||
.search(LdapFilter::Equality("ou".to_string(), "people".to_string()))
|
.search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(), "people".to_string()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if res.is_empty() {
|
if res.is_empty() {
|
||||||
|
@ -96,14 +97,14 @@ impl DirectoryServer {
|
||||||
dn: format!("ou=people,{}", self.ldap.basedn),
|
dn: format!("ou=people,{}", self.ldap.basedn),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"top".as_bytes().into(),
|
"top".as_bytes().into(),
|
||||||
"organizationalUnit".as_bytes().into(),
|
"organizationalUnit".as_bytes().into(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "ou".to_string(),
|
atype: LDAP_ATTR_OU.to_string(),
|
||||||
vals: vec!["people".as_bytes().into()],
|
vals: vec!["people".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -113,25 +114,25 @@ impl DirectoryServer {
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.ldap
|
.ldap
|
||||||
.search(LdapFilter::Equality("ou".to_string(), "groups".to_string()))
|
.search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(),LDAP_ATTR_GROUPS.to_string()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if res.is_empty() {
|
if res.is_empty() {
|
||||||
// Doesn't exist
|
// Doesn't exist
|
||||||
info!("Creating ou=groups");
|
info!("Creating ou={}", LDAP_ATTR_GROUPS);
|
||||||
let ou_groups = LdapAddRequest {
|
let ou_groups: LdapAddRequest = LdapAddRequest {
|
||||||
dn: format!("ou=groups,{}", self.ldap.basedn),
|
dn: format!("ou={},{}", LDAP_ATTR_GROUPS, self.ldap.basedn),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"top".as_bytes().into(),
|
"top".as_bytes().into(),
|
||||||
"organizationalUnit".as_bytes().into(),
|
"organizationalUnit".as_bytes().into(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "ou".to_string(),
|
atype: LDAP_ATTR_OU.to_string(),
|
||||||
vals: vec!["groups".as_bytes().into()],
|
vals: vec![LDAP_ATTR_GROUPS.as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -144,7 +145,7 @@ impl DirectoryServer {
|
||||||
// does it already exist?
|
// does it already exist?
|
||||||
let res = self
|
let res = self
|
||||||
.ldap
|
.ldap
|
||||||
.search(LdapFilter::Equality("cn".to_string(), u.to_string()))
|
.search(LdapFilter::Equality(LDAP_ATTR_CN.to_string(), u.to_string()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !res.is_empty() {
|
if !res.is_empty() {
|
||||||
|
@ -159,7 +160,7 @@ impl DirectoryServer {
|
||||||
dn,
|
dn,
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"top".as_bytes().into(),
|
"top".as_bytes().into(),
|
||||||
"nsPerson".as_bytes().into(),
|
"nsPerson".as_bytes().into(),
|
||||||
|
@ -169,15 +170,15 @@ impl DirectoryServer {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: LDAP_ATTR_CN.to_string(),
|
||||||
vals: vec![a.uuid.as_bytes().to_vec()],
|
vals: vec![a.uuid.as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "uid".to_string(),
|
atype: ATTR_UID.to_string(),
|
||||||
vals: vec![a.name.as_bytes().into()],
|
vals: vec![a.name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "displayName".to_string(),
|
atype: LDAP_ATTR_DISPLAY_NAME.to_string(),
|
||||||
vals: vec![a.display_name.as_bytes().into()],
|
vals: vec![a.display_name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
|
@ -205,14 +206,14 @@ impl DirectoryServer {
|
||||||
dn,
|
dn,
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"top".as_bytes().into(),
|
"top".as_bytes().into(),
|
||||||
"groupOfNames".as_bytes().into(),
|
"groupOfNames".as_bytes().into(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: LDAP_ATTR_CN.to_string(),
|
||||||
vals: vec![g.uuid.as_bytes().to_vec(), g.name.as_bytes().into()],
|
vals: vec![g.uuid.as_bytes().to_vec(), g.name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -268,7 +269,7 @@ impl DirectoryServer {
|
||||||
let res = self
|
let res = self
|
||||||
.ldap
|
.ldap
|
||||||
.search(LdapFilter::Equality(
|
.search(LdapFilter::Equality(
|
||||||
"cn".to_string(),
|
LDAP_ATTR_CN.to_string(),
|
||||||
"priv_account_manage".to_string(),
|
"priv_account_manage".to_string(),
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -280,11 +281,11 @@ impl DirectoryServer {
|
||||||
dn: format!("cn=priv_account_manage,{}", self.ldap.basedn),
|
dn: format!("cn=priv_account_manage,{}", self.ldap.basedn),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||||
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: LDAP_ATTR_CN.to_string(),
|
||||||
vals: vec!["priv_account_manage".as_bytes().into()],
|
vals: vec!["priv_account_manage".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -295,7 +296,7 @@ impl DirectoryServer {
|
||||||
let res = self
|
let res = self
|
||||||
.ldap
|
.ldap
|
||||||
.search(LdapFilter::Equality(
|
.search(LdapFilter::Equality(
|
||||||
"cn".to_string(),
|
LDAP_ATTR_CN.to_string(),
|
||||||
"priv_group_manage".to_string(),
|
"priv_group_manage".to_string(),
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -307,11 +308,11 @@ impl DirectoryServer {
|
||||||
dn: format!("cn=priv_group_manage,{}", self.ldap.basedn),
|
dn: format!("cn=priv_group_manage,{}", self.ldap.basedn),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||||
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: LDAP_ATTR_CN.to_string(),
|
||||||
vals: vec!["priv_group_manage".as_bytes().into()],
|
vals: vec!["priv_group_manage".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_DISPLAY_NAME, LDAP_CLASS_GROUPOFNAMES, LDAP_ATTR_OBJECTCLASS, LDAP_ATTR_CN};
|
||||||
use ldap3_proto::proto::*;
|
use ldap3_proto::proto::*;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -93,7 +94,7 @@ impl IpaServer {
|
||||||
dn,
|
dn,
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"ipaobject".as_bytes().into(),
|
"ipaobject".as_bytes().into(),
|
||||||
"person".as_bytes().into(),
|
"person".as_bytes().into(),
|
||||||
|
@ -113,11 +114,11 @@ impl IpaServer {
|
||||||
vals: vec!["autogenerate".as_bytes().into()],
|
vals: vec!["autogenerate".as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "uid".to_string(),
|
atype: ATTR_UID.to_string(),
|
||||||
vals: vec![a.name.as_bytes().into()],
|
vals: vec![a.name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: LDAP_ATTR_CN.to_string(),
|
||||||
vals: vec![a.name.as_bytes().into()],
|
vals: vec![a.name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
|
@ -129,7 +130,7 @@ impl IpaServer {
|
||||||
vals: vec![a.name.as_bytes().into()],
|
vals: vec![a.name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "displayName".to_string(),
|
atype: LDAP_ATTR_DISPLAY_NAME.to_string(),
|
||||||
vals: vec![a.display_name.as_bytes().into()],
|
vals: vec![a.display_name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
|
@ -200,7 +201,7 @@ impl IpaServer {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"top".as_bytes().into(),
|
"top".as_bytes().into(),
|
||||||
"groupofnames".as_bytes().into(),
|
LDAP_CLASS_GROUPOFNAMES.as_bytes().into(),
|
||||||
"nestedgroup".as_bytes().into(),
|
"nestedgroup".as_bytes().into(),
|
||||||
"ipausergroup".as_bytes().into(),
|
"ipausergroup".as_bytes().into(),
|
||||||
"ipaobject".as_bytes().into(),
|
"ipaobject".as_bytes().into(),
|
||||||
|
|
Loading…
Reference in a new issue