64 120 session claims (#462)

This commit is contained in:
Firstyear 2021-06-02 09:30:37 +10:00 committed by GitHub
parent 033b977906
commit 807af81184
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 375 additions and 293 deletions

View file

@ -901,9 +901,8 @@ impl KanidmAsyncClient {
pub async fn idm_account_primary_credential_generate_totp( pub async fn idm_account_primary_credential_generate_totp(
&self, &self,
id: &str, id: &str,
label: &str,
) -> Result<(Uuid, TotpSecret), ClientError> { ) -> Result<(Uuid, TotpSecret), ClientError> {
let r = SetCredentialRequest::TotpGenerate(label.to_string()); let r = SetCredentialRequest::TotpGenerate;
let res: Result<SetCredentialResponse, ClientError> = self let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request( .perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(), format!("/v1/account/{}/_credential/primary", id).as_str(),

View file

@ -605,11 +605,10 @@ impl KanidmClient {
pub fn idm_account_primary_credential_generate_totp( pub fn idm_account_primary_credential_generate_totp(
&self, &self,
id: &str, id: &str,
label: &str,
) -> Result<(Uuid, TotpSecret), ClientError> { ) -> Result<(Uuid, TotpSecret), ClientError> {
tokio_block_on( tokio_block_on(
self.asclient self.asclient
.idm_account_primary_credential_generate_totp(id, label), .idm_account_primary_credential_generate_totp(id),
) )
} }

View file

@ -57,7 +57,7 @@ static DEFAULT_NOT_HP_GROUP_NAMES: [&str; 2] =
["idm_account_unix_extend_priv", "idm_group_unix_extend_priv"]; ["idm_account_unix_extend_priv", "idm_group_unix_extend_priv"];
fn create_user(rsclient: &KanidmClient, id: &str, group_name: &str) -> () { 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 // Create group and add to user to test read attr: member_of
let _ = match rsclient.idm_group_get(&group_name).unwrap() { let _ = match rsclient.idm_group_get(&group_name).unwrap() {

View file

@ -756,7 +756,7 @@ fn test_server_rest_totp_auth_lifecycle() {
.idm_account_primary_credential_set_password("demo_account", "sohdi3iuHo6mai7noh0a") .idm_account_primary_credential_set_password("demo_account", "sohdi3iuHo6mai7noh0a")
.is_ok()); .is_ok());
let (sessionid, tok) = rsclient let (sessionid, tok) = rsclient
.idm_account_primary_credential_generate_totp("demo_account", "demo") .idm_account_primary_credential_generate_totp("demo_account")
.unwrap(); .unwrap();
let r_tok: Totp = tok.into(); let r_tok: Totp = tok.into();

View file

@ -150,18 +150,33 @@ pub struct Application {
} }
*/ */
// The currently authenticated user, and any required metadata for them #[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)]
// to properly authorise them. This is similar in nature to oauth and the krb #[serde(rename_all = "lowercase")]
// PAC/PAD structures. Currently we only use this internally, but we should pub enum AuthType {
// consider making it "parseable" by the client so they can have per-session Anonymous,
// group/authorisation data. UnixPassword,
// Password,
// This structure and how it works will *very much* change over time from this GeneratedPassword,
// point onward! Webauthn,
// PasswordMfa,
// It's likely that this must have a relationship to the server's user structure // PasswordWebauthn,
// and to the Entry so that filters or access controls can be applied. // 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)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub struct UserAuthToken { pub struct UserAuthToken {
pub session_id: Uuid, pub session_id: Uuid,
// When this data should be considered invalid. Interpretation // When this data should be considered invalid. Interpretation
@ -175,6 +190,7 @@ pub struct UserAuthToken {
// pub application: Option<Application>, // pub application: Option<Application>,
pub groups: Vec<Group>, pub groups: Vec<Group>,
pub claims: Vec<Claim>, pub claims: Vec<Claim>,
pub auth_type: AuthType,
// Should we allow supplemental ava's to be added on request? // Should we allow supplemental ava's to be added on request?
pub lim_uidx: bool, pub lim_uidx: bool,
pub lim_rmax: usize, pub lim_rmax: usize,
@ -649,7 +665,7 @@ pub struct AuthResponse {
pub enum SetCredentialRequest { pub enum SetCredentialRequest {
Password(String), Password(String),
GeneratePassword, GeneratePassword,
TotpGenerate(String), TotpGenerate,
TotpVerify(Uuid, u32), TotpVerify(Uuid, u32),
TotpRemove, TotpRemove,
// Start the rego. // Start the rego.

View file

@ -146,7 +146,6 @@ impl AccountOpt {
let client = acsopt.copt.to_client(); let client = acsopt.copt.to_client();
let (session, tok) = match client.idm_account_primary_credential_generate_totp( let (session, tok) = match client.idm_account_primary_credential_generate_totp(
acsopt.aopts.account_id.as_str(), acsopt.aopts.account_id.as_str(),
acsopt.tag.as_str(),
) { ) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {

View file

@ -158,24 +158,29 @@ pub struct AccountCreateOpt {
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
pub enum AccountCredential { pub enum AccountCredential {
/// Set this accounts password
#[structopt(name = "set_password")] #[structopt(name = "set_password")]
SetPassword(AccountCredentialSet), SetPassword(AccountCredentialSet),
#[structopt(name = "generate_password")] /// Register a new webauthn device to this account.
GeneratePassword(AccountCredentialSet),
#[structopt(name = "register_webauthn")] #[structopt(name = "register_webauthn")]
RegisterWebauthn(AccountNamedTagOpt), RegisterWebauthn(AccountNamedTagOpt),
/// Remove a webauthn device from this account
#[structopt(name = "remove_webauthn")] #[structopt(name = "remove_webauthn")]
RemoveWebauthn(AccountNamedTagOpt), RemoveWebauthn(AccountNamedTagOpt),
/// Set the TOTP credential of the account. If a TOTP already exists, on a successful /// Set the TOTP credential of the account. If a TOTP already exists, on a successful
/// registration, this will replace it. /// registration, this will replace it.
#[structopt(name = "set_totp")] #[structopt(name = "register_totp")]
RegisterTotp(AccountNamedTagOpt), RegisterTotp(AccountNamedOpt),
/// Remove TOTP from the account. If no TOTP exists, no action is taken. /// Remove TOTP from the account. If no TOTP exists, no action is taken.
#[structopt(name = "remove_totp")] #[structopt(name = "remove_totp")]
RemoveTotp(AccountNamedOpt), RemoveTotp(AccountNamedOpt),
/// Show the status of the accounts credentials. /// Show the status of the accounts credentials.
#[structopt(name = "status")] #[structopt(name = "status")]
Status(AccountNamedOpt), 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)] #[derive(Debug, StructOpt)]

View file

@ -82,7 +82,7 @@ impl QueryServerReadV1 {
let res = lperf_op_segment!(&mut audit, "actors::v1_read::handle<SearchMessage>", || { let res = lperf_op_segment!(&mut audit, "actors::v1_read::handle<SearchMessage>", || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -185,7 +185,7 @@ impl QueryServerReadV1 {
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .validate_and_parse_uat(&mut audit, uat.as_deref(), ct)
.and_then(|uat| { .and_then(|uat| {
idms_prox_read idms_prox_read
.process_uat_to_identity(&mut audit, &uat) .process_uat_to_identity(&mut audit, &uat, ct)
.map(|i| (uat, i)) .map(|i| (uat, i))
}) })
.map_err(|e| { .map_err(|e| {
@ -252,7 +252,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -305,7 +305,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -356,7 +356,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -429,7 +429,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -486,7 +486,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -545,7 +545,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -600,7 +600,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -673,7 +673,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -749,7 +749,7 @@ impl QueryServerReadV1 {
// resolve the id // resolve the id
let ident = idm_auth let ident = idm_auth
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -804,7 +804,7 @@ impl QueryServerReadV1 {
|| { || {
let ident = idms_prox_read let ident = idms_prox_read
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e

View file

@ -73,7 +73,7 @@ impl QueryServerWriteV1 {
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -126,7 +126,7 @@ impl QueryServerWriteV1 {
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -180,7 +180,7 @@ impl QueryServerWriteV1 {
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -234,7 +234,7 @@ impl QueryServerWriteV1 {
let ct = duration_from_epoch_now(); let ct = duration_from_epoch_now();
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(&mut audit, "Invalid identity: {:?}", e); ladmin_error!(&mut audit, "Invalid identity: {:?}", e);
e e
@ -287,7 +287,7 @@ impl QueryServerWriteV1 {
let ct = duration_from_epoch_now(); let ct = duration_from_epoch_now();
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -339,7 +339,7 @@ impl QueryServerWriteV1 {
let ct = duration_from_epoch_now(); let ct = duration_from_epoch_now();
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -387,7 +387,7 @@ impl QueryServerWriteV1 {
let ct = duration_from_epoch_now(); let ct = duration_from_epoch_now();
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -425,7 +425,6 @@ impl QueryServerWriteV1 {
&self, &self,
uat: Option<String>, uat: Option<String>,
uuid_or_name: String, uuid_or_name: String,
appid: Option<String>,
sac: SetCredentialRequest, sac: SetCredentialRequest,
eventid: Uuid, eventid: Uuid,
) -> Result<SetCredentialResponse, OperationError> { ) -> Result<SetCredentialResponse, OperationError> {
@ -443,7 +442,7 @@ impl QueryServerWriteV1 {
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -471,7 +470,6 @@ impl QueryServerWriteV1 {
ident, ident,
target_uuid, target_uuid,
cleartext, cleartext,
appid,
) )
.map_err(|e| { .map_err(|e| {
ladmin_error!( ladmin_error!(
@ -492,7 +490,6 @@ impl QueryServerWriteV1 {
// &idms_prox_write.qs_write, // &idms_prox_write.qs_write,
ident, ident,
target_uuid, target_uuid,
appid,
) )
.map_err(|e| { .map_err(|e| {
ladmin_error!( ladmin_error!(
@ -507,13 +504,12 @@ impl QueryServerWriteV1 {
.and_then(|r| idms_prox_write.commit(&mut audit).map(|_| r)) .and_then(|r| idms_prox_write.commit(&mut audit).map(|_| r))
.map(SetCredentialResponse::Token) .map(SetCredentialResponse::Token)
} }
SetCredentialRequest::TotpGenerate(label) => { SetCredentialRequest::TotpGenerate => {
let gte = GenerateTotpEvent::from_parts( let gte = GenerateTotpEvent::from_parts(
&mut audit, &mut audit,
// &idms_prox_write.qs_write, // &idms_prox_write.qs_write,
ident, ident,
target_uuid, target_uuid,
label,
) )
.map_err(|e| { .map_err(|e| {
ladmin_error!( ladmin_error!(
@ -655,7 +651,7 @@ impl QueryServerWriteV1 {
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -700,7 +696,7 @@ impl QueryServerWriteV1 {
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -758,7 +754,7 @@ impl QueryServerWriteV1 {
let ct = duration_from_epoch_now(); let ct = duration_from_epoch_now();
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -819,7 +815,7 @@ impl QueryServerWriteV1 {
let ct = duration_from_epoch_now(); let ct = duration_from_epoch_now();
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e
@ -1123,7 +1119,7 @@ impl QueryServerWriteV1 {
let ident = idms_prox_write let ident = idms_prox_write
.validate_and_parse_uat(&mut audit, uat.as_deref(), ct) .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| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e

View file

@ -388,10 +388,7 @@ async fn json_rest_event_delete_id_attr(
} }
} }
async fn json_rest_event_credential_put( async fn json_rest_event_credential_put(mut req: tide::Request<AppState>) -> tide::Result {
mut req: tide::Request<AppState>,
appid: Option<String>,
) -> tide::Result {
let uat = req.get_current_uat(); let uat = req.get_current_uat();
let uuid_or_name = req.get_url_param("id")?; let uuid_or_name = req.get_url_param("id")?;
let sac: SetCredentialRequest = req.body_json().await?; let sac: SetCredentialRequest = req.body_json().await?;
@ -400,7 +397,7 @@ async fn json_rest_event_credential_put(
let res = req let res = req
.state() .state()
.qe_w_ref .qe_w_ref
.handle_credentialset(uat, uuid_or_name, appid, sac, eventid) .handle_credentialset(uat, uuid_or_name, sac, eventid)
.await; .await;
to_tide_response(res, hvalue) to_tide_response(res, hvalue)
} }
@ -541,7 +538,7 @@ pub async fn account_id_delete(req: tide::Request<AppState>) -> tide::Result {
} }
pub async fn account_put_id_credential_primary(req: tide::Request<AppState>) -> tide::Result { pub async fn account_put_id_credential_primary(req: tide::Request<AppState>) -> 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<AppState>) -> tide::Result { pub async fn account_get_id_credential_status(req: tide::Request<AppState>) -> tide::Result {

View file

@ -390,7 +390,7 @@ pub fn verify_server_core(config: &Configuration) {
// Now add IDM server verifications? // 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 mut audit = AuditScope::new("recover_account", uuid::Uuid::new_v4(), config.log_level);
let schema = match Schema::new(&mut audit) { 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. // Run the password change.
let mut idms_prox_write = task::block_on(idms.proxy_write_async(duration_from_epoch_now())); 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) { match idms_prox_write.recover_account(&mut audit, &name, None) {
Ok(_) => match idms_prox_write.commit(&mut audit) { Ok(new_pw) => match idms_prox_write.commit(&mut audit) {
Ok(()) => { Ok(()) => {
audit.write_log(); audit.write_log();
info!("Password reset!"); info!("Password reset to -> {}", new_pw);
} }
Err(e) => { Err(e) => {
error!("A critical error during commit occured {:?}", e); error!("A critical error during commit occured {:?}", e);
@ -515,7 +515,7 @@ pub async fn create_server_core(config: Configuration) -> Result<(), ()> {
Some(itc) => { Some(itc) => {
let mut idms_prox_write = let mut idms_prox_write =
task::block_on(idms.proxy_write_async(duration_from_epoch_now())); 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(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
audit.write_log(); audit.write_log();

View file

@ -341,6 +341,13 @@ impl Credential {
Password::new(policy, cleartext).map(Self::new_from_password) Password::new(policy, cleartext).map(Self::new_from_password)
} }
pub fn new_generatedpassword_only(
policy: &CryptoPolicy,
cleartext: &str,
) -> Result<Self, OperationError> {
Password::new(policy, cleartext).map(Self::new_from_generatedpassword)
}
pub fn new_webauthn_only(label: String, cred: WebauthnCredential) -> Self { pub fn new_webauthn_only(label: String, cred: WebauthnCredential) -> Self {
let mut webauthn_map = Map::new(); let mut webauthn_map = Map::new();
webauthn_map.insert(label, cred); webauthn_map.insert(label, cred);
@ -593,8 +600,9 @@ impl Credential {
pub(crate) fn update_password(&self, pw: Password) -> Self { pub(crate) fn update_password(&self, pw: Password) -> Self {
let type_ = match &self.type_ { let type_ = match &self.type_ {
CredentialType::Password(_) => CredentialType::Password(pw), CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => {
CredentialType::GeneratedPassword(_) => CredentialType::GeneratedPassword(pw), CredentialType::Password(pw)
}
CredentialType::PasswordMfa(_, totp, wan) => { CredentialType::PasswordMfa(_, totp, wan) => {
CredentialType::PasswordMfa(pw, totp.clone(), wan.clone()) 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 { pub(crate) fn new_from_password(pw: Password) -> Self {
Credential { Credential {
type_: CredentialType::Password(pw), type_: CredentialType::Password(pw),

View file

@ -60,7 +60,6 @@ impl TotpAlgo {
/// https://tools.ietf.org/html/rfc6238 which relies on https://tools.ietf.org/html/rfc4226 /// https://tools.ietf.org/html/rfc6238 which relies on https://tools.ietf.org/html/rfc4226
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Totp { pub struct Totp {
label: String,
secret: Vec<u8>, secret: Vec<u8>,
pub(crate) step: u64, pub(crate) step: u64,
algo: TotpAlgo, algo: TotpAlgo,
@ -76,7 +75,6 @@ impl TryFrom<DbTotpV1> for Totp {
DbTotpAlgoV1::S512 => TotpAlgo::Sha512, DbTotpAlgoV1::S512 => TotpAlgo::Sha512,
}; };
Ok(Totp { Ok(Totp {
label: value.l,
secret: value.k, secret: value.k,
step: value.s, step: value.s,
algo, algo,
@ -87,7 +85,6 @@ impl TryFrom<DbTotpV1> for Totp {
impl From<ProtoTotp> for Totp { impl From<ProtoTotp> for Totp {
fn from(value: ProtoTotp) -> Self { fn from(value: ProtoTotp) -> Self {
Totp { Totp {
label: "test_token".to_string(),
secret: value.secret, secret: value.secret,
algo: match value.algo { algo: match value.algo {
ProtoTotpAlgo::Sha1 => TotpAlgo::Sha1, ProtoTotpAlgo::Sha1 => TotpAlgo::Sha1,
@ -100,31 +97,21 @@ impl From<ProtoTotp> for Totp {
} }
impl Totp { impl Totp {
pub fn new(label: String, secret: Vec<u8>, step: u64, algo: TotpAlgo) -> Self { pub fn new(secret: Vec<u8>, step: u64, algo: TotpAlgo) -> Self {
Totp { Totp { secret, step, algo }
label,
secret,
step,
algo,
}
} }
// Create a new token with secure key and 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 mut rng = rand::thread_rng();
let secret: Vec<u8> = (0..SECRET_SIZE_BYTES).map(|_| rng.gen()).collect(); let secret: Vec<u8> = (0..SECRET_SIZE_BYTES).map(|_| rng.gen()).collect();
let algo = TotpAlgo::Sha512; let algo = TotpAlgo::Sha512;
Totp { Totp { secret, step, algo }
label,
secret,
step,
algo,
}
} }
pub(crate) fn to_dbtotpv1(&self) -> DbTotpV1 { pub(crate) fn to_dbtotpv1(&self) -> DbTotpV1 {
DbTotpV1 { DbTotpV1 {
l: self.label.clone(), l: "totp".to_string(),
k: self.secret.clone(), k: self.secret.clone(),
s: self.step, s: self.step,
a: match self.algo { a: match self.algo {
@ -204,16 +191,16 @@ mod tests {
#[test] #[test]
fn hotp_basic() { 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)); 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)); 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)); assert!(otp_sha512.digest(0) == Ok(674061));
} }
fn do_test(key: Vec<u8>, algo: TotpAlgo, secs: u64, step: u64, expect: Result<u32, TotpError>) { fn do_test(key: Vec<u8>, algo: TotpAlgo, secs: u64, step: u64, expect: Result<u32, TotpError>) {
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 d = Duration::from_secs(secs);
let r = otp.do_totp_duration_from_epoch(&d); let r = otp.do_totp_duration_from_epoch(&d);
debug!( debug!(
@ -281,12 +268,7 @@ mod tests {
fn totp_allow_one_previous() { fn totp_allow_one_previous() {
let key = vec![0x00, 0xaa, 0xbb, 0xcc]; let key = vec![0x00, 0xaa, 0xbb, 0xcc];
let secs = 1585369780; let secs = 1585369780;
let otp = Totp::new( let otp = Totp::new(key.clone(), TOTP_DEFAULT_STEP, TotpAlgo::Sha512);
"".to_string(),
key.clone(),
TOTP_DEFAULT_STEP,
TotpAlgo::Sha512,
);
let d = Duration::from_secs(secs); let d = Duration::from_secs(secs);
// Step // Step
assert!(otp.verify(952181, &d)); assert!(otp.verify(952181, &d));

View file

@ -867,6 +867,10 @@ impl Entry<EntrySealed, EntryCommitted> {
self self
} }
pub fn insert_claim(&mut self, value: &str) {
self.add_ava_int("claim", Value::new_iutf8(value));
}
pub fn compare(&self, rhs: &Entry<EntrySealed, EntryCommitted>) -> bool { pub fn compare(&self, rhs: &Entry<EntrySealed, EntryCommitted>) -> bool {
compare_attrs(&self.attrs, &rhs.attrs) compare_attrs(&self.attrs, &rhs.attrs)
} }

View file

@ -133,4 +133,14 @@ impl Identity {
pub fn get_event_origin_id(&self) -> IdentityId { pub fn get_event_origin_id(&self) -> IdentityId {
IdentityId::from(&self.origin) 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)),
}
}
} }

View file

@ -3,13 +3,12 @@ use crate::prelude::*;
use kanidm_proto::v1::CredentialStatus; use kanidm_proto::v1::CredentialStatus;
use kanidm_proto::v1::OperationError; use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::UserAuthToken; use kanidm_proto::v1::{AuthType, UserAuthToken};
use crate::constants::UUID_ANONYMOUS; use crate::constants::UUID_ANONYMOUS;
use crate::credential::policy::CryptoPolicy; use crate::credential::policy::CryptoPolicy;
use crate::credential::totp::Totp; use crate::credential::totp::Totp;
use crate::credential::{softlock::CredSoftLockPolicy, Credential}; use crate::credential::{softlock::CredSoftLockPolicy, Credential};
use crate::idm::claim::Claim;
use crate::idm::group::Group; use crate::idm::group::Group;
use crate::modify::{ModifyInvalid, ModifyList}; use crate::modify::{ModifyInvalid, ModifyList};
use crate::value::{PartialValue, Value}; use crate::value::{PartialValue, Value};
@ -155,12 +154,15 @@ impl Account {
try_from_entry!(value, vec![]) 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( pub(crate) fn to_userauthtoken(
&self, &self,
session_id: Uuid, session_id: Uuid,
claims: &[Claim],
ct: Duration, ct: Duration,
auth_type: AuthType,
) -> Option<UserAuthToken> { ) -> Option<UserAuthToken> {
// This could consume self? // This could consume self?
// The cred handler provided is what authenticated this user, so we can use it to // The cred handler provided is what authenticated this user, so we can use it to
@ -179,7 +181,9 @@ impl Account {
uuid: self.uuid, uuid: self.uuid,
// application: None, // application: None,
groups: self.groups.iter().map(|g| g.to_proto()).collect(), 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? // What's the best way to get access to these limits with regard to claims/other?
lim_uidx: false, lim_uidx: false,
lim_rmax: 128, 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 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 // If current time greater than strat time window
vft < &cot vft <= &cot
} else { } else {
// We have no time, not expired. // We have no time, not expired.
true true
}; };
let vmax = if let Some(ext) = &self.expire { let vmax = if let Some(ext) = expire {
// If exp greater than ct then expired. // If exp greater than ct then expired.
&cot < ext &cot <= ext
} else { } else {
// If not present, we are not expired // If not present, we are not expired
true true
@ -209,6 +217,10 @@ impl Account {
vmin && vmax 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 { pub fn primary_cred_uuid(&self) -> Uuid {
match &self.primary { match &self.primary {
Some(cred) => cred.uuid, Some(cred) => cred.uuid,
@ -226,12 +238,12 @@ impl Account {
self.uuid == *UUID_ANONYMOUS self.uuid == *UUID_ANONYMOUS
} }
pub(crate) fn gen_password_recover_mod( pub(crate) fn gen_generatedpassword_recover_mod(
&self, &self,
cleartext: &str, cleartext: &str,
crypto_policy: &CryptoPolicy, crypto_policy: &CryptoPolicy,
) -> Result<ModifyList<ModifyInvalid>, OperationError> { ) -> Result<ModifyList<ModifyInvalid>, 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); let vcred = Value::new_credential("primary", ncred);
Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
} }
@ -239,16 +251,8 @@ impl Account {
pub(crate) fn gen_password_mod( pub(crate) fn gen_password_mod(
&self, &self,
cleartext: &str, cleartext: &str,
appid: &Option<String>,
crypto_policy: &CryptoPolicy, crypto_policy: &CryptoPolicy,
) -> Result<ModifyList<ModifyInvalid>, OperationError> { ) -> Result<ModifyList<ModifyInvalid>, 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),
None => {
// TODO #59: Enforce PW policy. Can we allow this change?
match &self.primary { match &self.primary {
// Change the cred // Change the cred
Some(primary) => { Some(primary) => {
@ -263,8 +267,6 @@ impl Account {
Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
} }
} }
} // no appid
}
} }
pub(crate) fn gen_totp_mod( pub(crate) fn gen_totp_mod(
@ -355,19 +357,11 @@ impl Account {
} }
} }
pub(crate) fn check_credential_pw( pub(crate) fn check_credential_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
&self, self.primary
cleartext: &str,
appid: &Option<String>,
) -> Result<bool, OperationError> {
match appid {
Some(_) => Err(OperationError::InvalidState),
None => self
.primary
.as_ref() .as_ref()
.ok_or(OperationError::InvalidState) .ok_or(OperationError::InvalidState)
.and_then(|cred| cred.password_ref().and_then(|pw| pw.verify(cleartext))), .and_then(|cred| cred.password_ref().and_then(|pw| pw.verify(cleartext)))
}
} }
pub(crate) fn regenerate_radius_secret_mod( pub(crate) fn regenerate_radius_secret_mod(

View file

@ -1,10 +1,9 @@
use crate::idm::account::Account; use crate::idm::account::Account;
use crate::idm::claim::Claim;
use crate::idm::AuthState; use crate::idm::AuthState;
use crate::prelude::*; use crate::prelude::*;
use hashbrown::HashSet; use hashbrown::HashSet;
use kanidm_proto::v1::OperationError; 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}; 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"; const PW_BADLIST_MSG: &str = "password is in badlist";
enum CredState { enum CredState {
Success(Vec<Claim>), Success(AuthType),
Continue(Vec<AuthAllowed>), Continue(Vec<AuthAllowed>),
Denied(&'static str), Denied(&'static str),
} }
@ -67,7 +66,7 @@ struct CredWebauthn {
enum CredHandler { enum CredHandler {
Anonymous, Anonymous,
// AppPassword (?) // AppPassword (?)
Password(Password), Password(Password, bool),
PasswordMfa(Box<CredMfa>), PasswordMfa(Box<CredMfa>),
Webauthn(CredWebauthn), Webauthn(CredWebauthn),
// Webauthn + Password // Webauthn + Password
@ -81,9 +80,8 @@ impl CredHandler {
webauthn: &Webauthn<WebauthnDomainConfig>, webauthn: &Webauthn<WebauthnDomainConfig>,
) -> Result<Self, ()> { ) -> Result<Self, ()> {
match &c.type_ { match &c.type_ {
CredentialType::Password(pw) | CredentialType::GeneratedPassword(pw) => { CredentialType::Password(pw) => Ok(CredHandler::Password(pw.clone(), false)),
Ok(CredHandler::Password(pw.clone())) CredentialType::GeneratedPassword(pw) => Ok(CredHandler::Password(pw.clone(), true)),
}
CredentialType::PasswordMfa(pw, maybe_totp, maybe_wan) => { CredentialType::PasswordMfa(pw, maybe_totp, maybe_wan) => {
let wan = if !maybe_wan.is_empty() { let wan = if !maybe_wan.is_empty() {
webauthn webauthn
@ -152,7 +150,6 @@ impl CredHandler {
if let Err(_e) = async_tx.send(DelayedAction::PwUpgrade(PasswordUpgrade { if let Err(_e) = async_tx.send(DelayedAction::PwUpgrade(PasswordUpgrade {
target_uuid: who, target_uuid: who,
existing_password: cleartext.to_string(), existing_password: cleartext.to_string(),
appid: None,
})) { })) {
ladmin_warning!(au, "unable to queue delayed pwupgrade, continuing ... "); ladmin_warning!(au, "unable to queue delayed pwupgrade, continuing ... ");
}; };
@ -164,7 +161,7 @@ impl CredHandler {
AuthCredential::Anonymous => { AuthCredential::Anonymous => {
// For anonymous, no claims will ever be issued. // For anonymous, no claims will ever be issued.
lsecurity!(au, "Handler::Anonymous -> Result::Success"); lsecurity!(au, "Handler::Anonymous -> Result::Success");
CredState::Success(Vec::new()) CredState::Success(AuthType::Anonymous)
} }
_ => { _ => {
lsecurity!( lsecurity!(
@ -180,6 +177,7 @@ impl CredHandler {
au: &mut AuditScope, au: &mut AuditScope,
cred: &AuthCredential, cred: &AuthCredential,
pw: &mut Password, pw: &mut Password,
generated: bool,
who: Uuid, who: Uuid,
async_tx: &Sender<DelayedAction>, async_tx: &Sender<DelayedAction>,
pw_badlist_set: Option<&HashSet<String>>, pw_badlist_set: Option<&HashSet<String>>,
@ -198,7 +196,11 @@ impl CredHandler {
_ => { _ => {
lsecurity!(au, "Handler::Password -> Result::Success"); lsecurity!(au, "Handler::Password -> Result::Success");
Self::maybe_pw_upgrade(au, pw, who, cleartext.as_str(), async_tx); 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 { } else {
@ -312,7 +314,7 @@ impl CredHandler {
cleartext.as_str(), cleartext.as_str(),
async_tx, async_tx,
); );
CredState::Success(Vec::new()) CredState::Success(AuthType::PasswordMfa)
} }
} }
} else { } else {
@ -377,7 +379,7 @@ impl CredHandler {
ladmin_warning!(au, "unable to queue delayed webauthn counter increment, continuing ... "); ladmin_warning!(au, "unable to queue delayed webauthn counter increment, continuing ... ");
}; };
}; };
CredState::Success(Vec::new()) CredState::Success(AuthType::Webauthn)
}) })
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
wan_cred.state = CredVerifyState::Fail; wan_cred.state = CredVerifyState::Fail;
@ -409,8 +411,8 @@ impl CredHandler {
) -> CredState { ) -> CredState {
match self { match self {
CredHandler::Anonymous => Self::validate_anonymous(au, cred), CredHandler::Anonymous => Self::validate_anonymous(au, cred),
CredHandler::Password(ref mut pw) => { CredHandler::Password(ref mut pw, generated) => {
Self::validate_password(au, cred, pw, who, async_tx, pw_badlist_set) Self::validate_password(au, cred, pw, *generated, who, async_tx, pw_badlist_set)
} }
CredHandler::PasswordMfa(ref mut pw_mfa) => Self::validate_password_mfa( CredHandler::PasswordMfa(ref mut pw_mfa) => Self::validate_password_mfa(
au, au,
@ -431,7 +433,7 @@ impl CredHandler {
pub fn next_auth_allowed(&self) -> Vec<AuthAllowed> { pub fn next_auth_allowed(&self) -> Vec<AuthAllowed> {
match &self { match &self {
CredHandler::Anonymous => vec![AuthAllowed::Anonymous], CredHandler::Anonymous => vec![AuthAllowed::Anonymous],
CredHandler::Password(_) => vec![AuthAllowed::Password], CredHandler::Password(_, _) => vec![AuthAllowed::Password],
CredHandler::PasswordMfa(ref pw_mfa) => pw_mfa CredHandler::PasswordMfa(ref pw_mfa) => pw_mfa
.totp .totp
.iter() .iter()
@ -450,7 +452,7 @@ impl CredHandler {
fn can_proceed(&self, mech: &AuthMech) -> bool { fn can_proceed(&self, mech: &AuthMech) -> bool {
match (self, mech) { match (self, mech) {
(CredHandler::Anonymous, AuthMech::Anonymous) (CredHandler::Anonymous, AuthMech::Anonymous)
| (CredHandler::Password(_), AuthMech::Password) | (CredHandler::Password(_, _), AuthMech::Password)
| (CredHandler::PasswordMfa(_), AuthMech::PasswordMfa) | (CredHandler::PasswordMfa(_), AuthMech::PasswordMfa)
| (CredHandler::Webauthn(_), AuthMech::Webauthn) => true, | (CredHandler::Webauthn(_), AuthMech::Webauthn) => true,
(_, _) => false, (_, _) => false,
@ -460,7 +462,7 @@ impl CredHandler {
fn allows_mech(&self) -> AuthMech { fn allows_mech(&self) -> AuthMech {
match self { match self {
CredHandler::Anonymous => AuthMech::Anonymous, CredHandler::Anonymous => AuthMech::Anonymous,
CredHandler::Password(_) => AuthMech::Password, CredHandler::Password(_, _) => AuthMech::Password,
CredHandler::PasswordMfa(_) => AuthMech::PasswordMfa, CredHandler::PasswordMfa(_) => AuthMech::PasswordMfa,
CredHandler::Webauthn(_) => AuthMech::Webauthn, CredHandler::Webauthn(_) => AuthMech::Webauthn,
} }
@ -508,7 +510,6 @@ impl AuthSession {
pub fn new( pub fn new(
au: &mut AuditScope, au: &mut AuditScope,
account: Account, account: Account,
_appid: &Option<String>,
webauthn: &Webauthn<WebauthnDomainConfig>, webauthn: &Webauthn<WebauthnDomainConfig>,
ct: Duration, ct: Duration,
) -> (Option<Self>, AuthState) { ) -> (Option<Self>, AuthState) {
@ -657,11 +658,11 @@ impl AuthSession {
webauthn, webauthn,
pw_badlist_set, pw_badlist_set,
) { ) {
CredState::Success(claims) => { CredState::Success(auth_type) => {
lsecurity!(au, "Successful cred handling"); lsecurity!(au, "Successful cred handling");
let uat = self let uat = self
.account .account
.to_userauthtoken(au.uuid, &claims, *time) .to_userauthtoken(au.uuid, *time, auth_type)
.ok_or(OperationError::InvalidState)?; .ok_or(OperationError::InvalidState)?;
// Now encrypt and prepare the token for return to the client. // Now encrypt and prepare the token for return to the client.
@ -790,7 +791,6 @@ mod tests {
let (session, state) = AuthSession::new( let (session, state) = AuthSession::new(
&mut audit, &mut audit,
anon_account, anon_account,
&None,
&webauthn, &webauthn,
duration_from_epoch_now(), 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 { macro_rules! start_password_session {
( (
$audit:expr, $audit:expr,
@ -861,7 +832,6 @@ mod tests {
let (session, state) = AuthSession::new( let (session, state) = AuthSession::new(
$audit, $audit,
$account.clone(), $account.clone(),
&None,
$webauthn, $webauthn,
duration_from_epoch_now(), duration_from_epoch_now(),
); );
@ -1008,7 +978,6 @@ mod tests {
let (session, state) = AuthSession::new( let (session, state) = AuthSession::new(
$audit, $audit,
$account.clone(), $account.clone(),
&None,
$webauthn, $webauthn,
duration_from_epoch_now(), duration_from_epoch_now(),
); );
@ -1067,7 +1036,7 @@ mod tests {
let ts = Duration::from_secs(12345); let ts = Duration::from_secs(12345);
// manually load in a cred // 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 let totp_good = totp
.do_totp_duration_from_epoch(&ts) .do_totp_duration_from_epoch(&ts)
@ -1234,7 +1203,7 @@ mod tests {
let ts = Duration::from_secs(12345); let ts = Duration::from_secs(12345);
// manually load in a cred // 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 let totp_good = totp
.do_totp_duration_from_epoch(&ts) .do_totp_duration_from_epoch(&ts)
@ -1301,7 +1270,6 @@ mod tests {
let (session, state) = AuthSession::new( let (session, state) = AuthSession::new(
$audit, $audit,
$account.clone(), $account.clone(),
&None,
$webauthn, $webauthn,
duration_from_epoch_now(), duration_from_epoch_now(),
); );
@ -1706,7 +1674,7 @@ mod tests {
let (webauthn, mut wa, wan_cred) = setup_webauthn(account.name.as_str()); let (webauthn, mut wa, wan_cred) = setup_webauthn(account.name.as_str());
let hs512 = create_hs512(); 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 let totp_good = totp
.do_totp_duration_from_epoch(&ts) .do_totp_duration_from_epoch(&ts)
.expect("failed to perform totp."); .expect("failed to perform totp.");

View file

@ -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!();
}
}

View file

@ -10,7 +10,6 @@ pub(crate) enum DelayedAction {
pub(crate) struct PasswordUpgrade { pub(crate) struct PasswordUpgrade {
pub target_uuid: Uuid, pub target_uuid: Uuid,
pub existing_password: String, pub existing_password: String,
pub appid: Option<String>,
} }
pub(crate) struct UnixPasswordUpgrade { pub(crate) struct UnixPasswordUpgrade {

View file

@ -9,16 +9,14 @@ pub struct PasswordChangeEvent {
pub ident: Identity, pub ident: Identity,
pub target: Uuid, pub target: Uuid,
pub cleartext: String, pub cleartext: String,
pub appid: Option<String>,
} }
impl PasswordChangeEvent { impl PasswordChangeEvent {
pub fn new_internal(target: &Uuid, cleartext: &str, appid: Option<&str>) -> Self { pub fn new_internal(target: &Uuid, cleartext: &str) -> Self {
PasswordChangeEvent { PasswordChangeEvent {
ident: Identity::from_internal(), ident: Identity::from_internal(),
target: *target, target: *target,
cleartext: cleartext.to_string(), cleartext: cleartext.to_string(),
appid: appid.map(|v| v.to_string()),
} }
} }
@ -34,7 +32,6 @@ impl PasswordChangeEvent {
ident, ident,
target: u, target: u,
cleartext, cleartext,
appid: None,
}) })
} }
@ -44,13 +41,11 @@ impl PasswordChangeEvent {
ident: Identity, ident: Identity,
target: Uuid, target: Uuid,
cleartext: String, cleartext: String,
appid: Option<String>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
Ok(PasswordChangeEvent { Ok(PasswordChangeEvent {
ident, ident,
target, target,
cleartext, cleartext,
appid,
}) })
} }
} }
@ -90,7 +85,6 @@ impl UnixPasswordChangeEvent {
pub struct GeneratePasswordEvent { pub struct GeneratePasswordEvent {
pub ident: Identity, pub ident: Identity,
pub target: Uuid, pub target: Uuid,
pub appid: Option<String>,
} }
impl GeneratePasswordEvent { impl GeneratePasswordEvent {
@ -99,13 +93,8 @@ impl GeneratePasswordEvent {
// qs: &QueryServerWriteTransaction, // qs: &QueryServerWriteTransaction,
ident: Identity, ident: Identity,
target: Uuid, target: Uuid,
appid: Option<String>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
Ok(GeneratePasswordEvent { Ok(GeneratePasswordEvent { ident, target })
ident,
target,
appid,
})
} }
} }
@ -248,7 +237,6 @@ impl UnixUserAuthEvent {
pub struct GenerateTotpEvent { pub struct GenerateTotpEvent {
pub ident: Identity, pub ident: Identity,
pub target: Uuid, pub target: Uuid,
pub label: String,
} }
impl GenerateTotpEvent { impl GenerateTotpEvent {
@ -257,24 +245,15 @@ impl GenerateTotpEvent {
// qs: &QueryServerWriteTransaction, // qs: &QueryServerWriteTransaction,
ident: Identity, ident: Identity,
target: Uuid, target: Uuid,
label: String,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
Ok(GenerateTotpEvent { Ok(GenerateTotpEvent { ident, target })
ident,
target,
label,
})
} }
#[cfg(test)] #[cfg(test)]
pub fn new_internal(target: Uuid) -> Self { pub fn new_internal(target: Uuid) -> Self {
let ident = Identity::from_internal(); let ident = Identity::from_internal();
GenerateTotpEvent { GenerateTotpEvent { ident, target }
ident,
target,
label: "internal_token".to_string(),
}
} }
} }

View file

@ -61,11 +61,10 @@ impl MfaRegSession {
pub fn totp_new( pub fn totp_new(
origin: IdentityId, origin: IdentityId,
account: Account, account: Account,
label: String,
) -> Result<(Self, MfaRegNext), OperationError> { ) -> Result<(Self, MfaRegNext), OperationError> {
// Based on the req, init our session, and the return the next step. // Based on the req, init our session, and the return the next step.
// Store the ID of the event that start's the attempt // 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 accountname = account.name.as_str();
let issuer = account.spn.as_str(); let issuer = account.spn.as_str();

View file

@ -1,6 +1,5 @@
pub(crate) mod account; pub(crate) mod account;
pub(crate) mod authsession; pub(crate) mod authsession;
pub(crate) mod claim;
pub(crate) mod delayed; pub(crate) mod delayed;
pub(crate) mod event; pub(crate) mod event;
pub(crate) mod group; pub(crate) mod group;

View file

@ -26,8 +26,8 @@ use crate::idm::delayed::{
use hashbrown::HashSet; use hashbrown::HashSet;
use kanidm_proto::v1::{ use kanidm_proto::v1::{
CredentialStatus, RadiusAuthToken, SetCredentialResponse, UnixGroupToken, UnixUserToken, AuthType, CredentialStatus, RadiusAuthToken, SetCredentialResponse, UnixGroupToken,
UserAuthToken, UnixUserToken, UserAuthToken,
}; };
use std::str::FromStr; use std::str::FromStr;
@ -354,6 +354,7 @@ pub trait IdmServerTransaction<'a> {
&self, &self,
audit: &mut AuditScope, audit: &mut AuditScope,
uat: &UserAuthToken, uat: &UserAuthToken,
ct: Duration,
) -> Result<Identity, OperationError> { ) -> Result<Identity, OperationError> {
// From a UAT, get the current identity and associated information. // From a UAT, get the current identity and associated information.
let entry = self let entry = self
@ -364,11 +365,52 @@ pub trait IdmServerTransaction<'a> {
e e
})?; })?;
// TODO #64: Now apply claims from the uat into the Entry // #59: If the account is expired, do not allow the event
// to allow filtering.
// TODO #59: If the account is expiredy, do not allow the event
// to proceed // 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); let limits = Limits::from_uat(uat);
Ok(Identity { Ok(Identity {
@ -479,7 +521,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
}; };
let (auth_session, state) = if is_valid { let (auth_session, state) = if is_valid {
AuthSession::new(au, account, &init.appid, self.webauthn, ct) AuthSession::new(au, account, self.webauthn, ct)
} else { } else {
// it's softlocked, don't even bother. // it's softlocked, don't even bother.
lsecurity!(au, "Account is softlocked."); lsecurity!(au, "Account is softlocked.");
@ -764,7 +806,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
Ok(Some(LdapBoundToken { Ok(Some(LdapBoundToken {
uuid: *UUID_ANONYMOUS, uuid: *UUID_ANONYMOUS,
effective_uat: account effective_uat: account
.to_userauthtoken(au.uuid, &[], ct) .to_userauthtoken(au.uuid, ct, AuthType::Anonymous)
.ok_or(OperationError::InvalidState) .ok_or(OperationError::InvalidState)
.map_err(|e| { .map_err(|e| {
ladmin_error!(au, "Unable to generate effective_uat -> {:?}", e); ladmin_error!(au, "Unable to generate effective_uat -> {:?}", e);
@ -826,7 +868,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
spn: account.spn, spn: account.spn,
uuid: account.uuid, uuid: account.uuid,
effective_uat: anon_account effective_uat: anon_account
.to_userauthtoken(au.uuid, &[], ct) .to_userauthtoken(au.uuid, ct, AuthType::UnixPassword)
.ok_or(OperationError::InvalidState) .ok_or(OperationError::InvalidState)
.map_err(|e| { .map_err(|e| {
ladmin_error!(au, "Unable to generate effective_uat -> {:?}", 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. // Get the modifications we *want* to perform.
let modlist = account 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| { .map_err(|e| {
ladmin_error!(au, "Failed to generate password mod {:?}", e); ladmin_error!(au, "Failed to generate password mod {:?}", e);
e e
@ -1233,8 +1275,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
&mut self, &mut self,
au: &mut AuditScope, au: &mut AuditScope,
name: &str, name: &str,
cleartext: &str, cleartext: Option<&str>,
) -> Result<(), OperationError> { ) -> Result<String, OperationError> {
// name to uuid // name to uuid
let target = self.qs_write.name_to_uuid(au, name).map_err(|e| { let target = self.qs_write.name_to_uuid(au, name).map_err(|e| {
ladmin_error!(au, "name to uuid failed {:?}", 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 account = self.target_to_account(au, &target)?;
let cleartext = cleartext
.map(|s| s.to_string())
.unwrap_or_else(password_from_random);
let modlist = account let modlist = account
.gen_password_recover_mod(cleartext, self.crypto_policy) .gen_generatedpassword_recover_mod(&cleartext, self.crypto_policy)
.map_err(|e| { .map_err(|e| {
ladmin_error!(au, "Failed to generate password mod {:?}", e); ladmin_error!(au, "Failed to generate password mod {:?}", e);
e e
@ -1263,7 +1309,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
e e
})?; })?;
Ok(()) Ok(cleartext)
} }
pub fn generate_account_password( pub fn generate_account_password(
@ -1283,7 +1329,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// it returns a modify // it returns a modify
let modlist = account 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| { .map_err(|e| {
ladmin_error!(au, "Unable to generate password mod {:?}", e); ladmin_error!(au, "Unable to generate password mod {:?}", e);
e e
@ -1473,8 +1519,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
let sessionid = uuid_from_duration(ct, self.sid); let sessionid = uuid_from_duration(ct, self.sid);
let origin = (&gte.ident.origin).into(); let origin = (&gte.ident.origin).into();
let label = gte.label.clone(); let (session, next) = MfaRegSession::totp_new(origin, account).map_err(|e| {
let (session, next) = MfaRegSession::totp_new(origin, account, label).map_err(|e| {
ladmin_error!(au, "Unable to start totp MfaRegSession {:?}", e); ladmin_error!(au, "Unable to start totp MfaRegSession {:?}", e);
e e
})?; })?;
@ -1585,16 +1630,12 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
let account = self.target_to_account(au, &pwu.target_uuid)?; let account = self.target_to_account(au, &pwu.target_uuid)?;
// check, does the pw still match? // 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 yes, gen the pw mod and apply.
if same { if same {
let modlist = account let modlist = account
.gen_password_mod( .gen_password_mod(pwu.existing_password.as_str(), self.crypto_policy)
pwu.existing_password.as_str(),
&pwu.appid,
self.crypto_policy,
)
.map_err(|e| { .map_err(|e| {
ladmin_error!(au, "Unable to generate password mod {:?}", e); ladmin_error!(au, "Unable to generate password mod {:?}", e);
e e
@ -1739,7 +1780,7 @@ mod tests {
use crate::prelude::*; use crate::prelude::*;
use kanidm_proto::v1::OperationError; use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::SetCredentialResponse; use kanidm_proto::v1::SetCredentialResponse;
use kanidm_proto::v1::{AuthAllowed, AuthMech}; use kanidm_proto::v1::{AuthAllowed, AuthMech, AuthType};
use crate::idm::server::{IdmServer, IdmServerTransaction}; use crate::idm::server::{IdmServer, IdmServerTransaction};
// , IdmServerDelayed; // , IdmServerDelayed;
@ -2184,7 +2225,7 @@ mod tests {
idms: &IdmServer, idms: &IdmServer,
_idms_delayed: &IdmServerDelayed, _idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| { 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()); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
assert!(idms_prox_write.set_account_password(au, &pce).is_ok()); assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
@ -2199,7 +2240,7 @@ mod tests {
idms: &IdmServer, idms: &IdmServer,
_idms_delayed: &IdmServerDelayed, _idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| { 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()); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
assert!(idms_prox_write.set_account_password(au, &pce).is_err()); assert!(idms_prox_write.set_account_password(au, &pce).is_err());
@ -2271,7 +2312,7 @@ mod tests {
.expect("Failed to reset radius credential 1"); .expect("Failed to reset radius credential 1");
// Try and set that as the main account password, should fail. // 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); let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err()); assert!(e.is_err());
@ -2316,17 +2357,17 @@ mod tests {
// len check // len check
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); 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); let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err()); assert!(e.is_err());
// zxcvbn check // 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); let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err()); assert!(e.is_err());
// Check the "name" checking works too (I think admin may hit a common pw rule first) // 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); let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err()); assert!(e.is_err());
@ -2334,7 +2375,6 @@ mod tests {
let pce = PasswordChangeEvent::new_internal( let pce = PasswordChangeEvent::new_internal(
&UUID_ADMIN, &UUID_ADMIN,
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1", "demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1",
None,
); );
let e = idms_prox_write.set_account_password(au, &pce); let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err()); assert!(e.is_err());
@ -2352,7 +2392,7 @@ mod tests {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
// Check that the badlist password inserted is rejected. // 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); let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err()); assert!(e.is_err());
@ -2585,7 +2625,7 @@ mod tests {
idms_prox_write.expire_mfareg_sessions(expire.clone()); idms_prox_write.expire_mfareg_sessions(expire.clone());
// Set a password. // 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()); assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
// == reg, but change the event source part way in the process (failure) // == reg, but change the event source part way in the process (failure)
@ -3354,7 +3394,7 @@ mod tests {
_ => assert!(false), _ => assert!(false),
}; };
// Reg a pw. // 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()); assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
// Now remove, it will work. // Now remove, it will work.
idms_prox_write 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"));
})
}
} }

View file

@ -232,7 +232,7 @@ impl LdapServer {
ladmin_info!(audit, "LDAP Search Request LDAP Attrs -> {:?}", l_attrs); ladmin_info!(audit, "LDAP Search Request LDAP Attrs -> {:?}", l_attrs);
ladmin_info!(audit, "LDAP Search Request Mapped Attrs -> {:?}", k_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; let idm_read = idms.proxy_read_async().await;
lperf_segment!(audit, "ldap::do_search<core>", || { lperf_segment!(audit, "ldap::do_search<core>", || {
// Now start the txn - we need it for resolving filter components. // 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. // ! Remember, searchEvent wraps to ignore hidden for us.
let se = lperf_trace_segment!(audit, "ldap::do_search<core><prepare_se>", || { let se = lperf_trace_segment!(audit, "ldap::do_search<core><prepare_se>", || {
let ident = idm_read let ident = idm_read
.process_uat_to_identity(audit, &uat.effective_uat) .process_uat_to_identity(audit, &uat.effective_uat, ct)
.map_err(|e| { .map_err(|e| {
ladmin_error!(audit, "Invalid identity: {:?}", e); ladmin_error!(audit, "Invalid identity: {:?}", e);
e e

View file

@ -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 p = CryptoPolicy::minimum();
let c = Credential::new_password_only(&p, "password") let c = Credential::new_password_only(&p, "password")
.unwrap() .unwrap()

View file

@ -1080,12 +1080,14 @@ impl<'a> SchemaWriteTransaction<'a> {
SchemaAttribute { SchemaAttribute {
name: AttrString::from("claim"), name: AttrString::from("claim"),
uuid: *UUID_SCHEMA_ATTR_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, multivalue: true,
unique: false, unique: false,
phantom: true, phantom: true,
index: vec![], index: vec![],
syntax: SyntaxType::SecurityPrincipalName, syntax: SyntaxType::Utf8StringInsensitive,
}, },
); );
self.attributes.insert( self.attributes.insert(

View file

@ -273,14 +273,7 @@ async fn main() {
} }
KanidmdOpt::RecoverAccount(raopt) => { KanidmdOpt::RecoverAccount(raopt) => {
eprintln!("Running account recovery ..."); eprintln!("Running account recovery ...");
let password = match rpassword::prompt_password_stderr("new password: ") { recover_account_core(&config, &raopt.name);
Ok(pw) => pw,
Err(e) => {
eprintln!("Failed to get password from prompt {:?}", e);
std::process::exit(1);
}
};
recover_account_core(&config, &raopt.name, &password);
} }
KanidmdOpt::Reindex(_copt) => { KanidmdOpt::Reindex(_copt) => {
eprintln!("Running in reindex mode ..."); eprintln!("Running in reindex mode ...");