1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2025-01-22 07:09:00 +01:00

rename membership and adopt newtype pattern (#5320)

* rename membership

rename UserOrganization to Membership to clarify the relation
and prevent confusion whether something refers to a member(ship) or user

* use newtype pattern

* implement custom derive macro IdFromParam

* add UuidFromParam macro for UUIDs

* add macros to Docker build

Co-authored-by: dfunkt <dfunkt@users.noreply.github.com>

---------

Co-authored-by: dfunkt <dfunkt@users.noreply.github.com>
Dieser Commit ist enthalten in:
Stefan Melmuk 2025-01-09 18:37:23 +01:00 committet von GitHub
Ursprung 10d12676cf
Commit 871a3f214a
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
51 geänderte Dateien mit 2800 neuen und 2114 gelöschten Zeilen

Datei anzeigen

@ -5,6 +5,7 @@
!.git !.git
!docker/healthcheck.sh !docker/healthcheck.sh
!docker/start.sh !docker/start.sh
!macros
!migrations !migrations
!src !src

43
Cargo.lock generiert
Datei anzeigen

@ -752,6 +752,27 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "derive_more"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]] [[package]]
name = "devise" name = "devise"
version = "0.4.2" version = "0.4.2"
@ -809,6 +830,17 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "diesel-derive-newtype"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5adf688c584fe33726ce0e2898f608a2a92578ac94a4a92fcecf73214fe0716"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "diesel_derives" name = "diesel_derives"
version = "2.2.3" version = "2.2.3"
@ -2013,6 +2045,14 @@ dependencies = [
"linked-hash-map", "linked-hash-map",
] ]
[[package]]
name = "macros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]] [[package]]
name = "match_cfg" name = "match_cfg"
version = "0.1.0" version = "0.1.0"
@ -4011,7 +4051,9 @@ dependencies = [
"dashmap", "dashmap",
"data-encoding", "data-encoding",
"data-url", "data-url",
"derive_more",
"diesel", "diesel",
"diesel-derive-newtype",
"diesel_logger", "diesel_logger",
"diesel_migrations", "diesel_migrations",
"dotenvy", "dotenvy",
@ -4028,6 +4070,7 @@ dependencies = [
"lettre", "lettre",
"libsqlite3-sys", "libsqlite3-sys",
"log", "log",
"macros",
"mimalloc", "mimalloc",
"num-derive", "num-derive",
"num-traits", "num-traits",

Datei anzeigen

@ -1,3 +1,5 @@
workspace = { members = ["macros"] }
[package] [package]
name = "vaultwarden" name = "vaultwarden"
version = "1.0.0" version = "1.0.0"
@ -39,6 +41,8 @@ unstable = []
syslog = "7.0.0" syslog = "7.0.0"
[dependencies] [dependencies]
macros = { path = "./macros" }
# Logging # Logging
log = "0.4.22" log = "0.4.22"
fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] } fern = { version = "0.7.1", features = ["syslog-7", "reopen-1"] }
@ -78,6 +82,9 @@ diesel = { version = "2.2.6", features = ["chrono", "r2d2", "numeric"] }
diesel_migrations = "2.2.0" diesel_migrations = "2.2.0"
diesel_logger = { version = "0.4.0", optional = true } diesel_logger = { version = "0.4.0", optional = true }
derive_more = { version = "1.0.0", features = ["from", "into", "as_ref", "deref", "display"] }
diesel-derive-newtype = "2.1.2"
# Bundled/Static SQLite # Bundled/Static SQLite
libsqlite3-sys = { version = "0.30.1", features = ["bundled"], optional = true } libsqlite3-sys = { version = "0.30.1", features = ["bundled"], optional = true }

Datei anzeigen

@ -76,6 +76,7 @@ RUN source /env-cargo && \
# Copies over *only* your manifests and build files # Copies over *only* your manifests and build files
COPY ./Cargo.* ./rust-toolchain.toml ./build.rs ./ COPY ./Cargo.* ./rust-toolchain.toml ./build.rs ./
COPY ./macros ./macros
ARG CARGO_PROFILE=release ARG CARGO_PROFILE=release

Datei anzeigen

@ -116,6 +116,7 @@ RUN source /env-cargo && \
# Copies over *only* your manifests and build files # Copies over *only* your manifests and build files
COPY ./Cargo.* ./rust-toolchain.toml ./build.rs ./ COPY ./Cargo.* ./rust-toolchain.toml ./build.rs ./
COPY ./macros ./macros
ARG CARGO_PROFILE=release ARG CARGO_PROFILE=release

Datei anzeigen

@ -143,6 +143,7 @@ RUN source /env-cargo && \
# Copies over *only* your manifests and build files # Copies over *only* your manifests and build files
COPY ./Cargo.* ./rust-toolchain.toml ./build.rs ./ COPY ./Cargo.* ./rust-toolchain.toml ./build.rs ./
COPY ./macros ./macros
ARG CARGO_PROFILE=release ARG CARGO_PROFILE=release

13
macros/Cargo.toml Normale Datei
Datei anzeigen

@ -0,0 +1,13 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2021"
[lib]
name = "macros"
path = "src/lib.rs"
proc-macro = true
[dependencies]
quote = "1.0.38"
syn = "2.0.94"

58
macros/src/lib.rs Normale Datei
Datei anzeigen

@ -0,0 +1,58 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_derive(UuidFromParam)]
pub fn derive_uuid_from_param(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_derive_uuid_macro(&ast)
}
fn impl_derive_uuid_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
#[automatically_derived]
impl<'r> rocket::request::FromParam<'r> for #name {
type Error = ();
#[inline(always)]
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
if uuid::Uuid::parse_str(param).is_ok() {
Ok(Self(param.to_string()))
} else {
Err(())
}
}
}
};
gen.into()
}
#[proc_macro_derive(IdFromParam)]
pub fn derive_id_from_param(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_derive_safestring_macro(&ast)
}
fn impl_derive_safestring_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
#[automatically_derived]
impl<'r> rocket::request::FromParam<'r> for #name {
type Error = ();
#[inline(always)]
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
if param.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) {
Ok(Self(param.to_string()))
} else {
Err(())
}
}
}
};
gen.into()
}

Datei anzeigen

@ -50,7 +50,7 @@ pub fn routes() -> Vec<Route> {
disable_user, disable_user,
enable_user, enable_user,
remove_2fa, remove_2fa,
update_user_org_type, update_membership_type,
update_revision_users, update_revision_users,
post_config, post_config,
delete_config, delete_config,
@ -280,8 +280,8 @@ struct InviteData {
email: String, email: String,
} }
async fn get_user_or_404(uuid: &str, conn: &mut DbConn) -> ApiResult<User> { async fn get_user_or_404(user_id: &UserId, conn: &mut DbConn) -> ApiResult<User> {
if let Some(user) = User::find_by_uuid(uuid, conn).await { if let Some(user) = User::find_by_uuid(user_id, conn).await {
Ok(user) Ok(user)
} else { } else {
err_code!("User doesn't exist", Status::NotFound.code); err_code!("User doesn't exist", Status::NotFound.code);
@ -381,29 +381,29 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, mut conn: DbConn)
} }
} }
#[get("/users/<uuid>")] #[get("/users/<user_id>")]
async fn get_user_json(uuid: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult { async fn get_user_json(user_id: UserId, _token: AdminToken, mut conn: DbConn) -> JsonResult {
let u = get_user_or_404(uuid, &mut conn).await?; let u = get_user_or_404(&user_id, &mut conn).await?;
let mut usr = u.to_json(&mut conn).await; let mut usr = u.to_json(&mut conn).await;
usr["userEnabled"] = json!(u.enabled); usr["userEnabled"] = json!(u.enabled);
usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
Ok(Json(usr)) Ok(Json(usr))
} }
#[post("/users/<uuid>/delete")] #[post("/users/<user_id>/delete")]
async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn delete_user(user_id: UserId, token: AdminToken, mut conn: DbConn) -> EmptyResult {
let user = get_user_or_404(uuid, &mut conn).await?; let user = get_user_or_404(&user_id, &mut conn).await?;
// Get the user_org records before deleting the actual user // Get the membership records before deleting the actual user
let user_orgs = UserOrganization::find_any_state_by_user(uuid, &mut conn).await; let memberships = Membership::find_any_state_by_user(&user_id, &mut conn).await;
let res = user.delete(&mut conn).await; let res = user.delete(&mut conn).await;
for user_org in user_orgs { for membership in memberships {
log_event( log_event(
EventType::OrganizationUserRemoved as i32, EventType::OrganizationUserRemoved as i32,
&user_org.uuid, &membership.uuid,
&user_org.org_uuid, &membership.org_uuid,
ACTING_ADMIN_USER, &ACTING_ADMIN_USER.into(),
14, // Use UnknownBrowser type 14, // Use UnknownBrowser type
&token.ip.ip, &token.ip.ip,
&mut conn, &mut conn,
@ -414,9 +414,9 @@ async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRe
res res
} }
#[post("/users/<uuid>/deauth")] #[post("/users/<user_id>/deauth")]
async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn deauth_user(user_id: UserId, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(&user_id, &mut conn).await?;
nt.send_logout(&user, None).await; nt.send_logout(&user, None).await;
@ -435,9 +435,9 @@ async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notif
user.save(&mut conn).await user.save(&mut conn).await
} }
#[post("/users/<uuid>/disable")] #[post("/users/<user_id>/disable")]
async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn disable_user(user_id: UserId, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(&user_id, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
user.enabled = false; user.enabled = false;
@ -449,26 +449,26 @@ async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Noti
save_result save_result
} }
#[post("/users/<uuid>/enable")] #[post("/users/<user_id>/enable")]
async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn enable_user(user_id: UserId, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(&user_id, &mut conn).await?;
user.enabled = true; user.enabled = true;
user.save(&mut conn).await user.save(&mut conn).await
} }
#[post("/users/<uuid>/remove-2fa")] #[post("/users/<user_id>/remove-2fa")]
async fn remove_2fa(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn remove_2fa(user_id: UserId, token: AdminToken, mut conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(&user_id, &mut conn).await?;
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?;
two_factor::enforce_2fa_policy(&user, ACTING_ADMIN_USER, 14, &token.ip.ip, &mut conn).await?; two_factor::enforce_2fa_policy(&user, &ACTING_ADMIN_USER.into(), 14, &token.ip.ip, &mut conn).await?;
user.totp_recover = None; user.totp_recover = None;
user.save(&mut conn).await user.save(&mut conn).await
} }
#[post("/users/<uuid>/invite/resend")] #[post("/users/<user_id>/invite/resend")]
async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn resend_user_invite(user_id: UserId, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
if let Some(user) = User::find_by_uuid(uuid, &mut conn).await { if let Some(user) = User::find_by_uuid(&user_id, &mut conn).await {
//TODO: replace this with user.status check when it will be available (PR#3397) //TODO: replace this with user.status check when it will be available (PR#3397)
if !user.password_hash.is_empty() { if !user.password_hash.is_empty() {
err_code!("User already accepted invitation", Status::BadRequest.code); err_code!("User already accepted invitation", Status::BadRequest.code);
@ -485,42 +485,41 @@ async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) ->
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct UserOrgTypeData { struct MembershipTypeData {
user_type: NumberOrString, user_type: NumberOrString,
user_uuid: String, user_uuid: UserId,
org_uuid: String, org_uuid: OrganizationId,
} }
#[post("/users/org_type", data = "<data>")] #[post("/users/org_type", data = "<data>")]
async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn update_membership_type(data: Json<MembershipTypeData>, token: AdminToken, mut conn: DbConn) -> EmptyResult {
let data: UserOrgTypeData = data.into_inner(); let data: MembershipTypeData = data.into_inner();
let Some(mut user_to_edit) = let Some(mut member_to_edit) = Membership::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &mut conn).await
UserOrganization::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &mut conn).await
else { else {
err!("The specified user isn't member of the organization") err!("The specified user isn't member of the organization")
}; };
let new_type = match UserOrgType::from_str(&data.user_type.into_string()) { let new_type = match MembershipType::from_str(&data.user_type.into_string()) {
Some(new_type) => new_type as i32, Some(new_type) => new_type as i32,
None => err!("Invalid type"), None => err!("Invalid type"),
}; };
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner { if member_to_edit.atype == MembershipType::Owner && new_type != MembershipType::Owner {
// Removing owner permission, check that there is at least one other confirmed owner // Removing owner permission, check that there is at least one other confirmed owner
if UserOrganization::count_confirmed_by_org_and_type(&data.org_uuid, UserOrgType::Owner, &mut conn).await <= 1 { if Membership::count_confirmed_by_org_and_type(&data.org_uuid, MembershipType::Owner, &mut conn).await <= 1 {
err!("Can't change the type of the last owner") err!("Can't change the type of the last owner")
} }
} }
// This check is also done at api::organizations::{accept_invite(), _confirm_invite, _activate_user(), edit_user()}, update_user_org_type // This check is also done at api::organizations::{accept_invite, _confirm_invite, _activate_member, edit_member}, update_membership_type
// It returns different error messages per function. // It returns different error messages per function.
if new_type < UserOrgType::Admin { if new_type < MembershipType::Admin {
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &user_to_edit.org_uuid, true, &mut conn).await { match OrgPolicy::is_user_allowed(&member_to_edit.user_uuid, &member_to_edit.org_uuid, true, &mut conn).await {
Ok(_) => {} Ok(_) => {}
Err(OrgPolicyErr::TwoFactorMissing) => { Err(OrgPolicyErr::TwoFactorMissing) => {
if CONFIG.email_2fa_auto_fallback() { if CONFIG.email_2fa_auto_fallback() {
two_factor::email::find_and_activate_email_2fa(&user_to_edit.user_uuid, &mut conn).await?; two_factor::email::find_and_activate_email_2fa(&member_to_edit.user_uuid, &mut conn).await?;
} else { } else {
err!("You cannot modify this user to this type because they have not setup 2FA"); err!("You cannot modify this user to this type because they have not setup 2FA");
} }
@ -533,17 +532,17 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
log_event( log_event(
EventType::OrganizationUserUpdated as i32, EventType::OrganizationUserUpdated as i32,
&user_to_edit.uuid, &member_to_edit.uuid,
&data.org_uuid, &data.org_uuid,
ACTING_ADMIN_USER, &ACTING_ADMIN_USER.into(),
14, // Use UnknownBrowser type 14, // Use UnknownBrowser type
&token.ip.ip, &token.ip.ip,
&mut conn, &mut conn,
) )
.await; .await;
user_to_edit.atype = new_type; member_to_edit.atype = new_type;
user_to_edit.save(&mut conn).await member_to_edit.save(&mut conn).await
} }
#[post("/users/update_revision")] #[post("/users/update_revision")]
@ -557,7 +556,7 @@ async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResu
let mut organizations_json = Vec::with_capacity(organizations.len()); let mut organizations_json = Vec::with_capacity(organizations.len());
for o in organizations { for o in organizations {
let mut org = o.to_json(); let mut org = o.to_json();
org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &mut conn).await); org["user_count"] = json!(Membership::count_by_org(&o.uuid, &mut conn).await);
org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &mut conn).await); org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &mut conn).await);
org["collection_count"] = json!(Collection::count_by_org(&o.uuid, &mut conn).await); org["collection_count"] = json!(Collection::count_by_org(&o.uuid, &mut conn).await);
org["group_count"] = json!(Group::count_by_org(&o.uuid, &mut conn).await); org["group_count"] = json!(Group::count_by_org(&o.uuid, &mut conn).await);
@ -571,9 +570,9 @@ async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResu
Ok(Html(text)) Ok(Html(text))
} }
#[post("/organizations/<uuid>/delete")] #[post("/organizations/<org_id>/delete")]
async fn delete_organization(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn delete_organization(org_id: OrganizationId, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
let org = Organization::find_by_uuid(uuid, &mut conn).await.map_res("Organization doesn't exist")?; let org = Organization::find_by_uuid(&org_id, &mut conn).await.map_res("Organization doesn't exist")?;
org.delete(&mut conn).await org.delete(&mut conn).await
} }

Datei anzeigen

@ -79,7 +79,7 @@ pub struct RegisterData {
name: Option<String>, name: Option<String>,
token: Option<String>, token: Option<String>,
#[allow(dead_code)] #[allow(dead_code)]
organization_user_id: Option<String>, organization_user_id: Option<MembershipId>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -106,15 +106,15 @@ fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult
} }
Ok(()) Ok(())
} }
async fn is_email_2fa_required(org_user_uuid: Option<String>, conn: &mut DbConn) -> bool { async fn is_email_2fa_required(member_id: Option<MembershipId>, conn: &mut DbConn) -> bool {
if !CONFIG._enable_email_2fa() { if !CONFIG._enable_email_2fa() {
return false; return false;
} }
if CONFIG.email_2fa_enforce_on_verified_invite() { if CONFIG.email_2fa_enforce_on_verified_invite() {
return true; return true;
} }
if org_user_uuid.is_some() { if member_id.is_some() {
return OrgPolicy::is_enabled_for_member(&org_user_uuid.unwrap(), OrgPolicyType::TwoFactorAuthentication, conn) return OrgPolicy::is_enabled_for_member(&member_id.unwrap(), OrgPolicyType::TwoFactorAuthentication, conn)
.await; .await;
} }
false false
@ -161,9 +161,9 @@ pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult
err!("Registration email does not match invite email") err!("Registration email does not match invite email")
} }
} else if Invitation::take(&email, &mut conn).await { } else if Invitation::take(&email, &mut conn).await {
for user_org in UserOrganization::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() { for membership in Membership::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() {
user_org.status = UserOrgStatus::Accepted as i32; membership.status = MembershipStatus::Accepted as i32;
user_org.save(&mut conn).await?; membership.save(&mut conn).await?;
} }
user user
} else if CONFIG.is_signup_allowed(&email) } else if CONFIG.is_signup_allowed(&email)
@ -305,9 +305,9 @@ async fn put_avatar(data: Json<AvatarData>, headers: Headers, mut conn: DbConn)
Ok(Json(user.to_json(&mut conn).await)) Ok(Json(user.to_json(&mut conn).await))
} }
#[get("/users/<uuid>/public-key")] #[get("/users/<user_id>/public-key")]
async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_public_keys(user_id: UserId, _headers: Headers, mut conn: DbConn) -> JsonResult {
let user = match User::find_by_uuid(uuid, &mut conn).await { let user = match User::find_by_uuid(&user_id, &mut conn).await {
Some(user) if user.public_key.is_some() => user, Some(user) if user.public_key.is_some() => user,
Some(_) => err_code!("User has no public_key", Status::NotFound.code), Some(_) => err_code!("User has no public_key", Status::NotFound.code),
None => err_code!("User doesn't exist", Status::NotFound.code), None => err_code!("User doesn't exist", Status::NotFound.code),
@ -379,7 +379,7 @@ async fn post_password(data: Json<ChangePassData>, headers: Headers, mut conn: D
// Prevent logging out the client where the user requested this endpoint from. // Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side. // If you do logout the user it will causes issues at the client side.
// Adding the device uuid will prevent this. // Adding the device uuid will prevent this.
nt.send_logout(&user, Some(headers.device.uuid)).await; nt.send_logout(&user, Some(headers.device.uuid.clone())).await;
save_result save_result
} }
@ -439,7 +439,7 @@ async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, mut conn: DbConn,
user.set_password(&data.new_master_password_hash, Some(data.key), true, None); user.set_password(&data.new_master_password_hash, Some(data.key), true, None);
let save_result = user.save(&mut conn).await; let save_result = user.save(&mut conn).await;
nt.send_logout(&user, Some(headers.device.uuid)).await; nt.send_logout(&user, Some(headers.device.uuid.clone())).await;
save_result save_result
} }
@ -450,21 +450,21 @@ struct UpdateFolderData {
// There is a bug in 2024.3.x which adds a `null` item. // There is a bug in 2024.3.x which adds a `null` item.
// To bypass this we allow a Option here, but skip it during the updates // To bypass this we allow a Option here, but skip it during the updates
// See: https://github.com/bitwarden/clients/issues/8453 // See: https://github.com/bitwarden/clients/issues/8453
id: Option<String>, id: Option<FolderId>,
name: String, name: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct UpdateEmergencyAccessData { struct UpdateEmergencyAccessData {
id: String, id: EmergencyAccessId,
key_encrypted: String, key_encrypted: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct UpdateResetPasswordData { struct UpdateResetPasswordData {
organization_id: String, organization_id: OrganizationId,
reset_password_key: String, reset_password_key: String,
} }
@ -489,48 +489,49 @@ fn validate_keydata(
existing_ciphers: &[Cipher], existing_ciphers: &[Cipher],
existing_folders: &[Folder], existing_folders: &[Folder],
existing_emergency_access: &[EmergencyAccess], existing_emergency_access: &[EmergencyAccess],
existing_user_orgs: &[UserOrganization], existing_memberships: &[Membership],
existing_sends: &[Send], existing_sends: &[Send],
) -> EmptyResult { ) -> EmptyResult {
// Check that we're correctly rotating all the user's ciphers // Check that we're correctly rotating all the user's ciphers
let existing_cipher_ids = existing_ciphers.iter().map(|c| c.uuid.as_str()).collect::<HashSet<_>>(); let existing_cipher_ids = existing_ciphers.iter().map(|c| &c.uuid).collect::<HashSet<&CipherId>>();
let provided_cipher_ids = data let provided_cipher_ids = data
.ciphers .ciphers
.iter() .iter()
.filter(|c| c.organization_id.is_none()) .filter(|c| c.organization_id.is_none())
.filter_map(|c| c.id.as_deref()) .filter_map(|c| c.id.as_ref())
.collect::<HashSet<_>>(); .collect::<HashSet<&CipherId>>();
if !provided_cipher_ids.is_superset(&existing_cipher_ids) { if !provided_cipher_ids.is_superset(&existing_cipher_ids) {
err!("All existing ciphers must be included in the rotation") err!("All existing ciphers must be included in the rotation")
} }
// Check that we're correctly rotating all the user's folders // Check that we're correctly rotating all the user's folders
let existing_folder_ids = existing_folders.iter().map(|f| f.uuid.as_str()).collect::<HashSet<_>>(); let existing_folder_ids = existing_folders.iter().map(|f| &f.uuid).collect::<HashSet<&FolderId>>();
let provided_folder_ids = data.folders.iter().filter_map(|f| f.id.as_deref()).collect::<HashSet<_>>(); let provided_folder_ids = data.folders.iter().filter_map(|f| f.id.as_ref()).collect::<HashSet<&FolderId>>();
if !provided_folder_ids.is_superset(&existing_folder_ids) { if !provided_folder_ids.is_superset(&existing_folder_ids) {
err!("All existing folders must be included in the rotation") err!("All existing folders must be included in the rotation")
} }
// Check that we're correctly rotating all the user's emergency access keys // Check that we're correctly rotating all the user's emergency access keys
let existing_emergency_access_ids = let existing_emergency_access_ids =
existing_emergency_access.iter().map(|ea| ea.uuid.as_str()).collect::<HashSet<_>>(); existing_emergency_access.iter().map(|ea| &ea.uuid).collect::<HashSet<&EmergencyAccessId>>();
let provided_emergency_access_ids = let provided_emergency_access_ids =
data.emergency_access_keys.iter().map(|ea| ea.id.as_str()).collect::<HashSet<_>>(); data.emergency_access_keys.iter().map(|ea| &ea.id).collect::<HashSet<&EmergencyAccessId>>();
if !provided_emergency_access_ids.is_superset(&existing_emergency_access_ids) { if !provided_emergency_access_ids.is_superset(&existing_emergency_access_ids) {
err!("All existing emergency access keys must be included in the rotation") err!("All existing emergency access keys must be included in the rotation")
} }
// Check that we're correctly rotating all the user's reset password keys // Check that we're correctly rotating all the user's reset password keys
let existing_reset_password_ids = existing_user_orgs.iter().map(|uo| uo.org_uuid.as_str()).collect::<HashSet<_>>(); let existing_reset_password_ids =
existing_memberships.iter().map(|m| &m.org_uuid).collect::<HashSet<&OrganizationId>>();
let provided_reset_password_ids = let provided_reset_password_ids =
data.reset_password_keys.iter().map(|rp| rp.organization_id.as_str()).collect::<HashSet<_>>(); data.reset_password_keys.iter().map(|rp| &rp.organization_id).collect::<HashSet<&OrganizationId>>();
if !provided_reset_password_ids.is_superset(&existing_reset_password_ids) { if !provided_reset_password_ids.is_superset(&existing_reset_password_ids) {
err!("All existing reset password keys must be included in the rotation") err!("All existing reset password keys must be included in the rotation")
} }
// Check that we're correctly rotating all the user's sends // Check that we're correctly rotating all the user's sends
let existing_send_ids = existing_sends.iter().map(|s| s.uuid.as_str()).collect::<HashSet<_>>(); let existing_send_ids = existing_sends.iter().map(|s| &s.uuid).collect::<HashSet<&SendId>>();
let provided_send_ids = data.sends.iter().filter_map(|s| s.id.as_deref()).collect::<HashSet<_>>(); let provided_send_ids = data.sends.iter().filter_map(|s| s.id.as_ref()).collect::<HashSet<&SendId>>();
if !provided_send_ids.is_superset(&existing_send_ids) { if !provided_send_ids.is_superset(&existing_send_ids) {
err!("All existing sends must be included in the rotation") err!("All existing sends must be included in the rotation")
} }
@ -553,24 +554,24 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, mut conn: DbConn,
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks. // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
Cipher::validate_cipher_data(&data.ciphers)?; Cipher::validate_cipher_data(&data.ciphers)?;
let user_uuid = &headers.user.uuid; let user_id = &headers.user.uuid;
// TODO: Ideally we'd do everything after this point in a single transaction. // TODO: Ideally we'd do everything after this point in a single transaction.
let mut existing_ciphers = Cipher::find_owned_by_user(user_uuid, &mut conn).await; let mut existing_ciphers = Cipher::find_owned_by_user(user_id, &mut conn).await;
let mut existing_folders = Folder::find_by_user(user_uuid, &mut conn).await; let mut existing_folders = Folder::find_by_user(user_id, &mut conn).await;
let mut existing_emergency_access = EmergencyAccess::find_all_by_grantor_uuid(user_uuid, &mut conn).await; let mut existing_emergency_access = EmergencyAccess::find_all_by_grantor_uuid(user_id, &mut conn).await;
let mut existing_user_orgs = UserOrganization::find_by_user(user_uuid, &mut conn).await; let mut existing_memberships = Membership::find_by_user(user_id, &mut conn).await;
// We only rotate the reset password key if it is set. // We only rotate the reset password key if it is set.
existing_user_orgs.retain(|uo| uo.reset_password_key.is_some()); existing_memberships.retain(|m| m.reset_password_key.is_some());
let mut existing_sends = Send::find_by_user(user_uuid, &mut conn).await; let mut existing_sends = Send::find_by_user(user_id, &mut conn).await;
validate_keydata( validate_keydata(
&data, &data,
&existing_ciphers, &existing_ciphers,
&existing_folders, &existing_folders,
&existing_emergency_access, &existing_emergency_access,
&existing_user_orgs, &existing_memberships,
&existing_sends, &existing_sends,
)?; )?;
@ -602,14 +603,14 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, mut conn: DbConn,
// Update reset password data // Update reset password data
for reset_password_data in data.reset_password_keys { for reset_password_data in data.reset_password_keys {
let Some(user_org) = let Some(membership) =
existing_user_orgs.iter_mut().find(|uo| uo.org_uuid == reset_password_data.organization_id) existing_memberships.iter_mut().find(|m| m.org_uuid == reset_password_data.organization_id)
else { else {
err!("Reset password doesn't exist") err!("Reset password doesn't exist")
}; };
user_org.reset_password_key = Some(reset_password_data.reset_password_key); membership.reset_password_key = Some(reset_password_data.reset_password_key);
user_org.save(&mut conn).await? membership.save(&mut conn).await?
} }
// Update send data // Update send data
@ -650,7 +651,7 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, mut conn: DbConn,
// Prevent logging out the client where the user requested this endpoint from. // Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side. // If you do logout the user it will causes issues at the client side.
// Adding the device uuid will prevent this. // Adding the device uuid will prevent this.
nt.send_logout(&user, Some(headers.device.uuid)).await; nt.send_logout(&user, Some(headers.device.uuid.clone())).await;
save_result save_result
} }
@ -797,7 +798,7 @@ async fn post_verify_email(headers: Headers) -> EmptyResult {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct VerifyEmailTokenData { struct VerifyEmailTokenData {
user_id: String, user_id: UserId,
token: String, token: String,
} }
@ -812,7 +813,7 @@ async fn post_verify_email_token(data: Json<VerifyEmailTokenData>, mut conn: DbC
let Ok(claims) = decode_verify_email(&data.token) else { let Ok(claims) = decode_verify_email(&data.token) else {
err!("Invalid claim") err!("Invalid claim")
}; };
if claims.sub != user.uuid { if claims.sub != *user.uuid {
err!("Invalid claim"); err!("Invalid claim");
} }
user.verified_at = Some(Utc::now().naive_utc()); user.verified_at = Some(Utc::now().naive_utc());
@ -854,7 +855,7 @@ async fn post_delete_recover(data: Json<DeleteRecoverData>, mut conn: DbConn) ->
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct DeleteRecoverTokenData { struct DeleteRecoverTokenData {
user_id: String, user_id: UserId,
token: String, token: String,
} }
@ -870,7 +871,7 @@ async fn post_delete_recover_token(data: Json<DeleteRecoverTokenData>, mut conn:
err!("User doesn't exist") err!("User doesn't exist")
}; };
if claims.sub != user.uuid { if claims.sub != *user.uuid {
err!("Invalid claim"); err!("Invalid claim");
} }
user.delete(&mut conn).await user.delete(&mut conn).await
@ -1032,7 +1033,7 @@ async fn get_known_device(device: KnownDevice, mut conn: DbConn) -> JsonResult {
struct KnownDevice { struct KnownDevice {
email: String, email: String,
uuid: String, uuid: DeviceId,
} }
#[rocket::async_trait] #[rocket::async_trait]
@ -1055,7 +1056,7 @@ impl<'r> FromRequest<'r> for KnownDevice {
}; };
let uuid = if let Some(uuid) = req.headers().get_one("X-Device-Identifier") { let uuid = if let Some(uuid) = req.headers().get_one("X-Device-Identifier") {
uuid.to_string() uuid.to_string().into()
} else { } else {
return Outcome::Error((Status::BadRequest, "X-Device-Identifier value is required")); return Outcome::Error((Status::BadRequest, "X-Device-Identifier value is required"));
}; };
@ -1073,26 +1074,31 @@ struct PushToken {
push_token: String, push_token: String,
} }
#[post("/devices/identifier/<uuid>/token", data = "<data>")] #[post("/devices/identifier/<device_id>/token", data = "<data>")]
async fn post_device_token(uuid: &str, data: Json<PushToken>, headers: Headers, conn: DbConn) -> EmptyResult { async fn post_device_token(device_id: DeviceId, data: Json<PushToken>, headers: Headers, conn: DbConn) -> EmptyResult {
put_device_token(uuid, data, headers, conn).await put_device_token(device_id, data, headers, conn).await
} }
#[put("/devices/identifier/<uuid>/token", data = "<data>")] #[put("/devices/identifier/<device_id>/token", data = "<data>")]
async fn put_device_token(uuid: &str, data: Json<PushToken>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn put_device_token(
device_id: DeviceId,
data: Json<PushToken>,
headers: Headers,
mut conn: DbConn,
) -> EmptyResult {
let data = data.into_inner(); let data = data.into_inner();
let token = data.push_token; let token = data.push_token;
let Some(mut device) = Device::find_by_uuid_and_user(&headers.device.uuid, &headers.user.uuid, &mut conn).await let Some(mut device) = Device::find_by_uuid_and_user(&headers.device.uuid, &headers.user.uuid, &mut conn).await
else { else {
err!(format!("Error: device {uuid} should be present before a token can be assigned")) err!(format!("Error: device {device_id} should be present before a token can be assigned"))
}; };
// if the device already has been registered // if the device already has been registered
if device.is_registered() { if device.is_registered() {
// check if the new token is the same as the registered token // check if the new token is the same as the registered token
if device.push_token.is_some() && device.push_token.unwrap() == token.clone() { if device.push_token.is_some() && device.push_token.unwrap() == token.clone() {
debug!("Device {} is already registered and token is the same", uuid); debug!("Device {} is already registered and token is the same", device_id);
return Ok(()); return Ok(());
} else { } else {
// Try to unregister already registered device // Try to unregister already registered device
@ -1111,8 +1117,8 @@ async fn put_device_token(uuid: &str, data: Json<PushToken>, headers: Headers, m
Ok(()) Ok(())
} }
#[put("/devices/identifier/<uuid>/clear-token")] #[put("/devices/identifier/<device_id>/clear-token")]
async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult { async fn put_clear_device_token(device_id: DeviceId, mut conn: DbConn) -> EmptyResult {
// This only clears push token // This only clears push token
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109 // https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37 // https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
@ -1121,8 +1127,8 @@ async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
return Ok(()); return Ok(());
} }
if let Some(device) = Device::find_by_uuid(uuid, &mut conn).await { if let Some(device) = Device::find_by_uuid(&device_id, &mut conn).await {
Device::clear_push_token_by_uuid(uuid, &mut conn).await?; Device::clear_push_token_by_uuid(&device_id, &mut conn).await?;
unregister_push_device(device.push_uuid).await?; unregister_push_device(device.push_uuid).await?;
} }
@ -1130,16 +1136,16 @@ async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
} }
// On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere // On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere
#[post("/devices/identifier/<uuid>/clear-token")] #[post("/devices/identifier/<device_id>/clear-token")]
async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult { async fn post_clear_device_token(device_id: DeviceId, conn: DbConn) -> EmptyResult {
put_clear_device_token(uuid, conn).await put_clear_device_token(device_id, conn).await
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct AuthRequestRequest { struct AuthRequestRequest {
access_code: String, access_code: String,
device_identifier: String, device_identifier: DeviceId,
email: String, email: String,
public_key: String, public_key: String,
// Not used for now // Not used for now
@ -1193,16 +1199,17 @@ async fn post_auth_request(
}))) })))
} }
#[get("/auth-requests/<uuid>")] #[get("/auth-requests/<auth_request_id>")]
async fn get_auth_request(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_auth_request(auth_request_id: AuthRequestId, headers: Headers, mut conn: DbConn) -> JsonResult {
let Some(auth_request) = AuthRequest::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await else { let Some(auth_request) = AuthRequest::find_by_uuid_and_user(&auth_request_id, &headers.user.uuid, &mut conn).await
else {
err!("AuthRequest doesn't exist", "Record not found or user uuid does not match") err!("AuthRequest doesn't exist", "Record not found or user uuid does not match")
}; };
let response_date_utc = auth_request.response_date.map(|response_date| format_date(&response_date)); let response_date_utc = auth_request.response_date.map(|response_date| format_date(&response_date));
Ok(Json(json!({ Ok(Json(json!({
"id": uuid, "id": &auth_request_id,
"publicKey": auth_request.public_key, "publicKey": auth_request.public_key,
"requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(), "requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(),
"requestIpAddress": auth_request.request_ip, "requestIpAddress": auth_request.request_ip,
@ -1219,15 +1226,15 @@ async fn get_auth_request(uuid: &str, headers: Headers, mut conn: DbConn) -> Jso
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct AuthResponseRequest { struct AuthResponseRequest {
device_identifier: String, device_identifier: DeviceId,
key: String, key: String,
master_password_hash: Option<String>, master_password_hash: Option<String>,
request_approved: bool, request_approved: bool,
} }
#[put("/auth-requests/<uuid>", data = "<data>")] #[put("/auth-requests/<auth_request_id>", data = "<data>")]
async fn put_auth_request( async fn put_auth_request(
uuid: &str, auth_request_id: AuthRequestId,
data: Json<AuthResponseRequest>, data: Json<AuthResponseRequest>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -1235,7 +1242,9 @@ async fn put_auth_request(
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data = data.into_inner(); let data = data.into_inner();
let Some(mut auth_request) = AuthRequest::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await else { let Some(mut auth_request) =
AuthRequest::find_by_uuid_and_user(&auth_request_id, &headers.user.uuid, &mut conn).await
else {
err!("AuthRequest doesn't exist", "Record not found or user uuid does not match") err!("AuthRequest doesn't exist", "Record not found or user uuid does not match")
}; };
@ -1262,7 +1271,7 @@ async fn put_auth_request(
} }
Ok(Json(json!({ Ok(Json(json!({
"id": uuid, "id": &auth_request_id,
"publicKey": auth_request.public_key, "publicKey": auth_request.public_key,
"requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(), "requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(),
"requestIpAddress": auth_request.request_ip, "requestIpAddress": auth_request.request_ip,
@ -1276,14 +1285,14 @@ async fn put_auth_request(
}))) })))
} }
#[get("/auth-requests/<uuid>/response?<code>")] #[get("/auth-requests/<auth_request_id>/response?<code>")]
async fn get_auth_request_response( async fn get_auth_request_response(
uuid: &str, auth_request_id: AuthRequestId,
code: &str, code: &str,
client_headers: ClientHeaders, client_headers: ClientHeaders,
mut conn: DbConn, mut conn: DbConn,
) -> JsonResult { ) -> JsonResult {
let Some(auth_request) = AuthRequest::find_by_uuid(uuid, &mut conn).await else { let Some(auth_request) = AuthRequest::find_by_uuid(&auth_request_id, &mut conn).await else {
err!("AuthRequest doesn't exist", "User not found") err!("AuthRequest doesn't exist", "User not found")
}; };
@ -1297,7 +1306,7 @@ async fn get_auth_request_response(
let response_date_utc = auth_request.response_date.map(|response_date| format_date(&response_date)); let response_date_utc = auth_request.response_date.map(|response_date| format_date(&response_date));
Ok(Json(json!({ Ok(Json(json!({
"id": uuid, "id": &auth_request_id,
"publicKey": auth_request.public_key, "publicKey": auth_request.public_key,
"requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(), "requestDeviceType": DeviceType::from_i32(auth_request.device_type).to_string(),
"requestIpAddress": auth_request.request_ip, "requestIpAddress": auth_request.request_ip,

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -93,10 +93,10 @@ async fn get_grantees(headers: Headers, mut conn: DbConn) -> Json<Value> {
} }
#[get("/emergency-access/<emer_id>")] #[get("/emergency-access/<emer_id>")]
async fn get_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
match EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await { match EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &mut conn).await {
Some(emergency_access) => Ok(Json( Some(emergency_access) => Ok(Json(
emergency_access.to_json_grantee_details(&mut conn).await.expect("Grantee user should exist but does not!"), emergency_access.to_json_grantee_details(&mut conn).await.expect("Grantee user should exist but does not!"),
)), )),
@ -118,7 +118,7 @@ struct EmergencyAccessUpdateData {
#[put("/emergency-access/<emer_id>", data = "<data>")] #[put("/emergency-access/<emer_id>", data = "<data>")]
async fn put_emergency_access( async fn put_emergency_access(
emer_id: &str, emer_id: EmergencyAccessId,
data: Json<EmergencyAccessUpdateData>, data: Json<EmergencyAccessUpdateData>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -128,7 +128,7 @@ async fn put_emergency_access(
#[post("/emergency-access/<emer_id>", data = "<data>")] #[post("/emergency-access/<emer_id>", data = "<data>")]
async fn post_emergency_access( async fn post_emergency_access(
emer_id: &str, emer_id: EmergencyAccessId,
data: Json<EmergencyAccessUpdateData>, data: Json<EmergencyAccessUpdateData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -138,7 +138,7 @@ async fn post_emergency_access(
let data: EmergencyAccessUpdateData = data.into_inner(); let data: EmergencyAccessUpdateData = data.into_inner();
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -163,12 +163,12 @@ async fn post_emergency_access(
// region delete // region delete
#[delete("/emergency-access/<emer_id>")] #[delete("/emergency-access/<emer_id>")]
async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn delete_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> EmptyResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let emergency_access = match ( let emergency_access = match (
EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await, EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &mut conn).await,
EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &headers.user.uuid, &mut conn).await, EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &headers.user.uuid, &mut conn).await,
) { ) {
(Some(grantor_emer), None) => { (Some(grantor_emer), None) => {
info!("Grantor deleted emergency access {emer_id}"); info!("Grantor deleted emergency access {emer_id}");
@ -186,7 +186,7 @@ async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
} }
#[post("/emergency-access/<emer_id>/delete")] #[post("/emergency-access/<emer_id>/delete")]
async fn post_delete_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> EmptyResult { async fn post_delete_emergency_access(emer_id: EmergencyAccessId, headers: Headers, conn: DbConn) -> EmptyResult {
delete_emergency_access(emer_id, headers, conn).await delete_emergency_access(emer_id, headers, conn).await
} }
@ -266,8 +266,8 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, mu
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_emergency_access_invite( mail::send_emergency_access_invite(
&new_emergency_access.email.expect("Grantee email does not exists"), &new_emergency_access.email.expect("Grantee email does not exists"),
&grantee_user.uuid, grantee_user.uuid,
&new_emergency_access.uuid, new_emergency_access.uuid,
&grantor_user.name, &grantor_user.name,
&grantor_user.email, &grantor_user.email,
) )
@ -281,11 +281,11 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, mu
} }
#[post("/emergency-access/<emer_id>/reinvite")] #[post("/emergency-access/<emer_id>/reinvite")]
async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn resend_invite(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> EmptyResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -307,8 +307,8 @@ async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> Emp
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_emergency_access_invite( mail::send_emergency_access_invite(
&email, &email,
&grantor_user.uuid, grantor_user.uuid,
&emergency_access.uuid, emergency_access.uuid,
&grantor_user.name, &grantor_user.name,
&grantor_user.email, &grantor_user.email,
) )
@ -331,7 +331,12 @@ struct AcceptData {
} }
#[post("/emergency-access/<emer_id>/accept", data = "<data>")] #[post("/emergency-access/<emer_id>/accept", data = "<data>")]
async fn accept_invite(emer_id: &str, data: Json<AcceptData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn accept_invite(
emer_id: EmergencyAccessId,
data: Json<AcceptData>,
headers: Headers,
mut conn: DbConn,
) -> EmptyResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let data: AcceptData = data.into_inner(); let data: AcceptData = data.into_inner();
@ -355,7 +360,7 @@ async fn accept_invite(emer_id: &str, data: Json<AcceptData>, headers: Headers,
// We need to search for the uuid in combination with the email, since we do not yet store the uuid of the grantee in the database. // We need to search for the uuid in combination with the email, since we do not yet store the uuid of the grantee in the database.
// The uuid of the grantee gets stored once accepted. // The uuid of the grantee gets stored once accepted.
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_email(emer_id, &headers.user.email, &mut conn).await EmergencyAccess::find_by_uuid_and_grantee_email(&emer_id, &headers.user.email, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -389,7 +394,7 @@ struct ConfirmData {
#[post("/emergency-access/<emer_id>/confirm", data = "<data>")] #[post("/emergency-access/<emer_id>/confirm", data = "<data>")]
async fn confirm_emergency_access( async fn confirm_emergency_access(
emer_id: &str, emer_id: EmergencyAccessId,
data: Json<ConfirmData>, data: Json<ConfirmData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -401,7 +406,7 @@ async fn confirm_emergency_access(
let key = data.key; let key = data.key;
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &confirming_user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &confirming_user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -441,12 +446,12 @@ async fn confirm_emergency_access(
// region access emergency access // region access emergency access
#[post("/emergency-access/<emer_id>/initiate")] #[post("/emergency-access/<emer_id>/initiate")]
async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn initiate_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let initiating_user = headers.user; let initiating_user = headers.user;
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &initiating_user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &initiating_user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -479,11 +484,11 @@ async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
} }
#[post("/emergency-access/<emer_id>/approve")] #[post("/emergency-access/<emer_id>/approve")]
async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn approve_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -514,11 +519,11 @@ async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbC
} }
#[post("/emergency-access/<emer_id>/reject")] #[post("/emergency-access/<emer_id>/reject")]
async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn reject_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let Some(mut emergency_access) = let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantor_uuid(emer_id, &headers.user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -551,11 +556,11 @@ async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
// region action // region action
#[post("/emergency-access/<emer_id>/view")] #[post("/emergency-access/<emer_id>/view")]
async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn view_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let Some(emergency_access) = let Some(emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &headers.user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &headers.user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -589,12 +594,12 @@ async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn
} }
#[post("/emergency-access/<emer_id>/takeover")] #[post("/emergency-access/<emer_id>/takeover")]
async fn takeover_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn takeover_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> JsonResult {
check_emergency_access_enabled()?; check_emergency_access_enabled()?;
let requesting_user = headers.user; let requesting_user = headers.user;
let Some(emergency_access) = let Some(emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &requesting_user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &requesting_user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -628,7 +633,7 @@ struct EmergencyAccessPasswordData {
#[post("/emergency-access/<emer_id>/password", data = "<data>")] #[post("/emergency-access/<emer_id>/password", data = "<data>")]
async fn password_emergency_access( async fn password_emergency_access(
emer_id: &str, emer_id: EmergencyAccessId,
data: Json<EmergencyAccessPasswordData>, data: Json<EmergencyAccessPasswordData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -641,7 +646,7 @@ async fn password_emergency_access(
let requesting_user = headers.user; let requesting_user = headers.user;
let Some(emergency_access) = let Some(emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &requesting_user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &requesting_user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -662,9 +667,9 @@ async fn password_emergency_access(
TwoFactor::delete_all_by_user(&grantor_user.uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&grantor_user.uuid, &mut conn).await?;
// Remove grantor from all organisations unless Owner // Remove grantor from all organisations unless Owner
for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &mut conn).await { for member in Membership::find_any_state_by_user(&grantor_user.uuid, &mut conn).await {
if user_org.atype != UserOrgType::Owner as i32 { if member.atype != MembershipType::Owner as i32 {
user_org.delete(&mut conn).await?; member.delete(&mut conn).await?;
} }
} }
Ok(()) Ok(())
@ -673,10 +678,10 @@ async fn password_emergency_access(
// endregion // endregion
#[get("/emergency-access/<emer_id>/policies")] #[get("/emergency-access/<emer_id>/policies")]
async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn policies_emergency_access(emer_id: EmergencyAccessId, headers: Headers, mut conn: DbConn) -> JsonResult {
let requesting_user = headers.user; let requesting_user = headers.user;
let Some(emergency_access) = let Some(emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_uuid(emer_id, &requesting_user.uuid, &mut conn).await EmergencyAccess::find_by_uuid_and_grantee_uuid(&emer_id, &requesting_user.uuid, &mut conn).await
else { else {
err!("Emergency access not valid.") err!("Emergency access not valid.")
}; };
@ -701,11 +706,11 @@ async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
fn is_valid_request( fn is_valid_request(
emergency_access: &EmergencyAccess, emergency_access: &EmergencyAccess,
requesting_user_uuid: &str, requesting_user_id: &UserId,
requested_access_type: EmergencyAccessType, requested_access_type: EmergencyAccessType,
) -> bool { ) -> bool {
emergency_access.grantee_uuid.is_some() emergency_access.grantee_uuid.is_some()
&& emergency_access.grantee_uuid.as_ref().unwrap() == requesting_user_uuid && emergency_access.grantee_uuid.as_ref().unwrap() == requesting_user_id
&& emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32 && emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32
&& emergency_access.atype == requested_access_type as i32 && emergency_access.atype == requested_access_type as i32
} }

Datei anzeigen

@ -8,7 +8,7 @@ use crate::{
api::{EmptyResult, JsonResult}, api::{EmptyResult, JsonResult},
auth::{AdminHeaders, Headers}, auth::{AdminHeaders, Headers},
db::{ db::{
models::{Cipher, Event, UserOrganization}, models::{Cipher, CipherId, Event, Membership, MembershipId, OrganizationId, UserId},
DbConn, DbPool, DbConn, DbPool,
}, },
util::parse_date, util::parse_date,
@ -31,69 +31,8 @@ struct EventRange {
// Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41 // Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41
#[get("/organizations/<org_id>/events?<data..>")] #[get("/organizations/<org_id>/events?<data..>")]
async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { async fn get_org_events(
// Return an empty vec when we org events are disabled. org_id: OrganizationId,
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
Vec::with_capacity(0)
} else {
let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date)
} else {
parse_date(&data.end)
};
Event::find_by_organization_uuid(org_id, &start_date, &end_date, &mut conn)
.await
.iter()
.map(|e| e.to_json())
.collect()
};
Ok(Json(json!({
"data": events_json,
"object": "list",
"continuationToken": get_continuation_token(&events_json),
})))
}
#[get("/ciphers/<cipher_id>/events?<data..>")]
async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult {
// Return an empty vec when we org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
Vec::with_capacity(0)
} else {
let mut events_json = Vec::with_capacity(0);
if UserOrganization::user_has_ge_admin_access_to_cipher(&headers.user.uuid, cipher_id, &mut conn).await {
let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date)
} else {
parse_date(&data.end)
};
events_json = Event::find_by_cipher_uuid(cipher_id, &start_date, &end_date, &mut conn)
.await
.iter()
.map(|e| e.to_json())
.collect()
}
events_json
};
Ok(Json(json!({
"data": events_json,
"object": "list",
"continuationToken": get_continuation_token(&events_json),
})))
}
#[get("/organizations/<org_id>/users/<user_org_id>/events?<data..>")]
async fn get_user_events(
org_id: &str,
user_org_id: &str,
data: EventRange, data: EventRange,
_headers: AdminHeaders, _headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
@ -110,7 +49,73 @@ async fn get_user_events(
parse_date(&data.end) parse_date(&data.end)
}; };
Event::find_by_org_and_user_org(org_id, user_org_id, &start_date, &end_date, &mut conn) Event::find_by_organization_uuid(&org_id, &start_date, &end_date, &mut conn)
.await
.iter()
.map(|e| e.to_json())
.collect()
};
Ok(Json(json!({
"data": events_json,
"object": "list",
"continuationToken": get_continuation_token(&events_json),
})))
}
#[get("/ciphers/<cipher_id>/events?<data..>")]
async fn get_cipher_events(cipher_id: CipherId, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult {
// Return an empty vec when we org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
Vec::with_capacity(0)
} else {
let mut events_json = Vec::with_capacity(0);
if Membership::user_has_ge_admin_access_to_cipher(&headers.user.uuid, &cipher_id, &mut conn).await {
let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date)
} else {
parse_date(&data.end)
};
events_json = Event::find_by_cipher_uuid(&cipher_id, &start_date, &end_date, &mut conn)
.await
.iter()
.map(|e| e.to_json())
.collect()
}
events_json
};
Ok(Json(json!({
"data": events_json,
"object": "list",
"continuationToken": get_continuation_token(&events_json),
})))
}
#[get("/organizations/<org_id>/users/<member_id>/events?<data..>")]
async fn get_user_events(
org_id: OrganizationId,
member_id: MembershipId,
data: EventRange,
_headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
// Return an empty vec when we org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
Vec::with_capacity(0)
} else {
let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date)
} else {
parse_date(&data.end)
};
Event::find_by_org_and_member(&org_id, &member_id, &start_date, &end_date, &mut conn)
.await .await
.iter() .iter()
.map(|e| e.to_json()) .map(|e| e.to_json())
@ -152,8 +157,8 @@ struct EventCollection {
date: String, date: String,
// Optional // Optional
cipher_id: Option<String>, cipher_id: Option<CipherId>,
organization_id: Option<String>, organization_id: Option<OrganizationId>,
} }
// Upstream: // Upstream:
@ -180,11 +185,11 @@ async fn post_events_collect(data: Json<Vec<EventCollection>>, headers: Headers,
.await; .await;
} }
1600..=1699 => { 1600..=1699 => {
if let Some(org_uuid) = &event.organization_id { if let Some(org_id) = &event.organization_id {
_log_event( _log_event(
event.r#type, event.r#type,
org_uuid, org_id,
org_uuid, org_id,
&headers.user.uuid, &headers.user.uuid,
headers.device.atype, headers.device.atype,
Some(event_date), Some(event_date),
@ -197,11 +202,11 @@ async fn post_events_collect(data: Json<Vec<EventCollection>>, headers: Headers,
_ => { _ => {
if let Some(cipher_uuid) = &event.cipher_id { if let Some(cipher_uuid) = &event.cipher_id {
if let Some(cipher) = Cipher::find_by_uuid(cipher_uuid, &mut conn).await { if let Some(cipher) = Cipher::find_by_uuid(cipher_uuid, &mut conn).await {
if let Some(org_uuid) = cipher.organization_uuid { if let Some(org_id) = cipher.organization_uuid {
_log_event( _log_event(
event.r#type, event.r#type,
cipher_uuid, cipher_uuid,
&org_uuid, &org_id,
&headers.user.uuid, &headers.user.uuid,
headers.device.atype, headers.device.atype,
Some(event_date), Some(event_date),
@ -218,38 +223,38 @@ async fn post_events_collect(data: Json<Vec<EventCollection>>, headers: Headers,
Ok(()) Ok(())
} }
pub async fn log_user_event(event_type: i32, user_uuid: &str, device_type: i32, ip: &IpAddr, conn: &mut DbConn) { pub async fn log_user_event(event_type: i32, user_id: &UserId, device_type: i32, ip: &IpAddr, conn: &mut DbConn) {
if !CONFIG.org_events_enabled() { if !CONFIG.org_events_enabled() {
return; return;
} }
_log_user_event(event_type, user_uuid, device_type, None, ip, conn).await; _log_user_event(event_type, user_id, device_type, None, ip, conn).await;
} }
async fn _log_user_event( async fn _log_user_event(
event_type: i32, event_type: i32,
user_uuid: &str, user_id: &UserId,
device_type: i32, device_type: i32,
event_date: Option<NaiveDateTime>, event_date: Option<NaiveDateTime>,
ip: &IpAddr, ip: &IpAddr,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
let orgs = UserOrganization::get_org_uuid_by_user(user_uuid, conn).await; let orgs = Membership::get_orgs_by_user(user_id, conn).await;
let mut events: Vec<Event> = Vec::with_capacity(orgs.len() + 1); // We need an event per org and one without an org let mut events: Vec<Event> = Vec::with_capacity(orgs.len() + 1); // We need an event per org and one without an org
// Upstream saves the event also without any org_uuid. // Upstream saves the event also without any org_id.
let mut event = Event::new(event_type, event_date); let mut event = Event::new(event_type, event_date);
event.user_uuid = Some(String::from(user_uuid)); event.user_uuid = Some(user_id.clone());
event.act_user_uuid = Some(String::from(user_uuid)); event.act_user_uuid = Some(user_id.clone());
event.device_type = Some(device_type); event.device_type = Some(device_type);
event.ip_address = Some(ip.to_string()); event.ip_address = Some(ip.to_string());
events.push(event); events.push(event);
// For each org a user is a member of store these events per org // For each org a user is a member of store these events per org
for org_uuid in orgs { for org_id in orgs {
let mut event = Event::new(event_type, event_date); let mut event = Event::new(event_type, event_date);
event.user_uuid = Some(String::from(user_uuid)); event.user_uuid = Some(user_id.clone());
event.org_uuid = Some(org_uuid); event.org_uuid = Some(org_id);
event.act_user_uuid = Some(String::from(user_uuid)); event.act_user_uuid = Some(user_id.clone());
event.device_type = Some(device_type); event.device_type = Some(device_type);
event.ip_address = Some(ip.to_string()); event.ip_address = Some(ip.to_string());
events.push(event); events.push(event);
@ -261,8 +266,8 @@ async fn _log_user_event(
pub async fn log_event( pub async fn log_event(
event_type: i32, event_type: i32,
source_uuid: &str, source_uuid: &str,
org_uuid: &str, org_id: &OrganizationId,
act_user_uuid: &str, act_user_id: &UserId,
device_type: i32, device_type: i32,
ip: &IpAddr, ip: &IpAddr,
conn: &mut DbConn, conn: &mut DbConn,
@ -270,15 +275,15 @@ pub async fn log_event(
if !CONFIG.org_events_enabled() { if !CONFIG.org_events_enabled() {
return; return;
} }
_log_event(event_type, source_uuid, org_uuid, act_user_uuid, device_type, None, ip, conn).await; _log_event(event_type, source_uuid, org_id, act_user_id, device_type, None, ip, conn).await;
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn _log_event( async fn _log_event(
event_type: i32, event_type: i32,
source_uuid: &str, source_uuid: &str,
org_uuid: &str, org_id: &OrganizationId,
act_user_uuid: &str, act_user_id: &UserId,
device_type: i32, device_type: i32,
event_date: Option<NaiveDateTime>, event_date: Option<NaiveDateTime>,
ip: &IpAddr, ip: &IpAddr,
@ -290,31 +295,31 @@ async fn _log_event(
// 1000..=1099 Are user events, they need to be logged via log_user_event() // 1000..=1099 Are user events, they need to be logged via log_user_event()
// Cipher Events // Cipher Events
1100..=1199 => { 1100..=1199 => {
event.cipher_uuid = Some(String::from(source_uuid)); event.cipher_uuid = Some(source_uuid.to_string().into());
} }
// Collection Events // Collection Events
1300..=1399 => { 1300..=1399 => {
event.collection_uuid = Some(String::from(source_uuid)); event.collection_uuid = Some(source_uuid.to_string().into());
} }
// Group Events // Group Events
1400..=1499 => { 1400..=1499 => {
event.group_uuid = Some(String::from(source_uuid)); event.group_uuid = Some(source_uuid.to_string().into());
} }
// Org User Events // Org User Events
1500..=1599 => { 1500..=1599 => {
event.org_user_uuid = Some(String::from(source_uuid)); event.org_user_uuid = Some(source_uuid.to_string().into());
} }
// 1600..=1699 Are organizational events, and they do not need the source_uuid // 1600..=1699 Are organizational events, and they do not need the source_uuid
// Policy Events // Policy Events
1700..=1799 => { 1700..=1799 => {
event.policy_uuid = Some(String::from(source_uuid)); event.policy_uuid = Some(source_uuid.to_string().into());
} }
// Ignore others // Ignore others
_ => {} _ => {}
} }
event.org_uuid = Some(String::from(org_uuid)); event.org_uuid = Some(org_id.clone());
event.act_user_uuid = Some(String::from(act_user_uuid)); event.act_user_uuid = Some(act_user_id.clone());
event.device_type = Some(device_type); event.device_type = Some(device_type);
event.ip_address = Some(ip.to_string()); event.ip_address = Some(ip.to_string());
event.save(conn).await.unwrap_or(()); event.save(conn).await.unwrap_or(());

Datei anzeigen

@ -23,9 +23,9 @@ async fn get_folders(headers: Headers, mut conn: DbConn) -> Json<Value> {
})) }))
} }
#[get("/folders/<uuid>")] #[get("/folders/<folder_id>")]
async fn get_folder(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_folder(folder_id: FolderId, headers: Headers, mut conn: DbConn) -> JsonResult {
match Folder::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await { match Folder::find_by_uuid_and_user(&folder_id, &headers.user.uuid, &mut conn).await {
Some(folder) => Ok(Json(folder.to_json())), Some(folder) => Ok(Json(folder.to_json())),
_ => err!("Invalid folder", "Folder does not exist or belongs to another user"), _ => err!("Invalid folder", "Folder does not exist or belongs to another user"),
} }
@ -35,7 +35,7 @@ async fn get_folder(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResul
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct FolderData { pub struct FolderData {
pub name: String, pub name: String,
pub id: Option<String>, pub id: Option<FolderId>,
} }
#[post("/folders", data = "<data>")] #[post("/folders", data = "<data>")]
@ -50,14 +50,20 @@ async fn post_folders(data: Json<FolderData>, headers: Headers, mut conn: DbConn
Ok(Json(folder.to_json())) Ok(Json(folder.to_json()))
} }
#[post("/folders/<uuid>", data = "<data>")] #[post("/folders/<folder_id>", data = "<data>")]
async fn post_folder(uuid: &str, data: Json<FolderData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_folder(
put_folder(uuid, data, headers, conn, nt).await folder_id: FolderId,
data: Json<FolderData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
put_folder(folder_id, data, headers, conn, nt).await
} }
#[put("/folders/<uuid>", data = "<data>")] #[put("/folders/<folder_id>", data = "<data>")]
async fn put_folder( async fn put_folder(
uuid: &str, folder_id: FolderId,
data: Json<FolderData>, data: Json<FolderData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -65,7 +71,7 @@ async fn put_folder(
) -> JsonResult { ) -> JsonResult {
let data: FolderData = data.into_inner(); let data: FolderData = data.into_inner();
let Some(mut folder) = Folder::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await else { let Some(mut folder) = Folder::find_by_uuid_and_user(&folder_id, &headers.user.uuid, &mut conn).await else {
err!("Invalid folder", "Folder does not exist or belongs to another user") err!("Invalid folder", "Folder does not exist or belongs to another user")
}; };
@ -77,14 +83,14 @@ async fn put_folder(
Ok(Json(folder.to_json())) Ok(Json(folder.to_json()))
} }
#[post("/folders/<uuid>/delete")] #[post("/folders/<folder_id>/delete")]
async fn delete_folder_post(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_folder_post(folder_id: FolderId, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
delete_folder(uuid, headers, conn, nt).await delete_folder(folder_id, headers, conn, nt).await
} }
#[delete("/folders/<uuid>")] #[delete("/folders/<folder_id>")]
async fn delete_folder(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_folder(folder_id: FolderId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let Some(folder) = Folder::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await else { let Some(folder) = Folder::find_by_uuid_and_user(&folder_id, &headers.user.uuid, &mut conn).await else {
err!("Invalid folder", "Folder does not exist or belongs to another user") err!("Invalid folder", "Folder does not exist or belongs to another user")
}; };

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -55,38 +55,33 @@ async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, mut conn: Db
let mut user_created: bool = false; let mut user_created: bool = false;
if user_data.deleted { if user_data.deleted {
// If user is marked for deletion and it exists, revoke it // If user is marked for deletion and it exists, revoke it
if let Some(mut user_org) = if let Some(mut member) = Membership::find_by_email_and_org(&user_data.email, &org_id, &mut conn).await {
UserOrganization::find_by_email_and_org(&user_data.email, &org_id, &mut conn).await
{
// Only revoke a user if it is not the last confirmed owner // Only revoke a user if it is not the last confirmed owner
let revoked = if user_org.atype == UserOrgType::Owner let revoked = if member.atype == MembershipType::Owner
&& user_org.status == UserOrgStatus::Confirmed as i32 && member.status == MembershipStatus::Confirmed as i32
{ {
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn).await if Membership::count_confirmed_by_org_and_type(&org_id, MembershipType::Owner, &mut conn).await <= 1
<= 1
{ {
warn!("Can't revoke the last owner"); warn!("Can't revoke the last owner");
false false
} else { } else {
user_org.revoke() member.revoke()
} }
} else { } else {
user_org.revoke() member.revoke()
}; };
let ext_modified = user_org.set_external_id(Some(user_data.external_id.clone())); let ext_modified = member.set_external_id(Some(user_data.external_id.clone()));
if revoked || ext_modified { if revoked || ext_modified {
user_org.save(&mut conn).await?; member.save(&mut conn).await?;
} }
} }
// If user is part of the organization, restore it // If user is part of the organization, restore it
} else if let Some(mut user_org) = } else if let Some(mut member) = Membership::find_by_email_and_org(&user_data.email, &org_id, &mut conn).await {
UserOrganization::find_by_email_and_org(&user_data.email, &org_id, &mut conn).await let restored = member.restore();
{ let ext_modified = member.set_external_id(Some(user_data.external_id.clone()));
let restored = user_org.restore();
let ext_modified = user_org.set_external_id(Some(user_data.external_id.clone()));
if restored || ext_modified { if restored || ext_modified {
user_org.save(&mut conn).await?; member.save(&mut conn).await?;
} }
} else { } else {
// If user is not part of the organization // If user is not part of the organization
@ -104,17 +99,17 @@ async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, mut conn: Db
new_user new_user
} }
}; };
let user_org_status = if CONFIG.mail_enabled() || user.password_hash.is_empty() { let member_status = if CONFIG.mail_enabled() || user.password_hash.is_empty() {
UserOrgStatus::Invited as i32 MembershipStatus::Invited as i32
} else { } else {
UserOrgStatus::Accepted as i32 // Automatically mark user as accepted if no email invites MembershipStatus::Accepted as i32 // Automatically mark user as accepted if no email invites
}; };
let mut new_member = UserOrganization::new(user.uuid.clone(), org_id.clone()); let mut new_member = Membership::new(user.uuid.clone(), org_id.clone());
new_member.set_external_id(Some(user_data.external_id.clone())); new_member.set_external_id(Some(user_data.external_id.clone()));
new_member.access_all = false; new_member.access_all = false;
new_member.atype = UserOrgType::User as i32; new_member.atype = MembershipType::User as i32;
new_member.status = user_org_status; new_member.status = member_status;
new_member.save(&mut conn).await?; new_member.save(&mut conn).await?;
@ -166,9 +161,8 @@ async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, mut conn: Db
GroupUser::delete_all_by_group(&group_uuid, &mut conn).await?; GroupUser::delete_all_by_group(&group_uuid, &mut conn).await?;
for ext_id in &group_data.member_external_ids { for ext_id in &group_data.member_external_ids {
if let Some(user_org) = UserOrganization::find_by_external_id_and_org(ext_id, &org_id, &mut conn).await if let Some(member) = Membership::find_by_external_id_and_org(ext_id, &org_id, &mut conn).await {
{ let mut group_user = GroupUser::new(group_uuid.clone(), member.uuid.clone());
let mut group_user = GroupUser::new(group_uuid.clone(), user_org.uuid.clone());
group_user.save(&mut conn).await?; group_user.save(&mut conn).await?;
} }
} }
@ -181,20 +175,19 @@ async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, mut conn: Db
if data.overwrite_existing { if data.overwrite_existing {
// Generate a HashSet to quickly verify if a member is listed or not. // Generate a HashSet to quickly verify if a member is listed or not.
let sync_members: HashSet<String> = data.members.into_iter().map(|m| m.external_id).collect(); let sync_members: HashSet<String> = data.members.into_iter().map(|m| m.external_id).collect();
for user_org in UserOrganization::find_by_org(&org_id, &mut conn).await { for member in Membership::find_by_org(&org_id, &mut conn).await {
if let Some(ref user_external_id) = user_org.external_id { if let Some(ref user_external_id) = member.external_id {
if !sync_members.contains(user_external_id) { if !sync_members.contains(user_external_id) {
if user_org.atype == UserOrgType::Owner && user_org.status == UserOrgStatus::Confirmed as i32 { if member.atype == MembershipType::Owner && member.status == MembershipStatus::Confirmed as i32 {
// Removing owner, check that there is at least one other confirmed owner // Removing owner, check that there is at least one other confirmed owner
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn) if Membership::count_confirmed_by_org_and_type(&org_id, MembershipType::Owner, &mut conn).await
.await
<= 1 <= 1
{ {
warn!("Can't delete the last owner"); warn!("Can't delete the last owner");
continue; continue;
} }
} }
user_org.delete(&mut conn).await?; member.delete(&mut conn).await?;
} }
} }
} }
@ -203,7 +196,7 @@ async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, mut conn: Db
Ok(()) Ok(())
} }
pub struct PublicToken(String); pub struct PublicToken(OrganizationId);
#[rocket::async_trait] #[rocket::async_trait]
impl<'r> FromRequest<'r> for PublicToken { impl<'r> FromRequest<'r> for PublicToken {
@ -243,10 +236,11 @@ impl<'r> FromRequest<'r> for PublicToken {
Outcome::Success(conn) => conn, Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"), _ => err_handler!("Error getting DB"),
}; };
let Some(org_uuid) = claims.client_id.strip_prefix("organization.") else { let Some(org_id) = claims.client_id.strip_prefix("organization.") else {
err_handler!("Malformed client_id") err_handler!("Malformed client_id")
}; };
let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(org_uuid, &conn).await else { let org_id: OrganizationId = org_id.to_string().into();
let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(&org_id, &conn).await else {
err_handler!("Invalid client_id") err_handler!("Invalid client_id")
}; };
if org_api_key.org_uuid != claims.client_sub { if org_api_key.org_uuid != claims.client_sub {

Datei anzeigen

@ -12,7 +12,7 @@ use crate::{
api::{ApiResult, EmptyResult, JsonResult, Notify, UpdateType}, api::{ApiResult, EmptyResult, JsonResult, Notify, UpdateType},
auth::{ClientIp, Headers, Host}, auth::{ClientIp, Headers, Host},
db::{models::*, DbConn, DbPool}, db::{models::*, DbConn, DbPool},
util::{NumberOrString, SafeString}, util::NumberOrString,
CONFIG, CONFIG,
}; };
@ -67,7 +67,7 @@ pub struct SendData {
file_length: Option<NumberOrString>, file_length: Option<NumberOrString>,
// Used for key rotations // Used for key rotations
pub id: Option<String>, pub id: Option<SendId>,
} }
/// Enforces the `Disable Send` policy. A non-owner/admin user belonging to /// Enforces the `Disable Send` policy. A non-owner/admin user belonging to
@ -79,9 +79,9 @@ pub struct SendData {
/// There is also a Vaultwarden-specific `sends_allowed` config setting that /// There is also a Vaultwarden-specific `sends_allowed` config setting that
/// controls this policy globally. /// controls this policy globally.
async fn enforce_disable_send_policy(headers: &Headers, conn: &mut DbConn) -> EmptyResult { async fn enforce_disable_send_policy(headers: &Headers, conn: &mut DbConn) -> EmptyResult {
let user_uuid = &headers.user.uuid; let user_id = &headers.user.uuid;
if !CONFIG.sends_allowed() if !CONFIG.sends_allowed()
|| OrgPolicy::is_applicable_to_user(user_uuid, OrgPolicyType::DisableSend, None, conn).await || OrgPolicy::is_applicable_to_user(user_id, OrgPolicyType::DisableSend, None, conn).await
{ {
err!("Due to an Enterprise Policy, you are only able to delete an existing Send.") err!("Due to an Enterprise Policy, you are only able to delete an existing Send.")
} }
@ -95,9 +95,9 @@ async fn enforce_disable_send_policy(headers: &Headers, conn: &mut DbConn) -> Em
/// ///
/// Ref: https://bitwarden.com/help/article/policies/#send-options /// Ref: https://bitwarden.com/help/article/policies/#send-options
async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &mut DbConn) -> EmptyResult { async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &mut DbConn) -> EmptyResult {
let user_uuid = &headers.user.uuid; let user_id = &headers.user.uuid;
let hide_email = data.hide_email.unwrap_or(false); let hide_email = data.hide_email.unwrap_or(false);
if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn).await { if hide_email && OrgPolicy::is_hide_email_disabled(user_id, conn).await {
err!( err!(
"Due to an Enterprise Policy, you are not allowed to hide your email address \ "Due to an Enterprise Policy, you are not allowed to hide your email address \
from recipients when creating or editing a Send." from recipients when creating or editing a Send."
@ -106,7 +106,7 @@ async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, c
Ok(()) Ok(())
} }
fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { fn create_send(data: SendData, user_id: UserId) -> ApiResult<Send> {
let data_val = if data.r#type == SendType::Text as i32 { let data_val = if data.r#type == SendType::Text as i32 {
data.text data.text
} else if data.r#type == SendType::File as i32 { } else if data.r#type == SendType::File as i32 {
@ -129,7 +129,7 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
} }
let mut send = Send::new(data.r#type, data.name, data_str, data.key, data.deletion_date.naive_utc()); let mut send = Send::new(data.r#type, data.name, data_str, data.key, data.deletion_date.naive_utc());
send.user_uuid = Some(user_uuid); send.user_uuid = Some(user_id);
send.notes = data.notes; send.notes = data.notes;
send.max_access_count = match data.max_access_count { send.max_access_count = match data.max_access_count {
Some(m) => Some(m.into_i32()?), Some(m) => Some(m.into_i32()?),
@ -157,11 +157,11 @@ async fn get_sends(headers: Headers, mut conn: DbConn) -> Json<Value> {
})) }))
} }
#[get("/sends/<uuid>")] #[get("/sends/<send_id>")]
async fn get_send(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_send(send_id: SendId, headers: Headers, mut conn: DbConn) -> JsonResult {
match Send::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await { match Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &mut conn).await {
Some(send) => Ok(Json(send.to_json())), Some(send) => Ok(Json(send.to_json())),
None => err!("Send not found", "Invalid uuid or does not belong to user"), None => err!("Send not found", "Invalid send uuid or does not belong to user"),
} }
} }
@ -249,7 +249,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
err!("Send content is not a file"); err!("Send content is not a file");
} }
let file_id = crate::crypto::generate_send_id(); let file_id = crate::crypto::generate_send_file_id();
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send.uuid); let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send.uuid);
let file_path = folder_path.join(&file_id); let file_path = folder_path.join(&file_id);
tokio::fs::create_dir_all(&folder_path).await?; tokio::fs::create_dir_all(&folder_path).await?;
@ -324,7 +324,7 @@ async fn post_send_file_v2(data: Json<SendData>, headers: Headers, mut conn: DbC
let mut send = create_send(data, headers.user.uuid)?; let mut send = create_send(data, headers.user.uuid)?;
let file_id = crate::crypto::generate_send_id(); let file_id = crate::crypto::generate_send_file_id();
let mut data_value: Value = serde_json::from_str(&send.data)?; let mut data_value: Value = serde_json::from_str(&send.data)?;
if let Some(o) = data_value.as_object_mut() { if let Some(o) = data_value.as_object_mut() {
@ -346,16 +346,16 @@ async fn post_send_file_v2(data: Json<SendData>, headers: Headers, mut conn: DbC
#[derive(Deserialize)] #[derive(Deserialize)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub struct SendFileData { pub struct SendFileData {
id: String, id: SendFileId,
size: u64, size: u64,
fileName: String, fileName: String,
} }
// https://github.com/bitwarden/server/blob/66f95d1c443490b653e5a15d32977e2f5a3f9e32/src/Api/Tools/Controllers/SendsController.cs#L250 // https://github.com/bitwarden/server/blob/66f95d1c443490b653e5a15d32977e2f5a3f9e32/src/Api/Tools/Controllers/SendsController.cs#L250
#[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")] #[post("/sends/<send_id>/file/<file_id>", format = "multipart/form-data", data = "<data>")]
async fn post_send_file_v2_data( async fn post_send_file_v2_data(
send_uuid: &str, send_id: SendId,
file_id: &str, file_id: SendFileId,
data: Form<UploadDataV2<'_>>, data: Form<UploadDataV2<'_>>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -365,8 +365,8 @@ async fn post_send_file_v2_data(
let mut data = data.into_inner(); let mut data = data.into_inner();
let Some(send) = Send::find_by_uuid_and_user(send_uuid, &headers.user.uuid, &mut conn).await else { let Some(send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &mut conn).await else {
err!("Send not found. Unable to save the file.", "Invalid uuid or does not belong to user.") err!("Send not found. Unable to save the file.", "Invalid send uuid or does not belong to user.")
}; };
if send.atype != SendType::File as i32 { if send.atype != SendType::File as i32 {
@ -402,7 +402,7 @@ async fn post_send_file_v2_data(
err!("Send file size does not match.", format!("Expected a file size of {} got {size}", send_data.size)); err!("Send file size does not match.", format!("Expected a file size of {} got {size}", send_data.size));
} }
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(send_uuid); let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(send_id);
let file_path = folder_path.join(file_id); let file_path = folder_path.join(file_id);
// Check if the file already exists, if that is the case do not overwrite it // Check if the file already exists, if that is the case do not overwrite it
@ -485,7 +485,7 @@ async fn post_access(
UpdateType::SyncSendUpdate, UpdateType::SyncSendUpdate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&mut conn).await,
&String::from("00000000-0000-0000-0000-000000000000"), &String::from("00000000-0000-0000-0000-000000000000").into(),
&mut conn, &mut conn,
) )
.await; .await;
@ -495,14 +495,14 @@ async fn post_access(
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")] #[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
async fn post_access_file( async fn post_access_file(
send_id: &str, send_id: SendId,
file_id: &str, file_id: SendFileId,
data: Json<SendAccessData>, data: Json<SendAccessData>,
host: Host, host: Host,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let Some(mut send) = Send::find_by_uuid(send_id, &mut conn).await else { let Some(mut send) = Send::find_by_uuid(&send_id, &mut conn).await else {
err_code!(SEND_INACCESSIBLE_MSG, 404) err_code!(SEND_INACCESSIBLE_MSG, 404)
}; };
@ -542,12 +542,12 @@ async fn post_access_file(
UpdateType::SyncSendUpdate, UpdateType::SyncSendUpdate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&mut conn).await,
&String::from("00000000-0000-0000-0000-000000000000"), &String::from("00000000-0000-0000-0000-000000000000").into(),
&mut conn, &mut conn,
) )
.await; .await;
let token_claims = crate::auth::generate_send_claims(send_id, file_id); let token_claims = crate::auth::generate_send_claims(&send_id, &file_id);
let token = crate::auth::encode_jwt(&token_claims); let token = crate::auth::encode_jwt(&token_claims);
Ok(Json(json!({ Ok(Json(json!({
"object": "send-fileDownload", "object": "send-fileDownload",
@ -557,7 +557,7 @@ async fn post_access_file(
} }
#[get("/sends/<send_id>/<file_id>?<t>")] #[get("/sends/<send_id>/<file_id>?<t>")]
async fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Option<NamedFile> { async fn download_send(send_id: SendId, file_id: SendFileId, t: &str) -> Option<NamedFile> {
if let Ok(claims) = crate::auth::decode_send(t) { if let Ok(claims) = crate::auth::decode_send(t) {
if claims.sub == format!("{send_id}/{file_id}") { if claims.sub == format!("{send_id}/{file_id}") {
return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok(); return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok();
@ -566,15 +566,21 @@ async fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Opt
None None
} }
#[put("/sends/<uuid>", data = "<data>")] #[put("/sends/<send_id>", data = "<data>")]
async fn put_send(uuid: &str, data: Json<SendData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn put_send(
send_id: SendId,
data: Json<SendData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?; enforce_disable_send_policy(&headers, &mut conn).await?;
let data: SendData = data.into_inner(); let data: SendData = data.into_inner();
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?; enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?;
let Some(mut send) = Send::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await else { let Some(mut send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &mut conn).await else {
err!("Send not found", "Send uuid is invalid or does not belong to user") err!("Send not found", "Send send_id is invalid or does not belong to user")
}; };
update_send_from_data(&mut send, data, &headers, &mut conn, &nt, UpdateType::SyncSendUpdate).await?; update_send_from_data(&mut send, data, &headers, &mut conn, &nt, UpdateType::SyncSendUpdate).await?;
@ -640,9 +646,9 @@ pub async fn update_send_from_data(
Ok(()) Ok(())
} }
#[delete("/sends/<uuid>")] #[delete("/sends/<send_id>")]
async fn delete_send(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_send(send_id: SendId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let Some(send) = Send::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await else { let Some(send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &mut conn).await else {
err!("Send not found", "Invalid send uuid, or does not belong to user") err!("Send not found", "Invalid send uuid, or does not belong to user")
}; };
@ -659,11 +665,11 @@ async fn delete_send(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<
Ok(()) Ok(())
} }
#[put("/sends/<uuid>/remove-password")] #[put("/sends/<send_id>/remove-password")]
async fn put_remove_password(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn put_remove_password(send_id: SendId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?; enforce_disable_send_policy(&headers, &mut conn).await?;
let Some(mut send) = Send::find_by_uuid_and_user(uuid, &headers.user.uuid, &mut conn).await else { let Some(mut send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &mut conn).await else {
err!("Send not found", "Invalid send uuid, or does not belong to user") err!("Send not found", "Invalid send uuid, or does not belong to user")
}; };

Datei anzeigen

@ -7,7 +7,7 @@ use crate::{
auth::{ClientIp, Headers}, auth::{ClientIp, Headers},
crypto, crypto,
db::{ db::{
models::{EventType, TwoFactor, TwoFactorType}, models::{EventType, TwoFactor, TwoFactorType, UserId},
DbConn, DbConn,
}, },
util::NumberOrString, util::NumberOrString,
@ -95,7 +95,7 @@ async fn activate_authenticator_put(data: Json<EnableAuthenticatorData>, headers
} }
pub async fn validate_totp_code_str( pub async fn validate_totp_code_str(
user_uuid: &str, user_id: &UserId,
totp_code: &str, totp_code: &str,
secret: &str, secret: &str,
ip: &ClientIp, ip: &ClientIp,
@ -105,11 +105,11 @@ pub async fn validate_totp_code_str(
err!("TOTP code is not a number"); err!("TOTP code is not a number");
} }
validate_totp_code(user_uuid, totp_code, secret, ip, conn).await validate_totp_code(user_id, totp_code, secret, ip, conn).await
} }
pub async fn validate_totp_code( pub async fn validate_totp_code(
user_uuid: &str, user_id: &UserId,
totp_code: &str, totp_code: &str,
secret: &str, secret: &str,
ip: &ClientIp, ip: &ClientIp,
@ -121,10 +121,10 @@ pub async fn validate_totp_code(
err!("Invalid TOTP secret") err!("Invalid TOTP secret")
}; };
let mut twofactor = let mut twofactor = match TwoFactor::find_by_user_and_type(user_id, TwoFactorType::Authenticator as i32, conn).await
match TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Authenticator as i32, conn).await { {
Some(tf) => tf, Some(tf) => tf,
_ => TwoFactor::new(user_uuid.to_string(), TwoFactorType::Authenticator, secret.to_string()), _ => TwoFactor::new(user_id.clone(), TwoFactorType::Authenticator, secret.to_string()),
}; };
// The amount of steps back and forward in time // The amount of steps back and forward in time

Datei anzeigen

@ -11,7 +11,7 @@ use crate::{
auth::Headers, auth::Headers,
crypto, crypto,
db::{ db::{
models::{EventType, TwoFactor, TwoFactorType, User}, models::{EventType, TwoFactor, TwoFactorType, User, UserId},
DbConn, DbConn,
}, },
error::MapResult, error::MapResult,
@ -228,11 +228,11 @@ const AUTH_PREFIX: &str = "AUTH";
const DUO_PREFIX: &str = "TX"; const DUO_PREFIX: &str = "TX";
const APP_PREFIX: &str = "APP"; const APP_PREFIX: &str = "APP";
async fn get_user_duo_data(uuid: &str, conn: &mut DbConn) -> DuoStatus { async fn get_user_duo_data(user_id: &UserId, conn: &mut DbConn) -> DuoStatus {
let type_ = TwoFactorType::Duo as i32; let type_ = TwoFactorType::Duo as i32;
// If the user doesn't have an entry, disabled // If the user doesn't have an entry, disabled
let Some(twofactor) = TwoFactor::find_by_user_and_type(uuid, type_, conn).await else { let Some(twofactor) = TwoFactor::find_by_user_and_type(user_id, type_, conn).await else {
return DuoStatus::Disabled(DuoData::global().is_some()); return DuoStatus::Disabled(DuoData::global().is_some());
}; };

Datei anzeigen

@ -10,7 +10,7 @@ use crate::{
api::{core::two_factor::duo::get_duo_keys_email, EmptyResult}, api::{core::two_factor::duo::get_duo_keys_email, EmptyResult},
crypto, crypto,
db::{ db::{
models::{EventType, TwoFactorDuoContext}, models::{DeviceId, EventType, TwoFactorDuoContext},
DbConn, DbPool, DbConn, DbPool,
}, },
error::Error, error::Error,
@ -379,7 +379,7 @@ fn make_callback_url(client_name: &str) -> Result<String, Error> {
pub async fn get_duo_auth_url( pub async fn get_duo_auth_url(
email: &str, email: &str,
client_id: &str, client_id: &str,
device_identifier: &String, device_identifier: &DeviceId,
conn: &mut DbConn, conn: &mut DbConn,
) -> Result<String, Error> { ) -> Result<String, Error> {
let (ik, sk, _, host) = get_duo_keys_email(email, conn).await?; let (ik, sk, _, host) = get_duo_keys_email(email, conn).await?;
@ -417,7 +417,7 @@ pub async fn validate_duo_login(
email: &str, email: &str,
two_factor_token: &str, two_factor_token: &str,
client_id: &str, client_id: &str,
device_identifier: &str, device_identifier: &DeviceId,
conn: &mut DbConn, conn: &mut DbConn,
) -> EmptyResult { ) -> EmptyResult {
// Result supplied to us by clients in the form "<authz code>|<state>" // Result supplied to us by clients in the form "<authz code>|<state>"

Datei anzeigen

@ -10,7 +10,7 @@ use crate::{
auth::Headers, auth::Headers,
crypto, crypto,
db::{ db::{
models::{EventType, TwoFactor, TwoFactorType, User}, models::{EventType, TwoFactor, TwoFactorType, User, UserId},
DbConn, DbConn,
}, },
error::{Error, MapResult}, error::{Error, MapResult},
@ -59,10 +59,9 @@ async fn send_email_login(data: Json<SendEmailLoginData>, mut conn: DbConn) -> E
} }
/// Generate the token, save the data for later verification and send email to user /// Generate the token, save the data for later verification and send email to user
pub async fn send_token(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn send_token(user_id: &UserId, conn: &mut DbConn) -> EmptyResult {
let type_ = TwoFactorType::Email as i32; let type_ = TwoFactorType::Email as i32;
let mut twofactor = let mut twofactor = TwoFactor::find_by_user_and_type(user_id, type_, conn).await.map_res("Two factor not found")?;
TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await.map_res("Two factor not found")?;
let generated_token = crypto::generate_email_token(CONFIG.email_token_size()); let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
@ -198,9 +197,9 @@ async fn email(data: Json<EmailData>, headers: Headers, mut conn: DbConn) -> Jso
} }
/// Validate the email code when used as TwoFactor token mechanism /// Validate the email code when used as TwoFactor token mechanism
pub async fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &mut DbConn) -> EmptyResult { pub async fn validate_email_code_str(user_id: &UserId, token: &str, data: &str, conn: &mut DbConn) -> EmptyResult {
let mut email_data = EmailTokenData::from_json(data)?; let mut email_data = EmailTokenData::from_json(data)?;
let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Email as i32, conn) let mut twofactor = TwoFactor::find_by_user_and_type(user_id, TwoFactorType::Email as i32, conn)
.await .await
.map_res("Two factor not found")?; .map_res("Two factor not found")?;
let Some(issued_token) = &email_data.last_token else { let Some(issued_token) = &email_data.last_token else {
@ -327,8 +326,8 @@ pub fn obscure_email(email: &str) -> String {
format!("{}@{}", new_name, &domain) format!("{}@{}", new_name, &domain)
} }
pub async fn find_and_activate_email_2fa(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn find_and_activate_email_2fa(user_id: &UserId, conn: &mut DbConn) -> EmptyResult {
if let Some(user) = User::find_by_uuid(user_uuid, conn).await { if let Some(user) = User::find_by_uuid(user_id, conn).await {
activate_email_2fa(&user, conn).await activate_email_2fa(&user, conn).await
} else { } else {
err!("User not found!"); err!("User not found!");

Datei anzeigen

@ -173,17 +173,16 @@ async fn disable_twofactor_put(data: Json<DisableTwoFactorData>, headers: Header
pub async fn enforce_2fa_policy( pub async fn enforce_2fa_policy(
user: &User, user: &User,
act_uuid: &str, act_user_id: &UserId,
device_type: i32, device_type: i32,
ip: &std::net::IpAddr, ip: &std::net::IpAddr,
conn: &mut DbConn, conn: &mut DbConn,
) -> EmptyResult { ) -> EmptyResult {
for member in UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, conn) for member in
.await Membership::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, conn).await.into_iter()
.into_iter()
{ {
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org // Policy only applies to non-Owner/non-Admin members who have accepted joining the org
if member.atype < UserOrgType::Admin { if member.atype < MembershipType::Admin {
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
let org = Organization::find_by_uuid(&member.org_uuid, conn).await.unwrap(); let org = Organization::find_by_uuid(&member.org_uuid, conn).await.unwrap();
mail::send_2fa_removed_from_org(&user.email, &org.name).await?; mail::send_2fa_removed_from_org(&user.email, &org.name).await?;
@ -196,7 +195,7 @@ pub async fn enforce_2fa_policy(
EventType::OrganizationUserRevoked as i32, EventType::OrganizationUserRevoked as i32,
&member.uuid, &member.uuid,
&member.org_uuid, &member.org_uuid,
act_uuid, act_user_id,
device_type, device_type,
ip, ip,
conn, conn,
@ -209,16 +208,16 @@ pub async fn enforce_2fa_policy(
} }
pub async fn enforce_2fa_policy_for_org( pub async fn enforce_2fa_policy_for_org(
org_uuid: &str, org_id: &OrganizationId,
act_uuid: &str, act_user_id: &UserId,
device_type: i32, device_type: i32,
ip: &std::net::IpAddr, ip: &std::net::IpAddr,
conn: &mut DbConn, conn: &mut DbConn,
) -> EmptyResult { ) -> EmptyResult {
let org = Organization::find_by_uuid(org_uuid, conn).await.unwrap(); let org = Organization::find_by_uuid(org_id, conn).await.unwrap();
for member in UserOrganization::find_confirmed_by_org(org_uuid, conn).await.into_iter() { for member in Membership::find_confirmed_by_org(org_id, conn).await.into_iter() {
// Don't enforce the policy for Admins and Owners. // Don't enforce the policy for Admins and Owners.
if member.atype < UserOrgType::Admin && TwoFactor::find_by_user(&member.user_uuid, conn).await.is_empty() { if member.atype < MembershipType::Admin && TwoFactor::find_by_user(&member.user_uuid, conn).await.is_empty() {
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
let user = User::find_by_uuid(&member.user_uuid, conn).await.unwrap(); let user = User::find_by_uuid(&member.user_uuid, conn).await.unwrap();
mail::send_2fa_removed_from_org(&user.email, &org.name).await?; mail::send_2fa_removed_from_org(&user.email, &org.name).await?;
@ -230,8 +229,8 @@ pub async fn enforce_2fa_policy_for_org(
log_event( log_event(
EventType::OrganizationUserRevoked as i32, EventType::OrganizationUserRevoked as i32,
&member.uuid, &member.uuid,
org_uuid, org_id,
act_uuid, act_user_id,
device_type, device_type,
ip, ip,
conn, conn,

Datei anzeigen

@ -6,7 +6,7 @@ use crate::{
auth::Headers, auth::Headers,
crypto, crypto,
db::{ db::{
models::{TwoFactor, TwoFactorType}, models::{TwoFactor, TwoFactorType, UserId},
DbConn, DbConn,
}, },
error::{Error, MapResult}, error::{Error, MapResult},
@ -104,11 +104,11 @@ async fn verify_otp(data: Json<ProtectedActionVerify>, headers: Headers, mut con
pub async fn validate_protected_action_otp( pub async fn validate_protected_action_otp(
otp: &str, otp: &str,
user_uuid: &str, user_id: &UserId,
delete_if_valid: bool, delete_if_valid: bool,
conn: &mut DbConn, conn: &mut DbConn,
) -> EmptyResult { ) -> EmptyResult {
let pa = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::ProtectedActions as i32, conn) let pa = TwoFactor::find_by_user_and_type(user_id, TwoFactorType::ProtectedActions as i32, conn)
.await .await
.map_res("Protected action token not found, try sending the code again or restart the process")?; .map_res("Protected action token not found, try sending the code again or restart the process")?;
let mut pa_data = ProtectedActionData::from_json(&pa.data)?; let mut pa_data = ProtectedActionData::from_json(&pa.data)?;

Datei anzeigen

@ -11,7 +11,7 @@ use crate::{
}, },
auth::Headers, auth::Headers,
db::{ db::{
models::{EventType, TwoFactor, TwoFactorType}, models::{EventType, TwoFactor, TwoFactorType, UserId},
DbConn, DbConn,
}, },
error::Error, error::Error,
@ -148,7 +148,7 @@ async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Hea
)?; )?;
let type_ = TwoFactorType::WebauthnRegisterChallenge; let type_ = TwoFactorType::WebauthnRegisterChallenge;
TwoFactor::new(user.uuid, type_, serde_json::to_string(&state)?).save(&mut conn).await?; TwoFactor::new(user.uuid.clone(), type_, serde_json::to_string(&state)?).save(&mut conn).await?;
let mut challenge_value = serde_json::to_value(challenge.public_key)?; let mut challenge_value = serde_json::to_value(challenge.public_key)?;
challenge_value["status"] = "ok".into(); challenge_value["status"] = "ok".into();
@ -352,20 +352,20 @@ async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, mut conn:
} }
pub async fn get_webauthn_registrations( pub async fn get_webauthn_registrations(
user_uuid: &str, user_id: &UserId,
conn: &mut DbConn, conn: &mut DbConn,
) -> Result<(bool, Vec<WebauthnRegistration>), Error> { ) -> Result<(bool, Vec<WebauthnRegistration>), Error> {
let type_ = TwoFactorType::Webauthn as i32; let type_ = TwoFactorType::Webauthn as i32;
match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { match TwoFactor::find_by_user_and_type(user_id, type_, conn).await {
Some(tf) => Ok((tf.enabled, serde_json::from_str(&tf.data)?)), Some(tf) => Ok((tf.enabled, serde_json::from_str(&tf.data)?)),
None => Ok((false, Vec::new())), // If no data, return empty list None => Ok((false, Vec::new())), // If no data, return empty list
} }
} }
pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> JsonResult { pub async fn generate_webauthn_login(user_id: &UserId, conn: &mut DbConn) -> JsonResult {
// Load saved credentials // Load saved credentials
let creds: Vec<Credential> = let creds: Vec<Credential> =
get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect(); get_webauthn_registrations(user_id, conn).await?.1.into_iter().map(|r| r.credential).collect();
if creds.is_empty() { if creds.is_empty() {
err!("No Webauthn devices registered") err!("No Webauthn devices registered")
@ -376,7 +376,7 @@ pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> Json
let (response, state) = WebauthnConfig::load().generate_challenge_authenticate_options(creds, Some(ext))?; let (response, state) = WebauthnConfig::load().generate_challenge_authenticate_options(creds, Some(ext))?;
// Save the challenge state for later validation // Save the challenge state for later validation
TwoFactor::new(user_uuid.into(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?) TwoFactor::new(user_id.clone(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?)
.save(conn) .save(conn)
.await?; .await?;
@ -384,9 +384,9 @@ pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> Json
Ok(Json(serde_json::to_value(response.public_key)?)) Ok(Json(serde_json::to_value(response.public_key)?))
} }
pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut DbConn) -> EmptyResult { pub async fn validate_webauthn_login(user_id: &UserId, response: &str, conn: &mut DbConn) -> EmptyResult {
let type_ = TwoFactorType::WebauthnLoginChallenge as i32; let type_ = TwoFactorType::WebauthnLoginChallenge as i32;
let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { let state = match TwoFactor::find_by_user_and_type(user_id, type_, conn).await {
Some(tf) => { Some(tf) => {
let state: AuthenticationState = serde_json::from_str(&tf.data)?; let state: AuthenticationState = serde_json::from_str(&tf.data)?;
tf.delete(conn).await?; tf.delete(conn).await?;
@ -403,7 +403,7 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut
let rsp: PublicKeyCredentialCopy = serde_json::from_str(response)?; let rsp: PublicKeyCredentialCopy = serde_json::from_str(response)?;
let rsp: PublicKeyCredential = rsp.into(); let rsp: PublicKeyCredential = rsp.into();
let mut registrations = get_webauthn_registrations(user_uuid, conn).await?.1; let mut registrations = get_webauthn_registrations(user_id, conn).await?.1;
// If the credential we received is migrated from U2F, enable the U2F compatibility // If the credential we received is migrated from U2F, enable the U2F compatibility
//let use_u2f = registrations.iter().any(|r| r.migrated && r.credential.cred_id == rsp.raw_id.0); //let use_u2f = registrations.iter().any(|r| r.migrated && r.credential.cred_id == rsp.raw_id.0);
@ -413,7 +413,7 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut
if &reg.credential.cred_id == cred_id { if &reg.credential.cred_id == cred_id {
reg.credential.counter = auth_data.counter; reg.credential.counter = auth_data.counter;
TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?) TwoFactor::new(user_id.clone(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?)
.save(conn) .save(conn)
.await?; .await?;
return Ok(()); return Ok(());

Datei anzeigen

@ -92,10 +92,10 @@ async fn generate_yubikey(data: Json<PasswordOrOtpData>, headers: Headers, mut c
data.validate(&user, false, &mut conn).await?; data.validate(&user, false, &mut conn).await?;
let user_uuid = &user.uuid; let user_id = &user.uuid;
let yubikey_type = TwoFactorType::YubiKey as i32; let yubikey_type = TwoFactorType::YubiKey as i32;
let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &mut conn).await; let r = TwoFactor::find_by_user_and_type(user_id, yubikey_type, &mut conn).await;
if let Some(r) = r { if let Some(r) = r {
let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?; let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?;

Datei anzeigen

@ -31,7 +31,7 @@ pub fn routes() -> Vec<Route> {
async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn: DbConn) -> JsonResult { async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn: DbConn) -> JsonResult {
let data: ConnectData = data.into_inner(); let data: ConnectData = data.into_inner();
let mut user_uuid: Option<String> = None; let mut user_id: Option<UserId> = None;
let login_result = match data.grant_type.as_ref() { let login_result = match data.grant_type.as_ref() {
"refresh_token" => { "refresh_token" => {
@ -48,7 +48,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
_check_is_some(&data.device_name, "device_name cannot be blank")?; _check_is_some(&data.device_name, "device_name cannot be blank")?;
_check_is_some(&data.device_type, "device_type cannot be blank")?; _check_is_some(&data.device_type, "device_type cannot be blank")?;
_password_login(data, &mut user_uuid, &mut conn, &client_header.ip).await _password_login(data, &mut user_id, &mut conn, &client_header.ip).await
} }
"client_credentials" => { "client_credentials" => {
_check_is_some(&data.client_id, "client_id cannot be blank")?; _check_is_some(&data.client_id, "client_id cannot be blank")?;
@ -59,17 +59,17 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
_check_is_some(&data.device_name, "device_name cannot be blank")?; _check_is_some(&data.device_name, "device_name cannot be blank")?;
_check_is_some(&data.device_type, "device_type cannot be blank")?; _check_is_some(&data.device_type, "device_type cannot be blank")?;
_api_key_login(data, &mut user_uuid, &mut conn, &client_header.ip).await _api_key_login(data, &mut user_id, &mut conn, &client_header.ip).await
} }
t => err!("Invalid type", t), t => err!("Invalid type", t),
}; };
if let Some(user_uuid) = user_uuid { if let Some(user_id) = user_id {
match &login_result { match &login_result {
Ok(_) => { Ok(_) => {
log_user_event( log_user_event(
EventType::UserLoggedIn as i32, EventType::UserLoggedIn as i32,
&user_uuid, &user_id,
client_header.device_type, client_header.device_type,
&client_header.ip.ip, &client_header.ip.ip,
&mut conn, &mut conn,
@ -80,7 +80,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
if let Some(ev) = e.get_event() { if let Some(ev) = e.get_event() {
log_user_event( log_user_event(
ev.event as i32, ev.event as i32,
&user_uuid, &user_id,
client_header.device_type, client_header.device_type,
&client_header.ip.ip, &client_header.ip.ip,
&mut conn, &mut conn,
@ -111,7 +111,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out // Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
// See: https://github.com/dani-garcia/vaultwarden/issues/4156 // See: https://github.com/dani-garcia/vaultwarden/issues/4156
// --- // ---
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await; // let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec); let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
device.save(conn).await?; device.save(conn).await?;
@ -141,7 +141,7 @@ struct MasterPasswordPolicy {
async fn _password_login( async fn _password_login(
data: ConnectData, data: ConnectData,
user_uuid: &mut Option<String>, user_id: &mut Option<UserId>,
conn: &mut DbConn, conn: &mut DbConn,
ip: &ClientIp, ip: &ClientIp,
) -> JsonResult { ) -> JsonResult {
@ -161,8 +161,8 @@ async fn _password_login(
err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)) err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username))
}; };
// Set the user_uuid here to be passed back used for event logging. // Set the user_id here to be passed back used for event logging.
*user_uuid = Some(user.uuid.clone()); *user_id = Some(user.uuid.clone());
// Check if the user is disabled // Check if the user is disabled
if !user.enabled { if !user.enabled {
@ -178,9 +178,8 @@ async fn _password_login(
let password = data.password.as_ref().unwrap(); let password = data.password.as_ref().unwrap();
// If we get an auth request, we don't check the user's password, but the access code of the auth request // If we get an auth request, we don't check the user's password, but the access code of the auth request
if let Some(ref auth_request_uuid) = data.auth_request { if let Some(ref auth_request_id) = data.auth_request {
let Some(auth_request) = AuthRequest::find_by_uuid_and_user(auth_request_uuid.as_str(), &user.uuid, conn).await let Some(auth_request) = AuthRequest::find_by_uuid_and_user(auth_request_id, &user.uuid, conn).await else {
else {
err!( err!(
"Auth request not found. Try again.", "Auth request not found. Try again.",
format!("IP: {}. Username: {}.", ip.ip, username), format!("IP: {}. Username: {}.", ip.ip, username),
@ -291,7 +290,7 @@ async fn _password_login(
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out // Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
// See: https://github.com/dani-garcia/vaultwarden/issues/4156 // See: https://github.com/dani-garcia/vaultwarden/issues/4156
// --- // ---
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await; // let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec); let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
device.save(conn).await?; device.save(conn).await?;
@ -359,7 +358,7 @@ async fn _password_login(
async fn _api_key_login( async fn _api_key_login(
data: ConnectData, data: ConnectData,
user_uuid: &mut Option<String>, user_id: &mut Option<UserId>,
conn: &mut DbConn, conn: &mut DbConn,
ip: &ClientIp, ip: &ClientIp,
) -> JsonResult { ) -> JsonResult {
@ -368,7 +367,7 @@ async fn _api_key_login(
// Validate scope // Validate scope
match data.scope.as_ref().unwrap().as_ref() { match data.scope.as_ref().unwrap().as_ref() {
"api" => _user_api_key_login(data, user_uuid, conn, ip).await, "api" => _user_api_key_login(data, user_id, conn, ip).await,
"api.organization" => _organization_api_key_login(data, conn, ip).await, "api.organization" => _organization_api_key_login(data, conn, ip).await,
_ => err!("Scope not supported"), _ => err!("Scope not supported"),
} }
@ -376,21 +375,22 @@ async fn _api_key_login(
async fn _user_api_key_login( async fn _user_api_key_login(
data: ConnectData, data: ConnectData,
user_uuid: &mut Option<String>, user_id: &mut Option<UserId>,
conn: &mut DbConn, conn: &mut DbConn,
ip: &ClientIp, ip: &ClientIp,
) -> JsonResult { ) -> JsonResult {
// Get the user via the client_id // Get the user via the client_id
let client_id = data.client_id.as_ref().unwrap(); let client_id = data.client_id.as_ref().unwrap();
let Some(client_user_uuid) = client_id.strip_prefix("user.") else { let Some(client_user_id) = client_id.strip_prefix("user.") else {
err!("Malformed client_id", format!("IP: {}.", ip.ip)) err!("Malformed client_id", format!("IP: {}.", ip.ip))
}; };
let Some(user) = User::find_by_uuid(client_user_uuid, conn).await else { let client_user_id: UserId = client_user_id.into();
let Some(user) = User::find_by_uuid(&client_user_id, conn).await else {
err!("Invalid client_id", format!("IP: {}.", ip.ip)) err!("Invalid client_id", format!("IP: {}.", ip.ip))
}; };
// Set the user_uuid here to be passed back used for event logging. // Set the user_id here to be passed back used for event logging.
*user_uuid = Some(user.uuid.clone()); *user_id = Some(user.uuid.clone());
// Check if the user is disabled // Check if the user is disabled
if !user.enabled { if !user.enabled {
@ -440,7 +440,7 @@ async fn _user_api_key_login(
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out // Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
// See: https://github.com/dani-garcia/vaultwarden/issues/4156 // See: https://github.com/dani-garcia/vaultwarden/issues/4156
// --- // ---
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await; // let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec); let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
device.save(conn).await?; device.save(conn).await?;
@ -469,10 +469,11 @@ async fn _user_api_key_login(
async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) -> JsonResult { async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) -> JsonResult {
// Get the org via the client_id // Get the org via the client_id
let client_id = data.client_id.as_ref().unwrap(); let client_id = data.client_id.as_ref().unwrap();
let Some(org_uuid) = client_id.strip_prefix("organization.") else { let Some(org_id) = client_id.strip_prefix("organization.") else {
err!("Malformed client_id", format!("IP: {}.", ip.ip)) err!("Malformed client_id", format!("IP: {}.", ip.ip))
}; };
let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(org_uuid, conn).await else { let org_id: OrganizationId = org_id.to_string().into();
let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(&org_id, conn).await else {
err!("Invalid client_id", format!("IP: {}.", ip.ip)) err!("Invalid client_id", format!("IP: {}.", ip.ip))
}; };
@ -614,7 +615,7 @@ fn _selected_data(tf: Option<TwoFactor>) -> ApiResult<String> {
async fn _json_err_twofactor( async fn _json_err_twofactor(
providers: &[i32], providers: &[i32],
user_uuid: &str, user_id: &UserId,
data: &ConnectData, data: &ConnectData,
conn: &mut DbConn, conn: &mut DbConn,
) -> ApiResult<Value> { ) -> ApiResult<Value> {
@ -635,12 +636,12 @@ async fn _json_err_twofactor(
Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ } Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ }
Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => { Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => {
let request = webauthn::generate_webauthn_login(user_uuid, conn).await?; let request = webauthn::generate_webauthn_login(user_id, conn).await?;
result["TwoFactorProviders2"][provider.to_string()] = request.0; result["TwoFactorProviders2"][provider.to_string()] = request.0;
} }
Some(TwoFactorType::Duo) => { Some(TwoFactorType::Duo) => {
let email = match User::find_by_uuid(user_uuid, conn).await { let email = match User::find_by_uuid(user_id, conn).await {
Some(u) => u.email, Some(u) => u.email,
None => err!("User does not exist"), None => err!("User does not exist"),
}; };
@ -672,7 +673,7 @@ async fn _json_err_twofactor(
} }
Some(tf_type @ TwoFactorType::YubiKey) => { Some(tf_type @ TwoFactorType::YubiKey) => {
let Some(twofactor) = TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await else { let Some(twofactor) = TwoFactor::find_by_user_and_type(user_id, tf_type as i32, conn).await else {
err!("No YubiKey devices registered") err!("No YubiKey devices registered")
}; };
@ -684,13 +685,13 @@ async fn _json_err_twofactor(
} }
Some(tf_type @ TwoFactorType::Email) => { Some(tf_type @ TwoFactorType::Email) => {
let Some(twofactor) = TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await else { let Some(twofactor) = TwoFactor::find_by_user_and_type(user_id, tf_type as i32, conn).await else {
err!("No twofactor email registered") err!("No twofactor email registered")
}; };
// Send email immediately if email is the only 2FA option // Send email immediately if email is the only 2FA option
if providers.len() == 1 { if providers.len() == 1 {
email::send_token(user_uuid, conn).await? email::send_token(user_id, conn).await?
} }
let email_data = email::EmailTokenData::from_json(&twofactor.data)?; let email_data = email::EmailTokenData::from_json(&twofactor.data)?;
@ -745,7 +746,7 @@ struct ConnectData {
#[field(name = uncased("device_identifier"))] #[field(name = uncased("device_identifier"))]
#[field(name = uncased("deviceidentifier"))] #[field(name = uncased("deviceidentifier"))]
device_identifier: Option<String>, device_identifier: Option<DeviceId>,
#[field(name = uncased("device_name"))] #[field(name = uncased("device_name"))]
#[field(name = uncased("devicename"))] #[field(name = uncased("devicename"))]
device_name: Option<String>, device_name: Option<String>,
@ -768,7 +769,7 @@ struct ConnectData {
#[field(name = uncased("twofactorremember"))] #[field(name = uncased("twofactorremember"))]
two_factor_remember: Option<i32>, two_factor_remember: Option<i32>,
#[field(name = uncased("authrequest"))] #[field(name = uncased("authrequest"))]
auth_request: Option<String>, auth_request: Option<AuthRequestId>,
} }
fn _check_is_some<T>(value: &Option<T>, msg: &str) -> EmptyResult { fn _check_is_some<T>(value: &Option<T>, msg: &str) -> EmptyResult {

Datei anzeigen

@ -10,7 +10,7 @@ use rocket_ws::{Message, WebSocket};
use crate::{ use crate::{
auth::{ClientIp, WsAccessTokenHeader}, auth::{ClientIp, WsAccessTokenHeader},
db::{ db::{
models::{Cipher, Folder, Send as DbSend, User}, models::{Cipher, CollectionId, DeviceId, Folder, Send as DbSend, User, UserId},
DbConn, DbConn,
}, },
Error, CONFIG, Error, CONFIG,
@ -53,13 +53,13 @@ struct WsAccessToken {
struct WSEntryMapGuard { struct WSEntryMapGuard {
users: Arc<WebSocketUsers>, users: Arc<WebSocketUsers>,
user_uuid: String, user_uuid: UserId,
entry_uuid: uuid::Uuid, entry_uuid: uuid::Uuid,
addr: IpAddr, addr: IpAddr,
} }
impl WSEntryMapGuard { impl WSEntryMapGuard {
fn new(users: Arc<WebSocketUsers>, user_uuid: String, entry_uuid: uuid::Uuid, addr: IpAddr) -> Self { fn new(users: Arc<WebSocketUsers>, user_uuid: UserId, entry_uuid: uuid::Uuid, addr: IpAddr) -> Self {
Self { Self {
users, users,
user_uuid, user_uuid,
@ -72,7 +72,7 @@ impl WSEntryMapGuard {
impl Drop for WSEntryMapGuard { impl Drop for WSEntryMapGuard {
fn drop(&mut self) { fn drop(&mut self) {
info!("Closing WS connection from {}", self.addr); info!("Closing WS connection from {}", self.addr);
if let Some(mut entry) = self.users.map.get_mut(&self.user_uuid) { if let Some(mut entry) = self.users.map.get_mut(self.user_uuid.as_ref()) {
entry.retain(|(uuid, _)| uuid != &self.entry_uuid); entry.retain(|(uuid, _)| uuid != &self.entry_uuid);
} }
} }
@ -130,7 +130,7 @@ fn websockets_hub<'r>(
// Add a channel to send messages to this client to the map // Add a channel to send messages to this client to the map
let entry_uuid = uuid::Uuid::new_v4(); let entry_uuid = uuid::Uuid::new_v4();
let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100); let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100);
users.map.entry(claims.sub.clone()).or_default().push((entry_uuid, tx)); users.map.entry(claims.sub.to_string()).or_default().push((entry_uuid, tx));
// Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map // Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
(rx, WSEntryMapGuard::new(users, claims.sub, entry_uuid, addr)) (rx, WSEntryMapGuard::new(users, claims.sub, entry_uuid, addr))
@ -330,8 +330,8 @@ pub struct WebSocketUsers {
} }
impl WebSocketUsers { impl WebSocketUsers {
async fn send_update(&self, user_uuid: &str, data: &[u8]) { async fn send_update(&self, user_id: &UserId, data: &[u8]) {
if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) { if let Some(user) = self.map.get(user_id.as_ref()).map(|v| v.clone()) {
for (_, sender) in user.iter() { for (_, sender) in user.iter() {
if let Err(e) = sender.send(Message::binary(data)).await { if let Err(e) = sender.send(Message::binary(data)).await {
error!("Error sending WS update {e}"); error!("Error sending WS update {e}");
@ -347,7 +347,7 @@ impl WebSocketUsers {
return; return;
} }
let data = create_update( let data = create_update(
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))],
ut, ut,
None, None,
); );
@ -361,15 +361,15 @@ impl WebSocketUsers {
} }
} }
pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) { pub async fn send_logout(&self, user: &User, acting_device_id: Option<DeviceId>) {
// Skip any processing if both WebSockets and Push are not active // Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED { if *NOTIFICATIONS_DISABLED {
return; return;
} }
let data = create_update( let data = create_update(
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))],
UpdateType::LogOut, UpdateType::LogOut,
acting_device_uuid.clone(), acting_device_id.clone(),
); );
if CONFIG.enable_websocket() { if CONFIG.enable_websocket() {
@ -377,7 +377,7 @@ impl WebSocketUsers {
} }
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_logout(user, acting_device_uuid); push_logout(user, acting_device_id.clone());
} }
} }
@ -385,7 +385,7 @@ impl WebSocketUsers {
&self, &self,
ut: UpdateType, ut: UpdateType,
folder: &Folder, folder: &Folder,
acting_device_uuid: &String, acting_device_id: &DeviceId,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active // Skip any processing if both WebSockets and Push are not active
@ -394,12 +394,12 @@ impl WebSocketUsers {
} }
let data = create_update( let data = create_update(
vec![ vec![
("Id".into(), folder.uuid.clone().into()), ("Id".into(), folder.uuid.to_string().into()),
("UserId".into(), folder.user_uuid.clone().into()), ("UserId".into(), folder.user_uuid.to_string().into()),
("RevisionDate".into(), serialize_date(folder.updated_at)), ("RevisionDate".into(), serialize_date(folder.updated_at)),
], ],
ut, ut,
Some(acting_device_uuid.into()), Some(acting_device_id.clone()),
); );
if CONFIG.enable_websocket() { if CONFIG.enable_websocket() {
@ -407,7 +407,7 @@ impl WebSocketUsers {
} }
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_folder_update(ut, folder, acting_device_uuid, conn).await; push_folder_update(ut, folder, acting_device_id, conn).await;
} }
} }
@ -415,48 +415,48 @@ impl WebSocketUsers {
&self, &self,
ut: UpdateType, ut: UpdateType,
cipher: &Cipher, cipher: &Cipher,
user_uuids: &[String], user_ids: &[UserId],
acting_device_uuid: &String, acting_device_id: &DeviceId,
collection_uuids: Option<Vec<String>>, collection_uuids: Option<Vec<CollectionId>>,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active // Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED { if *NOTIFICATIONS_DISABLED {
return; return;
} }
let org_uuid = convert_option(cipher.organization_uuid.clone()); let org_id = convert_option(cipher.organization_uuid.as_deref());
// Depending if there are collections provided or not, we need to have different values for the following variables. // Depending if there are collections provided or not, we need to have different values for the following variables.
// The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change. // The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change.
let (user_uuid, collection_uuids, revision_date) = if let Some(collection_uuids) = collection_uuids { let (user_id, collection_uuids, revision_date) = if let Some(collection_uuids) = collection_uuids {
( (
Value::Nil, Value::Nil,
Value::Array(collection_uuids.into_iter().map(|v| v.into()).collect::<Vec<Value>>()), Value::Array(collection_uuids.into_iter().map(|v| v.to_string().into()).collect::<Vec<Value>>()),
serialize_date(Utc::now().naive_utc()), serialize_date(Utc::now().naive_utc()),
) )
} else { } else {
(convert_option(cipher.user_uuid.clone()), Value::Nil, serialize_date(cipher.updated_at)) (convert_option(cipher.user_uuid.as_deref()), Value::Nil, serialize_date(cipher.updated_at))
}; };
let data = create_update( let data = create_update(
vec![ vec![
("Id".into(), cipher.uuid.clone().into()), ("Id".into(), cipher.uuid.to_string().into()),
("UserId".into(), user_uuid), ("UserId".into(), user_id),
("OrganizationId".into(), org_uuid), ("OrganizationId".into(), org_id),
("CollectionIds".into(), collection_uuids), ("CollectionIds".into(), collection_uuids),
("RevisionDate".into(), revision_date), ("RevisionDate".into(), revision_date),
], ],
ut, ut,
Some(acting_device_uuid.into()), Some(acting_device_id.clone()),
); );
if CONFIG.enable_websocket() { if CONFIG.enable_websocket() {
for uuid in user_uuids { for uuid in user_ids {
self.send_update(uuid, &data).await; self.send_update(uuid, &data).await;
} }
} }
if CONFIG.push_enabled() && user_uuids.len() == 1 { if CONFIG.push_enabled() && user_ids.len() == 1 {
push_cipher_update(ut, cipher, acting_device_uuid, conn).await; push_cipher_update(ut, cipher, acting_device_id, conn).await;
} }
} }
@ -464,20 +464,20 @@ impl WebSocketUsers {
&self, &self,
ut: UpdateType, ut: UpdateType,
send: &DbSend, send: &DbSend,
user_uuids: &[String], user_ids: &[UserId],
acting_device_uuid: &String, acting_device_id: &DeviceId,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active // Skip any processing if both WebSockets and Push are not active
if *NOTIFICATIONS_DISABLED { if *NOTIFICATIONS_DISABLED {
return; return;
} }
let user_uuid = convert_option(send.user_uuid.clone()); let user_id = convert_option(send.user_uuid.as_deref());
let data = create_update( let data = create_update(
vec![ vec![
("Id".into(), send.uuid.clone().into()), ("Id".into(), send.uuid.to_string().into()),
("UserId".into(), user_uuid), ("UserId".into(), user_id),
("RevisionDate".into(), serialize_date(send.revision_date)), ("RevisionDate".into(), serialize_date(send.revision_date)),
], ],
ut, ut,
@ -485,20 +485,20 @@ impl WebSocketUsers {
); );
if CONFIG.enable_websocket() { if CONFIG.enable_websocket() {
for uuid in user_uuids { for uuid in user_ids {
self.send_update(uuid, &data).await; self.send_update(uuid, &data).await;
} }
} }
if CONFIG.push_enabled() && user_uuids.len() == 1 { if CONFIG.push_enabled() && user_ids.len() == 1 {
push_send_update(ut, send, acting_device_uuid, conn).await; push_send_update(ut, send, acting_device_id, conn).await;
} }
} }
pub async fn send_auth_request( pub async fn send_auth_request(
&self, &self,
user_uuid: &String, user_id: &UserId,
auth_request_uuid: &String, auth_request_uuid: &String,
acting_device_uuid: &String, acting_device_id: &DeviceId,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active // Skip any processing if both WebSockets and Push are not active
@ -506,24 +506,24 @@ impl WebSocketUsers {
return; return;
} }
let data = create_update( let data = create_update(
vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_id.to_string().into())],
UpdateType::AuthRequest, UpdateType::AuthRequest,
Some(acting_device_uuid.to_string()), Some(acting_device_id.clone()),
); );
if CONFIG.enable_websocket() { if CONFIG.enable_websocket() {
self.send_update(user_uuid, &data).await; self.send_update(user_id, &data).await;
} }
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_auth_request(user_uuid.to_string(), auth_request_uuid.to_string(), conn).await; push_auth_request(user_id.clone(), auth_request_uuid.to_string(), conn).await;
} }
} }
pub async fn send_auth_response( pub async fn send_auth_response(
&self, &self,
user_uuid: &String, user_id: &UserId,
auth_response_uuid: &str, auth_response_uuid: &str,
approving_device_uuid: String, approving_device_uuid: DeviceId,
conn: &mut DbConn, conn: &mut DbConn,
) { ) {
// Skip any processing if both WebSockets and Push are not active // Skip any processing if both WebSockets and Push are not active
@ -531,17 +531,16 @@ impl WebSocketUsers {
return; return;
} }
let data = create_update( let data = create_update(
vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_id.to_string().into())],
UpdateType::AuthRequestResponse, UpdateType::AuthRequestResponse,
approving_device_uuid.clone().into(), Some(approving_device_uuid.clone()),
); );
if CONFIG.enable_websocket() { if CONFIG.enable_websocket() {
self.send_update(auth_response_uuid, &data).await; self.send_update(user_id, &data).await;
} }
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
push_auth_response(user_uuid.to_string(), auth_response_uuid.to_string(), approving_device_uuid, conn) push_auth_response(user_id.clone(), auth_response_uuid.to_string(), approving_device_uuid, conn).await;
.await;
} }
} }
} }
@ -560,16 +559,16 @@ impl AnonymousWebSocketSubscriptions {
} }
} }
pub async fn send_auth_response(&self, user_uuid: &String, auth_response_uuid: &str) { pub async fn send_auth_response(&self, user_id: &UserId, auth_response_uuid: &str) {
if !CONFIG.enable_websocket() { if !CONFIG.enable_websocket() {
return; return;
} }
let data = create_anonymous_update( let data = create_anonymous_update(
vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_id.to_string().into())],
UpdateType::AuthRequestResponse, UpdateType::AuthRequestResponse,
user_uuid.to_string(), user_id.clone(),
); );
self.send_update(auth_response_uuid, &data).await; self.send_update(user_id, &data).await;
} }
} }
@ -581,14 +580,14 @@ impl AnonymousWebSocketSubscriptions {
"ReceiveMessage", // Target "ReceiveMessage", // Target
[ // Arguments [ // Arguments
{ {
"ContextId": acting_device_uuid || Nil, "ContextId": acting_device_id || Nil,
"Type": ut as i32, "Type": ut as i32,
"Payload": {} "Payload": {}
} }
] ]
] ]
*/ */
fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uuid: Option<String>) -> Vec<u8> { fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_id: Option<DeviceId>) -> Vec<u8> {
use rmpv::Value as V; use rmpv::Value as V;
let value = V::Array(vec![ let value = V::Array(vec![
@ -597,7 +596,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uui
V::Nil, V::Nil,
"ReceiveMessage".into(), "ReceiveMessage".into(),
V::Array(vec![V::Map(vec![ V::Array(vec![V::Map(vec![
("ContextId".into(), acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| V::Nil)), ("ContextId".into(), acting_device_id.map(|v| v.to_string().into()).unwrap_or_else(|| V::Nil)),
("Type".into(), (ut as i32).into()), ("Type".into(), (ut as i32).into()),
("Payload".into(), payload.into()), ("Payload".into(), payload.into()),
])]), ])]),
@ -606,7 +605,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uui
serialize(value) serialize(value)
} }
fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id: String) -> Vec<u8> { fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id: UserId) -> Vec<u8> {
use rmpv::Value as V; use rmpv::Value as V;
let value = V::Array(vec![ let value = V::Array(vec![
@ -617,7 +616,7 @@ fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id
V::Array(vec![V::Map(vec![ V::Array(vec![V::Map(vec![
("Type".into(), (ut as i32).into()), ("Type".into(), (ut as i32).into()),
("Payload".into(), payload.into()), ("Payload".into(), payload.into()),
("UserId".into(), user_id.into()), ("UserId".into(), user_id.to_string().into()),
])]), ])]),
]); ]);

Datei anzeigen

@ -7,7 +7,7 @@ use tokio::sync::RwLock;
use crate::{ use crate::{
api::{ApiResult, EmptyResult, UpdateType}, api::{ApiResult, EmptyResult, UpdateType},
db::models::{Cipher, Device, Folder, Send, User}, db::models::{Cipher, Device, DeviceId, Folder, Send, User, UserId},
http_client::make_http_request, http_client::make_http_request,
util::format_date, util::format_date,
CONFIG, CONFIG,
@ -126,15 +126,15 @@ pub async fn register_push_device(device: &mut Device, conn: &mut crate::db::DbC
Ok(()) Ok(())
} }
pub async fn unregister_push_device(push_uuid: Option<String>) -> EmptyResult { pub async fn unregister_push_device(push_id: Option<String>) -> EmptyResult {
if !CONFIG.push_enabled() || push_uuid.is_none() { if !CONFIG.push_enabled() || push_id.is_none() {
return Ok(()); return Ok(());
} }
let auth_push_token = get_auth_push_token().await?; let auth_push_token = get_auth_push_token().await?;
let auth_header = format!("Bearer {}", &auth_push_token); let auth_header = format!("Bearer {}", &auth_push_token);
match make_http_request(Method::DELETE, &(CONFIG.push_relay_uri() + "/push/" + &push_uuid.unwrap()))? match make_http_request(Method::DELETE, &(CONFIG.push_relay_uri() + "/push/" + &push_id.unwrap()))?
.header(AUTHORIZATION, auth_header) .header(AUTHORIZATION, auth_header)
.send() .send()
.await .await
@ -148,24 +148,24 @@ pub async fn unregister_push_device(push_uuid: Option<String>) -> EmptyResult {
pub async fn push_cipher_update( pub async fn push_cipher_update(
ut: UpdateType, ut: UpdateType,
cipher: &Cipher, cipher: &Cipher,
acting_device_uuid: &String, acting_device_id: &DeviceId,
conn: &mut crate::db::DbConn, conn: &mut crate::db::DbConn,
) { ) {
// We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too. // We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too.
if cipher.organization_uuid.is_some() { if cipher.organization_uuid.is_some() {
return; return;
}; };
let Some(user_uuid) = &cipher.user_uuid else { let Some(user_id) = &cipher.user_uuid else {
debug!("Cipher has no uuid"); debug!("Cipher has no uuid");
return; return;
}; };
if Device::check_user_has_push_device(user_uuid, conn).await { if Device::check_user_has_push_device(user_id, conn).await {
send_to_push_relay(json!({ send_to_push_relay(json!({
"userId": user_uuid, "userId": user_id,
"organizationId": (), "organizationId": (),
"deviceId": acting_device_uuid, "deviceId": acting_device_id,
"identifier": acting_device_uuid, "identifier": acting_device_id,
"type": ut as i32, "type": ut as i32,
"payload": { "payload": {
"Id": cipher.uuid, "Id": cipher.uuid,
@ -178,14 +178,14 @@ pub async fn push_cipher_update(
} }
} }
pub fn push_logout(user: &User, acting_device_uuid: Option<String>) { pub fn push_logout(user: &User, acting_device_id: Option<DeviceId>) {
let acting_device_uuid: Value = acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| Value::Null); let acting_device_id: Value = acting_device_id.map(|v| v.to_string().into()).unwrap_or_else(|| Value::Null);
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
"userId": user.uuid, "userId": user.uuid,
"organizationId": (), "organizationId": (),
"deviceId": acting_device_uuid, "deviceId": acting_device_id,
"identifier": acting_device_uuid, "identifier": acting_device_id,
"type": UpdateType::LogOut as i32, "type": UpdateType::LogOut as i32,
"payload": { "payload": {
"UserId": user.uuid, "UserId": user.uuid,
@ -211,15 +211,15 @@ pub fn push_user_update(ut: UpdateType, user: &User) {
pub async fn push_folder_update( pub async fn push_folder_update(
ut: UpdateType, ut: UpdateType,
folder: &Folder, folder: &Folder,
acting_device_uuid: &String, acting_device_id: &DeviceId,
conn: &mut crate::db::DbConn, conn: &mut crate::db::DbConn,
) { ) {
if Device::check_user_has_push_device(&folder.user_uuid, conn).await { if Device::check_user_has_push_device(&folder.user_uuid, conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
"userId": folder.user_uuid, "userId": folder.user_uuid,
"organizationId": (), "organizationId": (),
"deviceId": acting_device_uuid, "deviceId": acting_device_id,
"identifier": acting_device_uuid, "identifier": acting_device_id,
"type": ut as i32, "type": ut as i32,
"payload": { "payload": {
"Id": folder.uuid, "Id": folder.uuid,
@ -230,14 +230,14 @@ pub async fn push_folder_update(
} }
} }
pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_uuid: &String, conn: &mut crate::db::DbConn) { pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_id: &DeviceId, conn: &mut crate::db::DbConn) {
if let Some(s) = &send.user_uuid { if let Some(s) = &send.user_uuid {
if Device::check_user_has_push_device(s, conn).await { if Device::check_user_has_push_device(s, conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
"userId": send.user_uuid, "userId": send.user_uuid,
"organizationId": (), "organizationId": (),
"deviceId": acting_device_uuid, "deviceId": acting_device_id,
"identifier": acting_device_uuid, "identifier": acting_device_id,
"type": ut as i32, "type": ut as i32,
"payload": { "payload": {
"Id": send.uuid, "Id": send.uuid,
@ -284,38 +284,38 @@ async fn send_to_push_relay(notification_data: Value) {
}; };
} }
pub async fn push_auth_request(user_uuid: String, auth_request_uuid: String, conn: &mut crate::db::DbConn) { pub async fn push_auth_request(user_id: UserId, auth_request_id: String, conn: &mut crate::db::DbConn) {
if Device::check_user_has_push_device(user_uuid.as_str(), conn).await { if Device::check_user_has_push_device(&user_id, conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
"userId": user_uuid, "userId": user_id,
"organizationId": (), "organizationId": (),
"deviceId": null, "deviceId": null,
"identifier": null, "identifier": null,
"type": UpdateType::AuthRequest as i32, "type": UpdateType::AuthRequest as i32,
"payload": { "payload": {
"Id": auth_request_uuid, "Id": auth_request_id,
"UserId": user_uuid, "UserId": user_id,
} }
}))); })));
} }
} }
pub async fn push_auth_response( pub async fn push_auth_response(
user_uuid: String, user_id: UserId,
auth_request_uuid: String, auth_request_id: String,
approving_device_uuid: String, approving_device_id: DeviceId,
conn: &mut crate::db::DbConn, conn: &mut crate::db::DbConn,
) { ) {
if Device::check_user_has_push_device(user_uuid.as_str(), conn).await { if Device::check_user_has_push_device(&user_id, conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
"userId": user_uuid, "userId": user_id,
"organizationId": (), "organizationId": (),
"deviceId": approving_device_uuid, "deviceId": approving_device_id,
"identifier": approving_device_uuid, "identifier": approving_device_id,
"type": UpdateType::AuthRequestResponse as i32, "type": UpdateType::AuthRequestResponse as i32,
"payload": { "payload": {
"Id": auth_request_uuid, "Id": auth_request_id,
"UserId": user_uuid, "UserId": user_id,
} }
}))); })));
} }

Datei anzeigen

@ -12,8 +12,9 @@ use serde_json::Value;
use crate::{ use crate::{
api::{core::now, ApiResult, EmptyResult}, api::{core::now, ApiResult, EmptyResult},
auth::decode_file_download, auth::decode_file_download,
db::models::{AttachmentId, CipherId},
error::Error, error::Error,
util::{Cached, SafeString}, util::Cached,
CONFIG, CONFIG,
}; };
@ -158,16 +159,16 @@ async fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).await.ok(), true) Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).await.ok(), true)
} }
#[get("/attachments/<uuid>/<file_id>?<token>")] #[get("/attachments/<cipher_id>/<file_id>?<token>")]
async fn attachments(uuid: SafeString, file_id: SafeString, token: String) -> Option<NamedFile> { async fn attachments(cipher_id: CipherId, file_id: AttachmentId, token: String) -> Option<NamedFile> {
let Ok(claims) = decode_file_download(&token) else { let Ok(claims) = decode_file_download(&token) else {
return None; return None;
}; };
if claims.sub != *uuid || claims.file_id != *file_id { if claims.sub != cipher_id || claims.file_id != file_id {
return None; return None;
} }
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok() NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(cipher_id.as_ref()).join(file_id.as_ref())).await.ok()
} }
// We use DbConn here to let the alive healthcheck also verify the database connection. // We use DbConn here to let the alive healthcheck also verify the database connection.

Datei anzeigen

@ -14,6 +14,10 @@ use std::{
net::IpAddr, net::IpAddr,
}; };
use crate::db::models::{
AttachmentId, CipherId, CollectionId, DeviceId, EmergencyAccessId, MembershipId, OrgApiKeyId, OrganizationId,
SendFileId, SendId, UserId,
};
use crate::{error::Error, CONFIG}; use crate::{error::Error, CONFIG};
const JWT_ALGORITHM: Algorithm = Algorithm::RS256; const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
@ -150,7 +154,7 @@ pub struct LoginJwtClaims {
// Issuer // Issuer
pub iss: String, pub iss: String,
// Subject // Subject
pub sub: String, pub sub: UserId,
pub premium: bool, pub premium: bool,
pub name: String, pub name: String,
@ -171,7 +175,7 @@ pub struct LoginJwtClaims {
// user security_stamp // user security_stamp
pub sstamp: String, pub sstamp: String,
// device uuid // device uuid
pub device: String, pub device: DeviceId,
// [ "api", "offline_access" ] // [ "api", "offline_access" ]
pub scope: Vec<String>, pub scope: Vec<String>,
// [ "Application" ] // [ "Application" ]
@ -187,19 +191,19 @@ pub struct InviteJwtClaims {
// Issuer // Issuer
pub iss: String, pub iss: String,
// Subject // Subject
pub sub: String, pub sub: UserId,
pub email: String, pub email: String,
pub org_id: Option<String>, pub org_id: Option<OrganizationId>,
pub user_org_id: Option<String>, pub member_id: Option<MembershipId>,
pub invited_by_email: Option<String>, pub invited_by_email: Option<String>,
} }
pub fn generate_invite_claims( pub fn generate_invite_claims(
uuid: String, user_id: UserId,
email: String, email: String,
org_id: Option<String>, org_id: Option<OrganizationId>,
user_org_id: Option<String>, member_id: Option<MembershipId>,
invited_by_email: Option<String>, invited_by_email: Option<String>,
) -> InviteJwtClaims { ) -> InviteJwtClaims {
let time_now = Utc::now(); let time_now = Utc::now();
@ -208,10 +212,10 @@ pub fn generate_invite_claims(
nbf: time_now.timestamp(), nbf: time_now.timestamp(),
exp: (time_now + TimeDelta::try_hours(expire_hours).unwrap()).timestamp(), exp: (time_now + TimeDelta::try_hours(expire_hours).unwrap()).timestamp(),
iss: JWT_INVITE_ISSUER.to_string(), iss: JWT_INVITE_ISSUER.to_string(),
sub: uuid, sub: user_id,
email, email,
org_id, org_id,
user_org_id, member_id,
invited_by_email, invited_by_email,
} }
} }
@ -225,18 +229,18 @@ pub struct EmergencyAccessInviteJwtClaims {
// Issuer // Issuer
pub iss: String, pub iss: String,
// Subject // Subject
pub sub: String, pub sub: UserId,
pub email: String, pub email: String,
pub emer_id: String, pub emer_id: EmergencyAccessId,
pub grantor_name: String, pub grantor_name: String,
pub grantor_email: String, pub grantor_email: String,
} }
pub fn generate_emergency_access_invite_claims( pub fn generate_emergency_access_invite_claims(
uuid: String, user_id: UserId,
email: String, email: String,
emer_id: String, emer_id: EmergencyAccessId,
grantor_name: String, grantor_name: String,
grantor_email: String, grantor_email: String,
) -> EmergencyAccessInviteJwtClaims { ) -> EmergencyAccessInviteJwtClaims {
@ -246,7 +250,7 @@ pub fn generate_emergency_access_invite_claims(
nbf: time_now.timestamp(), nbf: time_now.timestamp(),
exp: (time_now + TimeDelta::try_hours(expire_hours).unwrap()).timestamp(), exp: (time_now + TimeDelta::try_hours(expire_hours).unwrap()).timestamp(),
iss: JWT_EMERGENCY_ACCESS_INVITE_ISSUER.to_string(), iss: JWT_EMERGENCY_ACCESS_INVITE_ISSUER.to_string(),
sub: uuid, sub: user_id,
email, email,
emer_id, emer_id,
grantor_name, grantor_name,
@ -263,21 +267,24 @@ pub struct OrgApiKeyLoginJwtClaims {
// Issuer // Issuer
pub iss: String, pub iss: String,
// Subject // Subject
pub sub: String, pub sub: OrgApiKeyId,
pub client_id: String, pub client_id: String,
pub client_sub: String, pub client_sub: OrganizationId,
pub scope: Vec<String>, pub scope: Vec<String>,
} }
pub fn generate_organization_api_key_login_claims(uuid: String, org_id: String) -> OrgApiKeyLoginJwtClaims { pub fn generate_organization_api_key_login_claims(
org_api_key_uuid: OrgApiKeyId,
org_id: OrganizationId,
) -> OrgApiKeyLoginJwtClaims {
let time_now = Utc::now(); let time_now = Utc::now();
OrgApiKeyLoginJwtClaims { OrgApiKeyLoginJwtClaims {
nbf: time_now.timestamp(), nbf: time_now.timestamp(),
exp: (time_now + TimeDelta::try_hours(1).unwrap()).timestamp(), exp: (time_now + TimeDelta::try_hours(1).unwrap()).timestamp(),
iss: JWT_ORG_API_KEY_ISSUER.to_string(), iss: JWT_ORG_API_KEY_ISSUER.to_string(),
sub: uuid, sub: org_api_key_uuid,
client_id: format!("organization.{org_id}"), client_id: format!("organization.{}", org_id),
client_sub: org_id, client_sub: org_id,
scope: vec!["api.organization".into()], scope: vec!["api.organization".into()],
} }
@ -292,18 +299,18 @@ pub struct FileDownloadClaims {
// Issuer // Issuer
pub iss: String, pub iss: String,
// Subject // Subject
pub sub: String, pub sub: CipherId,
pub file_id: String, pub file_id: AttachmentId,
} }
pub fn generate_file_download_claims(uuid: String, file_id: String) -> FileDownloadClaims { pub fn generate_file_download_claims(cipher_id: CipherId, file_id: AttachmentId) -> FileDownloadClaims {
let time_now = Utc::now(); let time_now = Utc::now();
FileDownloadClaims { FileDownloadClaims {
nbf: time_now.timestamp(), nbf: time_now.timestamp(),
exp: (time_now + TimeDelta::try_minutes(5).unwrap()).timestamp(), exp: (time_now + TimeDelta::try_minutes(5).unwrap()).timestamp(),
iss: JWT_FILE_DOWNLOAD_ISSUER.to_string(), iss: JWT_FILE_DOWNLOAD_ISSUER.to_string(),
sub: uuid, sub: cipher_id,
file_id, file_id,
} }
} }
@ -331,14 +338,14 @@ pub fn generate_delete_claims(uuid: String) -> BasicJwtClaims {
} }
} }
pub fn generate_verify_email_claims(uuid: String) -> BasicJwtClaims { pub fn generate_verify_email_claims(user_id: UserId) -> BasicJwtClaims {
let time_now = Utc::now(); let time_now = Utc::now();
let expire_hours = i64::from(CONFIG.invitation_expiration_hours()); let expire_hours = i64::from(CONFIG.invitation_expiration_hours());
BasicJwtClaims { BasicJwtClaims {
nbf: time_now.timestamp(), nbf: time_now.timestamp(),
exp: (time_now + TimeDelta::try_hours(expire_hours).unwrap()).timestamp(), exp: (time_now + TimeDelta::try_hours(expire_hours).unwrap()).timestamp(),
iss: JWT_VERIFYEMAIL_ISSUER.to_string(), iss: JWT_VERIFYEMAIL_ISSUER.to_string(),
sub: uuid, sub: user_id.to_string(),
} }
} }
@ -352,7 +359,7 @@ pub fn generate_admin_claims() -> BasicJwtClaims {
} }
} }
pub fn generate_send_claims(send_id: &str, file_id: &str) -> BasicJwtClaims { pub fn generate_send_claims(send_id: &SendId, file_id: &SendFileId) -> BasicJwtClaims {
let time_now = Utc::now(); let time_now = Utc::now();
BasicJwtClaims { BasicJwtClaims {
nbf: time_now.timestamp(), nbf: time_now.timestamp(),
@ -371,7 +378,7 @@ use rocket::{
}; };
use crate::db::{ use crate::db::{
models::{Collection, Device, User, UserOrgStatus, UserOrgType, UserOrganization, UserStampException}, models::{Collection, Device, Membership, MembershipStatus, MembershipType, User, UserStampException},
DbConn, DbConn,
}; };
@ -475,19 +482,19 @@ impl<'r> FromRequest<'r> for Headers {
err_handler!("Invalid claim") err_handler!("Invalid claim")
}; };
let device_uuid = claims.device; let device_id = claims.device;
let user_uuid = claims.sub; let user_id = claims.sub;
let mut conn = match DbConn::from_request(request).await { let mut conn = match DbConn::from_request(request).await {
Outcome::Success(conn) => conn, Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"), _ => err_handler!("Error getting DB"),
}; };
let Some(device) = Device::find_by_uuid_and_user(&device_uuid, &user_uuid, &mut conn).await else { let Some(device) = Device::find_by_uuid_and_user(&device_id, &user_id, &mut conn).await else {
err_handler!("Invalid device id") err_handler!("Invalid device id")
}; };
let Some(user) = User::find_by_uuid(&user_uuid, &mut conn).await else { let Some(user) = User::find_by_uuid(&user_id, &mut conn).await else {
err_handler!("Device has no user associated") err_handler!("Device has no user associated")
}; };
@ -534,8 +541,8 @@ pub struct OrgHeaders {
pub host: String, pub host: String,
pub device: Device, pub device: Device,
pub user: User, pub user: User,
pub org_user_type: UserOrgType, pub membership_type: MembershipType,
pub org_user: UserOrganization, pub membership: Membership,
pub ip: ClientIp, pub ip: ClientIp,
} }
@ -549,17 +556,17 @@ impl<'r> FromRequest<'r> for OrgHeaders {
// org_id is usually the second path param ("/organizations/<org_id>"), // org_id is usually the second path param ("/organizations/<org_id>"),
// but there are cases where it is a query value. // but there are cases where it is a query value.
// First check the path, if this is not a valid uuid, try the query values. // First check the path, if this is not a valid uuid, try the query values.
let url_org_id: Option<&str> = { let url_org_id: Option<OrganizationId> = {
let mut url_org_id = None; let mut url_org_id = None;
if let Some(Ok(org_id)) = request.param::<&str>(1) { if let Some(Ok(org_id)) = request.param::<&str>(1) {
if uuid::Uuid::parse_str(org_id).is_ok() { if uuid::Uuid::parse_str(org_id).is_ok() {
url_org_id = Some(org_id); url_org_id = Some(org_id.to_string().into());
} }
} }
if let Some(Ok(org_id)) = request.query_value::<&str>("organizationId") { if let Some(Ok(org_id)) = request.query_value::<&str>("organizationId") {
if uuid::Uuid::parse_str(org_id).is_ok() { if uuid::Uuid::parse_str(org_id).is_ok() {
url_org_id = Some(org_id); url_org_id = Some(org_id.to_string().into());
} }
} }
@ -574,10 +581,10 @@ impl<'r> FromRequest<'r> for OrgHeaders {
}; };
let user = headers.user; let user = headers.user;
let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, org_id, &mut conn).await { let membership = match Membership::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await {
Some(user) => { Some(member) => {
if user.status == UserOrgStatus::Confirmed as i32 { if member.status == MembershipStatus::Confirmed as i32 {
user member
} else { } else {
err_handler!("The current user isn't confirmed member of the organization") err_handler!("The current user isn't confirmed member of the organization")
} }
@ -589,15 +596,15 @@ impl<'r> FromRequest<'r> for OrgHeaders {
host: headers.host, host: headers.host,
device: headers.device, device: headers.device,
user, user,
org_user_type: { membership_type: {
if let Some(org_usr_type) = UserOrgType::from_i32(org_user.atype) { if let Some(org_usr_type) = MembershipType::from_i32(membership.atype) {
org_usr_type org_usr_type
} else { } else {
// This should only happen if the DB is corrupted // This should only happen if the DB is corrupted
err_handler!("Unknown user type in the database") err_handler!("Unknown user type in the database")
} }
}, },
org_user, membership,
ip: headers.ip, ip: headers.ip,
}) })
} }
@ -610,7 +617,7 @@ pub struct AdminHeaders {
pub host: String, pub host: String,
pub device: Device, pub device: Device,
pub user: User, pub user: User,
pub org_user_type: UserOrgType, pub membership_type: MembershipType,
pub ip: ClientIp, pub ip: ClientIp,
} }
@ -620,12 +627,12 @@ impl<'r> FromRequest<'r> for AdminHeaders {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let headers = try_outcome!(OrgHeaders::from_request(request).await); let headers = try_outcome!(OrgHeaders::from_request(request).await);
if headers.org_user_type >= UserOrgType::Admin { if headers.membership_type >= MembershipType::Admin {
Outcome::Success(Self { Outcome::Success(Self {
host: headers.host, host: headers.host,
device: headers.device, device: headers.device,
user: headers.user, user: headers.user,
org_user_type: headers.org_user_type, membership_type: headers.membership_type,
ip: headers.ip, ip: headers.ip,
}) })
} else { } else {
@ -648,16 +655,16 @@ impl From<AdminHeaders> for Headers {
// col_id is usually the fourth path param ("/organizations/<org_id>/collections/<col_id>"), // col_id is usually the fourth path param ("/organizations/<org_id>/collections/<col_id>"),
// but there could be cases where it is a query value. // but there could be cases where it is a query value.
// First check the path, if this is not a valid uuid, try the query values. // First check the path, if this is not a valid uuid, try the query values.
fn get_col_id(request: &Request<'_>) -> Option<String> { fn get_col_id(request: &Request<'_>) -> Option<CollectionId> {
if let Some(Ok(col_id)) = request.param::<String>(3) { if let Some(Ok(col_id)) = request.param::<String>(3) {
if uuid::Uuid::parse_str(&col_id).is_ok() { if uuid::Uuid::parse_str(&col_id).is_ok() {
return Some(col_id); return Some(col_id.into());
} }
} }
if let Some(Ok(col_id)) = request.query_value::<String>("collectionId") { if let Some(Ok(col_id)) = request.query_value::<String>("collectionId") {
if uuid::Uuid::parse_str(&col_id).is_ok() { if uuid::Uuid::parse_str(&col_id).is_ok() {
return Some(col_id); return Some(col_id.into());
} }
} }
@ -680,7 +687,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let headers = try_outcome!(OrgHeaders::from_request(request).await); let headers = try_outcome!(OrgHeaders::from_request(request).await);
if headers.org_user_type >= UserOrgType::Manager { if headers.membership_type >= MembershipType::Manager {
match get_col_id(request) { match get_col_id(request) {
Some(col_id) => { Some(col_id) => {
let mut conn = match DbConn::from_request(request).await { let mut conn = match DbConn::from_request(request).await {
@ -688,7 +695,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders {
_ => err_handler!("Error getting DB"), _ => err_handler!("Error getting DB"),
}; };
if !Collection::can_access_collection(&headers.org_user, &col_id, &mut conn).await { if !Collection::can_access_collection(&headers.membership, &col_id, &mut conn).await {
err_handler!("The current user isn't a manager for this collection") err_handler!("The current user isn't a manager for this collection")
} }
} }
@ -724,7 +731,7 @@ pub struct ManagerHeadersLoose {
pub host: String, pub host: String,
pub device: Device, pub device: Device,
pub user: User, pub user: User,
pub org_user: UserOrganization, pub membership: Membership,
pub ip: ClientIp, pub ip: ClientIp,
} }
@ -734,12 +741,12 @@ impl<'r> FromRequest<'r> for ManagerHeadersLoose {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let headers = try_outcome!(OrgHeaders::from_request(request).await); let headers = try_outcome!(OrgHeaders::from_request(request).await);
if headers.org_user_type >= UserOrgType::Manager { if headers.membership_type >= MembershipType::Manager {
Outcome::Success(Self { Outcome::Success(Self {
host: headers.host, host: headers.host,
device: headers.device, device: headers.device,
user: headers.user, user: headers.user,
org_user: headers.org_user, membership: headers.membership,
ip: headers.ip, ip: headers.ip,
}) })
} else { } else {
@ -762,14 +769,14 @@ impl From<ManagerHeadersLoose> for Headers {
impl ManagerHeaders { impl ManagerHeaders {
pub async fn from_loose( pub async fn from_loose(
h: ManagerHeadersLoose, h: ManagerHeadersLoose,
collections: &Vec<String>, collections: &Vec<CollectionId>,
conn: &mut DbConn, conn: &mut DbConn,
) -> Result<ManagerHeaders, Error> { ) -> Result<ManagerHeaders, Error> {
for col_id in collections { for col_id in collections {
if uuid::Uuid::parse_str(col_id).is_err() { if uuid::Uuid::parse_str(col_id.as_ref()).is_err() {
err!("Collection Id is malformed!"); err!("Collection Id is malformed!");
} }
if !Collection::can_access_collection(&h.org_user, col_id, conn).await { if !Collection::can_access_collection(&h.membership, col_id, conn).await {
err!("You don't have access to all collections!"); err!("You don't have access to all collections!");
} }
} }
@ -795,7 +802,7 @@ impl<'r> FromRequest<'r> for OwnerHeaders {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let headers = try_outcome!(OrgHeaders::from_request(request).await); let headers = try_outcome!(OrgHeaders::from_request(request).await);
if headers.org_user_type == UserOrgType::Owner { if headers.membership_type == MembershipType::Owner {
Outcome::Success(Self { Outcome::Success(Self {
device: headers.device, device: headers.device,
user: headers.user, user: headers.user,

Datei anzeigen

@ -84,14 +84,15 @@ pub fn generate_id<const N: usize>() -> String {
encode_random_bytes::<N>(HEXLOWER) encode_random_bytes::<N>(HEXLOWER)
} }
pub fn generate_send_id() -> String { pub fn generate_send_file_id() -> String {
// Send IDs are globally scoped, so make them longer to avoid collisions. // Send File IDs are globally scoped, so make them longer to avoid collisions.
generate_id::<32>() // 256 bits generate_id::<32>() // 256 bits
} }
pub fn generate_attachment_id() -> String { use crate::db::models::AttachmentId;
pub fn generate_attachment_id() -> AttachmentId {
// Attachment IDs are scoped to a cipher, so they can be smaller. // Attachment IDs are scoped to a cipher, so they can be smaller.
generate_id::<10>() // 80 bits AttachmentId(generate_id::<10>()) // 80 bits
} }
/// Generates a numeric token for email-based verifications. /// Generates a numeric token for email-based verifications.

Datei anzeigen

@ -1,9 +1,12 @@
use std::io::ErrorKind; use std::io::ErrorKind;
use bigdecimal::{BigDecimal, ToPrimitive}; use bigdecimal::{BigDecimal, ToPrimitive};
use derive_more::{AsRef, Deref, Display};
use serde_json::Value; use serde_json::Value;
use super::{CipherId, OrganizationId, UserId};
use crate::CONFIG; use crate::CONFIG;
use macros::IdFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -11,8 +14,8 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(id))] #[diesel(primary_key(id))]
pub struct Attachment { pub struct Attachment {
pub id: String, pub id: AttachmentId,
pub cipher_uuid: String, pub cipher_uuid: CipherId,
pub file_name: String, // encrypted pub file_name: String, // encrypted
pub file_size: i64, pub file_size: i64,
pub akey: Option<String>, pub akey: Option<String>,
@ -21,7 +24,13 @@ db_object! {
/// Local methods /// Local methods
impl Attachment { impl Attachment {
pub const fn new(id: String, cipher_uuid: String, file_name: String, file_size: i64, akey: Option<String>) -> Self { pub const fn new(
id: AttachmentId,
cipher_uuid: CipherId,
file_name: String,
file_size: i64,
akey: Option<String>,
) -> Self {
Self { Self {
id, id,
cipher_uuid, cipher_uuid,
@ -117,14 +126,14 @@ impl Attachment {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await { for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await {
attachment.delete(conn).await?; attachment.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_id(id: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_id(id: &AttachmentId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.filter(attachments::id.eq(id.to_lowercase())) .filter(attachments::id.eq(id.to_lowercase()))
@ -134,7 +143,7 @@ impl Attachment {
}} }}
} }
pub async fn find_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.filter(attachments::cipher_uuid.eq(cipher_uuid)) .filter(attachments::cipher_uuid.eq(cipher_uuid))
@ -144,7 +153,7 @@ impl Attachment {
}} }}
} }
pub async fn size_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn size_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
let result: Option<BigDecimal> = attachments::table let result: Option<BigDecimal> = attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -161,7 +170,7 @@ impl Attachment {
}} }}
} }
pub async fn count_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -172,7 +181,7 @@ impl Attachment {
}} }}
} }
pub async fn size_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn size_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
let result: Option<BigDecimal> = attachments::table let result: Option<BigDecimal> = attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -189,7 +198,7 @@ impl Attachment {
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -203,7 +212,11 @@ impl Attachment {
// This will return all attachments linked to the user or org // This will return all attachments linked to the user or org
// There is no filtering done here if the user actually has access! // There is no filtering done here if the user actually has access!
// It is used to speed up the sync process, and the matching is done in a different part. // It is used to speed up the sync process, and the matching is done in a different part.
pub async fn find_all_by_user_and_orgs(user_uuid: &str, org_uuids: &Vec<String>, conn: &mut DbConn) -> Vec<Self> { pub async fn find_all_by_user_and_orgs(
user_uuid: &UserId,
org_uuids: &Vec<OrganizationId>,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -216,3 +229,20 @@ impl Attachment {
}} }}
} }
} }
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
IdFromParam,
)]
pub struct AttachmentId(pub String);

Datei anzeigen

@ -1,5 +1,8 @@
use super::{DeviceId, OrganizationId, UserId};
use crate::crypto::ct_eq; use crate::crypto::ct_eq;
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use derive_more::{AsRef, Deref, Display, From};
use macros::UuidFromParam;
db_object! { db_object! {
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)] #[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
@ -7,15 +10,15 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct AuthRequest { pub struct AuthRequest {
pub uuid: String, pub uuid: AuthRequestId,
pub user_uuid: String, pub user_uuid: UserId,
pub organization_uuid: Option<String>, pub organization_uuid: Option<OrganizationId>,
pub request_device_identifier: String, pub request_device_identifier: DeviceId,
pub device_type: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs pub device_type: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs
pub request_ip: String, pub request_ip: String,
pub response_device_id: Option<String>, pub response_device_id: Option<DeviceId>,
pub access_code: String, pub access_code: String,
pub public_key: String, pub public_key: String,
@ -33,8 +36,8 @@ db_object! {
impl AuthRequest { impl AuthRequest {
pub fn new( pub fn new(
user_uuid: String, user_uuid: UserId,
request_device_identifier: String, request_device_identifier: DeviceId,
device_type: i32, device_type: i32,
request_ip: String, request_ip: String,
access_code: String, access_code: String,
@ -43,7 +46,7 @@ impl AuthRequest {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
Self { Self {
uuid: crate::util::get_uuid(), uuid: AuthRequestId(crate::util::get_uuid()),
user_uuid, user_uuid,
organization_uuid: None, organization_uuid: None,
@ -101,7 +104,7 @@ impl AuthRequest {
} }
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &AuthRequestId, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
auth_requests::table auth_requests::table
.filter(auth_requests::uuid.eq(uuid)) .filter(auth_requests::uuid.eq(uuid))
@ -111,7 +114,7 @@ impl AuthRequest {
}} }}
} }
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_user(uuid: &AuthRequestId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
auth_requests::table auth_requests::table
.filter(auth_requests::uuid.eq(uuid)) .filter(auth_requests::uuid.eq(uuid))
@ -122,7 +125,7 @@ impl AuthRequest {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
auth_requests::table auth_requests::table
.filter(auth_requests::user_uuid.eq(user_uuid)) .filter(auth_requests::user_uuid.eq(user_uuid))
@ -157,3 +160,21 @@ impl AuthRequest {
} }
} }
} }
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct AuthRequestId(String);

Datei anzeigen

@ -1,13 +1,15 @@
use crate::util::LowerCase; use crate::util::LowerCase;
use crate::CONFIG; use crate::CONFIG;
use chrono::{NaiveDateTime, TimeDelta, Utc}; use chrono::{NaiveDateTime, TimeDelta, Utc};
use derive_more::{AsRef, Deref, Display, From};
use serde_json::Value; use serde_json::Value;
use super::{ use super::{
Attachment, CollectionCipher, Favorite, FolderCipher, Group, User, UserOrgStatus, UserOrgType, UserOrganization, Attachment, CollectionCipher, CollectionId, Favorite, FolderCipher, FolderId, Group, Membership, MembershipStatus,
MembershipType, OrganizationId, User, UserId,
}; };
use crate::api::core::{CipherData, CipherSyncData, CipherSyncType}; use crate::api::core::{CipherData, CipherSyncData, CipherSyncType};
use macros::UuidFromParam;
use std::borrow::Cow; use std::borrow::Cow;
@ -17,12 +19,12 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Cipher { pub struct Cipher {
pub uuid: String, pub uuid: CipherId,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
pub user_uuid: Option<String>, pub user_uuid: Option<UserId>,
pub organization_uuid: Option<String>, pub organization_uuid: Option<OrganizationId>,
pub key: Option<String>, pub key: Option<String>,
@ -57,7 +59,7 @@ impl Cipher {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
Self { Self {
uuid: crate::util::get_uuid(), uuid: CipherId(crate::util::get_uuid()),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
@ -135,7 +137,7 @@ impl Cipher {
pub async fn to_json( pub async fn to_json(
&self, &self,
host: &str, host: &str,
user_uuid: &str, user_uuid: &UserId,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
sync_type: CipherSyncType, sync_type: CipherSyncType,
conn: &mut DbConn, conn: &mut DbConn,
@ -302,7 +304,7 @@ impl Cipher {
Cow::from(Vec::with_capacity(0)) Cow::from(Vec::with_capacity(0))
} }
} else { } else {
Cow::from(self.get_admin_collections(user_uuid.to_string(), conn).await) Cow::from(self.get_admin_collections(user_uuid.clone(), conn).await)
}; };
// There are three types of cipher response models in upstream // There are three types of cipher response models in upstream
@ -351,7 +353,7 @@ impl Cipher {
// Skip adding these fields in that case // Skip adding these fields in that case
if sync_type == CipherSyncType::User { if sync_type == CipherSyncType::User {
json_object["folderId"] = json!(if let Some(cipher_sync_data) = cipher_sync_data { json_object["folderId"] = json!(if let Some(cipher_sync_data) = cipher_sync_data {
cipher_sync_data.cipher_folders.get(&self.uuid).map(|c| c.to_string()) cipher_sync_data.cipher_folders.get(&self.uuid).cloned()
} else { } else {
self.get_folder_uuid(user_uuid, conn).await self.get_folder_uuid(user_uuid, conn).await
}); });
@ -380,7 +382,7 @@ impl Cipher {
json_object json_object
} }
pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<String> { pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<UserId> {
let mut user_uuids = Vec::new(); let mut user_uuids = Vec::new();
match self.user_uuid { match self.user_uuid {
Some(ref user_uuid) => { Some(ref user_uuid) => {
@ -391,17 +393,16 @@ impl Cipher {
// Belongs to Organization, need to update affected users // Belongs to Organization, need to update affected users
if let Some(ref org_uuid) = self.organization_uuid { if let Some(ref org_uuid) = self.organization_uuid {
// users having access to the collection // users having access to the collection
let mut collection_users = let mut collection_users = Membership::find_by_cipher_and_org(&self.uuid, org_uuid, conn).await;
UserOrganization::find_by_cipher_and_org(&self.uuid, org_uuid, conn).await;
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
// members of a group having access to the collection // members of a group having access to the collection
let group_users = let group_users =
UserOrganization::find_by_cipher_and_org_with_group(&self.uuid, org_uuid, conn).await; Membership::find_by_cipher_and_org_with_group(&self.uuid, org_uuid, conn).await;
collection_users.extend(group_users); collection_users.extend(group_users);
} }
for user_org in collection_users { for member in collection_users {
User::update_uuid_revision(&user_org.user_uuid, conn).await; User::update_uuid_revision(&member.user_uuid, conn).await;
user_uuids.push(user_org.user_uuid.clone()) user_uuids.push(member.user_uuid.clone())
} }
} }
} }
@ -459,7 +460,7 @@ impl Cipher {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
// TODO: Optimize this by executing a DELETE directly on the database, instead of first fetching. // TODO: Optimize this by executing a DELETE directly on the database, instead of first fetching.
for cipher in Self::find_by_org(org_uuid, conn).await { for cipher in Self::find_by_org(org_uuid, conn).await {
cipher.delete(conn).await?; cipher.delete(conn).await?;
@ -467,7 +468,7 @@ impl Cipher {
Ok(()) Ok(())
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
for cipher in Self::find_owned_by_user(user_uuid, conn).await { for cipher in Self::find_owned_by_user(user_uuid, conn).await {
cipher.delete(conn).await?; cipher.delete(conn).await?;
} }
@ -485,52 +486,59 @@ impl Cipher {
} }
} }
pub async fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn move_to_folder(
&self,
folder_uuid: Option<FolderId>,
user_uuid: &UserId,
conn: &mut DbConn,
) -> EmptyResult {
User::update_uuid_revision(user_uuid, conn).await; User::update_uuid_revision(user_uuid, conn).await;
match (self.get_folder_uuid(user_uuid, conn).await, folder_uuid) { match (self.get_folder_uuid(user_uuid, conn).await, folder_uuid) {
// No changes // No changes
(None, None) => Ok(()), (None, None) => Ok(()),
(Some(ref old), Some(ref new)) if old == new => Ok(()), (Some(ref old_folder), Some(ref new_folder)) if old_folder == new_folder => Ok(()),
// Add to folder // Add to folder
(None, Some(new)) => FolderCipher::new(&new, &self.uuid).save(conn).await, (None, Some(new_folder)) => FolderCipher::new(new_folder, self.uuid.clone()).save(conn).await,
// Remove from folder // Remove from folder
(Some(old), None) => match FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn).await { (Some(old_folder), None) => {
Some(old) => old.delete(conn).await, match FolderCipher::find_by_folder_and_cipher(&old_folder, &self.uuid, conn).await {
Some(old_folder) => old_folder.delete(conn).await,
None => err!("Couldn't move from previous folder"), None => err!("Couldn't move from previous folder"),
}, }
}
// Move to another folder // Move to another folder
(Some(old), Some(new)) => { (Some(old_folder), Some(new_folder)) => {
if let Some(old) = FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn).await { if let Some(old_folder) = FolderCipher::find_by_folder_and_cipher(&old_folder, &self.uuid, conn).await {
old.delete(conn).await?; old_folder.delete(conn).await?;
} }
FolderCipher::new(&new, &self.uuid).save(conn).await FolderCipher::new(new_folder, self.uuid.clone()).save(conn).await
} }
} }
} }
/// Returns whether this cipher is directly owned by the user. /// Returns whether this cipher is directly owned by the user.
pub fn is_owned_by_user(&self, user_uuid: &str) -> bool { pub fn is_owned_by_user(&self, user_uuid: &UserId) -> bool {
self.user_uuid.is_some() && self.user_uuid.as_ref().unwrap() == user_uuid self.user_uuid.is_some() && self.user_uuid.as_ref().unwrap() == user_uuid
} }
/// Returns whether this cipher is owned by an org in which the user has full access. /// Returns whether this cipher is owned by an org in which the user has full access.
async fn is_in_full_access_org( async fn is_in_full_access_org(
&self, &self,
user_uuid: &str, user_uuid: &UserId,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
conn: &mut DbConn, conn: &mut DbConn,
) -> bool { ) -> bool {
if let Some(ref org_uuid) = self.organization_uuid { if let Some(ref org_uuid) = self.organization_uuid {
if let Some(cipher_sync_data) = cipher_sync_data { if let Some(cipher_sync_data) = cipher_sync_data {
if let Some(cached_user_org) = cipher_sync_data.user_organizations.get(org_uuid) { if let Some(cached_member) = cipher_sync_data.members.get(org_uuid) {
return cached_user_org.has_full_access(); return cached_member.has_full_access();
} }
} else if let Some(user_org) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await { } else if let Some(member) = Membership::find_by_user_and_org(user_uuid, org_uuid, conn).await {
return user_org.has_full_access(); return member.has_full_access();
} }
} }
false false
@ -539,7 +547,7 @@ impl Cipher {
/// Returns whether this cipher is owned by an group in which the user has full access. /// Returns whether this cipher is owned by an group in which the user has full access.
async fn is_in_full_access_group( async fn is_in_full_access_group(
&self, &self,
user_uuid: &str, user_uuid: &UserId,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
conn: &mut DbConn, conn: &mut DbConn,
) -> bool { ) -> bool {
@ -563,7 +571,7 @@ impl Cipher {
/// the access restrictions. /// the access restrictions.
pub async fn get_access_restrictions( pub async fn get_access_restrictions(
&self, &self,
user_uuid: &str, user_uuid: &UserId,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
conn: &mut DbConn, conn: &mut DbConn,
) -> Option<(bool, bool)> { ) -> Option<(bool, bool)> {
@ -623,7 +631,7 @@ impl Cipher {
Some((read_only, hide_passwords)) Some((read_only, hide_passwords))
} }
async fn get_user_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> { async fn get_user_collections_access_flags(&self, user_uuid: &UserId, conn: &mut DbConn) -> Vec<(bool, bool)> {
db_run! {conn: { db_run! {conn: {
// Check whether this cipher is in any collections accessible to the // Check whether this cipher is in any collections accessible to the
// user. If so, retrieve the access flags for each collection. // user. If so, retrieve the access flags for each collection.
@ -640,7 +648,7 @@ impl Cipher {
}} }}
} }
async fn get_group_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> { async fn get_group_collections_access_flags(&self, user_uuid: &UserId, conn: &mut DbConn) -> Vec<(bool, bool)> {
if !CONFIG.org_groups_enabled() { if !CONFIG.org_groups_enabled() {
return Vec::new(); return Vec::new();
} }
@ -666,43 +674,43 @@ impl Cipher {
}} }}
} }
pub async fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_write_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
match self.get_access_restrictions(user_uuid, None, conn).await { match self.get_access_restrictions(user_uuid, None, conn).await {
Some((read_only, _hide_passwords)) => !read_only, Some((read_only, _hide_passwords)) => !read_only,
None => false, None => false,
} }
} }
pub async fn is_accessible_to_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
self.get_access_restrictions(user_uuid, None, conn).await.is_some() self.get_access_restrictions(user_uuid, None, conn).await.is_some()
} }
// Returns whether this cipher is a favorite of the specified user. // Returns whether this cipher is a favorite of the specified user.
pub async fn is_favorite(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_favorite(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
Favorite::is_favorite(&self.uuid, user_uuid, conn).await Favorite::is_favorite(&self.uuid, user_uuid, conn).await
} }
// Sets whether this cipher is a favorite of the specified user. // Sets whether this cipher is a favorite of the specified user.
pub async fn set_favorite(&self, favorite: Option<bool>, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn set_favorite(&self, favorite: Option<bool>, user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
match favorite { match favorite {
None => Ok(()), // No change requested. None => Ok(()), // No change requested.
Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await, Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await,
} }
} }
pub async fn get_folder_uuid(&self, user_uuid: &str, conn: &mut DbConn) -> Option<String> { pub async fn get_folder_uuid(&self, user_uuid: &UserId, conn: &mut DbConn) -> Option<FolderId> {
db_run! {conn: { db_run! {conn: {
folders_ciphers::table folders_ciphers::table
.inner_join(folders::table) .inner_join(folders::table)
.filter(folders::user_uuid.eq(&user_uuid)) .filter(folders::user_uuid.eq(&user_uuid))
.filter(folders_ciphers::cipher_uuid.eq(&self.uuid)) .filter(folders_ciphers::cipher_uuid.eq(&self.uuid))
.select(folders_ciphers::folder_uuid) .select(folders_ciphers::folder_uuid)
.first::<String>(conn) .first::<FolderId>(conn)
.ok() .ok()
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &CipherId, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::uuid.eq(uuid)) .filter(ciphers::uuid.eq(uuid))
@ -712,7 +720,11 @@ impl Cipher {
}} }}
} }
pub async fn find_by_uuid_and_org(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_org(
cipher_uuid: &CipherId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::uuid.eq(cipher_uuid)) .filter(ciphers::uuid.eq(cipher_uuid))
@ -735,7 +747,7 @@ impl Cipher {
// true, then the non-interesting ciphers will not be returned. As a // true, then the non-interesting ciphers will not be returned. As a
// result, those ciphers will not appear in "My Vault" for the org // result, those ciphers will not appear in "My Vault" for the org
// owner/admin, but they can still be accessed via the org vault view. // owner/admin, but they can still be accessed via the org vault view.
pub async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, visible_only: bool, conn: &mut DbConn) -> Vec<Self> {
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! {conn: { db_run! {conn: {
let mut query = ciphers::table let mut query = ciphers::table
@ -745,7 +757,7 @@ impl Cipher {
.left_join(users_organizations::table.on( .left_join(users_organizations::table.on(
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()) ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
.and(users_organizations::user_uuid.eq(user_uuid)) .and(users_organizations::user_uuid.eq(user_uuid))
.and(users_organizations::status.eq(UserOrgStatus::Confirmed as i32)) .and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
)) ))
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
@ -772,7 +784,7 @@ impl Cipher {
if !visible_only { if !visible_only {
query = query.or_filter( query = query.or_filter(
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin/owner users_organizations::atype.le(MembershipType::Admin as i32) // Org admin/owner
); );
} }
@ -790,7 +802,7 @@ impl Cipher {
.left_join(users_organizations::table.on( .left_join(users_organizations::table.on(
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()) ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
.and(users_organizations::user_uuid.eq(user_uuid)) .and(users_organizations::user_uuid.eq(user_uuid))
.and(users_organizations::status.eq(UserOrgStatus::Confirmed as i32)) .and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
)) ))
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
@ -804,7 +816,7 @@ impl Cipher {
if !visible_only { if !visible_only {
query = query.or_filter( query = query.or_filter(
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin/owner users_organizations::atype.le(MembershipType::Admin as i32) // Org admin/owner
); );
} }
@ -817,12 +829,12 @@ impl Cipher {
} }
// Find all ciphers visible to the specified user. // Find all ciphers visible to the specified user.
pub async fn find_by_user_visible(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user_visible(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
Self::find_by_user(user_uuid, true, conn).await Self::find_by_user(user_uuid, true, conn).await
} }
// Find all ciphers directly owned by the specified user. // Find all ciphers directly owned by the specified user.
pub async fn find_owned_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_owned_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter( .filter(
@ -833,7 +845,7 @@ impl Cipher {
}} }}
} }
pub async fn count_owned_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_owned_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::user_uuid.eq(user_uuid)) .filter(ciphers::user_uuid.eq(user_uuid))
@ -844,7 +856,7 @@ impl Cipher {
}} }}
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::organization_uuid.eq(org_uuid)) .filter(ciphers::organization_uuid.eq(org_uuid))
@ -852,7 +864,7 @@ impl Cipher {
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::organization_uuid.eq(org_uuid)) .filter(ciphers::organization_uuid.eq(org_uuid))
@ -863,7 +875,7 @@ impl Cipher {
}} }}
} }
pub async fn find_by_folder(folder_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_folder(folder_uuid: &FolderId, conn: &mut DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
folders_ciphers::table.inner_join(ciphers::table) folders_ciphers::table.inner_join(ciphers::table)
.filter(folders_ciphers::folder_uuid.eq(folder_uuid)) .filter(folders_ciphers::folder_uuid.eq(folder_uuid))
@ -881,7 +893,7 @@ impl Cipher {
}} }}
} }
pub async fn get_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<String> { pub async fn get_collections(&self, user_uuid: UserId, conn: &mut DbConn) -> Vec<CollectionId> {
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
@ -891,11 +903,11 @@ impl Cipher {
)) ))
.left_join(users_organizations::table.on( .left_join(users_organizations::table.on(
users_organizations::org_uuid.eq(collections::org_uuid) users_organizations::org_uuid.eq(collections::org_uuid)
.and(users_organizations::user_uuid.eq(user_id.clone())) .and(users_organizations::user_uuid.eq(user_uuid.clone()))
)) ))
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_id.clone())) .and(users_collections::user_uuid.eq(user_uuid.clone()))
)) ))
.left_join(groups_users::table.on( .left_join(groups_users::table.on(
groups_users::users_organizations_uuid.eq(users_organizations::uuid) groups_users::users_organizations_uuid.eq(users_organizations::uuid)
@ -906,14 +918,14 @@ impl Cipher {
.and(collections_groups::groups_uuid.eq(groups::uuid)) .and(collections_groups::groups_uuid.eq(groups::uuid))
)) ))
.filter(users_organizations::access_all.eq(true) // User has access all .filter(users_organizations::access_all.eq(true) // User has access all
.or(users_collections::user_uuid.eq(user_id) // User has access to collection .or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
.and(users_collections::read_only.eq(false))) .and(users_collections::read_only.eq(false)))
.or(groups::access_all.eq(true)) // Access via groups .or(groups::access_all.eq(true)) // Access via groups
.or(collections_groups::collections_uuid.is_not_null() // Access via groups .or(collections_groups::collections_uuid.is_not_null() // Access via groups
.and(collections_groups::read_only.eq(false))) .and(collections_groups::read_only.eq(false)))
) )
.select(ciphers_collections::collection_uuid) .select(ciphers_collections::collection_uuid)
.load::<String>(conn).unwrap_or_default() .load::<CollectionId>(conn).unwrap_or_default()
}} }}
} else { } else {
db_run! {conn: { db_run! {conn: {
@ -924,23 +936,23 @@ impl Cipher {
)) ))
.inner_join(users_organizations::table.on( .inner_join(users_organizations::table.on(
users_organizations::org_uuid.eq(collections::org_uuid) users_organizations::org_uuid.eq(collections::org_uuid)
.and(users_organizations::user_uuid.eq(user_id.clone())) .and(users_organizations::user_uuid.eq(user_uuid.clone()))
)) ))
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_id.clone())) .and(users_collections::user_uuid.eq(user_uuid.clone()))
)) ))
.filter(users_organizations::access_all.eq(true) // User has access all .filter(users_organizations::access_all.eq(true) // User has access all
.or(users_collections::user_uuid.eq(user_id) // User has access to collection .or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
.and(users_collections::read_only.eq(false))) .and(users_collections::read_only.eq(false)))
) )
.select(ciphers_collections::collection_uuid) .select(ciphers_collections::collection_uuid)
.load::<String>(conn).unwrap_or_default() .load::<CollectionId>(conn).unwrap_or_default()
}} }}
} }
} }
pub async fn get_admin_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<String> { pub async fn get_admin_collections(&self, user_uuid: UserId, conn: &mut DbConn) -> Vec<CollectionId> {
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
@ -950,11 +962,11 @@ impl Cipher {
)) ))
.left_join(users_organizations::table.on( .left_join(users_organizations::table.on(
users_organizations::org_uuid.eq(collections::org_uuid) users_organizations::org_uuid.eq(collections::org_uuid)
.and(users_organizations::user_uuid.eq(user_id.clone())) .and(users_organizations::user_uuid.eq(user_uuid.clone()))
)) ))
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_id.clone())) .and(users_collections::user_uuid.eq(user_uuid.clone()))
)) ))
.left_join(groups_users::table.on( .left_join(groups_users::table.on(
groups_users::users_organizations_uuid.eq(users_organizations::uuid) groups_users::users_organizations_uuid.eq(users_organizations::uuid)
@ -965,15 +977,15 @@ impl Cipher {
.and(collections_groups::groups_uuid.eq(groups::uuid)) .and(collections_groups::groups_uuid.eq(groups::uuid))
)) ))
.filter(users_organizations::access_all.eq(true) // User has access all .filter(users_organizations::access_all.eq(true) // User has access all
.or(users_collections::user_uuid.eq(user_id) // User has access to collection .or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
.and(users_collections::read_only.eq(false))) .and(users_collections::read_only.eq(false)))
.or(groups::access_all.eq(true)) // Access via groups .or(groups::access_all.eq(true)) // Access via groups
.or(collections_groups::collections_uuid.is_not_null() // Access via groups .or(collections_groups::collections_uuid.is_not_null() // Access via groups
.and(collections_groups::read_only.eq(false))) .and(collections_groups::read_only.eq(false)))
.or(users_organizations::atype.le(UserOrgType::Admin as i32)) // User is admin or owner .or(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
) )
.select(ciphers_collections::collection_uuid) .select(ciphers_collections::collection_uuid)
.load::<String>(conn).unwrap_or_default() .load::<CollectionId>(conn).unwrap_or_default()
}} }}
} else { } else {
db_run! {conn: { db_run! {conn: {
@ -984,26 +996,29 @@ impl Cipher {
)) ))
.inner_join(users_organizations::table.on( .inner_join(users_organizations::table.on(
users_organizations::org_uuid.eq(collections::org_uuid) users_organizations::org_uuid.eq(collections::org_uuid)
.and(users_organizations::user_uuid.eq(user_id.clone())) .and(users_organizations::user_uuid.eq(user_uuid.clone()))
)) ))
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_id.clone())) .and(users_collections::user_uuid.eq(user_uuid.clone()))
)) ))
.filter(users_organizations::access_all.eq(true) // User has access all .filter(users_organizations::access_all.eq(true) // User has access all
.or(users_collections::user_uuid.eq(user_id) // User has access to collection .or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
.and(users_collections::read_only.eq(false))) .and(users_collections::read_only.eq(false)))
.or(users_organizations::atype.le(UserOrgType::Admin as i32)) // User is admin or owner .or(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
) )
.select(ciphers_collections::collection_uuid) .select(ciphers_collections::collection_uuid)
.load::<String>(conn).unwrap_or_default() .load::<CollectionId>(conn).unwrap_or_default()
}} }}
} }
} }
/// Return a Vec with (cipher_uuid, collection_uuid) /// Return a Vec with (cipher_uuid, collection_uuid)
/// This is used during a full sync so we only need one query for all collections accessible. /// This is used during a full sync so we only need one query for all collections accessible.
pub async fn get_collections_with_cipher_by_user(user_id: String, conn: &mut DbConn) -> Vec<(String, String)> { pub async fn get_collections_with_cipher_by_user(
user_uuid: UserId,
conn: &mut DbConn,
) -> Vec<(CipherId, CollectionId)> {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
.inner_join(collections::table.on( .inner_join(collections::table.on(
@ -1011,12 +1026,12 @@ impl Cipher {
)) ))
.inner_join(users_organizations::table.on( .inner_join(users_organizations::table.on(
users_organizations::org_uuid.eq(collections::org_uuid).and( users_organizations::org_uuid.eq(collections::org_uuid).and(
users_organizations::user_uuid.eq(user_id.clone()) users_organizations::user_uuid.eq(user_uuid.clone())
) )
)) ))
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid).and( users_collections::collection_uuid.eq(ciphers_collections::collection_uuid).and(
users_collections::user_uuid.eq(user_id.clone()) users_collections::user_uuid.eq(user_uuid.clone())
) )
)) ))
.left_join(groups_users::table.on( .left_join(groups_users::table.on(
@ -1030,14 +1045,32 @@ impl Cipher {
collections_groups::groups_uuid.eq(groups::uuid) collections_groups::groups_uuid.eq(groups::uuid)
) )
)) ))
.or_filter(users_collections::user_uuid.eq(user_id)) // User has access to collection .or_filter(users_collections::user_uuid.eq(user_uuid)) // User has access to collection
.or_filter(users_organizations::access_all.eq(true)) // User has access all .or_filter(users_organizations::access_all.eq(true)) // User has access all
.or_filter(users_organizations::atype.le(UserOrgType::Admin as i32)) // User is admin or owner .or_filter(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
.or_filter(groups::access_all.eq(true)) //Access via group .or_filter(groups::access_all.eq(true)) //Access via group
.or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group .or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group
.select(ciphers_collections::all_columns) .select(ciphers_collections::all_columns)
.distinct() .distinct()
.load::<(String, String)>(conn).unwrap_or_default() .load::<(CipherId, CollectionId)>(conn).unwrap_or_default()
}} }}
} }
} }
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct CipherId(String);

Datei anzeigen

@ -1,15 +1,20 @@
use derive_more::{AsRef, Deref, Display, From};
use serde_json::Value; use serde_json::Value;
use super::{CollectionGroup, GroupUser, User, UserOrgStatus, UserOrgType, UserOrganization}; use super::{
CipherId, CollectionGroup, GroupUser, Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId,
User, UserId,
};
use crate::CONFIG; use crate::CONFIG;
use macros::UuidFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = collections)] #[diesel(table_name = collections)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Collection { pub struct Collection {
pub uuid: String, pub uuid: CollectionId,
pub org_uuid: String, pub org_uuid: OrganizationId,
pub name: String, pub name: String,
pub external_id: Option<String>, pub external_id: Option<String>,
} }
@ -18,8 +23,8 @@ db_object! {
#[diesel(table_name = users_collections)] #[diesel(table_name = users_collections)]
#[diesel(primary_key(user_uuid, collection_uuid))] #[diesel(primary_key(user_uuid, collection_uuid))]
pub struct CollectionUser { pub struct CollectionUser {
pub user_uuid: String, pub user_uuid: UserId,
pub collection_uuid: String, pub collection_uuid: CollectionId,
pub read_only: bool, pub read_only: bool,
pub hide_passwords: bool, pub hide_passwords: bool,
} }
@ -28,16 +33,16 @@ db_object! {
#[diesel(table_name = ciphers_collections)] #[diesel(table_name = ciphers_collections)]
#[diesel(primary_key(cipher_uuid, collection_uuid))] #[diesel(primary_key(cipher_uuid, collection_uuid))]
pub struct CollectionCipher { pub struct CollectionCipher {
pub cipher_uuid: String, pub cipher_uuid: CipherId,
pub collection_uuid: String, pub collection_uuid: CollectionId,
} }
} }
/// Local methods /// Local methods
impl Collection { impl Collection {
pub fn new(org_uuid: String, name: String, external_id: Option<String>) -> Self { pub fn new(org_uuid: OrganizationId, name: String, external_id: Option<String>) -> Self {
let mut new_model = Self { let mut new_model = Self {
uuid: crate::util::get_uuid(), uuid: CollectionId(crate::util::get_uuid()),
org_uuid, org_uuid,
name, name,
external_id: None, external_id: None,
@ -74,18 +79,18 @@ impl Collection {
pub async fn to_json_details( pub async fn to_json_details(
&self, &self,
user_uuid: &str, user_uuid: &UserId,
cipher_sync_data: Option<&crate::api::core::CipherSyncData>, cipher_sync_data: Option<&crate::api::core::CipherSyncData>,
conn: &mut DbConn, conn: &mut DbConn,
) -> Value { ) -> Value {
let (read_only, hide_passwords, can_manage) = if let Some(cipher_sync_data) = cipher_sync_data { let (read_only, hide_passwords, can_manage) = if let Some(cipher_sync_data) = cipher_sync_data {
match cipher_sync_data.user_organizations.get(&self.org_uuid) { match cipher_sync_data.members.get(&self.org_uuid) {
// Only for Manager types Bitwarden returns true for the can_manage option // Only for Manager types Bitwarden returns true for the can_manage option
// Owners and Admins always have true // Owners and Admins always have true
Some(uo) if uo.has_full_access() => (false, false, uo.atype >= UserOrgType::Manager), Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager),
Some(uo) => { Some(m) => {
// Only let a manager manage collections when the have full read/write access // Only let a manager manage collections when the have full read/write access
let is_manager = uo.atype == UserOrgType::Manager; let is_manager = m.atype == MembershipType::Manager;
if let Some(uc) = cipher_sync_data.user_collections.get(&self.uuid) { if let Some(uc) = cipher_sync_data.user_collections.get(&self.uuid) {
(uc.read_only, uc.hide_passwords, is_manager && !uc.read_only && !uc.hide_passwords) (uc.read_only, uc.hide_passwords, is_manager && !uc.read_only && !uc.hide_passwords)
} else if let Some(cg) = cipher_sync_data.user_collections_groups.get(&self.uuid) { } else if let Some(cg) = cipher_sync_data.user_collections_groups.get(&self.uuid) {
@ -97,10 +102,10 @@ impl Collection {
_ => (true, true, false), _ => (true, true, false),
} }
} else { } else {
match UserOrganization::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await { match Membership::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await {
Some(ou) if ou.has_full_access() => (false, false, ou.atype >= UserOrgType::Manager), Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager),
Some(ou) => { Some(m) => {
let is_manager = ou.atype == UserOrgType::Manager; let is_manager = m.atype == MembershipType::Manager;
let read_only = !self.is_writable_by_user(user_uuid, conn).await; let read_only = !self.is_writable_by_user(user_uuid, conn).await;
let hide_passwords = self.hide_passwords_for_user(user_uuid, conn).await; let hide_passwords = self.hide_passwords_for_user(user_uuid, conn).await;
(read_only, hide_passwords, is_manager && !read_only && !hide_passwords) (read_only, hide_passwords, is_manager && !read_only && !hide_passwords)
@ -121,13 +126,13 @@ impl Collection {
json_object json_object
} }
pub async fn can_access_collection(org_user: &UserOrganization, col_id: &str, conn: &mut DbConn) -> bool { pub async fn can_access_collection(member: &Membership, col_id: &CollectionId, conn: &mut DbConn) -> bool {
org_user.has_status(UserOrgStatus::Confirmed) member.has_status(MembershipStatus::Confirmed)
&& (org_user.has_full_access() && (member.has_full_access()
|| CollectionUser::has_access_to_collection_by_user(col_id, &org_user.user_uuid, conn).await || CollectionUser::has_access_to_collection_by_user(col_id, &member.user_uuid, conn).await
|| (CONFIG.org_groups_enabled() || (CONFIG.org_groups_enabled()
&& (GroupUser::has_full_access_by_member(&org_user.org_uuid, &org_user.uuid, conn).await && (GroupUser::has_full_access_by_member(&member.org_uuid, &member.uuid, conn).await
|| GroupUser::has_access_to_collection_by_member(col_id, &org_user.uuid, conn).await))) || GroupUser::has_access_to_collection_by_member(col_id, &member.uuid, conn).await)))
} }
} }
@ -185,7 +190,7 @@ impl Collection {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
for collection in Self::find_by_organization(org_uuid, conn).await { for collection in Self::find_by_organization(org_uuid, conn).await {
collection.delete(conn).await?; collection.delete(conn).await?;
} }
@ -193,12 +198,12 @@ impl Collection {
} }
pub async fn update_users_revision(&self, conn: &mut DbConn) { pub async fn update_users_revision(&self, conn: &mut DbConn) {
for user_org in UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await.iter() { for member in Membership::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await.iter() {
User::update_uuid_revision(&user_org.user_uuid, conn).await; User::update_uuid_revision(&member.user_uuid, conn).await;
} }
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &CollectionId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::uuid.eq(uuid)) .filter(collections::uuid.eq(uuid))
@ -208,7 +213,7 @@ impl Collection {
}} }}
} }
pub async fn find_by_user_uuid(user_uuid: String, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user_uuid(user_uuid: UserId, conn: &mut DbConn) -> Vec<Self> {
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
@ -234,7 +239,7 @@ impl Collection {
) )
)) ))
.filter( .filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(MembershipStatus::Confirmed as i32)
) )
.filter( .filter(
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
@ -265,7 +270,7 @@ impl Collection {
) )
)) ))
.filter( .filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(MembershipStatus::Confirmed as i32)
) )
.filter( .filter(
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
@ -279,15 +284,19 @@ impl Collection {
} }
} }
pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization_and_user_uuid(
org_uuid: &OrganizationId,
user_uuid: &UserId,
conn: &mut DbConn,
) -> Vec<Self> {
Self::find_by_user_uuid(user_uuid.to_owned(), conn) Self::find_by_user_uuid(user_uuid.to_owned(), conn)
.await .await
.into_iter() .into_iter()
.filter(|c| c.org_uuid == org_uuid) .filter(|c| &c.org_uuid == org_uuid)
.collect() .collect()
} }
pub async fn find_by_organization(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
@ -297,7 +306,7 @@ impl Collection {
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
@ -308,7 +317,11 @@ impl Collection {
}} }}
} }
pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_org(
uuid: &CollectionId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::uuid.eq(uuid)) .filter(collections::uuid.eq(uuid))
@ -320,7 +333,7 @@ impl Collection {
}} }}
} }
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: String, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_user(uuid: &CollectionId, user_uuid: UserId, conn: &mut DbConn) -> Option<Self> {
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
@ -349,7 +362,7 @@ impl Collection {
.filter( .filter(
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
users_organizations::access_all.eq(true).or( // access_all in Organization users_organizations::access_all.eq(true).or( // access_all in Organization
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner
)).or( )).or(
groups::access_all.eq(true) // access_all in groups groups::access_all.eq(true) // access_all in groups
).or( // access via groups ).or( // access via groups
@ -378,7 +391,7 @@ impl Collection {
.filter( .filter(
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
users_organizations::access_all.eq(true).or( // access_all in Organization users_organizations::access_all.eq(true).or( // access_all in Organization
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner
)) ))
).select(collections::all_columns) ).select(collections::all_columns)
.first::<CollectionDb>(conn).ok() .first::<CollectionDb>(conn).ok()
@ -387,7 +400,7 @@ impl Collection {
} }
} }
pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_writable_by_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
let user_uuid = user_uuid.to_string(); let user_uuid = user_uuid.to_string();
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! { conn: { db_run! { conn: {
@ -411,7 +424,7 @@ impl Collection {
collections_groups::groups_uuid.eq(groups_users::groups_uuid) collections_groups::groups_uuid.eq(groups_users::groups_uuid)
.and(collections_groups::collections_uuid.eq(collections::uuid)) .and(collections_groups::collections_uuid.eq(collections::uuid))
)) ))
.filter(users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner .filter(users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner
.or(users_organizations::access_all.eq(true)) // access_all via membership .or(users_organizations::access_all.eq(true)) // access_all via membership
.or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection .or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection
.and(users_collections::read_only.eq(false))) .and(users_collections::read_only.eq(false)))
@ -436,7 +449,7 @@ impl Collection {
users_collections::collection_uuid.eq(collections::uuid) users_collections::collection_uuid.eq(collections::uuid)
.and(users_collections::user_uuid.eq(user_uuid)) .and(users_collections::user_uuid.eq(user_uuid))
)) ))
.filter(users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner .filter(users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner
.or(users_organizations::access_all.eq(true)) // access_all via membership .or(users_organizations::access_all.eq(true)) // access_all via membership
.or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection .or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection
.and(users_collections::read_only.eq(false))) .and(users_collections::read_only.eq(false)))
@ -449,7 +462,7 @@ impl Collection {
} }
} }
pub async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn hide_passwords_for_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
let user_uuid = user_uuid.to_string(); let user_uuid = user_uuid.to_string();
db_run! { conn: { db_run! { conn: {
collections::table collections::table
@ -478,7 +491,7 @@ impl Collection {
.filter( .filter(
users_collections::collection_uuid.eq(&self.uuid).and(users_collections::hide_passwords.eq(true)).or(// Directly accessed collection users_collections::collection_uuid.eq(&self.uuid).and(users_collections::hide_passwords.eq(true)).or(// Directly accessed collection
users_organizations::access_all.eq(true).or( // access_all in Organization users_organizations::access_all.eq(true).or( // access_all in Organization
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner
)).or( )).or(
groups::access_all.eq(true) // access_all in groups groups::access_all.eq(true) // access_all in groups
).or( // access via groups ).or( // access via groups
@ -498,7 +511,11 @@ impl Collection {
/// Database methods /// Database methods
impl CollectionUser { impl CollectionUser {
pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization_and_user_uuid(
org_uuid: &OrganizationId,
user_uuid: &UserId,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::user_uuid.eq(user_uuid)) .filter(users_collections::user_uuid.eq(user_uuid))
@ -511,11 +528,11 @@ impl CollectionUser {
}} }}
} }
pub async fn find_by_organization_swap_user_uuid_with_org_user_uuid( pub async fn find_by_organization_swap_user_uuid_with_member_uuid(
org_uuid: &str, org_uuid: &OrganizationId,
conn: &mut DbConn, conn: &mut DbConn,
) -> Vec<Self> { ) -> Vec<CollectionMembership> {
db_run! { conn: { let col_users = db_run! { conn: {
users_collections::table users_collections::table
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid))) .inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
@ -524,12 +541,13 @@ impl CollectionUser {
.load::<CollectionUserDb>(conn) .load::<CollectionUserDb>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db() .from_db()
}} }};
col_users.into_iter().map(|c| c.into()).collect()
} }
pub async fn save( pub async fn save(
user_uuid: &str, user_uuid: &UserId,
collection_uuid: &str, collection_uuid: &CollectionId,
read_only: bool, read_only: bool,
hide_passwords: bool, hide_passwords: bool,
conn: &mut DbConn, conn: &mut DbConn,
@ -599,7 +617,7 @@ impl CollectionUser {
}} }}
} }
pub async fn find_by_collection(collection_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid)) .filter(users_collections::collection_uuid.eq(collection_uuid))
@ -610,11 +628,11 @@ impl CollectionUser {
}} }}
} }
pub async fn find_by_collection_swap_user_uuid_with_org_user_uuid( pub async fn find_by_collection_swap_user_uuid_with_member_uuid(
collection_uuid: &str, collection_uuid: &CollectionId,
conn: &mut DbConn, conn: &mut DbConn,
) -> Vec<Self> { ) -> Vec<CollectionMembership> {
db_run! { conn: { let col_users = db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid)) .filter(users_collections::collection_uuid.eq(collection_uuid))
.inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid))) .inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid)))
@ -622,12 +640,13 @@ impl CollectionUser {
.load::<CollectionUserDb>(conn) .load::<CollectionUserDb>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db() .from_db()
}} }};
col_users.into_iter().map(|c| c.into()).collect()
} }
pub async fn find_by_collection_and_user( pub async fn find_by_collection_and_user(
collection_uuid: &str, collection_uuid: &CollectionId,
user_uuid: &str, user_uuid: &UserId,
conn: &mut DbConn, conn: &mut DbConn,
) -> Option<Self> { ) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
@ -641,7 +660,7 @@ impl CollectionUser {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::user_uuid.eq(user_uuid)) .filter(users_collections::user_uuid.eq(user_uuid))
@ -652,7 +671,7 @@ impl CollectionUser {
}} }}
} }
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
for collection in CollectionUser::find_by_collection(collection_uuid, conn).await.iter() { for collection in CollectionUser::find_by_collection(collection_uuid, conn).await.iter() {
User::update_uuid_revision(&collection.user_uuid, conn).await; User::update_uuid_revision(&collection.user_uuid, conn).await;
} }
@ -664,7 +683,11 @@ impl CollectionUser {
}} }}
} }
pub async fn delete_all_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user_and_org(
user_uuid: &UserId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> EmptyResult {
let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await; let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await;
db_run! { conn: { db_run! { conn: {
@ -680,14 +703,18 @@ impl CollectionUser {
}} }}
} }
pub async fn has_access_to_collection_by_user(col_id: &str, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn has_access_to_collection_by_user(
col_id: &CollectionId,
user_uuid: &UserId,
conn: &mut DbConn,
) -> bool {
Self::find_by_collection_and_user(col_id, user_uuid, conn).await.is_some() Self::find_by_collection_and_user(col_id, user_uuid, conn).await.is_some()
} }
} }
/// Database methods /// Database methods
impl CollectionCipher { impl CollectionCipher {
pub async fn save(cipher_uuid: &str, collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn save(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
Self::update_users_revision(collection_uuid, conn).await; Self::update_users_revision(collection_uuid, conn).await;
db_run! { conn: db_run! { conn:
@ -717,7 +744,7 @@ impl CollectionCipher {
} }
} }
pub async fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
Self::update_users_revision(collection_uuid, conn).await; Self::update_users_revision(collection_uuid, conn).await;
db_run! { conn: { db_run! { conn: {
@ -731,7 +758,7 @@ impl CollectionCipher {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))) diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
@ -739,7 +766,7 @@ impl CollectionCipher {
}} }}
} }
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid))) diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
.execute(conn) .execute(conn)
@ -747,9 +774,60 @@ impl CollectionCipher {
}} }}
} }
pub async fn update_users_revision(collection_uuid: &str, conn: &mut DbConn) { pub async fn update_users_revision(collection_uuid: &CollectionId, conn: &mut DbConn) {
if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await { if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await {
collection.update_users_revision(conn).await; collection.update_users_revision(conn).await;
} }
} }
} }
// Added in case we need the membership_uuid instead of the user_uuid
pub struct CollectionMembership {
pub membership_uuid: MembershipId,
pub collection_uuid: CollectionId,
pub read_only: bool,
pub hide_passwords: bool,
}
impl CollectionMembership {
pub fn to_json_details_for_user(&self, membership_type: i32) -> Value {
json!({
"id": self.membership_uuid,
"readOnly": self.read_only,
"hidePasswords": self.hide_passwords,
"manage": membership_type >= MembershipType::Admin
|| (membership_type == MembershipType::Manager
&& !self.read_only
&& !self.hide_passwords),
})
}
}
impl From<CollectionUser> for CollectionMembership {
fn from(c: CollectionUser) -> Self {
Self {
membership_uuid: c.user_uuid.to_string().into(),
collection_uuid: c.collection_uuid,
read_only: c.read_only,
hide_passwords: c.hide_passwords,
}
}
}
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct CollectionId(String);

Datei anzeigen

@ -1,7 +1,9 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use derive_more::{Display, From};
use super::UserId;
use crate::{crypto, CONFIG}; use crate::{crypto, CONFIG};
use core::fmt; use macros::IdFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -9,11 +11,11 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid, user_uuid))] #[diesel(primary_key(uuid, user_uuid))]
pub struct Device { pub struct Device {
pub uuid: String, pub uuid: DeviceId,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
pub user_uuid: String, pub user_uuid: UserId,
pub name: String, pub name: String,
pub atype: i32, // https://github.com/bitwarden/server/blob/dcc199bcce4aa2d5621f6fab80f1b49d8b143418/src/Core/Enums/DeviceType.cs pub atype: i32, // https://github.com/bitwarden/server/blob/dcc199bcce4aa2d5621f6fab80f1b49d8b143418/src/Core/Enums/DeviceType.cs
@ -28,7 +30,7 @@ db_object! {
/// Local methods /// Local methods
impl Device { impl Device {
pub fn new(uuid: String, user_uuid: String, name: String, atype: i32) -> Self { pub fn new(uuid: DeviceId, user_uuid: UserId, name: String, atype: i32) -> Self {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
Self { Self {
@ -75,12 +77,12 @@ impl Device {
// Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients // Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients
// Because these might get used in the future, and they are added by the Bitwarden Server, lets keep it, but then commented out // Because these might get used in the future, and they are added by the Bitwarden Server, lets keep it, but then commented out
// --- // ---
// fn arg: orgs: Vec<super::UserOrganization>, // fn arg: members: Vec<super::Membership>,
// --- // ---
// let orgowner: Vec<_> = orgs.iter().filter(|o| o.atype == 0).map(|o| o.org_uuid.clone()).collect(); // let orgowner: Vec<_> = members.iter().filter(|m| m.atype == 0).map(|o| o.org_uuid.clone()).collect();
// let orgadmin: Vec<_> = orgs.iter().filter(|o| o.atype == 1).map(|o| o.org_uuid.clone()).collect(); // let orgadmin: Vec<_> = members.iter().filter(|m| m.atype == 1).map(|o| o.org_uuid.clone()).collect();
// let orguser: Vec<_> = orgs.iter().filter(|o| o.atype == 2).map(|o| o.org_uuid.clone()).collect(); // let orguser: Vec<_> = members.iter().filter(|m| m.atype == 2).map(|o| o.org_uuid.clone()).collect();
// let orgmanager: Vec<_> = orgs.iter().filter(|o| o.atype == 3).map(|o| o.org_uuid.clone()).collect(); // let orgmanager: Vec<_> = members.iter().filter(|m| m.atype == 3).map(|o| o.org_uuid.clone()).collect();
// Create the JWT claims struct, to send to the client // Create the JWT claims struct, to send to the client
use crate::auth::{encode_jwt, LoginJwtClaims, DEFAULT_VALIDITY, JWT_LOGIN_ISSUER}; use crate::auth::{encode_jwt, LoginJwtClaims, DEFAULT_VALIDITY, JWT_LOGIN_ISSUER};
@ -150,7 +152,7 @@ impl Device {
} }
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(devices::table.filter(devices::user_uuid.eq(user_uuid))) diesel::delete(devices::table.filter(devices::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)
@ -158,7 +160,7 @@ impl Device {
}} }}
} }
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_user(uuid: &DeviceId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::uuid.eq(uuid)) .filter(devices::uuid.eq(uuid))
@ -169,7 +171,7 @@ impl Device {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
@ -179,7 +181,7 @@ impl Device {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &DeviceId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::uuid.eq(uuid)) .filter(devices::uuid.eq(uuid))
@ -189,7 +191,7 @@ impl Device {
}} }}
} }
pub async fn clear_push_token_by_uuid(uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn clear_push_token_by_uuid(uuid: &DeviceId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::update(devices::table) diesel::update(devices::table)
.filter(devices::uuid.eq(uuid)) .filter(devices::uuid.eq(uuid))
@ -208,7 +210,7 @@ impl Device {
}} }}
} }
pub async fn find_latest_active_by_user(user_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_latest_active_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
@ -219,7 +221,7 @@ impl Device {
}} }}
} }
pub async fn find_push_devices_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_push_devices_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
@ -230,7 +232,7 @@ impl Device {
}} }}
} }
pub async fn check_user_has_push_device(user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn check_user_has_push_device(user_uuid: &UserId, conn: &mut DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
@ -243,68 +245,62 @@ impl Device {
} }
} }
#[derive(Display)]
pub enum DeviceType { pub enum DeviceType {
#[display("Android")]
Android = 0, Android = 0,
#[display("iOS")]
Ios = 1, Ios = 1,
#[display("Chrome Extension")]
ChromeExtension = 2, ChromeExtension = 2,
#[display("Firefox Extension")]
FirefoxExtension = 3, FirefoxExtension = 3,
#[display("Opera Extension")]
OperaExtension = 4, OperaExtension = 4,
#[display("Edge Extension")]
EdgeExtension = 5, EdgeExtension = 5,
#[display("Windows")]
WindowsDesktop = 6, WindowsDesktop = 6,
#[display("macOS")]
MacOsDesktop = 7, MacOsDesktop = 7,
#[display("Linux")]
LinuxDesktop = 8, LinuxDesktop = 8,
#[display("Chrome")]
ChromeBrowser = 9, ChromeBrowser = 9,
#[display("Firefox")]
FirefoxBrowser = 10, FirefoxBrowser = 10,
#[display("Opera")]
OperaBrowser = 11, OperaBrowser = 11,
#[display("Edge")]
EdgeBrowser = 12, EdgeBrowser = 12,
#[display("Internet Explorer")]
IEBrowser = 13, IEBrowser = 13,
#[display("Unknown Browser")]
UnknownBrowser = 14, UnknownBrowser = 14,
#[display("Android")]
AndroidAmazon = 15, AndroidAmazon = 15,
#[display("UWP")]
Uwp = 16, Uwp = 16,
#[display("Safari")]
SafariBrowser = 17, SafariBrowser = 17,
#[display("Vivaldi")]
VivaldiBrowser = 18, VivaldiBrowser = 18,
#[display("Vivaldi Extension")]
VivaldiExtension = 19, VivaldiExtension = 19,
#[display("Safari Extension")]
SafariExtension = 20, SafariExtension = 20,
#[display("SDK")]
Sdk = 21, Sdk = 21,
#[display("Server")]
Server = 22, Server = 22,
#[display("Windows CLI")]
WindowsCLI = 23, WindowsCLI = 23,
#[display("macOS CLI")]
MacOsCLI = 24, MacOsCLI = 24,
#[display("Linux CLI")]
LinuxCLI = 25, LinuxCLI = 25,
} }
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DeviceType::Android => write!(f, "Android"),
DeviceType::Ios => write!(f, "iOS"),
DeviceType::ChromeExtension => write!(f, "Chrome Extension"),
DeviceType::FirefoxExtension => write!(f, "Firefox Extension"),
DeviceType::OperaExtension => write!(f, "Opera Extension"),
DeviceType::EdgeExtension => write!(f, "Edge Extension"),
DeviceType::WindowsDesktop => write!(f, "Windows"),
DeviceType::MacOsDesktop => write!(f, "macOS"),
DeviceType::LinuxDesktop => write!(f, "Linux"),
DeviceType::ChromeBrowser => write!(f, "Chrome"),
DeviceType::FirefoxBrowser => write!(f, "Firefox"),
DeviceType::OperaBrowser => write!(f, "Opera"),
DeviceType::EdgeBrowser => write!(f, "Edge"),
DeviceType::IEBrowser => write!(f, "Internet Explorer"),
DeviceType::UnknownBrowser => write!(f, "Unknown Browser"),
DeviceType::AndroidAmazon => write!(f, "Android"),
DeviceType::Uwp => write!(f, "UWP"),
DeviceType::SafariBrowser => write!(f, "Safari"),
DeviceType::VivaldiBrowser => write!(f, "Vivaldi"),
DeviceType::VivaldiExtension => write!(f, "Vivaldi Extension"),
DeviceType::SafariExtension => write!(f, "Safari Extension"),
DeviceType::Sdk => write!(f, "SDK"),
DeviceType::Server => write!(f, "Server"),
DeviceType::WindowsCLI => write!(f, "Windows CLI"),
DeviceType::MacOsCLI => write!(f, "macOS CLI"),
DeviceType::LinuxCLI => write!(f, "Linux CLI"),
}
}
}
impl DeviceType { impl DeviceType {
pub fn from_i32(value: i32) -> DeviceType { pub fn from_i32(value: i32) -> DeviceType {
match value { match value {
@ -338,3 +334,8 @@ impl DeviceType {
} }
} }
} }
#[derive(
Clone, Debug, DieselNewType, Display, From, FromForm, Hash, PartialEq, Eq, Serialize, Deserialize, IdFromParam,
)]
pub struct DeviceId(String);

Datei anzeigen

@ -1,9 +1,10 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use derive_more::{AsRef, Deref, Display, From};
use serde_json::Value; use serde_json::Value;
use super::{User, UserId};
use crate::{api::EmptyResult, db::DbConn, error::MapResult}; use crate::{api::EmptyResult, db::DbConn, error::MapResult};
use macros::UuidFromParam;
use super::User;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -11,9 +12,9 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct EmergencyAccess { pub struct EmergencyAccess {
pub uuid: String, pub uuid: EmergencyAccessId,
pub grantor_uuid: String, pub grantor_uuid: UserId,
pub grantee_uuid: Option<String>, pub grantee_uuid: Option<UserId>,
pub email: Option<String>, pub email: Option<String>,
pub key_encrypted: Option<String>, pub key_encrypted: Option<String>,
pub atype: i32, //EmergencyAccessType pub atype: i32, //EmergencyAccessType
@ -29,11 +30,11 @@ db_object! {
// Local methods // Local methods
impl EmergencyAccess { impl EmergencyAccess {
pub fn new(grantor_uuid: String, email: String, status: i32, atype: i32, wait_time_days: i32) -> Self { pub fn new(grantor_uuid: UserId, email: String, status: i32, atype: i32, wait_time_days: i32) -> Self {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
Self { Self {
uuid: crate::util::get_uuid(), uuid: EmergencyAccessId(crate::util::get_uuid()),
grantor_uuid, grantor_uuid,
grantee_uuid: None, grantee_uuid: None,
email: Some(email), email: Some(email),
@ -82,7 +83,7 @@ impl EmergencyAccess {
} }
pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Option<Value> { pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Option<Value> {
let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() { let grantee_user = if let Some(grantee_uuid) = &self.grantee_uuid {
User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.") User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
} else if let Some(email) = self.email.as_deref() { } else if let Some(email) = self.email.as_deref() {
match User::find_by_mail(email, conn).await { match User::find_by_mail(email, conn).await {
@ -211,7 +212,7 @@ impl EmergencyAccess {
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await { for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await {
ea.delete(conn).await?; ea.delete(conn).await?;
} }
@ -239,8 +240,8 @@ impl EmergencyAccess {
} }
pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email( pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email(
grantor_uuid: &str, grantor_uuid: &UserId,
grantee_uuid: &str, grantee_uuid: &UserId,
email: &str, email: &str,
conn: &mut DbConn, conn: &mut DbConn,
) -> Option<Self> { ) -> Option<Self> {
@ -262,7 +263,11 @@ impl EmergencyAccess {
}} }}
} }
pub async fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_grantor_uuid(
uuid: &EmergencyAccessId,
grantor_uuid: &UserId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table emergency_access::table
.filter(emergency_access::uuid.eq(uuid)) .filter(emergency_access::uuid.eq(uuid))
@ -272,7 +277,11 @@ impl EmergencyAccess {
}} }}
} }
pub async fn find_by_uuid_and_grantee_uuid(uuid: &str, grantee_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_grantee_uuid(
uuid: &EmergencyAccessId,
grantee_uuid: &UserId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table emergency_access::table
.filter(emergency_access::uuid.eq(uuid)) .filter(emergency_access::uuid.eq(uuid))
@ -282,7 +291,11 @@ impl EmergencyAccess {
}} }}
} }
pub async fn find_by_uuid_and_grantee_email(uuid: &str, grantee_email: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_grantee_email(
uuid: &EmergencyAccessId,
grantee_email: &str,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table emergency_access::table
.filter(emergency_access::uuid.eq(uuid)) .filter(emergency_access::uuid.eq(uuid))
@ -292,7 +305,7 @@ impl EmergencyAccess {
}} }}
} }
pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_all_by_grantee_uuid(grantee_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table emergency_access::table
.filter(emergency_access::grantee_uuid.eq(grantee_uuid)) .filter(emergency_access::grantee_uuid.eq(grantee_uuid))
@ -319,7 +332,7 @@ impl EmergencyAccess {
}} }}
} }
pub async fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_all_by_grantor_uuid(grantor_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table emergency_access::table
.filter(emergency_access::grantor_uuid.eq(grantor_uuid)) .filter(emergency_access::grantor_uuid.eq(grantor_uuid))
@ -327,7 +340,12 @@ impl EmergencyAccess {
}} }}
} }
pub async fn accept_invite(&mut self, grantee_uuid: &str, grantee_email: &str, conn: &mut DbConn) -> EmptyResult { pub async fn accept_invite(
&mut self,
grantee_uuid: &UserId,
grantee_email: &str,
conn: &mut DbConn,
) -> EmptyResult {
if self.email.is_none() || self.email.as_ref().unwrap() != grantee_email { if self.email.is_none() || self.email.as_ref().unwrap() != grantee_email {
err!("User email does not match invite."); err!("User email does not match invite.");
} }
@ -337,10 +355,28 @@ impl EmergencyAccess {
} }
self.status = EmergencyAccessStatus::Accepted as i32; self.status = EmergencyAccessStatus::Accepted as i32;
self.grantee_uuid = Some(String::from(grantee_uuid)); self.grantee_uuid = Some(grantee_uuid.clone());
self.email = None; self.email = None;
self.save(conn).await self.save(conn).await
} }
} }
// endregion // endregion
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct EmergencyAccessId(String);

Datei anzeigen

@ -1,9 +1,9 @@
use crate::db::DbConn; use chrono::{NaiveDateTime, TimeDelta, Utc};
//use derive_more::{AsRef, Deref, Display, From};
use serde_json::Value; use serde_json::Value;
use crate::{api::EmptyResult, error::MapResult, CONFIG}; use super::{CipherId, CollectionId, GroupId, MembershipId, OrgPolicyId, OrganizationId, UserId};
use crate::{api::EmptyResult, db::DbConn, error::MapResult, CONFIG};
use chrono::{NaiveDateTime, TimeDelta, Utc};
// https://bitwarden.com/help/event-logs/ // https://bitwarden.com/help/event-logs/
@ -15,20 +15,20 @@ db_object! {
#[diesel(table_name = event)] #[diesel(table_name = event)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Event { pub struct Event {
pub uuid: String, pub uuid: EventId,
pub event_type: i32, // EventType pub event_type: i32, // EventType
pub user_uuid: Option<String>, pub user_uuid: Option<UserId>,
pub org_uuid: Option<String>, pub org_uuid: Option<OrganizationId>,
pub cipher_uuid: Option<String>, pub cipher_uuid: Option<CipherId>,
pub collection_uuid: Option<String>, pub collection_uuid: Option<CollectionId>,
pub group_uuid: Option<String>, pub group_uuid: Option<GroupId>,
pub org_user_uuid: Option<String>, pub org_user_uuid: Option<MembershipId>,
pub act_user_uuid: Option<String>, pub act_user_uuid: Option<UserId>,
// Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/DeviceType.cs // Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/DeviceType.cs
pub device_type: Option<i32>, pub device_type: Option<i32>,
pub ip_address: Option<String>, pub ip_address: Option<String>,
pub event_date: NaiveDateTime, pub event_date: NaiveDateTime,
pub policy_uuid: Option<String>, pub policy_uuid: Option<OrgPolicyId>,
pub provider_uuid: Option<String>, pub provider_uuid: Option<String>,
pub provider_user_uuid: Option<String>, pub provider_user_uuid: Option<String>,
pub provider_org_uuid: Option<String>, pub provider_org_uuid: Option<String>,
@ -128,7 +128,7 @@ impl Event {
}; };
Self { Self {
uuid: crate::util::get_uuid(), uuid: EventId(crate::util::get_uuid()),
event_type, event_type,
user_uuid: None, user_uuid: None,
org_uuid: None, org_uuid: None,
@ -246,7 +246,7 @@ impl Event {
/// ############## /// ##############
/// Custom Queries /// Custom Queries
pub async fn find_by_organization_uuid( pub async fn find_by_organization_uuid(
org_uuid: &str, org_uuid: &OrganizationId,
start: &NaiveDateTime, start: &NaiveDateTime,
end: &NaiveDateTime, end: &NaiveDateTime,
conn: &mut DbConn, conn: &mut DbConn,
@ -263,7 +263,7 @@ impl Event {
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
event::table event::table
.filter(event::org_uuid.eq(org_uuid)) .filter(event::org_uuid.eq(org_uuid))
@ -274,16 +274,16 @@ impl Event {
}} }}
} }
pub async fn find_by_org_and_user_org( pub async fn find_by_org_and_member(
org_uuid: &str, org_uuid: &OrganizationId,
user_org_uuid: &str, member_uuid: &MembershipId,
start: &NaiveDateTime, start: &NaiveDateTime,
end: &NaiveDateTime, end: &NaiveDateTime,
conn: &mut DbConn, conn: &mut DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
event::table event::table
.inner_join(users_organizations::table.on(users_organizations::uuid.eq(user_org_uuid))) .inner_join(users_organizations::table.on(users_organizations::uuid.eq(member_uuid)))
.filter(event::org_uuid.eq(org_uuid)) .filter(event::org_uuid.eq(org_uuid))
.filter(event::event_date.between(start, end)) .filter(event::event_date.between(start, end))
.filter(event::user_uuid.eq(users_organizations::user_uuid.nullable()).or(event::act_user_uuid.eq(users_organizations::user_uuid.nullable()))) .filter(event::user_uuid.eq(users_organizations::user_uuid.nullable()).or(event::act_user_uuid.eq(users_organizations::user_uuid.nullable())))
@ -297,7 +297,7 @@ impl Event {
} }
pub async fn find_by_cipher_uuid( pub async fn find_by_cipher_uuid(
cipher_uuid: &str, cipher_uuid: &CipherId,
start: &NaiveDateTime, start: &NaiveDateTime,
end: &NaiveDateTime, end: &NaiveDateTime,
conn: &mut DbConn, conn: &mut DbConn,
@ -327,3 +327,6 @@ impl Event {
} }
} }
} }
#[derive(Clone, Debug, DieselNewType, FromForm, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventId(String);

Datei anzeigen

@ -1,12 +1,12 @@
use super::User; use super::{CipherId, User, UserId};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
#[diesel(table_name = favorites)] #[diesel(table_name = favorites)]
#[diesel(primary_key(user_uuid, cipher_uuid))] #[diesel(primary_key(user_uuid, cipher_uuid))]
pub struct Favorite { pub struct Favorite {
pub user_uuid: String, pub user_uuid: UserId,
pub cipher_uuid: String, pub cipher_uuid: CipherId,
} }
} }
@ -17,7 +17,7 @@ use crate::error::MapResult;
impl Favorite { impl Favorite {
// Returns whether the specified cipher is a favorite of the specified user. // Returns whether the specified cipher is a favorite of the specified user.
pub async fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_favorite(cipher_uuid: &CipherId, user_uuid: &UserId, conn: &mut DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
let query = favorites::table let query = favorites::table
.filter(favorites::cipher_uuid.eq(cipher_uuid)) .filter(favorites::cipher_uuid.eq(cipher_uuid))
@ -29,7 +29,12 @@ impl Favorite {
} }
// Sets whether the specified cipher is a favorite of the specified user. // Sets whether the specified cipher is a favorite of the specified user.
pub async fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn set_favorite(
favorite: bool,
cipher_uuid: &CipherId,
user_uuid: &UserId,
conn: &mut DbConn,
) -> EmptyResult {
let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite); let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite);
match (old, new) { match (old, new) {
(false, true) => { (false, true) => {
@ -62,7 +67,7 @@ impl Favorite {
} }
// Delete all favorite entries associated with the specified cipher. // Delete all favorite entries associated with the specified cipher.
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid))) diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
@ -71,7 +76,7 @@ impl Favorite {
} }
// Delete all favorite entries associated with the specified user. // Delete all favorite entries associated with the specified user.
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid))) diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)
@ -81,12 +86,12 @@ impl Favorite {
/// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers /// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers
/// This is used during a full sync so we only need one query for all favorite cipher matches. /// This is used during a full sync so we only need one query for all favorite cipher matches.
pub async fn get_all_cipher_uuid_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<String> { pub async fn get_all_cipher_uuid_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<CipherId> {
db_run! { conn: { db_run! { conn: {
favorites::table favorites::table
.filter(favorites::user_uuid.eq(user_uuid)) .filter(favorites::user_uuid.eq(user_uuid))
.select(favorites::cipher_uuid) .select(favorites::cipher_uuid)
.load::<String>(conn) .load::<CipherId>(conn)
.unwrap_or_default() .unwrap_or_default()
}} }}
} }

Datei anzeigen

@ -1,17 +1,19 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use derive_more::{AsRef, Deref, Display, From};
use serde_json::Value; use serde_json::Value;
use super::User; use super::{CipherId, User, UserId};
use macros::UuidFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = folders)] #[diesel(table_name = folders)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Folder { pub struct Folder {
pub uuid: String, pub uuid: FolderId,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
pub user_uuid: String, pub user_uuid: UserId,
pub name: String, pub name: String,
} }
@ -19,18 +21,18 @@ db_object! {
#[diesel(table_name = folders_ciphers)] #[diesel(table_name = folders_ciphers)]
#[diesel(primary_key(cipher_uuid, folder_uuid))] #[diesel(primary_key(cipher_uuid, folder_uuid))]
pub struct FolderCipher { pub struct FolderCipher {
pub cipher_uuid: String, pub cipher_uuid: CipherId,
pub folder_uuid: String, pub folder_uuid: FolderId,
} }
} }
/// Local methods /// Local methods
impl Folder { impl Folder {
pub fn new(user_uuid: String, name: String) -> Self { pub fn new(user_uuid: UserId, name: String) -> Self {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
Self { Self {
uuid: crate::util::get_uuid(), uuid: FolderId(crate::util::get_uuid()),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
@ -52,10 +54,10 @@ impl Folder {
} }
impl FolderCipher { impl FolderCipher {
pub fn new(folder_uuid: &str, cipher_uuid: &str) -> Self { pub fn new(folder_uuid: FolderId, cipher_uuid: CipherId) -> Self {
Self { Self {
folder_uuid: folder_uuid.to_string(), folder_uuid,
cipher_uuid: cipher_uuid.to_string(), cipher_uuid,
} }
} }
} }
@ -113,14 +115,14 @@ impl Folder {
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
for folder in Self::find_by_user(user_uuid, conn).await { for folder in Self::find_by_user(user_uuid, conn).await {
folder.delete(conn).await?; folder.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_user(uuid: &FolderId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
folders::table folders::table
.filter(folders::uuid.eq(uuid)) .filter(folders::uuid.eq(uuid))
@ -131,7 +133,7 @@ impl Folder {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
folders::table folders::table
.filter(folders::user_uuid.eq(user_uuid)) .filter(folders::user_uuid.eq(user_uuid))
@ -177,7 +179,7 @@ impl FolderCipher {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))) diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
@ -185,7 +187,7 @@ impl FolderCipher {
}} }}
} }
pub async fn delete_all_by_folder(folder_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_folder(folder_uuid: &FolderId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid))) diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid)))
.execute(conn) .execute(conn)
@ -193,7 +195,11 @@ impl FolderCipher {
}} }}
} }
pub async fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_folder_and_cipher(
folder_uuid: &FolderId,
cipher_uuid: &CipherId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid)) .filter(folders_ciphers::folder_uuid.eq(folder_uuid))
@ -204,7 +210,7 @@ impl FolderCipher {
}} }}
} }
pub async fn find_by_folder(folder_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_folder(folder_uuid: &FolderId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid)) .filter(folders_ciphers::folder_uuid.eq(folder_uuid))
@ -216,14 +222,32 @@ impl FolderCipher {
/// Return a vec with (cipher_uuid, folder_uuid) /// Return a vec with (cipher_uuid, folder_uuid)
/// This is used during a full sync so we only need one query for all folder matches. /// This is used during a full sync so we only need one query for all folder matches.
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<(String, String)> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<(CipherId, FolderId)> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.inner_join(folders::table) .inner_join(folders::table)
.filter(folders::user_uuid.eq(user_uuid)) .filter(folders::user_uuid.eq(user_uuid))
.select(folders_ciphers::all_columns) .select(folders_ciphers::all_columns)
.load::<(String, String)>(conn) .load::<(CipherId, FolderId)>(conn)
.unwrap_or_default() .unwrap_or_default()
}} }}
} }
} }
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct FolderId(String);

Datei anzeigen

@ -1,8 +1,10 @@
use super::{User, UserOrganization}; use super::{CollectionId, Membership, MembershipId, OrganizationId, User, UserId};
use crate::api::EmptyResult; use crate::api::EmptyResult;
use crate::db::DbConn; use crate::db::DbConn;
use crate::error::MapResult; use crate::error::MapResult;
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use derive_more::{AsRef, Deref, Display, From};
use macros::UuidFromParam;
use serde_json::Value; use serde_json::Value;
db_object! { db_object! {
@ -10,8 +12,8 @@ db_object! {
#[diesel(table_name = groups)] #[diesel(table_name = groups)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Group { pub struct Group {
pub uuid: String, pub uuid: GroupId,
pub organizations_uuid: String, pub organizations_uuid: OrganizationId,
pub name: String, pub name: String,
pub access_all: bool, pub access_all: bool,
pub external_id: Option<String>, pub external_id: Option<String>,
@ -23,8 +25,8 @@ db_object! {
#[diesel(table_name = collections_groups)] #[diesel(table_name = collections_groups)]
#[diesel(primary_key(collections_uuid, groups_uuid))] #[diesel(primary_key(collections_uuid, groups_uuid))]
pub struct CollectionGroup { pub struct CollectionGroup {
pub collections_uuid: String, pub collections_uuid: CollectionId,
pub groups_uuid: String, pub groups_uuid: GroupId,
pub read_only: bool, pub read_only: bool,
pub hide_passwords: bool, pub hide_passwords: bool,
} }
@ -33,18 +35,23 @@ db_object! {
#[diesel(table_name = groups_users)] #[diesel(table_name = groups_users)]
#[diesel(primary_key(groups_uuid, users_organizations_uuid))] #[diesel(primary_key(groups_uuid, users_organizations_uuid))]
pub struct GroupUser { pub struct GroupUser {
pub groups_uuid: String, pub groups_uuid: GroupId,
pub users_organizations_uuid: String pub users_organizations_uuid: MembershipId
} }
} }
/// Local methods /// Local methods
impl Group { impl Group {
pub fn new(organizations_uuid: String, name: String, access_all: bool, external_id: Option<String>) -> Self { pub fn new(
organizations_uuid: OrganizationId,
name: String,
access_all: bool,
external_id: Option<String>,
) -> Self {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let mut new_model = Self { let mut new_model = Self {
uuid: crate::util::get_uuid(), uuid: GroupId(crate::util::get_uuid()),
organizations_uuid, organizations_uuid,
name, name,
access_all, access_all,
@ -111,7 +118,7 @@ impl Group {
} }
impl CollectionGroup { impl CollectionGroup {
pub fn new(collections_uuid: String, groups_uuid: String, read_only: bool, hide_passwords: bool) -> Self { pub fn new(collections_uuid: CollectionId, groups_uuid: GroupId, read_only: bool, hide_passwords: bool) -> Self {
Self { Self {
collections_uuid, collections_uuid,
groups_uuid, groups_uuid,
@ -119,10 +126,22 @@ impl CollectionGroup {
hide_passwords, hide_passwords,
} }
} }
pub fn to_json_details_for_group(&self) -> Value {
// If both read_only and hide_passwords are false, then manage should be true
// You can't have an entry with read_only and manage, or hide_passwords and manage
// Or an entry with everything to false
json!({
"id": self.groups_uuid,
"readOnly": self.read_only,
"hidePasswords": self.hide_passwords,
"manage": !self.read_only && !self.hide_passwords,
})
}
} }
impl GroupUser { impl GroupUser {
pub fn new(groups_uuid: String, users_organizations_uuid: String) -> Self { pub fn new(groups_uuid: GroupId, users_organizations_uuid: MembershipId) -> Self {
Self { Self {
groups_uuid, groups_uuid,
users_organizations_uuid, users_organizations_uuid,
@ -166,27 +185,27 @@ impl Group {
} }
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
for group in Self::find_by_organization(org_uuid, conn).await { for group in Self::find_by_organization(org_uuid, conn).await {
group.delete(conn).await?; group.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_organization(organizations_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::organizations_uuid.eq(organizations_uuid)) .filter(groups::organizations_uuid.eq(org_uuid))
.load::<GroupDb>(conn) .load::<GroupDb>(conn)
.expect("Error loading groups") .expect("Error loading groups")
.from_db() .from_db()
}} }}
} }
pub async fn count_by_org(organizations_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::organizations_uuid.eq(organizations_uuid)) .filter(groups::organizations_uuid.eq(org_uuid))
.count() .count()
.first::<i64>(conn) .first::<i64>(conn)
.ok() .ok()
@ -194,7 +213,7 @@ impl Group {
}} }}
} }
pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_org(uuid: &GroupId, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::uuid.eq(uuid)) .filter(groups::uuid.eq(uuid))
@ -205,7 +224,11 @@ impl Group {
}} }}
} }
pub async fn find_by_external_id_and_org(external_id: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_external_id_and_org(
external_id: &str,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::external_id.eq(external_id)) .filter(groups::external_id.eq(external_id))
@ -216,7 +239,7 @@ impl Group {
}} }}
} }
//Returns all organizations the user has full access to //Returns all organizations the user has full access to
pub async fn gather_user_organizations_full_access(user_uuid: &str, conn: &mut DbConn) -> Vec<String> { pub async fn get_orgs_by_user_with_full_access(user_uuid: &UserId, conn: &mut DbConn) -> Vec<OrganizationId> {
db_run! { conn: { db_run! { conn: {
groups_users::table groups_users::table
.inner_join(users_organizations::table.on( .inner_join(users_organizations::table.on(
@ -229,12 +252,12 @@ impl Group {
.filter(groups::access_all.eq(true)) .filter(groups::access_all.eq(true))
.select(groups::organizations_uuid) .select(groups::organizations_uuid)
.distinct() .distinct()
.load::<String>(conn) .load::<OrganizationId>(conn)
.expect("Error loading organization group full access information for user") .expect("Error loading organization group full access information for user")
}} }}
} }
pub async fn is_in_full_access_group(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_in_full_access_group(user_uuid: &UserId, org_uuid: &OrganizationId, conn: &mut DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.inner_join(groups_users::table.on( .inner_join(groups_users::table.on(
@ -263,13 +286,13 @@ impl Group {
}} }}
} }
pub async fn update_revision(uuid: &str, conn: &mut DbConn) { pub async fn update_revision(uuid: &GroupId, conn: &mut DbConn) {
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await { if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
warn!("Failed to update revision for {}: {:#?}", uuid, e); warn!("Failed to update revision for {}: {:#?}", uuid, e);
} }
} }
async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult { async fn _update_revision(uuid: &GroupId, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult {
db_run! {conn: { db_run! {conn: {
crate::util::retry(|| { crate::util::retry(|| {
diesel::update(groups::table.filter(groups::uuid.eq(uuid))) diesel::update(groups::table.filter(groups::uuid.eq(uuid)))
@ -337,7 +360,7 @@ impl CollectionGroup {
} }
} }
pub async fn find_by_group(group_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections_groups::table collections_groups::table
.filter(collections_groups::groups_uuid.eq(group_uuid)) .filter(collections_groups::groups_uuid.eq(group_uuid))
@ -347,7 +370,7 @@ impl CollectionGroup {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections_groups::table collections_groups::table
.inner_join(groups_users::table.on( .inner_join(groups_users::table.on(
@ -364,7 +387,7 @@ impl CollectionGroup {
}} }}
} }
pub async fn find_by_collection(collection_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections_groups::table collections_groups::table
.filter(collections_groups::collections_uuid.eq(collection_uuid)) .filter(collections_groups::collections_uuid.eq(collection_uuid))
@ -390,7 +413,7 @@ impl CollectionGroup {
}} }}
} }
pub async fn delete_all_by_group(group_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> EmptyResult {
let group_users = GroupUser::find_by_group(group_uuid, conn).await; let group_users = GroupUser::find_by_group(group_uuid, conn).await;
for group_user in group_users { for group_user in group_users {
group_user.update_user_revision(conn).await; group_user.update_user_revision(conn).await;
@ -404,7 +427,7 @@ impl CollectionGroup {
}} }}
} }
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
let collection_assigned_to_groups = CollectionGroup::find_by_collection(collection_uuid, conn).await; let collection_assigned_to_groups = CollectionGroup::find_by_collection(collection_uuid, conn).await;
for collection_assigned_to_group in collection_assigned_to_groups { for collection_assigned_to_group in collection_assigned_to_groups {
let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, conn).await; let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, conn).await;
@ -469,7 +492,7 @@ impl GroupUser {
} }
} }
pub async fn find_by_group(group_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
groups_users::table groups_users::table
.filter(groups_users::groups_uuid.eq(group_uuid)) .filter(groups_users::groups_uuid.eq(group_uuid))
@ -479,10 +502,10 @@ impl GroupUser {
}} }}
} }
pub async fn find_by_user(users_organizations_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_member(member_uuid: &MembershipId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
groups_users::table groups_users::table
.filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid)) .filter(groups_users::users_organizations_uuid.eq(member_uuid))
.load::<GroupUserDb>(conn) .load::<GroupUserDb>(conn)
.expect("Error loading groups for user") .expect("Error loading groups for user")
.from_db() .from_db()
@ -490,8 +513,8 @@ impl GroupUser {
} }
pub async fn has_access_to_collection_by_member( pub async fn has_access_to_collection_by_member(
collection_uuid: &str, collection_uuid: &CollectionId,
member_uuid: &str, member_uuid: &MembershipId,
conn: &mut DbConn, conn: &mut DbConn,
) -> bool { ) -> bool {
db_run! { conn: { db_run! { conn: {
@ -507,7 +530,11 @@ impl GroupUser {
}} }}
} }
pub async fn has_full_access_by_member(org_uuid: &str, member_uuid: &str, conn: &mut DbConn) -> bool { pub async fn has_full_access_by_member(
org_uuid: &OrganizationId,
member_uuid: &MembershipId,
conn: &mut DbConn,
) -> bool {
db_run! { conn: { db_run! { conn: {
groups_users::table groups_users::table
.inner_join(groups::table.on( .inner_join(groups::table.on(
@ -523,32 +550,32 @@ impl GroupUser {
} }
pub async fn update_user_revision(&self, conn: &mut DbConn) { pub async fn update_user_revision(&self, conn: &mut DbConn) {
match UserOrganization::find_by_uuid(&self.users_organizations_uuid, conn).await { match Membership::find_by_uuid(&self.users_organizations_uuid, conn).await {
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await, Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await,
None => warn!("User could not be found!"), None => warn!("Member could not be found!"),
} }
} }
pub async fn delete_by_group_id_and_user_id( pub async fn delete_by_group_and_member(
group_uuid: &str, group_uuid: &GroupId,
users_organizations_uuid: &str, member_uuid: &MembershipId,
conn: &mut DbConn, conn: &mut DbConn,
) -> EmptyResult { ) -> EmptyResult {
match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await { match Membership::find_by_uuid(member_uuid, conn).await {
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await, Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await,
None => warn!("User could not be found!"), None => warn!("Member could not be found!"),
}; };
db_run! { conn: { db_run! { conn: {
diesel::delete(groups_users::table) diesel::delete(groups_users::table)
.filter(groups_users::groups_uuid.eq(group_uuid)) .filter(groups_users::groups_uuid.eq(group_uuid))
.filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid)) .filter(groups_users::users_organizations_uuid.eq(member_uuid))
.execute(conn) .execute(conn)
.map_res("Error deleting group users") .map_res("Error deleting group users")
}} }}
} }
pub async fn delete_all_by_group(group_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> EmptyResult {
let group_users = GroupUser::find_by_group(group_uuid, conn).await; let group_users = GroupUser::find_by_group(group_uuid, conn).await;
for group_user in group_users { for group_user in group_users {
group_user.update_user_revision(conn).await; group_user.update_user_revision(conn).await;
@ -562,17 +589,35 @@ impl GroupUser {
}} }}
} }
pub async fn delete_all_by_user(users_organizations_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_member(member_uuid: &MembershipId, conn: &mut DbConn) -> EmptyResult {
match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await { match Membership::find_by_uuid(member_uuid, conn).await {
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await, Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await,
None => warn!("User could not be found!"), None => warn!("Member could not be found!"),
} }
db_run! { conn: { db_run! { conn: {
diesel::delete(groups_users::table) diesel::delete(groups_users::table)
.filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid)) .filter(groups_users::users_organizations_uuid.eq(member_uuid))
.execute(conn) .execute(conn)
.map_res("Error deleting user groups") .map_res("Error deleting user groups")
}} }}
} }
} }
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct GroupId(String);

Datei anzeigen

@ -16,20 +16,26 @@ mod two_factor_duo_context;
mod two_factor_incomplete; mod two_factor_incomplete;
mod user; mod user;
pub use self::attachment::Attachment; pub use self::attachment::{Attachment, AttachmentId};
pub use self::auth_request::AuthRequest; pub use self::auth_request::{AuthRequest, AuthRequestId};
pub use self::cipher::{Cipher, RepromptType}; pub use self::cipher::{Cipher, CipherId, RepromptType};
pub use self::collection::{Collection, CollectionCipher, CollectionUser}; pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser};
pub use self::device::{Device, DeviceType}; pub use self::device::{Device, DeviceId, DeviceType};
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType}; pub use self::emergency_access::{EmergencyAccess, EmergencyAccessId, EmergencyAccessStatus, EmergencyAccessType};
pub use self::event::{Event, EventType}; pub use self::event::{Event, EventType};
pub use self::favorite::Favorite; pub use self::favorite::Favorite;
pub use self::folder::{Folder, FolderCipher}; pub use self::folder::{Folder, FolderCipher, FolderId};
pub use self::group::{CollectionGroup, Group, GroupUser}; pub use self::group::{CollectionGroup, Group, GroupId, GroupUser};
pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType}; pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyId, OrgPolicyType};
pub use self::organization::{Organization, OrganizationApiKey, UserOrgStatus, UserOrgType, UserOrganization}; pub use self::organization::{
pub use self::send::{Send, SendType}; Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey,
OrganizationId,
};
pub use self::send::{
id::{SendFileId, SendId},
Send, SendType,
};
pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::two_factor_duo_context::TwoFactorDuoContext; pub use self::two_factor_duo_context::TwoFactorDuoContext;
pub use self::two_factor_incomplete::TwoFactorIncomplete; pub use self::two_factor_incomplete::TwoFactorIncomplete;
pub use self::user::{Invitation, User, UserKdfType, UserStampException}; pub use self::user::{Invitation, User, UserId, UserKdfType, UserStampException};

Datei anzeigen

@ -1,3 +1,4 @@
use derive_more::{AsRef, From};
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
@ -5,15 +6,15 @@ use crate::api::EmptyResult;
use crate::db::DbConn; use crate::db::DbConn;
use crate::error::MapResult; use crate::error::MapResult;
use super::{TwoFactor, UserOrgStatus, UserOrgType, UserOrganization}; use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor, UserId};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = org_policies)] #[diesel(table_name = org_policies)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct OrgPolicy { pub struct OrgPolicy {
pub uuid: String, pub uuid: OrgPolicyId,
pub org_uuid: String, pub org_uuid: OrganizationId,
pub atype: i32, pub atype: i32,
pub enabled: bool, pub enabled: bool,
pub data: String, pub data: String,
@ -62,9 +63,9 @@ pub enum OrgPolicyErr {
/// Local methods /// Local methods
impl OrgPolicy { impl OrgPolicy {
pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self { pub fn new(org_uuid: OrganizationId, atype: OrgPolicyType, data: String) -> Self {
Self { Self {
uuid: crate::util::get_uuid(), uuid: OrgPolicyId(crate::util::get_uuid()),
org_uuid, org_uuid,
atype: atype as i32, atype: atype as i32,
enabled: false, enabled: false,
@ -142,7 +143,7 @@ impl OrgPolicy {
}} }}
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid)) .filter(org_policies::org_uuid.eq(org_uuid))
@ -152,7 +153,7 @@ impl OrgPolicy {
}} }}
} }
pub async fn find_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.inner_join( .inner_join(
@ -161,7 +162,7 @@ impl OrgPolicy {
.and(users_organizations::user_uuid.eq(user_uuid))) .and(users_organizations::user_uuid.eq(user_uuid)))
) )
.filter( .filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(MembershipStatus::Confirmed as i32)
) )
.select(org_policies::all_columns) .select(org_policies::all_columns)
.load::<OrgPolicyDb>(conn) .load::<OrgPolicyDb>(conn)
@ -170,7 +171,11 @@ impl OrgPolicy {
}} }}
} }
pub async fn find_by_org_and_type(org_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_org_and_type(
org_uuid: &OrganizationId,
policy_type: OrgPolicyType,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid)) .filter(org_policies::org_uuid.eq(org_uuid))
@ -181,7 +186,7 @@ impl OrgPolicy {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid))) diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
.execute(conn) .execute(conn)
@ -190,7 +195,7 @@ impl OrgPolicy {
} }
pub async fn find_accepted_and_confirmed_by_user_and_active_policy( pub async fn find_accepted_and_confirmed_by_user_and_active_policy(
user_uuid: &str, user_uuid: &UserId,
policy_type: OrgPolicyType, policy_type: OrgPolicyType,
conn: &mut DbConn, conn: &mut DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
@ -202,10 +207,10 @@ impl OrgPolicy {
.and(users_organizations::user_uuid.eq(user_uuid))) .and(users_organizations::user_uuid.eq(user_uuid)))
) )
.filter( .filter(
users_organizations::status.eq(UserOrgStatus::Accepted as i32) users_organizations::status.eq(MembershipStatus::Accepted as i32)
) )
.or_filter( .or_filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(MembershipStatus::Confirmed as i32)
) )
.filter(org_policies::atype.eq(policy_type as i32)) .filter(org_policies::atype.eq(policy_type as i32))
.filter(org_policies::enabled.eq(true)) .filter(org_policies::enabled.eq(true))
@ -217,7 +222,7 @@ impl OrgPolicy {
} }
pub async fn find_confirmed_by_user_and_active_policy( pub async fn find_confirmed_by_user_and_active_policy(
user_uuid: &str, user_uuid: &UserId,
policy_type: OrgPolicyType, policy_type: OrgPolicyType,
conn: &mut DbConn, conn: &mut DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
@ -229,7 +234,7 @@ impl OrgPolicy {
.and(users_organizations::user_uuid.eq(user_uuid))) .and(users_organizations::user_uuid.eq(user_uuid)))
) )
.filter( .filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(MembershipStatus::Confirmed as i32)
) )
.filter(org_policies::atype.eq(policy_type as i32)) .filter(org_policies::atype.eq(policy_type as i32))
.filter(org_policies::enabled.eq(true)) .filter(org_policies::enabled.eq(true))
@ -244,21 +249,21 @@ impl OrgPolicy {
/// and the user is not an owner or admin of that org. This is only useful for checking /// and the user is not an owner or admin of that org. This is only useful for checking
/// applicability of policy types that have these particular semantics. /// applicability of policy types that have these particular semantics.
pub async fn is_applicable_to_user( pub async fn is_applicable_to_user(
user_uuid: &str, user_uuid: &UserId,
policy_type: OrgPolicyType, policy_type: OrgPolicyType,
exclude_org_uuid: Option<&str>, exclude_org_uuid: Option<&OrganizationId>,
conn: &mut DbConn, conn: &mut DbConn,
) -> bool { ) -> bool {
for policy in for policy in
OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await
{ {
// Check if we need to skip this organization. // Check if we need to skip this organization.
if exclude_org_uuid.is_some() && exclude_org_uuid.unwrap() == policy.org_uuid { if exclude_org_uuid.is_some() && *exclude_org_uuid.unwrap() == policy.org_uuid {
continue; continue;
} }
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await { if let Some(user) = Membership::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
if user.atype < UserOrgType::Admin { if user.atype < MembershipType::Admin {
return true; return true;
} }
} }
@ -267,8 +272,8 @@ impl OrgPolicy {
} }
pub async fn is_user_allowed( pub async fn is_user_allowed(
user_uuid: &str, user_uuid: &UserId,
org_uuid: &str, org_uuid: &OrganizationId,
exclude_current_org: bool, exclude_current_org: bool,
conn: &mut DbConn, conn: &mut DbConn,
) -> OrgPolicyResult { ) -> OrgPolicyResult {
@ -296,7 +301,7 @@ impl OrgPolicy {
Ok(()) Ok(())
} }
pub async fn org_is_reset_password_auto_enroll(org_uuid: &str, conn: &mut DbConn) -> bool { pub async fn org_is_reset_password_auto_enroll(org_uuid: &OrganizationId, conn: &mut DbConn) -> bool {
match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await { match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await {
Some(policy) => match serde_json::from_str::<ResetPasswordDataModel>(&policy.data) { Some(policy) => match serde_json::from_str::<ResetPasswordDataModel>(&policy.data) {
Ok(opts) => { Ok(opts) => {
@ -312,12 +317,12 @@ impl OrgPolicy {
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail` /// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
/// option of the `Send Options` policy, and the user is not an owner or admin of that org. /// option of the `Send Options` policy, and the user is not an owner or admin of that org.
pub async fn is_hide_email_disabled(user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_hide_email_disabled(user_uuid: &UserId, conn: &mut DbConn) -> bool {
for policy in for policy in
OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await
{ {
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await { if let Some(user) = Membership::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
if user.atype < UserOrgType::Admin { if user.atype < MembershipType::Admin {
match serde_json::from_str::<SendOptionsPolicyData>(&policy.data) { match serde_json::from_str::<SendOptionsPolicyData>(&policy.data) {
Ok(opts) => { Ok(opts) => {
if opts.disable_hide_email { if opts.disable_hide_email {
@ -332,12 +337,19 @@ impl OrgPolicy {
false false
} }
pub async fn is_enabled_for_member(org_user_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> bool { pub async fn is_enabled_for_member(
if let Some(membership) = UserOrganization::find_by_uuid(org_user_uuid, conn).await { member_uuid: &MembershipId,
if let Some(policy) = OrgPolicy::find_by_org_and_type(&membership.org_uuid, policy_type, conn).await { policy_type: OrgPolicyType,
conn: &mut DbConn,
) -> bool {
if let Some(member) = Membership::find_by_uuid(member_uuid, conn).await {
if let Some(policy) = OrgPolicy::find_by_org_and_type(&member.org_uuid, policy_type, conn).await {
return policy.enabled; return policy.enabled;
} }
} }
false false
} }
} }
#[derive(Clone, Debug, AsRef, DieselNewType, From, FromForm, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OrgPolicyId(String);

Datei anzeigen

@ -1,4 +1,5 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use derive_more::{AsRef, Deref, Display, From};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use serde_json::Value; use serde_json::Value;
use std::{ use std::{
@ -6,16 +7,19 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
}; };
use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User}; use super::{
use crate::db::models::{Collection, CollectionGroup}; CipherId, Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy,
OrgPolicyType, TwoFactor, User, UserId,
};
use crate::CONFIG; use crate::CONFIG;
use macros::UuidFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = organizations)] #[diesel(table_name = organizations)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Organization { pub struct Organization {
pub uuid: String, pub uuid: OrganizationId,
pub name: String, pub name: String,
pub billing_email: String, pub billing_email: String,
pub private_key: Option<String>, pub private_key: Option<String>,
@ -25,10 +29,10 @@ db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = users_organizations)] #[diesel(table_name = users_organizations)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct UserOrganization { pub struct Membership {
pub uuid: String, pub uuid: MembershipId,
pub user_uuid: String, pub user_uuid: UserId,
pub org_uuid: String, pub org_uuid: OrganizationId,
pub access_all: bool, pub access_all: bool,
pub akey: String, pub akey: String,
@ -42,8 +46,8 @@ db_object! {
#[diesel(table_name = organization_api_key)] #[diesel(table_name = organization_api_key)]
#[diesel(primary_key(uuid, org_uuid))] #[diesel(primary_key(uuid, org_uuid))]
pub struct OrganizationApiKey { pub struct OrganizationApiKey {
pub uuid: String, pub uuid: OrgApiKeyId,
pub org_uuid: String, pub org_uuid: OrganizationId,
pub atype: i32, pub atype: i32,
pub api_key: String, pub api_key: String,
pub revision_date: NaiveDateTime, pub revision_date: NaiveDateTime,
@ -51,7 +55,7 @@ db_object! {
} }
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs // https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs
pub enum UserOrgStatus { pub enum MembershipStatus {
Revoked = -1, Revoked = -1,
Invited = 0, Invited = 0,
Accepted = 1, Accepted = 1,
@ -59,29 +63,29 @@ pub enum UserOrgStatus {
} }
#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)] #[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
pub enum UserOrgType { pub enum MembershipType {
Owner = 0, Owner = 0,
Admin = 1, Admin = 1,
User = 2, User = 2,
Manager = 3, Manager = 3,
} }
impl UserOrgType { impl MembershipType {
pub fn from_str(s: &str) -> Option<Self> { pub fn from_str(s: &str) -> Option<Self> {
match s { match s {
"0" | "Owner" => Some(UserOrgType::Owner), "0" | "Owner" => Some(MembershipType::Owner),
"1" | "Admin" => Some(UserOrgType::Admin), "1" | "Admin" => Some(MembershipType::Admin),
"2" | "User" => Some(UserOrgType::User), "2" | "User" => Some(MembershipType::User),
"3" | "Manager" => Some(UserOrgType::Manager), "3" | "Manager" => Some(MembershipType::Manager),
// HACK: We convert the custom role to a manager role // HACK: We convert the custom role to a manager role
"4" | "Custom" => Some(UserOrgType::Manager), "4" | "Custom" => Some(MembershipType::Manager),
_ => None, _ => None,
} }
} }
} }
impl Ord for UserOrgType { impl Ord for MembershipType {
fn cmp(&self, other: &UserOrgType) -> Ordering { fn cmp(&self, other: &MembershipType) -> Ordering {
// For easy comparison, map each variant to an access level (where 0 is lowest). // For easy comparison, map each variant to an access level (where 0 is lowest).
static ACCESS_LEVEL: [i32; 4] = [ static ACCESS_LEVEL: [i32; 4] = [
3, // Owner 3, // Owner
@ -93,19 +97,19 @@ impl Ord for UserOrgType {
} }
} }
impl PartialOrd for UserOrgType { impl PartialOrd for MembershipType {
fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> { fn partial_cmp(&self, other: &MembershipType) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl PartialEq<i32> for UserOrgType { impl PartialEq<i32> for MembershipType {
fn eq(&self, other: &i32) -> bool { fn eq(&self, other: &i32) -> bool {
*other == *self as i32 *other == *self as i32
} }
} }
impl PartialOrd<i32> for UserOrgType { impl PartialOrd<i32> for MembershipType {
fn partial_cmp(&self, other: &i32) -> Option<Ordering> { fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
if let Some(other) = Self::from_i32(*other) { if let Some(other) = Self::from_i32(*other) {
return Some(self.cmp(&other)); return Some(self.cmp(&other));
@ -122,25 +126,25 @@ impl PartialOrd<i32> for UserOrgType {
} }
} }
impl PartialEq<UserOrgType> for i32 { impl PartialEq<MembershipType> for i32 {
fn eq(&self, other: &UserOrgType) -> bool { fn eq(&self, other: &MembershipType) -> bool {
*self == *other as i32 *self == *other as i32
} }
} }
impl PartialOrd<UserOrgType> for i32 { impl PartialOrd<MembershipType> for i32 {
fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> { fn partial_cmp(&self, other: &MembershipType) -> Option<Ordering> {
if let Some(self_type) = UserOrgType::from_i32(*self) { if let Some(self_type) = MembershipType::from_i32(*self) {
return Some(self_type.cmp(other)); return Some(self_type.cmp(other));
} }
None None
} }
fn lt(&self, other: &UserOrgType) -> bool { fn lt(&self, other: &MembershipType) -> bool {
matches!(self.partial_cmp(other), Some(Ordering::Less) | None) matches!(self.partial_cmp(other), Some(Ordering::Less) | None)
} }
fn le(&self, other: &UserOrgType) -> bool { fn le(&self, other: &MembershipType) -> bool {
matches!(self.partial_cmp(other), Some(Ordering::Less | Ordering::Equal) | None) matches!(self.partial_cmp(other), Some(Ordering::Less | Ordering::Equal) | None)
} }
} }
@ -149,7 +153,7 @@ impl PartialOrd<UserOrgType> for i32 {
impl Organization { impl Organization {
pub fn new(name: String, billing_email: String, private_key: Option<String>, public_key: Option<String>) -> Self { pub fn new(name: String, billing_email: String, private_key: Option<String>, public_key: Option<String>) -> Self {
Self { Self {
uuid: crate::util::get_uuid(), uuid: OrganizationId(crate::util::get_uuid()),
name, name,
billing_email, billing_email,
private_key, private_key,
@ -214,25 +218,25 @@ impl Organization {
// It should also provide enough room for 100+ types, which i doubt will ever happen. // It should also provide enough room for 100+ types, which i doubt will ever happen.
static ACTIVATE_REVOKE_DIFF: i32 = 128; static ACTIVATE_REVOKE_DIFF: i32 = 128;
impl UserOrganization { impl Membership {
pub fn new(user_uuid: String, org_uuid: String) -> Self { pub fn new(user_uuid: UserId, org_uuid: OrganizationId) -> Self {
Self { Self {
uuid: crate::util::get_uuid(), uuid: MembershipId(crate::util::get_uuid()),
user_uuid, user_uuid,
org_uuid, org_uuid,
access_all: false, access_all: false,
akey: String::new(), akey: String::new(),
status: UserOrgStatus::Accepted as i32, status: MembershipStatus::Accepted as i32,
atype: UserOrgType::User as i32, atype: MembershipType::User as i32,
reset_password_key: None, reset_password_key: None,
external_id: None, external_id: None,
} }
} }
pub fn restore(&mut self) -> bool { pub fn restore(&mut self) -> bool {
if self.status < UserOrgStatus::Invited as i32 { if self.status < MembershipStatus::Invited as i32 {
self.status += ACTIVATE_REVOKE_DIFF; self.status += ACTIVATE_REVOKE_DIFF;
return true; return true;
} }
@ -240,7 +244,7 @@ impl UserOrganization {
} }
pub fn revoke(&mut self) -> bool { pub fn revoke(&mut self) -> bool {
if self.status > UserOrgStatus::Revoked as i32 { if self.status > MembershipStatus::Revoked as i32 {
self.status -= ACTIVATE_REVOKE_DIFF; self.status -= ACTIVATE_REVOKE_DIFF;
return true; return true;
} }
@ -249,7 +253,7 @@ impl UserOrganization {
/// Return the status of the user in an unrevoked state /// Return the status of the user in an unrevoked state
pub fn get_unrevoked_status(&self) -> i32 { pub fn get_unrevoked_status(&self) -> i32 {
if self.status <= UserOrgStatus::Revoked as i32 { if self.status <= MembershipStatus::Revoked as i32 {
return self.status + ACTIVATE_REVOKE_DIFF; return self.status + ACTIVATE_REVOKE_DIFF;
} }
self.status self.status
@ -279,9 +283,9 @@ impl UserOrganization {
} }
impl OrganizationApiKey { impl OrganizationApiKey {
pub fn new(org_uuid: String, api_key: String) -> Self { pub fn new(org_uuid: OrganizationId, api_key: String) -> Self {
Self { Self {
uuid: crate::util::get_uuid(), uuid: OrgApiKeyId(crate::util::get_uuid()),
org_uuid, org_uuid,
atype: 0, // Type 0 is the default and only type we support currently atype: 0, // Type 0 is the default and only type we support currently
@ -307,8 +311,8 @@ impl Organization {
err!(format!("BillingEmail {} is not a valid email address", self.billing_email.trim())) err!(format!("BillingEmail {} is not a valid email address", self.billing_email.trim()))
} }
for user_org in UserOrganization::find_by_org(&self.uuid, conn).await.iter() { for member in Membership::find_by_org(&self.uuid, conn).await.iter() {
User::update_uuid_revision(&user_org.user_uuid, conn).await; User::update_uuid_revision(&member.user_uuid, conn).await;
} }
db_run! { conn: db_run! { conn:
@ -348,7 +352,7 @@ impl Organization {
Cipher::delete_all_by_organization(&self.uuid, conn).await?; Cipher::delete_all_by_organization(&self.uuid, conn).await?;
Collection::delete_all_by_organization(&self.uuid, conn).await?; Collection::delete_all_by_organization(&self.uuid, conn).await?;
UserOrganization::delete_all_by_organization(&self.uuid, conn).await?; Membership::delete_all_by_organization(&self.uuid, conn).await?;
OrgPolicy::delete_all_by_organization(&self.uuid, conn).await?; OrgPolicy::delete_all_by_organization(&self.uuid, conn).await?;
Group::delete_all_by_organization(&self.uuid, conn).await?; Group::delete_all_by_organization(&self.uuid, conn).await?;
OrganizationApiKey::delete_all_by_organization(&self.uuid, conn).await?; OrganizationApiKey::delete_all_by_organization(&self.uuid, conn).await?;
@ -360,7 +364,7 @@ impl Organization {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &OrganizationId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
organizations::table organizations::table
.filter(organizations::uuid.eq(uuid)) .filter(organizations::uuid.eq(uuid))
@ -376,13 +380,13 @@ impl Organization {
} }
} }
impl UserOrganization { impl Membership {
pub async fn to_json(&self, conn: &mut DbConn) -> Value { pub async fn to_json(&self, conn: &mut DbConn) -> Value {
let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap(); let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap();
// HACK: Convert the manager type to a custom type // HACK: Convert the manager type to a custom type
// It will be converted back on other locations // It will be converted back on other locations
let user_org_type = self.type_manager_as_custom(); let membership_type = self.type_manager_as_custom();
let permissions = json!({ let permissions = json!({
// TODO: Add full support for Custom User Roles // TODO: Add full support for Custom User Roles
@ -392,9 +396,9 @@ impl UserOrganization {
"accessImportExport": false, "accessImportExport": false,
"accessReports": false, "accessReports": false,
// If the following 3 Collection roles are set to true a custom user has access all permission // If the following 3 Collection roles are set to true a custom user has access all permission
"createNewCollections": user_org_type == 4 && self.access_all, "createNewCollections": membership_type == 4 && self.access_all,
"editAnyCollection": user_org_type == 4 && self.access_all, "editAnyCollection": membership_type == 4 && self.access_all,
"deleteAnyCollection": user_org_type == 4 && self.access_all, "deleteAnyCollection": membership_type == 4 && self.access_all,
"manageGroups": false, "manageGroups": false,
"managePolicies": false, "managePolicies": false,
"manageSso": false, // Not supported "manageSso": false, // Not supported
@ -459,7 +463,7 @@ impl UserOrganization {
"userId": self.user_uuid, "userId": self.user_uuid,
"key": self.akey, "key": self.akey,
"status": self.status, "status": self.status,
"type": user_org_type, "type": membership_type,
"enabled": true, "enabled": true,
"object": "profileOrganization", "object": "profileOrganization",
@ -476,16 +480,16 @@ impl UserOrganization {
// Because BitWarden want the status to be -1 for revoked users we need to catch that here. // Because BitWarden want the status to be -1 for revoked users we need to catch that here.
// We subtract/add a number so we can restore/activate the user to it's previous state again. // We subtract/add a number so we can restore/activate the user to it's previous state again.
let status = if self.status < UserOrgStatus::Revoked as i32 { let status = if self.status < MembershipStatus::Revoked as i32 {
UserOrgStatus::Revoked as i32 MembershipStatus::Revoked as i32
} else { } else {
self.status self.status
}; };
let twofactor_enabled = !TwoFactor::find_by_user(&user.uuid, conn).await.is_empty(); let twofactor_enabled = !TwoFactor::find_by_user(&user.uuid, conn).await.is_empty();
let groups: Vec<String> = if include_groups && CONFIG.org_groups_enabled() { let groups: Vec<GroupId> = if include_groups && CONFIG.org_groups_enabled() {
GroupUser::find_by_user(&self.uuid, conn).await.iter().map(|gu| gu.groups_uuid.clone()).collect() GroupUser::find_by_member(&self.uuid, conn).await.iter().map(|gu| gu.groups_uuid.clone()).collect()
} else { } else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled, // The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// so just act as if there are no groups. // so just act as if there are no groups.
@ -500,7 +504,7 @@ impl UserOrganization {
// If collections are to be included, only include them if the user does not have full access via a group or defined to the user it self // If collections are to be included, only include them if the user does not have full access via a group or defined to the user it self
let collections: Vec<Value> = if include_collections && !(full_access_group || self.has_full_access()) { let collections: Vec<Value> = if include_collections && !(full_access_group || self.has_full_access()) {
// Get all collections for the user here already to prevent more queries // Get all collections for the user here already to prevent more queries
let cu: HashMap<String, CollectionUser> = let cu: HashMap<CollectionId, CollectionUser> =
CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn) CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
.await .await
.into_iter() .into_iter()
@ -508,7 +512,7 @@ impl UserOrganization {
.collect(); .collect();
// Get all collection groups for this user to prevent there inclusion // Get all collection groups for this user to prevent there inclusion
let cg: HashSet<String> = CollectionGroup::find_by_user(&self.user_uuid, conn) let cg: HashSet<CollectionId> = CollectionGroup::find_by_user(&self.user_uuid, conn)
.await .await
.into_iter() .into_iter()
.map(|cg| cg.collections_uuid) .map(|cg| cg.collections_uuid)
@ -519,12 +523,12 @@ impl UserOrganization {
.into_iter() .into_iter()
.filter_map(|c| { .filter_map(|c| {
let (read_only, hide_passwords, can_manage) = if self.has_full_access() { let (read_only, hide_passwords, can_manage) = if self.has_full_access() {
(false, false, self.atype >= UserOrgType::Manager) (false, false, self.atype >= MembershipType::Manager)
} else if let Some(cu) = cu.get(&c.uuid) { } else if let Some(cu) = cu.get(&c.uuid) {
( (
cu.read_only, cu.read_only,
cu.hide_passwords, cu.hide_passwords,
self.atype == UserOrgType::Manager && !cu.read_only && !cu.hide_passwords, self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords,
) )
// If previous checks failed it might be that this user has access via a group, but we should not return those elements here // If previous checks failed it might be that this user has access via a group, but we should not return those elements here
// Those are returned via a special group endpoint // Those are returned via a special group endpoint
@ -548,11 +552,11 @@ impl UserOrganization {
// HACK: Convert the manager type to a custom type // HACK: Convert the manager type to a custom type
// It will be converted back on other locations // It will be converted back on other locations
let user_org_type = self.type_manager_as_custom(); let membership_type = self.type_manager_as_custom();
// HACK: Only return permissions if the user is of type custom and has access_all // HACK: Only return permissions if the user is of type custom and has access_all
// Else Bitwarden will assume the defaults of all false // Else Bitwarden will assume the defaults of all false
let permissions = if user_org_type == 4 && self.access_all { let permissions = if membership_type == 4 && self.access_all {
json!({ json!({
// TODO: Add full support for Custom User Roles // TODO: Add full support for Custom User Roles
// See: https://bitwarden.com/help/article/user-types-access-control/#custom-role // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
@ -578,7 +582,7 @@ impl UserOrganization {
json!({ json!({
"id": self.uuid, "id": self.uuid,
"userId": self.user_uuid, "userId": self.user_uuid,
"name": if self.get_unrevoked_status() >= UserOrgStatus::Accepted as i32 { Some(user.name) } else { None }, "name": if self.get_unrevoked_status() >= MembershipStatus::Accepted as i32 { Some(user.name) } else { None },
"email": user.email, "email": user.email,
"externalId": self.external_id, "externalId": self.external_id,
"avatarColor": user.avatar_color, "avatarColor": user.avatar_color,
@ -586,7 +590,7 @@ impl UserOrganization {
"collections": collections, "collections": collections,
"status": status, "status": status,
"type": user_org_type, "type": membership_type,
"accessAll": self.access_all, "accessAll": self.access_all,
"twoFactorEnabled": twofactor_enabled, "twoFactorEnabled": twofactor_enabled,
"resetPasswordEnrolled": self.reset_password_key.is_some(), "resetPasswordEnrolled": self.reset_password_key.is_some(),
@ -630,8 +634,8 @@ impl UserOrganization {
// Because BitWarden want the status to be -1 for revoked users we need to catch that here. // Because BitWarden want the status to be -1 for revoked users we need to catch that here.
// We subtract/add a number so we can restore/activate the user to it's previous state again. // We subtract/add a number so we can restore/activate the user to it's previous state again.
let status = if self.status < UserOrgStatus::Revoked as i32 { let status = if self.status < MembershipStatus::Revoked as i32 {
UserOrgStatus::Revoked as i32 MembershipStatus::Revoked as i32
} else { } else {
self.status self.status
}; };
@ -654,8 +658,8 @@ impl UserOrganization {
// Because Bitwarden wants the status to be -1 for revoked users we need to catch that here. // Because Bitwarden wants the status to be -1 for revoked users we need to catch that here.
// We subtract/add a number so we can restore/activate the user to it's previous state again. // We subtract/add a number so we can restore/activate the user to it's previous state again.
let status = if self.status < UserOrgStatus::Revoked as i32 { let status = if self.status < MembershipStatus::Revoked as i32 {
UserOrgStatus::Revoked as i32 MembershipStatus::Revoked as i32
} else { } else {
self.status self.status
}; };
@ -677,7 +681,7 @@ impl UserOrganization {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(users_organizations::table) match diesel::replace_into(users_organizations::table)
.values(UserOrganizationDb::to_db(self)) .values(MembershipDb::to_db(self))
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -685,7 +689,7 @@ impl UserOrganization {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(users_organizations::table) diesel::update(users_organizations::table)
.filter(users_organizations::uuid.eq(&self.uuid)) .filter(users_organizations::uuid.eq(&self.uuid))
.set(UserOrganizationDb::to_db(self)) .set(MembershipDb::to_db(self))
.execute(conn) .execute(conn)
.map_res("Error adding user to organization") .map_res("Error adding user to organization")
}, },
@ -693,7 +697,7 @@ impl UserOrganization {
}.map_res("Error adding user to organization") }.map_res("Error adding user to organization")
} }
postgresql { postgresql {
let value = UserOrganizationDb::to_db(self); let value = MembershipDb::to_db(self);
diesel::insert_into(users_organizations::table) diesel::insert_into(users_organizations::table)
.values(&value) .values(&value)
.on_conflict(users_organizations::uuid) .on_conflict(users_organizations::uuid)
@ -709,7 +713,7 @@ impl UserOrganization {
User::update_uuid_revision(&self.user_uuid, conn).await; User::update_uuid_revision(&self.user_uuid, conn).await;
CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?; CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?;
GroupUser::delete_all_by_user(&self.uuid, conn).await?; GroupUser::delete_all_by_member(&self.uuid, conn).await?;
db_run! { conn: { db_run! { conn: {
diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid))) diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
@ -718,121 +722,129 @@ impl UserOrganization {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
for user_org in Self::find_by_org(org_uuid, conn).await { for member in Self::find_by_org(org_uuid, conn).await {
user_org.delete(conn).await?; member.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
for user_org in Self::find_any_state_by_user(user_uuid, conn).await { for member in Self::find_any_state_by_user(user_uuid, conn).await {
user_org.delete(conn).await?; member.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_email_and_org(email: &str, org_id: &str, conn: &mut DbConn) -> Option<UserOrganization> { pub async fn find_by_email_and_org(
email: &str,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Membership> {
if let Some(user) = User::find_by_mail(email, conn).await { if let Some(user) = User::find_by_mail(email, conn).await {
if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, org_id, conn).await { if let Some(member) = Membership::find_by_user_and_org(&user.uuid, org_uuid, conn).await {
return Some(user_org); return Some(member);
} }
} }
None None
} }
pub fn has_status(&self, status: UserOrgStatus) -> bool { pub fn has_status(&self, status: MembershipStatus) -> bool {
self.status == status as i32 self.status == status as i32
} }
pub fn has_type(&self, user_type: UserOrgType) -> bool { pub fn has_type(&self, user_type: MembershipType) -> bool {
self.atype == user_type as i32 self.atype == user_type as i32
} }
pub fn has_full_access(&self) -> bool { pub fn has_full_access(&self) -> bool {
(self.access_all || self.atype >= UserOrgType::Admin) && self.has_status(UserOrgStatus::Confirmed) (self.access_all || self.atype >= MembershipType::Admin) && self.has_status(MembershipStatus::Confirmed)
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &MembershipId, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::uuid.eq(uuid)) .filter(users_organizations::uuid.eq(uuid))
.first::<UserOrganizationDb>(conn) .first::<MembershipDb>(conn)
.ok().from_db() .ok().from_db()
}} }}
} }
pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_org(
uuid: &MembershipId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::uuid.eq(uuid)) .filter(users_organizations::uuid.eq(uuid))
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.first::<UserOrganizationDb>(conn) .first::<MembershipDb>(conn)
.ok().from_db() .ok().from_db()
}} }}
} }
pub async fn find_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32)) .filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.unwrap_or_default().from_db() .unwrap_or_default().from_db()
}} }}
} }
pub async fn find_invited_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_invited_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Invited as i32)) .filter(users_organizations::status.eq(MembershipStatus::Invited as i32))
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.unwrap_or_default().from_db() .unwrap_or_default().from_db()
}} }}
} }
pub async fn find_any_state_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_any_state_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.unwrap_or_default().from_db() .unwrap_or_default().from_db()
}} }}
} }
pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_accepted_and_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Accepted as i32).or(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))) .filter(users_organizations::status.eq(MembershipStatus::Accepted as i32).or(users_organizations::status.eq(MembershipStatus::Confirmed as i32)))
.count() .count()
.first::<i64>(conn) .first::<i64>(conn)
.unwrap_or(0) .unwrap_or(0)
}} }}
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.expect("Error loading user organizations").from_db() .expect("Error loading user organizations").from_db()
}} }}
} }
pub async fn find_confirmed_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_confirmed_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32)) .filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.unwrap_or_default().from_db() .unwrap_or_default().from_db()
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -843,71 +855,91 @@ impl UserOrganization {
}} }}
} }
pub async fn find_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org_and_type(
org_uuid: &OrganizationId,
atype: MembershipType,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.filter(users_organizations::atype.eq(atype as i32)) .filter(users_organizations::atype.eq(atype as i32))
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.expect("Error loading user organizations").from_db() .expect("Error loading user organizations").from_db()
}} }}
} }
pub async fn count_confirmed_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &mut DbConn) -> i64 { pub async fn count_confirmed_by_org_and_type(
org_uuid: &OrganizationId,
atype: MembershipType,
conn: &mut DbConn,
) -> i64 {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.filter(users_organizations::atype.eq(atype as i32)) .filter(users_organizations::atype.eq(atype as i32))
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32)) .filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.count() .count()
.first::<i64>(conn) .first::<i64>(conn)
.unwrap_or(0) .unwrap_or(0)
}} }}
} }
pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_user_and_org(
user_uuid: &UserId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.first::<UserOrganizationDb>(conn) .first::<MembershipDb>(conn)
.ok().from_db() .ok().from_db()
}} }}
} }
pub async fn find_confirmed_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_confirmed_by_user_and_org(
user_uuid: &UserId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.filter( .filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(MembershipStatus::Confirmed as i32)
) )
.first::<UserOrganizationDb>(conn) .first::<MembershipDb>(conn)
.ok().from_db() .ok().from_db()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.expect("Error loading user organizations").from_db() .expect("Error loading user organizations").from_db()
}} }}
} }
pub async fn get_org_uuid_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<String> { pub async fn get_orgs_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<OrganizationId> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.select(users_organizations::org_uuid) .select(users_organizations::org_uuid)
.load::<String>(conn) .load::<OrganizationId>(conn)
.unwrap_or_default() .unwrap_or_default()
}} }}
} }
pub async fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user_and_policy(
user_uuid: &UserId,
policy_type: OrgPolicyType,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.inner_join( .inner_join(
@ -918,15 +950,19 @@ impl UserOrganization {
.and(org_policies::enabled.eq(true))) .and(org_policies::enabled.eq(true)))
) )
.filter( .filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(MembershipStatus::Confirmed as i32)
) )
.select(users_organizations::all_columns) .select(users_organizations::all_columns)
.load::<UserOrganizationDb>(conn) .load::<MembershipDb>(conn)
.unwrap_or_default().from_db() .unwrap_or_default().from_db()
}} }}
} }
pub async fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_cipher_and_org(
cipher_uuid: &CipherId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -945,11 +981,15 @@ impl UserOrganization {
) )
.select(users_organizations::all_columns) .select(users_organizations::all_columns)
.distinct() .distinct()
.load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db() .load::<MembershipDb>(conn).expect("Error loading user organizations").from_db()
}} }}
} }
pub async fn find_by_cipher_and_org_with_group(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_cipher_and_org_with_group(
cipher_uuid: &CipherId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -971,23 +1011,31 @@ impl UserOrganization {
) )
.select(users_organizations::all_columns) .select(users_organizations::all_columns)
.distinct() .distinct()
.load::<UserOrganizationDb>(conn).expect("Error loading user organizations with groups").from_db() .load::<MembershipDb>(conn).expect("Error loading user organizations with groups").from_db()
}} }}
} }
pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &str, cipher_uuid: &str, conn: &mut DbConn) -> bool { pub async fn user_has_ge_admin_access_to_cipher(
user_uuid: &UserId,
cipher_uuid: &CipherId,
conn: &mut DbConn,
) -> bool {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())))) .inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))))
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::atype.eq_any(vec![UserOrgType::Owner as i32, UserOrgType::Admin as i32])) .filter(users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]))
.count() .count()
.first::<i64>(conn) .first::<i64>(conn)
.ok().unwrap_or(0) != 0 .ok().unwrap_or(0) != 0
}} }}
} }
pub async fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_collection_and_org(
collection_uuid: &CollectionId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -1000,18 +1048,22 @@ impl UserOrganization {
) )
) )
.select(users_organizations::all_columns) .select(users_organizations::all_columns)
.load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db() .load::<MembershipDb>(conn).expect("Error loading user organizations").from_db()
}} }}
} }
pub async fn find_by_external_id_and_org(ext_id: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_external_id_and_org(
ext_id: &str,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
users_organizations::table users_organizations::table
.filter( .filter(
users_organizations::external_id.eq(ext_id) users_organizations::external_id.eq(ext_id)
.and(users_organizations::org_uuid.eq(org_uuid)) .and(users_organizations::org_uuid.eq(org_uuid))
) )
.first::<UserOrganizationDb>(conn).ok().from_db() .first::<MembershipDb>(conn).ok().from_db()
}} }}
} }
} }
@ -1050,7 +1102,7 @@ impl OrganizationApiKey {
} }
} }
pub async fn find_by_org_uuid(org_uuid: &str, conn: &DbConn) -> Option<Self> { pub async fn find_by_org_uuid(org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
organization_api_key::table organization_api_key::table
.filter(organization_api_key::org_uuid.eq(org_uuid)) .filter(organization_api_key::org_uuid.eq(org_uuid))
@ -1059,7 +1111,7 @@ impl OrganizationApiKey {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(organization_api_key::table.filter(organization_api_key::org_uuid.eq(org_uuid))) diesel::delete(organization_api_key::table.filter(organization_api_key::org_uuid.eq(org_uuid)))
.execute(conn) .execute(conn)
@ -1068,16 +1120,56 @@ impl OrganizationApiKey {
} }
} }
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
#[deref(forward)]
#[from(forward)]
pub struct OrganizationId(String);
#[derive(
Clone,
Debug,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct MembershipId(String);
#[derive(Clone, Debug, DieselNewType, Display, FromForm, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrgApiKeyId(String);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn partial_cmp_UserOrgType() { fn partial_cmp_MembershipType() {
assert!(UserOrgType::Owner > UserOrgType::Admin); assert!(MembershipType::Owner > MembershipType::Admin);
assert!(UserOrgType::Admin > UserOrgType::Manager); assert!(MembershipType::Admin > MembershipType::Manager);
assert!(UserOrgType::Manager > UserOrgType::User); assert!(MembershipType::Manager > MembershipType::User);
assert!(UserOrgType::Manager == UserOrgType::from_str("4").unwrap()); assert!(MembershipType::Manager == MembershipType::from_str("4").unwrap());
} }
} }

Datei anzeigen

@ -3,7 +3,8 @@ use serde_json::Value;
use crate::util::LowerCase; use crate::util::LowerCase;
use super::User; use super::{OrganizationId, User, UserId};
use id::SendId;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -11,11 +12,10 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Send { pub struct Send {
pub uuid: String, pub uuid: SendId,
pub user_uuid: Option<String>,
pub organization_uuid: Option<String>,
pub user_uuid: Option<UserId>,
pub organization_uuid: Option<OrganizationId>,
pub name: String, pub name: String,
pub notes: Option<String>, pub notes: Option<String>,
@ -51,7 +51,7 @@ impl Send {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
Self { Self {
uuid: crate::util::get_uuid(), uuid: SendId::from(crate::util::get_uuid()),
user_uuid: None, user_uuid: None,
organization_uuid: None, organization_uuid: None,
@ -243,7 +243,7 @@ impl Send {
} }
} }
pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<String> { pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<UserId> {
let mut user_uuids = Vec::new(); let mut user_uuids = Vec::new();
match &self.user_uuid { match &self.user_uuid {
Some(user_uuid) => { Some(user_uuid) => {
@ -257,7 +257,7 @@ impl Send {
user_uuids user_uuids
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
for send in Self::find_by_user(user_uuid, conn).await { for send in Self::find_by_user(user_uuid, conn).await {
send.delete(conn).await?; send.delete(conn).await?;
} }
@ -273,14 +273,14 @@ impl Send {
}; };
let uuid = match Uuid::from_slice(&uuid_vec) { let uuid = match Uuid::from_slice(&uuid_vec) {
Ok(u) => u.to_string(), Ok(u) => SendId::from(u.to_string()),
Err(_) => return None, Err(_) => return None,
}; };
Self::find_by_uuid(&uuid, conn).await Self::find_by_uuid(&uuid, conn).await
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &SendId, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::uuid.eq(uuid)) .filter(sends::uuid.eq(uuid))
@ -290,7 +290,7 @@ impl Send {
}} }}
} }
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_user(uuid: &SendId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::uuid.eq(uuid)) .filter(sends::uuid.eq(uuid))
@ -301,7 +301,7 @@ impl Send {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::user_uuid.eq(user_uuid)) .filter(sends::user_uuid.eq(user_uuid))
@ -309,7 +309,7 @@ impl Send {
}} }}
} }
pub async fn size_by_user(user_uuid: &str, conn: &mut DbConn) -> Option<i64> { pub async fn size_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Option<i64> {
let sends = Self::find_by_user(user_uuid, conn).await; let sends = Self::find_by_user(user_uuid, conn).await;
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
@ -332,7 +332,7 @@ impl Send {
Some(total) Some(total)
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::organization_uuid.eq(org_uuid)) .filter(sends::organization_uuid.eq(org_uuid))
@ -349,3 +349,48 @@ impl Send {
}} }}
} }
} }
// separate namespace to avoid name collision with std::marker::Send
pub mod id {
use derive_more::{AsRef, Deref, Display, From};
use macros::{IdFromParam, UuidFromParam};
use std::marker::Send;
use std::path::Path;
#[derive(
Clone,
Debug,
AsRef,
Deref,
DieselNewType,
Display,
From,
FromForm,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
UuidFromParam,
)]
pub struct SendId(String);
impl AsRef<Path> for SendId {
#[inline]
fn as_ref(&self) -> &Path {
Path::new(&self.0)
}
}
#[derive(
Clone, Debug, AsRef, Deref, Display, From, FromForm, Hash, PartialEq, Eq, Serialize, Deserialize, IdFromParam,
)]
pub struct SendFileId(String);
impl AsRef<Path> for SendFileId {
#[inline]
fn as_ref(&self) -> &Path {
Path::new(&self.0)
}
}
}

Datei anzeigen

@ -1,5 +1,6 @@
use serde_json::Value; use serde_json::Value;
use super::UserId;
use crate::{api::EmptyResult, db::DbConn, error::MapResult}; use crate::{api::EmptyResult, db::DbConn, error::MapResult};
db_object! { db_object! {
@ -7,8 +8,8 @@ db_object! {
#[diesel(table_name = twofactor)] #[diesel(table_name = twofactor)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct TwoFactor { pub struct TwoFactor {
pub uuid: String, pub uuid: TwoFactorId,
pub user_uuid: String, pub user_uuid: UserId,
pub atype: i32, pub atype: i32,
pub enabled: bool, pub enabled: bool,
pub data: String, pub data: String,
@ -41,9 +42,9 @@ pub enum TwoFactorType {
/// Local methods /// Local methods
impl TwoFactor { impl TwoFactor {
pub fn new(user_uuid: String, atype: TwoFactorType, data: String) -> Self { pub fn new(user_uuid: UserId, atype: TwoFactorType, data: String) -> Self {
Self { Self {
uuid: crate::util::get_uuid(), uuid: TwoFactorId(crate::util::get_uuid()),
user_uuid, user_uuid,
atype: atype as i32, atype: atype as i32,
enabled: true, enabled: true,
@ -118,7 +119,7 @@ impl TwoFactor {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
twofactor::table twofactor::table
.filter(twofactor::user_uuid.eq(user_uuid)) .filter(twofactor::user_uuid.eq(user_uuid))
@ -129,7 +130,7 @@ impl TwoFactor {
}} }}
} }
pub async fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_user_and_type(user_uuid: &UserId, atype: i32, conn: &mut DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
twofactor::table twofactor::table
.filter(twofactor::user_uuid.eq(user_uuid)) .filter(twofactor::user_uuid.eq(user_uuid))
@ -140,7 +141,7 @@ impl TwoFactor {
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid))) diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)
@ -217,3 +218,6 @@ impl TwoFactor {
Ok(()) Ok(())
} }
} }
#[derive(Clone, Debug, DieselNewType, FromForm, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TwoFactorId(String);

Datei anzeigen

@ -1,17 +1,26 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use crate::{api::EmptyResult, auth::ClientIp, db::DbConn, error::MapResult, CONFIG}; use crate::{
api::EmptyResult,
auth::ClientIp,
db::{
models::{DeviceId, UserId},
DbConn,
},
error::MapResult,
CONFIG,
};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = twofactor_incomplete)] #[diesel(table_name = twofactor_incomplete)]
#[diesel(primary_key(user_uuid, device_uuid))] #[diesel(primary_key(user_uuid, device_uuid))]
pub struct TwoFactorIncomplete { pub struct TwoFactorIncomplete {
pub user_uuid: String, pub user_uuid: UserId,
// This device UUID is simply what's claimed by the device. It doesn't // This device UUID is simply what's claimed by the device. It doesn't
// necessarily correspond to any UUID in the devices table, since a device // necessarily correspond to any UUID in the devices table, since a device
// must complete 2FA login before being added into the devices table. // must complete 2FA login before being added into the devices table.
pub device_uuid: String, pub device_uuid: DeviceId,
pub device_name: String, pub device_name: String,
pub device_type: i32, pub device_type: i32,
pub login_time: NaiveDateTime, pub login_time: NaiveDateTime,
@ -21,8 +30,8 @@ db_object! {
impl TwoFactorIncomplete { impl TwoFactorIncomplete {
pub async fn mark_incomplete( pub async fn mark_incomplete(
user_uuid: &str, user_uuid: &UserId,
device_uuid: &str, device_uuid: &DeviceId,
device_name: &str, device_name: &str,
device_type: i32, device_type: i32,
ip: &ClientIp, ip: &ClientIp,
@ -55,7 +64,7 @@ impl TwoFactorIncomplete {
}} }}
} }
pub async fn mark_complete(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn mark_complete(user_uuid: &UserId, device_uuid: &DeviceId, conn: &mut DbConn) -> EmptyResult {
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() {
return Ok(()); return Ok(());
} }
@ -63,7 +72,11 @@ impl TwoFactorIncomplete {
Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await
} }
pub async fn find_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_user_and_device(
user_uuid: &UserId,
device_uuid: &DeviceId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
twofactor_incomplete::table twofactor_incomplete::table
.filter(twofactor_incomplete::user_uuid.eq(user_uuid)) .filter(twofactor_incomplete::user_uuid.eq(user_uuid))
@ -88,7 +101,11 @@ impl TwoFactorIncomplete {
Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn).await Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn).await
} }
pub async fn delete_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_by_user_and_device(
user_uuid: &UserId,
device_uuid: &DeviceId,
conn: &mut DbConn,
) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(twofactor_incomplete::table diesel::delete(twofactor_incomplete::table
.filter(twofactor_incomplete::user_uuid.eq(user_uuid)) .filter(twofactor_incomplete::user_uuid.eq(user_uuid))
@ -98,7 +115,7 @@ impl TwoFactorIncomplete {
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid))) diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)

Datei anzeigen

@ -1,9 +1,19 @@
use crate::util::{format_date, get_uuid, retry};
use chrono::{NaiveDateTime, TimeDelta, Utc}; use chrono::{NaiveDateTime, TimeDelta, Utc};
use derive_more::{AsRef, Deref, Display, From};
use serde_json::Value; use serde_json::Value;
use crate::crypto; use super::{
use crate::CONFIG; Cipher, Device, EmergencyAccess, Favorite, Folder, Membership, MembershipType, TwoFactor, TwoFactorIncomplete,
};
use crate::{
api::EmptyResult,
crypto,
db::DbConn,
error::MapResult,
util::{format_date, get_uuid, retry},
CONFIG,
};
use macros::UuidFromParam;
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -11,7 +21,7 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct User { pub struct User {
pub uuid: String, pub uuid: UserId,
pub enabled: bool, pub enabled: bool,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
@ -91,7 +101,7 @@ impl User {
let email = email.to_lowercase(); let email = email.to_lowercase();
Self { Self {
uuid: get_uuid(), uuid: UserId(get_uuid()),
enabled: true, enabled: true,
created_at: now, created_at: now,
updated_at: now, updated_at: now,
@ -214,20 +224,11 @@ impl User {
} }
} }
use super::{
Cipher, Device, EmergencyAccess, Favorite, Folder, Send, TwoFactor, TwoFactorIncomplete, UserOrgType,
UserOrganization,
};
use crate::db::DbConn;
use crate::api::EmptyResult;
use crate::error::MapResult;
/// Database methods /// Database methods
impl User { impl User {
pub async fn to_json(&self, conn: &mut DbConn) -> Value { pub async fn to_json(&self, conn: &mut DbConn) -> Value {
let mut orgs_json = Vec::new(); let mut orgs_json = Vec::new();
for c in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await { for c in Membership::find_confirmed_by_user(&self.uuid, conn).await {
orgs_json.push(c.to_json(conn).await); orgs_json.push(c.to_json(conn).await);
} }
@ -304,19 +305,18 @@ impl User {
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await { for member in Membership::find_confirmed_by_user(&self.uuid, conn).await {
if user_org.atype == UserOrgType::Owner if member.atype == MembershipType::Owner
&& UserOrganization::count_confirmed_by_org_and_type(&user_org.org_uuid, UserOrgType::Owner, conn).await && Membership::count_confirmed_by_org_and_type(&member.org_uuid, MembershipType::Owner, conn).await <= 1
<= 1
{ {
err!("Can't delete last owner") err!("Can't delete last owner")
} }
} }
Send::delete_all_by_user(&self.uuid, conn).await?; super::Send::delete_all_by_user(&self.uuid, conn).await?;
EmergencyAccess::delete_all_by_user(&self.uuid, conn).await?; EmergencyAccess::delete_all_by_user(&self.uuid, conn).await?;
EmergencyAccess::delete_all_by_grantee_email(&self.email, conn).await?; EmergencyAccess::delete_all_by_grantee_email(&self.email, conn).await?;
UserOrganization::delete_all_by_user(&self.uuid, conn).await?; Membership::delete_all_by_user(&self.uuid, conn).await?;
Cipher::delete_all_by_user(&self.uuid, conn).await?; Cipher::delete_all_by_user(&self.uuid, conn).await?;
Favorite::delete_all_by_user(&self.uuid, conn).await?; Favorite::delete_all_by_user(&self.uuid, conn).await?;
Folder::delete_all_by_user(&self.uuid, conn).await?; Folder::delete_all_by_user(&self.uuid, conn).await?;
@ -332,7 +332,7 @@ impl User {
}} }}
} }
pub async fn update_uuid_revision(uuid: &str, conn: &mut DbConn) { pub async fn update_uuid_revision(uuid: &UserId, conn: &mut DbConn) {
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await { if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
warn!("Failed to update revision for {}: {:#?}", uuid, e); warn!("Failed to update revision for {}: {:#?}", uuid, e);
} }
@ -357,7 +357,7 @@ impl User {
Self::_update_revision(&self.uuid, &self.updated_at, conn).await Self::_update_revision(&self.uuid, &self.updated_at, conn).await
} }
async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult { async fn _update_revision(uuid: &UserId, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult {
db_run! {conn: { db_run! {conn: {
retry(|| { retry(|| {
diesel::update(users::table.filter(users::uuid.eq(uuid))) diesel::update(users::table.filter(users::uuid.eq(uuid)))
@ -379,7 +379,7 @@ impl User {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db() users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db()
}} }}
@ -458,3 +458,23 @@ impl Invitation {
} }
} }
} }
#[derive(
Clone,
Debug,
DieselNewType,
FromForm,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
AsRef,
Deref,
Display,
From,
UuidFromParam,
)]
#[deref(forward)]
#[from(forward)]
pub struct UserId(String);

Datei anzeigen

@ -17,7 +17,7 @@ use crate::{
encode_jwt, generate_delete_claims, generate_emergency_access_invite_claims, generate_invite_claims, encode_jwt, generate_delete_claims, generate_emergency_access_invite_claims, generate_invite_claims,
generate_verify_email_claims, generate_verify_email_claims,
}, },
db::models::{Device, DeviceType, User}, db::models::{Device, DeviceType, EmergencyAccessId, MembershipId, OrganizationId, User, UserId},
error::Error, error::Error,
CONFIG, CONFIG,
}; };
@ -166,8 +166,8 @@ pub async fn send_password_hint(address: &str, hint: Option<String>) -> EmptyRes
send_email(address, &subject, body_html, body_text).await send_email(address, &subject, body_html, body_text).await
} }
pub async fn send_delete_account(address: &str, uuid: &str) -> EmptyResult { pub async fn send_delete_account(address: &str, user_id: &UserId) -> EmptyResult {
let claims = generate_delete_claims(uuid.to_string()); let claims = generate_delete_claims(user_id.to_string());
let delete_token = encode_jwt(&claims); let delete_token = encode_jwt(&claims);
let (subject, body_html, body_text) = get_text( let (subject, body_html, body_text) = get_text(
@ -175,7 +175,7 @@ pub async fn send_delete_account(address: &str, uuid: &str) -> EmptyResult {
json!({ json!({
"url": CONFIG.domain(), "url": CONFIG.domain(),
"img_src": CONFIG._smtp_img_src(), "img_src": CONFIG._smtp_img_src(),
"user_id": uuid, "user_id": user_id,
"email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(), "email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(),
"token": delete_token, "token": delete_token,
}), }),
@ -184,8 +184,8 @@ pub async fn send_delete_account(address: &str, uuid: &str) -> EmptyResult {
send_email(address, &subject, body_html, body_text).await send_email(address, &subject, body_html, body_text).await
} }
pub async fn send_verify_email(address: &str, uuid: &str) -> EmptyResult { pub async fn send_verify_email(address: &str, user_id: &UserId) -> EmptyResult {
let claims = generate_verify_email_claims(uuid.to_string()); let claims = generate_verify_email_claims(user_id.clone());
let verify_email_token = encode_jwt(&claims); let verify_email_token = encode_jwt(&claims);
let (subject, body_html, body_text) = get_text( let (subject, body_html, body_text) = get_text(
@ -193,7 +193,7 @@ pub async fn send_verify_email(address: &str, uuid: &str) -> EmptyResult {
json!({ json!({
"url": CONFIG.domain(), "url": CONFIG.domain(),
"img_src": CONFIG._smtp_img_src(), "img_src": CONFIG._smtp_img_src(),
"user_id": uuid, "user_id": user_id,
"email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(), "email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(),
"token": verify_email_token, "token": verify_email_token,
}), }),
@ -214,8 +214,8 @@ pub async fn send_welcome(address: &str) -> EmptyResult {
send_email(address, &subject, body_html, body_text).await send_email(address, &subject, body_html, body_text).await
} }
pub async fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult { pub async fn send_welcome_must_verify(address: &str, user_id: &UserId) -> EmptyResult {
let claims = generate_verify_email_claims(uuid.to_string()); let claims = generate_verify_email_claims(user_id.clone());
let verify_email_token = encode_jwt(&claims); let verify_email_token = encode_jwt(&claims);
let (subject, body_html, body_text) = get_text( let (subject, body_html, body_text) = get_text(
@ -223,7 +223,7 @@ pub async fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult
json!({ json!({
"url": CONFIG.domain(), "url": CONFIG.domain(),
"img_src": CONFIG._smtp_img_src(), "img_src": CONFIG._smtp_img_src(),
"user_id": uuid, "user_id": user_id,
"token": verify_email_token, "token": verify_email_token,
}), }),
)?; )?;
@ -259,8 +259,8 @@ pub async fn send_single_org_removed_from_org(address: &str, org_name: &str) ->
pub async fn send_invite( pub async fn send_invite(
user: &User, user: &User,
org_id: Option<String>, org_id: Option<OrganizationId>,
org_user_id: Option<String>, member_id: Option<MembershipId>,
org_name: &str, org_name: &str,
invited_by_email: Option<String>, invited_by_email: Option<String>,
) -> EmptyResult { ) -> EmptyResult {
@ -268,18 +268,26 @@ pub async fn send_invite(
user.uuid.clone(), user.uuid.clone(),
user.email.clone(), user.email.clone(),
org_id.clone(), org_id.clone(),
org_user_id.clone(), member_id.clone(),
invited_by_email, invited_by_email,
); );
let invite_token = encode_jwt(&claims); let invite_token = encode_jwt(&claims);
let org_id = match org_id {
Some(ref org_id) => org_id.as_ref(),
None => "_",
};
let member_id = match member_id {
Some(ref member_id) => member_id.as_ref(),
None => "_",
};
let mut query = url::Url::parse("https://query.builder").unwrap(); let mut query = url::Url::parse("https://query.builder").unwrap();
{ {
let mut query_params = query.query_pairs_mut(); let mut query_params = query.query_pairs_mut();
query_params query_params
.append_pair("email", &user.email) .append_pair("email", &user.email)
.append_pair("organizationName", org_name) .append_pair("organizationName", org_name)
.append_pair("organizationId", org_id.as_deref().unwrap_or("_")) .append_pair("organizationId", org_id)
.append_pair("organizationUserId", org_user_id.as_deref().unwrap_or("_")) .append_pair("organizationUserId", member_id)
.append_pair("token", &invite_token); .append_pair("token", &invite_token);
if user.private_key.is_some() { if user.private_key.is_some() {
query_params.append_pair("orgUserHasExistingUser", "true"); query_params.append_pair("orgUserHasExistingUser", "true");
@ -305,15 +313,15 @@ pub async fn send_invite(
pub async fn send_emergency_access_invite( pub async fn send_emergency_access_invite(
address: &str, address: &str,
uuid: &str, user_id: UserId,
emer_id: &str, emer_id: EmergencyAccessId,
grantor_name: &str, grantor_name: &str,
grantor_email: &str, grantor_email: &str,
) -> EmptyResult { ) -> EmptyResult {
let claims = generate_emergency_access_invite_claims( let claims = generate_emergency_access_invite_claims(
String::from(uuid), user_id,
String::from(address), String::from(address),
String::from(emer_id), emer_id.clone(),
String::from(grantor_name), String::from(grantor_name),
String::from(grantor_email), String::from(grantor_email),
); );
@ -323,7 +331,7 @@ pub async fn send_emergency_access_invite(
{ {
let mut query_params = query.query_pairs_mut(); let mut query_params = query.query_pairs_mut();
query_params query_params
.append_pair("id", emer_id) .append_pair("id", &emer_id.to_string())
.append_pair("name", grantor_name) .append_pair("name", grantor_name)
.append_pair("email", address) .append_pair("email", address)
.append_pair("token", &encode_jwt(&claims)); .append_pair("token", &encode_jwt(&claims));

Datei anzeigen

@ -24,6 +24,8 @@ extern crate log;
extern crate diesel; extern crate diesel;
#[macro_use] #[macro_use]
extern crate diesel_migrations; extern crate diesel_migrations;
#[macro_use]
extern crate diesel_derive_newtype;
use std::{ use std::{
collections::HashMap, collections::HashMap,

Datei anzeigen

@ -1,13 +1,12 @@
// //
// Web Headers and caching // Web Headers and caching
// //
use std::{collections::HashMap, io::Cursor, ops::Deref, path::Path}; use std::{collections::HashMap, io::Cursor, path::Path};
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use rocket::{ use rocket::{
fairing::{Fairing, Info, Kind}, fairing::{Fairing, Info, Kind},
http::{ContentType, Header, HeaderMap, Method, Status}, http::{ContentType, Header, HeaderMap, Method, Status},
request::FromParam,
response::{self, Responder}, response::{self, Responder},
Data, Orbit, Request, Response, Rocket, Data, Orbit, Request, Response, Rocket,
}; };
@ -223,42 +222,6 @@ impl<'r, R: 'r + Responder<'r, 'static> + Send> Responder<'r, 'static> for Cache
} }
} }
pub struct SafeString(String);
impl fmt::Display for SafeString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Deref for SafeString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<Path> for SafeString {
#[inline]
fn as_ref(&self) -> &Path {
Path::new(&self.0)
}
}
impl<'r> FromParam<'r> for SafeString {
type Error = ();
#[inline(always)]
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
if param.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) {
Ok(SafeString(param.to_string()))
} else {
Err(())
}
}
}
// Log all the routes from the main paths list, and the attachments endpoint // Log all the routes from the main paths list, and the attachments endpoint
// Effectively ignores, any static file route, and the alive endpoint // Effectively ignores, any static file route, and the alive endpoint
const LOGGED_ROUTES: [&str; 7] = ["/api", "/admin", "/identity", "/icons", "/attachments", "/events", "/notifications"]; const LOGGED_ROUTES: [&str; 7] = ["/api", "/admin", "/identity", "/icons", "/attachments", "/events", "/notifications"];