mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 04:57:00 +01:00
64 120 session claims (#462)
This commit is contained in:
parent
033b977906
commit
807af81184
|
@ -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(),
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
|
@ -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!();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = (>e.ident.origin).into();
|
let origin = (>e.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"));
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 ...");
|
||||||
|
|
Loading…
Reference in a new issue