mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-11-22 03:43:06 +00:00
Add some extra access checks for attachments and groups
This commit is contained in:
parent
e7f083dee9
commit
60964c07e6
7 changed files with 101 additions and 28 deletions
|
@ -934,6 +934,15 @@ async fn share_cipher_by_uuid(
|
||||||
/// redirects to the same location as before the v2 API.
|
/// redirects to the same location as before the v2 API.
|
||||||
#[get("/ciphers/<uuid>/attachment/<attachment_id>")]
|
#[get("/ciphers/<uuid>/attachment/<attachment_id>")]
|
||||||
async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||||
|
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||||
|
Some(cipher) => cipher,
|
||||||
|
None => err!("Cipher doesn't exist"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !cipher.is_accessible_to_user(&headers.user.uuid, &mut conn).await {
|
||||||
|
err!("Cipher is not accessible")
|
||||||
|
}
|
||||||
|
|
||||||
match Attachment::find_by_id(attachment_id, &mut conn).await {
|
match Attachment::find_by_id(attachment_id, &mut conn).await {
|
||||||
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
|
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
|
||||||
Some(_) => err!("Attachment doesn't belong to cipher"),
|
Some(_) => err!("Attachment doesn't belong to cipher"),
|
||||||
|
|
|
@ -2578,11 +2578,15 @@ async fn put_user_groups(
|
||||||
err!("Group support is disabled");
|
err!("Group support is disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
|
let user_org = match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
|
||||||
Some(_) => { /* Do nothing */ }
|
Some(uo) => uo,
|
||||||
_ => err!("User could not be found!"),
|
_ => err!("User could not be found!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if user_org.org_uuid != org_id {
|
||||||
|
err!("Group doesn't belong to organization");
|
||||||
|
}
|
||||||
|
|
||||||
GroupUser::delete_all_by_user(org_user_id, &mut conn).await?;
|
GroupUser::delete_all_by_user(org_user_id, &mut conn).await?;
|
||||||
|
|
||||||
let assigned_group_ids = data.into_inner().data;
|
let assigned_group_ids = data.into_inner().data;
|
||||||
|
@ -2628,16 +2632,24 @@ async fn delete_group_user(
|
||||||
err!("Group support is disabled");
|
err!("Group support is disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
|
let user_org = match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
|
||||||
Some(_) => { /* Do nothing */ }
|
Some(uo) => uo,
|
||||||
_ => err!("User could not be found!"),
|
_ => err!("User could not be found!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
match Group::find_by_uuid(group_id, &mut conn).await {
|
if user_org.org_uuid != org_id {
|
||||||
Some(_) => { /* Do nothing */ }
|
err!("User doesn't belong to organization");
|
||||||
|
}
|
||||||
|
|
||||||
|
let group = match Group::find_by_uuid(group_id, &mut conn).await {
|
||||||
|
Some(g) => g,
|
||||||
_ => err!("Group could not be found!"),
|
_ => err!("Group could not be found!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if group.organizations_uuid != org_id {
|
||||||
|
err!("Group doesn't belong to organization");
|
||||||
|
}
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationUserUpdatedGroups as i32,
|
EventType::OrganizationUserUpdatedGroups as i32,
|
||||||
org_user_id,
|
org_user_id,
|
||||||
|
|
|
@ -340,27 +340,30 @@ async fn post_send_file_v2_data(
|
||||||
|
|
||||||
let mut data = data.into_inner();
|
let mut data = data.into_inner();
|
||||||
|
|
||||||
if let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await {
|
let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await else { err!("Send not found. Unable to save the file.") };
|
||||||
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(send_uuid);
|
|
||||||
let file_path = folder_path.join(file_id);
|
|
||||||
tokio::fs::create_dir_all(&folder_path).await?;
|
|
||||||
|
|
||||||
if let Err(_err) = data.data.persist_to(&file_path).await {
|
let Some(send_user_id) = &send.user_uuid else {err!("Sends are only supported for users at the moment")};
|
||||||
data.data.move_copy_to(file_path).await?
|
if send_user_id != &headers.user.uuid {
|
||||||
}
|
err!("Send doesn't belong to user");
|
||||||
|
|
||||||
nt.send_send_update(
|
|
||||||
UpdateType::SyncSendCreate,
|
|
||||||
&send,
|
|
||||||
&send.update_users_revision(&mut conn).await,
|
|
||||||
&headers.device.uuid,
|
|
||||||
&mut conn,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
} else {
|
|
||||||
err!("Send not found. Unable to save the file.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(send_uuid);
|
||||||
|
let file_path = folder_path.join(file_id);
|
||||||
|
tokio::fs::create_dir_all(&folder_path).await?;
|
||||||
|
|
||||||
|
if let Err(_err) = data.data.persist_to(&file_path).await {
|
||||||
|
data.data.move_copy_to(file_path).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
nt.send_send_update(
|
||||||
|
UpdateType::SyncSendCreate,
|
||||||
|
&send,
|
||||||
|
&send.update_users_revision(&mut conn).await,
|
||||||
|
&headers.device.uuid,
|
||||||
|
&mut conn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{core::now, ApiResult, EmptyResult},
|
api::{core::now, ApiResult, EmptyResult},
|
||||||
|
auth::decode_file_download,
|
||||||
error::Error,
|
error::Error,
|
||||||
util::{Cached, SafeString},
|
util::{Cached, SafeString},
|
||||||
CONFIG,
|
CONFIG,
|
||||||
|
@ -91,8 +92,13 @@ 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>")]
|
#[get("/attachments/<uuid>/<file_id>?<token>")]
|
||||||
async fn attachments(uuid: SafeString, file_id: SafeString) -> Option<NamedFile> {
|
async fn attachments(uuid: SafeString, file_id: SafeString, token: String) -> Option<NamedFile> {
|
||||||
|
let Ok(claims) = dbg!(decode_file_download(&token)) else { return None };
|
||||||
|
if claims.sub != *uuid || claims.file_id != *file_id {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok()
|
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
src/auth.rs
30
src/auth.rs
|
@ -24,6 +24,7 @@ static JWT_VERIFYEMAIL_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|verifyema
|
||||||
static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin()));
|
static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin()));
|
||||||
static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin()));
|
static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin()));
|
||||||
static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin()));
|
static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin()));
|
||||||
|
static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin()));
|
||||||
|
|
||||||
static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
|
static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
|
||||||
let key =
|
let key =
|
||||||
|
@ -98,6 +99,10 @@ pub fn decode_api_org(token: &str) -> Result<OrgApiKeyLoginJwtClaims, Error> {
|
||||||
decode_jwt(token, JWT_ORG_API_KEY_ISSUER.to_string())
|
decode_jwt(token, JWT_ORG_API_KEY_ISSUER.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_file_download(token: &str) -> Result<FileDownloadClaims, Error> {
|
||||||
|
decode_jwt(token, JWT_FILE_DOWNLOAD_ISSUER.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LoginJwtClaims {
|
pub struct LoginJwtClaims {
|
||||||
// Not before
|
// Not before
|
||||||
|
@ -234,6 +239,31 @@ pub fn generate_organization_api_key_login_claims(uuid: String, org_id: String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct FileDownloadClaims {
|
||||||
|
// Not before
|
||||||
|
pub nbf: i64,
|
||||||
|
// Expiration time
|
||||||
|
pub exp: i64,
|
||||||
|
// Issuer
|
||||||
|
pub iss: String,
|
||||||
|
// Subject
|
||||||
|
pub sub: String,
|
||||||
|
|
||||||
|
pub file_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_file_download_claims(uuid: String, file_id: String) -> FileDownloadClaims {
|
||||||
|
let time_now = Utc::now().naive_utc();
|
||||||
|
FileDownloadClaims {
|
||||||
|
nbf: time_now.timestamp(),
|
||||||
|
exp: (time_now + Duration::minutes(5)).timestamp(),
|
||||||
|
iss: JWT_FILE_DOWNLOAD_ISSUER.to_string(),
|
||||||
|
sub: uuid,
|
||||||
|
file_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct BasicJwtClaims {
|
pub struct BasicJwtClaims {
|
||||||
// Not before
|
// Not before
|
||||||
|
|
|
@ -35,7 +35,8 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_url(&self, host: &str) -> String {
|
pub fn get_url(&self, host: &str) -> String {
|
||||||
format!("{}/attachments/{}/{}", host, self.cipher_uuid, self.id)
|
let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone()));
|
||||||
|
format!("{}/attachments/{}/{}?token={}", host, self.cipher_uuid, self.id, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self, host: &str) -> Value {
|
pub fn to_json(&self, host: &str) -> Value {
|
||||||
|
@ -51,6 +52,7 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::auth::{encode_jwt, generate_file_download_claims};
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
|
|
13
src/util.rs
13
src/util.rs
|
@ -1,7 +1,10 @@
|
||||||
//
|
//
|
||||||
// Web Headers and caching
|
// Web Headers and caching
|
||||||
//
|
//
|
||||||
use std::io::{Cursor, ErrorKind};
|
use std::{
|
||||||
|
io::{Cursor, ErrorKind},
|
||||||
|
ops::Deref,
|
||||||
|
};
|
||||||
|
|
||||||
use rocket::{
|
use rocket::{
|
||||||
fairing::{Fairing, Info, Kind},
|
fairing::{Fairing, Info, Kind},
|
||||||
|
@ -209,6 +212,14 @@ impl std::fmt::Display for SafeString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for SafeString {
|
||||||
|
type Target = String;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<Path> for SafeString {
|
impl AsRef<Path> for SafeString {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_ref(&self) -> &Path {
|
fn as_ref(&self) -> &Path {
|
||||||
|
|
Loading…
Reference in a new issue