Browse Source

Merge 16b8a58eec into 17b159beff

pull/7855/merge
Timshel 17 hours ago
committed by GitHub
parent
commit
348908fc58
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 25
      src/api/core/accounts.rs
  2. 6
      src/api/core/ciphers.rs
  3. 20
      src/api/core/organizations.rs
  4. 4
      src/api/core/two_factor/authenticator.rs
  5. 4
      src/api/core/two_factor/duo.rs
  6. 4
      src/api/core/two_factor/email.rs
  7. 5
      src/api/core/two_factor/mod.rs
  8. 4
      src/api/core/two_factor/webauthn.rs
  9. 5
      src/api/core/two_factor/yubikey.rs
  10. 4
      src/api/identity.rs
  11. 4
      src/api/mod.rs
  12. 30
      src/db/models/user.rs

25
src/api/core/accounts.rs

@ -11,7 +11,7 @@ use serde_json::Value;
use crate::{
CONFIG,
api::{
AnonymousNotify, ApiResult, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
AnonymousNotify, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
core::{accept_org_invite, log_user_event, two_factor::email},
master_password_policy, push_logout_to_relay, register_push_device, unregister_push_device,
},
@ -1086,7 +1086,7 @@ async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbCo
let data: PasswordOrOtpData = data.into_inner();
let mut user = headers.user;
data.validate(&user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
user.reset_security_stamp(&conn).await?;
let save_result = user.save(&conn).await;
@ -1344,9 +1344,9 @@ async fn post_delete_account(
#[delete("/accounts", data = "<data>")]
async fn delete_account(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
data.validate(&user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
// The devices are deleted together with the user, so check for push devices beforehand
let has_push_device = CONFIG.push_enabled() && Device::check_user_has_push_device(&user.uuid, &conn).await;
@ -1472,19 +1472,6 @@ struct SecretVerificationRequest {
master_password_hash: String,
}
// Change the KDF Iterations if necessary
pub async fn kdf_upgrade(user: &mut User, pwd_hash: &str, conn: &DbConn) -> ApiResult<()> {
if user.password_iterations < CONFIG.password_iterations() {
user.password_iterations = CONFIG.password_iterations();
user.set_password(pwd_hash, None, false, None, conn).await?;
if let Err(e) = user.save(conn).await {
error!("Error updating user: {e:#?}");
}
}
Ok(())
}
#[post("/accounts/verify-password", data = "<data>")]
async fn verify_password(data: Json<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult {
let data: SecretVerificationRequest = data.into_inner();
@ -1494,7 +1481,7 @@ async fn verify_password(data: Json<SecretVerificationRequest>, headers: Headers
err!("Invalid password")
}
kdf_upgrade(&mut user, &data.master_password_hash, &conn).await?;
user.check_persist_kdf_upgrade(&data.master_password_hash, &conn).await?;
Ok(Json(master_password_policy(&user, &conn).await))
}
@ -1503,7 +1490,7 @@ async fn update_api_key(data: Json<PasswordOrOtpData>, rotate: bool, headers: He
let data: PasswordOrOtpData = data.into_inner();
let mut user = headers.user;
data.validate(&user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
if rotate || user.api_key.is_none() {
user.api_key = Some(crypto::generate_api_key());

6
src/api/core/ciphers.rs

@ -1812,9 +1812,9 @@ async fn purge_org_vault(
}
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
data.validate(&user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
match Membership::find_confirmed_by_user_and_org(&user.uuid, &organization.org_id, &conn).await {
Some(member) if member.atype == MembershipType::Owner => {
@ -1848,7 +1848,7 @@ async fn purge_personal_vault(
let data: PasswordOrOtpData = data.into_inner();
let mut user = headers.user;
data.validate(&user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
for cipher in Cipher::find_owned_by_user(&user.uuid, &conn).await {
cipher.delete(&conn).await?;

20
src/api/core/organizations.rs

@ -225,12 +225,14 @@ async fn delete_organization(
headers: OwnerHeaders,
conn: DbConn,
) -> EmptyResult {
let mut user = headers.user;
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: PasswordOrOtpData = data.into_inner();
data.validate(&headers.user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
match Organization::find_by_uuid(&org_id, &conn).await {
None => err!("Organization not found"),
@ -3090,12 +3092,13 @@ async fn put_reset_password_enrollment(
data: Json<OrganizationUserResetPasswordEnrollmentRequest>,
conn: DbConn,
) -> EmptyResult {
if user_id != headers.user.uuid {
let mut membership = headers.membership;
let mut user = headers.user;
if user_id != user.uuid {
err!("User to enroll isn't member of required organization", "The user_id and acting user do not match");
}
let mut membership = headers.membership;
check_reset_password_applicable(&org_id, &conn).await?;
let reset_request = data.into_inner();
@ -3115,7 +3118,7 @@ async fn put_reset_password_enrollment(
master_password_hash: reset_request.master_password_hash,
otp: reset_request.otp,
}
.validate(&headers.user, true, &conn)
.validate(&mut user, true, &conn)
.await?;
}
@ -3128,8 +3131,7 @@ async fn put_reset_password_enrollment(
EventType::OrganizationUserResetPasswordWithdraw
};
log_event(event_type, &membership.uuid, &org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &conn)
.await;
log_event(event_type, &membership.uuid, &org_id, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
Ok(())
}
@ -3163,10 +3165,10 @@ async fn api_key(
err!("Organization not found", "Organization id's do not match");
}
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
// Validate the owner users password/otp
data.validate(&user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
let org_api_key = if let Some(mut org_api_key) = OrganizationApiKey::find_by_org_uuid(org_id, &conn).await {
if rotate {

4
src/api/core/two_factor/authenticator.rs

@ -20,9 +20,9 @@ pub fn routes() -> Vec<Route> {
#[post("/two-factor/get-authenticator", data = "<data>")]
async fn generate_authenticator(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
let user = headers.user;
let mut user = headers.user;
data.validate(&user, false, &conn).await?;
data.validate(&mut user, false, &conn).await?;
let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Authenticator, &conn).await;

4
src/api/core/two_factor/duo.rs

@ -43,9 +43,9 @@ const DISABLED_MESSAGE_DEFAULT: &str = "<To use the global Duo keys, please leav
#[post("/two-factor/get-duo", data = "<data>")]
async fn get_duo(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
data.validate(&user, false, &conn).await?;
data.validate(&mut user, false, &conn).await?;
let (enabled, duo) = match get_user_duo_data(&user.uuid, &conn).await {
DuoStatus::Global(_) => (true, Some(DuoData::secret())),

4
src/api/core/two_factor/email.rs

@ -163,9 +163,9 @@ pub async fn send_token(user_id: &UserId, conn: &DbConn) -> EmptyResult {
#[post("/two-factor/get-email", data = "<data>")]
async fn get_email(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
data.validate(&user, false, &conn).await?;
data.validate(&mut user, false, &conn).await?;
let (enabled, mfa_email) =
if let Some(x) = TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email, &conn).await {

5
src/api/core/two_factor/mod.rs

@ -104,9 +104,9 @@ async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> {
#[post("/two-factor/get-recover", data = "<data>")]
async fn get_recover(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
data.validate(&user, true, &conn).await?;
data.validate(&mut user, true, &conn).await?;
Ok(Json(json!({
"code": user.totp_recover,
@ -129,6 +129,7 @@ pub async fn check_2fa_state(user: &User, device_type: i32, ip: &std::net::IpAdd
if TwoFactor::find_by_user(&user.uuid, conn).await.is_empty() {
enforce_2fa_policy(user, &user.uuid, device_type, ip, conn).await?;
}
Ok(())
}

4
src/api/core/two_factor/webauthn.rs

@ -123,9 +123,9 @@ async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbC
}
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
data.validate(&user, false, &conn).await?;
data.validate(&mut user, false, &conn).await?;
let (enabled, registrations) = get_webauthn_registrations(&user.uuid, &conn).await?;
let keys: Vec<i32> = registrations.iter().map(|r| r.id).collect();

5
src/api/core/two_factor/yubikey.rs

@ -122,9 +122,9 @@ async fn generate_yubikey(data: Json<PasswordOrOtpData>, headers: Headers, conn:
get_yubico_credentials()?;
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
let mut user = headers.user;
data.validate(&user, false, &conn).await?;
data.validate(&mut user, false, &conn).await?;
let user_id = &user.uuid;
@ -148,6 +148,7 @@ async fn generate_yubikey(data: Json<PasswordOrOtpData>, headers: Headers, conn:
#[post("/two-factor/yubikey", data = "<data>")]
async fn activate_yubikey(data: Json<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: EnableYubikeyData = data.into_inner();
let yubikeys = parse_yubikeys(&data);
let mut user = headers.user;

4
src/api/identity.rs

@ -14,7 +14,7 @@ use crate::{
api::{
ApiResult, EmptyResult, JsonResult,
core::{
accounts::{PreloginData, RegisterData, kdf_upgrade, prelogin, register},
accounts::{PreloginData, RegisterData, prelogin, register},
log_user_event,
two_factor::{
authenticator, duo_oidc, email, enforce_2fa_policy, is_twofactor_provider_usable, webauthn, yubikey,
@ -440,7 +440,7 @@ async fn password_login(data: ConnectData, user_id: &mut Option<UserId>, conn: &
// Change the KDF Iterations (only when not logging in with an auth request)
if data.auth_request.is_none() {
kdf_upgrade(&mut user, password, conn).await?;
user.check_persist_kdf_upgrade(password, conn).await?;
}
let now = Utc::now().naive_utc();

4
src/api/mod.rs

@ -58,7 +58,7 @@ impl PasswordOrOtpData {
/// Tokens used via this struct can be used multiple times during the process
/// First for the validation to continue, after that to enable or validate the following actions
/// This is different per caller, so it can be adjusted to delete the token or not
pub async fn validate(&self, user: &User, delete_if_valid: bool, conn: &DbConn) -> EmptyResult {
pub async fn validate(&self, user: &mut User, delete_if_valid: bool, conn: &DbConn) -> EmptyResult {
use crate::api::core::two_factor::protected_actions::validate_protected_action_otp;
match (self.master_password_hash.as_deref(), self.otp.as_deref()) {
@ -66,6 +66,8 @@ impl PasswordOrOtpData {
if !user.check_valid_password(pw_hash) {
err!("Invalid password");
}
user.check_persist_kdf_upgrade(pw_hash, conn).await?;
}
(None, Some(otp)) => {
validate_protected_action_otp(otp, &user.uuid, delete_if_valid, conn).await?;

30
src/db/models/user.rs

@ -2,6 +2,7 @@ use chrono::{NaiveDateTime, TimeDelta, Utc};
use derive_more::{AsRef, Deref, Display, From};
use diesel::prelude::*;
use serde_json::Value;
use std::cmp;
use crate::{
CONFIG,
@ -206,6 +207,7 @@ impl User {
allow_next_route: Option<Vec<String>>,
conn: &DbConn,
) -> EmptyResult {
self.password_iterations = cmp::max(self.password_iterations, CONFIG.password_iterations());
self.password_hash =
crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations.cast_unsigned());
@ -362,6 +364,34 @@ impl User {
}
}
pub async fn check_persist_kdf_upgrade(&mut self, pwd_hash: &str, conn: &DbConn) -> EmptyResult {
if self.password_iterations < CONFIG.password_iterations() {
let old_iteration = self.password_iterations;
let old_hash = self.password_hash.clone();
self.set_password(pwd_hash, None, false, None, conn).await?;
conn.run(move |conn| {
diesel::update(
users::table
.filter(users::uuid.eq(&self.uuid))
.filter(users::password_iterations.eq(old_iteration))
.filter(users::password_hash.eq(old_hash)),
)
.set((
users::updated_at.eq(Utc::now().naive_utc()),
users::password_iterations.eq(&self.password_iterations),
users::password_hash.eq(&self.password_hash),
))
.execute(conn)
.map_res("Error updating user KDF iterations")
})
.await
} else {
Ok(())
}
}
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
for member in Membership::find_confirmed_by_user(&self.uuid, conn).await {
if member.atype == MembershipType::Owner

Loading…
Cancel
Save