mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
20231019 1122 account policy basics (#2245)
--------- Co-authored-by: James Hodgkinson <james@terminaloutcomes.com>
This commit is contained in:
parent
684d72d09c
commit
afe9d28754
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2937,7 +2937,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kanidm_lib_file_permissions"
|
name = "kanidm_lib_file_permissions"
|
||||||
version = "0.1.0"
|
version = "1.1.0-rc.14-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kanidm_utils_users",
|
"kanidm_utils_users",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
- [Administration](administrivia.md)
|
- [Administration](administrivia.md)
|
||||||
- [Accounts and Groups](accounts_and_groups.md)
|
- [Accounts and Groups](accounts_and_groups.md)
|
||||||
|
- [Account Policy](account_policy.md)
|
||||||
- [Authentication and Credentials](authentication.md)
|
- [Authentication and Credentials](authentication.md)
|
||||||
- [POSIX Accounts and Groups](posix_accounts.md)
|
- [POSIX Accounts and Groups](posix_accounts.md)
|
||||||
- [Backup and Restore](backup_restore.md)
|
- [Backup and Restore](backup_restore.md)
|
||||||
|
|
86
book/src/account_policy.md
Normal file
86
book/src/account_policy.md
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# Account Policy
|
||||||
|
|
||||||
|
Account Policy defines the security requirements that accounts must meet and influences users
|
||||||
|
sessions.
|
||||||
|
|
||||||
|
Policy is defined on groups so that membership of a group influences the security of its members.
|
||||||
|
This allows you to express that if you can access a system or resource, then the account must also
|
||||||
|
meet the policy requirements.
|
||||||
|
|
||||||
|
## Default Account Policy
|
||||||
|
|
||||||
|
A default Account Policy is applied to `idm_all_accounts`. This provides the defaults that
|
||||||
|
influence all accounts in Kanidm. This policy can be modified the same as any other group's policy.
|
||||||
|
|
||||||
|
## Policy Resolution
|
||||||
|
|
||||||
|
When an account is affected by multiple policies, the strictest component from each policy is
|
||||||
|
applied. This can mean that two policies interact and make their combination stricter than their
|
||||||
|
parts.
|
||||||
|
|
||||||
|
| value | ordering |
|
||||||
|
| ---------------- | -------------- |
|
||||||
|
| auth-session | smallest value |
|
||||||
|
| privilege-expiry | smallest value |
|
||||||
|
|
||||||
|
### Example Resolution
|
||||||
|
|
||||||
|
If we had two policies where the first defined:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth-session: 86400
|
||||||
|
privilege-expiry: 600
|
||||||
|
```
|
||||||
|
|
||||||
|
And the second
|
||||||
|
|
||||||
|
```
|
||||||
|
auth-session: 3600
|
||||||
|
privilege-expiry: 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
As the value of auth-session from the second is smaller we would take that. We would take the
|
||||||
|
smallest value of privilege-expiry from the first. This leaves:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth-session: 3600
|
||||||
|
privilege-expiry: 600
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enabling Account Policy
|
||||||
|
|
||||||
|
Account Policy is enabled on a group with the command:
|
||||||
|
|
||||||
|
```
|
||||||
|
kanidm group account-policy enable <group name>
|
||||||
|
kanidm group account-policy enable my_admin_group
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting Maximum Session Time
|
||||||
|
|
||||||
|
The auth-session value influences the maximum time in seconds that an authenticated session can
|
||||||
|
exist. After this time, the user must reauthenticate.
|
||||||
|
|
||||||
|
This value provides a difficult balance - forcing frequent re-authentications can frustrate and
|
||||||
|
annoy users. However extremely long sessions allow a stolen or disclosed session token/device to
|
||||||
|
read data for an extended period. Due to Kanidm's read/write separation this mitigates the risk of
|
||||||
|
disclosed sessions as they can only _read_ data, not write it.
|
||||||
|
|
||||||
|
To set the maximum authentication session time
|
||||||
|
|
||||||
|
```
|
||||||
|
kanidm group account-policy auth-expiry <group name> <seconds>
|
||||||
|
kanidm group account-policy auth-expiry my_admin_group 86400
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting Maximum Privilege Time
|
||||||
|
|
||||||
|
The privilege-expiry time defines how long a session retains its write privileges after a
|
||||||
|
reauthentication. After this time, the session returns to read-only mode.
|
||||||
|
|
||||||
|
To set the maximum privilege time
|
||||||
|
|
||||||
|
```
|
||||||
|
kanidm group account-policy privilege-expiry <group name> <seconds>
|
||||||
|
kanidm group account-policy privilege-expiry my_admin_group 900
|
||||||
|
```
|
|
@ -72,8 +72,8 @@ You must modify the retro changelog plugin to include the full scope of the data
|
||||||
the sync tool can view the changes to the database. Currently dsconf can not modify the
|
the sync tool can view the changes to the database. Currently dsconf can not modify the
|
||||||
include-suffix so you must do this manually.
|
include-suffix so you must do this manually.
|
||||||
|
|
||||||
You need to change the `nsslapd-include-suffix` to match your LDAP baseDN here. You can access
|
You need to change the `nsslapd-include-suffix` to match your LDAP baseDN here. You can access the
|
||||||
the basedn with:
|
basedn with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ldapsearch -H ldaps://<SERVER HOSTNAME/IP> -x -b '' -s base namingContexts
|
ldapsearch -H ldaps://<SERVER HOSTNAME/IP> -x -b '' -s base namingContexts
|
||||||
|
|
|
@ -11,6 +11,10 @@ license = { workspace = true }
|
||||||
homepage = { workspace = true }
|
homepage = { workspace = true }
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
reqwest = { workspace = true, default-features = false, features = [
|
reqwest = { workspace = true, default-features = false, features = [
|
||||||
|
|
35
libs/client/src/group.rs
Normal file
35
libs/client/src/group.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::{ClientError, KanidmClient};
|
||||||
|
|
||||||
|
impl KanidmClient {
|
||||||
|
pub async fn group_account_policy_enable(&self, id: &str) -> Result<(), ClientError> {
|
||||||
|
self.perform_post_request(
|
||||||
|
&format!("/v1/group/{}/_attr/class", id),
|
||||||
|
vec!["account_policy".to_string()],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn group_account_policy_authsession_expiry_set(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
expiry: u32,
|
||||||
|
) -> Result<(), ClientError> {
|
||||||
|
self.perform_put_request(
|
||||||
|
&format!("/v1/group/{}/_attr/authsession_expiry", id),
|
||||||
|
vec![expiry.to_string()],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn group_account_policy_privilege_expiry_set(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
expiry: u32,
|
||||||
|
) -> Result<(), ClientError> {
|
||||||
|
self.perform_put_request(
|
||||||
|
&format!("/v1/group/{}/_attr/privilege_expiry", id),
|
||||||
|
vec![expiry.to_string()],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ use webauthn_rs_proto::{
|
||||||
PublicKeyCredential, RegisterPublicKeyCredential, RequestChallengeResponse,
|
PublicKeyCredential, RegisterPublicKeyCredential, RequestChallengeResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod group;
|
||||||
mod oauth;
|
mod oauth;
|
||||||
mod person;
|
mod person;
|
||||||
mod scim;
|
mod scim;
|
||||||
|
|
|
@ -40,40 +40,4 @@ impl KanidmClient {
|
||||||
self.perform_delete_request_with_body("/v1/system/_attr/denied_name", list)
|
self.perform_delete_request_with_body("/v1/system/_attr/denied_name", list)
|
||||||
.await
|
.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")
|
|
||||||
.await?;
|
|
||||||
list.ok_or(ClientError::EmptyResponse).and_then(|s| {
|
|
||||||
s[0].parse::<u32>()
|
|
||||||
.map_err(|err| ClientError::InvalidResponseFormat(err.to_string()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn system_authsession_expiry_set(&self, expiry: u32) -> Result<(), ClientError> {
|
|
||||||
self.perform_put_request(
|
|
||||||
"/v1/system/_attr/authsession_expiry",
|
|
||||||
vec![expiry.to_string()],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn system_auth_privilege_expiry_get(&self) -> Result<u32, ClientError> {
|
|
||||||
let list: Option<[String; 1]> = self
|
|
||||||
.perform_get_request("/v1/system/_attr/privilege_expiry")
|
|
||||||
.await?;
|
|
||||||
list.ok_or(ClientError::EmptyResponse).and_then(|s| {
|
|
||||||
s[0].parse::<u32>()
|
|
||||||
.map_err(|err| ClientError::InvalidResponseFormat(err.to_string()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn system_auth_privilege_expiry_set(&self, expiry: u32) -> Result<(), ClientError> {
|
|
||||||
self.perform_put_request(
|
|
||||||
"/v1/system/_attr/privilege_expiry",
|
|
||||||
vec![expiry.to_string()],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@ edition = "2021"
|
||||||
[features]
|
[features]
|
||||||
tpm = ["dep:tss-esapi"]
|
tpm = ["dep:tss-esapi"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argon2 = { workspace = true }
|
argon2 = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
|
|
|
@ -84,11 +84,11 @@ impl From<OpenSSLErrorStack> for CryptoError {
|
||||||
fn from(ossl_err: OpenSSLErrorStack) -> Self {
|
fn from(ossl_err: OpenSSLErrorStack) -> Self {
|
||||||
error!(?ossl_err);
|
error!(?ossl_err);
|
||||||
let code = ossl_err.errors().get(0).map(|e| e.code()).unwrap_or(0);
|
let code = ossl_err.errors().get(0).map(|e| e.code()).unwrap_or(0);
|
||||||
#[cfg(not(target_family="windows"))]
|
#[cfg(not(target_family = "windows"))]
|
||||||
let result = CryptoError::OpenSSL(code);
|
let result = CryptoError::OpenSSL(code);
|
||||||
|
|
||||||
// this is an .into() because on windows it's a u32 not a u64
|
// this is an .into() because on windows it's a u32 not a u64
|
||||||
#[cfg(target_family="windows")]
|
#[cfg(target_family = "windows")]
|
||||||
let result = CryptoError::OpenSSL(code.into());
|
let result = CryptoError::OpenSSL(code.into());
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "kanidm_lib_file_permissions"
|
name = "kanidm_lib_file_permissions"
|
||||||
version = "0.1.0"
|
description = "Kanidm File Permissions Library"
|
||||||
edition = "2021"
|
# documentation = "https://docs.rs/kanidm_proto/latest/kanidm_proto/"
|
||||||
|
version = { workspace = true }
|
||||||
|
authors = { workspace = true }
|
||||||
|
rust-version = { workspace = true }
|
||||||
|
edition = { workspace = true }
|
||||||
|
license = { workspace = true }
|
||||||
|
homepage = { workspace = true }
|
||||||
|
repository = { workspace = true }
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[lib]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::{path::Path, fs::Metadata};
|
use std::{fs::Metadata, path::Path};
|
||||||
/// Check a given file's metadata is read-only for the current user (true = read-only) Stub function if you're building for windows!
|
/// Check a given file's metadata is read-only for the current user (true = read-only) Stub function if you're building for windows!
|
||||||
pub fn readonly(meta: &Metadata) -> bool {
|
pub fn readonly(meta: &Metadata) -> bool {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|
|
@ -16,6 +16,8 @@ repository = { workspace = true }
|
||||||
[lib]
|
[lib]
|
||||||
name = "profiles"
|
name = "profiles"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
test = false
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
|
@ -11,6 +11,10 @@ license = { workspace = true }
|
||||||
homepage = { workspace = true }
|
homepage = { workspace = true }
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = false
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num_enum = { workspace = true }
|
num_enum = { workspace = true }
|
||||||
tracing = { workspace = true, features = ["attributes"] }
|
tracing = { workspace = true, features = ["attributes"] }
|
||||||
|
|
|
@ -8,5 +8,9 @@ license.workspace = true
|
||||||
homepage.workspace = true
|
homepage.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub mod unix;
|
pub mod unix;
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub use unix::*;
|
pub use unix::*;
|
||||||
|
|
|
@ -58,7 +58,6 @@ pub fn get_user_name_by_uid(uid: uid_t) -> Option<OsString> {
|
||||||
Some(name)
|
Some(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// just testing these literally don't panic
|
/// just testing these literally don't panic
|
||||||
fn test_get_effective_uid() {
|
fn test_get_effective_uid() {
|
||||||
|
@ -69,4 +68,4 @@ fn test_get_effective_uid() {
|
||||||
|
|
||||||
let username = get_user_name_by_uid(get_current_uid());
|
let username = get_user_name_by_uid(get_current_uid());
|
||||||
assert!(username.is_some());
|
assert!(username.is_some());
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@ license = { workspace = true }
|
||||||
homepage = { workspace = true }
|
homepage = { workspace = true }
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = true
|
||||||
|
doctest = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
wasm = ["webauthn-rs-proto/wasm"]
|
wasm = ["webauthn-rs-proto/wasm"]
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,10 @@ license = { workspace = true }
|
||||||
homepage = { workspace = true }
|
homepage = { workspace = true }
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
axum = { workspace = true }
|
axum = { workspace = true }
|
||||||
|
|
|
@ -16,6 +16,8 @@ repository = { workspace = true }
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kanidmd"
|
name = "kanidmd"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
kanidm_proto = { workspace = true }
|
kanidm_proto = { workspace = true }
|
||||||
|
|
|
@ -5,6 +5,8 @@ edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = { workspace = true }
|
proc-macro2 = { workspace = true }
|
||||||
|
|
|
@ -14,6 +14,8 @@ repository = { workspace = true }
|
||||||
[lib]
|
[lib]
|
||||||
name = "kanidmd_lib"
|
name = "kanidmd_lib"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "scaling_10k"
|
name = "scaling_10k"
|
||||||
|
|
|
@ -1212,7 +1212,6 @@ lazy_static! {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
pub static ref IDM_ACP_GROUP_MANAGE_PRIV_V1: BuiltinAcp = BuiltinAcp{
|
pub static ref IDM_ACP_GROUP_MANAGE_PRIV_V1: BuiltinAcp = BuiltinAcp{
|
||||||
classes: vec![
|
classes: vec![
|
||||||
EntryClass::Object,
|
EntryClass::Object,
|
||||||
|
@ -1246,6 +1245,56 @@ lazy_static! {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_PRIV_V1: BuiltinAcp = BuiltinAcp{
|
||||||
|
classes: vec![
|
||||||
|
EntryClass::Object,
|
||||||
|
EntryClass::AccessControlProfile,
|
||||||
|
EntryClass::AccessControlCreate,
|
||||||
|
EntryClass::AccessControlModify,
|
||||||
|
EntryClass::AccessControlSearch
|
||||||
|
],
|
||||||
|
name: "idm_acp_group_account_policy_manage",
|
||||||
|
uuid: UUID_IDM_GROUP_ACCOUNT_POLICY_MANAGE_PRIV,
|
||||||
|
description: "Builtin IDM Control for management of account policy on groups",
|
||||||
|
// For now just target SA because we are going to rework this soon and I think
|
||||||
|
// there isn't a great reason to make more small priv groups that we plan to
|
||||||
|
// erase.
|
||||||
|
receiver_group: UUID_SYSTEM_ADMINS,
|
||||||
|
// group which is not in HP, Recycled, Tombstone
|
||||||
|
target_scope: ProtoFilter::And(vec![
|
||||||
|
match_class_filter!(EntryClass::Group),
|
||||||
|
ProtoFilter::AndNot(Box::new(FILTER_HP_OR_RECYCLED_OR_TOMBSTONE.clone())),
|
||||||
|
|
||||||
|
]),
|
||||||
|
search_attrs: vec![
|
||||||
|
Attribute::Class,
|
||||||
|
Attribute::Name,
|
||||||
|
Attribute::Uuid,
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
Attribute::PrivilegeExpiry,
|
||||||
|
],
|
||||||
|
modify_removed_attrs: vec![
|
||||||
|
Attribute::Class,
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
Attribute::PrivilegeExpiry,
|
||||||
|
],
|
||||||
|
modify_present_attrs: vec![
|
||||||
|
Attribute::Class,
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
Attribute::PrivilegeExpiry,
|
||||||
|
],
|
||||||
|
modify_classes: vec![
|
||||||
|
EntryClass::AccountPolicy,
|
||||||
|
],
|
||||||
|
create_attrs: vec![
|
||||||
|
Attribute::Class,
|
||||||
|
],
|
||||||
|
create_classes: vec![
|
||||||
|
EntryClass::AccountPolicy,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
|
@ -551,6 +551,7 @@ pub enum EntryClass {
|
||||||
AccessControlProfile,
|
AccessControlProfile,
|
||||||
AccessControlSearch,
|
AccessControlSearch,
|
||||||
Account,
|
Account,
|
||||||
|
AccountPolicy,
|
||||||
AttributeType,
|
AttributeType,
|
||||||
Class,
|
Class,
|
||||||
ClassType,
|
ClassType,
|
||||||
|
@ -591,6 +592,7 @@ impl From<EntryClass> for &'static str {
|
||||||
EntryClass::AccessControlProfile => "access_control_profile",
|
EntryClass::AccessControlProfile => "access_control_profile",
|
||||||
EntryClass::AccessControlSearch => "access_control_search",
|
EntryClass::AccessControlSearch => "access_control_search",
|
||||||
EntryClass::Account => "account",
|
EntryClass::Account => "account",
|
||||||
|
EntryClass::AccountPolicy => "account_policy",
|
||||||
EntryClass::AttributeType => "attributetype",
|
EntryClass::AttributeType => "attributetype",
|
||||||
EntryClass::Class => ATTR_CLASS,
|
EntryClass::Class => ATTR_CLASS,
|
||||||
EntryClass::ClassType => "classtype",
|
EntryClass::ClassType => "classtype",
|
||||||
|
@ -704,7 +706,7 @@ lazy_static! {
|
||||||
Attribute::Description,
|
Attribute::Description,
|
||||||
Value::new_utf8s("System (local) info and metadata object.")
|
Value::new_utf8s("System (local) info and metadata object.")
|
||||||
),
|
),
|
||||||
(Attribute::Version, Value::Uint32(14))
|
(Attribute::Version, Value::Uint32(16))
|
||||||
);
|
);
|
||||||
|
|
||||||
pub static ref E_DOMAIN_INFO_V1: EntryInitNew = entry_init!(
|
pub static ref E_DOMAIN_INFO_V1: EntryInitNew = entry_init!(
|
||||||
|
|
|
@ -410,7 +410,6 @@ lazy_static! {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// Builtin IDM Group for allowing migrations of service accounts into persons
|
|
||||||
pub static ref IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV: BuiltinGroup = BuiltinGroup {
|
pub static ref IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV: BuiltinGroup = BuiltinGroup {
|
||||||
name: "idm_hp_sync_account_manage_priv",
|
name: "idm_hp_sync_account_manage_priv",
|
||||||
description: "Builtin IDM Group for managing synchronisation from external identity sources",
|
description: "Builtin IDM Group for managing synchronisation from external identity sources",
|
||||||
|
@ -421,7 +420,6 @@ lazy_static! {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Builtin IDM Group for extending high privilege accounts to be people.
|
|
||||||
pub static ref IDM_ALL_PERSONS: BuiltinGroup = BuiltinGroup {
|
pub static ref IDM_ALL_PERSONS: BuiltinGroup = BuiltinGroup {
|
||||||
name: "idm_all_persons",
|
name: "idm_all_persons",
|
||||||
description: "Builtin IDM Group for extending high privilege accounts to be people.",
|
description: "Builtin IDM Group for extending high privilege accounts to be people.",
|
||||||
|
@ -434,10 +432,15 @@ lazy_static! {
|
||||||
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
|
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
|
extra_attributes: vec![
|
||||||
|
// Enable account policy by default
|
||||||
|
(Attribute::Class, EntryClass::AccountPolicy.to_value()),
|
||||||
|
// Enforce this is a system protected object
|
||||||
|
(Attribute::Class, EntryClass::System.to_value()),
|
||||||
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Builtin IDM Group for extending high privilege accounts to be people.
|
|
||||||
pub static ref IDM_ALL_ACCOUNTS: BuiltinGroup = BuiltinGroup {
|
pub static ref IDM_ALL_ACCOUNTS: BuiltinGroup = BuiltinGroup {
|
||||||
name: "idm_all_accounts",
|
name: "idm_all_accounts",
|
||||||
description: "Builtin IDM dynamic group containing all entries that can authenticate.",
|
description: "Builtin IDM dynamic group containing all entries that can authenticate.",
|
||||||
|
@ -447,6 +450,12 @@ lazy_static! {
|
||||||
dyngroup_filter: Some(
|
dyngroup_filter: Some(
|
||||||
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
|
Filter::Eq(Attribute::Class.to_string(), EntryClass::Account.to_string()),
|
||||||
),
|
),
|
||||||
|
extra_attributes: vec![
|
||||||
|
// Enable account policy by default
|
||||||
|
(Attribute::Class, EntryClass::AccountPolicy.to_value()),
|
||||||
|
// Enforce this is a system protected object
|
||||||
|
(Attribute::Class, EntryClass::System.to_value()),
|
||||||
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -78,9 +78,13 @@ pub const AUTH_SESSION_TIMEOUT: u64 = 300;
|
||||||
pub const MFAREG_SESSION_TIMEOUT: u64 = 300;
|
pub const MFAREG_SESSION_TIMEOUT: u64 = 300;
|
||||||
pub const PW_MIN_LENGTH: usize = 10;
|
pub const PW_MIN_LENGTH: usize = 10;
|
||||||
|
|
||||||
// Default - sessions last for 1 hour.
|
// Maximum - Sessions have no upper bound.
|
||||||
|
pub const MAXIMUM_AUTH_SESSION_EXPIRY: u32 = u32::MAX;
|
||||||
|
// Default - sessions last for 1 day
|
||||||
pub const DEFAULT_AUTH_SESSION_EXPIRY: u32 = 86400;
|
pub const DEFAULT_AUTH_SESSION_EXPIRY: u32 = 86400;
|
||||||
pub const DEFAULT_AUTH_SESSION_LIMITED_EXPIRY: u32 = 3600;
|
pub const DEFAULT_AUTH_SESSION_LIMITED_EXPIRY: u32 = 3600;
|
||||||
|
// Maximum - privileges last for 1 hour.
|
||||||
|
pub const MAXIMUM_AUTH_PRIVILEGE_EXPIRY: u32 = 3600;
|
||||||
// Default - privileges last for 10 minutes.
|
// Default - privileges last for 10 minutes.
|
||||||
pub const DEFAULT_AUTH_PRIVILEGE_EXPIRY: u32 = 600;
|
pub const DEFAULT_AUTH_PRIVILEGE_EXPIRY: u32 = 600;
|
||||||
// Default - oauth refresh tokens last for 16 hours.
|
// Default - oauth refresh tokens last for 16 hours.
|
||||||
|
|
|
@ -621,6 +621,18 @@ pub static ref SCHEMA_CLASS_DYNGROUP: SchemaClass = SchemaClass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_ACCOUNT_POLICY: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_ACCOUNT_POLICY,
|
||||||
|
name: EntryClass::AccountPolicy.into(),
|
||||||
|
description: "Policies applied to accounts that are members of a group".to_string(),
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::AuthSessionExpiry.into(),
|
||||||
|
Attribute::PrivilegeExpiry.into()
|
||||||
|
],
|
||||||
|
systemsupplements: vec![Attribute::Group.into()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_ACCOUNT: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_ACCOUNT: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_ACCOUNT,
|
uuid: UUID_SCHEMA_CLASS_ACCOUNT,
|
||||||
name: EntryClass::Account.into(),
|
name: EntryClass::Account.into(),
|
||||||
|
|
|
@ -2,7 +2,6 @@ use crate::constants::uuids::*;
|
||||||
|
|
||||||
use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
|
use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
|
||||||
use crate::prelude::{Attribute, EntryClass};
|
use crate::prelude::{Attribute, EntryClass};
|
||||||
use crate::prelude::{DEFAULT_AUTH_PRIVILEGE_EXPIRY, DEFAULT_AUTH_SESSION_EXPIRY};
|
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
||||||
// Default entries for system_config
|
// Default entries for system_config
|
||||||
|
@ -28,14 +27,6 @@ lazy_static! {
|
||||||
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1"
|
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
|
||||||
Attribute::AuthSessionExpiry,
|
|
||||||
Value::Uint32(DEFAULT_AUTH_SESSION_EXPIRY)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Attribute::PrivilegeExpiry,
|
|
||||||
Value::Uint32(DEFAULT_AUTH_PRIVILEGE_EXPIRY)
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
Attribute::BadlistPassword,
|
Attribute::BadlistPassword,
|
||||||
Value::new_iutf8("100preteamare")
|
Value::new_iutf8("100preteamare")
|
||||||
|
|
|
@ -57,6 +57,8 @@ pub const UUID_IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV: Uuid =
|
||||||
pub const UUID_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES: Uuid =
|
pub const UUID_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES: Uuid =
|
||||||
uuid!("00000000-0000-0000-0000-000000000038");
|
uuid!("00000000-0000-0000-0000-000000000038");
|
||||||
pub const UUID_IDM_ACCOUNT_MAIL_READ_PRIV: Uuid = uuid!("00000000-0000-0000-0000-000000000039");
|
pub const UUID_IDM_ACCOUNT_MAIL_READ_PRIV: Uuid = uuid!("00000000-0000-0000-0000-000000000039");
|
||||||
|
pub const UUID_IDM_GROUP_ACCOUNT_POLICY_MANAGE_PRIV: Uuid =
|
||||||
|
uuid!("00000000-0000-0000-0000-000000000040");
|
||||||
|
|
||||||
//
|
//
|
||||||
pub const UUID_IDM_HIGH_PRIVILEGE: Uuid = uuid!("00000000-0000-0000-0000-000000001000");
|
pub const UUID_IDM_HIGH_PRIVILEGE: Uuid = uuid!("00000000-0000-0000-0000-000000001000");
|
||||||
|
@ -240,6 +242,9 @@ pub const UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: Uuid =
|
||||||
pub const UUID_SCHEMA_ATTR_IMAGE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000143");
|
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");
|
pub const UUID_SCHEMA_ATTR_DENIED_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000144");
|
||||||
|
|
||||||
|
// Leave 145 for ldap unix pw bind
|
||||||
|
pub const UUID_SCHEMA_CLASS_ACCOUNT_POLICY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000146");
|
||||||
|
|
||||||
// System and domain infos
|
// System and domain infos
|
||||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||||
pub const UUID_SYSTEM_INFO: Uuid = uuid!("00000000-0000-0000-0000-ffffff000001");
|
pub const UUID_SYSTEM_INFO: Uuid = uuid!("00000000-0000-0000-0000-ffffff000001");
|
||||||
|
|
|
@ -11,6 +11,7 @@ use webauthn_rs::prelude::{
|
||||||
AttestedPasskey as DeviceKeyV4, AuthenticationResult, CredentialID, Passkey as PasskeyV4,
|
AttestedPasskey as DeviceKeyV4, AuthenticationResult, CredentialID, Passkey as PasskeyV4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::accountpolicy::ResolvedAccountPolicy;
|
||||||
use crate::constants::UUID_ANONYMOUS;
|
use crate::constants::UUID_ANONYMOUS;
|
||||||
use crate::credential::softlock::CredSoftLockPolicy;
|
use crate::credential::softlock::CredSoftLockPolicy;
|
||||||
use crate::credential::Credential;
|
use crate::credential::Credential;
|
||||||
|
@ -229,16 +230,28 @@ impl Account {
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerReadTransaction,
|
qs: &mut QueryServerReadTransaction,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
let groups = Group::try_from_account_entry_ro(value, qs)?;
|
let groups = Group::try_from_account_entry(value, qs)?;
|
||||||
try_from_entry!(value, groups)
|
try_from_entry!(value, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "trace", skip_all)]
|
||||||
|
pub(crate) fn try_from_entry_with_policy<'a, TXN>(
|
||||||
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
|
qs: &mut TXN,
|
||||||
|
) -> Result<(Self, ResolvedAccountPolicy), OperationError>
|
||||||
|
where
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
let (groups, rap) = Group::try_from_account_entry_with_policy(value, qs)?;
|
||||||
|
try_from_entry!(value, groups).map(|acct| (acct, rap))
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
pub(crate) fn try_from_entry_rw(
|
pub(crate) fn try_from_entry_rw(
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerWriteTransaction,
|
qs: &mut QueryServerWriteTransaction,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
let groups = Group::try_from_account_entry_rw(value, qs)?;
|
let groups = Group::try_from_account_entry(value, qs)?;
|
||||||
try_from_entry!(value, groups)
|
try_from_entry!(value, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,16 +260,10 @@ impl Account {
|
||||||
value: &Entry<EntryReduced, EntryCommitted>,
|
value: &Entry<EntryReduced, EntryCommitted>,
|
||||||
qs: &mut QueryServerReadTransaction,
|
qs: &mut QueryServerReadTransaction,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
let groups = Group::try_from_account_entry_red_ro(value, qs)?;
|
let groups = Group::try_from_account_entry_reduced(value, qs)?;
|
||||||
try_from_entry!(value, groups)
|
try_from_entry!(value, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_from_entry_no_groups(
|
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
|
||||||
) -> Result<Self, OperationError> {
|
|
||||||
try_from_entry!(value, vec![])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given the session_id and other metadata, create a user authentication token
|
/// Given the session_id and other metadata, create a user authentication token
|
||||||
/// that represents a users session. Since this metadata can vary from session
|
/// that represents a users session. Since this metadata can vary from session
|
||||||
/// to session, this userauthtoken may contain some data (claims) that may yield
|
/// to session, this userauthtoken may contain some data (claims) that may yield
|
||||||
|
|
153
server/lib/src/idm/accountpolicy.rs
Normal file
153
server/lib/src/idm/accountpolicy.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
// use crate::idm::server::IdmServerProxyWriteTransaction;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Default)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub(crate) enum CredentialPolicy {
|
||||||
|
#[default]
|
||||||
|
NoPolicy = 0,
|
||||||
|
MfaRequired = 10,
|
||||||
|
PasskeyRequired = 20,
|
||||||
|
AttestedPasskeyRequired = 30,
|
||||||
|
AttestedResidentKeyRequired = 40,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for CredentialPolicy {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
if value >= CredentialPolicy::AttestedResidentKeyRequired as u32 {
|
||||||
|
CredentialPolicy::AttestedResidentKeyRequired
|
||||||
|
} else if value >= CredentialPolicy::AttestedPasskeyRequired as u32 {
|
||||||
|
CredentialPolicy::AttestedPasskeyRequired
|
||||||
|
} else if value >= CredentialPolicy::PasskeyRequired as u32 {
|
||||||
|
CredentialPolicy::PasskeyRequired
|
||||||
|
} else if value >= CredentialPolicy::MfaRequired as u32 {
|
||||||
|
CredentialPolicy::MfaRequired
|
||||||
|
} else {
|
||||||
|
CredentialPolicy::NoPolicy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct AccountPolicy {
|
||||||
|
privilege_expiry: u32,
|
||||||
|
authsession_expiry: u32,
|
||||||
|
credential_policy: CredentialPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Option<AccountPolicy>> for &EntrySealedCommitted {
|
||||||
|
fn into(self) -> Option<AccountPolicy> {
|
||||||
|
if !self.attribute_equality(
|
||||||
|
Attribute::Class,
|
||||||
|
&EntryClass::AccountPolicy.to_partialvalue(),
|
||||||
|
) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let authsession_expiry = self
|
||||||
|
.get_ava_single_uint32(Attribute::AuthSessionExpiry)
|
||||||
|
.unwrap_or(MAXIMUM_AUTH_SESSION_EXPIRY);
|
||||||
|
let privilege_expiry = self
|
||||||
|
.get_ava_single_uint32(Attribute::PrivilegeExpiry)
|
||||||
|
.unwrap_or(MAXIMUM_AUTH_PRIVILEGE_EXPIRY);
|
||||||
|
let credential_policy = CredentialPolicy::default();
|
||||||
|
|
||||||
|
Some(AccountPolicy {
|
||||||
|
privilege_expiry,
|
||||||
|
authsession_expiry,
|
||||||
|
credential_policy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(test, derive(Default))]
|
||||||
|
pub(crate) struct ResolvedAccountPolicy {
|
||||||
|
privilege_expiry: u32,
|
||||||
|
authsession_expiry: u32,
|
||||||
|
credential_policy: CredentialPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolvedAccountPolicy {
|
||||||
|
pub(crate) fn fold_from<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: Iterator<Item = AccountPolicy>,
|
||||||
|
{
|
||||||
|
// Start with our maximums
|
||||||
|
let mut accumulate = ResolvedAccountPolicy {
|
||||||
|
privilege_expiry: MAXIMUM_AUTH_PRIVILEGE_EXPIRY,
|
||||||
|
authsession_expiry: MAXIMUM_AUTH_SESSION_EXPIRY,
|
||||||
|
credential_policy: CredentialPolicy::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
iter.for_each(|acc_pol| {
|
||||||
|
// Take the smaller expiry
|
||||||
|
if acc_pol.privilege_expiry < accumulate.privilege_expiry {
|
||||||
|
accumulate.privilege_expiry = acc_pol.privilege_expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the smaller expiry
|
||||||
|
if acc_pol.authsession_expiry < accumulate.authsession_expiry {
|
||||||
|
accumulate.authsession_expiry = acc_pol.authsession_expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the greater credential type policy
|
||||||
|
if acc_pol.credential_policy > accumulate.credential_policy {
|
||||||
|
accumulate.credential_policy = acc_pol.credential_policy
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
accumulate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn privilege_expiry(&self) -> u32 {
|
||||||
|
self.privilege_expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn authsession_expiry(&self) -> u32 {
|
||||||
|
self.authsession_expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub(crate) fn credential_policy(&self) -> CredentialPolicy {
|
||||||
|
self.credential_policy
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{AccountPolicy, CredentialPolicy, ResolvedAccountPolicy};
|
||||||
|
// use crate::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_account_policy_resolve() {
|
||||||
|
let policy_a = AccountPolicy {
|
||||||
|
privilege_expiry: 100,
|
||||||
|
authsession_expiry: 100,
|
||||||
|
credential_policy: CredentialPolicy::MfaRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
let policy_b = AccountPolicy {
|
||||||
|
privilege_expiry: 150,
|
||||||
|
authsession_expiry: 50,
|
||||||
|
credential_policy: CredentialPolicy::PasskeyRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
let rap = ResolvedAccountPolicy::fold_from([policy_a, policy_b].into_iter());
|
||||||
|
|
||||||
|
assert_eq!(rap.privilege_expiry(), 100);
|
||||||
|
assert_eq!(rap.authsession_expiry(), 50);
|
||||||
|
assert_eq!(rap.credential_policy, CredentialPolicy::PasskeyRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[idm_test]
|
||||||
|
async fn test_idm_account_policy_load(
|
||||||
|
idms: &IdmServer,
|
||||||
|
_idms_delayed: &mut IdmServerDelayed,
|
||||||
|
) {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ use crate::prelude::*;
|
||||||
use crate::value::{Session, SessionState};
|
use crate::value::{Session, SessionState};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::server::AccountPolicy;
|
use super::accountpolicy::ResolvedAccountPolicy;
|
||||||
|
|
||||||
// Each CredHandler takes one or more credentials and determines if the
|
// Each CredHandler takes one or more credentials and determines if the
|
||||||
// handlers requirements can be 100% fulfilled. This is where MFA or other
|
// handlers requirements can be 100% fulfilled. This is where MFA or other
|
||||||
|
@ -721,7 +721,7 @@ pub(crate) struct AuthSession {
|
||||||
// How do we know what claims to add?
|
// How do we know what claims to add?
|
||||||
account: Account,
|
account: Account,
|
||||||
// This policies that apply to this account
|
// This policies that apply to this account
|
||||||
account_policy: AccountPolicy,
|
account_policy: ResolvedAccountPolicy,
|
||||||
|
|
||||||
// Store how we plan to handle this sessions authentication: this is generally
|
// 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
|
// made apparent by the presentation of an application id or not. If none is presented
|
||||||
|
@ -747,7 +747,7 @@ impl AuthSession {
|
||||||
/// or interleved write operations do not cause inconsistency in this process.
|
/// or interleved write operations do not cause inconsistency in this process.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
account: Account,
|
account: Account,
|
||||||
account_policy: AccountPolicy,
|
account_policy: ResolvedAccountPolicy,
|
||||||
issue: AuthIssueSession,
|
issue: AuthIssueSession,
|
||||||
privileged: bool,
|
privileged: bool,
|
||||||
webauthn: &Webauthn,
|
webauthn: &Webauthn,
|
||||||
|
@ -828,7 +828,7 @@ impl AuthSession {
|
||||||
/// initial authentication.
|
/// initial authentication.
|
||||||
pub(crate) fn new_reauth(
|
pub(crate) fn new_reauth(
|
||||||
account: Account,
|
account: Account,
|
||||||
account_policy: AccountPolicy,
|
account_policy: ResolvedAccountPolicy,
|
||||||
session_id: Uuid,
|
session_id: Uuid,
|
||||||
session: &Session,
|
session: &Session,
|
||||||
cred_id: Uuid,
|
cred_id: Uuid,
|
||||||
|
@ -1257,13 +1257,13 @@ mod tests {
|
||||||
use crate::credential::totp::{Totp, TOTP_DEFAULT_STEP};
|
use crate::credential::totp::{Totp, TOTP_DEFAULT_STEP};
|
||||||
use crate::credential::{BackupCodes, Credential};
|
use crate::credential::{BackupCodes, Credential};
|
||||||
use crate::idm::account::Account;
|
use crate::idm::account::Account;
|
||||||
|
use crate::idm::accountpolicy::ResolvedAccountPolicy;
|
||||||
use crate::idm::audit::AuditEvent;
|
use crate::idm::audit::AuditEvent;
|
||||||
use crate::idm::authsession::{
|
use crate::idm::authsession::{
|
||||||
AuthSession, BAD_AUTH_TYPE_MSG, BAD_BACKUPCODE_MSG, BAD_PASSWORD_MSG, BAD_TOTP_MSG,
|
AuthSession, BAD_AUTH_TYPE_MSG, BAD_BACKUPCODE_MSG, BAD_PASSWORD_MSG, BAD_TOTP_MSG,
|
||||||
BAD_WEBAUTHN_MSG, PW_BADLIST_MSG,
|
BAD_WEBAUTHN_MSG, PW_BADLIST_MSG,
|
||||||
};
|
};
|
||||||
use crate::idm::delayed::DelayedAction;
|
use crate::idm::delayed::DelayedAction;
|
||||||
use crate::idm::server::AccountPolicy;
|
|
||||||
use crate::idm::AuthState;
|
use crate::idm::AuthState;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::utils::readable_password_from_random;
|
use crate::utils::readable_password_from_random;
|
||||||
|
@ -1298,7 +1298,7 @@ mod tests {
|
||||||
|
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
anon_account,
|
anon_account,
|
||||||
AccountPolicy::default(),
|
ResolvedAccountPolicy::default(),
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
false,
|
false,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
@ -1335,7 +1335,7 @@ mod tests {
|
||||||
) => {{
|
) => {{
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
AccountPolicy::default(),
|
ResolvedAccountPolicy::default(),
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
$privileged,
|
$privileged,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
|
@ -1516,7 +1516,7 @@ mod tests {
|
||||||
) => {{
|
) => {{
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
AccountPolicy::default(),
|
ResolvedAccountPolicy::default(),
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
false,
|
false,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
|
@ -1844,7 +1844,7 @@ mod tests {
|
||||||
) => {{
|
) => {{
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
AccountPolicy::default(),
|
ResolvedAccountPolicy::default(),
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
false,
|
false,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
|
|
|
@ -4,6 +4,7 @@ use kanidm_proto::v1::UiHint;
|
||||||
use kanidm_proto::v1::{Group as ProtoGroup, OperationError};
|
use kanidm_proto::v1::{Group as ProtoGroup, OperationError};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::accountpolicy::{AccountPolicy, ResolvedAccountPolicy};
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryReduced, EntrySealed};
|
use crate::entry::{Entry, EntryCommitted, EntryReduced, EntrySealed};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::value::PartialValue;
|
use crate::value::PartialValue;
|
||||||
|
@ -16,17 +17,32 @@ pub struct Group {
|
||||||
pub ui_hints: BTreeSet<UiHint>,
|
pub ui_hints: BTreeSet<UiHint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! try_from_account_e {
|
macro_rules! entry_groups {
|
||||||
($value:expr, $qs:expr) => {{
|
($value:expr, $qs:expr) => {{
|
||||||
/*
|
match $value.get_ava_as_refuuid(Attribute::MemberOf) {
|
||||||
let name = $value
|
Some(riter) => {
|
||||||
.get_ava_single_iname(Attribute::Name)
|
// given a list of uuid, make a filter: even if this is empty, the be will
|
||||||
.map(str::to_string)
|
// just give and empty result set.
|
||||||
.ok_or_else(|| {
|
let f = filter!(f_or(
|
||||||
OperationError::InvalidAccountState("Missing attribute: name".to_string())
|
riter
|
||||||
})?;
|
.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
|
||||||
*/
|
.collect()
|
||||||
|
));
|
||||||
|
$qs.internal_search(f).map_err(|e| {
|
||||||
|
admin_error!(?e, "internal search failed");
|
||||||
|
e
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// No memberof, no groups!
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! upg_from_account_e {
|
||||||
|
($value:expr, $groups:expr) => {{
|
||||||
// Setup the user private group
|
// Setup the user private group
|
||||||
let spn = $value.get_ava_single_proto_string(Attribute::Spn).ok_or(
|
let spn = $value.get_ava_single_proto_string(Attribute::Spn).ok_or(
|
||||||
OperationError::InvalidAccountState(format!("Missing attribute: {}", Attribute::Spn)),
|
OperationError::InvalidAccountState(format!("Missing attribute: {}", Attribute::Spn)),
|
||||||
|
@ -43,60 +59,61 @@ macro_rules! try_from_account_e {
|
||||||
ui_hints,
|
ui_hints,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups: Vec<Group> = match $value.get_ava_as_refuuid(Attribute::MemberOf) {
|
// Now convert the group entries to groups.
|
||||||
Some(riter) => {
|
let groups: Result<Vec<_>, _> = $groups
|
||||||
// given a list of uuid, make a filter: even if this is empty, the be will
|
.iter()
|
||||||
// just give and empty result set.
|
.map(|e| Group::try_from_entry(e.as_ref()))
|
||||||
let f = filter!(f_or(
|
.chain(std::iter::once(Ok(upg)))
|
||||||
riter
|
.collect();
|
||||||
.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
|
|
||||||
.collect()
|
|
||||||
));
|
|
||||||
let group_entries: Vec<_> = $qs.internal_search(f).map_err(|e| {
|
|
||||||
admin_error!(?e, "internal search failed");
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
// Now convert the group entries to groups.
|
|
||||||
let groups: Result<Vec<_>, _> = group_entries
|
|
||||||
.iter()
|
|
||||||
.map(|e| Group::try_from_entry(e.as_ref()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
groups.map_err(|e| {
|
groups.map_err(|e| {
|
||||||
admin_error!(?e, "failed to transform group entries to groups");
|
error!(?e, "failed to transform group entries to groups");
|
||||||
e
|
e
|
||||||
})?
|
})
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// No memberof, no groups!
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
groups.push(upg);
|
|
||||||
Ok(groups)
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Group {
|
impl Group {
|
||||||
pub fn try_from_account_entry_red_ro(
|
pub fn try_from_account_entry_reduced<'a, TXN>(
|
||||||
value: &Entry<EntryReduced, EntryCommitted>,
|
value: &Entry<EntryReduced, EntryCommitted>,
|
||||||
qs: &mut QueryServerReadTransaction,
|
qs: &mut TXN,
|
||||||
) -> Result<Vec<Self>, OperationError> {
|
) -> Result<Vec<Self>, OperationError>
|
||||||
try_from_account_e!(value, qs)
|
where
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
let groups = entry_groups!(value, qs);
|
||||||
|
upg_from_account_e!(value, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_account_entry_ro(
|
pub fn try_from_account_entry<'a, TXN>(
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerReadTransaction,
|
qs: &mut TXN,
|
||||||
) -> Result<Vec<Self>, OperationError> {
|
) -> Result<Vec<Self>, OperationError>
|
||||||
try_from_account_e!(value, qs)
|
where
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
let groups = entry_groups!(value, qs);
|
||||||
|
upg_from_account_e!(value, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_account_entry_rw(
|
pub(crate) fn try_from_account_entry_with_policy<'b, 'a, TXN>(
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &'b Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerWriteTransaction,
|
qs: &mut TXN,
|
||||||
) -> Result<Vec<Self>, OperationError> {
|
) -> Result<(Vec<Self>, ResolvedAccountPolicy), OperationError>
|
||||||
try_from_account_e!(value, qs)
|
where
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
let groups = entry_groups!(value, qs);
|
||||||
|
// Get the account policy here.
|
||||||
|
|
||||||
|
let rap = ResolvedAccountPolicy::fold_from(groups.iter().filter_map(|entry| {
|
||||||
|
let acc_pol: Option<AccountPolicy> = entry.as_ref().into();
|
||||||
|
acc_pol
|
||||||
|
}));
|
||||||
|
|
||||||
|
let r_groups = upg_from_account_e!(value, groups)?;
|
||||||
|
|
||||||
|
Ok((r_groups, rap))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_entry(
|
pub fn try_from_entry(
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
//! is implemented.
|
//! is implemented.
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
|
pub(crate) mod accountpolicy;
|
||||||
pub(crate) mod applinks;
|
pub(crate) mod applinks;
|
||||||
pub mod audit;
|
pub mod audit;
|
||||||
pub(crate) mod authsession;
|
pub(crate) mod authsession;
|
||||||
|
|
|
@ -1641,7 +1641,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
||||||
return Ok(AccessTokenIntrospectResponse::inactive());
|
return Ok(AccessTokenIntrospectResponse::inactive());
|
||||||
};
|
};
|
||||||
|
|
||||||
let account = match Account::try_from_entry_no_groups(&entry) {
|
let account = match Account::try_from_entry_ro(&entry, &mut self.qs_read) {
|
||||||
Ok(account) => account,
|
Ok(account) => account,
|
||||||
Err(err) => return Err(Oauth2Error::ServerError(err)),
|
Err(err) => return Err(Oauth2Error::ServerError(err)),
|
||||||
};
|
};
|
||||||
|
|
|
@ -62,7 +62,7 @@ impl RadiusAccount {
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let groups = Group::try_from_account_entry_red_ro(value, qs)?;
|
let groups = Group::try_from_account_entry_reduced(value, qs)?;
|
||||||
|
|
||||||
let valid_from = value.get_ava_single_datetime(Attribute::AccountValidFrom);
|
let valid_from = value.get_ava_single_datetime(Attribute::AccountValidFrom);
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,8 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Setup the account record.
|
// Setup the account record.
|
||||||
let account = Account::try_from_entry_ro(entry.as_ref(), &mut self.qs_read)?;
|
let (account, account_policy) =
|
||||||
|
Account::try_from_entry_with_policy(entry.as_ref(), &mut self.qs_read)?;
|
||||||
let account_policy = (*self.account_policy).clone();
|
|
||||||
|
|
||||||
security_info!(
|
security_info!(
|
||||||
username = %account.name,
|
username = %account.name,
|
||||||
|
|
|
@ -68,38 +68,6 @@ pub struct DomainKeys {
|
||||||
pub(crate) cookie_key: [u8; 64],
|
pub(crate) cookie_key: [u8; 64],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct AccountPolicy {
|
|
||||||
privilege_expiry: u32,
|
|
||||||
authsession_expiry: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AccountPolicy {
|
|
||||||
pub(crate) fn new(privilege_expiry: u32, authsession_expiry: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
privilege_expiry,
|
|
||||||
authsession_expiry,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn privilege_expiry(&self) -> u32 {
|
|
||||||
self.privilege_expiry
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn authsession_expiry(&self) -> u32 {
|
|
||||||
self.authsession_expiry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AccountPolicy {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
privilege_expiry: DEFAULT_AUTH_PRIVILEGE_EXPIRY,
|
|
||||||
authsession_expiry: DEFAULT_AUTH_SESSION_EXPIRY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IdmServer {
|
pub struct IdmServer {
|
||||||
// There is a good reason to keep this single thread - it
|
// There is a good reason to keep this single thread - it
|
||||||
// means that limits to sessions can be easily applied and checked to
|
// means that limits to sessions can be easily applied and checked to
|
||||||
|
@ -120,7 +88,6 @@ pub struct IdmServer {
|
||||||
webauthn: Webauthn,
|
webauthn: Webauthn,
|
||||||
oauth2rs: Arc<Oauth2ResourceServers>,
|
oauth2rs: Arc<Oauth2ResourceServers>,
|
||||||
domain_keys: Arc<CowCell<DomainKeys>>,
|
domain_keys: Arc<CowCell<DomainKeys>>,
|
||||||
account_policy: Arc<CowCell<AccountPolicy>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains methods that require writes, but in the context of writing to the idm in memory structures (maybe the query server too). This is things like authentication.
|
/// Contains methods that require writes, but in the context of writing to the idm in memory structures (maybe the query server too). This is things like authentication.
|
||||||
|
@ -136,7 +103,6 @@ pub struct IdmServerAuthTransaction<'a> {
|
||||||
pub(crate) async_tx: Sender<DelayedAction>,
|
pub(crate) async_tx: Sender<DelayedAction>,
|
||||||
pub(crate) audit_tx: Sender<AuditEvent>,
|
pub(crate) audit_tx: Sender<AuditEvent>,
|
||||||
pub(crate) webauthn: &'a Webauthn,
|
pub(crate) webauthn: &'a Webauthn,
|
||||||
pub(crate) account_policy: CowCellReadTxn<AccountPolicy>,
|
|
||||||
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
|
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +110,6 @@ pub struct IdmServerCredUpdateTransaction<'a> {
|
||||||
pub(crate) qs_read: QueryServerReadTransaction<'a>,
|
pub(crate) qs_read: QueryServerReadTransaction<'a>,
|
||||||
// sid: Sid,
|
// sid: Sid,
|
||||||
pub(crate) webauthn: &'a Webauthn,
|
pub(crate) webauthn: &'a Webauthn,
|
||||||
pub(crate) _account_policy: CowCellReadTxn<AccountPolicy>,
|
|
||||||
pub(crate) cred_update_sessions: BptreeMapReadTxn<'a, Uuid, CredentialUpdateSessionMutex>,
|
pub(crate) cred_update_sessions: BptreeMapReadTxn<'a, Uuid, CredentialUpdateSessionMutex>,
|
||||||
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
|
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
|
||||||
pub(crate) crypto_policy: &'a CryptoPolicy,
|
pub(crate) crypto_policy: &'a CryptoPolicy,
|
||||||
|
@ -166,7 +131,6 @@ pub struct IdmServerProxyWriteTransaction<'a> {
|
||||||
pub(crate) sid: Sid,
|
pub(crate) sid: Sid,
|
||||||
crypto_policy: &'a CryptoPolicy,
|
crypto_policy: &'a CryptoPolicy,
|
||||||
webauthn: &'a Webauthn,
|
webauthn: &'a Webauthn,
|
||||||
account_policy: CowCellWriteTxn<'a, AccountPolicy>,
|
|
||||||
pub(crate) domain_keys: CowCellWriteTxn<'a, DomainKeys>,
|
pub(crate) domain_keys: CowCellWriteTxn<'a, DomainKeys>,
|
||||||
pub(crate) oauth2rs: Oauth2ResourceServersWriteTransaction<'a>,
|
pub(crate) oauth2rs: Oauth2ResourceServersWriteTransaction<'a>,
|
||||||
}
|
}
|
||||||
|
@ -191,16 +155,7 @@ impl IdmServer {
|
||||||
let (audit_tx, audit_rx) = unbounded();
|
let (audit_tx, audit_rx) = unbounded();
|
||||||
|
|
||||||
// Get the domain name, as the relying party id.
|
// Get the domain name, as the relying party id.
|
||||||
let (
|
let (rp_id, rp_name, fernet_private_key, es256_private_key, cookie_key, oauth2rs_set) = {
|
||||||
rp_id,
|
|
||||||
rp_name,
|
|
||||||
fernet_private_key,
|
|
||||||
es256_private_key,
|
|
||||||
cookie_key,
|
|
||||||
oauth2rs_set,
|
|
||||||
privilege_expiry,
|
|
||||||
authsession_expiry,
|
|
||||||
) = {
|
|
||||||
let mut qs_read = qs.read().await;
|
let mut qs_read = qs.read().await;
|
||||||
(
|
(
|
||||||
qs_read.get_domain_name().to_string(),
|
qs_read.get_domain_name().to_string(),
|
||||||
|
@ -210,8 +165,6 @@ impl IdmServer {
|
||||||
qs_read.get_domain_cookie_key()?,
|
qs_read.get_domain_cookie_key()?,
|
||||||
// Add a read/reload of all oauth2 configurations.
|
// Add a read/reload of all oauth2 configurations.
|
||||||
qs_read.get_oauth2rs_set()?,
|
qs_read.get_oauth2rs_set()?,
|
||||||
qs_read.get_privilege_expiry()?,
|
|
||||||
qs_read.get_authsession_expiry()?,
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -286,10 +239,6 @@ impl IdmServer {
|
||||||
async_tx,
|
async_tx,
|
||||||
audit_tx,
|
audit_tx,
|
||||||
webauthn,
|
webauthn,
|
||||||
account_policy: Arc::new(CowCell::new(AccountPolicy::new(
|
|
||||||
privilege_expiry,
|
|
||||||
authsession_expiry,
|
|
||||||
))),
|
|
||||||
domain_keys,
|
domain_keys,
|
||||||
oauth2rs: Arc::new(oauth2rs),
|
oauth2rs: Arc::new(oauth2rs),
|
||||||
},
|
},
|
||||||
|
@ -319,7 +268,6 @@ impl IdmServer {
|
||||||
async_tx: self.async_tx.clone(),
|
async_tx: self.async_tx.clone(),
|
||||||
audit_tx: self.audit_tx.clone(),
|
audit_tx: self.audit_tx.clone(),
|
||||||
webauthn: &self.webauthn,
|
webauthn: &self.webauthn,
|
||||||
account_policy: self.account_policy.read(),
|
|
||||||
domain_keys: self.domain_keys.read(),
|
domain_keys: self.domain_keys.read(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,7 +297,6 @@ impl IdmServer {
|
||||||
sid,
|
sid,
|
||||||
crypto_policy: &self.crypto_policy,
|
crypto_policy: &self.crypto_policy,
|
||||||
webauthn: &self.webauthn,
|
webauthn: &self.webauthn,
|
||||||
account_policy: self.account_policy.write(),
|
|
||||||
domain_keys: self.domain_keys.write(),
|
domain_keys: self.domain_keys.write(),
|
||||||
oauth2rs: self.oauth2rs.write(),
|
oauth2rs: self.oauth2rs.write(),
|
||||||
}
|
}
|
||||||
|
@ -360,7 +307,6 @@ impl IdmServer {
|
||||||
qs_read: self.qs.read().await,
|
qs_read: self.qs.read().await,
|
||||||
// sid: Sid,
|
// sid: Sid,
|
||||||
webauthn: &self.webauthn,
|
webauthn: &self.webauthn,
|
||||||
_account_policy: self.account_policy.read(),
|
|
||||||
cred_update_sessions: self.cred_update_sessions.read(),
|
cred_update_sessions: self.cred_update_sessions.read(),
|
||||||
domain_keys: self.domain_keys.read(),
|
domain_keys: self.domain_keys.read(),
|
||||||
crypto_policy: &self.crypto_policy,
|
crypto_policy: &self.crypto_policy,
|
||||||
|
@ -1041,9 +987,8 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
// typing and functionality so we can assess what auth types can
|
// typing and functionality so we can assess what auth types can
|
||||||
// continue, and helps to keep non-needed entry specific data
|
// continue, and helps to keep non-needed entry specific data
|
||||||
// out of the session tree.
|
// out of the session tree.
|
||||||
let account = Account::try_from_entry_ro(entry.as_ref(), &mut self.qs_read)?;
|
let (account, account_policy) =
|
||||||
|
Account::try_from_entry_with_policy(entry.as_ref(), &mut self.qs_read)?;
|
||||||
let account_policy = (*self.account_policy).clone();
|
|
||||||
|
|
||||||
trace!(?account.primary);
|
trace!(?account.primary);
|
||||||
|
|
||||||
|
@ -2073,10 +2018,6 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
pub fn commit(mut self) -> Result<(), OperationError> {
|
pub fn commit(mut self) -> Result<(), OperationError> {
|
||||||
if self.qs_write.get_changed_system_config() {
|
|
||||||
self.reload_system_account_policy()?;
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.qs_write.get_changed_ouath2() {
|
if self.qs_write.get_changed_ouath2() {
|
||||||
self.qs_write
|
self.qs_write
|
||||||
.get_oauth2rs_set()
|
.get_oauth2rs_set()
|
||||||
|
@ -2132,17 +2073,10 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
// Commit everything.
|
// Commit everything.
|
||||||
self.oauth2rs.commit();
|
self.oauth2rs.commit();
|
||||||
self.domain_keys.commit();
|
self.domain_keys.commit();
|
||||||
self.account_policy.commit();
|
|
||||||
self.cred_update_sessions.commit();
|
self.cred_update_sessions.commit();
|
||||||
trace!("cred_update_session.commit");
|
trace!("cred_update_session.commit");
|
||||||
self.qs_write.commit()
|
self.qs_write.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload_system_account_policy(&mut self) -> Result<(), OperationError> {
|
|
||||||
self.account_policy.authsession_expiry = self.qs_write.get_authsession_expiry()?;
|
|
||||||
self.account_policy.privilege_expiry = self.qs_write.get_privilege_expiry()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need tests of the sessions and the auth ...
|
// Need tests of the sessions and the auth ...
|
||||||
|
@ -3717,8 +3651,17 @@ mod tests {
|
||||||
//we first set the expiry to a custom value
|
//we first set the expiry to a custom value
|
||||||
let mut idms_prox_write = idms.proxy_write(ct).await;
|
let mut idms_prox_write = idms.proxy_write(ct).await;
|
||||||
|
|
||||||
let new_authsession_expiry = 1000_u32;
|
let new_authsession_expiry = 1000;
|
||||||
idms_prox_write.account_policy.authsession_expiry = new_authsession_expiry;
|
|
||||||
|
let modlist = ModifyList::new_purge_and_set(
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
Value::Uint32(new_authsession_expiry),
|
||||||
|
);
|
||||||
|
idms_prox_write
|
||||||
|
.qs_write
|
||||||
|
.internal_modify_uuid(UUID_IDM_ALL_ACCOUNTS, &modlist)
|
||||||
|
.expect("Unable to change default session exp");
|
||||||
|
|
||||||
assert!(idms_prox_write.commit().is_ok());
|
assert!(idms_prox_write.commit().is_ok());
|
||||||
|
|
||||||
// Start anonymous auth.
|
// Start anonymous auth.
|
||||||
|
|
|
@ -472,14 +472,14 @@ macro_rules! try_from_account_group_e {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnixGroup {
|
impl UnixGroup {
|
||||||
pub fn try_from_account_entry_rw(
|
pub(crate) fn try_from_account_entry_rw(
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerWriteTransaction,
|
qs: &mut QueryServerWriteTransaction,
|
||||||
) -> Result<Vec<Self>, OperationError> {
|
) -> Result<Vec<Self>, OperationError> {
|
||||||
try_from_account_group_e!(value, qs)
|
try_from_account_group_e!(value, qs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_account_entry_ro(
|
pub(crate) fn try_from_account_entry_ro(
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerReadTransaction,
|
qs: &mut QueryServerReadTransaction,
|
||||||
) -> Result<Vec<Self>, OperationError> {
|
) -> Result<Vec<Self>, OperationError> {
|
||||||
|
@ -495,13 +495,13 @@ impl UnixGroup {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub fn try_from_entry_reduced(
|
pub(crate) fn try_from_entry_reduced(
|
||||||
value: &Entry<EntryReduced, EntryCommitted>,
|
value: &Entry<EntryReduced, EntryCommitted>,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
try_from_group_e!(value)
|
try_from_group_e!(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_entry(
|
pub(crate) fn try_from_entry(
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
try_from_group_e!(value)
|
try_from_group_e!(value)
|
||||||
|
|
143
server/lib/src/plugins/default_values.rs
Normal file
143
server/lib/src/plugins/default_values.rs
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/// Set and maintain default values on entries that require them. This is separate to
|
||||||
|
/// migrations that enforce entry existence and state on startup, this enforces
|
||||||
|
/// default values for specific entry uuids over every transaction.
|
||||||
|
use std::iter::once;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// use crate::event::{CreateEvent, ModifyEvent};
|
||||||
|
use crate::plugins::Plugin;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub struct DefaultValues {}
|
||||||
|
|
||||||
|
impl Plugin for DefaultValues {
|
||||||
|
fn id() -> &'static str {
|
||||||
|
"plugin_default_values"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(
|
||||||
|
level = "debug",
|
||||||
|
name = "default_values::pre_create_transform",
|
||||||
|
skip_all
|
||||||
|
)]
|
||||||
|
fn pre_create_transform(
|
||||||
|
qs: &mut QueryServerWriteTransaction,
|
||||||
|
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||||
|
_ce: &CreateEvent,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
Self::modify_inner(qs, cand)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "default_values::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_inner(qs, cand)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "default_values::pre_batch_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_inner(qs, cand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultValues {
|
||||||
|
fn modify_inner<T: Clone + std::fmt::Debug>(
|
||||||
|
_qs: &mut QueryServerWriteTransaction,
|
||||||
|
cand: &mut [Entry<EntryInvalid, T>],
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
cand.iter_mut().try_for_each(|e| {
|
||||||
|
// We have to do this rather than get_uuid here because at this stage we haven't
|
||||||
|
// scheme validated the entry so it's uuid could be missing in theory.
|
||||||
|
|
||||||
|
let e_uuid = match e.get_ava_single_uuid(Attribute::Uuid) {
|
||||||
|
Some(e_uuid) => e_uuid,
|
||||||
|
None => {
|
||||||
|
trace!("entry does not contain a uuid");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if e_uuid == UUID_IDM_ALL_ACCOUNTS {
|
||||||
|
// Set default account policy values if none exist.
|
||||||
|
e.add_ava(Attribute::Class, EntryClass::AccountPolicy.to_value());
|
||||||
|
|
||||||
|
if !e.attribute_pres(Attribute::AuthSessionExpiry) {
|
||||||
|
e.set_ava(Attribute::AuthSessionExpiry, once(
|
||||||
|
Value::Uint32(DEFAULT_AUTH_SESSION_EXPIRY),
|
||||||
|
));
|
||||||
|
debug!("default_values: idm_all_accounts - restore default auth_session_expiry");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the minimum functional level if one is not set already.
|
||||||
|
if !e.attribute_pres(Attribute::PrivilegeExpiry) {
|
||||||
|
e.set_ava(Attribute::PrivilegeExpiry, once(
|
||||||
|
Value::Uint32(DEFAULT_AUTH_PRIVILEGE_EXPIRY),
|
||||||
|
));
|
||||||
|
debug!("default_values: idm_all_accounts - restore default privilege_session_expiry");
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(?e);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
// test we can create and generate the id
|
||||||
|
#[qs_test]
|
||||||
|
async fn test_default_values_idm_all_accounts(server: &QueryServer) {
|
||||||
|
let mut server_txn = server.write(duration_from_epoch_now()).await;
|
||||||
|
let e_all_accounts = server_txn
|
||||||
|
.internal_search_uuid(UUID_IDM_ALL_ACCOUNTS)
|
||||||
|
.expect("must not fail");
|
||||||
|
|
||||||
|
assert!(e_all_accounts.attribute_equality(
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
&PartialValue::Uint32(DEFAULT_AUTH_SESSION_EXPIRY)
|
||||||
|
));
|
||||||
|
assert!(e_all_accounts.attribute_equality(
|
||||||
|
Attribute::PrivilegeExpiry,
|
||||||
|
&PartialValue::Uint32(DEFAULT_AUTH_PRIVILEGE_EXPIRY)
|
||||||
|
));
|
||||||
|
|
||||||
|
// delete the values.
|
||||||
|
server_txn
|
||||||
|
.internal_modify_uuid(
|
||||||
|
UUID_IDM_ALL_ACCOUNTS,
|
||||||
|
&ModifyList::new_list(vec![
|
||||||
|
Modify::Purged(Attribute::AuthSessionExpiry.into()),
|
||||||
|
Modify::Purged(Attribute::PrivilegeExpiry.into()),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.expect("failed to modify account");
|
||||||
|
|
||||||
|
// They are re-populated.
|
||||||
|
let e_all_accounts = server_txn
|
||||||
|
.internal_search_uuid(UUID_IDM_ALL_ACCOUNTS)
|
||||||
|
.expect("must not fail");
|
||||||
|
|
||||||
|
assert!(e_all_accounts.attribute_equality(
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
&PartialValue::Uint32(DEFAULT_AUTH_SESSION_EXPIRY)
|
||||||
|
));
|
||||||
|
assert!(e_all_accounts.attribute_equality(
|
||||||
|
Attribute::PrivilegeExpiry,
|
||||||
|
&PartialValue::Uint32(DEFAULT_AUTH_PRIVILEGE_EXPIRY)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,6 @@ impl DynGroup {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn apply_dyngroup_change(
|
fn apply_dyngroup_change(
|
||||||
qs: &mut QueryServerWriteTransaction,
|
qs: &mut QueryServerWriteTransaction,
|
||||||
ident: &Identity,
|
|
||||||
candidate_tuples: &mut Vec<(Arc<EntrySealedCommitted>, EntryInvalidCommitted)>,
|
candidate_tuples: &mut Vec<(Arc<EntrySealedCommitted>, EntryInvalidCommitted)>,
|
||||||
affected_uuids: &mut Vec<Uuid>,
|
affected_uuids: &mut Vec<Uuid>,
|
||||||
expect: bool,
|
expect: bool,
|
||||||
|
@ -25,11 +24,16 @@ impl DynGroup {
|
||||||
dyn_groups: &mut DynGroupCache,
|
dyn_groups: &mut DynGroupCache,
|
||||||
n_dyn_groups: &[&Entry<EntrySealed, EntryCommitted>],
|
n_dyn_groups: &[&Entry<EntrySealed, EntryCommitted>],
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
|
/*
|
||||||
|
* This triggers even if we are modifying the dyngroups account policy attributes, which
|
||||||
|
* is allowed now. So we relax this, because systemprotection still blocks the creation
|
||||||
|
* of dyngroups.
|
||||||
if !ident.is_internal() {
|
if !ident.is_internal() {
|
||||||
// It should be impossible to trigger this right now due to protected plugin.
|
// It should be impossible to trigger this right now due to protected plugin.
|
||||||
error!("It is currently an error to create a dynamic group");
|
error!("It is currently an error to create a dynamic group");
|
||||||
return Err(OperationError::SystemProtectedObject);
|
return Err(OperationError::SystemProtectedObject);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Search all the new groups first.
|
// Search all the new groups first.
|
||||||
let filt = filter!(FC::Or(
|
let filt = filter!(FC::Or(
|
||||||
|
@ -95,7 +99,7 @@ impl DynGroup {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "dyngroup_reload", skip_all)]
|
#[instrument(level = "debug", name = "dyngroup::reload", skip_all)]
|
||||||
pub fn reload(qs: &mut QueryServerWriteTransaction) -> Result<(), OperationError> {
|
pub fn reload(qs: &mut QueryServerWriteTransaction) -> Result<(), OperationError> {
|
||||||
let ident_internal = Identity::from_internal();
|
let ident_internal = Identity::from_internal();
|
||||||
// Internal search all our definitions.
|
// Internal search all our definitions.
|
||||||
|
@ -135,11 +139,11 @@ impl DynGroup {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "dyngroup_post_create", skip_all)]
|
#[instrument(level = "debug", name = "dyngroup::post_create", skip_all)]
|
||||||
pub fn post_create(
|
pub fn post_create(
|
||||||
qs: &mut QueryServerWriteTransaction,
|
qs: &mut QueryServerWriteTransaction,
|
||||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||||
ident: &Identity,
|
_ident: &Identity,
|
||||||
) -> Result<Vec<Uuid>, OperationError> {
|
) -> Result<Vec<Uuid>, OperationError> {
|
||||||
let mut affected_uuids = Vec::with_capacity(cand.len());
|
let mut affected_uuids = Vec::with_capacity(cand.len());
|
||||||
|
|
||||||
|
@ -213,7 +217,6 @@ impl DynGroup {
|
||||||
trace!("considering new dyngroups");
|
trace!("considering new dyngroups");
|
||||||
Self::apply_dyngroup_change(
|
Self::apply_dyngroup_change(
|
||||||
qs,
|
qs,
|
||||||
ident,
|
|
||||||
&mut candidate_tuples,
|
&mut candidate_tuples,
|
||||||
&mut affected_uuids,
|
&mut affected_uuids,
|
||||||
false,
|
false,
|
||||||
|
@ -235,12 +238,12 @@ impl DynGroup {
|
||||||
Ok(affected_uuids)
|
Ok(affected_uuids)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "memberof_post_modify", skip_all)]
|
#[instrument(level = "debug", name = "dyngroup::post_modify", skip_all)]
|
||||||
pub fn post_modify(
|
pub fn post_modify(
|
||||||
qs: &mut QueryServerWriteTransaction,
|
qs: &mut QueryServerWriteTransaction,
|
||||||
pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
|
pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
|
||||||
cand: &[Entry<EntrySealed, EntryCommitted>],
|
cand: &[Entry<EntrySealed, EntryCommitted>],
|
||||||
ident: &Identity,
|
_ident: &Identity,
|
||||||
) -> Result<Vec<Uuid>, OperationError> {
|
) -> Result<Vec<Uuid>, OperationError> {
|
||||||
let mut affected_uuids = Vec::with_capacity(cand.len());
|
let mut affected_uuids = Vec::with_capacity(cand.len());
|
||||||
|
|
||||||
|
@ -276,7 +279,6 @@ impl DynGroup {
|
||||||
trace!("considering modified dyngroups");
|
trace!("considering modified dyngroups");
|
||||||
Self::apply_dyngroup_change(
|
Self::apply_dyngroup_change(
|
||||||
qs,
|
qs,
|
||||||
ident,
|
|
||||||
&mut candidate_tuples,
|
&mut candidate_tuples,
|
||||||
&mut affected_uuids,
|
&mut affected_uuids,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -15,6 +15,7 @@ use crate::prelude::*;
|
||||||
mod attrunique;
|
mod attrunique;
|
||||||
mod base;
|
mod base;
|
||||||
mod cred_import;
|
mod cred_import;
|
||||||
|
mod default_values;
|
||||||
mod domain;
|
mod domain;
|
||||||
pub(crate) mod dyngroup;
|
pub(crate) mod dyngroup;
|
||||||
mod eckeygen;
|
mod eckeygen;
|
||||||
|
@ -235,6 +236,7 @@ impl Plugins {
|
||||||
gidnumber::GidNumber::pre_create_transform(qs, cand, ce)?;
|
gidnumber::GidNumber::pre_create_transform(qs, cand, ce)?;
|
||||||
domain::Domain::pre_create_transform(qs, cand, ce)?;
|
domain::Domain::pre_create_transform(qs, cand, ce)?;
|
||||||
spn::Spn::pre_create_transform(qs, cand, ce)?;
|
spn::Spn::pre_create_transform(qs, cand, ce)?;
|
||||||
|
default_values::DefaultValues::pre_create_transform(qs, cand, ce)?;
|
||||||
namehistory::NameHistory::pre_create_transform(qs, cand, ce)?;
|
namehistory::NameHistory::pre_create_transform(qs, cand, ce)?;
|
||||||
eckeygen::EcdhKeyGen::pre_create_transform(qs, cand, ce)?;
|
eckeygen::EcdhKeyGen::pre_create_transform(qs, cand, ce)?;
|
||||||
// Should always be last
|
// Should always be last
|
||||||
|
@ -276,6 +278,7 @@ impl Plugins {
|
||||||
domain::Domain::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)?;
|
spn::Spn::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
session::SessionConsistency::pre_modify(qs, pre_cand, cand, me)?;
|
session::SessionConsistency::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
|
default_values::DefaultValues::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
namehistory::NameHistory::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)?;
|
eckeygen::EcdhKeyGen::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
// attr unique should always be last
|
// attr unique should always be last
|
||||||
|
@ -310,6 +313,7 @@ impl Plugins {
|
||||||
domain::Domain::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)?;
|
spn::Spn::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
session::SessionConsistency::pre_batch_modify(qs, pre_cand, cand, me)?;
|
session::SessionConsistency::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
|
default_values::DefaultValues::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
namehistory::NameHistory::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)?;
|
eckeygen::EcdhKeyGen::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
// attr unique should always be last
|
// attr unique should always be last
|
||||||
|
|
|
@ -32,6 +32,7 @@ lazy_static! {
|
||||||
m.insert(Attribute::BadlistPassword);
|
m.insert(Attribute::BadlistPassword);
|
||||||
m.insert(Attribute::DeniedName);
|
m.insert(Attribute::DeniedName);
|
||||||
m.insert(Attribute::DomainDisplayName);
|
m.insert(Attribute::DomainDisplayName);
|
||||||
|
// Allow modification of account policy values for dyngroups
|
||||||
m.insert(Attribute::AuthSessionExpiry);
|
m.insert(Attribute::AuthSessionExpiry);
|
||||||
m.insert(Attribute::PrivilegeExpiry);
|
m.insert(Attribute::PrivilegeExpiry);
|
||||||
m
|
m
|
||||||
|
@ -108,7 +109,6 @@ impl Plugin for Protected {
|
||||||
cand.iter().try_fold((), |(), cand| {
|
cand.iter().try_fold((), |(), cand| {
|
||||||
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
|
|
||||||
{
|
{
|
||||||
Err(OperationError::SystemProtectedObject)
|
Err(OperationError::SystemProtectedObject)
|
||||||
} else {
|
} else {
|
||||||
|
@ -189,7 +189,6 @@ impl Plugin for Protected {
|
||||||
cand.iter().try_fold((), |(), cand| {
|
cand.iter().try_fold((), |(), cand| {
|
||||||
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into())
|
|
||||||
{
|
{
|
||||||
Err(OperationError::SystemProtectedObject)
|
Err(OperationError::SystemProtectedObject)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -111,6 +111,10 @@ impl QueryServer {
|
||||||
if system_info_version < 15 {
|
if system_info_version < 15 {
|
||||||
write_txn.migrate_14_to_15()?;
|
write_txn.migrate_14_to_15()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if system_info_version < 16 {
|
||||||
|
write_txn.migrate_15_to_16()?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload if anything in migrations requires it.
|
// Reload if anything in migrations requires it.
|
||||||
|
@ -120,7 +124,7 @@ impl QueryServer {
|
||||||
|
|
||||||
// Now force everything to reload.
|
// Now force everything to reload.
|
||||||
write_txn.force_all_reload();
|
write_txn.force_all_reload();
|
||||||
// We are read to run
|
// We are ready to run
|
||||||
write_txn.set_phase(ServerPhase::Running);
|
write_txn.set_phase(ServerPhase::Running);
|
||||||
|
|
||||||
// Commit all changes, this also triggers the reload.
|
// Commit all changes, this also triggers the reload.
|
||||||
|
@ -454,6 +458,57 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
// Complete
|
// Complete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip_all)]
|
||||||
|
pub fn migrate_15_to_16(&mut self) -> Result<(), OperationError> {
|
||||||
|
admin_warn!("starting 15 to 16 migration.");
|
||||||
|
|
||||||
|
let sysconfig_entry = match self.internal_search_uuid(UUID_SYSTEM_CONFIG) {
|
||||||
|
Ok(entry) => entry,
|
||||||
|
Err(OperationError::NoMatchingEntries) => return Ok(()),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut all_account_modlist = Vec::with_capacity(3);
|
||||||
|
|
||||||
|
all_account_modlist.push(Modify::Present(
|
||||||
|
Attribute::Class.into(),
|
||||||
|
EntryClass::AccountPolicy.to_value(),
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Some(auth_exp) = sysconfig_entry.get_ava_single_uint32(Attribute::AuthSessionExpiry)
|
||||||
|
{
|
||||||
|
all_account_modlist.push(Modify::Present(
|
||||||
|
Attribute::AuthSessionExpiry.into(),
|
||||||
|
Value::Uint32(auth_exp),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(priv_exp) = sysconfig_entry.get_ava_single_uint32(Attribute::PrivilegeExpiry) {
|
||||||
|
all_account_modlist.push(Modify::Present(
|
||||||
|
Attribute::PrivilegeExpiry.into(),
|
||||||
|
Value::Uint32(priv_exp),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.internal_batch_modify(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
UUID_SYSTEM_CONFIG,
|
||||||
|
ModifyList::new_list(vec![
|
||||||
|
Modify::Purged(Attribute::AuthSessionExpiry.into()),
|
||||||
|
Modify::Purged(Attribute::PrivilegeExpiry.into()),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
UUID_IDM_ALL_ACCOUNTS,
|
||||||
|
ModifyList::new_list(all_account_modlist),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
// Complete
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
|
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
|
||||||
admin_debug!("initialise_schema_core -> start ...");
|
admin_debug!("initialise_schema_core -> start ...");
|
||||||
|
@ -565,6 +620,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
|
|
||||||
let idm_schema_classes: Vec<EntryInitNew> = vec![
|
let idm_schema_classes: Vec<EntryInitNew> = vec![
|
||||||
SCHEMA_CLASS_ACCOUNT.clone().into(),
|
SCHEMA_CLASS_ACCOUNT.clone().into(),
|
||||||
|
SCHEMA_CLASS_ACCOUNT_POLICY.clone().into(),
|
||||||
SCHEMA_CLASS_DOMAIN_INFO.clone().into(),
|
SCHEMA_CLASS_DOMAIN_INFO.clone().into(),
|
||||||
SCHEMA_CLASS_DYNGROUP.clone().into(),
|
SCHEMA_CLASS_DYNGROUP.clone().into(),
|
||||||
SCHEMA_CLASS_GROUP.clone().into(),
|
SCHEMA_CLASS_GROUP.clone().into(),
|
||||||
|
@ -683,6 +739,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
E_IDM_HP_ACP_SYNC_ACCOUNT_MANAGE_PRIV_V1.clone(),
|
E_IDM_HP_ACP_SYNC_ACCOUNT_MANAGE_PRIV_V1.clone(),
|
||||||
IDM_ACP_ACCOUNT_MAIL_READ_PRIV_V1.clone(),
|
IDM_ACP_ACCOUNT_MAIL_READ_PRIV_V1.clone(),
|
||||||
IDM_ACCOUNT_SELF_ACP_WRITE_V1.clone(),
|
IDM_ACCOUNT_SELF_ACP_WRITE_V1.clone(),
|
||||||
|
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_PRIV_V1.clone(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let res: Result<(), _> = idm_entries
|
let res: Result<(), _> = idm_entries
|
||||||
|
@ -710,6 +767,9 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
debug_assert!(res.is_ok());
|
debug_assert!(res.is_ok());
|
||||||
res?;
|
res?;
|
||||||
|
|
||||||
|
// Some attributes we don't want to stomp if they already exist. So we conditionally
|
||||||
|
// modify them.
|
||||||
|
|
||||||
self.changed_schema = true;
|
self.changed_schema = true;
|
||||||
self.changed_acp = true;
|
self.changed_acp = true;
|
||||||
|
|
||||||
|
|
|
@ -869,42 +869,6 @@ pub trait QueryServerTransaction<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_authsession_expiry(&mut self) -> Result<u32, OperationError> {
|
|
||||||
self.internal_search_uuid(UUID_SYSTEM_CONFIG)
|
|
||||||
.and_then(|e| {
|
|
||||||
if let Some(expiry_time) = e.get_ava_single_uint32(Attribute::AuthSessionExpiry) {
|
|
||||||
Ok(expiry_time)
|
|
||||||
} else {
|
|
||||||
Err(OperationError::NoMatchingAttributes)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
admin_error!(
|
|
||||||
?e,
|
|
||||||
"Failed to retrieve authsession_expiry from system configuration"
|
|
||||||
);
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_privilege_expiry(&mut self) -> Result<u32, OperationError> {
|
|
||||||
self.internal_search_uuid(UUID_SYSTEM_CONFIG)
|
|
||||||
.and_then(|e| {
|
|
||||||
if let Some(expiry_time) = e.get_ava_single_uint32(Attribute::PrivilegeExpiry) {
|
|
||||||
Ok(expiry_time)
|
|
||||||
} else {
|
|
||||||
Err(OperationError::NoMatchingAttributes)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
admin_error!(
|
|
||||||
?e,
|
|
||||||
"Failed to retrieve privilege_expiry from system configuration"
|
|
||||||
);
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_oauth2rs_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
|
fn get_oauth2rs_set(&mut self) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
|
||||||
self.internal_search(filter!(f_eq(
|
self.internal_search(filter!(f_eq(
|
||||||
Attribute::Class,
|
Attribute::Class,
|
||||||
|
@ -1681,10 +1645,6 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
self.changed_domain
|
self.changed_domain
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_changed_system_config(&self) -> bool {
|
|
||||||
self.changed_system_config
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_phase(&mut self, phase: ServerPhase) {
|
fn set_phase(&mut self, phase: ServerPhase) {
|
||||||
*self.phase = phase
|
*self.phase = phase
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = { workspace = true }
|
proc-macro2 = { workspace = true }
|
||||||
|
|
|
@ -14,6 +14,8 @@ repository = { workspace = true }
|
||||||
[lib]
|
[lib]
|
||||||
name = "kanidmd_testkit"
|
name = "kanidmd_testkit"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -1767,37 +1767,6 @@ async fn test_server_user_auth_reauthentication(rsclient: KanidmClient) {
|
||||||
assert!(uat.purpose_readwrite_active(now));
|
assert!(uat.purpose_readwrite_active(now));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
|
||||||
async fn test_authsession_expiry(rsclient: KanidmClient) {
|
|
||||||
let res = rsclient
|
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
||||||
.await;
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let authsession_expiry = 2878_u32;
|
|
||||||
rsclient
|
|
||||||
.system_authsession_expiry_set(authsession_expiry)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let result = rsclient.system_authsession_expiry_get().await.unwrap();
|
|
||||||
assert_eq!(authsession_expiry, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
|
||||||
async fn test_privilege_expiry(rsclient: KanidmClient) {
|
|
||||||
let res = rsclient
|
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
|
||||||
.await;
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let authsession_expiry = 2878_u32;
|
|
||||||
|
|
||||||
rsclient
|
|
||||||
.system_auth_privilege_expiry_set(authsession_expiry)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let result = rsclient.system_auth_privilege_expiry_get().await.unwrap();
|
|
||||||
assert_eq!(authsession_expiry, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn start_password_session(
|
async fn start_password_session(
|
||||||
rsclient: &KanidmClient,
|
rsclient: &KanidmClient,
|
||||||
username: &str,
|
username: &str,
|
||||||
|
|
|
@ -16,6 +16,8 @@ repository = "https://github.com/kanidm/kanidm/"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gloo = { workspace = true }
|
gloo = { workspace = true }
|
||||||
|
|
|
@ -19,15 +19,21 @@ unix = []
|
||||||
[lib]
|
[lib]
|
||||||
name = "kanidm_cli"
|
name = "kanidm_cli"
|
||||||
path = "src/cli/lib.rs"
|
path = "src/cli/lib.rs"
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kanidm"
|
name = "kanidm"
|
||||||
path = "src/cli/main.rs"
|
path = "src/cli/main.rs"
|
||||||
doc = false
|
doc = false
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kanidm_ssh_authorizedkeys_direct"
|
name = "kanidm_ssh_authorizedkeys_direct"
|
||||||
path = "src/ssh_authorizedkeys.rs"
|
path = "src/ssh_authorizedkeys.rs"
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-recursion = { workspace = true }
|
async-recursion = { workspace = true }
|
||||||
|
|
|
@ -361,6 +361,7 @@ pub fn prompt_for_username_get_username() -> Result<String, String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// This parses the token store and prompts the user to select their username, returns the token as a String
|
/// This parses the token store and prompts the user to select their username, returns the token as a String
|
||||||
///
|
///
|
||||||
/// Powered by [prompt_for_username_get_values]
|
/// Powered by [prompt_for_username_get_values]
|
||||||
|
@ -373,3 +374,4 @@ pub fn prompt_for_username_get_token() -> Result<String, String> {
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
47
tools/cli/src/cli/group/account_policy.rs
Normal file
47
tools/cli/src/cli/group/account_policy.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use crate::common::OpType;
|
||||||
|
use crate::{handle_client_error, GroupAccountPolicyOpt};
|
||||||
|
|
||||||
|
impl GroupAccountPolicyOpt {
|
||||||
|
pub fn debug(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
GroupAccountPolicyOpt::Enable { copt, .. }
|
||||||
|
| GroupAccountPolicyOpt::AuthSessionExpiry { copt, .. }
|
||||||
|
| GroupAccountPolicyOpt::PrivilegedSessionExpiry { copt, .. } => copt.debug,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn exec(&self) {
|
||||||
|
match self {
|
||||||
|
GroupAccountPolicyOpt::Enable { name, copt } => {
|
||||||
|
let client = copt.to_client(OpType::Write).await;
|
||||||
|
if let Err(e) = client.group_account_policy_enable(&name).await {
|
||||||
|
handle_client_error(e, &copt.output_mode);
|
||||||
|
} else {
|
||||||
|
println!("Group enabled for account policy.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GroupAccountPolicyOpt::AuthSessionExpiry { name, expiry, copt } => {
|
||||||
|
let client = copt.to_client(OpType::Write).await;
|
||||||
|
if let Err(e) = client
|
||||||
|
.group_account_policy_authsession_expiry_set(&name, *expiry)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
handle_client_error(e, &copt.output_mode);
|
||||||
|
} else {
|
||||||
|
println!("Updated authsession expiry.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GroupAccountPolicyOpt::PrivilegedSessionExpiry { name, expiry, copt } => {
|
||||||
|
let client = copt.to_client(OpType::Write).await;
|
||||||
|
if let Err(e) = client
|
||||||
|
.group_account_policy_privilege_expiry_set(&name, *expiry)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
handle_client_error(e, &copt.output_mode);
|
||||||
|
} else {
|
||||||
|
println!("Updated authsession expiry.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::common::OpType;
|
use crate::common::OpType;
|
||||||
use crate::{handle_client_error, GroupOpt, GroupPosix, OutputMode};
|
use crate::{handle_client_error, GroupOpt, GroupPosix, OutputMode};
|
||||||
|
|
||||||
|
mod account_policy;
|
||||||
|
|
||||||
impl GroupOpt {
|
impl GroupOpt {
|
||||||
pub fn debug(&self) -> bool {
|
pub fn debug(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
@ -17,6 +19,7 @@ impl GroupOpt {
|
||||||
GroupPosix::Show(gcopt) => gcopt.copt.debug,
|
GroupPosix::Show(gcopt) => gcopt.copt.debug,
|
||||||
GroupPosix::Set(gcopt) => gcopt.copt.debug,
|
GroupPosix::Set(gcopt) => gcopt.copt.debug,
|
||||||
},
|
},
|
||||||
|
GroupOpt::AccountPolicy { commands } => commands.debug(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +159,7 @@ impl GroupOpt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
GroupOpt::AccountPolicy { commands } => commands.exec().await,
|
||||||
} // end match
|
} // end match
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,19 +27,19 @@ use uuid::Uuid;
|
||||||
|
|
||||||
include!("../opt/kanidm.rs");
|
include!("../opt/kanidm.rs");
|
||||||
|
|
||||||
pub mod common;
|
mod common;
|
||||||
pub mod domain;
|
mod domain;
|
||||||
pub mod group;
|
mod group;
|
||||||
#[cfg(feature = "idv-tui")]
|
#[cfg(feature = "idv-tui")]
|
||||||
mod identify_user_tui;
|
mod identify_user_tui;
|
||||||
pub mod oauth2;
|
mod oauth2;
|
||||||
pub mod person;
|
mod person;
|
||||||
pub mod raw;
|
mod raw;
|
||||||
pub mod recycle;
|
mod recycle;
|
||||||
pub mod serviceaccount;
|
mod serviceaccount;
|
||||||
pub mod session;
|
mod session;
|
||||||
pub mod synch;
|
mod synch;
|
||||||
pub mod system_config;
|
mod system_config;
|
||||||
mod webauthn;
|
mod webauthn;
|
||||||
|
|
||||||
/// Throws an error and exits the program when we get an error
|
/// Throws an error and exits the program when we get an error
|
||||||
|
@ -148,8 +148,6 @@ impl SystemOpt {
|
||||||
SystemOpt::Oauth2 { commands } => commands.debug(),
|
SystemOpt::Oauth2 { commands } => commands.debug(),
|
||||||
SystemOpt::Domain { commands } => commands.debug(),
|
SystemOpt::Domain { commands } => commands.debug(),
|
||||||
SystemOpt::Synch { commands } => commands.debug(),
|
SystemOpt::Synch { commands } => commands.debug(),
|
||||||
SystemOpt::AuthSessionExpiry { commands } => commands.debug(),
|
|
||||||
SystemOpt::PrivilegedSessionExpiry { commands } => commands.debug(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,8 +158,6 @@ impl SystemOpt {
|
||||||
SystemOpt::Oauth2 { commands } => commands.exec().await,
|
SystemOpt::Oauth2 { commands } => commands.exec().await,
|
||||||
SystemOpt::Domain { commands } => commands.exec().await,
|
SystemOpt::Domain { commands } => commands.exec().await,
|
||||||
SystemOpt::Synch { commands } => commands.exec().await,
|
SystemOpt::Synch { commands } => commands.exec().await,
|
||||||
SystemOpt::AuthSessionExpiry { commands } => commands.exec().await,
|
|
||||||
SystemOpt::PrivilegedSessionExpiry { commands } => commands.exec().await,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod badlist;
|
pub mod badlist;
|
||||||
pub mod denied_names;
|
pub mod denied_names;
|
||||||
pub mod session_expiry;
|
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
use crate::common::OpType;
|
|
||||||
|
|
||||||
use crate::{handle_client_error, AuthSessionExpiryOpt, PrivilegedSessionExpiryOpt};
|
|
||||||
|
|
||||||
impl AuthSessionExpiryOpt {
|
|
||||||
pub fn debug(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
AuthSessionExpiryOpt::Get(copt) => copt.debug,
|
|
||||||
AuthSessionExpiryOpt::Set { copt, .. } => copt.debug,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn exec(&self) {
|
|
||||||
match self {
|
|
||||||
AuthSessionExpiryOpt::Get(copt) => {
|
|
||||||
let client = copt.to_client(OpType::Read).await;
|
|
||||||
match client.system_authsession_expiry_get().await {
|
|
||||||
Ok(exp_time) => {
|
|
||||||
println!(
|
|
||||||
"The current system auth session expiry time is: {exp_time} seconds."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => handle_client_error(e, &copt.output_mode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AuthSessionExpiryOpt::Set { copt, expiry } => {
|
|
||||||
let client = copt.to_client(OpType::Write).await;
|
|
||||||
match client.system_authsession_expiry_set(*expiry).await {
|
|
||||||
Ok(()) => {
|
|
||||||
println!("The system auth session expiry has been successfully updated.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) => handle_client_error(e, &copt.output_mode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrivilegedSessionExpiryOpt {
|
|
||||||
pub fn debug(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
PrivilegedSessionExpiryOpt::Get(copt) => copt.debug,
|
|
||||||
PrivilegedSessionExpiryOpt::Set { copt, .. } => copt.debug,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn exec(&self) {
|
|
||||||
match self {
|
|
||||||
PrivilegedSessionExpiryOpt::Get(copt) => {
|
|
||||||
let client = copt.to_client(OpType::Read).await;
|
|
||||||
match client.system_auth_privilege_expiry_get().await {
|
|
||||||
Ok(exp_time) => {
|
|
||||||
println!(
|
|
||||||
"The current system auth privilege expiry time is: {exp_time} seconds."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => handle_client_error(e, &copt.output_mode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PrivilegedSessionExpiryOpt::Set { copt, expiry } => {
|
|
||||||
let client = copt.to_client(OpType::Write).await;
|
|
||||||
match client.system_auth_privilege_expiry_set(*expiry).await {
|
|
||||||
Ok(()) => {
|
|
||||||
println!("The system auth privilege expiry has been successfully updated.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) => handle_client_error(e, &copt.output_mode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -86,6 +86,34 @@ pub enum GroupPosix {
|
||||||
Set(GroupPosixOpt),
|
Set(GroupPosixOpt),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum GroupAccountPolicyOpt {
|
||||||
|
/// Enable account policy for this group
|
||||||
|
#[clap(name = "enable")]
|
||||||
|
Enable {
|
||||||
|
name: String,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
},
|
||||||
|
/// Set the maximum time for session expiry
|
||||||
|
#[clap(name = "auth-expiry")]
|
||||||
|
AuthSessionExpiry {
|
||||||
|
name: String,
|
||||||
|
expiry: u32,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
},
|
||||||
|
/// Configure and display the privilege session expiry
|
||||||
|
/// Set the maximum time for privilege session expiry
|
||||||
|
#[clap(name = "privilege-expiry")]
|
||||||
|
PrivilegedSessionExpiry {
|
||||||
|
name: String,
|
||||||
|
expiry: u32,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum GroupOpt {
|
pub enum GroupOpt {
|
||||||
/// List all groups
|
/// List all groups
|
||||||
|
@ -122,6 +150,12 @@ pub enum GroupOpt {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
commands: GroupPosix,
|
commands: GroupPosix,
|
||||||
},
|
},
|
||||||
|
/// Manage the policies that apply to members of this group.
|
||||||
|
#[clap(name = "account-policy")]
|
||||||
|
AccountPolicy {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
commands: GroupAccountPolicyOpt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
|
@ -984,18 +1018,6 @@ pub enum SystemOpt {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
commands: DeniedNamesOpt,
|
commands: DeniedNamesOpt,
|
||||||
},
|
},
|
||||||
/// Configure and display the system auth session expiry
|
|
||||||
#[clap(name = "auth-expiry")]
|
|
||||||
AuthSessionExpiry {
|
|
||||||
#[clap(subcommand)]
|
|
||||||
commands: AuthSessionExpiryOpt,
|
|
||||||
},
|
|
||||||
/// Configure and display the system auth privilege session expiry
|
|
||||||
#[clap(name = "privilege-expiry")]
|
|
||||||
PrivilegedSessionExpiry {
|
|
||||||
#[clap(subcommand)]
|
|
||||||
commands: PrivilegedSessionExpiryOpt,
|
|
||||||
},
|
|
||||||
#[clap(name = "oauth2")]
|
#[clap(name = "oauth2")]
|
||||||
/// Configure and display oauth2/oidc resource server configuration
|
/// Configure and display oauth2/oidc resource server configuration
|
||||||
Oauth2 {
|
Oauth2 {
|
||||||
|
|
|
@ -14,6 +14,8 @@ repository = { workspace = true }
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "orca"
|
name = "orca"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
|
|
@ -21,25 +21,35 @@ tpm = ["dep:tss-esapi", "kanidm_lib_crypto/tpm"]
|
||||||
name = "kanidm_unixd"
|
name = "kanidm_unixd"
|
||||||
path = "src/daemon.rs"
|
path = "src/daemon.rs"
|
||||||
required-features = ["unix"]
|
required-features = ["unix"]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kanidm_unixd_tasks"
|
name = "kanidm_unixd_tasks"
|
||||||
path = "src/tasks_daemon.rs"
|
path = "src/tasks_daemon.rs"
|
||||||
required-features = ["unix"]
|
required-features = ["unix"]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kanidm_ssh_authorizedkeys"
|
name = "kanidm_ssh_authorizedkeys"
|
||||||
path = "src/ssh_authorizedkeys.rs"
|
path = "src/ssh_authorizedkeys.rs"
|
||||||
required-features = ["unix"]
|
required-features = ["unix"]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kanidm-unix"
|
name = "kanidm-unix"
|
||||||
path = "src/tool.rs"
|
path = "src/tool.rs"
|
||||||
required-features = ["unix"]
|
required-features = ["unix"]
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "kanidm_unix_common"
|
name = "kanidm_unix_common"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
test = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
|
Loading…
Reference in a new issue