From 807af81184d8599310356263a8d429891aff8539 Mon Sep 17 00:00:00 2001 From: Firstyear Date: Wed, 2 Jun 2021 09:30:37 +1000 Subject: [PATCH] 64 120 session claims (#462) --- kanidm_client/src/asynchronous.rs | 3 +- kanidm_client/src/lib.rs | 3 +- kanidm_client/tests/default_entries.rs | 2 +- kanidm_client/tests/proto_v1_test.rs | 2 +- kanidm_proto/src/v1.rs | 40 ++-- kanidm_tools/src/cli/account.rs | 1 - kanidm_tools/src/opt/kanidm.rs | 13 +- kanidmd/src/lib/actors/v1_read.rs | 24 +-- kanidmd/src/lib/actors/v1_write.rs | 32 ++- kanidmd/src/lib/core/https.rs | 9 +- kanidmd/src/lib/core/mod.rs | 10 +- kanidmd/src/lib/credential/mod.rs | 20 +- kanidmd/src/lib/credential/totp.rs | 38 +--- kanidmd/src/lib/entry.rs | 4 + kanidmd/src/lib/identity.rs | 10 + kanidmd/src/lib/idm/account.rs | 88 ++++----- kanidmd/src/lib/idm/authsession.rs | 80 +++----- kanidmd/src/lib/idm/claim.rs | 20 -- kanidmd/src/lib/idm/delayed.rs | 1 - kanidmd/src/lib/idm/event.rs | 29 +-- kanidmd/src/lib/idm/mfareg.rs | 3 +- kanidmd/src/lib/idm/mod.rs | 1 - kanidmd/src/lib/idm/server.rs | 214 +++++++++++++++++---- kanidmd/src/lib/ldap.rs | 4 +- kanidmd/src/lib/plugins/password_import.rs | 2 +- kanidmd/src/lib/schema.rs | 6 +- kanidmd/src/server/main.rs | 9 +- 27 files changed, 375 insertions(+), 293 deletions(-) delete mode 100644 kanidmd/src/lib/idm/claim.rs diff --git a/kanidm_client/src/asynchronous.rs b/kanidm_client/src/asynchronous.rs index 3978d7733..62d02548f 100644 --- a/kanidm_client/src/asynchronous.rs +++ b/kanidm_client/src/asynchronous.rs @@ -901,9 +901,8 @@ impl KanidmAsyncClient { pub async fn idm_account_primary_credential_generate_totp( &self, id: &str, - label: &str, ) -> Result<(Uuid, TotpSecret), ClientError> { - let r = SetCredentialRequest::TotpGenerate(label.to_string()); + let r = SetCredentialRequest::TotpGenerate; let res: Result = self .perform_put_request( format!("/v1/account/{}/_credential/primary", id).as_str(), diff --git a/kanidm_client/src/lib.rs b/kanidm_client/src/lib.rs index 2c44fb0dd..fee745070 100644 --- a/kanidm_client/src/lib.rs +++ b/kanidm_client/src/lib.rs @@ -605,11 +605,10 @@ impl KanidmClient { pub fn idm_account_primary_credential_generate_totp( &self, id: &str, - label: &str, ) -> Result<(Uuid, TotpSecret), ClientError> { tokio_block_on( self.asclient - .idm_account_primary_credential_generate_totp(id, label), + .idm_account_primary_credential_generate_totp(id), ) } diff --git a/kanidm_client/tests/default_entries.rs b/kanidm_client/tests/default_entries.rs index c9ceb4c9c..cd6feb64c 100644 --- a/kanidm_client/tests/default_entries.rs +++ b/kanidm_client/tests/default_entries.rs @@ -57,7 +57,7 @@ static DEFAULT_NOT_HP_GROUP_NAMES: [&str; 2] = ["idm_account_unix_extend_priv", "idm_group_unix_extend_priv"]; fn create_user(rsclient: &KanidmClient, id: &str, group_name: &str) -> () { - rsclient.idm_account_create(id, "Deeeeemo").unwrap(); + rsclient.idm_account_create(id, id).unwrap(); // Create group and add to user to test read attr: member_of let _ = match rsclient.idm_group_get(&group_name).unwrap() { diff --git a/kanidm_client/tests/proto_v1_test.rs b/kanidm_client/tests/proto_v1_test.rs index fd623da33..d983ea581 100644 --- a/kanidm_client/tests/proto_v1_test.rs +++ b/kanidm_client/tests/proto_v1_test.rs @@ -756,7 +756,7 @@ fn test_server_rest_totp_auth_lifecycle() { .idm_account_primary_credential_set_password("demo_account", "sohdi3iuHo6mai7noh0a") .is_ok()); let (sessionid, tok) = rsclient - .idm_account_primary_credential_generate_totp("demo_account", "demo") + .idm_account_primary_credential_generate_totp("demo_account") .unwrap(); let r_tok: Totp = tok.into(); diff --git a/kanidm_proto/src/v1.rs b/kanidm_proto/src/v1.rs index 5910e9a04..97aac33d7 100644 --- a/kanidm_proto/src/v1.rs +++ b/kanidm_proto/src/v1.rs @@ -150,18 +150,33 @@ pub struct Application { } */ -// The currently authenticated user, and any required metadata for them -// to properly authorise them. This is similar in nature to oauth and the krb -// PAC/PAD structures. Currently we only use this internally, but we should -// consider making it "parseable" by the client so they can have per-session -// group/authorisation data. -// -// This structure and how it works will *very much* change over time from this -// point onward! -// -// It's likely that this must have a relationship to the server's user structure -// and to the Entry so that filters or access controls can be applied. +#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum AuthType { + Anonymous, + UnixPassword, + Password, + GeneratedPassword, + Webauthn, + PasswordMfa, + // PasswordWebauthn, + // WebauthnVerified, + // PasswordWebauthnVerified, +} + +/// The currently authenticated user, and any required metadata for them +/// to properly authorise them. This is similar in nature to oauth and the krb +/// PAC/PAD structures. Currently we only use this internally, but we should +/// consider making it "parseable" by the client so they can have per-session +/// group/authorisation data. +/// +/// This structure and how it works will *very much* change over time from this +/// point onward! +/// +/// It's likely that this must have a relationship to the server's user structure +/// and to the Entry so that filters or access controls can be applied. #[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "lowercase")] pub struct UserAuthToken { pub session_id: Uuid, // When this data should be considered invalid. Interpretation @@ -175,6 +190,7 @@ pub struct UserAuthToken { // pub application: Option, pub groups: Vec, pub claims: Vec, + pub auth_type: AuthType, // Should we allow supplemental ava's to be added on request? pub lim_uidx: bool, pub lim_rmax: usize, @@ -649,7 +665,7 @@ pub struct AuthResponse { pub enum SetCredentialRequest { Password(String), GeneratePassword, - TotpGenerate(String), + TotpGenerate, TotpVerify(Uuid, u32), TotpRemove, // Start the rego. diff --git a/kanidm_tools/src/cli/account.rs b/kanidm_tools/src/cli/account.rs index da018259a..930bae5ae 100644 --- a/kanidm_tools/src/cli/account.rs +++ b/kanidm_tools/src/cli/account.rs @@ -146,7 +146,6 @@ impl AccountOpt { let client = acsopt.copt.to_client(); let (session, tok) = match client.idm_account_primary_credential_generate_totp( acsopt.aopts.account_id.as_str(), - acsopt.tag.as_str(), ) { Ok(v) => v, Err(e) => { diff --git a/kanidm_tools/src/opt/kanidm.rs b/kanidm_tools/src/opt/kanidm.rs index 1207d9219..2467b336c 100644 --- a/kanidm_tools/src/opt/kanidm.rs +++ b/kanidm_tools/src/opt/kanidm.rs @@ -158,24 +158,29 @@ pub struct AccountCreateOpt { #[derive(Debug, StructOpt)] pub enum AccountCredential { + /// Set this accounts password #[structopt(name = "set_password")] SetPassword(AccountCredentialSet), - #[structopt(name = "generate_password")] - GeneratePassword(AccountCredentialSet), + /// Register a new webauthn device to this account. #[structopt(name = "register_webauthn")] RegisterWebauthn(AccountNamedTagOpt), + /// Remove a webauthn device from this account #[structopt(name = "remove_webauthn")] RemoveWebauthn(AccountNamedTagOpt), /// Set the TOTP credential of the account. If a TOTP already exists, on a successful /// registration, this will replace it. - #[structopt(name = "set_totp")] - RegisterTotp(AccountNamedTagOpt), + #[structopt(name = "register_totp")] + RegisterTotp(AccountNamedOpt), /// Remove TOTP from the account. If no TOTP exists, no action is taken. #[structopt(name = "remove_totp")] RemoveTotp(AccountNamedOpt), /// Show the status of the accounts credentials. #[structopt(name = "status")] Status(AccountNamedOpt), + /// Reset the accounts credentials, removing all TOTP, Webauthn, Passwords, + /// and generate a new strong random password. + #[structopt(name = "reset_credential")] + GeneratePassword(AccountCredentialSet), } #[derive(Debug, StructOpt)] diff --git a/kanidmd/src/lib/actors/v1_read.rs b/kanidmd/src/lib/actors/v1_read.rs index 5f6b9b5f9..f62dafae8 100644 --- a/kanidmd/src/lib/actors/v1_read.rs +++ b/kanidmd/src/lib/actors/v1_read.rs @@ -82,7 +82,7 @@ impl QueryServerReadV1 { let res = lperf_op_segment!(&mut audit, "actors::v1_read::handle", || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -185,7 +185,7 @@ impl QueryServerReadV1 { .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .and_then(|uat| { idms_prox_read - .process_uat_to_identity(&mut audit, &uat) + .process_uat_to_identity(&mut audit, &uat, ct) .map(|i| (uat, i)) }) .map_err(|e| { @@ -252,7 +252,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -305,7 +305,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -356,7 +356,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -429,7 +429,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -486,7 +486,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -545,7 +545,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -600,7 +600,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -673,7 +673,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -749,7 +749,7 @@ impl QueryServerReadV1 { // resolve the id let ident = idm_auth .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idm_auth.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idm_auth.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -804,7 +804,7 @@ impl QueryServerReadV1 { || { let ident = idms_prox_read .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_read.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e diff --git a/kanidmd/src/lib/actors/v1_write.rs b/kanidmd/src/lib/actors/v1_write.rs index 05d9c68d1..d46e17efc 100644 --- a/kanidmd/src/lib/actors/v1_write.rs +++ b/kanidmd/src/lib/actors/v1_write.rs @@ -73,7 +73,7 @@ impl QueryServerWriteV1 { let ident = idms_prox_write .validate_and_parse_uat(audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -126,7 +126,7 @@ impl QueryServerWriteV1 { let ident = idms_prox_write .validate_and_parse_uat(audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -180,7 +180,7 @@ impl QueryServerWriteV1 { let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -234,7 +234,7 @@ impl QueryServerWriteV1 { let ct = duration_from_epoch_now(); let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(&mut audit, "Invalid identity: {:?}", e); e @@ -287,7 +287,7 @@ impl QueryServerWriteV1 { let ct = duration_from_epoch_now(); let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -339,7 +339,7 @@ impl QueryServerWriteV1 { let ct = duration_from_epoch_now(); let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -387,7 +387,7 @@ impl QueryServerWriteV1 { let ct = duration_from_epoch_now(); let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -425,7 +425,6 @@ impl QueryServerWriteV1 { &self, uat: Option, uuid_or_name: String, - appid: Option, sac: SetCredentialRequest, eventid: Uuid, ) -> Result { @@ -443,7 +442,7 @@ impl QueryServerWriteV1 { let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -471,7 +470,6 @@ impl QueryServerWriteV1 { ident, target_uuid, cleartext, - appid, ) .map_err(|e| { ladmin_error!( @@ -492,7 +490,6 @@ impl QueryServerWriteV1 { // &idms_prox_write.qs_write, ident, target_uuid, - appid, ) .map_err(|e| { ladmin_error!( @@ -507,13 +504,12 @@ impl QueryServerWriteV1 { .and_then(|r| idms_prox_write.commit(&mut audit).map(|_| r)) .map(SetCredentialResponse::Token) } - SetCredentialRequest::TotpGenerate(label) => { + SetCredentialRequest::TotpGenerate => { let gte = GenerateTotpEvent::from_parts( &mut audit, // &idms_prox_write.qs_write, ident, target_uuid, - label, ) .map_err(|e| { ladmin_error!( @@ -655,7 +651,7 @@ impl QueryServerWriteV1 { let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -700,7 +696,7 @@ impl QueryServerWriteV1 { let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -758,7 +754,7 @@ impl QueryServerWriteV1 { let ct = duration_from_epoch_now(); let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -819,7 +815,7 @@ impl QueryServerWriteV1 { let ct = duration_from_epoch_now(); let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e @@ -1123,7 +1119,7 @@ impl QueryServerWriteV1 { let ident = idms_prox_write .validate_and_parse_uat(&mut audit, uat.as_deref(), ct) - .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat)) + .and_then(|uat| idms_prox_write.process_uat_to_identity(&mut audit, &uat, ct)) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e diff --git a/kanidmd/src/lib/core/https.rs b/kanidmd/src/lib/core/https.rs index a6e24f68c..07e1d47c0 100644 --- a/kanidmd/src/lib/core/https.rs +++ b/kanidmd/src/lib/core/https.rs @@ -388,10 +388,7 @@ async fn json_rest_event_delete_id_attr( } } -async fn json_rest_event_credential_put( - mut req: tide::Request, - appid: Option, -) -> tide::Result { +async fn json_rest_event_credential_put(mut req: tide::Request) -> tide::Result { let uat = req.get_current_uat(); let uuid_or_name = req.get_url_param("id")?; let sac: SetCredentialRequest = req.body_json().await?; @@ -400,7 +397,7 @@ async fn json_rest_event_credential_put( let res = req .state() .qe_w_ref - .handle_credentialset(uat, uuid_or_name, appid, sac, eventid) + .handle_credentialset(uat, uuid_or_name, sac, eventid) .await; to_tide_response(res, hvalue) } @@ -541,7 +538,7 @@ pub async fn account_id_delete(req: tide::Request) -> tide::Result { } pub async fn account_put_id_credential_primary(req: tide::Request) -> tide::Result { - json_rest_event_credential_put(req, None).await + json_rest_event_credential_put(req).await } pub async fn account_get_id_credential_status(req: tide::Request) -> tide::Result { diff --git a/kanidmd/src/lib/core/mod.rs b/kanidmd/src/lib/core/mod.rs index dca2ee651..0643ea97d 100644 --- a/kanidmd/src/lib/core/mod.rs +++ b/kanidmd/src/lib/core/mod.rs @@ -390,7 +390,7 @@ pub fn verify_server_core(config: &Configuration) { // Now add IDM server verifications? } -pub fn recover_account_core(config: &Configuration, name: &str, password: &str) { +pub fn recover_account_core(config: &Configuration, name: &str) { let mut audit = AuditScope::new("recover_account", uuid::Uuid::new_v4(), config.log_level); let schema = match Schema::new(&mut audit) { @@ -421,11 +421,11 @@ pub fn recover_account_core(config: &Configuration, name: &str, password: &str) // Run the password change. let mut idms_prox_write = task::block_on(idms.proxy_write_async(duration_from_epoch_now())); - match idms_prox_write.recover_account(&mut audit, &name, &password) { - Ok(_) => match idms_prox_write.commit(&mut audit) { + match idms_prox_write.recover_account(&mut audit, &name, None) { + Ok(new_pw) => match idms_prox_write.commit(&mut audit) { Ok(()) => { audit.write_log(); - info!("Password reset!"); + info!("Password reset to -> {}", new_pw); } Err(e) => { error!("A critical error during commit occured {:?}", e); @@ -515,7 +515,7 @@ pub async fn create_server_core(config: Configuration) -> Result<(), ()> { Some(itc) => { let mut idms_prox_write = task::block_on(idms.proxy_write_async(duration_from_epoch_now())); - match idms_prox_write.recover_account(&mut audit, "admin", &itc.admin_password) { + match idms_prox_write.recover_account(&mut audit, "admin", Some(&itc.admin_password)) { Ok(_) => {} Err(e) => { audit.write_log(); diff --git a/kanidmd/src/lib/credential/mod.rs b/kanidmd/src/lib/credential/mod.rs index 48b214087..ca15f71a9 100644 --- a/kanidmd/src/lib/credential/mod.rs +++ b/kanidmd/src/lib/credential/mod.rs @@ -341,6 +341,13 @@ impl Credential { Password::new(policy, cleartext).map(Self::new_from_password) } + pub fn new_generatedpassword_only( + policy: &CryptoPolicy, + cleartext: &str, + ) -> Result { + Password::new(policy, cleartext).map(Self::new_from_generatedpassword) + } + pub fn new_webauthn_only(label: String, cred: WebauthnCredential) -> Self { let mut webauthn_map = Map::new(); webauthn_map.insert(label, cred); @@ -593,8 +600,9 @@ impl Credential { pub(crate) fn update_password(&self, pw: Password) -> Self { let type_ = match &self.type_ { - CredentialType::Password(_) => CredentialType::Password(pw), - CredentialType::GeneratedPassword(_) => CredentialType::GeneratedPassword(pw), + CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => { + CredentialType::Password(pw) + } CredentialType::PasswordMfa(_, totp, wan) => { CredentialType::PasswordMfa(pw, totp.clone(), wan.clone()) } @@ -646,6 +654,14 @@ impl Credential { } } + pub(crate) fn new_from_generatedpassword(pw: Password) -> Self { + Credential { + type_: CredentialType::GeneratedPassword(pw), + claims: Vec::new(), + uuid: Uuid::new_v4(), + } + } + pub(crate) fn new_from_password(pw: Password) -> Self { Credential { type_: CredentialType::Password(pw), diff --git a/kanidmd/src/lib/credential/totp.rs b/kanidmd/src/lib/credential/totp.rs index 050ca5d94..0968f347f 100644 --- a/kanidmd/src/lib/credential/totp.rs +++ b/kanidmd/src/lib/credential/totp.rs @@ -60,7 +60,6 @@ impl TotpAlgo { /// https://tools.ietf.org/html/rfc6238 which relies on https://tools.ietf.org/html/rfc4226 #[derive(Debug, Clone)] pub struct Totp { - label: String, secret: Vec, pub(crate) step: u64, algo: TotpAlgo, @@ -76,7 +75,6 @@ impl TryFrom for Totp { DbTotpAlgoV1::S512 => TotpAlgo::Sha512, }; Ok(Totp { - label: value.l, secret: value.k, step: value.s, algo, @@ -87,7 +85,6 @@ impl TryFrom for Totp { impl From for Totp { fn from(value: ProtoTotp) -> Self { Totp { - label: "test_token".to_string(), secret: value.secret, algo: match value.algo { ProtoTotpAlgo::Sha1 => TotpAlgo::Sha1, @@ -100,31 +97,21 @@ impl From for Totp { } impl Totp { - pub fn new(label: String, secret: Vec, step: u64, algo: TotpAlgo) -> Self { - Totp { - label, - secret, - step, - algo, - } + pub fn new(secret: Vec, step: u64, algo: TotpAlgo) -> Self { + Totp { secret, step, algo } } // Create a new token with secure key and algo. - pub fn generate_secure(label: String, step: u64) -> Self { + pub fn generate_secure(step: u64) -> Self { let mut rng = rand::thread_rng(); let secret: Vec = (0..SECRET_SIZE_BYTES).map(|_| rng.gen()).collect(); let algo = TotpAlgo::Sha512; - Totp { - label, - secret, - step, - algo, - } + Totp { secret, step, algo } } pub(crate) fn to_dbtotpv1(&self) -> DbTotpV1 { DbTotpV1 { - l: self.label.clone(), + l: "totp".to_string(), k: self.secret.clone(), s: self.step, a: match self.algo { @@ -204,16 +191,16 @@ mod tests { #[test] fn hotp_basic() { - let otp_sha1 = Totp::new("".to_string(), vec![0], 30, TotpAlgo::Sha1); + let otp_sha1 = Totp::new(vec![0], 30, TotpAlgo::Sha1); assert!(otp_sha1.digest(0) == Ok(328482)); - let otp_sha256 = Totp::new("".to_string(), vec![0], 30, TotpAlgo::Sha256); + let otp_sha256 = Totp::new(vec![0], 30, TotpAlgo::Sha256); assert!(otp_sha256.digest(0) == Ok(356306)); - let otp_sha512 = Totp::new("".to_string(), vec![0], 30, TotpAlgo::Sha512); + let otp_sha512 = Totp::new(vec![0], 30, TotpAlgo::Sha512); assert!(otp_sha512.digest(0) == Ok(674061)); } fn do_test(key: Vec, algo: TotpAlgo, secs: u64, step: u64, expect: Result) { - let otp = Totp::new("".to_string(), key.clone(), step, algo.clone()); + let otp = Totp::new(key.clone(), step, algo.clone()); let d = Duration::from_secs(secs); let r = otp.do_totp_duration_from_epoch(&d); debug!( @@ -281,12 +268,7 @@ mod tests { fn totp_allow_one_previous() { let key = vec![0x00, 0xaa, 0xbb, 0xcc]; let secs = 1585369780; - let otp = Totp::new( - "".to_string(), - key.clone(), - TOTP_DEFAULT_STEP, - TotpAlgo::Sha512, - ); + let otp = Totp::new(key.clone(), TOTP_DEFAULT_STEP, TotpAlgo::Sha512); let d = Duration::from_secs(secs); // Step assert!(otp.verify(952181, &d)); diff --git a/kanidmd/src/lib/entry.rs b/kanidmd/src/lib/entry.rs index fbd998d76..4d6c023d7 100644 --- a/kanidmd/src/lib/entry.rs +++ b/kanidmd/src/lib/entry.rs @@ -867,6 +867,10 @@ impl Entry { self } + pub fn insert_claim(&mut self, value: &str) { + self.add_ava_int("claim", Value::new_iutf8(value)); + } + pub fn compare(&self, rhs: &Entry) -> bool { compare_attrs(&self.attrs, &rhs.attrs) } diff --git a/kanidmd/src/lib/identity.rs b/kanidmd/src/lib/identity.rs index ddb7f6656..a8325809f 100644 --- a/kanidmd/src/lib/identity.rs +++ b/kanidmd/src/lib/identity.rs @@ -133,4 +133,14 @@ impl Identity { pub fn get_event_origin_id(&self) -> IdentityId { IdentityId::from(&self.origin) } + + #[cfg(test)] + pub fn has_claim(&self, claim: &str) -> bool { + match &self.origin { + IdentType::Internal => false, + IdentType::User(u) => u + .entry + .attribute_equality("claim", &PartialValue::new_iutf8(claim)), + } + } } diff --git a/kanidmd/src/lib/idm/account.rs b/kanidmd/src/lib/idm/account.rs index 58c6782b7..6df982320 100644 --- a/kanidmd/src/lib/idm/account.rs +++ b/kanidmd/src/lib/idm/account.rs @@ -3,13 +3,12 @@ use crate::prelude::*; use kanidm_proto::v1::CredentialStatus; use kanidm_proto::v1::OperationError; -use kanidm_proto::v1::UserAuthToken; +use kanidm_proto::v1::{AuthType, UserAuthToken}; use crate::constants::UUID_ANONYMOUS; use crate::credential::policy::CryptoPolicy; use crate::credential::totp::Totp; use crate::credential::{softlock::CredSoftLockPolicy, Credential}; -use crate::idm::claim::Claim; use crate::idm::group::Group; use crate::modify::{ModifyInvalid, ModifyList}; use crate::value::{PartialValue, Value}; @@ -155,12 +154,15 @@ impl Account { try_from_entry!(value, vec![]) } - // Could this actually take a claims list and application instead? + /// Given the session_id and other metadata, create a user authentication token + /// that represents a users session. Since this metadata can vary from session + /// to session, this userauthtoken may contain some data (claims) that may yield + /// different privileges to the bearer. pub(crate) fn to_userauthtoken( &self, session_id: Uuid, - claims: &[Claim], ct: Duration, + auth_type: AuthType, ) -> Option { // This could consume self? // The cred handler provided is what authenticated this user, so we can use it to @@ -179,7 +181,9 @@ impl Account { uuid: self.uuid, // application: None, groups: self.groups.iter().map(|g| g.to_proto()).collect(), - claims: claims.iter().map(|c| c.to_proto()).collect(), + // claims: claims.iter().map(|c| c.to_proto()).collect(), + claims: Vec::new(), + auth_type, // What's the best way to get access to these limits with regard to claims/other? lim_uidx: false, lim_rmax: 128, @@ -188,19 +192,23 @@ impl Account { }) } - pub fn is_within_valid_time(&self, ct: Duration) -> bool { + pub fn check_within_valid_time( + ct: Duration, + valid_from: Option<&OffsetDateTime>, + expire: Option<&OffsetDateTime>, + ) -> bool { let cot = OffsetDateTime::unix_epoch() + ct; - let vmin = if let Some(vft) = &self.valid_from { + let vmin = if let Some(vft) = valid_from { // If current time greater than strat time window - vft < &cot + vft <= &cot } else { // We have no time, not expired. true }; - let vmax = if let Some(ext) = &self.expire { + let vmax = if let Some(ext) = expire { // If exp greater than ct then expired. - &cot < ext + &cot <= ext } else { // If not present, we are not expired true @@ -209,6 +217,10 @@ impl Account { vmin && vmax } + pub fn is_within_valid_time(&self, ct: Duration) -> bool { + Self::check_within_valid_time(ct, self.valid_from.as_ref(), self.expire.as_ref()) + } + pub fn primary_cred_uuid(&self) -> Uuid { match &self.primary { Some(cred) => cred.uuid, @@ -226,12 +238,12 @@ impl Account { self.uuid == *UUID_ANONYMOUS } - pub(crate) fn gen_password_recover_mod( + pub(crate) fn gen_generatedpassword_recover_mod( &self, cleartext: &str, crypto_policy: &CryptoPolicy, ) -> Result, OperationError> { - let ncred = Credential::new_password_only(crypto_policy, cleartext)?; + let ncred = Credential::new_generatedpassword_only(crypto_policy, cleartext)?; let vcred = Value::new_credential("primary", ncred); Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) } @@ -239,31 +251,21 @@ impl Account { pub(crate) fn gen_password_mod( &self, cleartext: &str, - appid: &Option, crypto_policy: &CryptoPolicy, ) -> Result, OperationError> { - // What should this look like? Probablf an appid + stuff -> modify? - // then the caller has to apply the modify under the requests event - // for proper auth checks. - match appid { - Some(_) => Err(OperationError::InvalidState), + match &self.primary { + // Change the cred + Some(primary) => { + let ncred = primary.set_password(crypto_policy, cleartext)?; + let vcred = Value::new_credential("primary", ncred); + Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) + } + // Make a new credential instead None => { - // TODO #59: Enforce PW policy. Can we allow this change? - match &self.primary { - // Change the cred - Some(primary) => { - let ncred = primary.set_password(crypto_policy, cleartext)?; - let vcred = Value::new_credential("primary", ncred); - Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) - } - // Make a new credential instead - None => { - let ncred = Credential::new_password_only(crypto_policy, cleartext)?; - let vcred = Value::new_credential("primary", ncred); - Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) - } - } - } // no appid + let ncred = Credential::new_password_only(crypto_policy, cleartext)?; + let vcred = Value::new_credential("primary", ncred); + Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) + } } } @@ -355,19 +357,11 @@ impl Account { } } - pub(crate) fn check_credential_pw( - &self, - cleartext: &str, - appid: &Option, - ) -> Result { - match appid { - Some(_) => Err(OperationError::InvalidState), - None => self - .primary - .as_ref() - .ok_or(OperationError::InvalidState) - .and_then(|cred| cred.password_ref().and_then(|pw| pw.verify(cleartext))), - } + pub(crate) fn check_credential_pw(&self, cleartext: &str) -> Result { + self.primary + .as_ref() + .ok_or(OperationError::InvalidState) + .and_then(|cred| cred.password_ref().and_then(|pw| pw.verify(cleartext))) } pub(crate) fn regenerate_radius_secret_mod( diff --git a/kanidmd/src/lib/idm/authsession.rs b/kanidmd/src/lib/idm/authsession.rs index cfccc5262..33c9b5576 100644 --- a/kanidmd/src/lib/idm/authsession.rs +++ b/kanidmd/src/lib/idm/authsession.rs @@ -1,10 +1,9 @@ use crate::idm::account::Account; -use crate::idm::claim::Claim; use crate::idm::AuthState; use crate::prelude::*; use hashbrown::HashSet; use kanidm_proto::v1::OperationError; -use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech}; +use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech, AuthType}; use crate::credential::{totp::Totp, Credential, CredentialType, Password}; @@ -35,7 +34,7 @@ const ACCOUNT_EXPIRED: &str = "account expired"; const PW_BADLIST_MSG: &str = "password is in badlist"; enum CredState { - Success(Vec), + Success(AuthType), Continue(Vec), Denied(&'static str), } @@ -67,7 +66,7 @@ struct CredWebauthn { enum CredHandler { Anonymous, // AppPassword (?) - Password(Password), + Password(Password, bool), PasswordMfa(Box), Webauthn(CredWebauthn), // Webauthn + Password @@ -81,9 +80,8 @@ impl CredHandler { webauthn: &Webauthn, ) -> Result { match &c.type_ { - CredentialType::Password(pw) | CredentialType::GeneratedPassword(pw) => { - Ok(CredHandler::Password(pw.clone())) - } + CredentialType::Password(pw) => Ok(CredHandler::Password(pw.clone(), false)), + CredentialType::GeneratedPassword(pw) => Ok(CredHandler::Password(pw.clone(), true)), CredentialType::PasswordMfa(pw, maybe_totp, maybe_wan) => { let wan = if !maybe_wan.is_empty() { webauthn @@ -152,7 +150,6 @@ impl CredHandler { if let Err(_e) = async_tx.send(DelayedAction::PwUpgrade(PasswordUpgrade { target_uuid: who, existing_password: cleartext.to_string(), - appid: None, })) { ladmin_warning!(au, "unable to queue delayed pwupgrade, continuing ... "); }; @@ -164,7 +161,7 @@ impl CredHandler { AuthCredential::Anonymous => { // For anonymous, no claims will ever be issued. lsecurity!(au, "Handler::Anonymous -> Result::Success"); - CredState::Success(Vec::new()) + CredState::Success(AuthType::Anonymous) } _ => { lsecurity!( @@ -180,6 +177,7 @@ impl CredHandler { au: &mut AuditScope, cred: &AuthCredential, pw: &mut Password, + generated: bool, who: Uuid, async_tx: &Sender, pw_badlist_set: Option<&HashSet>, @@ -198,7 +196,11 @@ impl CredHandler { _ => { lsecurity!(au, "Handler::Password -> Result::Success"); Self::maybe_pw_upgrade(au, pw, who, cleartext.as_str(), async_tx); - CredState::Success(Vec::new()) + if generated { + CredState::Success(AuthType::GeneratedPassword) + } else { + CredState::Success(AuthType::Password) + } } } } else { @@ -312,7 +314,7 @@ impl CredHandler { cleartext.as_str(), async_tx, ); - CredState::Success(Vec::new()) + CredState::Success(AuthType::PasswordMfa) } } } else { @@ -377,7 +379,7 @@ impl CredHandler { ladmin_warning!(au, "unable to queue delayed webauthn counter increment, continuing ... "); }; }; - CredState::Success(Vec::new()) + CredState::Success(AuthType::Webauthn) }) .unwrap_or_else(|e| { wan_cred.state = CredVerifyState::Fail; @@ -409,8 +411,8 @@ impl CredHandler { ) -> CredState { match self { CredHandler::Anonymous => Self::validate_anonymous(au, cred), - CredHandler::Password(ref mut pw) => { - Self::validate_password(au, cred, pw, who, async_tx, pw_badlist_set) + CredHandler::Password(ref mut pw, generated) => { + Self::validate_password(au, cred, pw, *generated, who, async_tx, pw_badlist_set) } CredHandler::PasswordMfa(ref mut pw_mfa) => Self::validate_password_mfa( au, @@ -431,7 +433,7 @@ impl CredHandler { pub fn next_auth_allowed(&self) -> Vec { match &self { CredHandler::Anonymous => vec![AuthAllowed::Anonymous], - CredHandler::Password(_) => vec![AuthAllowed::Password], + CredHandler::Password(_, _) => vec![AuthAllowed::Password], CredHandler::PasswordMfa(ref pw_mfa) => pw_mfa .totp .iter() @@ -450,7 +452,7 @@ impl CredHandler { fn can_proceed(&self, mech: &AuthMech) -> bool { match (self, mech) { (CredHandler::Anonymous, AuthMech::Anonymous) - | (CredHandler::Password(_), AuthMech::Password) + | (CredHandler::Password(_, _), AuthMech::Password) | (CredHandler::PasswordMfa(_), AuthMech::PasswordMfa) | (CredHandler::Webauthn(_), AuthMech::Webauthn) => true, (_, _) => false, @@ -460,7 +462,7 @@ impl CredHandler { fn allows_mech(&self) -> AuthMech { match self { CredHandler::Anonymous => AuthMech::Anonymous, - CredHandler::Password(_) => AuthMech::Password, + CredHandler::Password(_, _) => AuthMech::Password, CredHandler::PasswordMfa(_) => AuthMech::PasswordMfa, CredHandler::Webauthn(_) => AuthMech::Webauthn, } @@ -508,7 +510,6 @@ impl AuthSession { pub fn new( au: &mut AuditScope, account: Account, - _appid: &Option, webauthn: &Webauthn, ct: Duration, ) -> (Option, AuthState) { @@ -657,11 +658,11 @@ impl AuthSession { webauthn, pw_badlist_set, ) { - CredState::Success(claims) => { + CredState::Success(auth_type) => { lsecurity!(au, "Successful cred handling"); let uat = self .account - .to_userauthtoken(au.uuid, &claims, *time) + .to_userauthtoken(au.uuid, *time, auth_type) .ok_or(OperationError::InvalidState)?; // Now encrypt and prepare the token for return to the client. @@ -790,7 +791,6 @@ mod tests { let (session, state) = AuthSession::new( &mut audit, anon_account, - &None, &webauthn, duration_from_epoch_now(), ); @@ -823,35 +823,6 @@ mod tests { } } - // Deprecated, will remove later. - #[test] - fn test_idm_authsession_missing_appid() { - let webauthn = create_webauthn(); - let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1); - let mut audit = AuditScope::new( - "test_idm_authsession_missing_appid", - uuid::Uuid::new_v4(), - None, - ); - - let (session, state) = AuthSession::new( - &mut audit, - anon_account, - &Some("NonExistantAppID".to_string()), - &webauthn, - duration_from_epoch_now(), - ); - - // We now ignore appids. - assert!(session.is_some()); - - if let AuthState::Choose(_) = state { - // Pass - } else { - panic!(); - } - } - macro_rules! start_password_session { ( $audit:expr, @@ -861,7 +832,6 @@ mod tests { let (session, state) = AuthSession::new( $audit, $account.clone(), - &None, $webauthn, duration_from_epoch_now(), ); @@ -1008,7 +978,6 @@ mod tests { let (session, state) = AuthSession::new( $audit, $account.clone(), - &None, $webauthn, duration_from_epoch_now(), ); @@ -1067,7 +1036,7 @@ mod tests { let ts = Duration::from_secs(12345); // manually load in a cred - let totp = Totp::generate_secure("test_totp".to_string(), TOTP_DEFAULT_STEP); + let totp = Totp::generate_secure(TOTP_DEFAULT_STEP); let totp_good = totp .do_totp_duration_from_epoch(&ts) @@ -1234,7 +1203,7 @@ mod tests { let ts = Duration::from_secs(12345); // manually load in a cred - let totp = Totp::generate_secure("test_totp".to_string(), TOTP_DEFAULT_STEP); + let totp = Totp::generate_secure(TOTP_DEFAULT_STEP); let totp_good = totp .do_totp_duration_from_epoch(&ts) @@ -1301,7 +1270,6 @@ mod tests { let (session, state) = AuthSession::new( $audit, $account.clone(), - &None, $webauthn, duration_from_epoch_now(), ); @@ -1706,7 +1674,7 @@ mod tests { let (webauthn, mut wa, wan_cred) = setup_webauthn(account.name.as_str()); let hs512 = create_hs512(); - let totp = Totp::generate_secure("test_totp".to_string(), TOTP_DEFAULT_STEP); + let totp = Totp::generate_secure(TOTP_DEFAULT_STEP); let totp_good = totp .do_totp_duration_from_epoch(&ts) .expect("failed to perform totp."); diff --git a/kanidmd/src/lib/idm/claim.rs b/kanidmd/src/lib/idm/claim.rs deleted file mode 100644 index 9bff71db4..000000000 --- a/kanidmd/src/lib/idm/claim.rs +++ /dev/null @@ -1,20 +0,0 @@ -use kanidm_proto::v1::Claim as ProtoClaim; - -#[derive(Debug)] -pub struct Claim { - // For now, empty. Later we'll flesh this out to uuid + name? -} - -impl Claim { - /* - pub fn new() -> Self { - Claim { - // Fill this in! - } - } - */ - - pub fn to_proto(&self) -> ProtoClaim { - unimplemented!(); - } -} diff --git a/kanidmd/src/lib/idm/delayed.rs b/kanidmd/src/lib/idm/delayed.rs index b563f43b3..f11534ff7 100644 --- a/kanidmd/src/lib/idm/delayed.rs +++ b/kanidmd/src/lib/idm/delayed.rs @@ -10,7 +10,6 @@ pub(crate) enum DelayedAction { pub(crate) struct PasswordUpgrade { pub target_uuid: Uuid, pub existing_password: String, - pub appid: Option, } pub(crate) struct UnixPasswordUpgrade { diff --git a/kanidmd/src/lib/idm/event.rs b/kanidmd/src/lib/idm/event.rs index 5b1c92bcb..d213ce89f 100644 --- a/kanidmd/src/lib/idm/event.rs +++ b/kanidmd/src/lib/idm/event.rs @@ -9,16 +9,14 @@ pub struct PasswordChangeEvent { pub ident: Identity, pub target: Uuid, pub cleartext: String, - pub appid: Option, } impl PasswordChangeEvent { - pub fn new_internal(target: &Uuid, cleartext: &str, appid: Option<&str>) -> Self { + pub fn new_internal(target: &Uuid, cleartext: &str) -> Self { PasswordChangeEvent { ident: Identity::from_internal(), target: *target, cleartext: cleartext.to_string(), - appid: appid.map(|v| v.to_string()), } } @@ -34,7 +32,6 @@ impl PasswordChangeEvent { ident, target: u, cleartext, - appid: None, }) } @@ -44,13 +41,11 @@ impl PasswordChangeEvent { ident: Identity, target: Uuid, cleartext: String, - appid: Option, ) -> Result { Ok(PasswordChangeEvent { ident, target, cleartext, - appid, }) } } @@ -90,7 +85,6 @@ impl UnixPasswordChangeEvent { pub struct GeneratePasswordEvent { pub ident: Identity, pub target: Uuid, - pub appid: Option, } impl GeneratePasswordEvent { @@ -99,13 +93,8 @@ impl GeneratePasswordEvent { // qs: &QueryServerWriteTransaction, ident: Identity, target: Uuid, - appid: Option, ) -> Result { - Ok(GeneratePasswordEvent { - ident, - target, - appid, - }) + Ok(GeneratePasswordEvent { ident, target }) } } @@ -248,7 +237,6 @@ impl UnixUserAuthEvent { pub struct GenerateTotpEvent { pub ident: Identity, pub target: Uuid, - pub label: String, } impl GenerateTotpEvent { @@ -257,24 +245,15 @@ impl GenerateTotpEvent { // qs: &QueryServerWriteTransaction, ident: Identity, target: Uuid, - label: String, ) -> Result { - Ok(GenerateTotpEvent { - ident, - target, - label, - }) + Ok(GenerateTotpEvent { ident, target }) } #[cfg(test)] pub fn new_internal(target: Uuid) -> Self { let ident = Identity::from_internal(); - GenerateTotpEvent { - ident, - target, - label: "internal_token".to_string(), - } + GenerateTotpEvent { ident, target } } } diff --git a/kanidmd/src/lib/idm/mfareg.rs b/kanidmd/src/lib/idm/mfareg.rs index d65b6aa0f..edaded997 100644 --- a/kanidmd/src/lib/idm/mfareg.rs +++ b/kanidmd/src/lib/idm/mfareg.rs @@ -61,11 +61,10 @@ impl MfaRegSession { pub fn totp_new( origin: IdentityId, account: Account, - label: String, ) -> Result<(Self, MfaRegNext), OperationError> { // Based on the req, init our session, and the return the next step. // Store the ID of the event that start's the attempt - let token = Totp::generate_secure(label, TOTP_DEFAULT_STEP); + let token = Totp::generate_secure(TOTP_DEFAULT_STEP); let accountname = account.name.as_str(); let issuer = account.spn.as_str(); diff --git a/kanidmd/src/lib/idm/mod.rs b/kanidmd/src/lib/idm/mod.rs index 2252c7425..d7930d258 100644 --- a/kanidmd/src/lib/idm/mod.rs +++ b/kanidmd/src/lib/idm/mod.rs @@ -1,6 +1,5 @@ pub(crate) mod account; pub(crate) mod authsession; -pub(crate) mod claim; pub(crate) mod delayed; pub(crate) mod event; pub(crate) mod group; diff --git a/kanidmd/src/lib/idm/server.rs b/kanidmd/src/lib/idm/server.rs index bfa968a36..224ef7900 100644 --- a/kanidmd/src/lib/idm/server.rs +++ b/kanidmd/src/lib/idm/server.rs @@ -26,8 +26,8 @@ use crate::idm::delayed::{ use hashbrown::HashSet; use kanidm_proto::v1::{ - CredentialStatus, RadiusAuthToken, SetCredentialResponse, UnixGroupToken, UnixUserToken, - UserAuthToken, + AuthType, CredentialStatus, RadiusAuthToken, SetCredentialResponse, UnixGroupToken, + UnixUserToken, UserAuthToken, }; use std::str::FromStr; @@ -354,6 +354,7 @@ pub trait IdmServerTransaction<'a> { &self, audit: &mut AuditScope, uat: &UserAuthToken, + ct: Duration, ) -> Result { // From a UAT, get the current identity and associated information. let entry = self @@ -364,11 +365,52 @@ pub trait IdmServerTransaction<'a> { e })?; - // TODO #64: Now apply claims from the uat into the Entry - // to allow filtering. - - // TODO #59: If the account is expiredy, do not allow the event + // #59: If the account is expired, do not allow the event // to proceed + let valid = Account::check_within_valid_time( + ct, + entry.get_ava_single_datetime("account_valid_from").as_ref(), + entry.get_ava_single_datetime("account_expire").as_ref(), + ); + + if !valid { + lsecurity!( + audit, + "Account has expired or is not yet valid, not allowing to proceed" + ); + return Err(OperationError::SessionExpired); + } + + // #64: Now apply claims from the uat into the Entry + // to allow filtering. + /* + entry.insert_claim(match &uat.auth_type { + AuthType::Anonymous => "authtype_anonymous", + AuthType::UnixPassword => "authtype_unixpassword", + AuthType::Password => "authtype_password", + AuthType::GeneratedPassword => "authtype_generatedpassword", + AuthType::Webauthn => "authtype_webauthn", + AuthType::PasswordMfa => "authtype_passwordmfa", + }); + + match &uat.auth_type { + AuthType::Anonymous | AuthType::UnixPassword | AuthType::Password => {} + AuthType::GeneratedPassword | AuthType::Webauthn | AuthType::PasswordMfa => { + entry.insert_claim("authlevel_strong") + } + }; + + match &uat.auth_type { + AuthType::Anonymous => {} + AuthType::UnixPassword + | AuthType::Password + | AuthType::GeneratedPassword + | AuthType::Webauthn => entry.insert_claim("authclass_single"), + AuthType::PasswordMfa => entry.insert_claim("authclass_mfa"), + }; + */ + + ltrace!(audit, "Applied claims -> {:?}", entry.get_ava_set("claim")); let limits = Limits::from_uat(uat); Ok(Identity { @@ -479,7 +521,7 @@ impl<'a> IdmServerAuthTransaction<'a> { }; let (auth_session, state) = if is_valid { - AuthSession::new(au, account, &init.appid, self.webauthn, ct) + AuthSession::new(au, account, self.webauthn, ct) } else { // it's softlocked, don't even bother. lsecurity!(au, "Account is softlocked."); @@ -764,7 +806,7 @@ impl<'a> IdmServerAuthTransaction<'a> { Ok(Some(LdapBoundToken { uuid: *UUID_ANONYMOUS, effective_uat: account - .to_userauthtoken(au.uuid, &[], ct) + .to_userauthtoken(au.uuid, ct, AuthType::Anonymous) .ok_or(OperationError::InvalidState) .map_err(|e| { ladmin_error!(au, "Unable to generate effective_uat -> {:?}", e); @@ -826,7 +868,7 @@ impl<'a> IdmServerAuthTransaction<'a> { spn: account.spn, uuid: account.uuid, effective_uat: anon_account - .to_userauthtoken(au.uuid, &[], ct) + .to_userauthtoken(au.uuid, ct, AuthType::UnixPassword) .ok_or(OperationError::InvalidState) .map_err(|e| { ladmin_error!(au, "Unable to generate effective_uat -> {:?}", e); @@ -1074,7 +1116,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // Get the modifications we *want* to perform. let modlist = account - .gen_password_mod(pce.cleartext.as_str(), &pce.appid, self.crypto_policy) + .gen_password_mod(pce.cleartext.as_str(), self.crypto_policy) .map_err(|e| { ladmin_error!(au, "Failed to generate password mod {:?}", e); e @@ -1233,8 +1275,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { &mut self, au: &mut AuditScope, name: &str, - cleartext: &str, - ) -> Result<(), OperationError> { + cleartext: Option<&str>, + ) -> Result { // name to uuid let target = self.qs_write.name_to_uuid(au, name).map_err(|e| { ladmin_error!(au, "name to uuid failed {:?}", e); @@ -1243,8 +1285,12 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let account = self.target_to_account(au, &target)?; + let cleartext = cleartext + .map(|s| s.to_string()) + .unwrap_or_else(password_from_random); + let modlist = account - .gen_password_recover_mod(cleartext, self.crypto_policy) + .gen_generatedpassword_recover_mod(&cleartext, self.crypto_policy) .map_err(|e| { ladmin_error!(au, "Failed to generate password mod {:?}", e); e @@ -1263,7 +1309,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { e })?; - Ok(()) + Ok(cleartext) } pub fn generate_account_password( @@ -1283,7 +1329,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // it returns a modify let modlist = account - .gen_password_mod(cleartext.as_str(), &gpe.appid, self.crypto_policy) + .gen_generatedpassword_recover_mod(cleartext.as_str(), self.crypto_policy) .map_err(|e| { ladmin_error!(au, "Unable to generate password mod {:?}", e); e @@ -1473,8 +1519,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let sessionid = uuid_from_duration(ct, self.sid); let origin = (>e.ident.origin).into(); - let label = gte.label.clone(); - let (session, next) = MfaRegSession::totp_new(origin, account, label).map_err(|e| { + let (session, next) = MfaRegSession::totp_new(origin, account).map_err(|e| { ladmin_error!(au, "Unable to start totp MfaRegSession {:?}", e); e })?; @@ -1585,16 +1630,12 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let account = self.target_to_account(au, &pwu.target_uuid)?; // check, does the pw still match? - let same = account.check_credential_pw(pwu.existing_password.as_str(), &pwu.appid)?; + let same = account.check_credential_pw(pwu.existing_password.as_str())?; // if yes, gen the pw mod and apply. if same { let modlist = account - .gen_password_mod( - pwu.existing_password.as_str(), - &pwu.appid, - self.crypto_policy, - ) + .gen_password_mod(pwu.existing_password.as_str(), self.crypto_policy) .map_err(|e| { ladmin_error!(au, "Unable to generate password mod {:?}", e); e @@ -1739,7 +1780,7 @@ mod tests { use crate::prelude::*; use kanidm_proto::v1::OperationError; use kanidm_proto::v1::SetCredentialResponse; - use kanidm_proto::v1::{AuthAllowed, AuthMech}; + use kanidm_proto::v1::{AuthAllowed, AuthMech, AuthType}; use crate::idm::server::{IdmServer, IdmServerTransaction}; // , IdmServerDelayed; @@ -2184,7 +2225,7 @@ mod tests { idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); assert!(idms_prox_write.set_account_password(au, &pce).is_ok()); @@ -2199,7 +2240,7 @@ mod tests { idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { - let pce = PasswordChangeEvent::new_internal(&UUID_ANONYMOUS, TEST_PASSWORD, None); + let pce = PasswordChangeEvent::new_internal(&UUID_ANONYMOUS, TEST_PASSWORD); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); assert!(idms_prox_write.set_account_password(au, &pce).is_err()); @@ -2271,7 +2312,7 @@ mod tests { .expect("Failed to reset radius credential 1"); // Try and set that as the main account password, should fail. - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, r1.as_str(), None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, r1.as_str()); let e = idms_prox_write.set_account_password(au, &pce); assert!(e.is_err()); @@ -2316,17 +2357,17 @@ mod tests { // len check let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password", None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password"); let e = idms_prox_write.set_account_password(au, &pce); assert!(e.is_err()); // zxcvbn check - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password1234", None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password1234"); let e = idms_prox_write.set_account_password(au, &pce); assert!(e.is_err()); // Check the "name" checking works too (I think admin may hit a common pw rule first) - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "admin_nta", None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "admin_nta"); let e = idms_prox_write.set_account_password(au, &pce); assert!(e.is_err()); @@ -2334,7 +2375,6 @@ mod tests { let pce = PasswordChangeEvent::new_internal( &UUID_ADMIN, "demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1", - None, ); let e = idms_prox_write.set_account_password(au, &pce); assert!(e.is_err()); @@ -2352,7 +2392,7 @@ mod tests { let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); // Check that the badlist password inserted is rejected. - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "bad@no3IBTyqHu$list", None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "bad@no3IBTyqHu$list"); let e = idms_prox_write.set_account_password(au, &pce); assert!(e.is_err()); @@ -2585,7 +2625,7 @@ mod tests { idms_prox_write.expire_mfareg_sessions(expire.clone()); // Set a password. - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD); assert!(idms_prox_write.set_account_password(au, &pce).is_ok()); // == reg, but change the event source part way in the process (failure) @@ -3354,7 +3394,7 @@ mod tests { _ => assert!(false), }; // Reg a pw. - let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None); + let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD); assert!(idms_prox_write.set_account_password(au, &pce).is_ok()); // Now remove, it will work. idms_prox_write @@ -3394,4 +3434,110 @@ mod tests { } }) } + + #[test] + fn test_idm_uat_claim_insertion() { + run_idm_test!(|_qs: &QueryServer, + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed, + audit: &mut AuditScope| { + let ct = Duration::from_secs(TEST_CURRENT_TIME); + let mut idms_prox_write = idms.proxy_write(ct.clone()); + + // get an account. + let account = idms_prox_write + .target_to_account(audit, &UUID_ADMIN) + .expect("account must exist"); + + // create some fake uats + // process them and see what claims fall out :D + let session_id = uuid::Uuid::new_v4(); + + // For the different auth types, check that we get the correct claims: + + // == anonymous + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Anonymous) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(audit, &uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_anonymous")); + // Does NOT have this + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_single")); + assert!(!ident.has_claim("authclass_mfa")); + + // == unixpassword + let uat = account + .to_userauthtoken(session_id, ct, AuthType::UnixPassword) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(audit, &uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_unixpassword")); + assert!(!ident.has_claim("authclass_single")); + // Does NOT have this + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_mfa")); + + // == password + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Password) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(audit, &uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_password")); + assert!(!ident.has_claim("authclass_single")); + // Does NOT have this + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_mfa")); + + // == generatedpassword + let uat = account + .to_userauthtoken(session_id, ct, AuthType::GeneratedPassword) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(audit, &uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_generatedpassword")); + assert!(!ident.has_claim("authclass_single")); + assert!(!ident.has_claim("authlevel_strong")); + // Does NOT have this + assert!(!ident.has_claim("authclass_mfa")); + + // == webauthn + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Webauthn) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(audit, &uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_webauthn")); + assert!(!ident.has_claim("authclass_single")); + assert!(!ident.has_claim("authlevel_strong")); + // Does NOT have this + assert!(!ident.has_claim("authclass_mfa")); + + // == passwordmfa + let uat = account + .to_userauthtoken(session_id, ct, AuthType::PasswordMfa) + .expect("Unable to create uat"); + let ident = idms_prox_write + .process_uat_to_identity(audit, &uat, ct) + .expect("Unable to process uat"); + + assert!(!ident.has_claim("authtype_passwordmfa")); + assert!(!ident.has_claim("authlevel_strong")); + assert!(!ident.has_claim("authclass_mfa")); + // Does NOT have this + assert!(!ident.has_claim("authclass_single")); + }) + } } diff --git a/kanidmd/src/lib/ldap.rs b/kanidmd/src/lib/ldap.rs index 7fb5b5bf3..6ef06cc85 100644 --- a/kanidmd/src/lib/ldap.rs +++ b/kanidmd/src/lib/ldap.rs @@ -232,7 +232,7 @@ impl LdapServer { ladmin_info!(audit, "LDAP Search Request LDAP Attrs -> {:?}", l_attrs); ladmin_info!(audit, "LDAP Search Request Mapped Attrs -> {:?}", k_attrs); - // let ct = duration_from_epoch_now(); + let ct = duration_from_epoch_now(); let idm_read = idms.proxy_read_async().await; lperf_segment!(audit, "ldap::do_search", || { // Now start the txn - we need it for resolving filter components. @@ -271,7 +271,7 @@ impl LdapServer { // ! Remember, searchEvent wraps to ignore hidden for us. let se = lperf_trace_segment!(audit, "ldap::do_search", || { let ident = idm_read - .process_uat_to_identity(audit, &uat.effective_uat) + .process_uat_to_identity(audit, &uat.effective_uat, ct) .map_err(|e| { ladmin_error!(audit, "Invalid identity: {:?}", e); e diff --git a/kanidmd/src/lib/plugins/password_import.rs b/kanidmd/src/lib/plugins/password_import.rs index 65599ff61..1fe7e7707 100644 --- a/kanidmd/src/lib/plugins/password_import.rs +++ b/kanidmd/src/lib/plugins/password_import.rs @@ -234,7 +234,7 @@ mod tests { }"#, ); - let totp = Totp::generate_secure("test_totp".to_string(), TOTP_DEFAULT_STEP); + let totp = Totp::generate_secure(TOTP_DEFAULT_STEP); let p = CryptoPolicy::minimum(); let c = Credential::new_password_only(&p, "password") .unwrap() diff --git a/kanidmd/src/lib/schema.rs b/kanidmd/src/lib/schema.rs index 1a1f74d1d..ff7460840 100644 --- a/kanidmd/src/lib/schema.rs +++ b/kanidmd/src/lib/schema.rs @@ -1080,12 +1080,14 @@ impl<'a> SchemaWriteTransaction<'a> { SchemaAttribute { name: AttrString::from("claim"), uuid: *UUID_SCHEMA_ATTR_CLAIM, - description: String::from("The spn of a claim this entry holds"), + description: String::from( + "The string identifier of an extracted claim that can be filtered", + ), multivalue: true, unique: false, phantom: true, index: vec![], - syntax: SyntaxType::SecurityPrincipalName, + syntax: SyntaxType::Utf8StringInsensitive, }, ); self.attributes.insert( diff --git a/kanidmd/src/server/main.rs b/kanidmd/src/server/main.rs index 9addf9ede..3749e989f 100644 --- a/kanidmd/src/server/main.rs +++ b/kanidmd/src/server/main.rs @@ -273,14 +273,7 @@ async fn main() { } KanidmdOpt::RecoverAccount(raopt) => { eprintln!("Running account recovery ..."); - let password = match rpassword::prompt_password_stderr("new password: ") { - Ok(pw) => pw, - Err(e) => { - eprintln!("Failed to get password from prompt {:?}", e); - std::process::exit(1); - } - }; - recover_account_core(&config, &raopt.name, &password); + recover_account_core(&config, &raopt.name); } KanidmdOpt::Reindex(_copt) => { eprintln!("Running in reindex mode ...");