Configurable session timeouts (#1965)

* added `auth_session_expiry` and `auth_privilege_expiry`
* Added `AcountPolicy` struct
* spelling and stuff
* added cli tools
This commit is contained in:
Sebastiano Tocci 2023-08-22 03:00:43 +02:00 committed by GitHub
parent 17e4ad52f8
commit eb7527379b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 738 additions and 146 deletions

93
Cargo.lock generated
View file

@ -155,7 +155,7 @@ dependencies = [
"num-traits",
"rusticata-macros",
"thiserror",
"time 0.3.24",
"time 0.3.25",
]
[[package]]
@ -426,11 +426,11 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.65.1"
version = "0.66.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5"
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.3.3",
"cexpr",
"clang-sys",
"lazy_static",
@ -563,11 +563,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.79"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"jobserver",
"libc",
]
[[package]]
@ -771,7 +772,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
dependencies = [
"percent-encoding",
"time 0.3.24",
"time 0.3.25",
"version_check",
]
@ -788,7 +789,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"time 0.3.24",
"time 0.3.25",
"url",
]
@ -1088,9 +1089,9 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01"
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
dependencies = [
"serde",
]
@ -1343,7 +1344,7 @@ dependencies = [
"mime",
"serde",
"serde_json",
"time 0.3.24",
"time 0.3.25",
"tokio",
"url",
"webdriver",
@ -2227,7 +2228,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"time 0.3.24",
"time 0.3.25",
"tokio",
"toml",
"tracing",
@ -2273,7 +2274,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with",
"time 0.3.24",
"time 0.3.25",
"tracing",
"url",
"urlencoding",
@ -2300,7 +2301,7 @@ dependencies = [
"serde",
"serde_json",
"shellexpand",
"time 0.3.24",
"time 0.3.25",
"tokio",
"tracing",
"tracing-subscriber",
@ -2386,7 +2387,7 @@ dependencies = [
"serde_json",
"serde_with",
"sketching",
"time 0.3.24",
"time 0.3.25",
"tokio",
"tokio-openssl",
"tokio-util",
@ -2439,7 +2440,7 @@ dependencies = [
"smartstring",
"smolset",
"sshkeys",
"time 0.3.24",
"time 0.3.25",
"tokio",
"tokio-util",
"toml",
@ -2484,7 +2485,7 @@ dependencies = [
"serde_json",
"sketching",
"testkit-macros",
"time 0.3.24",
"time 0.3.25",
"tokio",
"tracing",
"url",
@ -2505,7 +2506,7 @@ dependencies = [
"serde",
"serde-wasm-bindgen 0.5.0",
"serde_json",
"time 0.3.24",
"time 0.3.25",
"url",
"uuid",
"wasm-bindgen",
@ -2519,9 +2520,9 @@ dependencies = [
[[package]]
name = "kqueue"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
@ -2529,9 +2530,9 @@ dependencies = [
[[package]]
name = "kqueue-sys"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
@ -2750,9 +2751,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "matchit"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67827e6ea8ee8a7c4a72227ef4fc08957040acffdb5f122733b24fa12daff41b"
checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef"
[[package]]
name = "mathru"
@ -3310,9 +3311,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pest"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5"
checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a"
dependencies = [
"thiserror",
"ucd-trie",
@ -3355,18 +3356,18 @@ dependencies = [
[[package]]
name = "pin-project"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
@ -3824,9 +3825,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.4"
version = "0.38.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399"
dependencies = [
"bitflags 2.3.3",
"errno",
@ -3875,7 +3876,7 @@ dependencies = [
"peg",
"serde",
"serde_json",
"time 0.3.24",
"time 0.3.25",
"tracing",
"tracing-subscriber",
"url",
@ -3939,9 +3940,9 @@ dependencies = [
[[package]]
name = "selinux-sys"
version = "0.6.5"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b8dbf5dd0b21d466538786194081b2c4d61878e1427f7e52f99d5845432483"
checksum = "d56602385930248c57e45f6174a6a48e12b723d0cc2ae8f467fcbe80c0d06f41"
dependencies = [
"bindgen",
"cc",
@ -4085,7 +4086,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with_macros",
"time 0.3.24",
"time 0.3.25",
]
[[package]]
@ -4331,9 +4332,9 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
[[package]]
name = "tempfile"
version = "3.7.0"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998"
checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
dependencies = [
"cfg-if",
"fastrand",
@ -4415,9 +4416,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b"
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
dependencies = [
"deranged",
"itoa",
@ -4719,7 +4720,7 @@ dependencies = [
"sharded-slab",
"smallvec",
"thread_local",
"time 0.3.24",
"time 0.3.25",
"tracing",
"tracing-core",
"tracing-log",
@ -5113,7 +5114,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"time 0.3.24",
"time 0.3.25",
"unicode-segmentation",
"url",
]
@ -5370,9 +5371,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.5.2"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7"
checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64"
dependencies = [
"memchr",
]
@ -5401,7 +5402,7 @@ dependencies = [
"oid-registry",
"rusticata-macros",
"thiserror",
"time 0.3.24",
"time 0.3.25",
]
[[package]]
@ -5536,5 +5537,5 @@ dependencies = [
"lazy_static",
"quick-error",
"regex",
"time 0.3.24",
"time 0.3.25",
]

View file

@ -61,6 +61,7 @@ pub enum ClientError {
TotpVerifyFailed(Uuid, TotpSecret),
TotpInvalidSha1(Uuid),
JsonDecode(reqwest::Error, String),
InvalidResponseFormat(String),
JsonEncode(SerdeJsonError),
SystemError,
ConfigParseIssue(String),

View file

@ -23,4 +23,40 @@ impl KanidmClient {
self.perform_delete_request_with_body("/v1/system/_attr/badlist_password", list)
.await
}
pub async fn system_authsession_expiry_get(&self) -> Result<u32, ClientError> {
let list: Option<[String; 1]> = self
.perform_get_request("/v1/system/_attr/authsession_expiry")
.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
}
}

View file

@ -24,6 +24,7 @@ pub const ATTR_ACP_TARGET_SCOPE: &str = "acp_targetscope";
pub const ATTR_API_TOKEN_SESSION: &str = "api_token_session";
pub const ATTR_ATTR: &str = "attr";
pub const ATTR_ATTRIBUTENAME: &str = "attributename";
pub const ATTR_AUTH_SESSION_EXPIRY: &str = "authsession_expiry";
pub const ATTR_BADLIST_PASSWORD: &str = "badlist_password";
pub const ATTR_CLAIM: &str = "claim";
pub const ATTR_CLASS: &str = "class";
@ -91,6 +92,7 @@ pub const ATTR_PASSWORD_IMPORT: &str = "password_import";
pub const ATTR_PHANTOM: &str = "phantom";
pub const ATTR_PRIMARY_CREDENTIAL: &str = "primary_credential";
pub const ATTR_PRIVATE_COOKIE_KEY: &str = "private_cookie_key";
pub const ATTR_PRIVILEGE_EXPIRY: &str = "privilege_expiry";
pub const ATTR_RADIUS_SECRET: &str = "radius_secret";
pub const ATTR_RECYCLED: &str = "recycled";
pub const ATTR_REPLICATED: &str = "replicated";

View file

@ -1219,6 +1219,24 @@ pub async fn system_delete_attr(
.await
}
pub async fn system_put_attr(
State(state): State<ServerState>,
Path(attr): Path<String>,
Extension(kopid): Extension<KOpId>,
Json(values): Json<Vec<String>>,
) -> impl IntoResponse {
let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SystemConfig.into()));
json_rest_event_put_attr(
state,
STR_UUID_SYSTEM_CONFIG.to_string(),
attr,
filter,
values,
kopid,
)
.await
}
pub async fn recycle_bin_get(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
@ -1661,6 +1679,7 @@ pub fn router(state: ServerState) -> Router<ServerState> {
"/v1/system/_attr/:attr",
get(system_get_attr)
.post(system_post_attr)
.put(system_put_attr)
.delete(system_delete_attr),
)
.route("/v1/recycle_bin", get(recycle_bin_get))

View file

@ -341,7 +341,6 @@ async fn main() -> ExitCode {
config.update_output_mode(opt.commands.commonopt().output_mode.to_owned().into());
config.update_trust_x_forward_for(sconfig.trust_x_forward_for);
config.update_admin_bind_path(&sconfig.adminbindpath);
match &opt.commands {
// we aren't going to touch the DB so we can carry on
KanidmdOpt::HealthCheck(_) => (),

View file

@ -1447,6 +1447,43 @@ lazy_static! {
);
}
lazy_static! {
pub static ref E_IDM_ACP_SYSTEM_CONFIG_SESSION_EXP_PRIV_V1: EntryInitNew = entry_init!(
(ATTR_CLASS, EntryClass::Object.to_value()),
(ATTR_CLASS, EntryClass::AccessControlProfile.to_value()),
(ATTR_CLASS, EntryClass::AccessControlModify.to_value()),
(ATTR_CLASS, EntryClass::AccessControlSearch.to_value()),
(ATTR_NAME, Value::new_iname("idm_acp_system_config_session_exp_priv")),
(ATTR_UUID, Value::Uuid(UUID_IDM_ACP_SYSTEM_CONFIG_SESSION_EXP_PRIV_V1)),
(
Attribute::Description.as_ref(),
Value::new_utf8s("Builtin IDM Control for granting session expiry configuration rights")
),
(
ATTR_ACP_RECEIVER_GROUP,
Value::Refer(UUID_SYSTEM_ADMINS)
),
(
ATTR_ACP_TARGET_SCOPE,
Value::new_json_filter_s(
"{\"and\": [{\"eq\": [\"uuid\",\"00000000-0000-0000-0000-ffffff000027\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
)
.expect("Invalid JSON filter")
),
(ATTR_ACP_SEARCH_ATTR, Value::new_iutf8("class")),
(ATTR_ACP_SEARCH_ATTR, Value::new_iutf8("name")),
(ATTR_ACP_SEARCH_ATTR, Value::new_iutf8("uuid")),
(ATTR_ACP_SEARCH_ATTR, Value::new_iutf8("description")),
(ATTR_ACP_SEARCH_ATTR, Attribute::AuthSessionExpiry.to_value()),
(ATTR_ACP_MODIFY_PRESENTATTR, Attribute::AuthSessionExpiry.to_value()),
(ATTR_ACP_MODIFY_REMOVEDATTR, Attribute::AuthSessionExpiry.to_value()),
(ATTR_ACP_SEARCH_ATTR, Attribute::PrivilegeExpiry.to_value()),
(ATTR_ACP_MODIFY_PRESENTATTR, Attribute::PrivilegeExpiry.to_value()),
(ATTR_ACP_MODIFY_REMOVEDATTR, Attribute::PrivilegeExpiry.to_value())
);
}
lazy_static! {
pub static ref E_IDM_ACP_ACCOUNT_UNIX_EXTEND_PRIV_V1: EntryInitNew = entry_init!(
(ATTR_CLASS, EntryClass::Object.to_value()),

View file

@ -50,6 +50,7 @@ pub enum Attribute {
ApiTokenSession,
Attr,
AttributeName,
AuthSessionExpiry,
BadlistPassword,
Claim,
Class,
@ -115,6 +116,7 @@ pub enum Attribute {
Phantom,
PrimaryCredential,
PrivateCookieKey,
PrivilegeExpiry,
RadiusSecret,
Replicated,
Rs256PrivateKeyDer,
@ -189,6 +191,7 @@ impl TryFrom<String> for Attribute {
ATTR_API_TOKEN_SESSION => Attribute::ApiTokenSession,
ATTR_ATTR => Attribute::Attr,
ATTR_ATTRIBUTENAME => Attribute::AttributeName,
ATTR_AUTH_SESSION_EXPIRY => Attribute::AuthSessionExpiry,
ATTR_BADLIST_PASSWORD => Attribute::BadlistPassword,
ATTR_CLAIM => Attribute::Claim,
ATTR_CLASS => Attribute::Class,
@ -256,6 +259,7 @@ impl TryFrom<String> for Attribute {
ATTR_PHANTOM => Attribute::Phantom,
ATTR_PRIMARY_CREDENTIAL => Attribute::PrimaryCredential,
ATTR_PRIVATE_COOKIE_KEY => Attribute::PrivateCookieKey,
ATTR_PRIVILEGE_EXPIRY => Attribute::PrivilegeExpiry,
ATTR_RADIUS_SECRET => Attribute::RadiusSecret,
ATTR_REPLICATED => Attribute::Replicated,
ATTR_RS256_PRIVATE_KEY_DER => Attribute::Rs256PrivateKeyDer,
@ -416,6 +420,8 @@ impl From<Attribute> for &'static str {
Attribute::TestAttr => TEST_ATTR_TEST_ATTR,
#[cfg(any(debug_assertions, test))]
Attribute::Extra => TEST_ATTR_EXTRA,
Attribute::AuthSessionExpiry => ATTR_AUTH_SESSION_EXPIRY,
Attribute::PrivilegeExpiry => ATTR_PRIVILEGE_EXPIRY,
}
}
}

View file

@ -77,9 +77,9 @@ pub const MFAREG_SESSION_TIMEOUT: u64 = 300;
pub const PW_MIN_LENGTH: usize = 10;
// Default - sessions last for 1 hour.
pub const AUTH_SESSION_EXPIRY: u64 = 3600;
pub const DEFAULT_AUTH_SESSION_EXPIRY: u32 = 3600;
// Default - privileges last for 10 minutes.
pub const AUTH_PRIVILEGE_EXPIRY: u64 = 600;
pub const DEFAULT_AUTH_PRIVILEGE_EXPIRY: u32 = 600;
// Default - oauth refresh tokens last for 16 hours.
pub const OAUTH_REFRESH_TOKEN_EXPIRY: u64 = 3600 * 8;

View file

@ -193,6 +193,24 @@ pub static ref SCHEMA_ATTR_BADLIST_PASSWORD: SchemaAttribute = SchemaAttribute {
..Default::default()
};
pub static ref SCHEMA_ATTR_AUTH_SESSION_EXPIRY: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_AUTH_SESSION_EXPIRY,
name: Attribute::AuthSessionExpiry.into(),
description: "An expiration time for an authentication session.".to_string(),
syntax: SyntaxType::Uint32,
..Default::default()
};
pub static ref SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY,
name: Attribute::PrivilegeExpiry.into(),
description: "An expiration time for a privileged authentication session.".to_string(),
syntax: SyntaxType::Uint32,
..Default::default()
};
pub static ref SCHEMA_ATTR_LOGINSHELL: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_LOGINSHELL,
name: Attribute::LoginShell.into(),
@ -714,6 +732,8 @@ pub static ref SCHEMA_CLASS_SYSTEM_CONFIG: SchemaClass = SchemaClass {
systemmay: vec![
Attribute::Description.into(),
Attribute::BadlistPassword.into(),
Attribute::AuthSessionExpiry.into(),
Attribute::PrivilegeExpiry.into()
],
..Default::default()
};

View file

@ -2,6 +2,7 @@ use crate::constants::uuids::*;
use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew};
use crate::prelude::{Attribute, EntryClass};
use crate::prelude::{DEFAULT_AUTH_PRIVILEGE_EXPIRY, DEFAULT_AUTH_SESSION_EXPIRY};
use crate::value::Value;
// Default entries for system_config
@ -30,6 +31,14 @@ lazy_static! {
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1"
)
),
(
Attribute::AuthSessionExpiry.as_ref(),
Value::Uint32(DEFAULT_AUTH_SESSION_EXPIRY)
),
(
Attribute::PrivilegeExpiry.as_ref(),
Value::Uint32(DEFAULT_AUTH_PRIVILEGE_EXPIRY)
),
(
Attribute::BadlistPassword.as_ref(),
Value::new_iutf8("100preteamare")

View file

@ -233,6 +233,10 @@ pub const UUID_SCHEMA_ATTR_SYNC_YIELD_AUTHORITY: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000138");
pub const UUID_SCHEMA_CLASS_CONFLICT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000139");
pub const UUID_SCHEMA_ATTR_SOURCE_UUID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000140");
pub const UUID_SCHEMA_ATTR_AUTH_SESSION_EXPIRY: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000141");
pub const UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000142");
// System and domain infos
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
@ -312,6 +316,8 @@ pub const UUID_IDM_HP_ACP_SYNC_ACCOUNT_MANAGE_PRIV_V1: Uuid =
pub const UUID_IDM_ACP_ACCOUNT_MAIL_READ_PRIV_V1: Uuid =
uuid!("00000000-0000-0000-0000-ffffff000045");
pub const UUID_IDM_ACCOUNT_SELF_ACP_WRITE_V1: Uuid = uuid!("00000000-0000-0000-0000-ffffff000046");
pub const UUID_IDM_ACP_SYSTEM_CONFIG_SESSION_EXP_PRIV_V1: Uuid =
uuid!("00000000-0000-0000-0000-ffffff000047");
// End of system ranges
pub const UUID_DOES_NOT_EXIST: Uuid = uuid!("00000000-0000-0000-0000-fffffffffffe");

View file

@ -219,6 +219,7 @@ impl Account {
session_id: Uuid,
scope: SessionScope,
ct: Duration,
auth_session_expiry: u32,
) -> Option<UserAuthToken> {
// TODO: Apply policy to this expiry time.
// We have to remove the nanoseconds because when we transmit this / serialise it we drop
@ -226,9 +227,8 @@ impl Account {
// ns value which breaks some checks.
let ct = ct - Duration::from_nanos(ct.subsec_nanos() as u64);
let issued_at = OffsetDateTime::UNIX_EPOCH + ct;
let expiry =
Some(OffsetDateTime::UNIX_EPOCH + ct + Duration::from_secs(AUTH_SESSION_EXPIRY));
Some(OffsetDateTime::UNIX_EPOCH + ct + Duration::from_secs(auth_session_expiry as u64));
let (purpose, expiry) = match scope {
// Issue an invalid/expired session.
@ -279,6 +279,7 @@ impl Account {
session_expiry: Option<OffsetDateTime>,
scope: SessionScope,
ct: Duration,
auth_privilege_expiry: u32,
) -> Option<UserAuthToken> {
let issued_at = OffsetDateTime::UNIX_EPOCH + ct;
@ -294,7 +295,9 @@ impl Account {
// Return a ReadWrite session with an inner expiry for the privileges
{
let expiry = Some(
OffsetDateTime::UNIX_EPOCH + ct + Duration::from_secs(AUTH_PRIVILEGE_EXPIRY),
OffsetDateTime::UNIX_EPOCH
+ ct
+ Duration::from_secs(auth_privilege_expiry.into()),
);
(
UatPurpose::ReadWrite { expiry },
@ -327,6 +330,7 @@ impl Account {
expire: Option<&OffsetDateTime>,
) -> bool {
let cot = OffsetDateTime::UNIX_EPOCH + ct;
trace!("Checking within valid time: {:?} {:?}", valid_from, expire);
let vmin = if let Some(vft) = valid_from {
// If current time greater than start time window
@ -539,7 +543,7 @@ impl Account {
) -> bool {
// Remember, token expiry is checked by validate_and_parse_token_to_token.
// If we wanted we could check other properties of the uat here?
// Alternatelly, we could always store LESS in the uat because of this?
// Alternatively, we could always store LESS in the uat because of this?
let within_valid_window = Account::check_within_valid_time(
ct,
@ -554,6 +558,7 @@ impl Account {
// Anonymous does NOT record it's sessions, so we simply check the expiry time
// of the token. This is already done for us as noted above.
trace!("{}", &uat);
if uat.uuid == UUID_ANONYMOUS {
security_debug!("Anonymous sessions do not have session records, session is valid.");
@ -845,7 +850,12 @@ mod tests {
.expect("account must exist");
let session_id = uuid::Uuid::new_v4();
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
// Check the ui hints are as expected.
@ -871,7 +881,12 @@ mod tests {
.expect("account must exist");
let session_id = uuid::Uuid::new_v4();
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_PRIVILEGE_EXPIRY,
)
.expect("Unable to create uat");
assert!(uat.ui_hints.len() == 2);
@ -902,7 +917,12 @@ mod tests {
.expect("account must exist");
let session_id = uuid::Uuid::new_v4();
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
assert!(uat.ui_hints.len() == 3);

View file

@ -37,6 +37,8 @@ use crate::prelude::*;
use crate::value::Session;
use time::OffsetDateTime;
use super::server::AccountPolicy;
// Each CredHandler takes one or more credentials and determines if the
// handlers requirements can be 100% fulfilled. This is where MFA or other
// auth policies would exist, but each credHandler has to be a whole
@ -332,7 +334,7 @@ impl CredHandler {
}
}
/// Validate a singule password credential of the account.
/// Validate a single password credential of the account.
fn validate_password(
cred: &AuthCredential,
cred_id: Uuid,
@ -1005,8 +1007,8 @@ impl AuthSession {
async_tx: &Sender<DelayedAction>,
audit_tx: &Sender<AuditEvent>,
webauthn: &Webauthn,
pw_badlist_set: Option<&HashSet<String>>,
uat_jwt_signer: &JwsSigner,
account_policy: &AccountPolicy,
) -> Result<AuthState, OperationError> {
let (next_state, response) = match &mut self.state {
AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => {
@ -1021,11 +1023,18 @@ impl AuthSession {
self.account.uuid,
async_tx,
webauthn,
pw_badlist_set,
Some(&account_policy.pw_badlist_cache),
) {
CredState::Success { auth_type, cred_id } => {
// Issue the uat based on a set of factors.
let uat = self.issue_uat(&auth_type, time, async_tx, cred_id)?;
let uat = self.issue_uat(
&auth_type,
time,
async_tx,
cred_id,
account_policy.authsession_expiry,
account_policy.privilege_expiry,
)?;
let jwt = Jws::new(uat);
// Now encrypt and prepare the token for return to the client.
@ -1096,6 +1105,8 @@ impl AuthSession {
time: Duration,
async_tx: &Sender<DelayedAction>,
cred_id: Uuid,
auth_session_expiry: u32,
auth_privilege_expiry: u32,
) -> Result<UserAuthToken, OperationError> {
security_debug!("Successful cred handling");
match self.intent {
@ -1121,7 +1132,7 @@ impl AuthSession {
let uat = self
.account
.to_userauthtoken(session_id, scope, time)
.to_userauthtoken(session_id, scope, time, auth_session_expiry)
.ok_or(OperationError::InvalidState)?;
// Queue the session info write.
@ -1181,7 +1192,13 @@ impl AuthSession {
let uat = self
.account
.to_reissue_userauthtoken(session_id, session_expiry, scope, time)
.to_reissue_userauthtoken(
session_id,
session_expiry,
scope,
time,
auth_privilege_expiry,
)
.ok_or(OperationError::InvalidState)?;
Ok(uat)
@ -1230,6 +1247,7 @@ mod tests {
BAD_WEBAUTHN_MSG, PW_BADLIST_MSG,
};
use crate::idm::delayed::DelayedAction;
use crate::idm::server::AccountPolicy;
use crate::idm::AuthState;
use crate::prelude::*;
use crate::utils::{duration_from_epoch_now, readable_password_from_random};
@ -1353,8 +1371,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(_)) => {}
_ => panic!(),
@ -1377,8 +1395,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -1421,8 +1439,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
_ => panic!(),
@ -1545,8 +1563,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1571,8 +1589,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1594,8 +1612,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
_ => panic!(),
@ -1619,8 +1637,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1631,8 +1649,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -1656,8 +1674,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1668,8 +1686,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -1733,8 +1751,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1745,8 +1763,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
_ => panic!(),
@ -1890,8 +1908,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
None,
&jws_signer,
&Default::default(),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1918,8 +1936,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
None,
&jws_signer,
&Default::default(),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -1953,8 +1971,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
None,
&jws_signer,
&Default::default(),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -2002,8 +2020,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
None,
&jws_signer,
&Default::default(),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -2055,8 +2073,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2079,8 +2097,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2114,8 +2132,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -2144,8 +2162,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2156,8 +2174,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2192,8 +2210,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2204,8 +2222,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2273,8 +2291,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2297,8 +2315,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
_ => panic!(),
@ -2330,8 +2348,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -2360,8 +2378,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2372,8 +2390,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2402,8 +2420,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2414,8 +2432,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2438,8 +2456,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2450,8 +2468,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2480,8 +2498,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2492,8 +2510,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2572,8 +2590,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -2595,8 +2613,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_BACKUPCODE_MSG),
_ => panic!(),
@ -2619,8 +2637,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2631,8 +2649,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -2661,8 +2679,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2673,8 +2691,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2705,8 +2723,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2717,8 +2735,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2788,8 +2806,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2800,8 +2818,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),
@ -2824,8 +2842,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache.to_owned()),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -2836,8 +2854,8 @@ mod tests {
&async_tx,
&audit_tx,
&webauthn,
Some(&pw_badlist_cache),
&jws_signer,
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
) {
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
_ => panic!(),

View file

@ -1280,7 +1280,7 @@ impl<'a> IdmServerCredUpdateTransaction<'a> {
// check a password badlist to eliminate more content
// we check the password as "lower case" to help eliminate possibilities
// also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
if (*self.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
if (self.account_policy.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
security_info!("Password found in badlist, rejecting");
Err(PasswordQuality::BadListed)
} else {

View file

@ -2118,7 +2118,12 @@ mod tests {
.target_to_account(UUID_ADMIN)
.expect("account must exist");
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
// Need the uat first for expiry.
@ -2232,7 +2237,12 @@ mod tests {
.target_to_account(UUID_ADMIN)
.expect("account must exist");
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
// Need the uat first for expiry.
@ -2287,7 +2297,12 @@ mod tests {
.expect("account must exist");
let session_id = uuid::Uuid::new_v4();
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(&uat, ct)
@ -2612,7 +2627,12 @@ mod tests {
.expect("account must exist");
let session_id = uuid::Uuid::new_v4();
let uat2 = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident2 = idms_prox_write
.process_uat_to_identity(&uat2, ct)
@ -3212,7 +3232,12 @@ mod tests {
.expect("account must exist");
let session_id = uuid::Uuid::new_v4();
let uat2 = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident2 = idms_prox_write
.process_uat_to_identity(&uat2, ct)

View file

@ -69,6 +69,44 @@ pub struct DomainKeys {
pub(crate) cookie_key: [u8; 64],
}
#[derive(Clone)]
pub struct AccountPolicy {
pub(crate) privilege_expiry: u32,
pub(crate) authsession_expiry: u32,
pub(crate) pw_badlist_cache: HashSet<String>,
}
impl AccountPolicy {
pub fn new(
privilege_expiry: u32,
authsession_expiry: u32,
pw_badlist_cache: HashSet<String>,
) -> Self {
Self {
privilege_expiry,
authsession_expiry,
pw_badlist_cache,
}
}
pub fn from_pw_badlist_cache(pw_badlist_cache: HashSet<String>) -> Self {
Self {
pw_badlist_cache,
..Default::default()
}
}
}
impl Default for AccountPolicy {
fn default() -> Self {
Self {
privilege_expiry: DEFAULT_AUTH_PRIVILEGE_EXPIRY,
authsession_expiry: DEFAULT_AUTH_SESSION_EXPIRY,
pw_badlist_cache: Default::default(), // TODO: more thoughts on this
}
}
}
pub struct IdmServer {
// There is a good reason to keep this single thread - it
// means that limits to sessions can be easily applied and checked to
@ -87,9 +125,9 @@ pub struct IdmServer {
audit_tx: Sender<AuditEvent>,
/// [Webauthn] verifier/config
webauthn: Webauthn,
pw_badlist_cache: Arc<CowCell<HashSet<String>>>,
oauth2rs: Arc<Oauth2ResourceServers>,
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.
@ -105,7 +143,7 @@ pub struct IdmServerAuthTransaction<'a> {
pub(crate) async_tx: Sender<DelayedAction>,
pub(crate) audit_tx: Sender<AuditEvent>,
pub(crate) webauthn: &'a Webauthn,
pub(crate) pw_badlist_cache: CowCellReadTxn<HashSet<String>>,
pub(crate) account_policy: CowCellReadTxn<AccountPolicy>,
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
}
@ -113,7 +151,7 @@ pub struct IdmServerCredUpdateTransaction<'a> {
pub(crate) _qs_read: QueryServerReadTransaction<'a>,
// sid: Sid,
pub(crate) webauthn: &'a Webauthn,
pub(crate) pw_badlist_cache: CowCellReadTxn<HashSet<String>>,
pub(crate) account_policy: CowCellReadTxn<AccountPolicy>,
pub(crate) cred_update_sessions: BptreeMapReadTxn<'a, Uuid, CredentialUpdateSessionMutex>,
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
pub(crate) crypto_policy: &'a CryptoPolicy,
@ -135,7 +173,7 @@ pub struct IdmServerProxyWriteTransaction<'a> {
pub(crate) sid: Sid,
crypto_policy: &'a CryptoPolicy,
webauthn: &'a Webauthn,
pw_badlist_cache: CowCellWriteTxn<'a, HashSet<String>>,
account_policy: CowCellWriteTxn<'a, AccountPolicy>,
pub(crate) domain_keys: CowCellWriteTxn<'a, DomainKeys>,
pub(crate) oauth2rs: Oauth2ResourceServersWriteTransaction<'a>,
}
@ -168,6 +206,8 @@ impl IdmServer {
cookie_key,
pw_badlist_set,
oauth2rs_set,
privilege_expiry,
authsession_expiry,
) = {
let mut qs_read = qs.read().await;
(
@ -179,6 +219,8 @@ impl IdmServer {
qs_read.get_password_badlist()?,
// Add a read/reload of all oauth2 configurations.
qs_read.get_oauth2rs_set()?,
qs_read.get_privilege_expiry()?,
qs_read.get_authsession_expiry()?,
)
};
@ -253,7 +295,11 @@ impl IdmServer {
async_tx,
audit_tx,
webauthn,
pw_badlist_cache: Arc::new(CowCell::new(pw_badlist_set)),
account_policy: Arc::new(CowCell::new(AccountPolicy::new(
privilege_expiry,
authsession_expiry,
pw_badlist_set,
))),
domain_keys,
oauth2rs: Arc::new(oauth2rs),
},
@ -283,7 +329,7 @@ impl IdmServer {
async_tx: self.async_tx.clone(),
audit_tx: self.audit_tx.clone(),
webauthn: &self.webauthn,
pw_badlist_cache: self.pw_badlist_cache.read(),
account_policy: self.account_policy.read(),
domain_keys: self.domain_keys.read(),
}
}
@ -313,7 +359,7 @@ impl IdmServer {
sid,
crypto_policy: &self.crypto_policy,
webauthn: &self.webauthn,
pw_badlist_cache: self.pw_badlist_cache.write(),
account_policy: self.account_policy.write(),
domain_keys: self.domain_keys.write(),
oauth2rs: self.oauth2rs.write(),
}
@ -324,7 +370,7 @@ impl IdmServer {
_qs_read: self.qs.read().await,
// sid: Sid,
webauthn: &self.webauthn,
pw_badlist_cache: self.pw_badlist_cache.read(),
account_policy: self.account_policy.read(),
cred_update_sessions: self.cred_update_sessions.read(),
domain_keys: self.domain_keys.read(),
crypto_policy: &self.crypto_policy,
@ -1120,7 +1166,6 @@ impl<'a> IdmServerAuthTransaction<'a> {
// Process the credentials here as required.
// Basically throw them at the auth_session and see what
// falls out.
let pw_badlist_cache = Some(&(*self.pw_badlist_cache));
auth_session
.validate_creds(
&creds.cred,
@ -1128,8 +1173,8 @@ impl<'a> IdmServerAuthTransaction<'a> {
&self.async_tx,
&self.audit_tx,
self.webauthn,
pw_badlist_cache,
&self.domain_keys.uat_jwt_signer,
&self.account_policy,
)
.map(|aus| {
// Inspect the result:
@ -1563,7 +1608,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// check a password badlist to eliminate more content
// we check the password as "lower case" to help eliminate possibilities
// also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
if (*self.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
if (self.account_policy.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
security_info!("Password found in badlist, rejecting");
Err(OperationError::PasswordQuality(vec![
PasswordFeedback::BadListed,
@ -1979,7 +2024,9 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
.get_changed_uuids()
.contains(&UUID_SYSTEM_CONFIG)
{
self.reload_password_badlist()?;
// TODO: probably it would be more efficient to introduce a single check for each of the possible system configs
// but that would require changing what uuid the operation is assigned
self.reload_system_account_policy()?;
};
if self.qs_write.get_changed_ouath2() {
self.qs_write
@ -2029,20 +2076,17 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// Commit everything.
self.oauth2rs.commit();
self.domain_keys.commit();
self.pw_badlist_cache.commit();
self.account_policy.commit();
self.cred_update_sessions.commit();
trace!("cred_update_session.commit");
self.qs_write.commit()
}
fn reload_password_badlist(&mut self) -> Result<(), OperationError> {
match self.qs_write.get_password_badlist() {
Ok(badlist_entry) => {
*self.pw_badlist_cache = badlist_entry;
Ok(())
}
Err(e) => Err(e),
}
fn reload_system_account_policy(&mut self) -> Result<(), OperationError> {
self.account_policy.pw_badlist_cache = self.qs_write.get_password_badlist()?;
self.account_policy.authsession_expiry = self.qs_write.get_authsession_expiry()?;
self.account_policy.privilege_expiry = self.qs_write.get_privilege_expiry()?;
Ok(())
}
}
@ -2067,7 +2111,7 @@ mod tests {
PasswordChangeEvent, RadiusAuthTokenEvent, RegenerateRadiusSecretEvent,
UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent,
};
use crate::idm::server::{IdmServer, IdmServerTransaction};
use crate::idm::server::{IdmServer, IdmServerTransaction, Token};
use crate::idm::AuthState;
use crate::modify::{Modify, ModifyList};
use crate::prelude::*;
@ -3396,7 +3440,7 @@ mod tests {
#[idm_test]
async fn test_idm_jwt_uat_expiry(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
let ct = Duration::from_secs(TEST_CURRENT_TIME);
let expiry = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1);
let expiry = ct + Duration::from_secs((DEFAULT_AUTH_SESSION_EXPIRY + 1).into());
// Do an authenticate
init_admin_w_password(idms, TEST_PASSWORD)
.await
@ -3431,8 +3475,8 @@ mod tests {
_idms_delayed: &mut IdmServerDelayed,
) {
let ct = Duration::from_secs(TEST_CURRENT_TIME);
let expiry_a = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1);
let expiry_b = ct + Duration::from_secs((AUTH_SESSION_EXPIRY + 1) * 2);
let expiry_a = ct + Duration::from_secs((DEFAULT_AUTH_SESSION_EXPIRY + 1).into());
let expiry_b = ct + Duration::from_secs(((DEFAULT_AUTH_SESSION_EXPIRY + 1) * 2).into());
let session_a = Uuid::new_v4();
let session_b = Uuid::new_v4();
@ -3533,7 +3577,7 @@ mod tests {
let ct = Duration::from_secs(TEST_CURRENT_TIME);
let post_grace = ct + GRACE_WINDOW + Duration::from_secs(1);
let expiry = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1);
let expiry = ct + Duration::from_secs(DEFAULT_AUTH_SESSION_EXPIRY as u64 + 1);
// Assert that our grace time is less than expiry, so we know the failure is due to
// this.
@ -3592,6 +3636,137 @@ mod tests {
}
}
#[idm_test]
async fn test_idm_account_session_expiry(
idms: &IdmServer,
_idms_delayed: &mut IdmServerDelayed,
) {
let ct = Duration::from_secs(TEST_CURRENT_TIME);
//we first set the expiry to a custom value
let mut idms_prox_write = idms.proxy_write(ct).await;
let new_authsession_expiry = 1000_u32;
idms_prox_write.account_policy.authsession_expiry = new_authsession_expiry;
assert!(idms_prox_write.commit().is_ok());
// Start anonymous auth.
let mut idms_auth = idms.auth().await;
// Send the initial auth event for initialising the session
let anon_init = AuthEvent::anonymous_init();
// Expect success
let r1 = idms_auth.auth(&anon_init, ct, Source::Internal).await;
/* Some weird lifetime things happen here ... */
let sid = match r1 {
Ok(ar) => {
let AuthResult { sessionid, state } = ar;
match state {
AuthState::Choose(mut conts) => {
// Should only be one auth mech
assert!(conts.len() == 1);
// And it should be anonymous
let m = conts.pop().expect("Should not fail");
assert!(m == AuthMech::Anonymous);
}
_ => {
error!("A critical error has occurred! We have a non-continue result!");
panic!();
}
};
// Now pass back the sessionid, we are good to continue.
sessionid
}
Err(e) => {
// Should not occur!
error!("A critical error has occurred! {:?}", e);
panic!();
}
};
idms_auth.commit().expect("Must not fail");
let mut idms_auth = idms.auth().await;
let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
let r2 = idms_auth.auth(&anon_begin, ct, Source::Internal).await;
match r2 {
Ok(ar) => {
let AuthResult {
sessionid: _,
state,
} = ar;
match state {
AuthState::Continue(allowed) => {
// Check the uat.
assert!(allowed.len() == 1);
assert!(allowed.first() == Some(&AuthAllowed::Anonymous));
}
_ => {
error!("A critical error has occurred! We have a non-continue result!");
panic!();
}
}
}
Err(e) => {
error!("A critical error has occurred! {:?}", e);
// Should not occur!
panic!();
}
};
idms_auth.commit().expect("Must not fail");
let mut idms_auth = idms.auth().await;
// Now send the anonymous request, given the session id.
let anon_step = AuthEvent::cred_step_anonymous(sid);
// Expect success
let r2 = idms_auth.auth(&anon_step, ct, Source::Internal).await;
let token = match r2 {
Ok(ar) => {
let AuthResult {
sessionid: _,
state,
} = ar;
match state {
AuthState::Success(uat, AuthIssueSession::Token) => uat,
_ => {
error!("A critical error has occurred! We have a non-succcess result!");
panic!();
}
}
}
Err(e) => {
error!("A critical error has occurred! {:?}", e);
// Should not occur!
panic!();
}
};
idms_auth.commit().expect("Must not fail");
// Token_str to uat
// we have to do it this way because anonymous doesn't have an ideantity for which we cam get the expiry value
let Token::UserAuthToken(uat) = idms
.proxy_read()
.await
.validate_and_parse_token_to_token(Some(&token), ct)
.expect("Must not fail") else {
panic!("Unexpected auth token type for anonymous auth");
};
debug!(?uat);
assert!(
matches!(uat.expiry, Some(exp) if exp == OffsetDateTime::UNIX_EPOCH + ct + Duration::from_secs(new_authsession_expiry as u64))
);
}
#[idm_test]
async fn test_idm_uat_claim_insertion(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
let ct = Duration::from_secs(TEST_CURRENT_TIME);
@ -3609,7 +3784,12 @@ mod tests {
// == anonymous
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(&uat, ct)
@ -3623,7 +3803,12 @@ mod tests {
// == unixpassword
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(&uat, ct)
@ -3637,7 +3822,12 @@ mod tests {
// == password
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(&uat, ct)
@ -3651,7 +3841,12 @@ mod tests {
// == generatedpassword
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(&uat, ct)
@ -3665,7 +3860,12 @@ mod tests {
// == webauthn
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(&uat, ct)
@ -3679,7 +3879,12 @@ mod tests {
// == passwordmfa
let uat = account
.to_userauthtoken(session_id, SessionScope::ReadWrite, ct)
.to_userauthtoken(
session_id,
SessionScope::ReadWrite,
ct,
DEFAULT_AUTH_SESSION_EXPIRY,
)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(&uat, ct)

View file

@ -32,6 +32,8 @@ lazy_static! {
m.insert("id_verification_eckey");
m.insert("badlist_password");
m.insert("domain_display_name");
m.insert("authsession_expiry");
m.insert("privilege_expiry");
m
};
}

View file

@ -490,6 +490,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
SCHEMA_ATTR_ACCOUNT_EXPIRE.clone().into(),
SCHEMA_ATTR_ACCOUNT_VALID_FROM.clone().into(),
SCHEMA_ATTR_API_TOKEN_SESSION.clone().into(),
SCHEMA_ATTR_AUTH_SESSION_EXPIRY.clone().into(),
SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY.clone().into(),
SCHEMA_ATTR_BADLIST_PASSWORD.clone().into(),
SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN.clone().into(),
SCHEMA_ATTR_DEVICEKEYS.clone().into(),
@ -701,6 +703,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
E_IDM_ACP_RADIUS_SERVERS_V1.clone(),
E_IDM_ACP_DOMAIN_ADMIN_PRIV_V1.clone(),
E_IDM_ACP_SYSTEM_CONFIG_PRIV_V1.clone(),
E_IDM_ACP_SYSTEM_CONFIG_SESSION_EXP_PRIV_V1.clone(),
E_IDM_ACP_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1.clone(),
E_IDM_ACP_PEOPLE_EXTEND_PRIV_V1.clone(),
E_IDM_ACP_HP_PEOPLE_READ_PRIV_V1.clone(),

View file

@ -820,6 +820,36 @@ 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("authsession_expiry") {
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("privilege_expiry") {
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> {
self.internal_search(filter!(f_eq(
Attribute::Class,

View file

@ -323,6 +323,8 @@ async fn test_default_entries_rbac_admins_schema_entries(rsclient: KanidmClient)
"domain_ssid",
"gidnumber",
"badlist_password",
"authsession_expiry",
"privilege_expiry",
"loginshell",
"unix_password",
"nsuniqueid",

View file

@ -1460,3 +1460,34 @@ async fn test_server_user_auth_reauthentication(rsclient: KanidmClient) {
eprintln!("{:?} {:?}", now, uat.purpose);
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 ans = rsclient.system_authsession_expiry_get().await.unwrap();
assert_eq!(authsession_expiry, ans);
}
#[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 ans = rsclient.system_auth_privilege_expiry_get().await.unwrap();
assert_eq!(authsession_expiry, ans);
}

View file

@ -31,6 +31,7 @@ pub mod raw;
pub mod recycle;
pub mod serviceaccount;
pub mod session;
pub mod session_expiry;
pub mod synch;
mod webauthn;
@ -72,6 +73,8 @@ impl SystemOpt {
SystemOpt::Oauth2 { commands } => commands.debug(),
SystemOpt::Domain { commands } => commands.debug(),
SystemOpt::Synch { commands } => commands.debug(),
SystemOpt::AuthSessionExpiry { commands } => commands.debug(),
SystemOpt::PrivilegedSessionExpiry { commands } => commands.debug(),
}
}
@ -81,6 +84,8 @@ impl SystemOpt {
SystemOpt::Oauth2 { commands } => commands.exec().await,
SystemOpt::Domain { commands } => commands.exec().await,
SystemOpt::Synch { commands } => commands.exec().await,
SystemOpt::AuthSessionExpiry { commands } => commands.exec().await,
SystemOpt::PrivilegedSessionExpiry { commands } => commands.exec().await,
}
}
}

View file

@ -0,0 +1,73 @@
use crate::common::OpType;
use crate::{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) => eprintln!("{:?}", e),
}
}
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) => eprintln!("{:?}", e),
}
}
}
}
}
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) => eprintln!("{:?}", e),
}
}
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) => eprintln!("{:?}", e),
}
}
}
}
}

View file

@ -904,6 +904,36 @@ pub enum SynchOpt {
},
}
#[derive(Debug, Subcommand)]
pub enum AuthSessionExpiryOpt {
#[clap[name = "get"]]
/// Show information about this system auth session expiry
Get(CommonOpt),
#[clap[name = "set"]]
/// Sets the system auth session expiry in seconds
Set {
#[clap(flatten)]
copt: CommonOpt,
#[clap(name = "expiry")]
expiry: u32,
},
}
#[derive(Debug, Subcommand)]
pub enum PrivilegedSessionExpiryOpt {
#[clap[name = "get"]]
/// Show information about this system privileged session expiry
Get(CommonOpt),
#[clap[name = "set"]]
/// Sets the system auth privilege session expiry in seconds
Set {
#[clap(flatten)]
copt: CommonOpt,
#[clap(name = "expiry")]
expiry: u32,
},
}
#[derive(Debug, Subcommand)]
pub enum SystemOpt {
#[clap(name = "pw-badlist")]
@ -912,6 +942,18 @@ pub enum SystemOpt {
#[clap(subcommand)]
commands: PwBadlistOpt,
},
/// 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")]
/// Configure and display oauth2/oidc resource server configuration
Oauth2 {