mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-23 01:13:54 +02:00
20231012 346 name deny list (#2214)
* Migrate to improved system config reload, cleanup acc pol * Denied names feature
This commit is contained in:
parent
88da55260a
commit
8bcf1935a5
Makefile
book/src
libs
proto/src
server
lib/src
be
constants
filter.rsidm
plugins
access.rsattrunique.rsbase.rscred_import.rsdyngroup.rsgidnumber.rsmemberof.rsmod.rsnamehistory.rsprotected.rsspn.rsvaluedeny.rs
repl
schema.rsserver
value.rstestkit/tests
tools
cli/src
iam_migrations
orca/src
4
Makefile
4
Makefile
|
@ -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) "{}" +
|
||||
|
||||
|
|
|
@ -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".
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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>],
|
||||
|
|
|
@ -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>],
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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>],
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")),
|
||||
|
|
204
server/lib/src/plugins/valuedeny.rs
Normal file
204
server/lib/src/plugins/valuedeny.rs
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
47
tools/cli/src/cli/system_config/denied_names.rs
Normal file
47
tools/cli/src/cli/system_config/denied_names.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
tools/cli/src/cli/system_config/mod.rs
Normal file
3
tools/cli/src/cli/system_config/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod badlist;
|
||||
pub mod denied_names;
|
||||
pub mod session_expiry;
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue