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(
&self,
id: &str,
label: &str,
) -> Result<(Uuid, TotpSecret), ClientError> {
let r = SetCredentialRequest::TotpGenerate(label.to_string());
let r = SetCredentialRequest::TotpGenerate;
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -867,6 +867,10 @@ impl Entry<EntrySealed, EntryCommitted> {
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 {
compare_attrs(&self.attrs, &rhs.attrs)
}

View file

@ -133,4 +133,14 @@ impl Identity {
pub fn get_event_origin_id(&self) -> IdentityId {
IdentityId::from(&self.origin)
}
#[cfg(test)]
pub fn has_claim(&self, claim: &str) -> bool {
match &self.origin {
IdentType::Internal => false,
IdentType::User(u) => u
.entry
.attribute_equality("claim", &PartialValue::new_iutf8(claim)),
}
}
}

View file

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

View file

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

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 target_uuid: Uuid,
pub existing_password: String,
pub appid: Option<String>,
}
pub(crate) struct UnixPasswordUpgrade {

View file

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

View file

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

View file

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

View file

@ -26,8 +26,8 @@ use crate::idm::delayed::{
use hashbrown::HashSet;
use kanidm_proto::v1::{
CredentialStatus, RadiusAuthToken, SetCredentialResponse, UnixGroupToken, UnixUserToken,
UserAuthToken,
AuthType, CredentialStatus, RadiusAuthToken, SetCredentialResponse, UnixGroupToken,
UnixUserToken, UserAuthToken,
};
use std::str::FromStr;
@ -354,6 +354,7 @@ pub trait IdmServerTransaction<'a> {
&self,
audit: &mut AuditScope,
uat: &UserAuthToken,
ct: Duration,
) -> Result<Identity, OperationError> {
// From a UAT, get the current identity and associated information.
let entry = self
@ -364,11 +365,52 @@ pub trait IdmServerTransaction<'a> {
e
})?;
// TODO #64: Now apply claims from the uat into the Entry
// to allow filtering.
// TODO #59: If the account is expiredy, do not allow the event
// #59: If the account is expired, do not allow the event
// to proceed
let valid = Account::check_within_valid_time(
ct,
entry.get_ava_single_datetime("account_valid_from").as_ref(),
entry.get_ava_single_datetime("account_expire").as_ref(),
);
if !valid {
lsecurity!(
audit,
"Account has expired or is not yet valid, not allowing to proceed"
);
return Err(OperationError::SessionExpired);
}
// #64: Now apply claims from the uat into the Entry
// to allow filtering.
/*
entry.insert_claim(match &uat.auth_type {
AuthType::Anonymous => "authtype_anonymous",
AuthType::UnixPassword => "authtype_unixpassword",
AuthType::Password => "authtype_password",
AuthType::GeneratedPassword => "authtype_generatedpassword",
AuthType::Webauthn => "authtype_webauthn",
AuthType::PasswordMfa => "authtype_passwordmfa",
});
match &uat.auth_type {
AuthType::Anonymous | AuthType::UnixPassword | AuthType::Password => {}
AuthType::GeneratedPassword | AuthType::Webauthn | AuthType::PasswordMfa => {
entry.insert_claim("authlevel_strong")
}
};
match &uat.auth_type {
AuthType::Anonymous => {}
AuthType::UnixPassword
| AuthType::Password
| AuthType::GeneratedPassword
| AuthType::Webauthn => entry.insert_claim("authclass_single"),
AuthType::PasswordMfa => entry.insert_claim("authclass_mfa"),
};
*/
ltrace!(audit, "Applied claims -> {:?}", entry.get_ava_set("claim"));
let limits = Limits::from_uat(uat);
Ok(Identity {
@ -479,7 +521,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
};
let (auth_session, state) = if is_valid {
AuthSession::new(au, account, &init.appid, self.webauthn, ct)
AuthSession::new(au, account, self.webauthn, ct)
} else {
// it's softlocked, don't even bother.
lsecurity!(au, "Account is softlocked.");
@ -764,7 +806,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
Ok(Some(LdapBoundToken {
uuid: *UUID_ANONYMOUS,
effective_uat: account
.to_userauthtoken(au.uuid, &[], ct)
.to_userauthtoken(au.uuid, ct, AuthType::Anonymous)
.ok_or(OperationError::InvalidState)
.map_err(|e| {
ladmin_error!(au, "Unable to generate effective_uat -> {:?}", e);
@ -826,7 +868,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
spn: account.spn,
uuid: account.uuid,
effective_uat: anon_account
.to_userauthtoken(au.uuid, &[], ct)
.to_userauthtoken(au.uuid, ct, AuthType::UnixPassword)
.ok_or(OperationError::InvalidState)
.map_err(|e| {
ladmin_error!(au, "Unable to generate effective_uat -> {:?}", e);
@ -1074,7 +1116,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// Get the modifications we *want* to perform.
let modlist = account
.gen_password_mod(pce.cleartext.as_str(), &pce.appid, self.crypto_policy)
.gen_password_mod(pce.cleartext.as_str(), self.crypto_policy)
.map_err(|e| {
ladmin_error!(au, "Failed to generate password mod {:?}", e);
e
@ -1233,8 +1275,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
&mut self,
au: &mut AuditScope,
name: &str,
cleartext: &str,
) -> Result<(), OperationError> {
cleartext: Option<&str>,
) -> Result<String, OperationError> {
// name to uuid
let target = self.qs_write.name_to_uuid(au, name).map_err(|e| {
ladmin_error!(au, "name to uuid failed {:?}", e);
@ -1243,8 +1285,12 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
let account = self.target_to_account(au, &target)?;
let cleartext = cleartext
.map(|s| s.to_string())
.unwrap_or_else(password_from_random);
let modlist = account
.gen_password_recover_mod(cleartext, self.crypto_policy)
.gen_generatedpassword_recover_mod(&cleartext, self.crypto_policy)
.map_err(|e| {
ladmin_error!(au, "Failed to generate password mod {:?}", e);
e
@ -1263,7 +1309,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
e
})?;
Ok(())
Ok(cleartext)
}
pub fn generate_account_password(
@ -1283,7 +1329,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// it returns a modify
let modlist = account
.gen_password_mod(cleartext.as_str(), &gpe.appid, self.crypto_policy)
.gen_generatedpassword_recover_mod(cleartext.as_str(), self.crypto_policy)
.map_err(|e| {
ladmin_error!(au, "Unable to generate password mod {:?}", e);
e
@ -1473,8 +1519,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
let sessionid = uuid_from_duration(ct, self.sid);
let origin = (&gte.ident.origin).into();
let label = gte.label.clone();
let (session, next) = MfaRegSession::totp_new(origin, account, label).map_err(|e| {
let (session, next) = MfaRegSession::totp_new(origin, account).map_err(|e| {
ladmin_error!(au, "Unable to start totp MfaRegSession {:?}", e);
e
})?;
@ -1585,16 +1630,12 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
let account = self.target_to_account(au, &pwu.target_uuid)?;
// check, does the pw still match?
let same = account.check_credential_pw(pwu.existing_password.as_str(), &pwu.appid)?;
let same = account.check_credential_pw(pwu.existing_password.as_str())?;
// if yes, gen the pw mod and apply.
if same {
let modlist = account
.gen_password_mod(
pwu.existing_password.as_str(),
&pwu.appid,
self.crypto_policy,
)
.gen_password_mod(pwu.existing_password.as_str(), self.crypto_policy)
.map_err(|e| {
ladmin_error!(au, "Unable to generate password mod {:?}", e);
e
@ -1739,7 +1780,7 @@ mod tests {
use crate::prelude::*;
use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::SetCredentialResponse;
use kanidm_proto::v1::{AuthAllowed, AuthMech};
use kanidm_proto::v1::{AuthAllowed, AuthMech, AuthType};
use crate::idm::server::{IdmServer, IdmServerTransaction};
// , IdmServerDelayed;
@ -2184,7 +2225,7 @@ mod tests {
idms: &IdmServer,
_idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| {
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
@ -2199,7 +2240,7 @@ mod tests {
idms: &IdmServer,
_idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| {
let pce = PasswordChangeEvent::new_internal(&UUID_ANONYMOUS, TEST_PASSWORD, None);
let pce = PasswordChangeEvent::new_internal(&UUID_ANONYMOUS, TEST_PASSWORD);
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
assert!(idms_prox_write.set_account_password(au, &pce).is_err());
@ -2271,7 +2312,7 @@ mod tests {
.expect("Failed to reset radius credential 1");
// Try and set that as the main account password, should fail.
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, r1.as_str(), None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, r1.as_str());
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
@ -2316,17 +2357,17 @@ mod tests {
// len check
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password", None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password");
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
// zxcvbn check
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password1234", None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password1234");
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
// Check the "name" checking works too (I think admin may hit a common pw rule first)
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "admin_nta", None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "admin_nta");
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
@ -2334,7 +2375,6 @@ mod tests {
let pce = PasswordChangeEvent::new_internal(
&UUID_ADMIN,
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1",
None,
);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
@ -2352,7 +2392,7 @@ mod tests {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
// Check that the badlist password inserted is rejected.
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "bad@no3IBTyqHu$list", None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "bad@no3IBTyqHu$list");
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
@ -2585,7 +2625,7 @@ mod tests {
idms_prox_write.expire_mfareg_sessions(expire.clone());
// Set a password.
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
// == reg, but change the event source part way in the process (failure)
@ -3354,7 +3394,7 @@ mod tests {
_ => assert!(false),
};
// Reg a pw.
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None);
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
// Now remove, it will work.
idms_prox_write
@ -3394,4 +3434,110 @@ mod tests {
}
})
}
#[test]
fn test_idm_uat_claim_insertion() {
run_idm_test!(|_qs: &QueryServer,
idms: &IdmServer,
_idms_delayed: &mut IdmServerDelayed,
audit: &mut AuditScope| {
let ct = Duration::from_secs(TEST_CURRENT_TIME);
let mut idms_prox_write = idms.proxy_write(ct.clone());
// get an account.
let account = idms_prox_write
.target_to_account(audit, &UUID_ADMIN)
.expect("account must exist");
// create some fake uats
// process them and see what claims fall out :D
let session_id = uuid::Uuid::new_v4();
// For the different auth types, check that we get the correct claims:
// == anonymous
let uat = account
.to_userauthtoken(session_id, ct, AuthType::Anonymous)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(audit, &uat, ct)
.expect("Unable to process uat");
assert!(!ident.has_claim("authtype_anonymous"));
// Does NOT have this
assert!(!ident.has_claim("authlevel_strong"));
assert!(!ident.has_claim("authclass_single"));
assert!(!ident.has_claim("authclass_mfa"));
// == unixpassword
let uat = account
.to_userauthtoken(session_id, ct, AuthType::UnixPassword)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(audit, &uat, ct)
.expect("Unable to process uat");
assert!(!ident.has_claim("authtype_unixpassword"));
assert!(!ident.has_claim("authclass_single"));
// Does NOT have this
assert!(!ident.has_claim("authlevel_strong"));
assert!(!ident.has_claim("authclass_mfa"));
// == password
let uat = account
.to_userauthtoken(session_id, ct, AuthType::Password)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(audit, &uat, ct)
.expect("Unable to process uat");
assert!(!ident.has_claim("authtype_password"));
assert!(!ident.has_claim("authclass_single"));
// Does NOT have this
assert!(!ident.has_claim("authlevel_strong"));
assert!(!ident.has_claim("authclass_mfa"));
// == generatedpassword
let uat = account
.to_userauthtoken(session_id, ct, AuthType::GeneratedPassword)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(audit, &uat, ct)
.expect("Unable to process uat");
assert!(!ident.has_claim("authtype_generatedpassword"));
assert!(!ident.has_claim("authclass_single"));
assert!(!ident.has_claim("authlevel_strong"));
// Does NOT have this
assert!(!ident.has_claim("authclass_mfa"));
// == webauthn
let uat = account
.to_userauthtoken(session_id, ct, AuthType::Webauthn)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(audit, &uat, ct)
.expect("Unable to process uat");
assert!(!ident.has_claim("authtype_webauthn"));
assert!(!ident.has_claim("authclass_single"));
assert!(!ident.has_claim("authlevel_strong"));
// Does NOT have this
assert!(!ident.has_claim("authclass_mfa"));
// == passwordmfa
let uat = account
.to_userauthtoken(session_id, ct, AuthType::PasswordMfa)
.expect("Unable to create uat");
let ident = idms_prox_write
.process_uat_to_identity(audit, &uat, ct)
.expect("Unable to process uat");
assert!(!ident.has_claim("authtype_passwordmfa"));
assert!(!ident.has_claim("authlevel_strong"));
assert!(!ident.has_claim("authclass_mfa"));
// Does NOT have this
assert!(!ident.has_claim("authclass_single"));
})
}
}

View file

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

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

View file

@ -1080,12 +1080,14 @@ impl<'a> SchemaWriteTransaction<'a> {
SchemaAttribute {
name: AttrString::from("claim"),
uuid: *UUID_SCHEMA_ATTR_CLAIM,
description: String::from("The spn of a claim this entry holds"),
description: String::from(
"The string identifier of an extracted claim that can be filtered",
),
multivalue: true,
unique: false,
phantom: true,
index: vec![],
syntax: SyntaxType::SecurityPrincipalName,
syntax: SyntaxType::Utf8StringInsensitive,
},
);
self.attributes.insert(

View file

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