This commit is contained in:
Firstyear 2025-02-26 00:12:25 +00:00 committed by GitHub
commit 8c0de4cd01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 13536 additions and 1436 deletions

2
Cargo.lock generated
View file

@ -3247,7 +3247,6 @@ dependencies = [
"escargot",
"fantoccini",
"futures",
"http 1.2.0",
"jsonschema",
"kanidm_build_profiles",
"kanidm_client",
@ -3255,6 +3254,7 @@ dependencies = [
"kanidm_proto",
"kanidmd_core",
"kanidmd_lib",
"ldap3_client",
"oauth2 4.4.2",
"openssl",
"petgraph",

View file

@ -241,6 +241,7 @@ reqwest = { version = "0.12.12", default-features = false, features = [
"json",
"gzip",
"rustls-tls-native-roots",
"rustls-tls-native-roots-no-provider",
] }
rusqlite = { version = "^0.28.0", features = ["array", "bundled"] }
rustls = { version = "0.23.21", default-features = false, features = [

View file

@ -27,6 +27,7 @@ use std::time::Duration;
use compact_jwt::Jwk;
pub use http;
use kanidm_proto::constants::uri::V1_AUTH_VALID;
use kanidm_proto::constants::{
ATTR_DOMAIN_DISPLAY_NAME, ATTR_DOMAIN_LDAP_BASEDN, ATTR_DOMAIN_SSID, ATTR_ENTRY_MANAGED_BY,
@ -137,6 +138,7 @@ pub struct KanidmClientBuilder {
use_system_proxies: bool,
/// Where to store auth tokens, only use in testing!
token_cache_path: Option<String>,
disable_system_ca_store: bool,
}
impl Display for KanidmClientBuilder {
@ -170,33 +172,6 @@ impl Display for KanidmClientBuilder {
}
}
#[test]
fn test_kanidmclientbuilder_display() {
let defaultclient = KanidmClientBuilder::default();
println!("{}", defaultclient);
assert!(defaultclient.to_string().contains("verify_ca"));
let testclient = KanidmClientBuilder {
address: Some("https://example.com".to_string()),
verify_ca: true,
verify_hostnames: true,
ca: None,
connect_timeout: Some(420),
request_timeout: Some(69),
use_system_proxies: true,
token_cache_path: Some(CLIENT_TOKEN_CACHE.to_string()),
};
println!("testclient {}", testclient);
assert!(testclient.to_string().contains("verify_ca: true"));
assert!(testclient.to_string().contains("verify_hostnames: true"));
let badness = testclient.danger_accept_invalid_hostnames(true);
let badness = badness.danger_accept_invalid_certs(true);
println!("badness: {}", badness);
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,
@ -233,6 +208,7 @@ impl KanidmClientBuilder {
request_timeout: None,
use_system_proxies: true,
token_cache_path: None,
disable_system_ca_store: false,
}
}
@ -290,6 +266,7 @@ impl KanidmClientBuilder {
request_timeout,
use_system_proxies,
token_cache_path,
disable_system_ca_store,
} = self;
// Process and apply all our options if they exist.
let address = match kcc.uri {
@ -316,6 +293,7 @@ impl KanidmClientBuilder {
request_timeout,
use_system_proxies,
token_cache_path,
disable_system_ca_store,
})
}
@ -416,6 +394,16 @@ impl KanidmClientBuilder {
}
}
/// Enable or disable the native ca roots. By default these roots are enabled.
pub fn enable_native_ca_roots(self, enable: bool) -> Self {
KanidmClientBuilder {
// We have to flip the bool state here due to Default on bool being false
// and we want our options to be positive to a native speaker.
disable_system_ca_store: !enable,
..self
}
}
pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostnames: bool) -> Self {
KanidmClientBuilder {
// We have to flip the bool state here due to english language.
@ -527,6 +515,7 @@ impl KanidmClientBuilder {
// implement sticky sessions with cookies.
.cookie_store(true)
.cookie_provider(client_cookies.clone())
.tls_built_in_native_certs(!self.disable_system_ca_store)
.danger_accept_invalid_hostnames(!self.verify_hostnames)
.danger_accept_invalid_certs(!self.verify_ca);
@ -579,32 +568,6 @@ impl KanidmClientBuilder {
}
}
#[test]
fn test_make_url() {
use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}", DEFAULT_SERVER_ADDRESS))
.build()
.unwrap();
assert_eq!(
client.get_url(),
Url::parse(&format!("https://{}", DEFAULT_SERVER_ADDRESS)).unwrap()
);
assert_eq!(
client.make_url("/hello"),
Url::parse(&format!("https://{}/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}/cheese/", DEFAULT_SERVER_ADDRESS))
.build()
.unwrap();
assert_eq!(
client.make_url("hello"),
Url::parse(&format!("https://{}/cheese/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
}
/// This is probably pretty jank but it works and was pulled from here:
/// <https://github.com/seanmonstar/reqwest/issues/1602#issuecomment-1220996681>
fn find_reqwest_error_source<E: std::error::Error + 'static>(
@ -623,6 +586,11 @@ fn find_reqwest_error_source<E: std::error::Error + 'static>(
}
impl KanidmClient {
/// Access the underlying reqwest client that has been configured for this Kanidm server
pub fn client(&self) -> &reqwest::Client {
&self.client
}
pub fn get_origin(&self) -> &Url {
&self.origin
}
@ -2174,31 +2142,97 @@ impl KanidmClient {
}
}
#[tokio::test]
async fn test_no_client_version_check_on_502() {
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::GATEWAY_TIMEOUT)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 504 and shouldn't check version...");
client.expect_version(&res).await;
#[cfg(test)]
mod tests {
use super::{KanidmClient, KanidmClientBuilder};
use kanidm_proto::constants::CLIENT_TOKEN_CACHE;
use reqwest::StatusCode;
use url::Url;
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::BAD_GATEWAY)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 502 and shouldn't check version...");
client.expect_version(&res).await;
#[tokio::test]
async fn test_no_client_version_check_on_502() {
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::GATEWAY_TIMEOUT)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.enable_native_ca_roots(false)
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 504 and shouldn't check version...");
client.expect_version(&res).await;
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::BAD_GATEWAY)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.enable_native_ca_roots(false)
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 502 and shouldn't check version...");
client.expect_version(&res).await;
}
#[test]
fn test_make_url() {
use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}", DEFAULT_SERVER_ADDRESS))
.enable_native_ca_roots(false)
.build()
.unwrap();
assert_eq!(
client.get_url(),
Url::parse(&format!("https://{}", DEFAULT_SERVER_ADDRESS)).unwrap()
);
assert_eq!(
client.make_url("/hello"),
Url::parse(&format!("https://{}/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}/cheese/", DEFAULT_SERVER_ADDRESS))
.enable_native_ca_roots(false)
.build()
.unwrap();
assert_eq!(
client.make_url("hello"),
Url::parse(&format!("https://{}/cheese/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
}
#[test]
fn test_kanidmclientbuilder_display() {
let defaultclient = KanidmClientBuilder::default();
println!("{}", defaultclient);
assert!(defaultclient.to_string().contains("verify_ca"));
let testclient = KanidmClientBuilder {
address: Some("https://example.com".to_string()),
verify_ca: true,
verify_hostnames: true,
ca: None,
connect_timeout: Some(420),
request_timeout: Some(69),
use_system_proxies: true,
token_cache_path: Some(CLIENT_TOKEN_CACHE.to_string()),
disable_system_ca_store: false,
};
println!("testclient {}", testclient);
assert!(testclient.to_string().contains("verify_ca: true"));
assert!(testclient.to_string().contains("verify_hostnames: true"));
let badness = testclient.danger_accept_invalid_hostnames(true);
let badness = badness.danger_accept_invalid_certs(true);
println!("badness: {}", badness);
assert!(badness.to_string().contains("verify_ca: false"));
assert!(badness.to_string().contains("verify_hostnames: false"));
}
}

View file

@ -222,6 +222,8 @@ pub enum OperationError {
MG0006SKConstraintsNotMet,
MG0007Oauth2StrictConstraintsNotMet,
MG0008SkipUpgradeAttempted,
MG0009InvalidTargetLevelForBootstrap,
MG0010MigrationDataMissingUuid,
//
KP0001KeyProviderNotLoaded,
KP0002KeyProviderInvalidClass,
@ -462,6 +464,8 @@ impl OperationError {
Self::MG0006SKConstraintsNotMet => Some("Migration Constraints Not Met - Security Keys should not be present.".into()),
Self::MG0007Oauth2StrictConstraintsNotMet => Some("Migration Constraints Not Met - All OAuth2 clients must have strict-redirect-uri mode enabled.".into()),
Self::MG0008SkipUpgradeAttempted => Some("Skip Upgrade Attempted.".into()),
Self::MG0009InvalidTargetLevelForBootstrap => Some("The request target domain level was not valid for bootstrapping a new server instance".into()),
Self::MG0010MigrationDataMissingUuid => Some("A migration entry was found to be invalid and missing a uuid.".into()),
Self::PL0001GidOverlapsSystemRange => None,
Self::SC0001IncomingSshPublicKey => None,
Self::SC0002ReferenceSyntaxInvalid => Some("A SCIM Reference Set contained invalid syntax and can not be processed.".into()),

View file

@ -1,8 +1,5 @@
use std::net;
use std::pin::Pin;
use std::str::FromStr;
use crate::actors::QueryServerReadV1;
use crate::CoreAction;
use futures_util::sink::SinkExt;
use futures_util::stream::StreamExt;
use kanidmd_lib::idm::ldap::{LdapBoundToken, LdapResponseState};
@ -10,13 +7,15 @@ use kanidmd_lib::prelude::*;
use ldap3_proto::proto::LdapMsg;
use ldap3_proto::LdapCodec;
use openssl::ssl::{Ssl, SslAcceptor};
use std::net;
use std::pin::Pin;
use std::str::FromStr;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::{TcpListener, TcpStream};
use tokio_openssl::SslStream;
use tokio_util::codec::{FramedRead, FramedWrite};
use crate::CoreAction;
use tokio::sync::broadcast;
use tokio::sync::mpsc;
use tokio_openssl::SslStream;
use tokio_util::codec::{FramedRead, FramedWrite};
struct LdapSession {
uat: Option<LdapBoundToken>,
@ -49,28 +48,14 @@ async fn client_process_msg(
.await
}
async fn client_process(
tcpstream: TcpStream,
tls_acceptor: SslAcceptor,
async fn client_process<STREAM>(
stream: STREAM,
client_address: net::SocketAddr,
qe_r_ref: &'static QueryServerReadV1,
) {
// Start the event
// From the parameters we need to create an SslContext.
let mut tlsstream = match Ssl::new(tls_acceptor.context())
.and_then(|tls_obj| SslStream::new(tls_obj, tcpstream))
{
Ok(ta) => ta,
Err(e) => {
error!("LDAP TLS setup error, continuing -> {:?}", e);
return;
}
};
if let Err(e) = SslStream::accept(Pin::new(&mut tlsstream)).await {
error!("LDAP TLS accept error, continuing -> {:?}", e);
return;
};
let (r, w) = tokio::io::split(tlsstream);
) where
STREAM: AsyncRead + AsyncWrite,
{
let (r, w) = tokio::io::split(stream);
let mut r = FramedRead::new(r, LdapCodec::default());
let mut w = FramedWrite::new(w, LdapCodec::default());
@ -126,7 +111,32 @@ async fn client_process(
}
}
/// TLS LDAP Listener, hands off to [client_process]
async fn client_tls_accept(
tcpstream: TcpStream,
tls_acceptor: SslAcceptor,
client_socket_addr: net::SocketAddr,
qe_r_ref: &'static QueryServerReadV1,
) {
// Start the event
// From the parameters we need to create an SslContext.
let mut tlsstream = match Ssl::new(tls_acceptor.context())
.and_then(|tls_obj| SslStream::new(tls_obj, tcpstream))
{
Ok(ta) => ta,
Err(err) => {
error!(?err, %client_socket_addr, "LDAP TLS setup error");
return;
}
};
if let Err(err) = SslStream::accept(Pin::new(&mut tlsstream)).await {
error!(?err, %client_socket_addr, "LDAP TLS accept error");
return;
};
tokio::spawn(client_process(tlsstream, client_socket_addr, qe_r_ref));
}
/// TLS LDAP Listener, hands off to [client_tls_accept]
async fn ldap_tls_acceptor(
listener: TcpListener,
mut tls_acceptor: SslAcceptor,
@ -145,10 +155,10 @@ async fn ldap_tls_acceptor(
match accept_result {
Ok((tcpstream, client_socket_addr)) => {
let clone_tls_acceptor = tls_acceptor.clone();
tokio::spawn(client_process(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
tokio::spawn(client_tls_accept(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
}
Err(e) => {
error!("LDAP acceptor error, continuing -> {:?}", e);
Err(err) => {
warn!(?err, "LDAP acceptor error, continuing");
}
}
}
@ -161,6 +171,34 @@ async fn ldap_tls_acceptor(
info!("Stopped {}", super::TaskName::LdapActor);
}
/// TLS LDAP Listener, hands off to [client_process]
async fn ldap_plaintext_acceptor(
listener: TcpListener,
qe_r_ref: &'static QueryServerReadV1,
mut rx: broadcast::Receiver<CoreAction>,
) {
loop {
tokio::select! {
Ok(action) = rx.recv() => {
match action {
CoreAction::Shutdown => break,
}
}
accept_result = listener.accept() => {
match accept_result {
Ok((tcpstream, client_socket_addr)) => {
tokio::spawn(client_process(tcpstream, client_socket_addr, qe_r_ref));
}
Err(e) => {
error!("LDAP acceptor error, continuing -> {:?}", e);
}
}
}
}
}
info!("Stopped {}", super::TaskName::LdapActor);
}
pub(crate) async fn create_ldap_server(
address: &str,
opt_ssl_acceptor: Option<SslAcceptor>,
@ -197,10 +235,7 @@ pub(crate) async fn create_ldap_server(
tls_acceptor_reload_rx,
))
}
None => {
error!("The server won't run without TLS!");
return Err(());
}
None => tokio::spawn(ldap_plaintext_acceptor(listener, qe_r_ref, rx)),
};
info!("Created LDAP interface");

View file

@ -115,9 +115,9 @@ async fn setup_qs_idms(
.await?;
// We generate a SINGLE idms only!
let is_integration_test = config.integration_test_config.is_some();
let (idms, idms_delayed, idms_audit) =
IdmServer::new(query_server.clone(), &config.origin).await?;
IdmServer::new(query_server.clone(), &config.origin, is_integration_test).await?;
Ok((query_server, idms, idms_delayed, idms_audit))
}
@ -1080,20 +1080,15 @@ pub async fn create_server_core(
Some(la) => {
let opt_ldap_ssl_acceptor = maybe_tls_acceptor.clone();
if !config_test {
// ⚠️ only start the sockets and listeners in non-config-test modes.
let h = ldaps::create_ldap_server(
la.as_str(),
opt_ldap_ssl_acceptor,
server_read_ref,
broadcast_tx.subscribe(),
ldap_tls_acceptor_reload_rx,
)
.await?;
Some(h)
} else {
None
}
let h = ldaps::create_ldap_server(
la.as_str(),
opt_ldap_ssl_acceptor,
server_read_ref,
broadcast_tx.subscribe(),
ldap_tls_acceptor_reload_rx,
)
.await?;
Some(h)
}
None => {
debug!("LDAP not requested, skipping");

View file

@ -16,7 +16,7 @@ repository = { workspace = true }
[[bin]]
name = "kanidmd"
path = "src/main.rs"
test = true
test = false
doctest = false
[features]

View file

@ -13,7 +13,7 @@ repository = { workspace = true }
[lib]
proc-macro = true
test = true
test = false
doctest = false
[dependencies]

View file

@ -34,7 +34,7 @@ fn parse_attributes(
});
if !args_are_allowed {
let msg = "Invalid test config attribute. The following are allow";
let msg = "Invalid test config attribute. The following are allowed";
return Err(syn::Error::new_spanned(
input.sig.fn_token,
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),

View file

@ -18,7 +18,8 @@ use crate::be::idl_sqlite::{
IdlSqlite, IdlSqliteReadTransaction, IdlSqliteTransaction, IdlSqliteWriteTransaction,
};
use crate::be::idxkey::{
IdlCacheKey, IdlCacheKeyRef, IdlCacheKeyToRef, IdxKey, IdxKeyRef, IdxKeyToRef, IdxSlope,
IdlCacheKey, IdlCacheKeyRef, IdlCacheKeyToRef, IdxKey, IdxKeyRef, IdxKeyToRef, IdxNameKey,
IdxSlope,
};
use crate::be::keystorage::{KeyHandle, KeyHandleId};
use crate::be::{BackendConfig, IdList, IdRawEntry};
@ -35,6 +36,10 @@ const DEFAULT_NAME_CACHE_RATIO: usize = 8;
const DEFAULT_CACHE_RMISS: usize = 0;
const DEFAULT_CACHE_WMISS: usize = 0;
const DEFAULT_IDX_CACHE_RMISS: usize = 8;
const DEFAULT_IDX_CACHE_WMISS: usize = 16;
const DEFAULT_IDX_EXISTS_TARGET: usize = 256;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
enum NameCacheKey {
Name2Uuid(String),
@ -55,6 +60,9 @@ pub struct IdlArcSqlite {
entry_cache: ARCache<u64, Arc<EntrySealedCommitted>>,
idl_cache: ARCache<IdlCacheKey, Box<IDLBitRange>>,
name_cache: ARCache<NameCacheKey, NameCacheValue>,
idx_exists_cache: ARCache<IdxNameKey, bool>,
op_ts_max: CowCell<Option<Duration>>,
allids: CowCell<IDLBitRange>,
maxid: CowCell<u64>,
@ -66,6 +74,8 @@ pub struct IdlArcSqliteReadTransaction<'a> {
entry_cache: ARCacheReadTxn<'a, u64, Arc<EntrySealedCommitted>, ()>,
idl_cache: ARCacheReadTxn<'a, IdlCacheKey, Box<IDLBitRange>, ()>,
name_cache: ARCacheReadTxn<'a, NameCacheKey, NameCacheValue, ()>,
idx_exists_cache: ARCacheReadTxn<'a, IdxNameKey, bool, ()>,
allids: CowCellReadTxn<IDLBitRange>,
}
@ -74,6 +84,9 @@ pub struct IdlArcSqliteWriteTransaction<'a> {
entry_cache: ARCacheWriteTxn<'a, u64, Arc<EntrySealedCommitted>, ()>,
idl_cache: ARCacheWriteTxn<'a, IdlCacheKey, Box<IDLBitRange>, ()>,
name_cache: ARCacheWriteTxn<'a, NameCacheKey, NameCacheValue, ()>,
idx_exists_cache: ARCacheWriteTxn<'a, IdxNameKey, bool, ()>,
op_ts_max: CowCellWriteTxn<'a, Option<Duration>>,
allids: CowCellWriteTxn<'a, IDLBitRange>,
maxid: CowCellWriteTxn<'a, u64>,
@ -178,8 +191,8 @@ macro_rules! get_idl {
// or smaller type. Perhaps even a small cache of the IdlCacheKeys that
// are allocated to reduce some allocs? Probably over thinking it at
// this point.
//
// First attempt to get from this cache.
// Now attempt to get from this cache.
let cache_key = IdlCacheKeyRef {
a: $attr,
i: $itype,
@ -195,16 +208,47 @@ macro_rules! get_idl {
);
return Ok(Some(data.as_ref().clone()));
}
// If it was a miss, does the actually exist in the DB?
let idx_key = IdxNameKey {
a: $attr.clone(),
i: $itype,
};
let idx_r = $self.idx_exists_cache.get(&idx_key);
if idx_r == Some(&false) {
// The idx does not exist - bail early.
return Ok(None)
}
// The table either exists and we don't have data on it yet,
// or it does not exist and we need to hear back from the lower level
// If miss, get from db *and* insert to the cache.
let db_r = $self.db.get_idl($attr, $itype, $idx_key)?;
if let Some(ref idl) = db_r {
if idx_r == None {
// It exists, so track that data, because we weren't
// previously tracking it.
$self.idx_exists_cache.insert(idx_key, true)
}
let ncache_key = IdlCacheKey {
a: $attr.clone(),
i: $itype.clone(),
k: $idx_key.into(),
};
$self.idl_cache.insert(ncache_key, Box::new(idl.clone()))
}
} else {
// The DB was unable to return this idx because table backing the
// idx does not exist. We should cache this to prevent repeat hits
// on sqlite until the db does exist, at which point the cache is
// cleared anyway.
//
// NOTE: If the db idx misses it returns Some(empty_set), so this
// only caches missing index tables.
$self.idx_exists_cache.insert(idx_key, false)
};
Ok(db_r)
}};
}
@ -593,6 +637,7 @@ impl IdlArcSqliteWriteTransaction<'_> {
*/
self.entry_cache.clear();
self.idl_cache.clear();
self.idx_exists_cache.clear();
self.name_cache.clear();
Ok(())
}
@ -604,6 +649,7 @@ impl IdlArcSqliteWriteTransaction<'_> {
mut entry_cache,
mut idl_cache,
mut name_cache,
idx_exists_cache,
op_ts_max,
allids,
maxid,
@ -677,6 +723,7 @@ impl IdlArcSqliteWriteTransaction<'_> {
// Can no longer fail from this point.
op_ts_max.commit();
name_cache.commit();
idx_exists_cache.commit();
idl_cache.commit();
allids.commit();
maxid.commit();
@ -708,6 +755,7 @@ impl IdlArcSqliteWriteTransaction<'_> {
*self.maxid = mid;
}
#[instrument(level = "trace", skip_all)]
pub fn write_identries<'b, I>(&'b mut self, mut entries: I) -> Result<(), OperationError>
where
I: Iterator<Item = &'b Entry<EntrySealed, EntryCommitted>>,
@ -757,6 +805,7 @@ impl IdlArcSqliteWriteTransaction<'_> {
})
}
#[instrument(level = "trace", skip_all)]
pub fn write_idl(
&mut self,
attr: &Attribute,
@ -1127,9 +1176,17 @@ impl IdlArcSqliteWriteTransaction<'_> {
Ok(())
}
pub fn create_idx(&self, attr: &Attribute, itype: IndexType) -> Result<(), OperationError> {
// We don't need to affect this, so pass it down.
self.db.create_idx(attr, itype)
pub fn create_idx(&mut self, attr: &Attribute, itype: IndexType) -> Result<(), OperationError> {
self.db.create_idx(attr, itype)?;
// Cache that this exists since we just made it.
let idx_key = IdxNameKey {
a: attr.clone(),
i: itype,
};
self.idx_exists_cache.insert(idx_key, true);
Ok(())
}
/// ⚠️ - This function will destroy all indexes in the database.
@ -1141,6 +1198,7 @@ impl IdlArcSqliteWriteTransaction<'_> {
debug!("CLEARING CACHE");
self.db.danger_purge_idxs().map(|()| {
self.idl_cache.clear();
self.idx_exists_cache.clear();
self.name_cache.clear();
})
}
@ -1266,6 +1324,21 @@ impl IdlArcSqlite {
OperationError::InvalidState
})?;
let idx_exists_cache = ARCacheBuilder::new()
.set_expected_workload(
DEFAULT_IDX_EXISTS_TARGET,
cfg.pool_size as usize,
DEFAULT_IDX_CACHE_RMISS,
DEFAULT_IDX_CACHE_WMISS,
true,
)
.set_reader_quiesce(true)
.build()
.ok_or_else(|| {
admin_error!("Failed to construct idx_exists_cache");
OperationError::InvalidState
})?;
let allids = CowCell::new(IDLBitRange::new());
let maxid = CowCell::new(0);
@ -1279,6 +1352,7 @@ impl IdlArcSqlite {
entry_cache,
idl_cache,
name_cache,
idx_exists_cache,
op_ts_max,
allids,
maxid,
@ -1298,6 +1372,7 @@ impl IdlArcSqlite {
let db_read = self.db.read()?;
let idl_cache_read = self.idl_cache.read();
let name_cache_read = self.name_cache.read();
let idx_exists_cache_read = self.idx_exists_cache.read();
let allids_read = self.allids.read();
Ok(IdlArcSqliteReadTransaction {
@ -1305,6 +1380,7 @@ impl IdlArcSqlite {
entry_cache: entry_cache_read,
idl_cache: idl_cache_read,
name_cache: name_cache_read,
idx_exists_cache: idx_exists_cache_read,
allids: allids_read,
})
}
@ -1315,6 +1391,7 @@ impl IdlArcSqlite {
let db_write = self.db.write()?;
let idl_cache_write = self.idl_cache.write();
let name_cache_write = self.name_cache.write();
let idx_exists_cache_write = self.idx_exists_cache.write();
let op_ts_max_write = self.op_ts_max.write();
let allids_write = self.allids.write();
let maxid_write = self.maxid.write();
@ -1325,6 +1402,7 @@ impl IdlArcSqlite {
entry_cache: entry_cache_write,
idl_cache: idl_cache_write,
name_cache: name_cache_write,
idx_exists_cache: idx_exists_cache_write,
op_ts_max: op_ts_max_write,
allids: allids_write,
maxid: maxid_write,

View file

@ -205,12 +205,15 @@ pub(crate) trait IdlSqliteTransaction {
let mut stmt = self
.get_conn()?
.prepare(&format!(
"SELECT COUNT(name) from {}.sqlite_master where name = :tname",
"SELECT rowid from {}.sqlite_master where name = :tname LIMIT 1",
self.get_db_name()
))
.map_err(sqlite_error)?;
let i: Option<i64> = stmt
.query_row(&[(":tname", tname)], |row| row.get(0))
// If the row doesn't exist, we don't mind.
.optional()
.map_err(sqlite_error)?;
match i {

View file

@ -155,3 +155,9 @@ impl Ord for (dyn IdlCacheKeyToRef + '_) {
self.keyref().cmp(&other.keyref())
}
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct IdxNameKey {
pub a: Attribute,
pub i: IndexType,
}

View file

@ -1,20 +1,12 @@
//! Constant Entries for the IDM
use std::fmt::Display;
use crate::constants::groups::idm_builtin_admin_groups;
use crate::constants::uuids::*;
use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
use crate::idm::account::Account;
use crate::value::PartialValue;
use crate::value::Value;
use crate::valueset::{ValueSet, ValueSetIutf8};
pub use kanidm_proto::attribute::Attribute;
use kanidm_proto::constants::*;
use kanidm_proto::internal::OperationError;
use kanidm_proto::scim_v1::JsonValue;
use kanidm_proto::v1::AccountType;
use uuid::Uuid;
//TODO: This would do well in the proto lib
// together with all the other definitions.
@ -202,158 +194,9 @@ impl EntryClass {
}
}
lazy_static! {
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_IDM_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "idm_admin",
uuid: UUID_IDM_ADMIN,
description: "Builtin IDM Admin account.",
displayname: "IDM Administrator",
};
pub static ref E_SYSTEM_INFO_V1: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::SystemInfo.to_value()),
(Attribute::Class, EntryClass::System.to_value()),
(Attribute::Uuid, Value::Uuid(UUID_SYSTEM_INFO)),
(
Attribute::Description,
Value::new_utf8s("System (local) info and metadata object.")
),
(Attribute::Version, Value::Uint32(20))
);
pub static ref E_DOMAIN_INFO_DL6: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::DomainInfo.to_value()),
(Attribute::Class, EntryClass::System.to_value()),
(Attribute::Class, EntryClass::KeyObject.to_value()),
(Attribute::Class, EntryClass::KeyObjectJwtEs256.to_value()),
(Attribute::Class, EntryClass::KeyObjectJweA128GCM.to_value()),
(Attribute::Name, Value::new_iname("domain_local")),
(Attribute::Uuid, Value::Uuid(UUID_DOMAIN_INFO)),
(
Attribute::Description,
Value::new_utf8s("This local domain's info and metadata object.")
)
);
}
#[derive(Debug, Clone)]
/// Built in accounts such as anonymous, idm_admin and admin
pub struct BuiltinAccount {
pub account_type: kanidm_proto::v1::AccountType,
pub entry_managed_by: Option<uuid::Uuid>,
pub name: &'static str,
pub uuid: Uuid,
pub description: &'static str,
pub displayname: &'static str,
}
impl Default for BuiltinAccount {
fn default() -> Self {
BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "",
uuid: Uuid::new_v4(),
description: "<set description>",
displayname: "<set displayname>",
}
}
}
impl From<BuiltinAccount> for Account {
fn from(value: BuiltinAccount) -> Self {
#[allow(clippy::panic)]
if value.uuid >= DYNAMIC_RANGE_MINIMUM_UUID {
panic!("Builtin ACP has invalid UUID! {:?}", value);
}
Account {
name: value.name.to_string(),
uuid: value.uuid,
displayname: value.displayname.to_string(),
spn: format!("{}@example.com", value.name),
mail_primary: None,
mail: Vec::with_capacity(0),
..Default::default()
}
}
}
impl From<BuiltinAccount> for EntryInitNew {
fn from(value: BuiltinAccount) -> Self {
let mut entry = EntryInitNew::new();
entry.add_ava(Attribute::Name, Value::new_iname(value.name));
#[allow(clippy::panic)]
if value.uuid >= DYNAMIC_RANGE_MINIMUM_UUID {
panic!("Builtin ACP has invalid UUID! {:?}", value);
}
entry.add_ava(Attribute::Uuid, Value::Uuid(value.uuid));
entry.add_ava(Attribute::Description, Value::new_utf8s(value.description));
entry.add_ava(Attribute::DisplayName, Value::new_utf8s(value.displayname));
if let Some(entry_manager) = value.entry_managed_by {
entry.add_ava(Attribute::EntryManagedBy, Value::Refer(entry_manager));
}
entry.set_ava(
Attribute::Class,
vec![
EntryClass::Account.to_value(),
EntryClass::MemberOf.to_value(),
EntryClass::Object.to_value(),
],
);
match value.account_type {
AccountType::Person => entry.add_ava(Attribute::Class, EntryClass::Person.to_value()),
AccountType::ServiceAccount => {
entry.add_ava(Attribute::Class, EntryClass::ServiceAccount.to_value())
}
}
entry
}
}
lazy_static! {
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "admin",
uuid: UUID_ADMIN,
description: "Builtin System Admin account.",
displayname: "System Administrator",
};
}
lazy_static! {
pub static ref BUILTIN_ACCOUNT_ANONYMOUS_DL6: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: Some(UUID_IDM_ADMINS),
name: "anonymous",
uuid: UUID_ANONYMOUS,
description: "Anonymous access account.",
displayname: "Anonymous",
};
}
pub fn builtin_accounts() -> Vec<&'static BuiltinAccount> {
vec![
&BUILTIN_ACCOUNT_ADMIN,
&BUILTIN_ACCOUNT_IDM_ADMIN,
&BUILTIN_ACCOUNT_ANONYMOUS_DL6,
]
}
// ============ TEST DATA ============
#[cfg(test)]
pub const UUID_TESTPERSON_1: Uuid = ::uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
#[cfg(test)]
pub const UUID_TESTPERSON_2: Uuid = ::uuid::uuid!("538faac7-4d29-473b-a59d-23023ac19955");
use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
#[cfg(test)]
lazy_static! {
@ -363,7 +206,10 @@ lazy_static! {
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson1")),
(Attribute::DisplayName, Value::new_utf8s("Test Person 1")),
(Attribute::Uuid, Value::Uuid(UUID_TESTPERSON_1))
(
Attribute::Uuid,
Value::Uuid(super::uuids::UUID_TESTPERSON_1)
)
);
pub static ref E_TESTPERSON_2: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
@ -371,28 +217,9 @@ lazy_static! {
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson2")),
(Attribute::DisplayName, Value::new_utf8s("Test Person 2")),
(Attribute::Uuid, Value::Uuid(UUID_TESTPERSON_2))
(
Attribute::Uuid,
Value::Uuid(super::uuids::UUID_TESTPERSON_2)
)
);
}
// ⚠️ DOMAIN LEVEL 1 ENTRIES ⚠️
// Future entries need to be added via migrations.
//
// DO NOT MODIFY THIS DEFINITION
/// Build a list of internal admin entries
pub fn idm_builtin_admin_entries() -> Result<Vec<EntryInitNew>, OperationError> {
let mut res: Vec<EntryInitNew> = vec![
BUILTIN_ACCOUNT_ADMIN.clone().into(),
BUILTIN_ACCOUNT_IDM_ADMIN.clone().into(),
];
for group in idm_builtin_admin_groups() {
let g: EntryInitNew = group.clone().try_into()?;
res.push(g);
}
// We need to push anonymous *after* groups due to entry-managed-by
res.push(BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into());
Ok(res)
}

View file

@ -1,20 +1,10 @@
// Re-export as needed
pub mod acp;
pub mod entries;
pub mod groups;
mod key_providers;
pub mod schema;
pub mod system_config;
pub mod uuids;
pub mod values;
pub use self::acp::*;
pub use self::entries::*;
pub use self::groups::*;
pub use self::key_providers::*;
pub use self::schema::*;
pub use self::system_config::*;
pub use self::uuids::*;
pub use self::values::*;

View file

@ -6,6 +6,7 @@ use uuid::{uuid, Uuid};
pub const STR_UUID_ADMIN: &str = "00000000-0000-0000-0000-000000000000";
pub const UUID_ADMIN: Uuid = uuid!("00000000-0000-0000-0000-000000000000");
pub const UUID_IDM_ADMINS: Uuid = uuid!("00000000-0000-0000-0000-000000000001");
pub const NAME_IDM_ADMINS: &str = "idm_admins";
pub const UUID_IDM_PEOPLE_PII_READ: Uuid = uuid!("00000000-0000-0000-0000-000000000002");
pub const UUID_IDM_PEOPLE_WRITE_PRIV: Uuid = uuid!("00000000-0000-0000-0000-000000000003");
pub const UUID_IDM_GROUP_WRITE_PRIV: Uuid = uuid!("00000000-0000-0000-0000-000000000004");
@ -26,6 +27,8 @@ pub const UUID_IDM_ADMIN: Uuid = uuid!("00000000-0000-0000-0000-000000000018");
pub const STR_UUID_SYSTEM_ADMINS: &str = "00000000-0000-0000-0000-000000000019";
pub const UUID_SYSTEM_ADMINS: Uuid = uuid!("00000000-0000-0000-0000-000000000019");
pub const NAME_SYSTEM_ADMINS: &str = "system_admins";
pub const UUID_DOMAIN_ADMINS: Uuid = uuid!("00000000-0000-0000-0000-000000000020");
pub const UUID_IDM_ACCOUNT_UNIX_EXTEND_PRIV: Uuid = uuid!("00000000-0000-0000-0000-000000000021");
pub const UUID_IDM_GROUP_UNIX_EXTEND_PRIV: Uuid = uuid!("00000000-0000-0000-0000-000000000022");
@ -50,6 +53,7 @@ pub const UUID_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV: Uuid =
pub const UUID_IDM_ALL_PERSONS: Uuid = uuid!("00000000-0000-0000-0000-000000000035");
pub const STR_UUID_IDM_ALL_ACCOUNTS: &str = "00000000-0000-0000-0000-000000000036";
pub const UUID_IDM_ALL_ACCOUNTS: Uuid = uuid!("00000000-0000-0000-0000-000000000036");
pub const NAME_IDM_ALL_ACCOUNTS: &str = "idm_all_accounts";
pub const UUID_IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV: Uuid =
uuid!("00000000-0000-0000-0000-000000000037");
@ -452,3 +456,9 @@ pub const UUID_DOES_NOT_EXIST: Uuid = uuid!("00000000-0000-0000-0000-fffffffffff
pub const UUID_ANONYMOUS: Uuid = uuid!("00000000-0000-0000-0000-ffffffffffff");
pub const DYNAMIC_RANGE_MINIMUM_UUID: Uuid = uuid!("00000000-0000-0000-0001-000000000000");
// ======= test data ======
#[cfg(test)]
pub const UUID_TESTPERSON_1: Uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
#[cfg(test)]
pub const UUID_TESTPERSON_2: Uuid = uuid!("538faac7-4d29-473b-a59d-23023ac19955");

View file

@ -636,23 +636,6 @@ impl ModifyEvent {
}
}
/// ⚠️ - Bypass the schema state machine and force the filter to be considered valid.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub fn new_impersonate_entry_ser(
e: BuiltinAccount,
filter: Filter<FilterInvalid>,
modlist: ModifyList<ModifyInvalid>,
) -> Self {
let ei: EntryInitNew = e.into();
ModifyEvent {
ident: Identity::from_impersonate_entry_readwrite(Arc::new(ei.into_sealed_committed())),
filter: filter.clone().into_valid(),
filter_orig: filter.into_valid(),
modlist: modlist.into_valid(),
}
}
/// ⚠️ - Bypass the schema state machine and force the filter to be considered valid.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]

View file

@ -1041,18 +1041,10 @@ impl IdmServerProxyReadTransaction<'_> {
#[cfg(test)]
mod tests {
use crate::idm::account::Account;
use crate::idm::accountpolicy::ResolvedAccountPolicy;
use crate::prelude::*;
use kanidm_proto::internal::UiHint;
#[test]
fn test_idm_account_from_anonymous() {
let account: Account = BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into();
debug!("{:?}", account);
// I think that's it? we may want to check anonymous mech ...
}
#[idm_test]
async fn test_idm_account_ui_hints(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
let ct = duration_from_epoch_now();

View file

@ -1716,6 +1716,7 @@ mod tests {
};
use crate::idm::delayed::DelayedAction;
use crate::idm::AuthState;
use crate::migration_data::{BUILTIN_ACCOUNT_ANONYMOUS, BUILTIN_ACCOUNT_TEST_PERSON};
use crate::prelude::*;
use crate::server::keys::KeyObjectInternal;
use crate::utils::readable_password_from_random;
@ -1742,7 +1743,7 @@ mod tests {
let webauthn = create_webauthn();
let anon_account: Account = BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into();
let anon_account: Account = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
let asd = AuthSessionData {
account: anon_account,
@ -1819,7 +1820,7 @@ mod tests {
fn start_session_simple_password_mech(privileged: bool) -> UserAuthToken {
let webauthn = create_webauthn();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
// manually load in a cred
let p = CryptoPolicy::minimum();
let cred = Credential::new_password_only(&p, "test_password").unwrap();
@ -1920,7 +1921,7 @@ mod tests {
sketching::test_init();
let webauthn = create_webauthn();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
// manually load in a cred
let p = CryptoPolicy::minimum();
let cred = Credential::new_password_only(&p, "list@no3IBTyqHu$bad").unwrap();
@ -2087,7 +2088,7 @@ mod tests {
sketching::test_init();
let webauthn = create_webauthn();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);
@ -2264,7 +2265,7 @@ mod tests {
sketching::test_init();
let webauthn = create_webauthn();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);
@ -2440,7 +2441,7 @@ mod tests {
let (audit_tx, mut audit_rx) = unbounded();
let ts = duration_from_epoch_now();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
let (webauthn, mut wa, wan_cred) = setup_webauthn_passkey(account.name.as_str());
@ -2594,7 +2595,7 @@ mod tests {
let (audit_tx, mut audit_rx) = unbounded();
let ts = duration_from_epoch_now();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.name.as_str());
let pw_good = "test_password";
@ -2787,7 +2788,7 @@ mod tests {
let (audit_tx, mut audit_rx) = unbounded();
let ts = duration_from_epoch_now();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.name.as_str());
@ -3053,7 +3054,7 @@ mod tests {
sketching::test_init();
let webauthn = create_webauthn();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);
@ -3262,7 +3263,7 @@ mod tests {
sketching::test_init();
let webauthn = create_webauthn();
// create the ent
let mut account: Account = BUILTIN_ACCOUNT_ADMIN.clone().into();
let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
// Setup a fake time stamp for consistency.
let ts = Duration::from_secs(12345);

View file

@ -1989,7 +1989,7 @@ mod tests {
let me = ModifyEvent::new_internal_invalid(
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname(BUILTIN_GROUP_PEOPLE_PII_READ.name)
PartialValue::new_iname("idm_people_pii_read")
)),
ModifyList::new_list(vec![Modify::Present(
Attribute::Member,

View file

@ -143,8 +143,9 @@ impl IdmServer {
pub async fn new(
qs: QueryServer,
origin: &str,
is_integration_test: bool,
) -> Result<(IdmServer, IdmServerDelayed, IdmServerAudit), OperationError> {
let crypto_policy = if cfg!(test) {
let crypto_policy = if cfg!(test) || is_integration_test {
CryptoPolicy::danger_test_minimum()
} else {
// This is calculated back from:

View file

@ -50,6 +50,12 @@ pub mod credential;
pub mod entry;
pub mod event;
pub mod filter;
// If this module is ever made public outside of this crate, firstyear will be extremely sad.
// This is *purely migration data*. Don't even think about using it in test cases for anything
// else.
pub(crate) mod migration_data;
pub mod modify;
pub mod time;
pub(crate) mod utils;

View file

@ -0,0 +1,35 @@
//! Constant Entries for the IDM
use crate::constants::uuids::*;
use crate::migration_data::types::BuiltinAccount;
use kanidm_proto::v1::AccountType;
lazy_static! {
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_IDM_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "idm_admin",
uuid: UUID_IDM_ADMIN,
description: "Builtin IDM Admin account.",
displayname: "IDM Administrator",
};
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "admin",
uuid: UUID_ADMIN,
description: "Builtin System Admin account.",
displayname: "System Administrator",
};
pub static ref BUILTIN_ACCOUNT_ANONYMOUS_DL6: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: Some(UUID_IDM_ADMINS),
name: "anonymous",
uuid: UUID_ANONYMOUS,
description: "Anonymous access account.",
displayname: "Anonymous",
};
}

View file

@ -0,0 +1,408 @@
use crate::entry::EntryInitNew;
use crate::prelude::*;
use crate::value::CredentialType;
use kanidm_proto::internal::{Filter, OperationError, UiHint};
#[derive(Clone, Debug, Default)]
/// Built-in group definitions
pub struct BuiltinGroup {
pub name: &'static str,
pub description: &'static str,
pub uuid: uuid::Uuid,
pub members: Vec<uuid::Uuid>,
pub entry_managed_by: Option<uuid::Uuid>,
pub dyngroup: bool,
pub dyngroup_filter: Option<Filter>,
pub extra_attributes: Vec<(Attribute, Value)>,
}
impl TryFrom<BuiltinGroup> for EntryInitNew {
type Error = OperationError;
fn try_from(val: BuiltinGroup) -> Result<Self, OperationError> {
let mut entry = EntryInitNew::new();
if val.uuid >= DYNAMIC_RANGE_MINIMUM_UUID {
error!("Builtin ACP has invalid UUID! {:?}", val);
return Err(OperationError::InvalidUuid);
}
entry.add_ava(Attribute::Name, Value::new_iname(val.name));
entry.add_ava(Attribute::Description, Value::new_utf8s(val.description));
// classes for groups
entry.set_ava(
Attribute::Class,
vec![EntryClass::Group.into(), EntryClass::Object.into()],
);
if val.dyngroup {
if !val.members.is_empty() {
return Err(OperationError::InvalidSchemaState(format!(
"Builtin dyngroup {} has members specified, this is not allowed",
val.name
)));
}
entry.add_ava(Attribute::Class, EntryClass::DynGroup.to_value());
match val.dyngroup_filter {
Some(filter) => entry.add_ava(Attribute::DynGroupFilter, Value::JsonFilt(filter)),
None => {
error!(
"No filter specified for dyngroup '{}' this is going to break things!",
val.name
);
return Err(OperationError::FilterGeneration);
}
};
}
if let Some(entry_manager) = val.entry_managed_by {
entry.add_ava(Attribute::EntryManagedBy, Value::Refer(entry_manager));
}
entry.add_ava(Attribute::Uuid, Value::Uuid(val.uuid));
entry.set_ava(
Attribute::Member,
val.members
.into_iter()
.map(Value::Refer)
.collect::<Vec<Value>>(),
);
// add any extra attributes
val.extra_attributes
.into_iter()
.for_each(|(attr, val)| entry.add_ava(attr, val));
// all done!
Ok(entry)
}
}
lazy_static! {
// There are our built in "roles". They encapsulate some higher level collections
// of roles. The intent is to allow a pretty generic and correct by default set
// of these use cases.
pub static ref BUILTIN_GROUP_SYSTEM_ADMINS_V1: BuiltinGroup = BuiltinGroup {
name: NAME_SYSTEM_ADMINS,
description: "Builtin System Administrators Group.",
uuid: UUID_SYSTEM_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_ADMIN],
..Default::default()
};
pub static ref BUILTIN_GROUP_IDM_ADMINS_V1: BuiltinGroup = BuiltinGroup {
name: NAME_IDM_ADMINS,
description: "Builtin IDM Administrators Group.",
uuid: UUID_IDM_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMIN],
..Default::default()
};
pub static ref BUILTIN_GROUP_SERVICE_DESK: BuiltinGroup = BuiltinGroup {
name: "idm_service_desk",
description: "Builtin Service Desk Group.",
uuid: UUID_IDM_SERVICE_DESK,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![],
..Default::default()
};
}
lazy_static! {
// These are the "finer" roles. They encapsulate different concepts in the system.
// The next section is the "system style" roles. These adjust the operation of
// kanidm and relate to it's internals and how it functions.
pub static ref BUILTIN_GROUP_RECYCLE_BIN_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_recycle_bin_admins",
description: "Builtin Recycle Bin Administrators Group.",
uuid: UUID_IDM_RECYCLE_BIN_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for granting local domain administration rights and trust administration rights
pub static ref BUILTIN_GROUP_DOMAIN_ADMINS: BuiltinGroup = BuiltinGroup {
name: "domain_admins",
description: "Builtin IDM Group for granting local domain administration rights and trust administration rights.",
uuid: UUID_DOMAIN_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_SCHEMA_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_schema_admins",
description: "Builtin Schema Administration Group.",
uuid: UUID_IDM_SCHEMA_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_ACCESS_CONTROL_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_access_control_admins",
description: "Builtin Access Control Administration Group.",
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
uuid: UUID_IDM_ACCESS_CONTROL_ADMINS,
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
// These are the IDM roles. They concern application integration, user permissions
// and credential security management.
/// Builtin IDM Group for managing persons and their account details
pub static ref BUILTIN_GROUP_PEOPLE_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_people_admins",
description: "Builtin People Administration Group.",
uuid: UUID_IDM_PEOPLE_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_PEOPLE_ON_BOARDING: BuiltinGroup = BuiltinGroup {
name: "idm_people_on_boarding",
description: "Builtin People On Boarding Group.",
uuid: UUID_IDM_PEOPLE_ON_BOARDING,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![],
..Default::default()
};
/// Builtin IDM Group for granting elevated people (personal data) read permissions.
pub static ref BUILTIN_GROUP_PEOPLE_PII_READ: BuiltinGroup = BuiltinGroup {
name: "idm_people_pii_read",
description: "Builtin IDM Group for granting elevated people (personal data) read permissions.",
uuid: UUID_IDM_PEOPLE_PII_READ,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![],
..Default::default()
};
/// Builtin IDM Group for granting people the ability to write to their own name attributes.
pub static ref BUILTIN_GROUP_PEOPLE_SELF_NAME_WRITE_DL7: BuiltinGroup = BuiltinGroup {
name: "idm_people_self_name_write",
description: "Builtin IDM Group denoting users that can write to their own name attributes.",
uuid: UUID_IDM_PEOPLE_SELF_NAME_WRITE,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![
UUID_IDM_ALL_PERSONS
],
..Default::default()
};
pub static ref BUILTIN_GROUP_SERVICE_ACCOUNT_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_service_account_admins",
description: "Builtin Service Account Administration Group.",
uuid: UUID_IDM_SERVICE_ACCOUNT_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for managing oauth2 resource server integrations to this authentication domain.
pub static ref BUILTIN_GROUP_OAUTH2_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_oauth2_admins",
description: "Builtin Oauth2 Integration Administration Group.",
uuid: UUID_IDM_OAUTH2_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_RADIUS_SERVICE_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_radius_service_admins",
description: "Builtin Radius Administration Group.",
uuid: UUID_IDM_RADIUS_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for RADIUS server access delegation.
pub static ref BUILTIN_IDM_RADIUS_SERVERS_V1: BuiltinGroup = BuiltinGroup {
name: "idm_radius_servers",
description: "Builtin IDM Group for RADIUS server access delegation.",
uuid: UUID_IDM_RADIUS_SERVERS,
entry_managed_by: Some(UUID_IDM_RADIUS_ADMINS),
members: vec![
],
..Default::default()
};
pub static ref BUILTIN_GROUP_MAIL_SERVICE_ADMINS_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_mail_service_admins",
description: "Builtin Mail Server Administration Group.",
uuid: UUID_IDM_MAIL_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for MAIL server Access delegation.
pub static ref BUILTIN_IDM_MAIL_SERVERS_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_mail_servers",
description: "Builtin IDM Group for MAIL server access delegation.",
uuid: UUID_IDM_MAIL_SERVERS,
entry_managed_by: Some(UUID_IDM_MAIL_ADMINS),
members: vec![
],
..Default::default()
};
pub static ref BUILTIN_GROUP_ACCOUNT_POLICY_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_account_policy_admins",
description: "Builtin Account Policy Administration Group.",
uuid: UUID_IDM_ACCOUNT_POLICY_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for managing posix/unix attributes on groups and users.
pub static ref BUILTIN_GROUP_UNIX_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_unix_admins",
description: "Builtin Unix Administration Group.",
uuid: UUID_IDM_UNIX_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for managing client authentication certificates.
pub static ref BUILTIN_GROUP_CLIENT_CERTIFICATE_ADMINS_DL7: BuiltinGroup = BuiltinGroup {
name: "idm_client_certificate_admins",
description: "Builtin Client Certificate Administration Group.",
uuid: UUID_IDM_CLIENT_CERTIFICATE_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for granting elevated group write and lifecycle permissions.
pub static ref IDM_GROUP_ADMINS_V1: BuiltinGroup = BuiltinGroup {
name: "idm_group_admins",
description: "Builtin IDM Group for granting elevated group write and lifecycle permissions.",
uuid: UUID_IDM_GROUP_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Self-write of mail
pub static ref IDM_PEOPLE_SELF_MAIL_WRITE_DL7: BuiltinGroup = BuiltinGroup {
name: "idm_people_self_mail_write",
description: "Builtin IDM Group for people accounts to update their own mail.",
uuid: UUID_IDM_PEOPLE_SELF_MAIL_WRITE,
members: Vec::with_capacity(0),
..Default::default()
};
}
// at some point vs code just gives up on syntax highlighting inside lazy_static...
lazy_static! {
pub static ref IDM_ALL_PERSONS: BuiltinGroup = BuiltinGroup {
name: "idm_all_persons",
description: "Builtin IDM dynamic group containing all persons.",
uuid: UUID_IDM_ALL_PERSONS,
members: Vec::with_capacity(0),
dyngroup: true,
dyngroup_filter: Some(
Filter::And(vec![
Filter::Eq(Attribute::Class.to_string(), EntryClass::Person.to_string()),
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
])
),
extra_attributes: vec![
// Enable account policy by default
(Attribute::Class, EntryClass::AccountPolicy.to_value()),
// Enforce this is a system protected object
(Attribute::Class, EntryClass::System.to_value()),
// MFA By Default
(Attribute::CredentialTypeMinimum, CredentialType::Mfa.into()),
],
..Default::default()
};
pub static ref IDM_ALL_ACCOUNTS: BuiltinGroup = BuiltinGroup {
name: NAME_IDM_ALL_ACCOUNTS,
description: "Builtin IDM dynamic group containing all entries that can authenticate.",
uuid: UUID_IDM_ALL_ACCOUNTS,
members: Vec::with_capacity(0),
dyngroup: true,
dyngroup_filter: Some(
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
),
extra_attributes: vec![
// Enable account policy by default
(Attribute::Class, EntryClass::AccountPolicy.to_value()),
// Enforce this is a system protected object
(Attribute::Class, EntryClass::System.to_value()),
],
..Default::default()
};
pub static ref IDM_UI_ENABLE_EXPERIMENTAL_FEATURES: BuiltinGroup = BuiltinGroup {
name: "idm_ui_enable_experimental_features",
description: "Members of this group will have access to experimental web UI features.",
uuid: UUID_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES,
entry_managed_by: Some(UUID_IDM_ADMINS),
extra_attributes: vec![
(Attribute::GrantUiHint, Value::UiHint(UiHint::ExperimentalFeatures))
],
..Default::default()
};
/// Members of this group will have access to read the mail attribute of all persons and service accounts.
pub static ref IDM_ACCOUNT_MAIL_READ: BuiltinGroup = BuiltinGroup {
name: "idm_account_mail_read",
description: "Members of this group will have access to read the mail attribute of all persons and service accounts.",
entry_managed_by: Some(UUID_IDM_ACCESS_CONTROL_ADMINS),
uuid: UUID_IDM_ACCOUNT_MAIL_READ,
..Default::default()
};
/// This must be the last group to init to include the UUID of the other high priv groups.
pub static ref IDM_HIGH_PRIVILEGE_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_high_privilege",
uuid: UUID_IDM_HIGH_PRIVILEGE,
entry_managed_by: Some(UUID_IDM_ACCESS_CONTROL_ADMINS),
description: "Builtin IDM provided groups with high levels of access that should be audited and limited in modification.",
members: vec![
UUID_SYSTEM_ADMINS,
UUID_IDM_ADMINS,
UUID_DOMAIN_ADMINS,
UUID_IDM_SERVICE_DESK,
UUID_IDM_RECYCLE_BIN_ADMINS,
UUID_IDM_SCHEMA_ADMINS,
UUID_IDM_ACCESS_CONTROL_ADMINS,
UUID_IDM_OAUTH2_ADMINS,
UUID_IDM_RADIUS_ADMINS,
UUID_IDM_ACCOUNT_POLICY_ADMINS,
UUID_IDM_RADIUS_SERVERS,
UUID_IDM_GROUP_ADMINS,
UUID_IDM_UNIX_ADMINS,
UUID_IDM_PEOPLE_PII_READ,
UUID_IDM_PEOPLE_ADMINS,
UUID_IDM_PEOPLE_ON_BOARDING,
UUID_IDM_SERVICE_ACCOUNT_ADMINS,
UUID_IDM_CLIENT_CERTIFICATE_ADMINS,
UUID_IDM_APPLICATION_ADMINS,
UUID_IDM_MAIL_ADMINS,
UUID_IDM_HIGH_PRIVILEGE,
],
..Default::default()
};
pub static ref BUILTIN_GROUP_APPLICATION_ADMINS_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_application_admins",
uuid: UUID_IDM_APPLICATION_ADMINS,
description: "Builtin Application Administration Group.",
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
}

View file

@ -0,0 +1,267 @@
mod access;
pub(super) mod accounts;
mod groups;
mod key_providers;
mod schema;
mod system_config;
use self::access::*;
use self::accounts::*;
use self::groups::*;
use self::key_providers::*;
use self::schema::*;
use self::system_config::*;
use crate::prelude::EntryInitNew;
use kanidm_proto::internal::OperationError;
pub fn phase_1_schema_attrs() -> Vec<EntryInitNew> {
vec![
SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL.clone().into(),
SCHEMA_ATTR_SYNC_YIELD_AUTHORITY.clone().into(),
SCHEMA_ATTR_ACCOUNT_EXPIRE.clone().into(),
SCHEMA_ATTR_ACCOUNT_VALID_FROM.clone().into(),
SCHEMA_ATTR_API_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_AUTH_SESSION_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH.clone().into(),
SCHEMA_ATTR_BADLIST_PASSWORD.clone().into(),
SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN.clone().into(),
SCHEMA_ATTR_ATTESTED_PASSKEYS.clone().into(),
SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(),
SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(),
SCHEMA_ATTR_DOMAIN_NAME.clone().into(),
SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND.clone().into(),
SCHEMA_ATTR_DOMAIN_SSID.clone().into(),
SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_DOMAIN_UUID.clone().into(),
SCHEMA_ATTR_DYNGROUP_FILTER.clone().into(),
SCHEMA_ATTR_EC_KEY_PRIVATE.clone().into(),
SCHEMA_ATTR_ES256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_FERNET_PRIVATE_KEY_STR.clone().into(),
SCHEMA_ATTR_GIDNUMBER.clone().into(),
SCHEMA_ATTR_GRANT_UI_HINT.clone().into(),
SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY.clone().into(),
SCHEMA_ATTR_LOGINSHELL.clone().into(),
SCHEMA_ATTR_NAME_HISTORY.clone().into(),
SCHEMA_ATTR_NSUNIQUEID.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
.clone()
.into(),
SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE.clone().into(),
SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_NAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_OAUTH2_SESSION.clone().into(),
SCHEMA_ATTR_PASSKEYS.clone().into(),
SCHEMA_ATTR_PRIMARY_CREDENTIAL.clone().into(),
SCHEMA_ATTR_PRIVATE_COOKIE_KEY.clone().into(),
SCHEMA_ATTR_RADIUS_SECRET.clone().into(),
SCHEMA_ATTR_RS256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_SSH_PUBLICKEY.clone().into(),
SCHEMA_ATTR_SYNC_COOKIE.clone().into(),
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_UNIX_PASSWORD.clone().into(),
SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM.clone().into(),
SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
// DL4
SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT_DL4
.clone()
.into(),
// DL5
// DL6
SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS_DL6.clone().into(),
SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6.clone().into(),
SCHEMA_ATTR_KEY_INTERNAL_DATA_DL6.clone().into(),
SCHEMA_ATTR_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_ROTATE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_REVOKE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_ES256_DL6.clone().into(),
// DL7
SCHEMA_ATTR_PATCH_LEVEL_DL7.clone().into(),
SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7.clone().into(),
SCHEMA_ATTR_REFERS_DL7.clone().into(),
SCHEMA_ATTR_CERTIFICATE_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7.clone().into(),
SCHEMA_ATTR_MAIL_DL7.clone().into(),
SCHEMA_ATTR_LEGALNAME_DL7.clone().into(),
SCHEMA_ATTR_DISPLAYNAME_DL7.clone().into(),
// DL8
SCHEMA_ATTR_LINKED_GROUP_DL8.clone().into(),
SCHEMA_ATTR_APPLICATION_PASSWORD_DL8.clone().into(),
SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK_DL8.clone().into(),
// DL9
SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9.clone().into(),
SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS_DL9.clone().into(),
// DL10
SCHEMA_ATTR_DENIED_NAME_DL10.clone().into(),
SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES.clone().into(),
]
}
pub fn phase_2_schema_classes() -> Vec<EntryInitNew> {
vec![
SCHEMA_CLASS_DYNGROUP.clone().into(),
SCHEMA_CLASS_ORGPERSON.clone().into(),
SCHEMA_CLASS_POSIXACCOUNT.clone().into(),
SCHEMA_CLASS_POSIXGROUP.clone().into(),
SCHEMA_CLASS_SYSTEM_CONFIG.clone().into(),
// DL4
SCHEMA_CLASS_OAUTH2_RS_PUBLIC_DL4.clone().into(),
// DL5
SCHEMA_CLASS_ACCOUNT_DL5.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_BASIC_DL5.clone().into(),
// DL6
SCHEMA_CLASS_GROUP_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_INTERNAL_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWT_ES256_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWE_A128GCM_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_INTERNAL_DL6.clone().into(),
// DL7
SCHEMA_CLASS_SERVICE_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_SYNC_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_CLIENT_CERTIFICATE_DL7.clone().into(),
// DL8
SCHEMA_CLASS_ACCOUNT_POLICY_DL8.clone().into(),
SCHEMA_CLASS_APPLICATION_DL8.clone().into(),
SCHEMA_CLASS_PERSON_DL8.clone().into(),
// DL9
SCHEMA_CLASS_OAUTH2_RS_DL9.clone().into(),
// DL10
SCHEMA_CLASS_DOMAIN_INFO_DL10.clone().into(),
]
}
pub fn phase_3_key_provider() -> Vec<EntryInitNew> {
vec![E_KEY_PROVIDER_INTERNAL_DL6.clone()]
}
pub fn phase_4_system_entries() -> Vec<EntryInitNew> {
vec![
E_SYSTEM_INFO_V1.clone(),
E_DOMAIN_INFO_DL6.clone(),
E_SYSTEM_CONFIG_V1.clone(),
]
}
pub fn phase_5_builtin_admin_entries() -> Result<Vec<EntryInitNew>, OperationError> {
Ok(vec![
BUILTIN_ACCOUNT_ADMIN.clone().into(),
BUILTIN_ACCOUNT_IDM_ADMIN.clone().into(),
BUILTIN_GROUP_SYSTEM_ADMINS_V1.clone().try_into()?,
BUILTIN_GROUP_IDM_ADMINS_V1.clone().try_into()?,
// We need to push anonymous *after* groups due to entry-managed-by
BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into(),
])
}
pub fn phase_6_builtin_non_admin_entries() -> Result<Vec<EntryInitNew>, OperationError> {
Ok(vec![
BUILTIN_GROUP_DOMAIN_ADMINS.clone().try_into()?,
BUILTIN_GROUP_SCHEMA_ADMINS.clone().try_into()?,
BUILTIN_GROUP_ACCESS_CONTROL_ADMINS.clone().try_into()?,
BUILTIN_GROUP_UNIX_ADMINS.clone().try_into()?,
BUILTIN_GROUP_RECYCLE_BIN_ADMINS.clone().try_into()?,
BUILTIN_GROUP_SERVICE_DESK.clone().try_into()?,
BUILTIN_GROUP_OAUTH2_ADMINS.clone().try_into()?,
BUILTIN_GROUP_RADIUS_SERVICE_ADMINS.clone().try_into()?,
BUILTIN_GROUP_ACCOUNT_POLICY_ADMINS.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_ADMINS.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_PII_READ.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_ON_BOARDING.clone().try_into()?,
BUILTIN_GROUP_SERVICE_ACCOUNT_ADMINS.clone().try_into()?,
BUILTIN_GROUP_MAIL_SERVICE_ADMINS_DL8.clone().try_into()?,
IDM_GROUP_ADMINS_V1.clone().try_into()?,
IDM_ALL_PERSONS.clone().try_into()?,
IDM_ALL_ACCOUNTS.clone().try_into()?,
BUILTIN_IDM_RADIUS_SERVERS_V1.clone().try_into()?,
BUILTIN_IDM_MAIL_SERVERS_DL8.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_SELF_NAME_WRITE_DL7
.clone()
.try_into()?,
IDM_PEOPLE_SELF_MAIL_WRITE_DL7.clone().try_into()?,
BUILTIN_GROUP_CLIENT_CERTIFICATE_ADMINS_DL7
.clone()
.try_into()?,
BUILTIN_GROUP_APPLICATION_ADMINS_DL8.clone().try_into()?,
// Write deps on read.clone().try_into()?, so write must be added first.
// All members must exist before we write HP
IDM_HIGH_PRIVILEGE_DL8.clone().try_into()?,
// other things
IDM_UI_ENABLE_EXPERIMENTAL_FEATURES.clone().try_into()?,
IDM_ACCOUNT_MAIL_READ.clone().try_into()?,
])
}
pub fn phase_7_builtin_access_control_profiles() -> Vec<EntryInitNew> {
vec![
// Built in access controls.
IDM_ACP_RECYCLE_BIN_SEARCH_V1.clone().into(),
IDM_ACP_RECYCLE_BIN_REVIVE_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_ATTRS_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_CLASSES_V1.clone().into(),
IDM_ACP_ACP_MANAGE_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGED_BY_MODIFY_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SYNC_ACCOUNT_MANAGE_V1.clone().into(),
IDM_ACP_RADIUS_SERVERS_V1.clone().into(),
IDM_ACP_RADIUS_SECRET_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_SELF_WRITE_MAIL_V1.clone().into(),
IDM_ACP_ACCOUNT_SELF_WRITE_V1.clone().into(),
IDM_ACP_ALL_ACCOUNTS_POSIX_READ_V1.clone().into(),
IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1
.clone()
.into(),
IDM_ACP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_HP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_GROUP_READ_V1.clone().into(),
IDM_ACP_ACCOUNT_UNIX_EXTEND_V1.clone().into(),
IDM_ACP_PEOPLE_PII_READ_V1.clone().into(),
IDM_ACP_PEOPLE_PII_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_READ_V1.clone().into(),
IDM_ACP_PEOPLE_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_DELETE_V1.clone().into(),
IDM_ACP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_HP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_CREATE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_DELETE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_HP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_SERVICE_ACCOUNT_MANAGE_V1.clone().into(),
// DL4
// DL5
// DL6
IDM_ACP_PEOPLE_CREATE_DL6.clone().into(),
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone().into(),
// DL7
IDM_ACP_SELF_NAME_WRITE_DL7.clone().into(),
IDM_ACP_HP_CLIENT_CERTIFICATE_MANAGER_DL7.clone().into(),
// DL8
IDM_ACP_SELF_READ_DL8.clone().into(),
IDM_ACP_SELF_WRITE_DL8.clone().into(),
IDM_ACP_APPLICATION_MANAGE_DL8.clone().into(),
IDM_ACP_APPLICATION_ENTRY_MANAGER_DL8.clone().into(),
IDM_ACP_MAIL_SERVERS_DL8.clone().into(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone().into(),
// DL9
IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(),
IDM_ACP_GROUP_MANAGE_DL9.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL9.clone().into(),
]
}

View file

@ -1,7 +1,4 @@
//! Core Constants
//!
//! Schema uuids start at `00000000-0000-0000-0000-ffff00000000`
//!
//! Schema Entries
use crate::constants::entries::{Attribute, EntryClass};
use crate::constants::uuids::*;
use crate::schema::{SchemaAttribute, SchemaClass};

View file

@ -8,6 +8,31 @@ use crate::value::Value;
// This is separated because the password badlist section may become very long
lazy_static! {
pub static ref E_SYSTEM_INFO_V1: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::SystemInfo.to_value()),
(Attribute::Class, EntryClass::System.to_value()),
(Attribute::Uuid, Value::Uuid(UUID_SYSTEM_INFO)),
(
Attribute::Description,
Value::new_utf8s("System (local) info and metadata object.")
),
(Attribute::Version, Value::Uint32(20))
);
pub static ref E_DOMAIN_INFO_DL6: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::DomainInfo.to_value()),
(Attribute::Class, EntryClass::System.to_value()),
(Attribute::Class, EntryClass::KeyObject.to_value()),
(Attribute::Class, EntryClass::KeyObjectJwtEs256.to_value()),
(Attribute::Class, EntryClass::KeyObjectJweA128GCM.to_value()),
(Attribute::Name, Value::new_iname("domain_local")),
(Attribute::Uuid, Value::Uuid(UUID_DOMAIN_INFO)),
(
Attribute::Description,
Value::new_utf8s("This local domain's info and metadata object.")
)
);
pub static ref E_SYSTEM_CONFIG_V1: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::SystemConfig.to_value()),

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
//! Constant Entries for the IDM
use crate::constants::uuids::*;
use crate::migration_data::types::BuiltinAccount;
use kanidm_proto::v1::AccountType;
lazy_static! {
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_IDM_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "idm_admin",
uuid: UUID_IDM_ADMIN,
description: "Builtin IDM Admin account.",
displayname: "IDM Administrator",
};
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "admin",
uuid: UUID_ADMIN,
description: "Builtin System Admin account.",
displayname: "System Administrator",
};
pub static ref BUILTIN_ACCOUNT_ANONYMOUS_DL6: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: Some(UUID_IDM_ADMINS),
name: "anonymous",
uuid: UUID_ANONYMOUS,
description: "Anonymous access account.",
displayname: "Anonymous",
};
}

View file

@ -106,7 +106,9 @@ lazy_static! {
members: vec![],
..Default::default()
};
}
lazy_static! {
// These are the "finer" roles. They encapsulate different concepts in the system.
// The next section is the "system style" roles. These adjust the operation of
// kanidm and relate to it's internals and how it functions.
@ -404,46 +406,3 @@ lazy_static! {
..Default::default()
};
}
/// Make a list of all the non-admin BuiltinGroup's that are created by default, doing it in a standard-ish way so we can use it around the platform
pub fn idm_builtin_non_admin_groups() -> Vec<&'static BuiltinGroup> {
// Create any system default schema entries.
vec![
&BUILTIN_GROUP_DOMAIN_ADMINS,
&BUILTIN_GROUP_SCHEMA_ADMINS,
&BUILTIN_GROUP_ACCESS_CONTROL_ADMINS,
&BUILTIN_GROUP_UNIX_ADMINS,
&BUILTIN_GROUP_RECYCLE_BIN_ADMINS,
&BUILTIN_GROUP_SERVICE_DESK,
&BUILTIN_GROUP_OAUTH2_ADMINS,
&BUILTIN_GROUP_RADIUS_SERVICE_ADMINS,
&BUILTIN_GROUP_ACCOUNT_POLICY_ADMINS,
&BUILTIN_GROUP_PEOPLE_ADMINS,
&BUILTIN_GROUP_PEOPLE_PII_READ,
&BUILTIN_GROUP_PEOPLE_ON_BOARDING,
&BUILTIN_GROUP_SERVICE_ACCOUNT_ADMINS,
&BUILTIN_GROUP_MAIL_SERVICE_ADMINS_DL8,
&IDM_GROUP_ADMINS_V1,
&IDM_ALL_PERSONS,
&IDM_ALL_ACCOUNTS,
&BUILTIN_IDM_RADIUS_SERVERS_V1,
&BUILTIN_IDM_MAIL_SERVERS_DL8,
&BUILTIN_GROUP_PEOPLE_SELF_NAME_WRITE_DL7,
&IDM_PEOPLE_SELF_MAIL_WRITE_DL7,
&BUILTIN_GROUP_CLIENT_CERTIFICATE_ADMINS_DL7,
&BUILTIN_GROUP_APPLICATION_ADMINS_DL8,
// Write deps on read, so write must be added first.
// All members must exist before we write HP
&IDM_HIGH_PRIVILEGE_DL8,
// other things
&IDM_UI_ENABLE_EXPERIMENTAL_FEATURES,
&IDM_ACCOUNT_MAIL_READ,
]
}
pub fn idm_builtin_admin_groups() -> Vec<&'static BuiltinGroup> {
vec![
&BUILTIN_GROUP_SYSTEM_ADMINS_V1,
&BUILTIN_GROUP_IDM_ADMINS_V1,
]
}

View file

@ -0,0 +1,18 @@
use crate::constants::entries::{Attribute, EntryClass};
use crate::constants::uuids::UUID_KEY_PROVIDER_INTERNAL;
use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
use crate::value::Value;
lazy_static! {
pub static ref E_KEY_PROVIDER_INTERNAL_DL6: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::KeyProvider.to_value()),
(Attribute::Class, EntryClass::KeyProviderInternal.to_value()),
(Attribute::Uuid, Value::Uuid(UUID_KEY_PROVIDER_INTERNAL)),
(Attribute::Name, Value::new_iname("key_provider_internal")),
(
Attribute::Description,
Value::new_utf8s("The default database internal cryptographic key provider.")
)
);
}

View file

@ -0,0 +1,273 @@
mod access;
mod accounts;
mod groups;
mod key_providers;
mod schema;
mod system_config;
use self::access::*;
use self::accounts::*;
use self::groups::*;
use self::key_providers::*;
use self::schema::*;
use self::system_config::*;
use crate::prelude::EntryInitNew;
use kanidm_proto::internal::OperationError;
pub fn phase_1_schema_attrs() -> Vec<EntryInitNew> {
vec![
SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL.clone().into(),
SCHEMA_ATTR_SYNC_YIELD_AUTHORITY.clone().into(),
SCHEMA_ATTR_ACCOUNT_EXPIRE.clone().into(),
SCHEMA_ATTR_ACCOUNT_VALID_FROM.clone().into(),
SCHEMA_ATTR_API_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_AUTH_SESSION_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH.clone().into(),
SCHEMA_ATTR_BADLIST_PASSWORD.clone().into(),
SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN.clone().into(),
SCHEMA_ATTR_ATTESTED_PASSKEYS.clone().into(),
SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(),
SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(),
SCHEMA_ATTR_DOMAIN_NAME.clone().into(),
SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND.clone().into(),
SCHEMA_ATTR_DOMAIN_SSID.clone().into(),
SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_DOMAIN_UUID.clone().into(),
SCHEMA_ATTR_DYNGROUP_FILTER.clone().into(),
SCHEMA_ATTR_EC_KEY_PRIVATE.clone().into(),
SCHEMA_ATTR_ES256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_FERNET_PRIVATE_KEY_STR.clone().into(),
SCHEMA_ATTR_GIDNUMBER.clone().into(),
SCHEMA_ATTR_GRANT_UI_HINT.clone().into(),
SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY.clone().into(),
SCHEMA_ATTR_LOGINSHELL.clone().into(),
SCHEMA_ATTR_NAME_HISTORY.clone().into(),
SCHEMA_ATTR_NSUNIQUEID.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
.clone()
.into(),
SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE.clone().into(),
SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_NAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_OAUTH2_SESSION.clone().into(),
SCHEMA_ATTR_PASSKEYS.clone().into(),
SCHEMA_ATTR_PRIMARY_CREDENTIAL.clone().into(),
SCHEMA_ATTR_PRIVATE_COOKIE_KEY.clone().into(),
SCHEMA_ATTR_RADIUS_SECRET.clone().into(),
SCHEMA_ATTR_RS256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_SSH_PUBLICKEY.clone().into(),
SCHEMA_ATTR_SYNC_COOKIE.clone().into(),
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_UNIX_PASSWORD.clone().into(),
SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_DENIED_NAME.clone().into(),
SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM.clone().into(),
SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
// DL4
SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT_DL4
.clone()
.into(),
// DL5
// DL6
SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS_DL6.clone().into(),
SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6.clone().into(),
SCHEMA_ATTR_KEY_INTERNAL_DATA_DL6.clone().into(),
SCHEMA_ATTR_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_ROTATE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_REVOKE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_ES256_DL6.clone().into(),
// DL7
SCHEMA_ATTR_PATCH_LEVEL_DL7.clone().into(),
SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7.clone().into(),
SCHEMA_ATTR_REFERS_DL7.clone().into(),
SCHEMA_ATTR_CERTIFICATE_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7.clone().into(),
SCHEMA_ATTR_MAIL_DL7.clone().into(),
SCHEMA_ATTR_LEGALNAME_DL7.clone().into(),
SCHEMA_ATTR_DISPLAYNAME_DL7.clone().into(),
// DL8
SCHEMA_ATTR_LINKED_GROUP_DL8.clone().into(),
SCHEMA_ATTR_APPLICATION_PASSWORD_DL8.clone().into(),
SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK_DL8.clone().into(),
]
}
pub fn phase_2_schema_classes() -> Vec<EntryInitNew> {
vec![
SCHEMA_CLASS_DYNGROUP.clone().into(),
SCHEMA_CLASS_ORGPERSON.clone().into(),
SCHEMA_CLASS_POSIXACCOUNT.clone().into(),
SCHEMA_CLASS_POSIXGROUP.clone().into(),
SCHEMA_CLASS_SYSTEM_CONFIG.clone().into(),
// DL4
SCHEMA_CLASS_OAUTH2_RS_PUBLIC_DL4.clone().into(),
// DL5
// SCHEMA_CLASS_PERSON_DL5.clone().into(),
SCHEMA_CLASS_ACCOUNT_DL5.clone().into(),
// SCHEMA_CLASS_OAUTH2_RS_DL5.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_BASIC_DL5.clone().into(),
// DL6
// SCHEMA_CLASS_ACCOUNT_POLICY_DL6.clone().into(),
// SCHEMA_CLASS_SERVICE_ACCOUNT_DL6.clone().into(),
// SCHEMA_CLASS_SYNC_ACCOUNT_DL6.clone().into(),
SCHEMA_CLASS_GROUP_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_INTERNAL_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWT_ES256_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWE_A128GCM_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_INTERNAL_DL6.clone().into(),
// SCHEMA_CLASS_DOMAIN_INFO_DL6.clone().into(),
// DL7
// SCHEMA_CLASS_DOMAIN_INFO_DL7.clone().into(),
SCHEMA_CLASS_SERVICE_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_SYNC_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_CLIENT_CERTIFICATE_DL7.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_DL7.clone().into(),
// DL8
SCHEMA_CLASS_ACCOUNT_POLICY_DL8.clone().into(),
SCHEMA_CLASS_APPLICATION_DL8.clone().into(),
SCHEMA_CLASS_PERSON_DL8.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL8.clone().into(),
]
}
pub fn phase_3_key_provider() -> Vec<EntryInitNew> {
vec![E_KEY_PROVIDER_INTERNAL_DL6.clone()]
}
pub fn phase_4_system_entries() -> Vec<EntryInitNew> {
vec![
E_SYSTEM_INFO_V1.clone(),
E_DOMAIN_INFO_DL6.clone(),
E_SYSTEM_CONFIG_V1.clone(),
]
}
pub fn phase_5_builtin_admin_entries() -> Result<Vec<EntryInitNew>, OperationError> {
Ok(vec![
BUILTIN_ACCOUNT_ADMIN.clone().into(),
BUILTIN_ACCOUNT_IDM_ADMIN.clone().into(),
BUILTIN_GROUP_SYSTEM_ADMINS_V1.clone().try_into()?,
BUILTIN_GROUP_IDM_ADMINS_V1.clone().try_into()?,
// We need to push anonymous *after* groups due to entry-managed-by
BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into(),
])
}
pub fn phase_6_builtin_non_admin_entries() -> Result<Vec<EntryInitNew>, OperationError> {
Ok(vec![
BUILTIN_GROUP_DOMAIN_ADMINS.clone().try_into()?,
BUILTIN_GROUP_SCHEMA_ADMINS.clone().try_into()?,
BUILTIN_GROUP_ACCESS_CONTROL_ADMINS.clone().try_into()?,
BUILTIN_GROUP_UNIX_ADMINS.clone().try_into()?,
BUILTIN_GROUP_RECYCLE_BIN_ADMINS.clone().try_into()?,
BUILTIN_GROUP_SERVICE_DESK.clone().try_into()?,
BUILTIN_GROUP_OAUTH2_ADMINS.clone().try_into()?,
BUILTIN_GROUP_RADIUS_SERVICE_ADMINS.clone().try_into()?,
BUILTIN_GROUP_ACCOUNT_POLICY_ADMINS.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_ADMINS.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_PII_READ.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_ON_BOARDING.clone().try_into()?,
BUILTIN_GROUP_SERVICE_ACCOUNT_ADMINS.clone().try_into()?,
BUILTIN_GROUP_MAIL_SERVICE_ADMINS_DL8.clone().try_into()?,
IDM_GROUP_ADMINS_V1.clone().try_into()?,
IDM_ALL_PERSONS.clone().try_into()?,
IDM_ALL_ACCOUNTS.clone().try_into()?,
BUILTIN_IDM_RADIUS_SERVERS_V1.clone().try_into()?,
BUILTIN_IDM_MAIL_SERVERS_DL8.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_SELF_NAME_WRITE_DL7
.clone()
.try_into()?,
IDM_PEOPLE_SELF_MAIL_WRITE_DL7.clone().try_into()?,
BUILTIN_GROUP_CLIENT_CERTIFICATE_ADMINS_DL7
.clone()
.try_into()?,
BUILTIN_GROUP_APPLICATION_ADMINS_DL8.clone().try_into()?,
// Write deps on read.clone().try_into()?, so write must be added first.
// All members must exist before we write HP
IDM_HIGH_PRIVILEGE_DL8.clone().try_into()?,
// other things
IDM_UI_ENABLE_EXPERIMENTAL_FEATURES.clone().try_into()?,
IDM_ACCOUNT_MAIL_READ.clone().try_into()?,
])
}
pub fn phase_7_builtin_access_control_profiles() -> Vec<EntryInitNew> {
vec![
// Built in access controls.
IDM_ACP_RECYCLE_BIN_SEARCH_V1.clone().into(),
IDM_ACP_RECYCLE_BIN_REVIVE_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_ATTRS_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_CLASSES_V1.clone().into(),
IDM_ACP_ACP_MANAGE_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGED_BY_MODIFY_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SYNC_ACCOUNT_MANAGE_V1.clone().into(),
IDM_ACP_RADIUS_SERVERS_V1.clone().into(),
IDM_ACP_RADIUS_SECRET_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_SELF_WRITE_MAIL_V1.clone().into(),
// IDM_ACP_SELF_READ_V1.clone(),
// IDM_ACP_SELF_WRITE_V1.clone(),
IDM_ACP_ACCOUNT_SELF_WRITE_V1.clone().into(),
// IDM_ACP_SELF_NAME_WRITE_V1.clone(),
IDM_ACP_ALL_ACCOUNTS_POSIX_READ_V1.clone().into(),
IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1
.clone()
.into(),
IDM_ACP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_HP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_GROUP_READ_V1.clone().into(),
IDM_ACP_ACCOUNT_UNIX_EXTEND_V1.clone().into(),
IDM_ACP_PEOPLE_PII_READ_V1.clone().into(),
IDM_ACP_PEOPLE_PII_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_READ_V1.clone().into(),
IDM_ACP_PEOPLE_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_DELETE_V1.clone().into(),
IDM_ACP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_HP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_CREATE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_DELETE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_HP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_SERVICE_ACCOUNT_MANAGE_V1.clone().into(),
// DL4
// DL5
// IDM_ACP_OAUTH2_MANAGE_DL5.clone(),
// DL6
// IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL6.clone(),
IDM_ACP_PEOPLE_CREATE_DL6.clone().into(),
IDM_ACP_GROUP_MANAGE_DL6.clone().into(),
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone().into(),
// IDM_ACP_DOMAIN_ADMIN_DL6.clone(),
// DL7
// IDM_ACP_SELF_WRITE_DL7.clone(),
IDM_ACP_SELF_NAME_WRITE_DL7.clone().into(),
IDM_ACP_HP_CLIENT_CERTIFICATE_MANAGER_DL7.clone().into(),
IDM_ACP_OAUTH2_MANAGE_DL7.clone().into(),
// DL8
IDM_ACP_SELF_READ_DL8.clone().into(),
IDM_ACP_SELF_WRITE_DL8.clone().into(),
IDM_ACP_APPLICATION_MANAGE_DL8.clone().into(),
IDM_ACP_APPLICATION_ENTRY_MANAGER_DL8.clone().into(),
IDM_ACP_MAIL_SERVERS_DL8.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL8.clone().into(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone().into(),
]
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
//! Constant Entries for the IDM
use crate::constants::uuids::*;
use crate::migration_data::types::BuiltinAccount;
use kanidm_proto::v1::AccountType;
lazy_static! {
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_IDM_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "idm_admin",
uuid: UUID_IDM_ADMIN,
description: "Builtin IDM Admin account.",
displayname: "IDM Administrator",
};
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_ADMIN: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "admin",
uuid: UUID_ADMIN,
description: "Builtin System Admin account.",
displayname: "System Administrator",
};
pub static ref BUILTIN_ACCOUNT_ANONYMOUS_DL6: BuiltinAccount = BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: Some(UUID_IDM_ADMINS),
name: "anonymous",
uuid: UUID_ANONYMOUS,
description: "Anonymous access account.",
displayname: "Anonymous",
};
}

View file

@ -0,0 +1,408 @@
use crate::entry::EntryInitNew;
use crate::prelude::*;
use crate::value::CredentialType;
use kanidm_proto::internal::{Filter, OperationError, UiHint};
#[derive(Clone, Debug, Default)]
/// Built-in group definitions
pub struct BuiltinGroup {
pub name: &'static str,
pub description: &'static str,
pub uuid: uuid::Uuid,
pub members: Vec<uuid::Uuid>,
pub entry_managed_by: Option<uuid::Uuid>,
pub dyngroup: bool,
pub dyngroup_filter: Option<Filter>,
pub extra_attributes: Vec<(Attribute, Value)>,
}
impl TryFrom<BuiltinGroup> for EntryInitNew {
type Error = OperationError;
fn try_from(val: BuiltinGroup) -> Result<Self, OperationError> {
let mut entry = EntryInitNew::new();
if val.uuid >= DYNAMIC_RANGE_MINIMUM_UUID {
error!("Builtin ACP has invalid UUID! {:?}", val);
return Err(OperationError::InvalidUuid);
}
entry.add_ava(Attribute::Name, Value::new_iname(val.name));
entry.add_ava(Attribute::Description, Value::new_utf8s(val.description));
// classes for groups
entry.set_ava(
Attribute::Class,
vec![EntryClass::Group.into(), EntryClass::Object.into()],
);
if val.dyngroup {
if !val.members.is_empty() {
return Err(OperationError::InvalidSchemaState(format!(
"Builtin dyngroup {} has members specified, this is not allowed",
val.name
)));
}
entry.add_ava(Attribute::Class, EntryClass::DynGroup.to_value());
match val.dyngroup_filter {
Some(filter) => entry.add_ava(Attribute::DynGroupFilter, Value::JsonFilt(filter)),
None => {
error!(
"No filter specified for dyngroup '{}' this is going to break things!",
val.name
);
return Err(OperationError::FilterGeneration);
}
};
}
if let Some(entry_manager) = val.entry_managed_by {
entry.add_ava(Attribute::EntryManagedBy, Value::Refer(entry_manager));
}
entry.add_ava(Attribute::Uuid, Value::Uuid(val.uuid));
entry.set_ava(
Attribute::Member,
val.members
.into_iter()
.map(Value::Refer)
.collect::<Vec<Value>>(),
);
// add any extra attributes
val.extra_attributes
.into_iter()
.for_each(|(attr, val)| entry.add_ava(attr, val));
// all done!
Ok(entry)
}
}
lazy_static! {
// There are our built in "roles". They encapsulate some higher level collections
// of roles. The intent is to allow a pretty generic and correct by default set
// of these use cases.
pub static ref BUILTIN_GROUP_SYSTEM_ADMINS_V1: BuiltinGroup = BuiltinGroup {
name: "system_admins",
description: "Builtin System Administrators Group.",
uuid: UUID_SYSTEM_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_ADMIN],
..Default::default()
};
pub static ref BUILTIN_GROUP_IDM_ADMINS_V1: BuiltinGroup = BuiltinGroup {
name: "idm_admins",
description: "Builtin IDM Administrators Group.",
uuid: UUID_IDM_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMIN],
..Default::default()
};
pub static ref BUILTIN_GROUP_SERVICE_DESK: BuiltinGroup = BuiltinGroup {
name: "idm_service_desk",
description: "Builtin Service Desk Group.",
uuid: UUID_IDM_SERVICE_DESK,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![],
..Default::default()
};
}
lazy_static! {
// These are the "finer" roles. They encapsulate different concepts in the system.
// The next section is the "system style" roles. These adjust the operation of
// kanidm and relate to it's internals and how it functions.
pub static ref BUILTIN_GROUP_RECYCLE_BIN_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_recycle_bin_admins",
description: "Builtin Recycle Bin Administrators Group.",
uuid: UUID_IDM_RECYCLE_BIN_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for granting local domain administration rights and trust administration rights
pub static ref BUILTIN_GROUP_DOMAIN_ADMINS: BuiltinGroup = BuiltinGroup {
name: "domain_admins",
description: "Builtin IDM Group for granting local domain administration rights and trust administration rights.",
uuid: UUID_DOMAIN_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_SCHEMA_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_schema_admins",
description: "Builtin Schema Administration Group.",
uuid: UUID_IDM_SCHEMA_ADMINS,
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_ACCESS_CONTROL_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_access_control_admins",
description: "Builtin Access Control Administration Group.",
entry_managed_by: Some(UUID_SYSTEM_ADMINS),
uuid: UUID_IDM_ACCESS_CONTROL_ADMINS,
members: vec![UUID_SYSTEM_ADMINS],
..Default::default()
};
// These are the IDM roles. They concern application integration, user permissions
// and credential security management.
/// Builtin IDM Group for managing persons and their account details
pub static ref BUILTIN_GROUP_PEOPLE_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_people_admins",
description: "Builtin People Administration Group.",
uuid: UUID_IDM_PEOPLE_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_PEOPLE_ON_BOARDING: BuiltinGroup = BuiltinGroup {
name: "idm_people_on_boarding",
description: "Builtin People On Boarding Group.",
uuid: UUID_IDM_PEOPLE_ON_BOARDING,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![],
..Default::default()
};
/// Builtin IDM Group for granting elevated people (personal data) read permissions.
pub static ref BUILTIN_GROUP_PEOPLE_PII_READ: BuiltinGroup = BuiltinGroup {
name: "idm_people_pii_read",
description: "Builtin IDM Group for granting elevated people (personal data) read permissions.",
uuid: UUID_IDM_PEOPLE_PII_READ,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![],
..Default::default()
};
/// Builtin IDM Group for granting people the ability to write to their own name attributes.
pub static ref BUILTIN_GROUP_PEOPLE_SELF_NAME_WRITE_DL7: BuiltinGroup = BuiltinGroup {
name: "idm_people_self_name_write",
description: "Builtin IDM Group denoting users that can write to their own name attributes.",
uuid: UUID_IDM_PEOPLE_SELF_NAME_WRITE,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![
UUID_IDM_ALL_PERSONS
],
..Default::default()
};
pub static ref BUILTIN_GROUP_SERVICE_ACCOUNT_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_service_account_admins",
description: "Builtin Service Account Administration Group.",
uuid: UUID_IDM_SERVICE_ACCOUNT_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for managing oauth2 resource server integrations to this authentication domain.
pub static ref BUILTIN_GROUP_OAUTH2_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_oauth2_admins",
description: "Builtin Oauth2 Integration Administration Group.",
uuid: UUID_IDM_OAUTH2_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
pub static ref BUILTIN_GROUP_RADIUS_SERVICE_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_radius_service_admins",
description: "Builtin Radius Administration Group.",
uuid: UUID_IDM_RADIUS_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for RADIUS server access delegation.
pub static ref BUILTIN_IDM_RADIUS_SERVERS_V1: BuiltinGroup = BuiltinGroup {
name: "idm_radius_servers",
description: "Builtin IDM Group for RADIUS server access delegation.",
uuid: UUID_IDM_RADIUS_SERVERS,
entry_managed_by: Some(UUID_IDM_RADIUS_ADMINS),
members: vec![
],
..Default::default()
};
pub static ref BUILTIN_GROUP_MAIL_SERVICE_ADMINS_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_mail_service_admins",
description: "Builtin Mail Server Administration Group.",
uuid: UUID_IDM_MAIL_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for MAIL server Access delegation.
pub static ref BUILTIN_IDM_MAIL_SERVERS_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_mail_servers",
description: "Builtin IDM Group for MAIL server access delegation.",
uuid: UUID_IDM_MAIL_SERVERS,
entry_managed_by: Some(UUID_IDM_MAIL_ADMINS),
members: vec![
],
..Default::default()
};
pub static ref BUILTIN_GROUP_ACCOUNT_POLICY_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_account_policy_admins",
description: "Builtin Account Policy Administration Group.",
uuid: UUID_IDM_ACCOUNT_POLICY_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for managing posix/unix attributes on groups and users.
pub static ref BUILTIN_GROUP_UNIX_ADMINS: BuiltinGroup = BuiltinGroup {
name: "idm_unix_admins",
description: "Builtin Unix Administration Group.",
uuid: UUID_IDM_UNIX_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for managing client authentication certificates.
pub static ref BUILTIN_GROUP_CLIENT_CERTIFICATE_ADMINS_DL7: BuiltinGroup = BuiltinGroup {
name: "idm_client_certificate_admins",
description: "Builtin Client Certificate Administration Group.",
uuid: UUID_IDM_CLIENT_CERTIFICATE_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Builtin IDM Group for granting elevated group write and lifecycle permissions.
pub static ref IDM_GROUP_ADMINS_V1: BuiltinGroup = BuiltinGroup {
name: "idm_group_admins",
description: "Builtin IDM Group for granting elevated group write and lifecycle permissions.",
uuid: UUID_IDM_GROUP_ADMINS,
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
/// Self-write of mail
pub static ref IDM_PEOPLE_SELF_MAIL_WRITE_DL7: BuiltinGroup = BuiltinGroup {
name: "idm_people_self_mail_write",
description: "Builtin IDM Group for people accounts to update their own mail.",
uuid: UUID_IDM_PEOPLE_SELF_MAIL_WRITE,
members: Vec::with_capacity(0),
..Default::default()
};
}
// at some point vs code just gives up on syntax highlighting inside lazy_static...
lazy_static! {
pub static ref IDM_ALL_PERSONS: BuiltinGroup = BuiltinGroup {
name: "idm_all_persons",
description: "Builtin IDM dynamic group containing all persons.",
uuid: UUID_IDM_ALL_PERSONS,
members: Vec::with_capacity(0),
dyngroup: true,
dyngroup_filter: Some(
Filter::And(vec![
Filter::Eq(Attribute::Class.to_string(), EntryClass::Person.to_string()),
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
])
),
extra_attributes: vec![
// Enable account policy by default
(Attribute::Class, EntryClass::AccountPolicy.to_value()),
// Enforce this is a system protected object
(Attribute::Class, EntryClass::System.to_value()),
// MFA By Default
(Attribute::CredentialTypeMinimum, CredentialType::Mfa.into()),
],
..Default::default()
};
pub static ref IDM_ALL_ACCOUNTS: BuiltinGroup = BuiltinGroup {
name: "idm_all_accounts",
description: "Builtin IDM dynamic group containing all entries that can authenticate.",
uuid: UUID_IDM_ALL_ACCOUNTS,
members: Vec::with_capacity(0),
dyngroup: true,
dyngroup_filter: Some(
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
),
extra_attributes: vec![
// Enable account policy by default
(Attribute::Class, EntryClass::AccountPolicy.to_value()),
// Enforce this is a system protected object
(Attribute::Class, EntryClass::System.to_value()),
],
..Default::default()
};
pub static ref IDM_UI_ENABLE_EXPERIMENTAL_FEATURES: BuiltinGroup = BuiltinGroup {
name: "idm_ui_enable_experimental_features",
description: "Members of this group will have access to experimental web UI features.",
uuid: UUID_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES,
entry_managed_by: Some(UUID_IDM_ADMINS),
extra_attributes: vec![
(Attribute::GrantUiHint, Value::UiHint(UiHint::ExperimentalFeatures))
],
..Default::default()
};
/// Members of this group will have access to read the mail attribute of all persons and service accounts.
pub static ref IDM_ACCOUNT_MAIL_READ: BuiltinGroup = BuiltinGroup {
name: "idm_account_mail_read",
description: "Members of this group will have access to read the mail attribute of all persons and service accounts.",
entry_managed_by: Some(UUID_IDM_ACCESS_CONTROL_ADMINS),
uuid: UUID_IDM_ACCOUNT_MAIL_READ,
..Default::default()
};
/// This must be the last group to init to include the UUID of the other high priv groups.
pub static ref IDM_HIGH_PRIVILEGE_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_high_privilege",
uuid: UUID_IDM_HIGH_PRIVILEGE,
entry_managed_by: Some(UUID_IDM_ACCESS_CONTROL_ADMINS),
description: "Builtin IDM provided groups with high levels of access that should be audited and limited in modification.",
members: vec![
UUID_SYSTEM_ADMINS,
UUID_IDM_ADMINS,
UUID_DOMAIN_ADMINS,
UUID_IDM_SERVICE_DESK,
UUID_IDM_RECYCLE_BIN_ADMINS,
UUID_IDM_SCHEMA_ADMINS,
UUID_IDM_ACCESS_CONTROL_ADMINS,
UUID_IDM_OAUTH2_ADMINS,
UUID_IDM_RADIUS_ADMINS,
UUID_IDM_ACCOUNT_POLICY_ADMINS,
UUID_IDM_RADIUS_SERVERS,
UUID_IDM_GROUP_ADMINS,
UUID_IDM_UNIX_ADMINS,
UUID_IDM_PEOPLE_PII_READ,
UUID_IDM_PEOPLE_ADMINS,
UUID_IDM_PEOPLE_ON_BOARDING,
UUID_IDM_SERVICE_ACCOUNT_ADMINS,
UUID_IDM_CLIENT_CERTIFICATE_ADMINS,
UUID_IDM_APPLICATION_ADMINS,
UUID_IDM_MAIL_ADMINS,
UUID_IDM_HIGH_PRIVILEGE,
],
..Default::default()
};
pub static ref BUILTIN_GROUP_APPLICATION_ADMINS_DL8: BuiltinGroup = BuiltinGroup {
name: "idm_application_admins",
uuid: UUID_IDM_APPLICATION_ADMINS,
description: "Builtin Application Administration Group.",
entry_managed_by: Some(UUID_IDM_ADMINS),
members: vec![UUID_IDM_ADMINS],
..Default::default()
};
}

View file

@ -0,0 +1,18 @@
use crate::constants::entries::{Attribute, EntryClass};
use crate::constants::uuids::UUID_KEY_PROVIDER_INTERNAL;
use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
use crate::value::Value;
lazy_static! {
pub static ref E_KEY_PROVIDER_INTERNAL_DL6: EntryInitNew = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::KeyProvider.to_value()),
(Attribute::Class, EntryClass::KeyProviderInternal.to_value()),
(Attribute::Uuid, Value::Uuid(UUID_KEY_PROVIDER_INTERNAL)),
(Attribute::Name, Value::new_iname("key_provider_internal")),
(
Attribute::Description,
Value::new_utf8s("The default database internal cryptographic key provider.")
)
);
}

View file

@ -0,0 +1,264 @@
mod access;
mod accounts;
mod groups;
mod key_providers;
mod schema;
mod system_config;
use self::access::*;
use self::accounts::*;
use self::groups::*;
use self::key_providers::*;
use self::schema::*;
use self::system_config::*;
use crate::prelude::EntryInitNew;
use kanidm_proto::internal::OperationError;
pub fn phase_1_schema_attrs() -> Vec<EntryInitNew> {
vec![
SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL.clone().into(),
SCHEMA_ATTR_SYNC_YIELD_AUTHORITY.clone().into(),
SCHEMA_ATTR_ACCOUNT_EXPIRE.clone().into(),
SCHEMA_ATTR_ACCOUNT_VALID_FROM.clone().into(),
SCHEMA_ATTR_API_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_AUTH_SESSION_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH.clone().into(),
SCHEMA_ATTR_BADLIST_PASSWORD.clone().into(),
SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN.clone().into(),
SCHEMA_ATTR_ATTESTED_PASSKEYS.clone().into(),
SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(),
SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(),
SCHEMA_ATTR_DOMAIN_NAME.clone().into(),
SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND.clone().into(),
SCHEMA_ATTR_DOMAIN_SSID.clone().into(),
SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_DOMAIN_UUID.clone().into(),
SCHEMA_ATTR_DYNGROUP_FILTER.clone().into(),
SCHEMA_ATTR_EC_KEY_PRIVATE.clone().into(),
SCHEMA_ATTR_ES256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_FERNET_PRIVATE_KEY_STR.clone().into(),
SCHEMA_ATTR_GIDNUMBER.clone().into(),
SCHEMA_ATTR_GRANT_UI_HINT.clone().into(),
SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY.clone().into(),
SCHEMA_ATTR_LOGINSHELL.clone().into(),
SCHEMA_ATTR_NAME_HISTORY.clone().into(),
SCHEMA_ATTR_NSUNIQUEID.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
.clone()
.into(),
SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE.clone().into(),
SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_NAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_OAUTH2_SESSION.clone().into(),
SCHEMA_ATTR_PASSKEYS.clone().into(),
SCHEMA_ATTR_PRIMARY_CREDENTIAL.clone().into(),
SCHEMA_ATTR_PRIVATE_COOKIE_KEY.clone().into(),
SCHEMA_ATTR_RADIUS_SECRET.clone().into(),
SCHEMA_ATTR_RS256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_SSH_PUBLICKEY.clone().into(),
SCHEMA_ATTR_SYNC_COOKIE.clone().into(),
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_UNIX_PASSWORD.clone().into(),
SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_DENIED_NAME.clone().into(),
SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM.clone().into(),
SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
// DL4
SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT_DL4
.clone()
.into(),
// DL5
// DL6
SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS_DL6.clone().into(),
SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6.clone().into(),
SCHEMA_ATTR_KEY_INTERNAL_DATA_DL6.clone().into(),
SCHEMA_ATTR_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_ROTATE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_REVOKE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_ES256_DL6.clone().into(),
// DL7
SCHEMA_ATTR_PATCH_LEVEL_DL7.clone().into(),
SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7.clone().into(),
SCHEMA_ATTR_REFERS_DL7.clone().into(),
SCHEMA_ATTR_CERTIFICATE_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7.clone().into(),
SCHEMA_ATTR_MAIL_DL7.clone().into(),
SCHEMA_ATTR_LEGALNAME_DL7.clone().into(),
SCHEMA_ATTR_DISPLAYNAME_DL7.clone().into(),
// DL8
SCHEMA_ATTR_LINKED_GROUP_DL8.clone().into(),
SCHEMA_ATTR_APPLICATION_PASSWORD_DL8.clone().into(),
SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK_DL8.clone().into(),
// DL9
SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9.clone().into(),
SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS_DL9.clone().into(),
]
}
pub fn phase_2_schema_classes() -> Vec<EntryInitNew> {
vec![
SCHEMA_CLASS_DYNGROUP.clone().into(),
SCHEMA_CLASS_ORGPERSON.clone().into(),
SCHEMA_CLASS_POSIXACCOUNT.clone().into(),
SCHEMA_CLASS_POSIXGROUP.clone().into(),
SCHEMA_CLASS_SYSTEM_CONFIG.clone().into(),
// DL4
SCHEMA_CLASS_OAUTH2_RS_PUBLIC_DL4.clone().into(),
// DL5
SCHEMA_CLASS_ACCOUNT_DL5.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_BASIC_DL5.clone().into(),
// DL6
SCHEMA_CLASS_GROUP_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_INTERNAL_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWT_ES256_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWE_A128GCM_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_INTERNAL_DL6.clone().into(),
// DL7
SCHEMA_CLASS_SERVICE_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_SYNC_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_CLIENT_CERTIFICATE_DL7.clone().into(),
// DL8
SCHEMA_CLASS_ACCOUNT_POLICY_DL8.clone().into(),
SCHEMA_CLASS_APPLICATION_DL8.clone().into(),
SCHEMA_CLASS_PERSON_DL8.clone().into(),
// DL9
SCHEMA_CLASS_OAUTH2_RS_DL9.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL9.clone().into(),
]
}
pub fn phase_3_key_provider() -> Vec<EntryInitNew> {
vec![E_KEY_PROVIDER_INTERNAL_DL6.clone()]
}
pub fn phase_4_system_entries() -> Vec<EntryInitNew> {
vec![
E_SYSTEM_INFO_V1.clone(),
E_DOMAIN_INFO_DL6.clone(),
E_SYSTEM_CONFIG_V1.clone(),
]
}
pub fn phase_5_builtin_admin_entries() -> Result<Vec<EntryInitNew>, OperationError> {
Ok(vec![
BUILTIN_ACCOUNT_ADMIN.clone().into(),
BUILTIN_ACCOUNT_IDM_ADMIN.clone().into(),
BUILTIN_GROUP_SYSTEM_ADMINS_V1.clone().try_into()?,
BUILTIN_GROUP_IDM_ADMINS_V1.clone().try_into()?,
// We need to push anonymous *after* groups due to entry-managed-by
BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into(),
])
}
pub fn phase_6_builtin_non_admin_entries() -> Result<Vec<EntryInitNew>, OperationError> {
Ok(vec![
BUILTIN_GROUP_DOMAIN_ADMINS.clone().try_into()?,
BUILTIN_GROUP_SCHEMA_ADMINS.clone().try_into()?,
BUILTIN_GROUP_ACCESS_CONTROL_ADMINS.clone().try_into()?,
BUILTIN_GROUP_UNIX_ADMINS.clone().try_into()?,
BUILTIN_GROUP_RECYCLE_BIN_ADMINS.clone().try_into()?,
BUILTIN_GROUP_SERVICE_DESK.clone().try_into()?,
BUILTIN_GROUP_OAUTH2_ADMINS.clone().try_into()?,
BUILTIN_GROUP_RADIUS_SERVICE_ADMINS.clone().try_into()?,
BUILTIN_GROUP_ACCOUNT_POLICY_ADMINS.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_ADMINS.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_PII_READ.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_ON_BOARDING.clone().try_into()?,
BUILTIN_GROUP_SERVICE_ACCOUNT_ADMINS.clone().try_into()?,
BUILTIN_GROUP_MAIL_SERVICE_ADMINS_DL8.clone().try_into()?,
IDM_GROUP_ADMINS_V1.clone().try_into()?,
IDM_ALL_PERSONS.clone().try_into()?,
IDM_ALL_ACCOUNTS.clone().try_into()?,
BUILTIN_IDM_RADIUS_SERVERS_V1.clone().try_into()?,
BUILTIN_IDM_MAIL_SERVERS_DL8.clone().try_into()?,
BUILTIN_GROUP_PEOPLE_SELF_NAME_WRITE_DL7
.clone()
.try_into()?,
IDM_PEOPLE_SELF_MAIL_WRITE_DL7.clone().try_into()?,
BUILTIN_GROUP_CLIENT_CERTIFICATE_ADMINS_DL7
.clone()
.try_into()?,
BUILTIN_GROUP_APPLICATION_ADMINS_DL8.clone().try_into()?,
// Write deps on read.clone().try_into()?, so write must be added first.
// All members must exist before we write HP
IDM_HIGH_PRIVILEGE_DL8.clone().try_into()?,
// other things
IDM_UI_ENABLE_EXPERIMENTAL_FEATURES.clone().try_into()?,
IDM_ACCOUNT_MAIL_READ.clone().try_into()?,
])
}
pub fn phase_7_builtin_access_control_profiles() -> Vec<EntryInitNew> {
vec![
// Built in access controls.
IDM_ACP_RECYCLE_BIN_SEARCH_V1.clone().into(),
IDM_ACP_RECYCLE_BIN_REVIVE_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_ATTRS_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_CLASSES_V1.clone().into(),
IDM_ACP_ACP_MANAGE_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGED_BY_MODIFY_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SYNC_ACCOUNT_MANAGE_V1.clone().into(),
IDM_ACP_RADIUS_SERVERS_V1.clone().into(),
IDM_ACP_RADIUS_SECRET_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_SELF_WRITE_MAIL_V1.clone().into(),
IDM_ACP_ACCOUNT_SELF_WRITE_V1.clone().into(),
IDM_ACP_ALL_ACCOUNTS_POSIX_READ_V1.clone().into(),
IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1
.clone()
.into(),
IDM_ACP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_HP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_GROUP_READ_V1.clone().into(),
IDM_ACP_ACCOUNT_UNIX_EXTEND_V1.clone().into(),
IDM_ACP_PEOPLE_PII_READ_V1.clone().into(),
IDM_ACP_PEOPLE_PII_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_READ_V1.clone().into(),
IDM_ACP_PEOPLE_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_DELETE_V1.clone().into(),
IDM_ACP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_HP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_CREATE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_DELETE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_HP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_SERVICE_ACCOUNT_MANAGE_V1.clone().into(),
// DL4
// DL5
// DL6
IDM_ACP_PEOPLE_CREATE_DL6.clone().into(),
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone().into(),
// DL7
IDM_ACP_SELF_NAME_WRITE_DL7.clone().into(),
IDM_ACP_HP_CLIENT_CERTIFICATE_MANAGER_DL7.clone().into(),
// DL8
IDM_ACP_SELF_READ_DL8.clone().into(),
IDM_ACP_SELF_WRITE_DL8.clone().into(),
IDM_ACP_APPLICATION_MANAGE_DL8.clone().into(),
IDM_ACP_APPLICATION_ENTRY_MANAGER_DL8.clone().into(),
IDM_ACP_MAIL_SERVERS_DL8.clone().into(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone().into(),
// DL9
IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(),
IDM_ACP_GROUP_MANAGE_DL9.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL9.clone().into(),
]
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
pub(crate) mod dl10;
pub(crate) mod dl8;
pub(crate) mod dl9;
mod types;
#[cfg(test)]
pub(crate) use dl10::accounts::BUILTIN_ACCOUNT_ANONYMOUS_DL6 as BUILTIN_ACCOUNT_ANONYMOUS;
#[cfg(test)]
use self::types::BuiltinAccount;
#[cfg(test)]
lazy_static! {
/// Builtin System Admin account.
pub static ref BUILTIN_ACCOUNT_TEST_PERSON: BuiltinAccount = BuiltinAccount {
account_type: kanidm_proto::v1::AccountType::Person,
entry_managed_by: None,
name: "test_person",
uuid: crate::constants::uuids::UUID_TESTPERSON_1,
description: "Test Person",
displayname: "Test Person",
};
}

View file

@ -0,0 +1,83 @@
//! Constant Entries for the IDM
use crate::constants::uuids::*;
use crate::entry::EntryInitNew;
use crate::prelude::EntryClass;
use crate::value::Value;
pub use kanidm_proto::attribute::Attribute;
use kanidm_proto::v1::AccountType;
use uuid::Uuid;
#[derive(Debug, Clone)]
/// Built in accounts such as anonymous, idm_admin and admin
pub struct BuiltinAccount {
pub account_type: kanidm_proto::v1::AccountType,
pub entry_managed_by: Option<uuid::Uuid>,
pub name: &'static str,
pub uuid: Uuid,
pub description: &'static str,
pub displayname: &'static str,
}
#[cfg(test)]
impl Default for BuiltinAccount {
fn default() -> Self {
BuiltinAccount {
account_type: AccountType::ServiceAccount,
entry_managed_by: None,
name: "",
uuid: Uuid::new_v4(),
description: "<set description>",
displayname: "<set displayname>",
}
}
}
#[cfg(test)]
impl From<BuiltinAccount> for crate::idm::account::Account {
fn from(value: BuiltinAccount) -> Self {
Self {
name: value.name.to_string(),
uuid: value.uuid,
displayname: value.displayname.to_string(),
spn: format!("{}@example.com", value.name),
mail_primary: None,
mail: Vec::with_capacity(0),
..Default::default()
}
}
}
impl From<BuiltinAccount> for EntryInitNew {
fn from(value: BuiltinAccount) -> Self {
let mut entry = EntryInitNew::new();
entry.add_ava(Attribute::Name, Value::new_iname(value.name));
#[allow(clippy::panic)]
if value.uuid >= DYNAMIC_RANGE_MINIMUM_UUID {
panic!("Builtin ACP has invalid UUID! {:?}", value);
}
entry.add_ava(Attribute::Uuid, Value::Uuid(value.uuid));
entry.add_ava(Attribute::Description, Value::new_utf8s(value.description));
entry.add_ava(Attribute::DisplayName, Value::new_utf8s(value.displayname));
if let Some(entry_manager) = value.entry_managed_by {
entry.add_ava(Attribute::EntryManagedBy, Value::Refer(entry_manager));
}
entry.set_ava(
Attribute::Class,
vec![
EntryClass::Account.to_value(),
EntryClass::MemberOf.to_value(),
EntryClass::Object.to_value(),
],
);
match value.account_type {
AccountType::Person => entry.add_ava(Attribute::Class, EntryClass::Person.to_value()),
AccountType::ServiceAccount => {
entry.add_ava(Attribute::Class, EntryClass::ServiceAccount.to_value())
}
}
entry
}
}

View file

@ -571,35 +571,43 @@ impl Plugin for MemberOf {
Err(e) => return vec![e],
};
// First we have to build a direct membership map. This saves us
// needing to run queries since we already have every entry on hand
// from the all_cand search.
let mut direct_membership_map: BTreeMap<Uuid, BTreeSet<Uuid>> = Default::default();
let pv_class: PartialValue = EntryClass::Group.into();
for entry in all_cand.iter() {
if !entry.attribute_equality(Attribute::Class, &pv_class) {
// Not a group, move on.
continue;
}
let group_uuid = entry.get_uuid();
let member_iter = entry
.get_ava_refer(Attribute::Member)
.into_iter()
.flat_map(|set| set.iter())
.chain(
entry
.get_ava_refer(Attribute::DynMember)
.into_iter()
.flat_map(|set| set.iter()),
);
for member_uuid in member_iter {
let member_groups = direct_membership_map.entry(*member_uuid).or_default();
member_groups.insert(group_uuid);
}
}
// for each entry in the DB (live).
for e in all_cand {
let uuid = e.get_uuid();
let filt_in = filter!(f_and!([
f_eq(Attribute::Class, EntryClass::Group.into()),
f_or!([
f_eq(Attribute::Member, PartialValue::Refer(uuid)),
f_eq(Attribute::DynMember, PartialValue::Refer(uuid))
])
]));
// what groups is this entry a direct member of?
let direct_memberof = match qs
.internal_search(filt_in)
.map_err(|_| ConsistencyError::QueryServerSearchFailure)
{
Ok(d_mo) => d_mo,
Err(e) => return vec![Err(e)],
};
// for all direct -> add uuid to map
let d_groups_set: BTreeSet<Uuid> =
direct_memberof.iter().map(|e| e.get_uuid()).collect();
let d_groups_set = if d_groups_set.is_empty() {
None
} else {
Some(d_groups_set)
};
let d_groups_set: Option<&BTreeSet<Uuid>> = direct_membership_map.get(&uuid);
trace!(
"DMO search groups {:?} -> {:?}",
@ -607,12 +615,16 @@ impl Plugin for MemberOf {
d_groups_set
);
// Remember, we only need to check direct memberships, because when memberof
// it applies it clones dmo -> mo, so validation of all dmo sets implies mo is
// valid (and a subset) of dmo.
match (e.get_ava_set(Attribute::DirectMemberOf), d_groups_set) {
(Some(edmos), Some(b)) => {
// Can they both be reference sets?
match edmos.as_refer_set() {
Some(a) => {
let diff: Vec<_> = a.symmetric_difference(&b).collect();
let diff: Vec<_> = a.symmetric_difference(b).collect();
if !diff.is_empty() {
error!(
"MemberOfInvalid: Entry {}, DMO has inconsistencies",
@ -636,7 +648,7 @@ impl Plugin for MemberOf {
}
(entry_direct_member_of, expected_direct_groups) => {
error!(
"MemberOfInvalid directmemberof set and DMO search set differ in size: {}",
"MemberOfInvalid directmemberof set and DMO search set differ in presence: {}",
e.get_display_id()
);
// trace!(?e);
@ -645,19 +657,6 @@ impl Plugin for MemberOf {
r.push(Err(ConsistencyError::MemberOfInvalid(e.get_id())));
}
}
// Could check all dmos in mos?
/* To check nested! */
// add all direct to a stack
// for all in stack
// check their direct memberships
// if not in map
// add to map
// push to stack
// check mo == map set
// if not, consistency error!
}
r

View file

@ -1159,6 +1159,7 @@ mod tests {
},
Access, AccessClass, AccessControls, AccessControlsTransaction, AccessEffectivePermission,
};
use crate::migration_data::BUILTIN_ACCOUNT_ANONYMOUS;
use crate::prelude::*;
use crate::valueset::ValueSetIname;
@ -2511,6 +2512,7 @@ mod tests {
#[test]
fn test_access_enforce_scope_delete() {
sketching::test_init();
let ev1 = E_TESTPERSON_1.clone().into_sealed_committed();
let r_set = vec![Arc::new(ev1)];
@ -3052,7 +3054,7 @@ mod tests {
test_acp_search_reduce!(&se_a, vec![], r_set.clone(), ex_a_reduced);
// Check that anonymous is denied even though it's a member of the group.
let anon: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into();
let anon: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
let mut anon = anon.into_invalid_new();
anon.set_ava_set(&Attribute::MemberOf, ValueSetRefer::new(UUID_TEST_GROUP_1));
@ -3352,7 +3354,7 @@ mod tests {
#[test]
fn test_access_delete_protect_system_ranges() {
let ev1: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS_DL6.clone().into();
let ev1: EntryInitNew = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
let ev1 = ev1.into_sealed_committed();
let r_set = vec![Arc::new(ev1)];

View file

@ -1,12 +1,12 @@
use super::ServerPhase;
use crate::migration_data;
use crate::prelude::*;
use kanidm_proto::internal::{
DomainUpgradeCheckItem as ProtoDomainUpgradeCheckItem,
DomainUpgradeCheckReport as ProtoDomainUpgradeCheckReport,
DomainUpgradeCheckStatus as ProtoDomainUpgradeCheckStatus,
};
use super::ServerPhase;
use std::cmp::Ordering;
impl QueryServer {
#[instrument(level = "info", name = "system_initialisation", skip_all)]
@ -48,17 +48,24 @@ impl QueryServer {
debug!(?db_domain_version, "Before setting internal domain info");
if db_domain_version == 0 {
// No domain info was present, so neither was the rest of the IDM. We need to bootstrap
// the base-schema here.
write_txn.initialise_schema_idm()?;
// This is here to catch when we increase domain levels but didn't create the migration
// hooks. If this fails it probably means you need to add another migration hook
// in the above.
debug_assert!(domain_target_level <= DOMAIN_MAX_LEVEL);
write_txn.reload()?;
// Since we just loaded in a ton of schema, lets reindex it to make
// sure that some base IDM operations are fast. Since this is still
// very early in the bootstrap process, and very few entries exist,
// reindexing is very fast here.
write_txn.reindex(false)?;
// No domain info was present, so neither was the rest of the IDM. Bring up the
// full IDM here.
match domain_target_level {
DOMAIN_LEVEL_8 => write_txn.migrate_domain_7_to_8()?,
DOMAIN_LEVEL_9 => write_txn.migrate_domain_8_to_9()?,
DOMAIN_LEVEL_10 => write_txn.migrate_domain_9_to_10()?,
DOMAIN_LEVEL_11 => write_txn.migrate_domain_10_to_11()?,
_ => {
error!("Invalid requested domain target level for server bootstrap");
debug_assert!(false);
return Err(OperationError::MG0009InvalidTargetLevelForBootstrap);
}
}
} else {
// Domain info was present, so we need to reflect that in our server
// domain structures. If we don't do this, the in memory domain level
@ -71,23 +78,11 @@ impl QueryServer {
write_txn.force_domain_reload();
write_txn.reload()?;
}
// Indicate the schema is now ready, which allows dyngroups to work when they
// are created in the next phase of migrations.
write_txn.set_phase(ServerPhase::SchemaReady);
// Indicate the schema is now ready, which allows dyngroups to work when they
// are created in the next phase of migrations.
write_txn.set_phase(ServerPhase::SchemaReady);
// No domain info was present, so neither was the rest of the IDM. We need to bootstrap
// the base entries here.
if db_domain_version == 0 {
// Init idm will now set the system config version and minimum domain
// level if none was present
write_txn.initialise_domain_info()?;
// In this path because we create the dyn groups they are immediately added to the
// dyngroup cache and begin to operate.
write_txn.initialise_idm()?;
} else {
// #2756 - if we *aren't* creating the base IDM entries, then we
// need to force dyn groups to reload since we're now at schema
// ready. This is done indirectly by ... reloading the schema again.
@ -97,13 +92,13 @@ impl QueryServer {
// itself or a change to schema reloading. Since we aren't changing the
// dyngroup here, we have to go via the schema reload path.
write_txn.force_schema_reload();
};
// Reload as init idm affects access controls.
write_txn.reload()?;
// Reload as init idm affects access controls.
write_txn.reload()?;
// Domain info is now ready and reloaded, we can proceed.
write_txn.set_phase(ServerPhase::DomainInfoReady);
// Domain info is now ready and reloaded, we can proceed.
write_txn.set_phase(ServerPhase::DomainInfoReady);
}
// This is the start of domain info related migrations which we will need in future
// to handle replication. Due to the access control rework, and the addition of "managed by"
@ -126,20 +121,18 @@ impl QueryServer {
// If the database domain info is a lower version than our target level, we reload.
if domain_info_version < domain_target_level {
write_txn
.internal_modify_uuid(
UUID_DOMAIN_INFO,
&ModifyList::new_purge_and_set(
Attribute::Version,
Value::new_uint32(domain_target_level),
),
)
.internal_apply_domain_migration(domain_target_level)
.map(|()| {
warn!("Domain level has been raised to {}", domain_target_level);
})?;
// Reload if anything in migrations requires it - this triggers the domain migrations
// which in turn can trigger schema reloads etc.
reload_required = true;
// which in turn can trigger schema reloads etc. If the server was just brought up
// then we don't need the extra reload since we are already at the correct
// version of the server, and this call to set the target level is just for persistance
// of the value.
if domain_info_version != 0 {
reload_required = true;
}
} else if domain_development_taint {
// This forces pre-release versions to re-migrate each start up. This solves
// the domain-version-sprawl issue so that during a development cycle we can
@ -182,9 +175,6 @@ impl QueryServer {
// we would have skipped the patch level fix which needs to have occurred *first*.
if reload_required {
write_txn.reload()?;
// We are not yet at the schema phase where reindexes will auto-trigger
// so if one was required, do it now.
write_txn.reindex(false)?;
}
// Now set the db/domain devel taint flag to match our current release status
@ -206,7 +196,8 @@ impl QueryServer {
// We are ready to run
write_txn.set_phase(ServerPhase::Running);
// Commit all changes, this also triggers the reload.
// Commit all changes, this also triggers the final reload, this should be a no-op
// since we already did all the needed loads above.
write_txn.commit()?;
debug!("Database version check and migrations success! ☀️ ");
@ -217,7 +208,6 @@ impl QueryServer {
impl QueryServerWriteTransaction<'_> {
/// Apply a domain migration `to_level`. Panics if `to_level` is not greater than the active
/// level.
#[cfg(test)]
pub(crate) fn internal_apply_domain_migration(
&mut self,
to_level: u32,
@ -230,28 +220,160 @@ impl QueryServerWriteTransaction<'_> {
.and_then(|()| self.reload())
}
/// Given a set of entries, create entries that do not exist, and migrate entries
/// that do exist. This operation always applies the create step first, and
/// migrations second.
///
/// This means if you have *ordering* requirements for the entries in the migration
/// then you *MUST* express that ordering requirement in multiple subsequent calls
/// to this function.
#[instrument(level = "debug", skip_all)]
fn internal_migrate_or_create_batch(
&mut self,
_msg: &str,
entries: Vec<EntryInitNew>,
) -> Result<(), OperationError> {
// Pull out the uuids of all the entries.
let mut entries_w_uuid: Vec<(Uuid, EntryInitNew)> = Vec::with_capacity(entries.len());
for entry in entries.into_iter() {
let entry_uuid = entry
.get_uuid()
.ok_or(OperationError::MG0010MigrationDataMissingUuid)?;
entries_w_uuid.push((entry_uuid, entry));
}
// Now we can search for the entries.
let inner: Vec<_> = entries_w_uuid
.iter()
.map(|(u, _)| f_eq(Attribute::Uuid, PartialValue::Uuid(*u)))
.collect();
let filter = filter!(f_or(inner));
let mut entries = self.internal_search(filter)?;
// Now we have to partition the entries_w_uuid into entries that
// need creation, and ones that need to be migrated.
//
// To do this, we sort both lists by uuid and then look through them.
entries.sort_unstable_by_key(|e| e.get_uuid());
entries_w_uuid.sort_unstable_by_key(|(u, _)| *u);
let mut entries_to_create = Vec::with_capacity(entries_w_uuid.len() - entries.len());
let mut entries_to_migrate = Vec::with_capacity(entries.len());
let mut entry_iter = entries.into_iter();
let mut next_entry = entry_iter.next();
// This looks at both lists like:
//
// migrate: [ A, B, C, D, E ]
// db: [ B, C, E ]
//
// As we iter we start in the migrate list:
//
// v
// migrate: [ A, B, C, D, E ]
// db: [ B, C, E ]
// ^
//
// Since uuid A != B, and A < B, we push A to the create set.
//
// v
// migrate: [ A, B, C, D, E ]
// db: [ B, C, E ]
// ^
//
// Now B == B, so we push this to the migrate set and advance both.
//
// This normally will continue, but we have to account for *one* case
// that should be impossible, but paranoia is good.
//
// That case is a uuid in db that isn't in migrate.
//
// v
// migrate: [ A, B, D, E ]
// db: [ B, C, E ]
// ^
// In this case, we have to advance db iter, but not the entries_w_uuid iter
// so that D is still considered correctly.
'outer: for (u, entry) in entries_w_uuid.into_iter() {
// There is something here to compare against - is it our entry?
'inner: loop {
if let Some(ref db_entry) = next_entry {
match u.cmp(&db_entry.get_uuid()) {
Ordering::Equal => {
// This entry needs migration.
entries_to_migrate.push((u, entry));
// Advanced the db_entry iter.
next_entry = entry_iter.next();
continue 'outer;
}
Ordering::Less => {
// Since our uuid is less than db_entry, iterate only the
// entries_w_uuid set
entries_to_create.push(entry);
continue 'outer;
}
Ordering::Greater => {
// IMPOSSIBLE CASE
debug_assert!(false);
next_entry = entry_iter.next();
continue 'inner;
}
}
} else {
// Nothing left to compare, must just need create
entries_to_create.push(entry);
continue 'outer;
}
}
}
// Given the set of entries to create, perform that now.
if !entries_to_create.is_empty() {
self.internal_create(entries_to_create)?;
}
// Apply batched modifications now.
if !entries_to_migrate.is_empty() {
let mut modifications: Vec<(Uuid, _)> = Vec::with_capacity(entries_to_migrate.len());
for (u, entry) in entries_to_migrate {
/*
// If we need to ignore attrs, do so here.
for attr in attrs.iter() {
e.remove_ava(attr);
}
*/
let modlist = entry
.gen_modlist_assert(&self.schema)
.map_err(OperationError::SchemaViolation)?;
modifications.push((u, modlist));
}
self.internal_batch_modify(modifications.into_iter())?;
}
// Complete!!!
Ok(())
}
/// - If the thing exists:
/// - Ensure the set of attributes match and are present
/// (but don't delete multivalue, or extended attributes in the situation.
/// - If not:
/// - Create the entry
///
/// This will extra classes an attributes alone!
/// This will ignore the specified list of attributes, so that if an admin has
/// modified those values then we don't stomp them.
///
/// NOTE: `gen_modlist*` IS schema aware and will handle multivalue correctly!
pub fn internal_migrate_or_create(
&mut self,
e: Entry<EntryInit, EntryNew>,
) -> Result<(), OperationError> {
self.internal_migrate_or_create_ignore_attrs(e, &[])
}
/// This is the same as [QueryServerWriteTransaction::internal_migrate_or_create] but it will ignore the specified
/// list of attributes, so that if an admin has modified those values then we don't
/// stomp them.
#[instrument(level = "trace", skip_all)]
pub fn internal_migrate_or_create_ignore_attrs(
fn internal_migrate_or_create_ignore_attrs(
&mut self,
mut e: Entry<EntryInit, EntryNew>,
attrs: &[Attribute],
@ -294,6 +416,75 @@ impl QueryServerWriteTransaction<'_> {
}
}
/// Migration domain level 7 to 8 (1.4.0)
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_7_to_8(&mut self) -> Result<(), OperationError> {
if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_9 {
error!("Unable to raise domain level from 8 to 9.");
return Err(OperationError::MG0004DomainLevelInDevelopment);
}
// =========== Apply changes ==============
self.internal_migrate_or_create_batch(
"phase 1 - schema attrs",
migration_data::dl8::phase_1_schema_attrs(),
)?;
self.internal_migrate_or_create_batch(
"phase 2 - schema classes",
migration_data::dl8::phase_2_schema_classes(),
)?;
// Reload for the new schema.
self.reload()?;
// Reindex?
self.reindex(false)?;
// Set Phase
self.set_phase(ServerPhase::SchemaReady);
self.internal_migrate_or_create_batch(
"phase 3 - key provider",
migration_data::dl8::phase_3_key_provider(),
)?;
// Reload for the new key providers
self.reload()?;
self.internal_migrate_or_create_batch(
"phase 4 - system entries",
migration_data::dl8::phase_4_system_entries(),
)?;
// Reload for the new system entries
self.reload()?;
// Domain info is now ready and reloaded, we can proceed.
self.set_phase(ServerPhase::DomainInfoReady);
// Bring up the IDM entries.
self.internal_migrate_or_create_batch(
"phase 5 - builtin admin entries",
migration_data::dl8::phase_5_builtin_admin_entries()?,
)?;
self.internal_migrate_or_create_batch(
"phase 6 - builtin not admin entries",
migration_data::dl8::phase_6_builtin_non_admin_entries()?,
)?;
self.internal_migrate_or_create_batch(
"phase 7 - builtin access control profiles",
migration_data::dl8::phase_7_builtin_access_control_profiles(),
)?;
// Reload for all new access controls.
self.reload()?;
Ok(())
}
/// Migration domain level 8 to 9 (1.5.0)
#[instrument(level = "info", skip_all)]
pub(crate) fn migrate_domain_8_to_9(&mut self) -> Result<(), OperationError> {
@ -303,39 +494,61 @@ impl QueryServerWriteTransaction<'_> {
}
// =========== Apply changes ==============
self.internal_migrate_or_create_batch(
"phase 1 - schema attrs",
migration_data::dl9::phase_1_schema_attrs(),
)?;
// Now update schema
let idm_schema_changes = [
SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9.clone().into(),
SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS_DL9.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_DL9.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL9.clone().into(),
];
idm_schema_changes
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_8_to_9 -> Error");
err
})?;
self.internal_migrate_or_create_batch(
"phase 2 - schema classes",
migration_data::dl9::phase_2_schema_classes(),
)?;
// Reload for the new schema.
self.reload()?;
let idm_data = [
IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(),
IDM_ACP_GROUP_MANAGE_DL9.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL9.clone().into(),
];
// Reindex?
self.reindex(false)?;
idm_data
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_8_to_9 -> Error");
err
})?;
// Set Phase
self.set_phase(ServerPhase::SchemaReady);
self.internal_migrate_or_create_batch(
"phase 3 - key provider",
migration_data::dl9::phase_3_key_provider(),
)?;
// Reload for the new key providers
self.reload()?;
self.internal_migrate_or_create_batch(
"phase 4 - system entries",
migration_data::dl9::phase_4_system_entries(),
)?;
// Reload for the new system entries
self.reload()?;
// Domain info is now ready and reloaded, we can proceed.
self.set_phase(ServerPhase::DomainInfoReady);
// Bring up the IDM entries.
self.internal_migrate_or_create_batch(
"phase 5 - builtin admin entries",
migration_data::dl9::phase_5_builtin_admin_entries()?,
)?;
self.internal_migrate_or_create_batch(
"phase 6 - builtin not admin entries",
migration_data::dl9::phase_6_builtin_non_admin_entries()?,
)?;
self.internal_migrate_or_create_batch(
"phase 7 - builtin access control profiles",
migration_data::dl9::phase_7_builtin_access_control_profiles(),
)?;
// Reload for all new access controls.
self.reload()?;
Ok(())
@ -350,66 +563,10 @@ impl QueryServerWriteTransaction<'_> {
debug_assert!(*self.phase >= ServerPhase::SchemaReady);
let idm_data = [
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone().into(),
IDM_ACP_ACCOUNT_SELF_WRITE_V1.clone().into(),
IDM_ACP_ACCOUNT_UNIX_EXTEND_V1.clone().into(),
IDM_ACP_ACP_MANAGE_V1.clone().into(),
IDM_ACP_ALL_ACCOUNTS_POSIX_READ_V1.clone().into(),
IDM_ACP_APPLICATION_ENTRY_MANAGER_DL8.clone().into(),
IDM_ACP_APPLICATION_MANAGE_DL8.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL8.clone().into(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGED_BY_MODIFY_V1.clone().into(),
IDM_ACP_GROUP_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_GROUP_MANAGE_DL6.clone().into(),
IDM_ACP_GROUP_READ_V1.clone().into(),
IDM_ACP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_HP_CLIENT_CERTIFICATE_MANAGER_DL7.clone().into(),
IDM_ACP_HP_GROUP_UNIX_MANAGE_V1.clone().into(),
IDM_ACP_HP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_HP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_MAIL_SERVERS_DL8.clone().into(),
IDM_ACP_OAUTH2_MANAGE_DL7.clone().into(),
IDM_ACP_PEOPLE_CREATE_DL6.clone().into(),
IDM_ACP_PEOPLE_CREDENTIAL_RESET_V1.clone().into(),
IDM_ACP_PEOPLE_DELETE_V1.clone().into(),
IDM_ACP_PEOPLE_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_PII_MANAGE_V1.clone().into(),
IDM_ACP_PEOPLE_PII_READ_V1.clone().into(),
IDM_ACP_PEOPLE_READ_V1.clone().into(),
IDM_ACP_PEOPLE_SELF_WRITE_MAIL_V1.clone().into(),
IDM_ACP_RADIUS_SECRET_MANAGE_V1.clone().into(),
IDM_ACP_RADIUS_SERVERS_V1.clone().into(),
IDM_ACP_RECYCLE_BIN_REVIVE_V1.clone().into(),
IDM_ACP_RECYCLE_BIN_SEARCH_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_ATTRS_V1.clone().into(),
IDM_ACP_SCHEMA_WRITE_CLASSES_V1.clone().into(),
IDM_ACP_SELF_NAME_WRITE_DL7.clone().into(),
IDM_ACP_SELF_READ_DL8.clone().into(),
IDM_ACP_SELF_WRITE_DL8.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_CREATE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_DELETE_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1
.clone()
.into(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGER_V1.clone().into(),
IDM_ACP_SERVICE_ACCOUNT_MANAGE_V1.clone().into(),
IDM_ACP_SYNC_ACCOUNT_MANAGE_V1.clone().into(),
IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1
.clone()
.into(),
];
idm_data
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_patch_level_2 -> Error");
err
})?;
self.internal_migrate_or_create_batch(
"patch level 2 - access control profiles",
migration_data::dl9::phase_7_builtin_access_control_profiles(),
)?;
self.reload()?;
@ -425,21 +582,62 @@ impl QueryServerWriteTransaction<'_> {
}
// =========== Apply changes ==============
self.internal_migrate_or_create_batch(
"phase 1 - schema attrs",
migration_data::dl10::phase_1_schema_attrs(),
)?;
// Now update schema
let idm_schema_changes = [
SCHEMA_ATTR_DENIED_NAME_DL10.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL10.clone().into(),
SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES.clone().into(),
];
self.internal_migrate_or_create_batch(
"phase 2 - schema classes",
migration_data::dl10::phase_2_schema_classes(),
)?;
idm_schema_changes
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry))
.map_err(|err| {
error!(?err, "migrate_domain_9_to_10 -> Error");
err
})?;
// Reload for the new schema.
self.reload()?;
// Since we just loaded in a ton of schema, lets reindex it incase we added
// new indexes, or this is a bootstrap and we have no indexes yet.
self.reindex(false)?;
// Set Phase
// Indicate the schema is now ready, which allows dyngroups to work when they
// are created in the next phase of migrations.
self.set_phase(ServerPhase::SchemaReady);
self.internal_migrate_or_create_batch(
"phase 3 - key provider",
migration_data::dl10::phase_3_key_provider(),
)?;
// Reload for the new key providers
self.reload()?;
self.internal_migrate_or_create_batch(
"phase 4 - system entries",
migration_data::dl10::phase_4_system_entries(),
)?;
// Reload for the new system entries
self.reload()?;
// Domain info is now ready and reloaded, we can proceed.
self.set_phase(ServerPhase::DomainInfoReady);
// Bring up the IDM entries.
self.internal_migrate_or_create_batch(
"phase 5 - builtin admin entries",
migration_data::dl10::phase_5_builtin_admin_entries()?,
)?;
self.internal_migrate_or_create_batch(
"phase 6 - builtin not admin entries",
migration_data::dl10::phase_6_builtin_non_admin_entries()?,
)?;
self.internal_migrate_or_create_batch(
"phase 7 - builtin access control profiles",
migration_data::dl10::phase_7_builtin_access_control_profiles(),
)?;
self.reload()?;
@ -458,341 +656,21 @@ impl QueryServerWriteTransaction<'_> {
}
#[instrument(level = "info", skip_all)]
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
admin_debug!("initialise_schema_core -> start ...");
pub(crate) fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
// Load in all the "core" schema, that we already have in "memory".
let entries = self.schema.to_entries();
// admin_debug!("Dumping schemas: {:?}", entries);
let r = self
.internal_migrate_or_create_batch("initialise schema core", self.schema.to_entries());
// internal_migrate_or_create.
let r: Result<_, _> = entries.into_iter().try_for_each(|e| {
trace!(?e, "init schema entry");
self.internal_migrate_or_create(e)
});
if r.is_ok() {
admin_debug!("initialise_schema_core -> Ok!");
if let Err(err) = &r {
error!(?err);
debug_assert!(false);
} else {
admin_error!(?r, "initialise_schema_core -> Error");
debug!("Ok!");
}
// why do we have error handling if it's always supposed to be `Ok`?
debug_assert!(r.is_ok());
r
}
#[instrument(level = "info", skip_all)]
pub fn initialise_schema_idm(&mut self) -> Result<(), OperationError> {
admin_debug!("initialise_schema_idm -> start ...");
// ⚠️ DOMAIN LEVEL 1 SCHEMA ATTRIBUTES ⚠️
// Future schema attributes need to be added via migrations.
//
// DO NOT MODIFY THIS DEFINITION
let idm_schema_attrs = [
SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL.clone().into(),
SCHEMA_ATTR_SYNC_YIELD_AUTHORITY.clone().into(),
];
let r: Result<(), _> = idm_schema_attrs
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry));
if r.is_err() {
error!(res = ?r, "initialise_schema_idm -> Error");
}
debug_assert!(r.is_ok());
// ⚠️ DOMAIN LEVEL 1 SCHEMA ATTRIBUTES ⚠️
// Future schema classes need to be added via migrations.
//
// DO NOT MODIFY THIS DEFINITION
let idm_schema: Vec<EntryInitNew> = vec![
// SCHEMA_ATTR_MAIL.clone().into(),
SCHEMA_ATTR_ACCOUNT_EXPIRE.clone().into(),
SCHEMA_ATTR_ACCOUNT_VALID_FROM.clone().into(),
SCHEMA_ATTR_API_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_AUTH_SESSION_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH.clone().into(),
SCHEMA_ATTR_BADLIST_PASSWORD.clone().into(),
SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN.clone().into(),
SCHEMA_ATTR_ATTESTED_PASSKEYS.clone().into(),
// SCHEMA_ATTR_DISPLAYNAME.clone().into(),
SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(),
SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(),
SCHEMA_ATTR_DOMAIN_NAME.clone().into(),
SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND.clone().into(),
SCHEMA_ATTR_DOMAIN_SSID.clone().into(),
SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_DOMAIN_UUID.clone().into(),
SCHEMA_ATTR_DYNGROUP_FILTER.clone().into(),
SCHEMA_ATTR_EC_KEY_PRIVATE.clone().into(),
SCHEMA_ATTR_ES256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_FERNET_PRIVATE_KEY_STR.clone().into(),
SCHEMA_ATTR_GIDNUMBER.clone().into(),
SCHEMA_ATTR_GRANT_UI_HINT.clone().into(),
SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY.clone().into(),
// SCHEMA_ATTR_LEGALNAME.clone().into(),
SCHEMA_ATTR_LOGINSHELL.clone().into(),
SCHEMA_ATTR_NAME_HISTORY.clone().into(),
SCHEMA_ATTR_NSUNIQUEID.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
.clone()
.into(),
SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE.clone().into(),
SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_NAME.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING.clone().into(),
// SCHEMA_ATTR_OAUTH2_RS_ORIGIN.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_OAUTH2_SESSION.clone().into(),
SCHEMA_ATTR_PASSKEYS.clone().into(),
SCHEMA_ATTR_PRIMARY_CREDENTIAL.clone().into(),
SCHEMA_ATTR_PRIVATE_COOKIE_KEY.clone().into(),
SCHEMA_ATTR_RADIUS_SECRET.clone().into(),
SCHEMA_ATTR_RS256_PRIVATE_KEY_DER.clone().into(),
SCHEMA_ATTR_SSH_PUBLICKEY.clone().into(),
SCHEMA_ATTR_SYNC_COOKIE.clone().into(),
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_UNIX_PASSWORD.clone().into(),
SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_DENIED_NAME.clone().into(),
SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM.clone().into(),
SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
// DL4
SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4.clone().into(),
SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT_DL4
.clone()
.into(),
// DL5
// DL6
SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS_DL6.clone().into(),
SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6.clone().into(),
SCHEMA_ATTR_KEY_INTERNAL_DATA_DL6.clone().into(),
SCHEMA_ATTR_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_ROTATE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_REVOKE_DL6.clone().into(),
SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_ES256_DL6.clone().into(),
// DL7
SCHEMA_ATTR_PATCH_LEVEL_DL7.clone().into(),
SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7.clone().into(),
SCHEMA_ATTR_REFERS_DL7.clone().into(),
SCHEMA_ATTR_CERTIFICATE_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_RS_ORIGIN_DL7.clone().into(),
SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7.clone().into(),
SCHEMA_ATTR_MAIL_DL7.clone().into(),
SCHEMA_ATTR_LEGALNAME_DL7.clone().into(),
SCHEMA_ATTR_DISPLAYNAME_DL7.clone().into(),
// DL8
SCHEMA_ATTR_LINKED_GROUP_DL8.clone().into(),
SCHEMA_ATTR_APPLICATION_PASSWORD_DL8.clone().into(),
SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK_DL8.clone().into(),
];
let r = idm_schema
.into_iter()
// Each item individually logs it's result
.try_for_each(|entry| self.internal_migrate_or_create(entry));
if r.is_err() {
error!(res = ?r, "initialise_schema_idm -> Error");
}
debug_assert!(r.is_ok());
// ⚠️ DOMAIN LEVEL 1 SCHEMA CLASSES ⚠️
// Future schema classes need to be added via migrations.
//
// DO NOT MODIFY THIS DEFINITION
let idm_schema_classes_dl1: Vec<EntryInitNew> = vec![
SCHEMA_CLASS_DYNGROUP.clone().into(),
SCHEMA_CLASS_ORGPERSON.clone().into(),
SCHEMA_CLASS_POSIXACCOUNT.clone().into(),
SCHEMA_CLASS_POSIXGROUP.clone().into(),
SCHEMA_CLASS_SYSTEM_CONFIG.clone().into(),
// DL4
SCHEMA_CLASS_OAUTH2_RS_PUBLIC_DL4.clone().into(),
// DL5
// SCHEMA_CLASS_PERSON_DL5.clone().into(),
SCHEMA_CLASS_ACCOUNT_DL5.clone().into(),
// SCHEMA_CLASS_OAUTH2_RS_DL5.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_BASIC_DL5.clone().into(),
// DL6
// SCHEMA_CLASS_ACCOUNT_POLICY_DL6.clone().into(),
// SCHEMA_CLASS_SERVICE_ACCOUNT_DL6.clone().into(),
// SCHEMA_CLASS_SYNC_ACCOUNT_DL6.clone().into(),
SCHEMA_CLASS_GROUP_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_DL6.clone().into(),
SCHEMA_CLASS_KEY_PROVIDER_INTERNAL_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWT_ES256_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_JWE_A128GCM_DL6.clone().into(),
SCHEMA_CLASS_KEY_OBJECT_INTERNAL_DL6.clone().into(),
// SCHEMA_CLASS_DOMAIN_INFO_DL6.clone().into(),
// DL7
// SCHEMA_CLASS_DOMAIN_INFO_DL7.clone().into(),
SCHEMA_CLASS_SERVICE_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_SYNC_ACCOUNT_DL7.clone().into(),
SCHEMA_CLASS_CLIENT_CERTIFICATE_DL7.clone().into(),
SCHEMA_CLASS_OAUTH2_RS_DL7.clone().into(),
// DL8
SCHEMA_CLASS_ACCOUNT_POLICY_DL8.clone().into(),
SCHEMA_CLASS_APPLICATION_DL8.clone().into(),
SCHEMA_CLASS_PERSON_DL8.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL8.clone().into(),
];
let r: Result<(), _> = idm_schema_classes_dl1
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry));
if r.is_err() {
error!(res = ?r, "initialise_schema_idm -> Error");
}
debug_assert!(r.is_ok());
debug!("initialise_schema_idm -> Ok!");
r
}
#[instrument(level = "info", skip_all)]
/// This function is idempotent, runs all the startup functionality and checks
pub fn initialise_domain_info(&mut self) -> Result<(), OperationError> {
// Configure the default key provider. This needs to exist *before* the
// domain info!
self.internal_migrate_or_create(E_KEY_PROVIDER_INTERNAL_DL6.clone())
.and_then(|_| self.reload())
.map_err(|err| {
error!(?err, "initialise_domain_info::E_KEY_PROVIDER_INTERNAL_DL6");
debug_assert!(false);
err
})?;
self.internal_migrate_or_create(E_SYSTEM_INFO_V1.clone())
.and_then(|_| self.internal_migrate_or_create(E_DOMAIN_INFO_DL6.clone()))
.and_then(|_| self.internal_migrate_or_create(E_SYSTEM_CONFIG_V1.clone()))
.map_err(|err| {
error!(?err, "initialise_domain_info");
debug_assert!(false);
err
})
}
#[instrument(level = "info", skip_all)]
/// This function is idempotent, runs all the startup functionality and checks
pub fn initialise_idm(&mut self) -> Result<(), OperationError> {
// The domain info now exists, we should be able to do these migrations as they will
// cause SPN regenerations to occur
// Delete entries that no longer need to exist.
// TODO: Shouldn't this be a migration?
// Check the admin object exists (migrations).
// Create the default idm_admin group.
let admin_entries: Vec<EntryInitNew> = idm_builtin_admin_entries()?;
let res: Result<(), _> = admin_entries
.into_iter()
// Each item individually logs it's result
.try_for_each(|ent| self.internal_migrate_or_create(ent));
if res.is_ok() {
debug!("initialise_idm p1 -> result Ok!");
} else {
error!(?res, "initialise_idm p1 -> result");
}
debug_assert!(res.is_ok());
res?;
let res: Result<(), _> = idm_builtin_non_admin_groups()
.into_iter()
.try_for_each(|e| self.internal_migrate_or_create(e.clone().try_into()?));
if res.is_ok() {
debug!("initialise_idm p2 -> result Ok!");
} else {
error!(?res, "initialise_idm p2 -> result");
}
debug_assert!(res.is_ok());
res?;
// ⚠️ DOMAIN LEVEL 1 ENTRIES ⚠️
// Future entries need to be added via migrations.
//
// DO NOT MODIFY THIS DEFINITION
let idm_entries: Vec<BuiltinAcp> = vec![
// Built in access controls.
IDM_ACP_RECYCLE_BIN_SEARCH_V1.clone(),
IDM_ACP_RECYCLE_BIN_REVIVE_V1.clone(),
IDM_ACP_SCHEMA_WRITE_ATTRS_V1.clone(),
IDM_ACP_SCHEMA_WRITE_CLASSES_V1.clone(),
IDM_ACP_ACP_MANAGE_V1.clone(),
IDM_ACP_GROUP_ENTRY_MANAGED_BY_MODIFY_V1.clone(),
IDM_ACP_GROUP_ENTRY_MANAGER_V1.clone(),
IDM_ACP_SYNC_ACCOUNT_MANAGE_V1.clone(),
IDM_ACP_RADIUS_SERVERS_V1.clone(),
IDM_ACP_RADIUS_SECRET_MANAGE_V1.clone(),
IDM_ACP_PEOPLE_SELF_WRITE_MAIL_V1.clone(),
// IDM_ACP_SELF_READ_V1.clone(),
// IDM_ACP_SELF_WRITE_V1.clone(),
IDM_ACP_ACCOUNT_SELF_WRITE_V1.clone(),
// IDM_ACP_SELF_NAME_WRITE_V1.clone(),
IDM_ACP_ALL_ACCOUNTS_POSIX_READ_V1.clone(),
IDM_ACP_SYSTEM_CONFIG_ACCOUNT_POLICY_MANAGE_V1.clone(),
IDM_ACP_GROUP_UNIX_MANAGE_V1.clone(),
IDM_ACP_HP_GROUP_UNIX_MANAGE_V1.clone(),
IDM_ACP_GROUP_READ_V1.clone(),
IDM_ACP_ACCOUNT_UNIX_EXTEND_V1.clone(),
IDM_ACP_PEOPLE_PII_READ_V1.clone(),
IDM_ACP_PEOPLE_PII_MANAGE_V1.clone(),
IDM_ACP_PEOPLE_READ_V1.clone(),
IDM_ACP_PEOPLE_MANAGE_V1.clone(),
IDM_ACP_PEOPLE_DELETE_V1.clone(),
IDM_ACP_PEOPLE_CREDENTIAL_RESET_V1.clone(),
IDM_ACP_HP_PEOPLE_CREDENTIAL_RESET_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_CREATE_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_DELETE_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGER_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1.clone(),
IDM_ACP_HP_SERVICE_ACCOUNT_ENTRY_MANAGED_BY_MODIFY_V1.clone(),
IDM_ACP_SERVICE_ACCOUNT_MANAGE_V1.clone(),
// DL4
// DL5
// IDM_ACP_OAUTH2_MANAGE_DL5.clone(),
// DL6
// IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL6.clone(),
IDM_ACP_PEOPLE_CREATE_DL6.clone(),
IDM_ACP_GROUP_MANAGE_DL6.clone(),
IDM_ACP_ACCOUNT_MAIL_READ_DL6.clone(),
// IDM_ACP_DOMAIN_ADMIN_DL6.clone(),
// DL7
// IDM_ACP_SELF_WRITE_DL7.clone(),
IDM_ACP_SELF_NAME_WRITE_DL7.clone(),
IDM_ACP_HP_CLIENT_CERTIFICATE_MANAGER_DL7.clone(),
IDM_ACP_OAUTH2_MANAGE_DL7.clone(),
// DL8
IDM_ACP_SELF_READ_DL8.clone(),
IDM_ACP_SELF_WRITE_DL8.clone(),
IDM_ACP_APPLICATION_MANAGE_DL8.clone(),
IDM_ACP_APPLICATION_ENTRY_MANAGER_DL8.clone(),
IDM_ACP_MAIL_SERVERS_DL8.clone(),
IDM_ACP_DOMAIN_ADMIN_DL8.clone(),
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone(),
];
let res: Result<(), _> = idm_entries
.into_iter()
.try_for_each(|entry| self.internal_migrate_or_create(entry.into()));
if res.is_ok() {
admin_debug!("initialise_idm p3 -> result Ok!");
} else {
admin_error!(?res, "initialise_idm p3 -> result");
}
debug_assert!(res.is_ok());
res
}
}
impl QueryServerReadTransaction<'_> {

View file

@ -2420,8 +2420,17 @@ impl<'a> QueryServerWriteTransaction<'a> {
debug!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
debug!(domain_previous_patch_level = ?previous_patch_level, domain_target_patch_level = ?domain_info_patch_level);
// We have to check for DL0 since that's the initialisation level.
if previous_version < DOMAIN_MIN_REMIGRATION_LEVEL && previous_version != DOMAIN_LEVEL_0 {
// We have to check for DL0 since that's the initialisation level. If we are at DL0 then
// the server was just brought up and there are no other actions to take since we are
// now at TGT level.
if previous_version == DOMAIN_LEVEL_0 {
debug!(
"Server was just brought up, skipping migrations as we are already at target level"
);
return Ok(());
}
if previous_version < DOMAIN_MIN_REMIGRATION_LEVEL {
error!("UNABLE TO PROCEED. You are attempting a Skip update which is NOT SUPPORTED. You must upgrade one-version of Kanidm at a time.");
error!("For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html");
error!(domain_previous_version = ?previous_version, domain_target_version = ?domain_info_version);
@ -2434,7 +2443,10 @@ impl<'a> QueryServerWriteTransaction<'a> {
self.migrate_domain_8_to_9()?;
}
if previous_patch_level < PATCH_LEVEL_2 && domain_info_patch_level >= PATCH_LEVEL_2 {
if previous_patch_level < PATCH_LEVEL_2
&& domain_info_patch_level >= PATCH_LEVEL_2
&& domain_info_version == DOMAIN_LEVEL_9
{
self.migrate_domain_patch_level_2()?;
}
@ -2573,7 +2585,10 @@ impl<'a> QueryServerWriteTransaction<'a> {
}
fn set_phase(&mut self, phase: ServerPhase) {
*self.phase = phase
// Phase changes are one way
if phase > *self.phase {
*self.phase = phase
}
}
pub(crate) fn get_phase(&mut self) -> ServerPhase {

View file

@ -615,9 +615,11 @@ mod tests {
Err(OperationError::EmptyRequest)
);
let idm_admin = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
// Mod changes no objects
let me_nochg = ModifyEvent::new_impersonate_entry_ser(
BUILTIN_ACCOUNT_IDM_ADMIN.clone(),
let me_nochg = ModifyEvent::new_impersonate_entry(
idm_admin,
filter!(f_eq(
Attribute::Name,
PartialValue::new_iname("flarbalgarble")

View file

@ -89,7 +89,7 @@ pub async fn setup_idm_test(
) -> (IdmServer, IdmServerDelayed, IdmServerAudit) {
let qs = setup_test(config).await;
IdmServer::new(qs, "https://idm.example.com")
IdmServer::new(qs, "https://idm.example.com", true)
.await
.expect("Failed to setup idms")
}

View file

@ -5,7 +5,7 @@ edition = { workspace = true }
[lib]
proc-macro = true
test = true
test = false
doctest = false
[dependencies]

View file

@ -14,87 +14,73 @@ const ALLOWED_ATTRIBUTES: &[&str] = &[
"role",
"output_mode",
"log_level",
"ldap",
];
fn parse_knobs(
input: &syn::ItemFn,
server_config: &Punctuated<ExprAssign, syn::token::Comma>,
) -> TokenStream {
// If type mismatch occurs, the current rustc points to the last statement.
let (last_stmt_start_span, _last_stmt_end_span) = {
let mut last_stmt = input
.block
.stmts
.last()
.map(ToTokens::into_token_stream)
.unwrap_or_default()
.into_iter();
// `Span` on stable Rust has a limitation that only points to the first
// token, not the whole tokens. We can work around this limitation by
// using the first/last span of the tokens like
// `syn::Error::new_spanned` does.
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
let end = last_stmt.last().map_or(start, |t| t.span());
(start, end)
};
#[derive(Default)]
struct Flags {
ldap: bool,
}
// here we gather all the provided configuration in a struct like declaration
// By now we have already checked that the configurations provided belong to the allowed subset
let mut field_modifications = quote! {};
server_config.pairs().for_each(|p| {
let field_name = p.value().left.to_token_stream(); // here we can use to_token_stream as we know we're iterating over ExprAssigns
let field_value = p.value().right.to_token_stream();
field_modifications.extend(quote! {
#field_name: #field_value,})
fn parse_attributes(
args: &TokenStream,
input: &syn::ItemFn,
) -> Result<(proc_macro2::TokenStream, Flags), syn::Error> {
let args: Punctuated<ExprAssign, syn::token::Comma> =
Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone())?;
let args_are_allowed = args.pairs().all(|p| {
ALLOWED_ATTRIBUTES.to_vec().contains(
&p.value()
.left
.span()
.source_text()
.unwrap_or_default()
.as_str(),
)
});
// Setup the config filling the remaining fields with the default values
let default_config_struct = quote!(kanidmd_core::config::Configuration {
if !args_are_allowed {
let msg = "Invalid test config attribute. The following are allowed";
return Err(syn::Error::new_spanned(
input.sig.fn_token,
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
));
}
let mut flags = Flags::default();
let mut field_modifications = quote! {};
args.pairs().for_each(|p| {
match p
.value()
.left
.span()
.source_text()
.unwrap_or_default()
.as_str()
{
"ldap" => {
flags.ldap = true;
field_modifications.extend(quote! {
ldapaddress: Some("on".to_string()),})
}
_ => {
let field_name = p.value().left.to_token_stream(); // here we can use to_token_stream as we know we're iterating over ExprAssigns
let field_value = p.value().right.to_token_stream();
// This is printing out struct members.
field_modifications.extend(quote! {
#field_name: #field_value,})
}
}
});
let ts = quote!(kanidmd_core::config::Configuration {
#field_modifications
..kanidmd_core::config::Configuration::new_for_test()
});
let rt = quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_current_thread()
};
let header = quote! {
#[::core::prelude::v1::test]
};
let fn_name = &input.sig.ident;
let test_driver = Ident::new(&format!("tk_{}", fn_name), input.sig.span());
// Effectively we are just injecting a real test function around this which we will
// call.
let result = quote! {
#input
#header
fn #test_driver() {
let body = async {
let (rsclient, mut core_handle) = kanidmd_testkit::setup_async_test(#default_config_struct).await;
#fn_name(rsclient).await;
core_handle.shutdown().await;
};
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
{
return #rt
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(body);
}
}
};
result.into()
}
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
tokens.extend(TokenStream::from(error.into_compile_error()));
tokens
Ok((ts, flags))
}
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
@ -115,31 +101,80 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
let msg = "the `async` keyword is missing from the function declaration";
return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg));
}
let args: Punctuated<ExprAssign, syn::token::Comma> =
match Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone()) {
Ok(it) => it,
Err(e) => return token_stream_with_error(args, e),
};
let args_are_allowed = args.pairs().all(|p| {
ALLOWED_ATTRIBUTES.to_vec().contains(
&p.value()
.left
.span()
.source_text()
.unwrap_or_default()
.as_str(),
)
});
if !args_are_allowed {
let msg =
"Currently only a subset of all the server configs can be set. Here is the full list";
return token_stream_with_error(
item,
syn::Error::new_spanned(
input.sig.fn_token,
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
),
);
}
parse_knobs(&input, &args)
// If type mismatch occurs, the current rustc points to the last statement.
let (last_stmt_start_span, _last_stmt_end_span) = {
let mut last_stmt = input
.block
.stmts
.last()
.map(ToTokens::into_token_stream)
.unwrap_or_default()
.into_iter();
// `Span` on stable Rust has a limitation that only points to the first
// token, not the whole tokens. We can work around this limitation by
// using the first/last span of the tokens like
// `syn::Error::new_spanned` does.
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
let end = last_stmt.last().map_or(start, |t| t.span());
(start, end)
};
// Setup the config filling the remaining fields with the default values
let (default_config_struct, flags) = match parse_attributes(&args, &input) {
Ok(dc) => dc,
Err(e) => return token_stream_with_error(args, e),
};
let rt = quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_current_thread()
};
let header = quote! {
#[::core::prelude::v1::test]
};
let test_fn_args = if flags.ldap {
quote! {
&test_env
}
} else {
quote! {
&test_env.rsclient
}
};
let test_fn = &input.sig.ident;
let test_driver = Ident::new(&format!("tk_{}", test_fn), input.sig.span());
// Effectively we are just injecting a real test function around this which we will
// call.
let result = quote! {
#input
#header
fn #test_driver() {
let body = async {
let mut test_env = kanidmd_testkit::setup_async_test(#default_config_struct).await;
#test_fn(#test_fn_args).await;
test_env.core_handle.shutdown().await;
};
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
{
return #rt
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(body);
}
}
};
result.into()
}
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
tokens.extend(TokenStream::from(error.into_compile_error()));
tokens
}

View file

@ -14,7 +14,7 @@ repository = { workspace = true }
[lib]
name = "kanidmd_testkit"
path = "src/lib.rs"
test = true
test = false
doctest = false
[features]
@ -25,7 +25,6 @@ webdriver = []
dev-oauth2-device-flow = []
[dependencies]
http = { workspace = true }
kanidm_client = { workspace = true }
kanidm_proto = { workspace = true }
kanidmd_core = { workspace = true }
@ -54,10 +53,10 @@ escargot = "0.5.13"
# used for webdriver testing
fantoccini = { version = "0.21.4" }
futures = { workspace = true }
ldap3_client = { workspace = true }
oauth2_ext = { workspace = true, default-features = false, features = [
"reqwest",
] }
openssl = { workspace = true }
petgraph = { version = "0.7.1", features = ["serde"] }
serde_json = { workspace = true }
time = { workspace = true }

View file

@ -4,7 +4,9 @@
//!
use kanidmd_lib::constants::entries::Attribute;
use kanidmd_lib::constants::groups::{idm_builtin_admin_groups, idm_builtin_non_admin_groups};
use kanidmd_lib::migration_data::current::{
phase_5_builtin_admin_entries, phase_6_builtin_non_admin_entries
};
use kanidmd_lib::prelude::{builtin_accounts, EntryInitNew};
use petgraph::graphmap::{AllEdges, GraphMap, NodeTrait};
use petgraph::Directed;
@ -106,8 +108,8 @@ async fn enumerate_default_groups(/*_client: KanidmClient*/) {
graph.add_node(account.uuid);
});
let mut groups = idm_builtin_admin_groups();
groups.extend(idm_builtin_non_admin_groups());
let mut groups = phase_5_builtin_admin_entries();
groups.extend(phase_6_builtin_non_admin_entries());
groups.into_iter().for_each(|group| {
uuidmap.insert(group.uuid, group.clone().try_into().unwrap());

View file

@ -12,10 +12,9 @@ use kanidm_proto::oauth2::{
AccessTokenRequest, AccessTokenResponse, AuthorisationResponse, GrantTypeReq,
};
use kanidmd_lib::constants::NAME_IDM_ALL_ACCOUNTS;
use kanidmd_lib::prelude::uri::{OAUTH2_AUTHORISE_DEVICE, OAUTH2_TOKEN_ENDPOINT};
use kanidmd_lib::prelude::{
Attribute, IDM_ALL_ACCOUNTS, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID, OAUTH2_SCOPE_READ,
};
use kanidmd_lib::prelude::{Attribute, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID, OAUTH2_SCOPE_READ};
use kanidmd_testkit::{
assert_no_cache, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER, IDM_ADMIN_TEST_PASSWORD,
IDM_ADMIN_TEST_USER, NOT_ADMIN_TEST_EMAIL, NOT_ADMIN_TEST_PASSWORD, NOT_ADMIN_TEST_USERNAME,
@ -159,7 +158,7 @@ async fn oauth2_device_flow(rsclient: KanidmClient) {
rsclient
.idm_oauth2_rs_update_scope_map(
TEST_INTEGRATION_RS_ID,
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID],
)
.await
@ -168,7 +167,7 @@ async fn oauth2_device_flow(rsclient: KanidmClient) {
rsclient
.idm_oauth2_rs_update_sup_scope_map(
TEST_INTEGRATION_RS_ID,
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
vec![ADMIN_TEST_USER],
)
.await
@ -179,7 +178,7 @@ async fn oauth2_device_flow(rsclient: KanidmClient) {
.idm_oauth2_rs_update_claim_map(
TEST_INTEGRATION_RS_ID,
"test_claim",
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
&["claim_a".to_string(), "claim_b".to_string()],
)
.await

View file

@ -10,16 +10,16 @@
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
use std::net::TcpStream;
use std::sync::atomic::{AtomicU16, Ordering};
use kanidm_client::{KanidmClient, KanidmClientBuilder};
use kanidm_proto::internal::{Filter, Modify, ModifyList};
use kanidmd_core::config::{Configuration, IntegrationTestConfig};
use kanidmd_core::{create_server_core, CoreHandle};
use kanidmd_lib::prelude::{Attribute, BUILTIN_GROUP_IDM_ADMINS_V1};
use kanidmd_lib::prelude::Attribute;
use std::net::TcpStream;
use std::sync::atomic::{AtomicU16, Ordering};
use tokio::task;
use tracing::error;
use url::Url;
pub const ADMIN_TEST_USER: &str = "admin";
pub const ADMIN_TEST_PASSWORD: &str = "integration test admin password";
@ -46,14 +46,9 @@ pub fn is_free_port(port: u16) -> bool {
}
// Test external behaviours of the service.
// allowed because the use of this function is behind a test gate
#[allow(dead_code)]
pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreHandle) {
sketching::test_init();
fn port_loop() -> u16 {
let mut counter = 0;
let port = loop {
loop {
let possible_port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
if is_free_port(possible_port) {
break possible_port;
@ -64,7 +59,21 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
tracing::error!("Unable to allocate port!");
panic!();
}
};
}
}
pub struct AsyncTestEnvironment {
pub rsclient: KanidmClient,
pub core_handle: CoreHandle,
pub ldap_url: Option<Url>,
}
// allowed because the use of this function is behind a test gate
#[allow(dead_code)]
pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment {
sketching::test_init();
let port = port_loop();
let int_config = Box::new(IntegrationTestConfig {
admin_user: ADMIN_TEST_USER.to_string(),
@ -75,6 +84,16 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
let addr = format!("http://localhost:{}", port);
let ldap_url = if config.ldapaddress.is_some() {
let ldapport = port_loop();
config.ldapaddress = Some(format!("127.0.0.1:{}", ldapport));
Url::parse(&format!("ldap://127.0.0.1:{}", ldapport))
.inspect_err(|err| error!(?err, "ldap address setup"))
.ok()
} else {
None
};
// Setup the address and origin..
config.address = format!("127.0.0.1:{}", port);
config.integration_test_config = Some(int_config);
@ -92,6 +111,7 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
#[allow(clippy::panic)]
let rsclient = match KanidmClientBuilder::new()
.address(addr.clone())
.enable_native_ca_roots(false)
.no_proxy()
.build()
{
@ -101,7 +121,11 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
tracing::info!("Testkit server setup complete - {}", addr);
(rsclient, core_handle)
AsyncTestEnvironment {
rsclient,
core_handle,
ldap_url,
}
}
/// creates a user (username: `id`) and puts them into a group, creating it if need be.
@ -385,7 +409,7 @@ pub async fn login_put_admin_idm_admins(rsclient: &KanidmClient) {
#[allow(clippy::expect_used)]
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &[ADMIN_TEST_USER])
.idm_group_add_members("system_admins", &[ADMIN_TEST_USER])
.await
.expect("Failed to add admin user to idm_admins")
}
@ -396,7 +420,7 @@ macro_rules! assert_no_cache {
// Check we have correct nocache headers.
let cache_header: &str = $response
.headers()
.get(http::header::CACHE_CONTROL)
.get(kanidm_client::http::header::CACHE_CONTROL)
.expect("missing cache-control header")
.to_str()
.expect("invalid cache-control header");

View file

@ -0,0 +1,11 @@
DO NOT ADD MORE TESTS TO THIS DIRECTORY
Add them to the `testkit` directory.
Each new test in this folder is a separate binary, that must be complied linked and executed. This
takes HUGES AMOUNTS OF TIME. It makes tests unbelievably slow.
If you want to add an integration test, put it into the testkit dir so it becomes part of the
single larger integration test runner.

View file

@ -0,0 +1,3 @@
#![deny(warnings)]
mod testkit;

View file

@ -1,57 +0,0 @@
use kanidm_client::KanidmClient;
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
#[kanidmd_testkit::test]
async fn test_v1_self_applinks(rsclient: KanidmClient) {
// We need to do manual reqwests here.
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let response = match client
.get(rsclient.make_url("/v1/self/_applinks"))
.send()
.await
{
Ok(value) => value,
Err(error) => {
panic!(
"Failed to query {:?} : {:#?}",
rsclient.make_url("/v1/self/_applinks"),
error
);
}
};
eprintln!("response: {:#?}", response);
assert_eq!(response.status(), 401);
let body = response.text().await.unwrap();
eprintln!("{}", body);
}
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
#[kanidmd_testkit::test]
async fn test_v1_self_whoami_uat(rsclient: KanidmClient) {
// We need to do manual reqwests here.
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let response = match client.get(rsclient.make_url("/v1/self/_uat")).send().await {
Ok(value) => value,
Err(error) => {
panic!(
"Failed to query {:?} : {:#?}",
rsclient.make_url("/v1/self/_uat"),
error
);
}
};
eprintln!("response: {:#?}", response);
assert_eq!(response.status(), 401);
let body = response.text().await.unwrap();
eprintln!("{}", body);
}

View file

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use tracing::info;
#[kanidmd_testkit::test]
async fn check_that_the_swagger_api_loads(rsclient: kanidm_client::KanidmClient) {
async fn check_that_the_swagger_api_loads(rsclient: &kanidm_client::KanidmClient) {
#[derive(Serialize, Deserialize, Debug)]
struct OpenAPIResponse {
pub openapi: String,
@ -12,21 +12,18 @@ async fn check_that_the_swagger_api_loads(rsclient: kanidm_client::KanidmClient)
rsclient.set_token("".into()).await;
info!("Running test: check_that_the_swagger_api_loads");
let url = rsclient.make_url("/docs/v1/openapi.json");
let openapi_response: OpenAPIResponse = reqwest::get(url.clone())
let openapi_response = rsclient
.perform_get_request::<OpenAPIResponse>(url.as_str())
.await
.expect("Failed to get openapi.json")
.json()
.await
.unwrap();
.expect("Failed to get openapi.json");
assert_eq!(openapi_response.openapi, "3.0.3");
// this validates that it's valid JSON schema, but not that it's valid openapi... but it's a start.
let schema: serde_json::Value = reqwest::get(url)
let schema = rsclient
.perform_get_request::<serde_json::Value>(url.as_str())
.await
.expect("Failed to get openapi.json")
.json()
.await
.unwrap();
.expect("Failed to get openapi.json");
let instance = serde_json::json!("foo");
let compiled = Validator::new(&schema).expect("A valid schema");

View file

@ -3,7 +3,7 @@ use kanidm_proto::constants::ATTR_DOMAIN_DISPLAY_NAME;
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
#[kanidmd_testkit::test]
async fn test_idm_set_ldap_allow_unix_password_bind(rsclient: KanidmClient) {
async fn test_idm_set_ldap_allow_unix_password_bind(rsclient: &KanidmClient) {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await
@ -13,8 +13,9 @@ async fn test_idm_set_ldap_allow_unix_password_bind(rsclient: KanidmClient) {
.await
.expect("Failed to set LDAP allow unix password bind to true");
}
#[kanidmd_testkit::test]
async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await
@ -27,7 +28,7 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await
@ -40,7 +41,7 @@ async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_idm_domain_set_display_name(rsclient: KanidmClient) {
async fn test_idm_domain_set_display_name(rsclient: &KanidmClient) {
rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await

View file

@ -1,10 +1,10 @@
use kanidm_client::{ClientError, KanidmClient};
use kanidm_client::{ClientError, KanidmClient, StatusCode};
use kanidm_proto::constants::ATTR_DESCRIPTION;
use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
use serde_json::Value;
#[kanidmd_testkit::test]
async fn test_v1_group_id_patch(rsclient: KanidmClient) {
async fn test_v1_group_id_patch(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -25,7 +25,7 @@ async fn test_v1_group_id_patch(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_v1_group_id_attr_post(rsclient: KanidmClient) {
async fn test_v1_group_id_attr_post(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -48,6 +48,6 @@ async fn test_v1_group_id_attr_post(rsclient: KanidmClient) {
eprintln!("response: {:#?}", response);
assert!(matches!(
response,
ClientError::Http(reqwest::StatusCode::BAD_REQUEST, _, _)
ClientError::Http(StatusCode::BAD_REQUEST, _, _)
));
}

View file

@ -1,11 +1,16 @@
use kanidm_client::KanidmClient;
use kanidm_client::{http::header, KanidmClient};
#[kanidmd_testkit::test]
async fn test_https_manifest(rsclient: KanidmClient) {
async fn test_https_manifest(rsclient: &KanidmClient) {
// We need to do manual reqwests here.
let client = rsclient.client();
// here we test the /ui/ endpoint which should have the headers
let response = match reqwest::get(rsclient.make_url("/manifest.webmanifest")).await {
let response = match client
.get(rsclient.make_url("/manifest.webmanifest"))
.send()
.await
{
Ok(value) => value,
Err(error) => {
panic!(
@ -20,8 +25,6 @@ async fn test_https_manifest(rsclient: KanidmClient) {
eprintln!(
"csp headers: {:#?}",
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY)
response.headers().get(header::CONTENT_SECURITY_POLICY)
);
}

View file

@ -11,11 +11,9 @@ const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
// *test where we don't trust the x-forwarded-for header
#[kanidmd_testkit::test(trust_x_forward_for = false)]
async fn dont_trust_xff_send_header(rsclient: KanidmClient) {
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
async fn dont_trust_xff_send_header(rsclient: &KanidmClient) {
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(
@ -34,11 +32,9 @@ async fn dont_trust_xff_send_header(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test(trust_x_forward_for = false)]
async fn dont_trust_xff_dont_send_header(rsclient: KanidmClient) {
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
async fn dont_trust_xff_dont_send_header(rsclient: &KanidmClient) {
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(
@ -62,11 +58,9 @@ async fn dont_trust_xff_dont_send_header(rsclient: KanidmClient) {
// *test where we trust the x-forwarded-for header
#[kanidmd_testkit::test(trust_x_forward_for = true)]
async fn trust_xff_send_invalid_header_single_value(rsclient: KanidmClient) {
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
async fn trust_xff_send_invalid_header_single_value(rsclient: &KanidmClient) {
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(
@ -84,11 +78,9 @@ async fn trust_xff_send_invalid_header_single_value(rsclient: KanidmClient) {
// with a valid leftmost address and an invalid address later in the list. Right now it wouldn't work.
//
#[kanidmd_testkit::test(trust_x_forward_for = true)]
async fn trust_xff_send_invalid_header_multiple_values(rsclient: KanidmClient) {
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
async fn trust_xff_send_invalid_header_multiple_values(rsclient: &KanidmClient) {
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(
@ -103,13 +95,11 @@ async fn trust_xff_send_invalid_header_multiple_values(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test(trust_x_forward_for = true)]
async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: KanidmClient) {
async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: &KanidmClient) {
let ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348";
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(X_FORWARDED_FOR, ip_addr)
@ -125,13 +115,11 @@ async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: KanidmClient)
}
#[kanidmd_testkit::test(trust_x_forward_for = true)]
async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: KanidmClient) {
async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: &KanidmClient) {
let ip_addr = "203.0.113.195";
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(X_FORWARDED_FOR, ip_addr)
@ -147,13 +135,11 @@ async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: KanidmClient)
}
#[kanidmd_testkit::test(trust_x_forward_for = true)]
async fn trust_xff_send_valid_header_multiple_address(rsclient: KanidmClient) {
async fn trust_xff_send_valid_header_multiple_address(rsclient: &KanidmClient) {
let first_ip_addr = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348";
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(X_FORWARDED_FOR, first_ip_addr)
@ -172,10 +158,6 @@ async fn trust_xff_send_valid_header_multiple_address(rsclient: KanidmClient) {
let second_ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348, 198.51.100.178, 203.0.113.195";
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.header(X_FORWARDED_FOR, second_ip_addr)
@ -194,11 +176,9 @@ async fn trust_xff_send_valid_header_multiple_address(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test(trust_x_forward_for = true)]
async fn trust_xff_dont_send_header(rsclient: KanidmClient) {
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
async fn trust_xff_dont_send_header(rsclient: &KanidmClient) {
let client = rsclient.client();
let res = client
.get(rsclient.make_url("/v1/debug/ipinfo"))
.send()

View file

@ -1,11 +1,13 @@
use kanidm_client::http::header;
use kanidm_client::KanidmClient;
#[kanidmd_testkit::test]
async fn test_https_middleware_headers(rsclient: KanidmClient) {
async fn test_https_middleware_headers(rsclient: &KanidmClient) {
// We need to do manual reqwests here.
let client = rsclient.client();
// here we test the /ui/ endpoint which should have the headers
let response = match reqwest::get(rsclient.make_url("/ui")).await {
let response = match client.get(rsclient.make_url("/ui")).send().await {
Ok(value) => value,
Err(error) => {
panic!(
@ -19,19 +21,15 @@ async fn test_https_middleware_headers(rsclient: KanidmClient) {
assert_eq!(response.status(), 200);
eprintln!(
"csp headers: {:#?}",
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY)
response.headers().get(header::CONTENT_SECURITY_POLICY)
);
assert_ne!(
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY),
response.headers().get(header::CONTENT_SECURITY_POLICY),
None
);
// here we test the /ui/login endpoint which should have the headers
let response = match reqwest::get(rsclient.make_url("/ui/login")).await {
let response = match client.get(rsclient.make_url("/ui/login")).send().await {
Ok(value) => value,
Err(error) => {
panic!(
@ -46,14 +44,10 @@ async fn test_https_middleware_headers(rsclient: KanidmClient) {
eprintln!(
"csp headers: {:#?}",
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY)
response.headers().get(header::CONTENT_SECURITY_POLICY)
);
assert_ne!(
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY),
response.headers().get(header::CONTENT_SECURITY_POLICY),
None
);
}

View file

@ -1,11 +1,9 @@
use core::result::Result::Err;
use kanidm_client::KanidmClient;
use kanidm_client::{KanidmClient, StatusCode};
use kanidm_proto::internal::OperationError;
use kanidm_proto::internal::{IdentifyUserRequest, IdentifyUserResponse};
use kanidmd_lib::prelude::Attribute;
use kanidmd_testkit::ADMIN_TEST_PASSWORD;
use reqwest::StatusCode;
static UNIVERSAL_PW: &str = "eicieY7ahchaoCh0eeTa";
@ -17,7 +15,7 @@ static USER_B_NAME: &str = "valid_user_b";
// These tests check that invalid requests return the expected error
#[kanidmd_testkit::test]
async fn test_not_authenticated(rsclient: KanidmClient) {
async fn test_not_authenticated(rsclient: &KanidmClient) {
// basically here we try a bit of all the possible combinations while unauthenticated to check it's not working
setup_server(&rsclient).await;
create_user(&rsclient, USER_A_NAME).await;
@ -26,14 +24,14 @@ async fn test_not_authenticated(rsclient: KanidmClient) {
.idm_person_identify_user(USER_A_NAME, IdentifyUserRequest::Start)
.await;
assert!(
matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(reqwest::StatusCode::UNAUTHORIZED, ..)))
matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::UNAUTHORIZED, ..)))
);
let res = rsclient
.idm_person_identify_user(USER_A_NAME, IdentifyUserRequest::DisplayCode)
.await;
assert!(
matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(reqwest::StatusCode::UNAUTHORIZED, ..)))
matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::UNAUTHORIZED, ..)))
);
let res = rsclient
.idm_person_identify_user(
@ -43,12 +41,12 @@ async fn test_not_authenticated(rsclient: KanidmClient) {
.await;
assert!(
matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(reqwest::StatusCode::UNAUTHORIZED, ..)))
matches!(res, Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::UNAUTHORIZED, ..)))
);
}
#[kanidmd_testkit::test]
async fn test_non_existing_user_id(rsclient: KanidmClient) {
async fn test_non_existing_user_id(rsclient: &KanidmClient) {
setup_server(&rsclient).await;
create_user(&rsclient, USER_A_NAME).await;
create_user(&rsclient, USER_B_NAME).await;
@ -88,7 +86,7 @@ async fn test_non_existing_user_id(rsclient: KanidmClient) {
// error cases have already been tested in the previous section!
// Each tests is named like `test_{api input}_response_{expected api output}_or_{expected api output}`
#[kanidmd_testkit::test]
async fn test_start_response_identity_verification_available(rsclient: KanidmClient) {
async fn test_start_response_identity_verification_available(rsclient: &KanidmClient) {
setup_server(&rsclient).await;
create_user(&rsclient, USER_A_NAME).await;
login_with_user(&rsclient, USER_A_NAME).await;
@ -107,7 +105,7 @@ async fn test_start_response_identity_verification_available(rsclient: KanidmCli
// this function tests both possible POSITIVE outcomes if we start from
// `Start`, that is WaitForCode or ProvideCode
#[kanidmd_testkit::test]
async fn test_start_response_wait_for_code_or_provide_code(rsclient: KanidmClient) {
async fn test_start_response_wait_for_code_or_provide_code(rsclient: &KanidmClient) {
setup_server(&rsclient).await;
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
@ -131,7 +129,7 @@ async fn test_start_response_wait_for_code_or_provide_code(rsclient: KanidmClien
}
#[kanidmd_testkit::test]
async fn test_provide_code_response_code_failure_or_provide_code(rsclient: KanidmClient) {
async fn test_provide_code_response_code_failure_or_provide_code(rsclient: &KanidmClient) {
setup_server(&rsclient).await;
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
@ -159,7 +157,7 @@ async fn test_provide_code_response_code_failure_or_provide_code(rsclient: Kanid
// here we actually test the full idm flow by duplicating the server
#[kanidmd_testkit::test]
async fn test_full_identification_flow(rsclient: KanidmClient) {
async fn test_full_identification_flow(rsclient: &KanidmClient) {
setup_server(&rsclient).await;
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
@ -177,12 +175,12 @@ async fn test_full_identification_flow(rsclient: KanidmClient) {
(
valid_user_a_client,
USER_A_NAME,
valid_user_b_client,
&valid_user_b_client,
USER_B_NAME,
)
} else {
(
valid_user_b_client,
&valid_user_b_client,
USER_B_NAME,
valid_user_a_client,
USER_A_NAME,

View file

@ -66,7 +66,7 @@ async fn get_webdriver_client() -> fantoccini::Client {
#[kanidmd_testkit::test]
#[cfg(feature = "webdriver")]
async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) {
async fn test_webdriver_user_login(rsclient: &KanidmClient) {
if !cfg!(feature = "webdriver") {
println!("Skipping test as webdriver feature is not enabled!");
return;
@ -206,7 +206,7 @@ async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_domain_reset_token_key(rsclient: KanidmClient) {
async fn test_domain_reset_token_key(rsclient: &KanidmClient) {
login_put_admin_idm_admins(&rsclient).await;
let token = rsclient.get_token().await.expect("No bearer token present");
@ -219,7 +219,7 @@ async fn test_domain_reset_token_key(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
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")
@ -232,7 +232,7 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) {
login_put_admin_idm_admins(&rsclient).await;
assert!(rsclient
.idm_domain_set_ldap_max_queryable_attrs(20)
@ -246,7 +246,7 @@ async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
#[kanidmd_testkit::test]
/// Checks that a built-in group idm_all_persons has the "builtin" class as expected.
async fn test_all_persons_has_builtin_class(rsclient: KanidmClient) {
async fn test_all_persons_has_builtin_class(rsclient: &KanidmClient) {
login_put_admin_idm_admins(&rsclient).await;
let res = rsclient
.idm_group_get("idm_all_persons")

View file

@ -0,0 +1,16 @@
use kanidmd_testkit::AsyncTestEnvironment;
use ldap3_client::LdapClientBuilder;
#[kanidmd_testkit::test(ldap = true)]
async fn test_ldap_basic_unix_bind(test_env: &AsyncTestEnvironment) {
let ldap_url = test_env.ldap_url.as_ref().unwrap();
let mut ldap_client = LdapClientBuilder::new(ldap_url).build().await.unwrap();
// Bind as anonymous
ldap_client.bind("".into(), "".into()).await.unwrap();
let whoami = ldap_client.whoami().await.unwrap();
assert_eq!(whoami, Some("u: anonymous@localhost".to_string()));
}

View file

@ -0,0 +1,17 @@
mod apidocs;
mod domain;
mod group;
mod http_manifest;
mod https_extractors;
mod https_middleware;
mod identity_verification_tests;
mod integration;
mod ldap_basic;
mod mtls_test;
mod oauth2_test;
mod person;
mod proto_v1_test;
mod scim_test;
mod service_account;
mod system;
mod unix;

View file

@ -12,14 +12,14 @@ use kanidm_proto::oauth2::{
AccessTokenResponse, AccessTokenType, AuthorisationResponse, GrantTypeReq,
OidcDiscoveryResponse,
};
use kanidmd_lib::prelude::{Attribute, IDM_ALL_ACCOUNTS};
use kanidmd_lib::constants::NAME_IDM_ALL_ACCOUNTS;
use kanidmd_lib::prelude::Attribute;
use oauth2_ext::PkceCodeChallenge;
use reqwest::header::{HeaderValue, CONTENT_TYPE};
use reqwest::StatusCode;
use uri::{OAUTH2_TOKEN_ENDPOINT, OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
use url::{form_urlencoded::parse as query_parse, Url};
use kanidm_client::KanidmClient;
use kanidm_client::{http::header, KanidmClient, StatusCode};
use kanidmd_testkit::{
assert_no_cache, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER, NOT_ADMIN_TEST_EMAIL,
NOT_ADMIN_TEST_PASSWORD, NOT_ADMIN_TEST_USERNAME, TEST_INTEGRATION_RS_DISPLAY,
@ -40,7 +40,7 @@ use kanidmd_testkit::{
/// If `true`, use the `code` passed in the callback URI's fragment, and
/// require the query parameter to be empty.
async fn test_oauth2_openid_basic_flow_impl(
rsclient: KanidmClient,
rsclient: &KanidmClient,
response_mode: Option<&str>,
response_in_fragment: bool,
) {
@ -98,7 +98,7 @@ async fn test_oauth2_openid_basic_flow_impl(
rsclient
.idm_oauth2_rs_update_scope_map(
TEST_INTEGRATION_RS_ID,
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID],
)
.await
@ -107,7 +107,7 @@ async fn test_oauth2_openid_basic_flow_impl(
rsclient
.idm_oauth2_rs_update_sup_scope_map(
TEST_INTEGRATION_RS_ID,
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
vec![ADMIN_TEST_USER],
)
.await
@ -136,6 +136,7 @@ async fn test_oauth2_openid_basic_flow_impl(
// from here, we can now begin what would be a "interaction" to the oauth server.
// Create a new reqwest client - we'll be using this manually.
let client = reqwest::Client::builder()
.tls_built_in_native_certs(false)
.redirect(reqwest::redirect::Policy::none())
.no_proxy()
.build()
@ -151,11 +152,11 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send discovery preflight request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
let cors_header: &str = response
.headers()
.get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.expect("missing access-control-allow-origin header")
.to_str()
.expect("invalid access-control-allow-origin header");
@ -167,12 +168,12 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
// Assert CORS on the GET too.
let cors_header: &str = response
.headers()
.get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.expect("missing access-control-allow-origin header")
.to_str()
.expect("invalid access-control-allow-origin header");
@ -220,7 +221,7 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
assert_no_cache!(response);
let mut jwk_set: JwkKeySet = response
@ -263,7 +264,7 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
assert_no_cache!(response);
let consent_req: AuthorisationResponse = response
@ -297,7 +298,7 @@ async fn test_oauth2_openid_basic_flow_impl(
.expect("Failed to send request.");
// This should yield a 302 redirect with some query params.
assert_eq!(response.status(), reqwest::StatusCode::FOUND);
assert_eq!(response.status(), StatusCode::FOUND);
assert_no_cache!(response);
// And we should have a URL in the location header.
@ -342,11 +343,11 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send code exchange request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
let cors_header: &str = response
.headers()
.get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.expect("missing access-control-allow-origin header")
.to_str()
.expect("invalid access-control-allow-origin header");
@ -378,7 +379,7 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send token introspect request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
tracing::trace!("{:?}", response.headers());
assert!(
response.headers().get(CONTENT_TYPE) == Some(&HeaderValue::from_static(APPLICATION_JSON))
@ -484,7 +485,7 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send client credentials request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
let atr = response
.json::<AccessTokenResponse>()
@ -505,7 +506,7 @@ async fn test_oauth2_openid_basic_flow_impl(
.await
.expect("Failed to send token introspect request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
let tir = response
.json::<AccessTokenIntrospectResponse>()
@ -534,7 +535,7 @@ async fn test_oauth2_openid_basic_flow_impl(
///
/// The response should be returned as a query parameter.
#[kanidmd_testkit::test]
async fn test_oauth2_openid_basic_flow_mode_unset(rsclient: KanidmClient) {
async fn test_oauth2_openid_basic_flow_mode_unset(rsclient: &KanidmClient) {
test_oauth2_openid_basic_flow_impl(rsclient, None, false).await;
}
@ -543,7 +544,7 @@ async fn test_oauth2_openid_basic_flow_mode_unset(rsclient: KanidmClient) {
///
/// The response should be returned as a query parameter.
#[kanidmd_testkit::test]
async fn test_oauth2_openid_basic_flow_mode_query(rsclient: KanidmClient) {
async fn test_oauth2_openid_basic_flow_mode_query(rsclient: &KanidmClient) {
test_oauth2_openid_basic_flow_impl(rsclient, Some("query"), false).await;
}
@ -552,7 +553,7 @@ async fn test_oauth2_openid_basic_flow_mode_query(rsclient: KanidmClient) {
///
/// The response should be returned in the URI's fragment.
#[kanidmd_testkit::test]
async fn test_oauth2_openid_basic_flow_mode_fragment(rsclient: KanidmClient) {
async fn test_oauth2_openid_basic_flow_mode_fragment(rsclient: &KanidmClient) {
test_oauth2_openid_basic_flow_impl(rsclient, Some("fragment"), true).await;
}
@ -569,7 +570,7 @@ async fn test_oauth2_openid_basic_flow_mode_fragment(rsclient: KanidmClient) {
/// If `true`, use the `code` passed in the callback URI's fragment, and
/// require the query parameter to be empty.
async fn test_oauth2_openid_public_flow_impl(
rsclient: KanidmClient,
rsclient: &KanidmClient,
response_mode: Option<&str>,
response_in_fragment: bool,
) {
@ -627,7 +628,7 @@ async fn test_oauth2_openid_public_flow_impl(
rsclient
.idm_oauth2_rs_update_scope_map(
TEST_INTEGRATION_RS_ID,
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID],
)
.await
@ -636,7 +637,7 @@ async fn test_oauth2_openid_public_flow_impl(
rsclient
.idm_oauth2_rs_update_sup_scope_map(
TEST_INTEGRATION_RS_ID,
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
vec![ADMIN_TEST_USER],
)
.await
@ -647,7 +648,7 @@ async fn test_oauth2_openid_public_flow_impl(
.idm_oauth2_rs_update_claim_map(
TEST_INTEGRATION_RS_ID,
"test_claim",
IDM_ALL_ACCOUNTS.name,
NAME_IDM_ALL_ACCOUNTS,
&["claim_a".to_string(), "claim_b".to_string()],
)
.await
@ -680,6 +681,7 @@ async fn test_oauth2_openid_public_flow_impl(
// Create a new reqwest client - we'll be using this manually.
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.tls_built_in_native_certs(false)
.no_proxy()
.build()
.expect("Failed to create client.");
@ -691,7 +693,7 @@ async fn test_oauth2_openid_public_flow_impl(
.await
.expect("Failed to send request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
assert_no_cache!(response);
let mut jwk_set: JwkKeySet = response
@ -732,7 +734,7 @@ async fn test_oauth2_openid_public_flow_impl(
.await
.expect("Failed to send request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
assert_no_cache!(response);
let consent_req: AuthorisationResponse = response
@ -764,7 +766,7 @@ async fn test_oauth2_openid_public_flow_impl(
.expect("Failed to send request.");
// This should yield a 302 redirect with some query params.
assert_eq!(response.status(), reqwest::StatusCode::FOUND);
assert_eq!(response.status(), StatusCode::FOUND);
assert_no_cache!(response);
// And we should have a URL in the location header.
@ -812,7 +814,7 @@ async fn test_oauth2_openid_public_flow_impl(
.await
.expect("Failed to send code exchange request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
assert_no_cache!(response);
// The body is a json AccessTokenResponse
@ -857,10 +859,10 @@ async fn test_oauth2_openid_public_flow_impl(
.await
.expect("Failed to send userinfo preflight request.");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
let cors_header: &str = response
.headers()
.get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.expect("missing access-control-allow-origin header")
.to_str()
.expect("invalid access-control-allow-origin header");
@ -899,7 +901,7 @@ async fn test_oauth2_openid_public_flow_impl(
///
/// The response should be returned as a query parameter.
#[kanidmd_testkit::test]
async fn test_oauth2_openid_public_flow_mode_unset(rsclient: KanidmClient) {
async fn test_oauth2_openid_public_flow_mode_unset(rsclient: &KanidmClient) {
test_oauth2_openid_public_flow_impl(rsclient, None, false).await;
}
@ -908,7 +910,7 @@ async fn test_oauth2_openid_public_flow_mode_unset(rsclient: KanidmClient) {
///
/// The response should be returned as a query parameter.
#[kanidmd_testkit::test]
async fn test_oauth2_openid_public_flow_mode_query(rsclient: KanidmClient) {
async fn test_oauth2_openid_public_flow_mode_query(rsclient: &KanidmClient) {
test_oauth2_openid_public_flow_impl(rsclient, Some("query"), false).await;
}
@ -917,12 +919,12 @@ async fn test_oauth2_openid_public_flow_mode_query(rsclient: KanidmClient) {
///
/// The response should be returned in the URI's fragment.
#[kanidmd_testkit::test]
async fn test_oauth2_openid_public_flow_mode_fragment(rsclient: KanidmClient) {
async fn test_oauth2_openid_public_flow_mode_fragment(rsclient: &KanidmClient) {
test_oauth2_openid_public_flow_impl(rsclient, Some("fragment"), true).await;
}
#[kanidmd_testkit::test]
async fn test_oauth2_token_post_bad_bodies(rsclient: KanidmClient) {
async fn test_oauth2_token_post_bad_bodies(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -930,6 +932,7 @@ async fn test_oauth2_token_post_bad_bodies(rsclient: KanidmClient) {
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.tls_built_in_native_certs(false)
.no_proxy()
.build()
.expect("Failed to create client.");
@ -957,7 +960,7 @@ async fn test_oauth2_token_post_bad_bodies(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_oauth2_token_revoke_post(rsclient: KanidmClient) {
async fn test_oauth2_token_revoke_post(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -965,6 +968,7 @@ async fn test_oauth2_token_revoke_post(rsclient: KanidmClient) {
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.tls_built_in_native_certs(false)
.no_proxy()
.build()
.expect("Failed to create client.");

View file

@ -1,10 +1,10 @@
use kanidm_client::{ClientError, KanidmClient};
use kanidm_client::{ClientError, KanidmClient, StatusCode};
use kanidm_proto::constants::ATTR_MAIL;
use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
use serde_json::Value;
#[kanidmd_testkit::test]
async fn test_v1_person_id_patch(rsclient: KanidmClient) {
async fn test_v1_person_id_patch(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -25,7 +25,7 @@ async fn test_v1_person_id_patch(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_v1_person_id_ssh_pubkeys_post(rsclient: KanidmClient) {
async fn test_v1_person_id_ssh_pubkeys_post(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -48,6 +48,6 @@ async fn test_v1_person_id_ssh_pubkeys_post(rsclient: KanidmClient) {
eprintln!("response: {:#?}", response);
assert!(matches!(
response,
ClientError::Http(reqwest::StatusCode::BAD_REQUEST, _, _)
ClientError::Http(StatusCode::BAD_REQUEST, _, _)
));
}

View file

@ -3,6 +3,7 @@ use std::path::Path;
use std::time::SystemTime;
use kanidm_proto::constants::{ATTR_GIDNUMBER, KSESSIONID};
use kanidm_proto::internal::{
ApiToken, CURegState, Filter, ImageValue, Modify, ModifyList, UatPurpose, UserAuthToken,
};
@ -10,10 +11,10 @@ use kanidm_proto::v1::{
AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthResponse, AuthState, AuthStep,
Entry,
};
use kanidmd_lib::constants::{NAME_IDM_ADMINS, NAME_SYSTEM_ADMINS};
use kanidmd_lib::credential::totp::Totp;
use kanidmd_lib::prelude::{
Attribute, BUILTIN_GROUP_IDM_ADMINS_V1, BUILTIN_GROUP_SYSTEM_ADMINS_V1,
};
use kanidmd_lib::prelude::Attribute;
use tracing::{debug, trace};
use std::str::FromStr;
@ -28,7 +29,7 @@ use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
const UNIX_TEST_PASSWORD: &str = "unix test user password";
#[kanidmd_testkit::test]
async fn test_server_create(rsclient: KanidmClient) {
async fn test_server_create(rsclient: &KanidmClient) {
let e: Entry = serde_json::from_str(
r#"{
"attrs": {
@ -54,7 +55,7 @@ async fn test_server_create(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_whoami_anonymous(rsclient: KanidmClient) {
async fn test_server_whoami_anonymous(rsclient: &KanidmClient) {
// First show we are un-authenticated.
let pre_res = rsclient.whoami().await;
// This means it was okay whoami, but no uat attached.
@ -83,7 +84,7 @@ async fn test_server_whoami_anonymous(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_whoami_admin_simple_password(rsclient: KanidmClient) {
async fn test_server_whoami_admin_simple_password(rsclient: &KanidmClient) {
// First show we are un-authenticated.
let pre_res = rsclient.whoami().await;
// This means it was okay whoami, but no uat attached.
@ -108,7 +109,7 @@ async fn test_server_whoami_admin_simple_password(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_search(rsclient: KanidmClient) {
async fn test_server_search(rsclient: &KanidmClient) {
// First show we are un-authenticated.
let pre_res = rsclient.whoami().await;
// This means it was okay whoami, but no uat attached.
@ -134,7 +135,7 @@ async fn test_server_search(rsclient: KanidmClient) {
// test the rest group endpoint.
#[kanidmd_testkit::test]
async fn test_server_rest_group_read(rsclient: KanidmClient) {
async fn test_server_rest_group_read(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -144,16 +145,13 @@ async fn test_server_rest_group_read(rsclient: KanidmClient) {
let g_list = rsclient.idm_group_list().await.unwrap();
assert!(!g_list.is_empty());
let g = rsclient
.idm_group_get(BUILTIN_GROUP_IDM_ADMINS_V1.name)
.await
.unwrap();
let g = rsclient.idm_group_get(NAME_IDM_ADMINS).await.unwrap();
assert!(g.is_some());
println!("{:?}", g);
}
#[kanidmd_testkit::test]
async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_group_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -165,7 +163,7 @@ async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
// Create a new group
rsclient
.idm_group_create("demo_group", Some(BUILTIN_GROUP_IDM_ADMINS_V1.name))
.idm_group_create("demo_group", Some(NAME_IDM_ADMINS))
.await
.unwrap();
@ -245,16 +243,13 @@ async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
assert_eq!(g_list_3.len(), g_list.len());
// Check we can get an exact group
let g = rsclient
.idm_group_get(BUILTIN_GROUP_IDM_ADMINS_V1.name)
.await
.unwrap();
let g = rsclient.idm_group_get(NAME_IDM_ADMINS).await.unwrap();
assert!(g.is_some());
println!("{:?}", g);
// They should have members
let members = rsclient
.idm_group_get_members(BUILTIN_GROUP_IDM_ADMINS_V1.name)
.idm_group_get_members(NAME_IDM_ADMINS)
.await
.unwrap();
println!("{:?}", members);
@ -268,7 +263,7 @@ async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_account_read(rsclient: KanidmClient) {
async fn test_server_rest_account_read(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -284,7 +279,7 @@ async fn test_server_rest_account_read(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_schema_read(rsclient: KanidmClient) {
async fn test_server_rest_schema_read(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -318,7 +313,7 @@ async fn test_server_rest_schema_read(rsclient: KanidmClient) {
// Test resetting a radius cred, and then checking/viewing it.
#[kanidmd_testkit::test]
async fn test_server_radius_credential_lifecycle(rsclient: KanidmClient) {
async fn test_server_radius_credential_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -326,7 +321,7 @@ async fn test_server_radius_credential_lifecycle(rsclient: KanidmClient) {
// All admin to create persons.
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -389,7 +384,7 @@ async fn test_server_radius_credential_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_person_account_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_person_account_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -397,7 +392,7 @@ async fn test_server_rest_person_account_lifecycle(rsclient: KanidmClient) {
// To enable the admin to actually make some of these changes, we have
// to make them a people admin. NOT recommended in production!
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -444,7 +439,7 @@ async fn test_server_rest_person_account_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_sshkey_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_sshkey_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -514,7 +509,7 @@ async fn test_server_rest_sshkey_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_domain_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_domain_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -544,14 +539,14 @@ async fn test_server_rest_domain_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_posix_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", 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(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -571,7 +566,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
// Extend the group with posix attrs
rsclient
.idm_group_create("posix_group", Some(BUILTIN_GROUP_IDM_ADMINS_V1.name))
.idm_group_create("posix_group", Some(NAME_IDM_ADMINS))
.await
.unwrap();
rsclient
@ -665,7 +660,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_posix_auth_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_posix_auth_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -676,7 +671,7 @@ async fn test_server_rest_posix_auth_lifecycle(rsclient: KanidmClient) {
// Not recommended in production!
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -765,7 +760,7 @@ async fn test_server_rest_posix_auth_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_recycle_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_recycle_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -773,7 +768,7 @@ async fn test_server_rest_recycle_lifecycle(rsclient: KanidmClient) {
// Not recommended in production!
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -819,14 +814,14 @@ async fn test_server_rest_recycle_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
async fn test_server_rest_oauth2_basic_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
assert!(res.is_ok());
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -902,11 +897,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
// Check that we can add scope maps and delete them.
rsclient
.idm_oauth2_rs_update_scope_map(
"test_integration",
BUILTIN_GROUP_SYSTEM_ADMINS_V1.name,
vec!["a", "b"],
)
.idm_oauth2_rs_update_scope_map("test_integration", NAME_SYSTEM_ADMINS, vec!["a", "b"])
.await
.expect("Failed to create scope map");
@ -921,11 +912,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
// Check we can update a scope map
rsclient
.idm_oauth2_rs_update_scope_map(
"test_integration",
BUILTIN_GROUP_SYSTEM_ADMINS_V1.name,
vec!["a", "b", "c"],
)
.idm_oauth2_rs_update_scope_map("test_integration", NAME_SYSTEM_ADMINS, vec!["a", "b", "c"])
.await
.expect("Failed to create scope map");
@ -955,9 +942,8 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
assert!(res.is_ok());
//test getting the image
let client = reqwest::Client::new();
let response = client
let response = rsclient
.client()
.get(rsclient.make_url("/ui/images/oauth2/test_integration"))
.bearer_auth(rsclient.get_token().await.unwrap());
@ -1009,7 +995,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
// Check we can delete a scope map.
rsclient
.idm_oauth2_rs_delete_scope_map("test_integration", BUILTIN_GROUP_SYSTEM_ADMINS_V1.name)
.idm_oauth2_rs_delete_scope_map("test_integration", NAME_SYSTEM_ADMINS)
.await
.expect("Failed to delete scope map");
@ -1041,7 +1027,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
async fn test_server_credential_update_session_pw(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -1049,7 +1035,7 @@ async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
// Not recommended in production!
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -1116,7 +1102,7 @@ async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_credential_update_session_totp_pw(rsclient: KanidmClient) {
async fn test_server_credential_update_session_totp_pw(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -1124,7 +1110,7 @@ async fn test_server_credential_update_session_totp_pw(rsclient: KanidmClient) {
// Not recommended in production!
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -1253,7 +1239,7 @@ async fn setup_demo_account_passkey(rsclient: &KanidmClient) -> WebauthnAuthenti
// Not recommended in production!
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();
@ -1379,7 +1365,7 @@ async fn setup_demo_account_password(
}
#[kanidmd_testkit::test]
async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
async fn test_server_credential_update_session_passkey(rsclient: &KanidmClient) {
let mut wa = setup_demo_account_passkey(&rsclient).await;
let res = rsclient
@ -1397,7 +1383,7 @@ async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
async fn test_server_api_token_lifecycle(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -1409,7 +1395,7 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
.idm_service_account_create(
test_service_account_username,
"Test Service",
BUILTIN_GROUP_IDM_ADMINS_V1.name,
NAME_IDM_ADMINS,
)
.await
.expect("Failed to create service account");
@ -1580,7 +1566,7 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
}
#[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
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -1703,7 +1689,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_user_auth_reauthentication(rsclient: KanidmClient) {
async fn test_server_user_auth_reauthentication(rsclient: &KanidmClient) {
let mut wa = setup_demo_account_passkey(&rsclient).await;
let res = rsclient
@ -1793,7 +1779,8 @@ async fn start_password_session(
password: &str,
privileged: bool,
) -> Result<UserAuthToken, ()> {
let client = reqwest::Client::new();
let fresh_rsclient = rsclient.new_session().unwrap();
let client = fresh_rsclient.client();
let authreq = AuthRequest {
step: AuthStep::Init2 {
@ -1881,7 +1868,7 @@ async fn start_password_session(
}
#[kanidmd_testkit::test]
async fn test_server_user_auth_unprivileged(rsclient: KanidmClient) {
async fn test_server_user_auth_unprivileged(rsclient: &KanidmClient) {
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
.await
.expect("Failed to setup demo_account");
@ -1904,7 +1891,7 @@ async fn test_server_user_auth_unprivileged(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_server_user_auth_privileged_shortcut(rsclient: KanidmClient) {
async fn test_server_user_auth_privileged_shortcut(rsclient: &KanidmClient) {
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
.await
.expect("Failed to setup demo_account");

View file

@ -2,14 +2,14 @@ use compact_jwt::{traits::JwsVerifiable, JwsCompact, JwsEs256Verifier, JwsVerifi
use kanidm_client::KanidmClient;
use kanidm_proto::internal::ScimSyncToken;
use kanidm_proto::scim_v1::ScimEntryGetQuery;
use kanidmd_lib::prelude::{Attribute, BUILTIN_GROUP_IDM_ADMINS_V1};
use kanidmd_lib::constants::NAME_IDM_ADMINS;
use kanidmd_lib::prelude::Attribute;
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
use reqwest::header::HeaderValue;
use std::str::FromStr;
use url::Url;
#[kanidmd_testkit::test]
async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
async fn test_sync_account_lifecycle(rsclient: &KanidmClient) {
let a_res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
@ -104,65 +104,7 @@ async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
}
#[kanidmd_testkit::test]
async fn test_scim_sync_get(rsclient: KanidmClient) {
// We need to do manual reqwests here.
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {:?}", rsclient.get_token().await)).unwrap(),
);
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.default_headers(headers)
.build()
.unwrap();
// here we test the /ui/ endpoint which should have the headers
let response = match client.get(rsclient.make_url("/scim/v1/Sync")).send().await {
Ok(value) => value,
Err(error) => {
panic!(
"Failed to query {:?} : {:#?}",
rsclient.make_url("/scim/v1/Sync"),
error
);
}
};
eprintln!("response: {:#?}", response);
assert!(response.status().is_client_error());
// check that the CSP headers are coming back
eprintln!(
"csp headers: {:#?}",
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY)
);
assert_ne!(
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY),
None
);
// test that the proper content type comes back
let url = rsclient.make_url("/scim/v1/Sink");
let response = match client.get(url.clone()).send().await {
Ok(value) => value,
Err(error) => {
panic!("Failed to query {:?} : {:#?}", url, error);
}
};
assert!(response.status().is_success());
let content_type = response.headers().get(http::header::CONTENT_TYPE).unwrap();
assert!(content_type.to_str().unwrap().contains("text/html"));
assert!(response.text().await.unwrap().contains("Sink"));
}
#[kanidmd_testkit::test]
async fn test_scim_sync_entry_get(rsclient: KanidmClient) {
async fn test_scim_sync_entry_get(rsclient: &KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
@ -170,7 +112,7 @@ async fn test_scim_sync_entry_get(rsclient: KanidmClient) {
// All admin to create persons.
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.idm_group_add_members(NAME_IDM_ADMINS, &["admin"])
.await
.unwrap();

View file

@ -2,12 +2,9 @@ use kanidm_client::KanidmClient;
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
#[kanidmd_testkit::test]
async fn test_v1_service_account_id_attr_attr_delete(rsclient: KanidmClient) {
async fn test_v1_service_account_id_attr_attr_delete(rsclient: &KanidmClient) {
// We need to do manual reqwests here.
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let client = rsclient.client();
// let post_body = serde_json::json!({"filter": "self"}).to_string();

View file

@ -2,11 +2,8 @@ use kanidm_client::KanidmClient;
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
#[kanidmd_testkit::test]
async fn test_v1_system_post_attr(rsclient: KanidmClient) {
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
async fn test_v1_system_post_attr(rsclient: &KanidmClient) {
let client = rsclient.client();
let response = match client
.post(rsclient.make_url("/v1/system/_attr/domain_name"))

View file

@ -1,19 +1,14 @@
use kanidm_client::KanidmClient;
use kanidmd_lib::prelude::BUILTIN_GROUP_IDM_ADMINS_V1;
use kanidmd_lib::constants::NAME_IDM_ADMINS;
use kanidmd_testkit::*;
#[kanidmd_testkit::test]
async fn account_id_unix_token(rsclient: KanidmClient) {
login_put_admin_idm_admins(&rsclient).await;
async fn account_id_unix_token(rsclient: &KanidmClient) {
login_put_admin_idm_admins(rsclient).await;
create_user(&rsclient, "group_manager", "idm_group_manage_priv").await;
// create test user without creating new groups
create_user(
&rsclient,
NOT_ADMIN_TEST_USERNAME,
BUILTIN_GROUP_IDM_ADMINS_V1.name,
)
.await;
create_user(&rsclient, NOT_ADMIN_TEST_USERNAME, NAME_IDM_ADMINS).await;
login_account(&rsclient, "group_manager").await;
let response = rsclient

View file

@ -22,14 +22,14 @@ dev-oauth2-device-flow = []
[lib]
name = "kanidm_cli"
path = "src/cli/lib.rs"
test = true
test = false
doctest = false
[[bin]]
name = "kanidm"
path = "src/cli/main.rs"
doc = false
test = true
test = false
doctest = false
[[bin]]

View file

@ -12,8 +12,8 @@ repository = { workspace = true }
[lib]
test = false
doctest = false
test = false
[features]

View file

@ -11,6 +11,11 @@ license = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
[[bin]]
name = "kanidm-ipa-sync"
test = false
doctest = false
[dependencies]
clap = { workspace = true, features = ["derive", "env"] }
chrono = { workspace = true }

View file

@ -11,6 +11,11 @@ license = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
[[bin]]
name = "kanidm-ldap-sync"
test = false
doctest = false
[dependencies]
clap = { workspace = true, features = ["derive", "env"] }
chrono = { workspace = true }

View file

@ -14,7 +14,7 @@ repository = { workspace = true }
[[bin]]
name = "orca"
path = "src/main.rs"
test = true
test = false
doctest = false
[dependencies]

View file

@ -21,28 +21,28 @@ tpm = ["kanidm-hsm-crypto/tpm"]
name = "kanidm_unixd"
path = "src/bin/kanidm_unixd.rs"
required-features = ["unix"]
test = true
test = false
doctest = false
[[bin]]
name = "kanidm_unixd_tasks"
path = "src/bin/kanidm_unixd_tasks.rs"
required-features = ["unix"]
test = true
test = false
doctest = false
[[bin]]
name = "kanidm_ssh_authorizedkeys"
path = "src/bin/kanidm_ssh_authorizedkeys.rs"
required-features = ["unix"]
test = true
test = false
doctest = false
[[bin]]
name = "kanidm-unix"
path = "src/bin/kanidm-unix.rs"
required-features = ["unix"]
test = true
test = false
doctest = false
[lib]

View file

@ -88,6 +88,7 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
// Run fixtures
let adminclient = KanidmClientBuilder::new()
.address(addr.clone())
.enable_native_ca_roots(false)
.no_proxy()
.build()
.expect("Failed to build sync client");
@ -96,12 +97,14 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
let client = KanidmClientBuilder::new()
.address(addr.clone())
.enable_native_ca_roots(false)
.no_proxy()
.build()
.expect("Failed to build async admin client");
let rsclient = KanidmClientBuilder::new()
.address(addr)
.enable_native_ca_roots(false)
.no_proxy()
.build()
.expect("Failed to build client");