mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 04:27:02 +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",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sketching",
|
||||
"tokio",
|
||||
"toml",
|
||||
"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::{Deserialize, Serialize};
|
||||
use serde_json::error::Error as SerdeJsonError;
|
||||
use serde_json::json;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use url::Url;
|
||||
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)]
|
||||
pub struct KanidmClient {
|
||||
pub(crate) client: reqwest::Client,
|
||||
|
@ -890,7 +917,8 @@ impl KanidmClient {
|
|||
let response = self
|
||||
.client
|
||||
.delete(self.make_url(dest))
|
||||
.header(CONTENT_TYPE, APPLICATION_JSON);
|
||||
// empty-ish body that makes the parser happy
|
||||
.json(&json!([]));
|
||||
|
||||
let response = {
|
||||
let tguard = self.bearer_token.read().await;
|
||||
|
@ -932,12 +960,10 @@ impl KanidmClient {
|
|||
dest: &str,
|
||||
request: R,
|
||||
) -> Result<(), ClientError> {
|
||||
let req_string = serde_json::to_string(&request).map_err(ClientError::JsonEncode)?;
|
||||
let response = self
|
||||
.client
|
||||
.delete(self.make_url(dest))
|
||||
.body(req_string)
|
||||
.header(CONTENT_TYPE, APPLICATION_JSON);
|
||||
.json(&request);
|
||||
|
||||
let response = {
|
||||
let tguard = self.bearer_token.read().await;
|
||||
|
@ -1647,6 +1673,7 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
// TODO: add test coverage
|
||||
pub async fn idm_account_credential_update_accept_sha1_totp(
|
||||
&self,
|
||||
session_token: &CUSessionToken,
|
||||
|
@ -1666,6 +1693,7 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
// TODO: add test coverage
|
||||
pub async fn idm_account_credential_update_backup_codes_generate(
|
||||
&self,
|
||||
session_token: &CUSessionToken,
|
||||
|
@ -1675,6 +1703,7 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
// TODO: add test coverage
|
||||
pub async fn idm_account_credential_update_primary_remove(
|
||||
&self,
|
||||
session_token: &CUSessionToken,
|
||||
|
@ -1704,6 +1733,7 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
// TODO: add test coverage
|
||||
pub async fn idm_account_credential_update_passkey_remove(
|
||||
&self,
|
||||
session_token: &CUSessionToken,
|
||||
|
|
|
@ -2,10 +2,12 @@ use crate::{ClientError, KanidmClient};
|
|||
use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState};
|
||||
|
||||
impl KanidmClient {
|
||||
// TODO: testing for this
|
||||
pub async fn scim_v1_sync_status(&self) -> Result<ScimSyncState, ClientError> {
|
||||
self.perform_get_request("/scim/v1/Sync").await
|
||||
}
|
||||
|
||||
// TODO: testing for this
|
||||
pub async fn scim_v1_sync_update(
|
||||
&self,
|
||||
scim_sync_request: &ScimSyncRequest,
|
||||
|
|
|
@ -146,8 +146,11 @@ impl KanidmClient {
|
|||
|
||||
pub async fn idm_service_account_unix_extend(
|
||||
&self,
|
||||
// The username or uuid of the account
|
||||
id: &str,
|
||||
// The GID number to set for the account
|
||||
gidnumber: Option<u32>,
|
||||
// Set a default login shell
|
||||
shell: Option<&str>,
|
||||
) -> Result<(), ClientError> {
|
||||
let ux = AccountUnixExtend {
|
||||
|
@ -158,6 +161,7 @@ impl KanidmClient {
|
|||
.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> {
|
||||
self.perform_post_request(
|
||||
format!("/v1/service_account/{}/_into_person", id).as_str(),
|
||||
|
|
|
@ -78,6 +78,7 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Creates a sync token for a given sync account
|
||||
pub async fn idm_sync_account_generate_token(
|
||||
&self,
|
||||
id: &str,
|
||||
|
|
|
@ -168,6 +168,7 @@ pub const OAUTH2_SCOPE_READ: &str = "read";
|
|||
pub const OAUTH2_SCOPE_SUPPLEMENT: &str = "supplement";
|
||||
|
||||
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_PRIMARY: &str = "emailprimary";
|
||||
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_OBJECTCLASS: &str = "objectClass";
|
||||
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
|
||||
pub const TEST_ATTR_NON_EXIST: &str = "non-exist";
|
||||
|
|
|
@ -273,6 +273,8 @@ pub enum OperationError {
|
|||
ReplDomainLevelUnsatisfiable,
|
||||
ReplDomainUuidMismatch,
|
||||
TransactionAlreadyCommitted,
|
||||
/// when you ask for a gid that's lower than a safe minimum
|
||||
GidOverlapsSystemMin(u32),
|
||||
}
|
||||
|
||||
impl PartialEq for OperationError {
|
||||
|
|
|
@ -896,6 +896,7 @@ pub async fn account_id_radius_token(
|
|||
}
|
||||
|
||||
/// Expects an `AccountUnixExtend` object
|
||||
///
|
||||
#[instrument(name = "account_post_id_unix", level = "INFO", skip(id, state, kopid))]
|
||||
pub async fn account_post_id_unix(
|
||||
State(state): State<ServerState>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::time::{Duration, Instant};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
use criterion::{
|
||||
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_init;
|
||||
use kanidmd_lib::prelude::{Attribute, EntryClass};
|
||||
use kanidmd_lib::utils::duration_from_epoch_now;
|
||||
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) {
|
||||
let mut group = c.benchmark_group("user_create_single");
|
||||
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 {
|
||||
fn from(val: EntryClass) -> Self {
|
||||
let s: &'static str = val.into();
|
||||
|
|
|
@ -897,10 +897,10 @@ mod tests {
|
|||
assert_entry_contains!(
|
||||
lsre,
|
||||
"spn=testperson1@example.com,dc=example,dc=com",
|
||||
(Attribute::ObjectClass, "object"),
|
||||
(Attribute::ObjectClass, "person"),
|
||||
(Attribute::ObjectClass, "account"),
|
||||
(Attribute::ObjectClass, "posixaccount"),
|
||||
(Attribute::ObjectClass, EntryClass::Object.as_ref()),
|
||||
(Attribute::ObjectClass, EntryClass::Person.as_ref()),
|
||||
(Attribute::ObjectClass, EntryClass::Account.as_ref()),
|
||||
(Attribute::ObjectClass, EntryClass::PosixAccount.as_ref()),
|
||||
(Attribute::DisplayName, "testperson1"),
|
||||
(Attribute::Name, "testperson1"),
|
||||
(Attribute::GidNumber, "12345678"),
|
||||
|
|
|
@ -119,16 +119,22 @@ macro_rules! run_create_test {
|
|||
}};
|
||||
}
|
||||
|
||||
// #[macro_export]
|
||||
#[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 {
|
||||
(
|
||||
// expected outcome
|
||||
$expect:expr,
|
||||
// things to preload
|
||||
$preload_entries:ident,
|
||||
// the targets to modify
|
||||
$modify_filter:expr,
|
||||
// changes to make
|
||||
$modify_list:expr,
|
||||
$internal:expr,
|
||||
// something to run after the preload but before the modification, takes `&mut qs_write`
|
||||
$pre_hook:expr,
|
||||
// the result we expect
|
||||
$check:expr
|
||||
) => {{
|
||||
use crate::be::{Backend, BackendConfig};
|
||||
|
|
|
@ -498,15 +498,12 @@ mod tests {
|
|||
// Test entry in db, and same name, reject.
|
||||
#[test]
|
||||
fn test_pre_create_name_unique() {
|
||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["person"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
}
|
||||
}"#,
|
||||
|
||||
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||
);
|
||||
|
||||
let create = vec![e.clone()];
|
||||
|
@ -526,15 +523,11 @@ mod tests {
|
|||
// Test two entries in create that would have same name, reject.
|
||||
#[test]
|
||||
fn test_pre_create_name_unique_2() {
|
||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["person"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
}
|
||||
}"#,
|
||||
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||
);
|
||||
|
||||
let create = vec![e.clone(), e];
|
||||
|
@ -557,24 +550,15 @@ mod tests {
|
|||
// A mod to something that exists, reject.
|
||||
#[test]
|
||||
fn test_pre_modify_name_unique() {
|
||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_a"],
|
||||
"description": ["testgroup"]
|
||||
}
|
||||
}"#,
|
||||
let ea: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||
);
|
||||
|
||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_b"],
|
||||
"description": ["testgroup"]
|
||||
}
|
||||
}"#,
|
||||
let eb: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||
);
|
||||
|
||||
let preload = vec![ea, eb];
|
||||
|
@ -601,24 +585,15 @@ mod tests {
|
|||
// Two items modded to have the same value, reject.
|
||||
#[test]
|
||||
fn test_pre_modify_name_unique_2() {
|
||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_a"],
|
||||
"description": ["testgroup"]
|
||||
}
|
||||
}"#,
|
||||
let ea: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||
);
|
||||
|
||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_b"],
|
||||
"description": ["testgroup"]
|
||||
}
|
||||
}"#,
|
||||
let eb: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup"))
|
||||
);
|
||||
|
||||
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);
|
||||
// assert the value is greater than the system range.
|
||||
if gid < GID_SYSTEM_NUMBER_MIN {
|
||||
return Err(OperationError::InvalidAttribute(format!(
|
||||
"{} {} may overlap with system range {}",
|
||||
Attribute::GidNumber,
|
||||
gid,
|
||||
GID_SYSTEM_NUMBER_MIN
|
||||
)));
|
||||
admin_error!("Requested GID {} is lower than system minimum {}", gid, GID_SYSTEM_NUMBER_MIN);
|
||||
return Err(OperationError::GidOverlapsSystemMin(GID_SYSTEM_NUMBER_MIN));
|
||||
}
|
||||
|
||||
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) {
|
||||
// If they provided us with a gid number, ensure it's in a safe range.
|
||||
if gid <= GID_SAFETY_NUMBER_MIN {
|
||||
Err(OperationError::InvalidAttribute(format!(
|
||||
"{} {} overlaps into system secure range {}",
|
||||
Attribute::GidNumber,
|
||||
gid,
|
||||
GID_SAFETY_NUMBER_MIN
|
||||
)))
|
||||
admin_error!("Requested GID {} is lower or equal to a safe value {}", gid, GID_SAFETY_NUMBER_MIN);
|
||||
Err(OperationError::GidOverlapsSystemMin(GID_SAFETY_NUMBER_MIN))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -290,9 +282,7 @@ mod tests {
|
|||
let preload = Vec::new();
|
||||
|
||||
run_create_test!(
|
||||
Err(OperationError::InvalidAttribute(
|
||||
"gidnumber 580 may overlap with system range 65536".to_string()
|
||||
)),
|
||||
Err(OperationError::GidOverlapsSystemMin(65536)),
|
||||
preload,
|
||||
create,
|
||||
None,
|
||||
|
@ -315,9 +305,7 @@ mod tests {
|
|||
let preload = Vec::new();
|
||||
|
||||
run_create_test!(
|
||||
Err(OperationError::InvalidAttribute(
|
||||
"gidnumber 500 overlaps into system secure range 1000".to_string()
|
||||
)),
|
||||
Err(OperationError::GidOverlapsSystemMin(1000)),
|
||||
preload,
|
||||
create,
|
||||
None,
|
||||
|
@ -340,9 +328,7 @@ mod tests {
|
|||
let preload = Vec::new();
|
||||
|
||||
run_create_test!(
|
||||
Err(OperationError::InvalidAttribute(
|
||||
"gidnumber 0 overlaps into system secure range 1000".to_string()
|
||||
)),
|
||||
Err(OperationError::GidOverlapsSystemMin(1000)),
|
||||
preload,
|
||||
create,
|
||||
None,
|
||||
|
|
|
@ -456,23 +456,24 @@ mod tests {
|
|||
use crate::prelude::*;
|
||||
use crate::value::{Oauth2Session, Session, SessionState};
|
||||
use time::OffsetDateTime;
|
||||
use uuid::uuid;
|
||||
|
||||
use crate::credential::Credential;
|
||||
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
|
||||
#[test]
|
||||
fn test_create_uuid_reference_not_exist() {
|
||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup"],
|
||||
"description": ["testperson"],
|
||||
"member": ["ca85168c-91b7-49a8-b7bb-a3d5bb40e97e"]
|
||||
}
|
||||
}"#,
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Member,
|
||||
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let create = vec![e];
|
||||
|
@ -491,26 +492,23 @@ mod tests {
|
|||
// The create references a uuid that does exist - validate
|
||||
#[test]
|
||||
fn test_create_uuid_reference_exist() {
|
||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_a"],
|
||||
"description": ["testgroup"],
|
||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
||||
}
|
||||
}"#,
|
||||
let ea = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Uuid,
|
||||
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_b"],
|
||||
"description": ["testgroup"],
|
||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
||||
}
|
||||
}"#,
|
||||
let eb = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Member,
|
||||
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let preload = vec![ea];
|
||||
|
@ -604,7 +602,7 @@ mod tests {
|
|||
)),
|
||||
ModifyList::new_list(vec![Modify::Present(
|
||||
Attribute::Member.into(),
|
||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
||||
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||
)]),
|
||||
None,
|
||||
|_| {},
|
||||
|
@ -638,7 +636,7 @@ mod tests {
|
|||
)),
|
||||
ModifyList::new_list(vec![Modify::Present(
|
||||
Attribute::Member.into(),
|
||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
||||
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||
)]),
|
||||
None,
|
||||
|_| {},
|
||||
|
@ -685,7 +683,7 @@ mod tests {
|
|||
ModifyList::new_list(vec![
|
||||
Modify::Present(
|
||||
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)),
|
||||
]),
|
||||
|
@ -761,7 +759,7 @@ mod tests {
|
|||
)),
|
||||
ModifyList::new_list(vec![Modify::Present(
|
||||
Attribute::Member.into(),
|
||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
||||
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||
)]),
|
||||
None,
|
||||
|_| {},
|
||||
|
@ -806,7 +804,7 @@ mod tests {
|
|||
)),
|
||||
ModifyList::new_list(vec![Modify::Present(
|
||||
Attribute::Member.into(),
|
||||
Value::new_refer_s("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap()
|
||||
Value::new_refer_s(TEST_TESTGROUP_A_UUID).unwrap()
|
||||
)]),
|
||||
None,
|
||||
|qs: &mut QueryServerWriteTransaction| {
|
||||
|
@ -827,26 +825,23 @@ mod tests {
|
|||
// This is the valid case, where the reference is MAY.
|
||||
#[test]
|
||||
fn test_delete_remove_referent_valid() {
|
||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_a"],
|
||||
"description": ["testgroup"],
|
||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
||||
}
|
||||
}"#,
|
||||
let ea: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Uuid,
|
||||
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_b"],
|
||||
"description": ["testgroup"],
|
||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
||||
}
|
||||
}"#,
|
||||
let eb: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Member,
|
||||
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let preload = vec![ea, eb];
|
||||
|
@ -867,31 +862,30 @@ mod tests {
|
|||
//
|
||||
// this is the invalid case, where the reference is MUST.
|
||||
#[test]
|
||||
fn test_delete_remove_referent_invalid() {}
|
||||
fn test_delete_remove_referent_invalid() {
|
||||
// TODO: uh.. wot
|
||||
}
|
||||
|
||||
// Delete of something that holds references.
|
||||
#[test]
|
||||
fn test_delete_remove_referee() {
|
||||
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_a"],
|
||||
"description": ["testgroup"],
|
||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
||||
}
|
||||
}"#,
|
||||
let ea: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_a")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Uuid,
|
||||
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_b"],
|
||||
"description": ["testgroup"],
|
||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
||||
}
|
||||
}"#,
|
||||
let eb: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Member,
|
||||
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let preload = vec![ea, eb];
|
||||
|
@ -911,18 +905,19 @@ mod tests {
|
|||
// Delete something that has a self reference.
|
||||
#[test]
|
||||
fn test_delete_remove_reference_self() {
|
||||
let eb: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["group"],
|
||||
"name": ["testgroup_b"],
|
||||
"description": ["testgroup"],
|
||||
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"],
|
||||
"member": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
|
||||
}
|
||||
}"#,
|
||||
let eb: Entry<EntryInit, EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testgroup_b")),
|
||||
(Attribute::Description, Value::new_utf8s("testgroup")),
|
||||
(
|
||||
Attribute::Uuid,
|
||||
Value::Uuid(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
),
|
||||
(
|
||||
Attribute::Member,
|
||||
Value::Refer(Uuid::parse_str(TEST_TESTGROUP_A_UUID).unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
let preload = vec![eb];
|
||||
|
||||
run_delete_test!(
|
||||
|
@ -964,7 +959,7 @@ mod tests {
|
|||
(
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Value::new_oauthscopemap(
|
||||
uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
|
||||
Uuid::parse_str(TEST_TESTGROUP_B_UUID).unwrap(),
|
||||
btreeset![OAUTH2_SCOPE_READ.to_string()]
|
||||
)
|
||||
.expect("Invalid scope")
|
||||
|
@ -976,7 +971,7 @@ mod tests {
|
|||
(Attribute::Name, Value::new_iname("testgroup")),
|
||||
(
|
||||
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"))
|
||||
);
|
||||
|
@ -1015,7 +1010,7 @@ mod tests {
|
|||
// Create a user
|
||||
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 e1 = entry_init!(
|
||||
|
|
|
@ -225,16 +225,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_spn_generate_create() {
|
||||
// on create don't provide
|
||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
}
|
||||
}"#,
|
||||
// on create don't provide the spn, we generate it.
|
||||
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||
);
|
||||
|
||||
let create = vec![e];
|
||||
|
@ -252,16 +249,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_spn_generate_modify() {
|
||||
// on a purge of the spen, generate it.
|
||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
}
|
||||
}"#,
|
||||
// on a purge of the spn, generate it.
|
||||
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||
);
|
||||
|
||||
let preload = vec![e];
|
||||
|
@ -280,16 +274,14 @@ mod tests {
|
|||
#[test]
|
||||
fn test_spn_validate_create() {
|
||||
// on create providing invalid spn, we over-write it.
|
||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account", "service_account"],
|
||||
"spn": ["testperson@invalid_domain.com"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
}
|
||||
}"#,
|
||||
|
||||
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||
(Attribute::Spn, Value::new_utf8s("testperson@invalid_domain.com")),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||
);
|
||||
|
||||
let create = vec![e];
|
||||
|
@ -307,15 +299,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_spn_validate_modify() {
|
||||
// On modify (removed/present) of the spn, just regenerate it.
|
||||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
}
|
||||
}"#,
|
||||
|
||||
let e: Entry<EntryInit,EntryNew> = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::Description, Value::new_utf8s("testperson")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("testperson"))
|
||||
);
|
||||
|
||||
let preload = vec![e];
|
||||
|
|
|
@ -2903,7 +2903,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
e_service_person.validate(&schema),
|
||||
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}"),
|
||||
_ => unreachable!(),
|
||||
_ => unreachable!("You've specified the wrong type for the attribute, got: {:?}", self),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
//! 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
|
||||
#[allow(unused_macros)]
|
||||
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;
|
||||
}
|
||||
|
||||
#[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 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";
|
||||
|
||||
|
@ -1410,29 +1410,36 @@ async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
|
|||
#[kanidmd_testkit::test]
|
||||
async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
let test_service_account_username = "test_service";
|
||||
|
||||
// Not recommended in production!
|
||||
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
|
||||
.unwrap();
|
||||
|
||||
rsclient
|
||||
.idm_service_account_create("test_service", "Test Service")
|
||||
.idm_service_account_create(test_service_account_username, "Test Service")
|
||||
.await
|
||||
.expect("Failed to create service account");
|
||||
|
||||
let tokens = rsclient
|
||||
.idm_service_account_list_api_token("test_service")
|
||||
.idm_service_account_list_api_token(test_service_account_username)
|
||||
.await
|
||||
.expect("Failed to list service account api tokens");
|
||||
assert!(tokens.is_empty());
|
||||
|
||||
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
|
||||
.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");
|
||||
|
||||
let tokens = rsclient
|
||||
.idm_service_account_list_api_token("test_service")
|
||||
.idm_service_account_list_api_token(test_service_account_username)
|
||||
.await
|
||||
.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");
|
||||
|
||||
let tokens = rsclient
|
||||
.idm_service_account_list_api_token("test_service")
|
||||
.idm_service_account_list_api_token(test_service_account_username)
|
||||
.await
|
||||
.expect("Failed to list service account api tokens");
|
||||
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.
|
||||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
// Not recommended in production!
|
||||
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
|
||||
.unwrap();
|
||||
|
||||
|
@ -1553,7 +1670,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
|||
|
||||
// Since the session is revoked, check with the admin.
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
|
@ -1564,6 +1681,18 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
|||
assert!(tokens.is_empty());
|
||||
|
||||
// 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]
|
||||
|
@ -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 kanidm_client::KanidmClient;
|
||||
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 std::str::FromStr;
|
||||
use url::Url;
|
||||
|
@ -9,7 +9,7 @@ use url::Url;
|
|||
#[kanidmd_testkit::test]
|
||||
async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
|
||||
let a_res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(a_res.is_ok());
|
||||
|
||||
|
|
|
@ -497,6 +497,7 @@ impl ServiceAccountOpt {
|
|||
}
|
||||
}, // end ServiceAccountOpt::Validity
|
||||
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;
|
||||
match client
|
||||
.idm_service_account_into_person(aopt.aopts.account_id.as_str())
|
||||
|
|
|
@ -515,7 +515,7 @@ pub enum ServiceAccountOpt {
|
|||
#[clap(subcommand)]
|
||||
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
|
||||
/// as persons.
|
||||
#[clap(name = "into-person")]
|
||||
|
|
|
@ -23,8 +23,8 @@ use base64urlsafedata::Base64UrlSafeData;
|
|||
use chrono::Utc;
|
||||
use clap::Parser;
|
||||
use cron::Schedule;
|
||||
use kanidm_proto::constants::{LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS};
|
||||
use kanidmd_lib::prelude::Attribute;
|
||||
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_CLASS_GROUPOFNAMES};
|
||||
use kanidmd_lib::prelude::{Attribute, EntryClass};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::metadata;
|
||||
use std::fs::File;
|
||||
|
@ -583,7 +583,7 @@ async fn process_ipa_sync_result(
|
|||
.entry
|
||||
.attrs
|
||||
.get(LDAP_ATTR_OBJECTCLASS)
|
||||
.map(|oc| oc.contains("person"))
|
||||
.map(|oc| oc.contains(EntryClass::Person.as_ref()))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
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.
|
||||
dn.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();
|
||||
|
||||
|
@ -903,7 +903,7 @@ fn ipa_to_scim_entry(
|
|||
}
|
||||
.into(),
|
||||
))
|
||||
} else if oc.contains("groupofnames") {
|
||||
} else if oc.contains(LDAP_CLASS_GROUPOFNAMES) {
|
||||
let LdapSyncReplEntry {
|
||||
entry_uuid,
|
||||
state: _,
|
||||
|
|
|
@ -40,3 +40,6 @@ kanidm_utils_users = { workspace = true }
|
|||
[build-dependencies]
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
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 std::collections::BTreeMap;
|
||||
use url::Url;
|
||||
|
@ -7,15 +8,15 @@ use uuid::Uuid;
|
|||
use ldap3_client::proto::LdapFilter;
|
||||
|
||||
fn person_objectclass() -> String {
|
||||
"person".to_string()
|
||||
EntryClass::Person.to_string()
|
||||
}
|
||||
|
||||
fn person_attr_user_name() -> String {
|
||||
"uid".to_string()
|
||||
ATTR_UID.to_string()
|
||||
}
|
||||
|
||||
fn person_attr_display_name() -> String {
|
||||
"cn".to_string()
|
||||
LDAP_ATTR_CN.to_string()
|
||||
}
|
||||
|
||||
fn person_attr_gidnumber() -> String {
|
||||
|
@ -39,7 +40,7 @@ fn person_attr_ssh_public_key() -> String {
|
|||
}
|
||||
|
||||
fn group_objectclass() -> String {
|
||||
"groupofnames".to_string()
|
||||
LDAP_CLASS_GROUPOFNAMES.to_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) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
error!("Unable to open profile file [{:?}] 🥺", e);
|
||||
error!("Unable to open ldap sync config from '{}' [{:?}] 🥺", &opt.ldap_sync_config.display(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut contents = String::new();
|
||||
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;
|
||||
};
|
||||
|
||||
let sync_config: Config = match toml::from_str(contents.as_str()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("unable to parse config {:?}", e);
|
||||
eprintln!("Unable to parse config from '{}' error: {:?}", &opt.ldap_sync_config.display(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@ -781,3 +781,41 @@ fn main() {
|
|||
|
||||
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;
|
||||
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")]
|
||||
pub struct Opt {
|
||||
/// Enable debugging of the sync driver
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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 ldap3_proto::proto::*;
|
||||
|
@ -59,7 +60,7 @@ impl DirectoryServer {
|
|||
let filter = LdapFilter::Or(
|
||||
targets
|
||||
.iter()
|
||||
.map(|u| LdapFilter::Equality("cn".to_string(), u.to_string()))
|
||||
.map(|u| LdapFilter::Equality(LDAP_ATTR_CN.to_string(), u.to_string()))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
|
@ -86,7 +87,7 @@ impl DirectoryServer {
|
|||
// Check if ou=people and ou=group exist
|
||||
let res = self
|
||||
.ldap
|
||||
.search(LdapFilter::Equality("ou".to_string(), "people".to_string()))
|
||||
.search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(), "people".to_string()))
|
||||
.await?;
|
||||
|
||||
if res.is_empty() {
|
||||
|
@ -96,14 +97,14 @@ impl DirectoryServer {
|
|||
dn: format!("ou=people,{}", self.ldap.basedn),
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"organizationalUnit".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "ou".to_string(),
|
||||
atype: LDAP_ATTR_OU.to_string(),
|
||||
vals: vec!["people".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
|
@ -113,25 +114,25 @@ impl DirectoryServer {
|
|||
|
||||
let res = self
|
||||
.ldap
|
||||
.search(LdapFilter::Equality("ou".to_string(), "groups".to_string()))
|
||||
.search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(),LDAP_ATTR_GROUPS.to_string()))
|
||||
.await?;
|
||||
|
||||
if res.is_empty() {
|
||||
// Doesn't exist
|
||||
info!("Creating ou=groups");
|
||||
let ou_groups = LdapAddRequest {
|
||||
dn: format!("ou=groups,{}", self.ldap.basedn),
|
||||
info!("Creating ou={}", LDAP_ATTR_GROUPS);
|
||||
let ou_groups: LdapAddRequest = LdapAddRequest {
|
||||
dn: format!("ou={},{}", LDAP_ATTR_GROUPS, self.ldap.basedn),
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"organizationalUnit".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "ou".to_string(),
|
||||
vals: vec!["groups".as_bytes().into()],
|
||||
atype: LDAP_ATTR_OU.to_string(),
|
||||
vals: vec![LDAP_ATTR_GROUPS.as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -144,7 +145,7 @@ impl DirectoryServer {
|
|||
// does it already exist?
|
||||
let res = self
|
||||
.ldap
|
||||
.search(LdapFilter::Equality("cn".to_string(), u.to_string()))
|
||||
.search(LdapFilter::Equality(LDAP_ATTR_CN.to_string(), u.to_string()))
|
||||
.await?;
|
||||
|
||||
if !res.is_empty() {
|
||||
|
@ -159,7 +160,7 @@ impl DirectoryServer {
|
|||
dn,
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"nsPerson".as_bytes().into(),
|
||||
|
@ -169,15 +170,15 @@ impl DirectoryServer {
|
|||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
atype: LDAP_ATTR_CN.to_string(),
|
||||
vals: vec![a.uuid.as_bytes().to_vec()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "uid".to_string(),
|
||||
atype: ATTR_UID.to_string(),
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "displayName".to_string(),
|
||||
atype: LDAP_ATTR_DISPLAY_NAME.to_string(),
|
||||
vals: vec![a.display_name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
|
@ -205,14 +206,14 @@ impl DirectoryServer {
|
|||
dn,
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"groupOfNames".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
atype: LDAP_ATTR_CN.to_string(),
|
||||
vals: vec![g.uuid.as_bytes().to_vec(), g.name.as_bytes().into()],
|
||||
},
|
||||
],
|
||||
|
@ -268,7 +269,7 @@ impl DirectoryServer {
|
|||
let res = self
|
||||
.ldap
|
||||
.search(LdapFilter::Equality(
|
||||
"cn".to_string(),
|
||||
LDAP_ATTR_CN.to_string(),
|
||||
"priv_account_manage".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
@ -280,11 +281,11 @@ impl DirectoryServer {
|
|||
dn: format!("cn=priv_account_manage,{}", self.ldap.basedn),
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
atype: LDAP_ATTR_CN.to_string(),
|
||||
vals: vec!["priv_account_manage".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
|
@ -295,7 +296,7 @@ impl DirectoryServer {
|
|||
let res = self
|
||||
.ldap
|
||||
.search(LdapFilter::Equality(
|
||||
"cn".to_string(),
|
||||
LDAP_ATTR_CN.to_string(),
|
||||
"priv_group_manage".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
@ -307,11 +308,11 @@ impl DirectoryServer {
|
|||
dn: format!("cn=priv_group_manage,{}", self.ldap.basedn),
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
atype: LDAP_ATTR_CN.to_string(),
|
||||
vals: vec!["priv_group_manage".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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 std::time::{Duration, Instant};
|
||||
use uuid::Uuid;
|
||||
|
@ -93,7 +94,7 @@ impl IpaServer {
|
|||
dn,
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
atype: LDAP_ATTR_OBJECTCLASS.to_string(),
|
||||
vals: vec![
|
||||
"ipaobject".as_bytes().into(),
|
||||
"person".as_bytes().into(),
|
||||
|
@ -113,11 +114,11 @@ impl IpaServer {
|
|||
vals: vec!["autogenerate".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "uid".to_string(),
|
||||
atype: ATTR_UID.to_string(),
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
atype: LDAP_ATTR_CN.to_string(),
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
|
@ -129,7 +130,7 @@ impl IpaServer {
|
|||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "displayName".to_string(),
|
||||
atype: LDAP_ATTR_DISPLAY_NAME.to_string(),
|
||||
vals: vec![a.display_name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
|
@ -200,7 +201,7 @@ impl IpaServer {
|
|||
atype: "objectClass".to_string(),
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"groupofnames".as_bytes().into(),
|
||||
LDAP_CLASS_GROUPOFNAMES.as_bytes().into(),
|
||||
"nestedgroup".as_bytes().into(),
|
||||
"ipausergroup".as_bytes().into(),
|
||||
"ipaobject".as_bytes().into(),
|
||||
|
|
Loading…
Reference in a new issue