Skip to content

Commit

Permalink
Merge branch 'main' into 2FA-Email-Subject-Line-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
aureateflux authored Jul 13, 2023
2 parents 493d720 + 61f9081 commit cf0e76b
Show file tree
Hide file tree
Showing 24 changed files with 399 additions and 263 deletions.
450 changes: 235 additions & 215 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ once_cell = "1.18.0"

# Numerical libraries
num-traits = "0.2.15"
num-derive = "0.3.3"
num-derive = "0.4.0"

# Web framework
rocket = { version = "0.5.0-rc.3", features = ["tls", "json"], default-features = false }
Expand All @@ -68,11 +68,11 @@ dashmap = "5.4.0"

# Async futures
futures = "0.3.28"
tokio = { version = "1.28.2", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }
tokio = { version = "1.29.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }

# A generic serialization/deserialization framework
serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.97"
serde = { version = "1.0.166", features = ["derive"] }
serde_json = "1.0.99"

# A safe, extensible ORM and Query builder
diesel = { version = "2.1.0", features = ["chrono", "r2d2"] }
Expand All @@ -87,11 +87,11 @@ rand = { version = "0.8.5", features = ["small_rng"] }
ring = "0.16.20"

# UUID generation
uuid = { version = "1.3.4", features = ["v4"] }
uuid = { version = "1.4.0", features = ["v4"] }

# Date and time libraries
chrono = { version = "0.4.26", features = ["clock", "serde"], default-features = false }
chrono-tz = "0.8.2"
chrono-tz = "0.8.3"
time = "0.3.22"

# Job scheduler
Expand All @@ -117,7 +117,7 @@ url = "2.4.0"

# Email libraries
lettre = { version = "0.10.4", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
percent-encoding = "2.2.0" # URL encoding library used for URL's in the emails
percent-encoding = "2.3.0" # URL encoding library used for URL's in the emails
email_address = "0.2.4"

# HTML Template library
Expand Down Expand Up @@ -146,7 +146,7 @@ openssl = "0.10.55"
pico-args = "0.5.0"

# Macro ident concatenation
paste = "1.0.12"
paste = "1.0.13"
governor = "0.5.1"

# Check client versions for specific features.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Pull the docker image and mount a volume from the host for persistent storage:

```sh
docker pull vaultwarden/server:latest
docker run -d --name vaultwarden -v /vw-data/:/data/ -p 80:80 vaultwarden/server:latest
docker run -d --name vaultwarden -v /vw-data/:/data/ --restart unless-stopped -p 80:80 vaultwarden/server:latest
```
This will preserve any persistent data under /vw-data/, you can adapt the path to whatever suits you.

Expand Down
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fn version_from_git_info() -> Result<String, std::io::Error> {
// Combined version
if let Some(exact) = exact_tag {
Ok(exact)
} else if &branch != "main" && &branch != "master" {
} else if &branch != "main" && &branch != "master" && &branch != "HEAD" {
Ok(format!("{last_tag}-{rev_short} ({branch})"))
} else {
Ok(format!("{last_tag}-{rev_short}"))
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE collections ADD COLUMN external_id TEXT;
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE collections ADD COLUMN external_id TEXT;
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE collections ADD COLUMN external_id TEXT;
9 changes: 9 additions & 0 deletions src/api/core/ciphers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,15 @@ async fn share_cipher_by_uuid(
/// redirects to the same location as before the v2 API.
#[get("/ciphers/<uuid>/attachment/<attachment_id>")]
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 {
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
Some(_) => err!("Attachment doesn't belong to cipher"),
Expand Down
48 changes: 39 additions & 9 deletions src/api/core/organizations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ struct NewCollectionData {
Name: String,
Groups: Vec<NewCollectionObjectData>,
Users: Vec<NewCollectionObjectData>,
ExternalId: Option<String>,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -168,7 +169,7 @@ async fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, mut co

let org = Organization::new(data.Name, data.BillingEmail, private_key, public_key);
let mut user_org = UserOrganization::new(headers.user.uuid, org.uuid.clone());
let collection = Collection::new(org.uuid.clone(), data.CollectionName);
let collection = Collection::new(org.uuid.clone(), data.CollectionName, None);

user_org.akey = data.Key;
user_org.access_all = true;
Expand Down Expand Up @@ -390,7 +391,7 @@ async fn post_organization_collections(
None => err!("Can't find organization details"),
};

let collection = Collection::new(org.uuid, data.Name);
let collection = Collection::new(org.uuid, data.Name, data.ExternalId);
collection.save(&mut conn).await?;

log_event(
Expand Down Expand Up @@ -424,6 +425,10 @@ async fn post_organization_collections(
.await?;
}

if headers.org_user.atype == UserOrgType::Manager && !headers.org_user.access_all {
CollectionUser::save(&headers.org_user.user_uuid, &collection.uuid, false, false, &mut conn).await?;
}

Ok(Json(collection.to_json()))
}

Expand Down Expand Up @@ -463,6 +468,7 @@ async fn post_organization_collection_update(
}

collection.name = data.Name;
collection.external_id = data.ExternalId;
collection.save(&mut conn).await?;

log_event(
Expand Down Expand Up @@ -1576,7 +1582,7 @@ async fn post_org_import(

let mut collections = Vec::new();
for coll in data.Collections {
let collection = Collection::new(org_id.clone(), coll.Name);
let collection = Collection::new(org_id.clone(), coll.Name, coll.ExternalId);
if collection.save(&mut conn).await.is_err() {
collections.push(Err(Error::new("Failed to create Collection", "Failed to create Collection")));
} else {
Expand Down Expand Up @@ -2578,11 +2584,15 @@ async fn put_user_groups(
err!("Group support is disabled");
}

match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
Some(_) => { /* Do nothing */ }
let user_org = match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
Some(uo) => uo,
_ => 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?;

let assigned_group_ids = data.into_inner().data;
Expand Down Expand Up @@ -2628,16 +2638,24 @@ async fn delete_group_user(
err!("Group support is disabled");
}

match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
Some(_) => { /* Do nothing */ }
let user_org = match UserOrganization::find_by_uuid(org_user_id, &mut conn).await {
Some(uo) => uo,
_ => err!("User could not be found!"),
};

match Group::find_by_uuid(group_id, &mut conn).await {
Some(_) => { /* Do nothing */ }
if user_org.org_uuid != org_id {
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!"),
};

if group.organizations_uuid != org_id {
err!("Group doesn't belong to organization");
}

log_event(
EventType::OrganizationUserUpdatedGroups as i32,
org_user_id,
Expand All @@ -2656,6 +2674,7 @@ async fn delete_group_user(
#[allow(non_snake_case)]
struct OrganizationUserResetPasswordEnrollmentRequest {
ResetPasswordKey: Option<String>,
MasterPasswordHash: Option<String>,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -2837,6 +2856,17 @@ async fn put_reset_password_enrollment(
err!("Reset password can't be withdrawed due to an enterprise policy");
}

if reset_request.ResetPasswordKey.is_some() {
match reset_request.MasterPasswordHash {
Some(password) => {
if !headers.user.check_valid_password(&password) {
err!("Invalid or wrong password")
}
}
None => err!("No password provided"),
};
}

org_user.reset_password_key = reset_request.ResetPasswordKey;
org_user.save(&mut conn).await?;

Expand Down
37 changes: 20 additions & 17 deletions src/api/core/sends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,27 +340,30 @@ async fn post_send_file_v2_data(

let mut data = data.into_inner();

if let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await {
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?;
let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await else { err!("Send not found. Unable to save the file.") };

if let Err(_err) = data.data.persist_to(&file_path).await {
data.data.move_copy_to(file_path).await?
}
let Some(send_user_id) = &send.user_uuid else {err!("Sends are only supported for users at the moment")};
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(())
}

Expand Down
2 changes: 1 addition & 1 deletion src/api/icons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {

for icon in icon_result.iconlist.iter().take(5) {
if icon.href.starts_with("data:image") {
let datauri = DataUrl::process(&icon.href).unwrap();
let Ok(datauri) = DataUrl::process(&icon.href) else {continue};
// Check if we are able to decode the data uri
let mut body = BytesMut::new();
match datauri.decode::<_, ()>(|bytes| {
Expand Down
2 changes: 1 addition & 1 deletion src/api/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Drop for WSEntryMapGuard {
}

#[get("/hub?<data..>")]
async fn websockets_hub<'r>(
fn websockets_hub<'r>(
ws: rocket_ws::WebSocket,
data: WsAccessToken,
ip: ClientIp,
Expand Down
10 changes: 8 additions & 2 deletions src/api/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde_json::Value;

use crate::{
api::{core::now, ApiResult, EmptyResult},
auth::decode_file_download,
error::Error,
util::{Cached, SafeString},
CONFIG,
Expand Down Expand Up @@ -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)
}

#[get("/attachments/<uuid>/<file_id>")]
async fn attachments(uuid: SafeString, file_id: SafeString) -> Option<NamedFile> {
#[get("/attachments/<uuid>/<file_id>?<token>")]
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()
}

Expand Down
30 changes: 30 additions & 0 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin()));

static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
let key =
Expand Down Expand Up @@ -98,6 +99,10 @@ pub fn decode_api_org(token: &str) -> Result<OrgApiKeyLoginJwtClaims, Error> {
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)]
pub struct LoginJwtClaims {
// Not before
Expand Down Expand Up @@ -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)]
pub struct BasicJwtClaims {
// Not before
Expand Down
4 changes: 3 additions & 1 deletion src/db/models/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ impl Attachment {
}

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 {
Expand All @@ -51,6 +52,7 @@ impl Attachment {
}
}

use crate::auth::{encode_jwt, generate_file_download_claims};
use crate::db::DbConn;

use crate::api::EmptyResult;
Expand Down
Loading

0 comments on commit cf0e76b

Please sign in to comment.