20231012 346 name deny list ()

* Migrate to improved system config reload, cleanup acc pol
* Denied names feature
This commit is contained in:
Firstyear 2023-10-13 08:50:36 +10:00 committed by GitHub
parent 88da55260a
commit 8bcf1935a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 817 additions and 296 deletions

View file

@ -175,13 +175,13 @@ doc:
.PHONY: doc/format
doc/format: ## Format docs and the Kanidm book
find . -type f -not -path './target/*' -not -path '*/.venv/*' \
find . -type f -not -path './target/*' -not -path '*/.venv/*' -not -path './vendor/*'\
-name \*.md \
-exec deno fmt --check $(MARKDOWN_FORMAT_ARGS) "{}" +
.PHONY: doc/format/fix
doc/format/fix: ## Fix docs and the Kanidm book
find . -type f -not -path './target/*' -not -path '*/.venv/*' \
find . -type f -not -path './target/*' -not -path '*/.venv/*' -not -path './vendor/*'\
-name \*.md \
-exec deno fmt $(MARKDOWN_FORMAT_ARGS) "{}" +

View file

@ -284,6 +284,29 @@ their own mail.
kanidm group add-members idm_people_self_write_mail_priv demo_user --name idm_admin
```
### Denied Names
Users of Kanidm can change their name at any time. However, there are some cases where you may wish
to deny some name values from being usable.
To achieve this you can set names to be in the denied-name list:
```bash
kanidm system denied-names append <name> [<name> ...]
```
You can display the currently denied names with:
```bash
kanidm system denied-names show
```
To allow a name to be used again it can be removed from the list:
```
kanidm system denied-names remove <name> [<name> ...]
```
## Why Can't I Change admin With idm\_admin?
As a security mechanism there is a distinction between "accounts" and "high permission accounts".

View file

@ -57,8 +57,8 @@ You should _NOT_ use DNS based failover mechanisms as clients can cache DNS reco
## Maximum Downtime of a Server
Kanidm's replication protocol enforces limits on how long a server can be offline. This is due
to how tombstones are handled. By default the maximum is 7 days. If a server is offline for more
than 7 days a refresh will be required for that server to continue participation in the topology.
Kanidm's replication protocol enforces limits on how long a server can be offline. This is due to
how tombstones are handled. By default the maximum is 7 days. If a server is offline for more than 7
days a refresh will be required for that server to continue participation in the topology.
It is important you avoid extended downtime of servers to avoid this condition.

View file

@ -18,8 +18,8 @@ To minimise this, it's recommended that when you operate replication in a highly
deployment that you have a load balancer that uses sticky sessions so that users are redirected to
the same server unless a failover event occurs. This will help to minimise discrepancies.
Alternately you can treat replication and "active-passive" and have your load balancer failover
between the two nodes. Since replication is eventually consistent, there is no need for a
failover or failback procedure.
between the two nodes. Since replication is eventually consistent, there is no need for a failover
or failback procedure.
In this chapter we will cover the details of planning, deploying and maintaining replication between
Kanidm servers.

View file

@ -132,7 +132,6 @@ fn test_kanidmclientbuilder_display() {
println!("badness: {}", badness.to_string());
assert!(badness.to_string().contains("verify_ca: false"));
assert!(badness.to_string().contains("verify_hostnames: false"));
}
#[derive(Debug)]
@ -965,10 +964,7 @@ impl KanidmClient {
dest: &str,
request: R,
) -> Result<(), ClientError> {
let response = self
.client
.delete(self.make_url(dest))
.json(&request);
let response = self.client.delete(self.make_url(dest)).json(&request);
let response = {
let tguard = self.bearer_token.read().await;

View file

@ -24,6 +24,23 @@ impl KanidmClient {
.await
}
pub async fn system_denied_names_get(&self) -> Result<Vec<String>, ClientError> {
let list: Option<Vec<String>> = self
.perform_get_request("/v1/system/_attr/denied_name")
.await?;
Ok(list.unwrap_or_default())
}
pub async fn system_denied_names_append(&self, list: &Vec<String>) -> Result<(), ClientError> {
self.perform_post_request("/v1/system/_attr/denied_name", list)
.await
}
pub async fn system_denied_names_remove(&self, list: &Vec<String>) -> Result<(), ClientError> {
self.perform_delete_request_with_body("/v1/system/_attr/denied_name", list)
.await
}
pub async fn system_authsession_expiry_get(&self) -> Result<u32, ClientError> {
let list: Option<[String; 1]> = self
.perform_get_request("/v1/system/_attr/authsession_expiry")

View file

@ -142,7 +142,7 @@ impl fmt::Display for Diagnosis {
writeln!(
f,
"\n note that accesibility does not account for ACL's or MAC"
"\n note that accessibility does not account for ACL's or MAC"
)?;
writeln!(f, "-- end diagnosis")
}

View file

@ -54,6 +54,7 @@ pub const ATTR_CLASSNAME: &str = "classname";
pub const ATTR_CN: &str = "cn";
pub const ATTR_COOKIE_PRIVATE_KEY: &str = "cookie_private_key";
pub const ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: &str = "credential_update_intent_token";
pub const ATTR_DENIED_NAME: &str = "denied_name";
pub const ATTR_DESCRIPTION: &str = "description";
pub const ATTR_DEVICEKEYS: &str = "devicekeys";
pub const ATTR_DIRECTMEMBEROF: &str = "directmemberof";

View file

@ -85,6 +85,7 @@ pub enum ConsistencyError {
ChangelogDesynchronised(u64),
ChangeStateDesynchronised(u64),
RuvInconsistent(String),
DeniedName(Uuid),
}
#[derive(Serialize, Deserialize, Debug)]
@ -275,6 +276,8 @@ pub enum OperationError {
TransactionAlreadyCommitted,
/// when you ask for a gid that's lower than a safe minimum
GidOverlapsSystemMin(u32),
/// When a name is denied by the system config
ValueDenyName,
}
impl PartialEq for OperationError {

View file

@ -152,7 +152,7 @@ impl<'a> Hash for (dyn IdlCacheKeyToRef + 'a) {
impl<'a> PartialOrd for (dyn IdlCacheKeyToRef + 'a) {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.keyref().partial_cmp(&other.keyref())
Some(self.cmp(&other.keyref()))
}
}

View file

@ -1988,9 +1988,7 @@ impl Backend {
idxkeys: Vec<IdxKey>,
vacuum: bool,
) -> Result<Self, OperationError> {
info!("DB tickets -> {:?}", cfg.pool_size);
info!("Profile -> {}", env!("KANIDM_PROFILE_NAME"));
info!("CPU Flags -> {}", env!("KANIDM_CPU_FLAGS"));
debug!(db_tickets = ?cfg.pool_size, profile = %env!("KANIDM_PROFILE_NAME"), cpu_flags = %env!("KANIDM_CPU_FLAGS"));
// If in memory, reduce pool to 1
if cfg.path.is_empty() {

View file

@ -1392,9 +1392,10 @@ lazy_static! {
Attribute::Uuid,
Attribute::Description,
Attribute::BadlistPassword,
Attribute::DeniedName,
],
modify_removed_attrs: vec![Attribute::BadlistPassword],
modify_present_attrs: vec![Attribute::BadlistPassword],
modify_removed_attrs: vec![Attribute::BadlistPassword, Attribute::DeniedName],
modify_present_attrs: vec![Attribute::BadlistPassword, Attribute::DeniedName],
..Default::default()
};

View file

@ -61,6 +61,7 @@ pub enum Attribute {
Cn,
CookiePrivateKey,
CredentialUpdateIntentToken,
DeniedName,
Description,
DeviceKeys,
DirectMemberOf,
@ -236,6 +237,7 @@ impl TryFrom<String> for Attribute {
ATTR_CN => Attribute::Cn,
ATTR_COOKIE_PRIVATE_KEY => Attribute::CookiePrivateKey,
ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN => Attribute::CredentialUpdateIntentToken,
ATTR_DENIED_NAME => Attribute::DeniedName,
ATTR_DESCRIPTION => Attribute::Description,
ATTR_DEVICEKEYS => Attribute::DeviceKeys,
ATTR_DIRECTMEMBEROF => Attribute::DirectMemberOf,
@ -387,6 +389,7 @@ impl From<Attribute> for &'static str {
Attribute::Cn => ATTR_CN,
Attribute::CookiePrivateKey => ATTR_COOKIE_PRIVATE_KEY,
Attribute::CredentialUpdateIntentToken => ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
Attribute::DeniedName => ATTR_DENIED_NAME,
Attribute::Description => ATTR_DESCRIPTION,
Attribute::DeviceKeys => ATTR_DEVICEKEYS,
Attribute::DirectMemberOf => ATTR_DIRECTMEMBEROF,

View file

@ -142,6 +142,7 @@ pub static ref SCHEMA_ATTR_DOMAIN_UUID: SchemaAttribute = SchemaAttribute {
syntax: SyntaxType::Uuid,
..Default::default()
};
pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
name: Attribute::DomainSsid.into(),
@ -153,6 +154,14 @@ pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
..Default::default()
};
pub static ref SCHEMA_ATTR_DENIED_NAME: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_DENIED_NAME,
name: Attribute::DeniedName.into(),
description: "Iname values that are not allowed to be used in 'name'.".to_string(),
syntax: SyntaxType::Utf8StringIname,
..Default::default()
};
pub static ref SCHEMA_ATTR_DOMAIN_TOKEN_KEY: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_DOMAIN_TOKEN_KEY,
name: Attribute::DomainTokenKey.into(),
@ -733,7 +742,8 @@ pub static ref SCHEMA_CLASS_SYSTEM_CONFIG: SchemaClass = SchemaClass {
Attribute::Description.into(),
Attribute::BadlistPassword.into(),
Attribute::AuthSessionExpiry.into(),
Attribute::PrivilegeExpiry.into()
Attribute::PrivilegeExpiry.into(),
Attribute::DeniedName.into()
],
..Default::default()
};

View file

@ -237,8 +237,8 @@ pub const UUID_SCHEMA_ATTR_AUTH_SESSION_EXPIRY: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000141");
pub const UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000142");
pub const UUID_SCHEMA_ATTR_IMAGE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000143");
pub const UUID_SCHEMA_ATTR_DENIED_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000144");
// System and domain infos
// I'd like to strongly criticise william of the past for making poor choices about these allocations.

View file

@ -174,7 +174,7 @@ impl fmt::Debug for FilterComp {
write!(f, ")")
}
FilterComp::AndNot(inner) => {
write!(f, "and not ( {:?} )", inner)
write!(f, "not ( {:?} )", inner)
}
FilterComp::SelfUuid => {
write!(f, "uuid eq self")
@ -211,19 +211,37 @@ impl fmt::Debug for FilterResolved {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FilterResolved::Eq(attr, pv, idx) => {
write!(f, "{} ({:?}) eq {:?}", attr, idx, pv)
write!(
f,
"(s{} {} eq {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::Sub(attr, pv, idx) => {
write!(f, "{} ({:?}) sub {:?}", attr, idx, pv)
write!(
f,
"(s{} {} sub {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::Pres(attr, idx) => {
write!(f, "{} ({:?}) pres", attr, idx)
write!(f, "(s{} {} pres)", idx.unwrap_or(NonZeroU8::MAX), attr)
}
FilterResolved::LessThan(attr, pv, idx) => {
write!(f, "{} ({:?}) lt {:?}", attr, idx, pv)
write!(
f,
"(s{} {} lt {:?})",
idx.unwrap_or(NonZeroU8::MAX),
attr,
pv
)
}
FilterResolved::And(list, idx) => {
write!(f, "({:?} ", idx)?;
write!(f, "(s{} ", idx.unwrap_or(NonZeroU8::MAX))?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
@ -233,7 +251,7 @@ impl fmt::Debug for FilterResolved {
write!(f, ")")
}
FilterResolved::Or(list, idx) => {
write!(f, "({:?} ", idx)?;
write!(f, "(s{} ", idx.unwrap_or(NonZeroU8::MAX))?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
@ -243,7 +261,7 @@ impl fmt::Debug for FilterResolved {
write!(f, ")")
}
FilterResolved::Inclusion(list, idx) => {
write!(f, "({:?} ", idx)?;
write!(f, "(s{} ", idx.unwrap_or(NonZeroU8::MAX))?;
for (i, fc) in list.iter().enumerate() {
write!(f, "{:?}", fc)?;
if i != list.len() - 1 {
@ -253,7 +271,7 @@ impl fmt::Debug for FilterResolved {
write!(f, ")")
}
FilterResolved::AndNot(inner, idx) => {
write!(f, "and not ({:?} {:?} )", idx, inner)
write!(f, "not (s{} {:?})", idx.unwrap_or(NonZeroU8::MAX), inner)
}
}
}

View file

@ -341,29 +341,26 @@ impl CredHandler {
generated: bool,
who: Uuid,
async_tx: &Sender<DelayedAction>,
pw_badlist_set: Option<&HashSet<String>>,
pw_badlist_set: &HashSet<String>,
) -> CredState {
match cred {
AuthCredential::Password(cleartext) => {
if pw.verify(cleartext.as_str()).unwrap_or(false) {
match pw_badlist_set {
Some(p) if p.contains(&cleartext.to_lowercase()) => {
security_error!("Handler::Password -> Result::Denied - Password found in badlist during login");
CredState::Denied(PW_BADLIST_MSG)
}
_ => {
security_info!("Handler::Password -> Result::Success");
Self::maybe_pw_upgrade(pw, who, cleartext.as_str(), async_tx);
if generated {
CredState::Success {
auth_type: AuthType::GeneratedPassword,
cred_id,
}
} else {
CredState::Success {
auth_type: AuthType::Password,
cred_id,
}
if pw_badlist_set.contains(&cleartext.to_lowercase()) {
security_error!("Handler::Password -> Result::Denied - Password found in badlist during login");
CredState::Denied(PW_BADLIST_MSG)
} else {
security_info!("Handler::Password -> Result::Success");
Self::maybe_pw_upgrade(pw, who, cleartext.as_str(), async_tx);
if generated {
CredState::Success {
auth_type: AuthType::GeneratedPassword,
cred_id,
}
} else {
CredState::Success {
auth_type: AuthType::Password,
cred_id,
}
}
}
@ -393,7 +390,7 @@ impl CredHandler {
webauthn: &Webauthn,
who: Uuid,
async_tx: &Sender<DelayedAction>,
pw_badlist_set: Option<&HashSet<String>>,
pw_badlist_set: &HashSet<String>,
) -> CredState {
match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
(CredVerifyState::Init, CredVerifyState::Init) => {
@ -491,25 +488,22 @@ impl CredHandler {
match cred {
AuthCredential::Password(cleartext) => {
if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
match pw_badlist_set {
Some(p) if p.contains(&cleartext.to_lowercase()) => {
pw_mfa.pw_state = CredVerifyState::Fail;
security_error!("Handler::PasswordMfa -> Result::Denied - Password found in badlist during login");
CredState::Denied(PW_BADLIST_MSG)
}
_ => {
pw_mfa.pw_state = CredVerifyState::Success;
security_info!("Handler::PasswordMfa -> Result::Success - TOTP/WebAuthn/BackupCode OK, password OK");
Self::maybe_pw_upgrade(
&pw_mfa.pw,
who,
cleartext.as_str(),
async_tx,
);
CredState::Success {
auth_type: AuthType::PasswordMfa,
cred_id,
}
if pw_badlist_set.contains(&cleartext.to_lowercase()) {
pw_mfa.pw_state = CredVerifyState::Fail;
security_error!("Handler::PasswordMfa -> Result::Denied - Password found in badlist during login");
CredState::Denied(PW_BADLIST_MSG)
} else {
pw_mfa.pw_state = CredVerifyState::Success;
security_info!("Handler::PasswordMfa -> Result::Success - TOTP/WebAuthn/BackupCode OK, password OK");
Self::maybe_pw_upgrade(
&pw_mfa.pw,
who,
cleartext.as_str(),
async_tx,
);
CredState::Success {
auth_type: AuthType::PasswordMfa,
cred_id,
}
}
} else {
@ -609,7 +603,7 @@ impl CredHandler {
who: Uuid,
async_tx: &Sender<DelayedAction>,
webauthn: &Webauthn,
pw_badlist_set: Option<&HashSet<String>>,
pw_badlist_set: &HashSet<String>,
) -> CredState {
match self {
CredHandler::Anonymous { cred_id } => Self::validate_anonymous(cred, *cred_id),
@ -726,6 +720,9 @@ pub(crate) struct AuthSession {
// Do we store a copy of the entry?
// How do we know what claims to add?
account: Account,
// This policies that apply to this account
account_policy: AccountPolicy,
// Store how we plan to handle this sessions authentication: this is generally
// made apparent by the presentation of an application id or not. If none is presented
// we want the primary-interaction credentials.
@ -750,6 +747,7 @@ impl AuthSession {
/// or interleved write operations do not cause inconsistency in this process.
pub fn new(
account: Account,
account_policy: AccountPolicy,
issue: AuthIssueSession,
privileged: bool,
webauthn: &Webauthn,
@ -807,6 +805,7 @@ impl AuthSession {
// We can proceed
let auth_session = AuthSession {
account,
account_policy,
state,
issue,
intent: AuthIntent::InitialAuth { privileged },
@ -829,6 +828,7 @@ impl AuthSession {
/// initial authentication.
pub(crate) fn new_reauth(
account: Account,
account_policy: AccountPolicy,
session_id: Uuid,
session: &Session,
cred_id: Uuid,
@ -907,6 +907,7 @@ impl AuthSession {
let allow = handler.next_auth_allowed();
let auth_session = AuthSession {
account,
account_policy,
state: AuthSessionState::InProgress(handler),
issue,
intent: AuthIntent::Reauth {
@ -1019,7 +1020,7 @@ impl AuthSession {
audit_tx: &Sender<AuditEvent>,
webauthn: &Webauthn,
uat_jwt_signer: &JwsSigner,
account_policy: &AccountPolicy,
pw_badlist: &HashSet<String>,
) -> Result<AuthState, OperationError> {
let (next_state, response) = match &mut self.state {
AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => {
@ -1034,7 +1035,7 @@ impl AuthSession {
self.account.uuid,
async_tx,
webauthn,
Some(account_policy.pw_badlist_cache()),
pw_badlist,
) {
CredState::Success { auth_type, cred_id } => {
// Issue the uat based on a set of factors.
@ -1043,8 +1044,8 @@ impl AuthSession {
time,
async_tx,
cred_id,
account_policy.authsession_expiry(),
account_policy.privilege_expiry(),
self.account_policy.authsession_expiry(),
self.account_policy.privilege_expiry(),
)?;
let jwt = Jws::new(uat);
@ -1297,6 +1298,7 @@ mod tests {
let (session, state) = AuthSession::new(
anon_account,
AccountPolicy::default(),
AuthIssueSession::Token,
false,
&webauthn,
@ -1333,6 +1335,7 @@ mod tests {
) => {{
let (session, state) = AuthSession::new(
$account.clone(),
AccountPolicy::default(),
AuthIssueSession::Token,
$privileged,
$webauthn,
@ -1388,7 +1391,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(_)) => {}
_ => panic!(),
@ -1412,7 +1415,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(jwt, AuthIssueSession::Token)) => {
let uat = JwsUnverified::from_str(&jwt).expect("Failed to parse jwt");
@ -1489,7 +1492,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
_ => panic!(),
@ -1513,6 +1516,7 @@ mod tests {
) => {{
let (session, state) = AuthSession::new(
$account.clone(),
AccountPolicy::default(),
AuthIssueSession::Token,
false,
$webauthn,
@ -1614,7 +1618,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1640,7 +1644,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1663,7 +1667,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
_ => panic!(),
@ -1688,7 +1692,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1700,7 +1704,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -1725,7 +1729,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1737,7 +1741,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -1802,7 +1806,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1814,7 +1818,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
_ => panic!(),
@ -1840,6 +1844,7 @@ mod tests {
) => {{
let (session, state) = AuthSession::new(
$account.clone(),
AccountPolicy::default(),
AuthIssueSession::Token,
false,
$webauthn,
@ -2125,7 +2130,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2149,7 +2154,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2184,7 +2189,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -2214,7 +2219,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2226,7 +2231,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2262,7 +2267,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2274,7 +2279,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2343,7 +2348,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2367,7 +2372,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
_ => panic!(),
@ -2400,7 +2405,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -2430,7 +2435,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2442,7 +2447,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2472,7 +2477,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2484,7 +2489,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2508,7 +2513,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2520,7 +2525,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2550,7 +2555,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2562,7 +2567,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2642,7 +2647,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2665,7 +2670,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_BACKUPCODE_MSG),
_ => panic!(),
@ -2689,7 +2694,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2701,7 +2706,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2731,7 +2736,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2743,7 +2748,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2775,7 +2780,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2787,7 +2792,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2858,7 +2863,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2870,7 +2875,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2894,7 +2899,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
&pw_badlist_cache,
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2906,7 +2911,7 @@ mod tests {
&audit_tx,
&webauthn,
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
&pw_badlist_cache,
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),

View file

@ -1271,8 +1271,8 @@ impl<'a> IdmServerCredUpdateTransaction<'a> {
// we check the password as "lower case" to help eliminate possibilities
// also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
if self
.account_policy
.pw_badlist_cache()
.qs_read
.pw_badlist()
.contains(&cleartext.to_lowercase())
{
security_info!("Password found in badlist, rejecting");

View file

@ -35,6 +35,8 @@ impl<'a> IdmServerAuthTransaction<'a> {
// Setup the account record.
let account = Account::try_from_entry_ro(entry.as_ref(), &mut self.qs_read)?;
let account_policy = (*self.account_policy).clone();
security_info!(
username = %account.name,
issue = ?issue,
@ -130,6 +132,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
// Create a re-auth session
let (auth_session, state) = AuthSession::new_reauth(
account,
account_policy,
ident.session_id,
session,
session_cred_id,

View file

@ -11,7 +11,6 @@ use concread::cowcell::{CowCellReadTxn, CowCellWriteTxn};
use concread::hashmap::HashMap;
use concread::CowCell;
use fernet::Fernet;
use hashbrown::HashSet;
use kanidm_proto::internal::ScimSyncToken;
use kanidm_proto::v1::{
ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, UatPurpose,
@ -73,19 +72,13 @@ pub struct DomainKeys {
pub(crate) struct AccountPolicy {
privilege_expiry: u32,
authsession_expiry: u32,
pw_badlist_cache: HashSet<String>,
}
impl AccountPolicy {
pub(crate) fn new(
privilege_expiry: u32,
authsession_expiry: u32,
pw_badlist_cache: HashSet<String>,
) -> Self {
pub(crate) fn new(privilege_expiry: u32, authsession_expiry: u32) -> Self {
Self {
privilege_expiry,
authsession_expiry,
pw_badlist_cache,
}
}
@ -96,18 +89,6 @@ impl AccountPolicy {
pub(crate) fn authsession_expiry(&self) -> u32 {
self.authsession_expiry
}
pub(crate) fn pw_badlist_cache(&self) -> &HashSet<String> {
&self.pw_badlist_cache
}
#[cfg(test)]
pub(crate) fn from_pw_badlist_cache(pw_badlist_cache: HashSet<String>) -> Self {
Self {
pw_badlist_cache,
..Default::default()
}
}
}
impl Default for AccountPolicy {
@ -115,7 +96,6 @@ impl Default for AccountPolicy {
Self {
privilege_expiry: DEFAULT_AUTH_PRIVILEGE_EXPIRY,
authsession_expiry: DEFAULT_AUTH_SESSION_EXPIRY,
pw_badlist_cache: Default::default(), // TODO: more thoughts on this
}
}
}
@ -161,10 +141,10 @@ pub struct IdmServerAuthTransaction<'a> {
}
pub struct IdmServerCredUpdateTransaction<'a> {
pub(crate) _qs_read: QueryServerReadTransaction<'a>,
pub(crate) qs_read: QueryServerReadTransaction<'a>,
// sid: Sid,
pub(crate) webauthn: &'a Webauthn,
pub(crate) account_policy: CowCellReadTxn<AccountPolicy>,
pub(crate) _account_policy: CowCellReadTxn<AccountPolicy>,
pub(crate) cred_update_sessions: BptreeMapReadTxn<'a, Uuid, CredentialUpdateSessionMutex>,
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
pub(crate) crypto_policy: &'a CryptoPolicy,
@ -217,7 +197,6 @@ impl IdmServer {
fernet_private_key,
es256_private_key,
cookie_key,
pw_badlist_set,
oauth2rs_set,
privilege_expiry,
authsession_expiry,
@ -229,7 +208,6 @@ impl IdmServer {
qs_read.get_domain_fernet_private_key()?,
qs_read.get_domain_es256_private_key()?,
qs_read.get_domain_cookie_key()?,
qs_read.get_password_badlist()?,
// Add a read/reload of all oauth2 configurations.
qs_read.get_oauth2rs_set()?,
qs_read.get_privilege_expiry()?,
@ -311,7 +289,6 @@ impl IdmServer {
account_policy: Arc::new(CowCell::new(AccountPolicy::new(
privilege_expiry,
authsession_expiry,
pw_badlist_set,
))),
domain_keys,
oauth2rs: Arc::new(oauth2rs),
@ -380,10 +357,10 @@ impl IdmServer {
pub async fn cred_update_transaction(&self) -> IdmServerCredUpdateTransaction<'_> {
IdmServerCredUpdateTransaction {
_qs_read: self.qs.read().await,
qs_read: self.qs.read().await,
// sid: Sid,
webauthn: &self.webauthn,
account_policy: self.account_policy.read(),
_account_policy: self.account_policy.read(),
cred_update_sessions: self.cred_update_sessions.read(),
domain_keys: self.domain_keys.read(),
crypto_policy: &self.crypto_policy,
@ -1066,6 +1043,8 @@ impl<'a> IdmServerAuthTransaction<'a> {
// out of the session tree.
let account = Account::try_from_entry_ro(entry.as_ref(), &mut self.qs_read)?;
let account_policy = (*self.account_policy).clone();
trace!(?account.primary);
// Intent to take both trees to write.
@ -1100,6 +1079,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
let (auth_session, state) = AuthSession::new(
account,
account_policy,
init.issue,
init.privileged,
self.webauthn,
@ -1230,7 +1210,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
&self.audit_tx,
self.webauthn,
&self.domain_keys.uat_jwt_signer,
&self.account_policy,
self.qs_read.pw_badlist(),
)
.map(|aus| {
// Inspect the result:
@ -1666,7 +1646,11 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// check a password badlist to eliminate more content
// we check the password as "lower case" to help eliminate possibilities
// also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
if (self.account_policy.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
if self
.qs_write
.pw_badlist()
.contains(&cleartext.to_lowercase())
{
security_info!("Password found in badlist, rejecting");
Err(OperationError::PasswordQuality(vec![
PasswordFeedback::BadListed,
@ -2089,15 +2073,10 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
#[instrument(level = "debug", skip_all)]
pub fn commit(mut self) -> Result<(), OperationError> {
if self
.qs_write
.get_changed_uuids()
.contains(&UUID_SYSTEM_CONFIG)
{
// TODO: probably it would be more efficient to introduce a single check for each of the possible system configs
// but that would require changing what uuid the operation is assigned
if self.qs_write.get_changed_system_config() {
self.reload_system_account_policy()?;
};
if self.qs_write.get_changed_ouath2() {
self.qs_write
.get_oauth2rs_set()
@ -2160,7 +2139,6 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
}
fn reload_system_account_policy(&mut self) -> Result<(), OperationError> {
self.account_policy.pw_badlist_cache = self.qs_write.get_password_badlist()?;
self.account_policy.authsession_expiry = self.qs_write.get_authsession_expiry()?;
self.account_policy.privilege_expiry = self.qs_write.get_privilege_expiry()?;
Ok(())

View file

@ -31,7 +31,7 @@ impl Plugin for AccessExtract {
) -> Result<(), OperationError> {
}
#[instrument(level = "debug", name = "accessextract_pre_modify", skip(_qs, cand, _me))]
#[instrument(level = "debug", name = "accessextract_pre_modify", skip_all)]
fn pre_modify(
_qs: &mut QueryServerWriteTransaction,
_cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
@ -39,7 +39,7 @@ impl Plugin for AccessExtract {
) -> Result<(), OperationError> {
}
#[instrument(level = "debug", name = "accessextract_pre_delete", skip(_qs, cand, de))]
#[instrument(level = "debug", name = "accessextract_pre_delete", skip_all)]
fn pre_delete(
_qs: &mut QueryServerWriteTransaction,
// Should these be EntrySealed

View file

@ -498,8 +498,7 @@ mod tests {
// Test entry in db, and same name, reject.
#[test]
fn test_pre_create_name_unique() {
let e: Entry<EntryInit,EntryNew> = entry_init!(
let e: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson")),
(Attribute::Description, Value::new_utf8s("testperson")),
@ -523,7 +522,7 @@ mod tests {
// Test two entries in create that would have same name, reject.
#[test]
fn test_pre_create_name_unique_2() {
let e: Entry<EntryInit,EntryNew> = entry_init!(
let e: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson")),
(Attribute::Description, Value::new_utf8s("testperson")),
@ -550,12 +549,12 @@ mod tests {
// A mod to something that exists, reject.
#[test]
fn test_pre_modify_name_unique() {
let ea: Entry<EntryInit,EntryNew> = entry_init!(
let ea: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup_a")),
(Attribute::Description, Value::new_utf8s("testgroup"))
);
let eb: Entry<EntryInit,EntryNew> = entry_init!(
let eb: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup_b")),
(Attribute::Description, Value::new_utf8s("testgroup"))
@ -585,12 +584,12 @@ mod tests {
// Two items modded to have the same value, reject.
#[test]
fn test_pre_modify_name_unique_2() {
let ea: Entry<EntryInit,EntryNew> = entry_init!(
let ea: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup_a")),
(Attribute::Description, Value::new_utf8s("testgroup"))
);
let eb: Entry<EntryInit,EntryNew> = entry_init!(
let eb: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup_b")),
(Attribute::Description, Value::new_utf8s("testgroup"))

View file

@ -27,11 +27,7 @@ impl Plugin for Base {
"plugin_base"
}
#[instrument(
level = "debug",
name = "base_pre_create_transform",
skip(qs, cand, ce)
)]
#[instrument(level = "debug", name = "base_pre_create_transform", skip_all)]
#[allow(clippy::cognitive_complexity)]
fn pre_create_transform(
qs: &mut QueryServerWriteTransaction,
@ -156,7 +152,7 @@ impl Plugin for Base {
Ok(())
}
#[instrument(level = "debug", name = "base_pre_modify", skip(_qs, _cand, me))]
#[instrument(level = "debug", name = "base_pre_modify", skip_all)]
fn pre_modify(
_qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],
@ -180,7 +176,7 @@ impl Plugin for Base {
})
}
#[instrument(level = "debug", name = "base_pre_modify", skip(_qs, _cand, me))]
#[instrument(level = "debug", name = "base_pre_modify", skip_all)]
fn pre_batch_modify(
_qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],

View file

@ -20,7 +20,7 @@ impl Plugin for CredImport {
#[instrument(
level = "debug",
name = "password_import_pre_create_transform",
skip(_qs, cand, _ce)
skip_all
)]
fn pre_create_transform(
_qs: &mut QueryServerWriteTransaction,
@ -30,11 +30,7 @@ impl Plugin for CredImport {
Self::modify_inner(cand)
}
#[instrument(
level = "debug",
name = "password_import_pre_modify",
skip(_qs, cand, _me)
)]
#[instrument(level = "debug", name = "password_import_pre_modify", skip_all)]
fn pre_modify(
_qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],

View file

@ -95,7 +95,7 @@ impl DynGroup {
Ok(())
}
#[instrument(level = "debug", name = "dyngroup_reload", skip(qs))]
#[instrument(level = "debug", name = "dyngroup_reload", skip_all)]
pub fn reload(qs: &mut QueryServerWriteTransaction) -> Result<(), OperationError> {
let ident_internal = Identity::from_internal();
// Internal search all our definitions.

View file

@ -35,7 +35,11 @@ fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), Opera
let gid = uuid_to_gid_u32(u_ref);
// assert the value is greater than the system range.
if gid < GID_SYSTEM_NUMBER_MIN {
admin_error!("Requested GID {} is lower than system minimum {}", gid, GID_SYSTEM_NUMBER_MIN);
admin_error!(
"Requested GID {} is lower than system minimum {}",
gid,
GID_SYSTEM_NUMBER_MIN
);
return Err(OperationError::GidOverlapsSystemMin(GID_SYSTEM_NUMBER_MIN));
}
@ -46,7 +50,11 @@ fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), Opera
} else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) {
// If they provided us with a gid number, ensure it's in a safe range.
if gid <= GID_SAFETY_NUMBER_MIN {
admin_error!("Requested GID {} is lower or equal to a safe value {}", gid, GID_SAFETY_NUMBER_MIN);
admin_error!(
"Requested GID {} is lower or equal to a safe value {}",
gid,
GID_SAFETY_NUMBER_MIN
);
Err(OperationError::GidOverlapsSystemMin(GID_SAFETY_NUMBER_MIN))
} else {
Ok(())

View file

@ -212,7 +212,7 @@ impl Plugin for MemberOf {
Attribute::MemberOf.as_ref()
}
#[instrument(level = "debug", name = "memberof_post_create", skip(qs, cand, ce))]
#[instrument(level = "debug", name = "memberof_post_create", skip_all)]
fn post_create(
qs: &mut QueryServerWriteTransaction,
cand: &[Entry<EntrySealed, EntryCommitted>],
@ -263,7 +263,7 @@ impl Plugin for MemberOf {
Self::post_modify_inner(qs, pre_cand, cand, &me.ident)
}
#[instrument(level = "debug", name = "memberof_post_delete", skip(qs, cand, _de))]
#[instrument(level = "debug", name = "memberof_post_delete", skip_all)]
fn post_delete(
qs: &mut QueryServerWriteTransaction,
cand: &[Entry<EntrySealed, EntryCommitted>],

View file

@ -26,6 +26,8 @@ mod protected;
mod refint;
mod session;
mod spn;
mod valuedeny;
trait Plugin {
fn id() -> &'static str;
@ -226,16 +228,17 @@ impl Plugins {
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
ce: &CreateEvent,
) -> Result<(), OperationError> {
base::Base::pre_create_transform(qs, cand, ce)
.and_then(|_| cred_import::CredImport::pre_create_transform(qs, cand, ce))
.and_then(|_| jwskeygen::JwsKeygen::pre_create_transform(qs, cand, ce))
.and_then(|_| gidnumber::GidNumber::pre_create_transform(qs, cand, ce))
.and_then(|_| domain::Domain::pre_create_transform(qs, cand, ce))
.and_then(|_| spn::Spn::pre_create_transform(qs, cand, ce))
.and_then(|_| namehistory::NameHistory::pre_create_transform(qs, cand, ce))
.and_then(|_| eckeygen::EcdhKeyGen::pre_create_transform(qs, cand, ce))
// Should always be last
.and_then(|_| attrunique::AttrUnique::pre_create_transform(qs, cand, ce))
base::Base::pre_create_transform(qs, cand, ce)?;
valuedeny::ValueDeny::pre_create_transform(qs, cand, ce)?;
cred_import::CredImport::pre_create_transform(qs, cand, ce)?;
jwskeygen::JwsKeygen::pre_create_transform(qs, cand, ce)?;
gidnumber::GidNumber::pre_create_transform(qs, cand, ce)?;
domain::Domain::pre_create_transform(qs, cand, ce)?;
spn::Spn::pre_create_transform(qs, cand, ce)?;
namehistory::NameHistory::pre_create_transform(qs, cand, ce)?;
eckeygen::EcdhKeyGen::pre_create_transform(qs, cand, ce)?;
// Should always be last
attrunique::AttrUnique::pre_create_transform(qs, cand, ce)
}
#[instrument(level = "debug", name = "plugins::run_pre_create", skip_all)]
@ -253,8 +256,8 @@ impl Plugins {
cand: &[Entry<EntrySealed, EntryCommitted>],
ce: &CreateEvent,
) -> Result<(), OperationError> {
refint::ReferentialIntegrity::post_create(qs, cand, ce)
.and_then(|_| memberof::MemberOf::post_create(qs, cand, ce))
refint::ReferentialIntegrity::post_create(qs, cand, ce)?;
memberof::MemberOf::post_create(qs, cand, ce)
}
#[instrument(level = "debug", name = "plugins::run_pre_modify", skip_all)]
@ -264,18 +267,19 @@ impl Plugins {
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
me: &ModifyEvent,
) -> Result<(), OperationError> {
protected::Protected::pre_modify(qs, pre_cand, cand, me)
.and_then(|_| base::Base::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| cred_import::CredImport::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| jwskeygen::JwsKeygen::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| gidnumber::GidNumber::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| domain::Domain::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| spn::Spn::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| session::SessionConsistency::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| namehistory::NameHistory::pre_modify(qs, pre_cand, cand, me))
.and_then(|_| eckeygen::EcdhKeyGen::pre_modify(qs, pre_cand, cand, me))
// attr unique should always be last
.and_then(|_| attrunique::AttrUnique::pre_modify(qs, pre_cand, cand, me))
protected::Protected::pre_modify(qs, pre_cand, cand, me)?;
base::Base::pre_modify(qs, pre_cand, cand, me)?;
valuedeny::ValueDeny::pre_modify(qs, pre_cand, cand, me)?;
cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;
jwskeygen::JwsKeygen::pre_modify(qs, pre_cand, cand, me)?;
gidnumber::GidNumber::pre_modify(qs, pre_cand, cand, me)?;
domain::Domain::pre_modify(qs, pre_cand, cand, me)?;
spn::Spn::pre_modify(qs, pre_cand, cand, me)?;
session::SessionConsistency::pre_modify(qs, pre_cand, cand, me)?;
namehistory::NameHistory::pre_modify(qs, pre_cand, cand, me)?;
eckeygen::EcdhKeyGen::pre_modify(qs, pre_cand, cand, me)?;
// attr unique should always be last
attrunique::AttrUnique::pre_modify(qs, pre_cand, cand, me)
}
#[instrument(level = "debug", name = "plugins::run_post_modify", skip_all)]
@ -285,9 +289,9 @@ impl Plugins {
cand: &[Entry<EntrySealed, EntryCommitted>],
me: &ModifyEvent,
) -> Result<(), OperationError> {
refint::ReferentialIntegrity::post_modify(qs, pre_cand, cand, me)
.and_then(|_| spn::Spn::post_modify(qs, pre_cand, cand, me))
.and_then(|_| memberof::MemberOf::post_modify(qs, pre_cand, cand, me))
refint::ReferentialIntegrity::post_modify(qs, pre_cand, cand, me)?;
spn::Spn::post_modify(qs, pre_cand, cand, me)?;
memberof::MemberOf::post_modify(qs, pre_cand, cand, me)
}
#[instrument(level = "debug", name = "plugins::run_pre_batch_modify", skip_all)]
@ -297,18 +301,19 @@ impl Plugins {
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
me: &BatchModifyEvent,
) -> Result<(), OperationError> {
protected::Protected::pre_batch_modify(qs, pre_cand, cand, me)
.and_then(|_| base::Base::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| jwskeygen::JwsKeygen::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| gidnumber::GidNumber::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| domain::Domain::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| spn::Spn::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| session::SessionConsistency::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| namehistory::NameHistory::pre_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| eckeygen::EcdhKeyGen::pre_batch_modify(qs, pre_cand, cand, me))
// attr unique should always be last
.and_then(|_| attrunique::AttrUnique::pre_batch_modify(qs, pre_cand, cand, me))
protected::Protected::pre_batch_modify(qs, pre_cand, cand, me)?;
base::Base::pre_batch_modify(qs, pre_cand, cand, me)?;
valuedeny::ValueDeny::pre_batch_modify(qs, pre_cand, cand, me)?;
cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;
jwskeygen::JwsKeygen::pre_batch_modify(qs, pre_cand, cand, me)?;
gidnumber::GidNumber::pre_batch_modify(qs, pre_cand, cand, me)?;
domain::Domain::pre_batch_modify(qs, pre_cand, cand, me)?;
spn::Spn::pre_batch_modify(qs, pre_cand, cand, me)?;
session::SessionConsistency::pre_batch_modify(qs, pre_cand, cand, me)?;
namehistory::NameHistory::pre_batch_modify(qs, pre_cand, cand, me)?;
eckeygen::EcdhKeyGen::pre_batch_modify(qs, pre_cand, cand, me)?;
// attr unique should always be last
attrunique::AttrUnique::pre_batch_modify(qs, pre_cand, cand, me)
}
#[instrument(level = "debug", name = "plugins::run_post_batch_modify", skip_all)]
@ -318,9 +323,9 @@ impl Plugins {
cand: &[Entry<EntrySealed, EntryCommitted>],
me: &BatchModifyEvent,
) -> Result<(), OperationError> {
refint::ReferentialIntegrity::post_batch_modify(qs, pre_cand, cand, me)
.and_then(|_| spn::Spn::post_batch_modify(qs, pre_cand, cand, me))
.and_then(|_| memberof::MemberOf::post_batch_modify(qs, pre_cand, cand, me))
refint::ReferentialIntegrity::post_batch_modify(qs, pre_cand, cand, me)?;
spn::Spn::post_batch_modify(qs, pre_cand, cand, me)?;
memberof::MemberOf::post_batch_modify(qs, pre_cand, cand, me)
}
#[instrument(level = "debug", name = "plugins::run_pre_delete", skip_all)]
@ -338,8 +343,8 @@ impl Plugins {
cand: &[Entry<EntrySealed, EntryCommitted>],
de: &DeleteEvent,
) -> Result<(), OperationError> {
refint::ReferentialIntegrity::post_delete(qs, cand, de)
.and_then(|_| memberof::MemberOf::post_delete(qs, cand, de))
refint::ReferentialIntegrity::post_delete(qs, cand, de)?;
memberof::MemberOf::post_delete(qs, cand, de)
}
#[instrument(level = "debug", name = "plugins::run_pre_repl_refresh", skip_all)]
@ -355,8 +360,8 @@ impl Plugins {
qs: &mut QueryServerWriteTransaction,
cand: &[EntrySealedCommitted],
) -> Result<(), OperationError> {
refint::ReferentialIntegrity::post_repl_refresh(qs, cand)
.and_then(|_| memberof::MemberOf::post_repl_refresh(qs, cand))
refint::ReferentialIntegrity::post_repl_refresh(qs, cand)?;
memberof::MemberOf::post_repl_refresh(qs, cand)
}
#[instrument(level = "debug", name = "plugins::run_pre_repl_incremental", skip_all)]
@ -407,6 +412,7 @@ impl Plugins {
results: &mut Vec<Result<(), ConsistencyError>>,
) {
run_verify_plugin!(qs, results, base::Base);
run_verify_plugin!(qs, results, valuedeny::ValueDeny);
run_verify_plugin!(qs, results, attrunique::AttrUnique);
run_verify_plugin!(qs, results, refint::ReferentialIntegrity);
run_verify_plugin!(qs, results, dyngroup::DynGroup);

View file

@ -98,6 +98,7 @@ impl Plugin for NameHistory {
"plugin_name_history"
}
#[instrument(level = "debug", name = "name_history::pre_create_transform", skip_all)]
fn pre_create_transform(
qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<EntryInvalidNew>,
@ -106,6 +107,7 @@ impl Plugin for NameHistory {
Self::handle_name_creation(cand, qs.get_txn_cid())
}
#[instrument(level = "debug", name = "name_history::pre_modify", skip_all)]
fn pre_modify(
qs: &mut QueryServerWriteTransaction,
pre_cand: &[Arc<EntrySealedCommitted>],
@ -115,6 +117,7 @@ impl Plugin for NameHistory {
Self::handle_name_updates(pre_cand, cand, qs.get_txn_cid())
}
#[instrument(level = "debug", name = "name_history::pre_batch_modify", skip_all)]
fn pre_batch_modify(
qs: &mut QueryServerWriteTransaction,
pre_cand: &[Arc<EntrySealedCommitted>],

View file

@ -30,6 +30,7 @@ lazy_static! {
m.insert(Attribute::Es256PrivateKeyDer);
m.insert(Attribute::IdVerificationEcKey);
m.insert(Attribute::BadlistPassword);
m.insert(Attribute::DeniedName);
m.insert(Attribute::DomainDisplayName);
m.insert(Attribute::AuthSessionExpiry);
m.insert(Attribute::PrivilegeExpiry);
@ -42,7 +43,7 @@ impl Plugin for Protected {
"plugin_protected"
}
#[instrument(level = "debug", name = "protected_pre_create", skip(_qs, cand, ce))]
#[instrument(level = "debug", name = "protected_pre_create", skip_all)]
fn pre_create(
_qs: &mut QueryServerWriteTransaction,
// List of what we will commit that is valid?
@ -70,7 +71,7 @@ impl Plugin for Protected {
})
}
#[instrument(level = "debug", name = "protected_pre_modify", skip(_qs, cand, me))]
#[instrument(level = "debug", name = "protected_pre_modify", skip_all)]
fn pre_modify(
_qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],
@ -148,6 +149,7 @@ impl Plugin for Protected {
})
}
#[instrument(level = "debug", name = "protected_pre_batch_modify", skip_all)]
fn pre_batch_modify(
_qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],
@ -231,7 +233,7 @@ impl Plugin for Protected {
})
}
#[instrument(level = "debug", name = "protected_pre_delete", skip(_qs, cand, de))]
#[instrument(level = "debug", name = "protected_pre_delete", skip_all)]
fn pre_delete(
_qs: &mut QueryServerWriteTransaction,
// Should these be EntrySealed

View file

@ -74,6 +74,7 @@ impl Plugin for Spn {
Self::post_modify_inner(qs, pre_cand, cand)
}
#[instrument(level = "debug", name = "spn_post_repl_incremental", skip_all)]
fn post_repl_incremental(
qs: &mut QueryServerWriteTransaction,
pre_cand: &[Arc<EntrySealedCommitted>],
@ -226,7 +227,7 @@ mod tests {
#[test]
fn test_spn_generate_create() {
// on create don't provide the spn, we generate it.
let e: Entry<EntryInit,EntryNew> = entry_init!(
let e: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
(Attribute::Name, Value::new_iname("testperson")),
@ -250,7 +251,7 @@ mod tests {
#[test]
fn test_spn_generate_modify() {
// on a purge of the spn, generate it.
let e: Entry<EntryInit,EntryNew> = entry_init!(
let e: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
(Attribute::Name, Value::new_iname("testperson")),
@ -275,10 +276,13 @@ mod tests {
fn test_spn_validate_create() {
// on create providing invalid spn, we over-write it.
let e: Entry<EntryInit,EntryNew> = entry_init!(
let e: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
(Attribute::Spn, Value::new_utf8s("testperson@invalid_domain.com")),
(
Attribute::Spn,
Value::new_utf8s("testperson@invalid_domain.com")
),
(Attribute::Name, Value::new_iname("testperson")),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
@ -300,7 +304,7 @@ mod tests {
fn test_spn_validate_modify() {
// On modify (removed/present) of the spn, just regenerate it.
let e: Entry<EntryInit,EntryNew> = entry_init!(
let e: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
(Attribute::Name, Value::new_iname("testperson")),

View file

@ -0,0 +1,204 @@
use std::sync::Arc;
use crate::plugins::Plugin;
use crate::prelude::*;
pub struct ValueDeny {}
impl Plugin for ValueDeny {
fn id() -> &'static str {
"plugin_value_deny"
}
#[instrument(level = "debug", name = "base_pre_create_transform", skip_all)]
#[allow(clippy::cognitive_complexity)]
fn pre_create_transform(
qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
let denied_names = qs.denied_names();
let mut pass = true;
for entry in cand {
if let Some(name) = entry.get_ava_single_iname(Attribute::Name) {
if denied_names.contains(name) {
pass = false;
error!(?name, "name denied by system configuration");
}
}
}
if pass {
Ok(())
} else {
Err(OperationError::ValueDenyName)
}
}
#[instrument(level = "debug", name = "base_pre_modify", skip_all)]
fn pre_modify(
qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &ModifyEvent,
) -> Result<(), OperationError> {
Self::modify(qs, cand)
}
#[instrument(level = "debug", name = "base_pre_modify", skip_all)]
fn pre_batch_modify(
qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &BatchModifyEvent,
) -> Result<(), OperationError> {
Self::modify(qs, cand)
}
#[instrument(level = "debug", name = "base::verify", skip_all)]
fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
let denied_names = qs.denied_names().clone();
let mut results = Vec::new();
for denied_name in denied_names {
let filt = filter!(f_eq(Attribute::Name, PartialValue::new_iname(&denied_name)));
match qs.internal_search(filt) {
Ok(entries) => {
for entry in entries {
results.push(Err(ConsistencyError::DeniedName(entry.get_uuid())));
}
}
Err(err) => {
error!(?err);
results.push(Err(ConsistencyError::QueryServerSearchFailure))
}
}
}
results
}
}
impl ValueDeny {
fn modify(
qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
) -> Result<(), OperationError> {
let denied_names = qs.denied_names();
let mut pass = true;
for entry in cand {
if let Some(name) = entry.get_ava_single_iname(Attribute::Name) {
if denied_names.contains(name) {
pass = false;
error!(?name, "name denied by system configuration");
}
}
}
if pass {
Ok(())
} else {
Err(OperationError::ValueDenyName)
}
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
async fn setup_name_deny(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await;
let me_inv_m = ModifyEvent::new_internal_invalid(
filter!(f_eq(Attribute::Uuid, PVUUID_SYSTEM_CONFIG.clone())),
ModifyList::new_list(vec![Modify::Present(
Attribute::DeniedName.into(),
Value::new_iname("tobias"),
)]),
);
assert!(server_txn.modify(&me_inv_m).is_ok());
assert!(server_txn.commit().is_ok());
}
#[qs_test]
async fn test_valuedeny_create(server: &QueryServer) {
setup_name_deny(server).await;
let mut server_txn = server.write(duration_from_epoch_now()).await;
let t_uuid = Uuid::new_v4();
assert!(server_txn
.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("tobias")),
(Attribute::Uuid, Value::Uuid(t_uuid)),
(Attribute::Description, Value::new_utf8s("Tobias")),
(Attribute::DisplayName, Value::new_utf8s("Tobias"))
),])
.is_err());
}
#[qs_test]
async fn test_valuedeny_modify(server: &QueryServer) {
setup_name_deny(server).await;
let mut server_txn = server.write(duration_from_epoch_now()).await;
let t_uuid = Uuid::new_v4();
assert!(server_txn
.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("newname")),
(Attribute::Uuid, Value::Uuid(t_uuid)),
(Attribute::Description, Value::new_utf8s("Tobias")),
(Attribute::DisplayName, Value::new_utf8s("Tobias"))
),])
.is_ok());
// Now mod it
assert!(server_txn
.internal_modify_uuid(
t_uuid,
&ModifyList::new_purge_and_set(Attribute::Name, Value::new_iname("tobias"))
)
.is_err());
}
#[qs_test]
async fn test_valuedeny_batch_modify(server: &QueryServer) {
setup_name_deny(server).await;
let mut server_txn = server.write(duration_from_epoch_now()).await;
let t_uuid = Uuid::new_v4();
assert!(server_txn
.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("newname")),
(Attribute::Uuid, Value::Uuid(t_uuid)),
(Attribute::Description, Value::new_utf8s("Tobias")),
(Attribute::DisplayName, Value::new_utf8s("Tobias"))
),])
.is_ok());
// Now batch mod
assert!(server_txn
.internal_batch_modify(
[(
t_uuid,
ModifyList::new_purge_and_set(Attribute::Name, Value::new_iname("tobias"))
)]
.into_iter()
)
.is_err());
}
}

View file

@ -386,39 +386,36 @@ pub trait ReplicationUpdateVectorTransaction {
if *cv == &intersect {
trace!("{:?} is consistent!", ck);
} else {
admin_warn!("{:?} is NOT consistent! IDL's differ", ck);
error!("{:?} is NOT consistent! IDL's differ", ck);
debug_assert!(false);
results.push(Err(ConsistencyError::RuvInconsistent(ck.to_string())));
}
check_next = check_iter.next();
snap_next = snap_iter.next();
}
// Because we are zipping between these two sets, we only need to compare when
// the CID's are equal. Otherwise we need the other iter to "catch up"
Ordering::Less => {
// Due to deletes, it can be that the check ruv is missing whole entries
// in a rebuild.
admin_warn!("{:?} is NOT consistent! CID missing from RUV", ck);
// debug_assert!(false);
// results.push(Err(ConsistencyError::RuvInconsistent(ck.to_string())));
check_next = check_iter.next();
}
Ordering::Greater => {
admin_warn!("{:?} is NOT consistent! CID should not exist in RUV", sk);
// debug_assert!(false);
// results.push(Err(ConsistencyError::RuvInconsistent(sk.to_string())));
snap_next = snap_iter.next();
}
}
}
while let Some((ck, _cv)) = &check_next {
admin_warn!("{:?} is NOT consistent! CID missing from RUV", ck);
debug!("{:?} may not be consistent! CID missing from RUV", ck);
// debug_assert!(false);
// results.push(Err(ConsistencyError::RuvInconsistent(ck.to_string())));
check_next = check_iter.next();
}
while let Some((sk, _sv)) = &snap_next {
admin_warn!("{:?} is NOT consistent! CID should not exist in RUV", sk);
debug!(
"{:?} may not be consistent! CID should not exist in RUV",
sk
);
// debug_assert!(false);
// results.push(Err(ConsistencyError::RuvInconsistent(sk.to_string())));
snap_next = snap_iter.next();
@ -431,7 +428,7 @@ pub trait ReplicationUpdateVectorTransaction {
for cid in snapshot_ruv.keys() {
if let Some(server_range) = snapshot_range.get(&cid.s_uuid) {
if !server_range.contains(&cid.ts) {
admin_warn!(
warn!(
"{:?} is NOT consistent! server range is missing cid in index",
cid
);
@ -441,7 +438,7 @@ pub trait ReplicationUpdateVectorTransaction {
)));
}
} else {
admin_warn!(
warn!(
"{:?} is NOT consistent! server range is not present",
cid.s_uuid
);

View file

@ -2902,9 +2902,9 @@ mod tests {
assert_eq!(
e_service_person.validate(&schema),
Err(SchemaError::ExcludesNotSatisfied(
vec![EntryClass::Person.to_string()]
))
Err(SchemaError::ExcludesNotSatisfied(vec![
EntryClass::Person.to_string()
]))
);
// These are valid configurations.

View file

@ -126,6 +126,11 @@ impl<'a> QueryServerWriteTransaction<'a> {
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO));
}
if !self.changed_system_config {
self.changed_system_config = commit_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG));
}
if !self.changed_sync_agreement {
self.changed_sync_agreement = commit_cand
.iter()
@ -139,6 +144,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
acp_reload = ?self.changed_acp,
oauth2_reload = ?self.changed_oauth2,
domain_reload = ?self.changed_domain,
system_config_reload = ?self.changed_system_config,
changed_sync_agreement = ?self.changed_sync_agreement,
);

View file

@ -121,6 +121,11 @@ impl<'a> QueryServerWriteTransaction<'a> {
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO));
}
if !self.changed_system_config {
self.changed_system_config = del_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG));
}
if !self.changed_sync_agreement {
self.changed_sync_agreement = del_cand
.iter()
@ -135,6 +140,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
acp_reload = ?self.changed_acp,
oauth2_reload = ?self.changed_oauth2,
domain_reload = ?self.changed_domain,
system_config_reload = ?self.changed_system_config,
changed_sync_agreement = ?self.changed_sync_agreement
);

View file

@ -113,13 +113,18 @@ impl QueryServer {
}
}
// Reload if anything in migrations requires it.
write_txn.reload()?;
// Migrations complete. Init idm will now set the version as needed.
write_txn.initialise_idm()?;
write_txn.initialise_idm().and_then(|_| {
write_txn.set_phase(ServerPhase::Running);
write_txn.commit()
})?;
// Now force everything to reload.
write_txn.force_all_reload();
// We are read to run
write_txn.set_phase(ServerPhase::Running);
// Commit all changes, this also triggers the reload.
write_txn.commit()?;
// Here is where in the future we will need to apply domain version increments.
// The actually migrations are done in a transaction though, this just needs to
@ -544,6 +549,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
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(),
];
let r = idm_schema

View file

@ -67,11 +67,18 @@ pub struct DomainInfo {
pub(crate) d_vers: DomainVersion,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SystemConfig {
pub(crate) denied_names: HashSet<String>,
pub(crate) pw_badlist: HashSet<String>,
}
#[derive(Clone)]
pub struct QueryServer {
phase: Arc<CowCell<ServerPhase>>,
s_uuid: Uuid,
pub(crate) d_info: Arc<CowCell<DomainInfo>>,
system_config: Arc<CowCell<SystemConfig>>,
be: Backend,
schema: Arc<Schema>,
accesscontrols: Arc<AccessControls>,
@ -87,6 +94,7 @@ pub struct QueryServerReadTransaction<'a> {
// Anything else? In the future, we'll need to have a schema transaction
// type, maybe others?
pub(crate) d_info: CowCellReadTxn<DomainInfo>,
system_config: CowCellReadTxn<SystemConfig>,
schema: SchemaReadTransaction,
accesscontrols: AccessControlsReadTransaction<'a>,
_db_ticket: SemaphorePermit<'a>,
@ -102,6 +110,7 @@ pub struct QueryServerWriteTransaction<'a> {
committed: bool,
phase: CowCellWriteTxn<'a, ServerPhase>,
d_info: CowCellWriteTxn<'a, DomainInfo>,
system_config: CowCellWriteTxn<'a, SystemConfig>,
curtime: Duration,
cid: Cid,
trim_cid: Cid,
@ -111,13 +120,14 @@ pub struct QueryServerWriteTransaction<'a> {
// We store a set of flags that indicate we need a reload of
// schema or acp, which is tested by checking the classes of the
// changing content.
pub(crate) changed_schema: bool,
pub(crate) changed_acp: bool,
pub(crate) changed_oauth2: bool,
pub(crate) changed_domain: bool,
pub(crate) changed_sync_agreement: bool,
pub(super) changed_schema: bool,
pub(super) changed_acp: bool,
pub(super) changed_oauth2: bool,
pub(super) changed_domain: bool,
pub(super) changed_system_config: bool,
pub(super) changed_sync_agreement: bool,
// Store the list of changed uuids for other invalidation needs?
pub(crate) changed_uuid: HashSet<Uuid>,
pub(super) changed_uuid: HashSet<Uuid>,
_db_ticket: SemaphorePermit<'a>,
_write_ticket: SemaphorePermit<'a>,
resolve_filter_cache:
@ -150,6 +160,10 @@ pub trait QueryServerTransaction<'a> {
type AccessControlsTransactionType: AccessControlsTransaction<'a>;
fn get_accesscontrols(&self) -> &Self::AccessControlsTransactionType;
fn pw_badlist(&self) -> &HashSet<String>;
fn denied_names(&self) -> &HashSet<String>;
fn get_domain_uuid(&self) -> Uuid;
fn get_domain_name(&self) -> &str;
@ -817,15 +831,36 @@ pub trait QueryServerTransaction<'a> {
})
}
// This is a helper to get password badlist.
fn get_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
/// Get the password badlist from the system config. You should not call this directly
/// as this value is cached in the system_config() value.
fn get_sc_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
self.internal_search_uuid(UUID_SYSTEM_CONFIG)
.map(|e| match e.get_ava_iter_iutf8(Attribute::BadlistPassword) {
Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
None => HashSet::default(),
})
.map_err(|e| {
admin_error!(?e, "Failed to retrieve system configuration");
error!(
?e,
"Failed to retrieve password badlist from system configuration"
);
e
})
}
/// Get the denied name set from the system config. You should not call this directly
/// as this value is cached in the system_config() value.
fn get_sc_denied_names(&mut self) -> Result<HashSet<String>, OperationError> {
self.internal_search_uuid(UUID_SYSTEM_CONFIG)
.map(|e| match e.get_ava_iter_iname(Attribute::DeniedName) {
Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::<HashSet<_>>(),
None => HashSet::default(),
})
.map_err(|e| {
error!(
?e,
"Failed to retrieve denied names from system configuration"
);
e
})
}
@ -957,6 +992,14 @@ impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
(&mut self.be_txn, &mut self.resolve_filter_cache)
}
fn pw_badlist(&self) -> &HashSet<String> {
&self.system_config.pw_badlist
}
fn denied_names(&self) -> &HashSet<String> {
&self.system_config.denied_names
}
fn get_domain_uuid(&self) -> Uuid {
self.d_info.d_uuid
}
@ -1070,6 +1113,14 @@ impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
(&mut self.be_txn, &mut self.resolve_filter_cache)
}
fn pw_badlist(&self) -> &HashSet<String> {
&self.system_config.pw_badlist
}
fn denied_names(&self) -> &HashSet<String> {
&self.system_config.denied_names
}
fn get_domain_uuid(&self) -> Uuid {
self.d_info.d_uuid
}
@ -1113,6 +1164,9 @@ impl QueryServer {
d_display: domain_name,
}));
// These default to empty, but they'll be populated shortly.
let system_config = Arc::new(CowCell::new(SystemConfig::default()));
let dyngroup_cache = Arc::new(CowCell::new(DynGroupCache::default()));
let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
@ -1130,6 +1184,7 @@ impl QueryServer {
phase,
s_uuid,
d_info,
system_config,
be,
schema: Arc::new(schema),
accesscontrols: Arc::new(AccessControls::default()),
@ -1165,6 +1220,7 @@ impl QueryServer {
be_txn: self.be.read().unwrap(),
schema: self.schema.read(),
d_info: self.d_info.read(),
system_config: self.system_config.read(),
accesscontrols: self.accesscontrols.read(),
_db_ticket: db_ticket,
resolve_filter_cache: self.resolve_filter_cache.read(),
@ -1202,6 +1258,7 @@ impl QueryServer {
let schema_write = self.schema.write();
let mut be_txn = self.be.write().unwrap();
let d_info = self.d_info.write();
let system_config = self.system_config.write();
let phase = self.phase.write();
#[allow(clippy::expect_used)]
@ -1224,6 +1281,7 @@ impl QueryServer {
committed: false,
phase,
d_info,
system_config,
curtime,
cid,
trim_cid,
@ -1234,6 +1292,7 @@ impl QueryServer {
changed_acp: false,
changed_oauth2: false,
changed_domain: false,
changed_system_config: false,
changed_sync_agreement: false,
changed_uuid: HashSet::new(),
_db_ticket: db_ticket,
@ -1511,6 +1570,17 @@ impl<'a> QueryServerWriteTransaction<'a> {
})
}
#[instrument(level = "debug", skip_all)]
pub(crate) fn reload_system_config(&mut self) -> Result<(), OperationError> {
let denied_names = self.get_sc_denied_names()?;
let pw_badlist = self.get_sc_password_badlist()?;
let mut_system_config = self.system_config.get_mut();
mut_system_config.denied_names = denied_names;
mut_system_config.pw_badlist = pw_badlist;
Ok(())
}
/// Pulls the domain name from the database and updates the DomainInfo data in memory
#[instrument(level = "debug", skip_all)]
pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
@ -1581,6 +1651,15 @@ impl<'a> QueryServerWriteTransaction<'a> {
self.be_txn.reindex()
}
fn force_all_reload(&mut self) {
self.changed_schema = true;
self.changed_acp = true;
self.changed_oauth2 = true;
self.changed_domain = true;
self.changed_sync_agreement = true;
self.changed_system_config = true;
}
fn force_schema_reload(&mut self) {
self.changed_schema = true;
}
@ -1590,18 +1669,18 @@ impl<'a> QueryServerWriteTransaction<'a> {
self.be_txn.upgrade_reindex(v)
}
pub fn get_changed_uuids(&self) -> &HashSet<Uuid> {
&self.changed_uuid
}
pub fn get_changed_ouath2(&self) -> bool {
pub(crate) fn get_changed_ouath2(&self) -> bool {
self.changed_oauth2
}
pub fn get_changed_domain(&self) -> bool {
pub(crate) fn get_changed_domain(&self) -> bool {
self.changed_domain
}
pub(crate) fn get_changed_system_config(&self) -> bool {
self.changed_system_config
}
fn set_phase(&mut self, phase: ServerPhase) {
*self.phase = phase
}
@ -1630,6 +1709,10 @@ impl<'a> QueryServerWriteTransaction<'a> {
// .invalidate_related_cache(self.changed_uuid.into_inner().as_slice())
}
if self.changed_system_config {
self.reload_system_config()?;
}
if self.changed_domain {
self.reload_domain_info()?;
}
@ -1651,13 +1734,26 @@ impl<'a> QueryServerWriteTransaction<'a> {
let QueryServerWriteTransaction {
committed,
phase,
d_info,
system_config,
mut be_txn,
schema,
d_info,
accesscontrols,
cid,
dyngroup_cache,
..
// Ignore values that don't need a commit.
curtime: _,
trim_cid: _,
changed_schema: _,
changed_acp: _,
changed_oauth2: _,
changed_domain: _,
changed_system_config: _,
changed_sync_agreement: _,
changed_uuid: _,
_db_ticket: _,
_write_ticket: _,
resolve_filter_cache: _,
} = self;
debug_assert!(!committed);
@ -1672,6 +1768,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
schema
.commit()
.map(|_| d_info.commit())
.map(|_| system_config.commit())
.map(|_| phase.commit())
.map(|_| dyngroup_cache.commit())
.and_then(|_| accesscontrols.commit())

View file

@ -220,6 +220,12 @@ impl<'a> QueryServerWriteTransaction<'a> {
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO));
}
if !self.changed_system_config {
self.changed_system_config = norm_cand
.iter()
.chain(pre_candidates.iter().map(|e| e.as_ref()))
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG));
}
if !self.changed_sync_agreement {
self.changed_sync_agreement = norm_cand
.iter()
@ -239,6 +245,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
acp_reload = ?self.changed_acp,
oauth2_reload = ?self.changed_oauth2,
domain_reload = ?self.changed_domain,
system_config_reload = ?self.changed_system_config,
changed_sync_agreement = ?self.changed_sync_agreement
);
@ -375,6 +382,11 @@ impl<'a> QueryServerWriteTransaction<'a> {
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO));
}
if !self.changed_system_config {
self.changed_system_config = norm_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG));
}
self.changed_uuid.extend(
norm_cand
.iter()
@ -386,6 +398,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
acp_reload = ?self.changed_acp,
oauth2_reload = ?self.changed_oauth2,
domain_reload = ?self.changed_domain,
system_config_reload = ?self.changed_system_config,
);
trace!("Modify operation success");

View file

@ -1703,7 +1703,10 @@ impl Value {
}
}
Value::Spn(n, r) => format!("{n}@{r}"),
_ => unreachable!("You've specified the wrong type for the attribute, got: {:?}", self),
_ => unreachable!(
"You've specified the wrong type for the attribute, got: {:?}",
self
),
}
}

View file

@ -1544,12 +1544,17 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
.await
.is_err());
let pw = rsclient.idm_service_account_generate_password(test_service_account_username).await.expect("Failed to get a pw for the service account");
let pw = rsclient
.idm_service_account_generate_password(test_service_account_username)
.await
.expect("Failed to get a pw for the service account");
assert!(!pw.is_empty());
assert!(pw.is_ascii());
let res = rsclient.idm_service_account_get_credential_status(test_service_account_username).await;
let res = rsclient
.idm_service_account_get_credential_status(test_service_account_username)
.await;
dbg!(&res);
assert!(res.is_ok());
@ -1562,9 +1567,6 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
.await
.is_ok());
// let's create one and just yolo it into a person
// TODO: Turns out this doesn't work because admin doesn't have the right perms to remove `jws_es256_private_key` from the account?
// rsclient

View file

@ -27,7 +27,6 @@ use uuid::Uuid;
include!("../opt/kanidm.rs");
pub mod badlist;
pub mod common;
pub mod domain;
pub mod group;
@ -39,8 +38,8 @@ pub mod raw;
pub mod recycle;
pub mod serviceaccount;
pub mod session;
pub mod session_expiry;
pub mod synch;
pub mod system_config;
mod webauthn;
/// Throws an error and exits the program when we get an error
@ -145,6 +144,7 @@ impl SystemOpt {
pub fn debug(&self) -> bool {
match self {
SystemOpt::PwBadlist { commands } => commands.debug(),
SystemOpt::DeniedNames { commands } => commands.debug(),
SystemOpt::Oauth2 { commands } => commands.debug(),
SystemOpt::Domain { commands } => commands.debug(),
SystemOpt::Synch { commands } => commands.debug(),
@ -156,6 +156,7 @@ impl SystemOpt {
pub async fn exec(&self) {
match self {
SystemOpt::PwBadlist { commands } => commands.exec().await,
SystemOpt::DeniedNames { commands } => commands.exec().await,
SystemOpt::Oauth2 { commands } => commands.exec().await,
SystemOpt::Domain { commands } => commands.exec().await,
SystemOpt::Synch { commands } => commands.exec().await,

View file

@ -0,0 +1,47 @@
use crate::common::OpType;
use crate::{handle_client_error, DeniedNamesOpt};
impl DeniedNamesOpt {
pub fn debug(&self) -> bool {
match self {
DeniedNamesOpt::Show { copt }
| DeniedNamesOpt::Append { copt, .. }
| DeniedNamesOpt::Remove { copt, .. } => copt.debug,
}
}
pub async fn exec(&self) {
match self {
DeniedNamesOpt::Show { copt } => {
let client = copt.to_client(OpType::Read).await;
match client.system_denied_names_get().await {
Ok(list) => {
for i in list {
println!("{}", i);
}
eprintln!("--");
eprintln!("Success");
}
Err(e) => crate::handle_client_error(e, &copt.output_mode),
}
}
DeniedNamesOpt::Append { copt, names } => {
let client = copt.to_client(OpType::Write).await;
match client.system_denied_names_append(names).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
}
}
DeniedNamesOpt::Remove { copt, names } => {
let client = copt.to_client(OpType::Write).await;
match client.system_denied_names_remove(names).await {
Ok(_) => println!("Success"),
Err(e) => handle_client_error(e, &copt.output_mode),
}
}
}
}
}

View file

@ -0,0 +1,3 @@
pub mod badlist;
pub mod denied_names;
pub mod session_expiry;

View file

@ -794,6 +794,32 @@ pub enum PwBadlistOpt {
},
}
#[derive(Debug, Subcommand)]
pub enum DeniedNamesOpt {
#[clap[name = "show"]]
/// Show information about this system's denied name list
Show {
#[clap(flatten)]
copt: CommonOpt,
},
#[clap[name = "append"]]
Append {
#[clap(flatten)]
copt: CommonOpt,
#[clap(value_parser, required = true, num_args(1..))]
names: Vec<String>,
},
#[clap[name = "remove"]]
/// Remove a name from the denied name list.
Remove {
#[clap(flatten)]
copt: CommonOpt,
#[clap(value_parser, required = true, num_args(1..))]
names: Vec<String>,
},
}
#[derive(Debug, Subcommand)]
pub enum DomainOpt {
#[clap[name = "set-display-name"]]
@ -952,6 +978,12 @@ pub enum SystemOpt {
#[clap(subcommand)]
commands: PwBadlistOpt,
},
#[clap(name = "denied-names")]
/// Configure and manage denied names
DeniedNames {
#[clap(subcommand)]
commands: DeniedNamesOpt,
},
/// Configure and display the system auth session expiry
#[clap(name = "auth-expiry")]
AuthSessionExpiry {

View file

@ -23,7 +23,9 @@ use base64urlsafedata::Base64UrlSafeData;
use chrono::Utc;
use clap::Parser;
use cron::Schedule;
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_CLASS_GROUPOFNAMES};
use kanidm_proto::constants::{
ATTR_UID, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_CLASS_GROUPOFNAMES,
};
use kanidmd_lib::prelude::{Attribute, EntryClass};
use std::collections::BTreeMap;
use std::fs::metadata;

View file

@ -62,21 +62,33 @@ async fn driver_main(opt: Opt) {
let mut f = match File::open(&opt.ldap_sync_config) {
Ok(f) => f,
Err(e) => {
error!("Unable to open ldap sync config from '{}' [{:?}] 🥺", &opt.ldap_sync_config.display(), e);
error!(
"Unable to open ldap sync config from '{}' [{:?}] 🥺",
&opt.ldap_sync_config.display(),
e
);
return;
}
};
let mut contents = String::new();
if let Err(e) = f.read_to_string(&mut contents) {
error!("unable to read file '{}': {:?}", &opt.ldap_sync_config.display(), e);
error!(
"unable to read file '{}': {:?}",
&opt.ldap_sync_config.display(),
e
);
return;
};
let sync_config: Config = match toml::from_str(contents.as_str()) {
Ok(c) => c,
Err(e) => {
eprintln!("Unable to parse config from '{}' error: {:?}", &opt.ldap_sync_config.display(), e);
eprintln!(
"Unable to parse config from '{}' error: {:?}",
&opt.ldap_sync_config.display(),
e
);
return;
}
};
@ -800,7 +812,7 @@ async fn test_driver_main() {
assert_eq!(driver_main(testopt.clone()).await, ());
println!("done testing missing config");
let testopt = Opt{
let testopt = Opt {
client_config: PathBuf::from(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))),
ldap_sync_config: PathBuf::from(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))),
..testopt
@ -809,13 +821,19 @@ async fn test_driver_main() {
println!("valid file path, invalid contents");
assert_eq!(driver_main(testopt.clone()).await, ());
println!("done with valid file path, invalid contents");
let testopt = Opt{
client_config: PathBuf::from(format!("{}/../../../examples/iam_migration_ldap.toml", env!("CARGO_MANIFEST_DIR"))),
ldap_sync_config: PathBuf::from(format!("{}/../../../examples/iam_migration_ldap.toml", env!("CARGO_MANIFEST_DIR"))),
let testopt = Opt {
client_config: PathBuf::from(format!(
"{}/../../../examples/iam_migration_ldap.toml",
env!("CARGO_MANIFEST_DIR")
)),
ldap_sync_config: PathBuf::from(format!(
"{}/../../../examples/iam_migration_ldap.toml",
env!("CARGO_MANIFEST_DIR")
)),
..testopt
};
println!("valid file path, invalid contents");
assert_eq!(driver_main(testopt).await, ());
println!("done with valid file path, valid contents");
}
}

View file

@ -1,5 +1,8 @@
use hashbrown::{HashMap, HashSet};
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_DISPLAY_NAME, LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS, LDAP_ATTR_OU, LDAP_ATTR_GROUPS};
use kanidm_proto::constants::{
ATTR_UID, LDAP_ATTR_CN, LDAP_ATTR_DISPLAY_NAME, LDAP_ATTR_GROUPS, LDAP_ATTR_OBJECTCLASS,
LDAP_ATTR_OU,
};
use std::time::{Duration, Instant};
use ldap3_proto::proto::*;
@ -87,7 +90,10 @@ impl DirectoryServer {
// Check if ou=people and ou=group exist
let res = self
.ldap
.search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(), "people".to_string()))
.search(LdapFilter::Equality(
LDAP_ATTR_OU.to_string(),
"people".to_string(),
))
.await?;
if res.is_empty() {
@ -114,7 +120,10 @@ impl DirectoryServer {
let res = self
.ldap
.search(LdapFilter::Equality(LDAP_ATTR_OU.to_string(),LDAP_ATTR_GROUPS.to_string()))
.search(LdapFilter::Equality(
LDAP_ATTR_OU.to_string(),
LDAP_ATTR_GROUPS.to_string(),
))
.await?;
if res.is_empty() {
@ -145,7 +154,10 @@ impl DirectoryServer {
// does it already exist?
let res = self
.ldap
.search(LdapFilter::Equality(LDAP_ATTR_CN.to_string(), u.to_string()))
.search(LdapFilter::Equality(
LDAP_ATTR_CN.to_string(),
u.to_string(),
))
.await?;
if !res.is_empty() {

View file

@ -1,5 +1,7 @@
use hashbrown::{HashMap, HashSet};
use kanidm_proto::constants::{ATTR_UID, LDAP_ATTR_DISPLAY_NAME, LDAP_CLASS_GROUPOFNAMES, LDAP_ATTR_OBJECTCLASS, LDAP_ATTR_CN};
use kanidm_proto::constants::{
ATTR_UID, LDAP_ATTR_CN, LDAP_ATTR_DISPLAY_NAME, LDAP_ATTR_OBJECTCLASS, LDAP_CLASS_GROUPOFNAMES,
};
use ldap3_proto::proto::*;
use std::time::{Duration, Instant};
use uuid::Uuid;