Start using rustfmt and some style changes to make some lines shorter

This commit is contained in:
Daniel García
2018-12-30 23:34:31 +01:00
parent 72ed05c4a4
commit 30e768613b
26 changed files with 1172 additions and 898 deletions

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
max_width = 120

View File

@ -3,13 +3,13 @@ use rocket_contrib::json::Json;
use crate::db::models::*;
use crate::db::DbConn;
use crate::api::{EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData, UpdateType, WebSocketUsers};
use crate::auth::{Headers, decode_invite_jwt, InviteJWTClaims};
use crate::api::{EmptyResult, JsonResult, JsonUpcase, Notify, NumberOrString, PasswordData, UpdateType};
use crate::auth::{decode_invite_jwt, Headers, InviteJWTClaims};
use crate::mail;
use crate::CONFIG;
use rocket::{Route, State};
use rocket::Route;
pub fn routes() -> Vec<Route> {
routes![
@ -74,9 +74,9 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
} else {
let token = match &data.Token {
Some(token) => token,
None => err!("No valid invite token")
None => err!("No valid invite token"),
};
let claims: InviteJWTClaims = decode_invite_jwt(&token)?;
if &claims.email == &data.Email {
user
@ -257,7 +257,7 @@ struct KeyData {
}
#[post("/accounts/key", data = "<data>")]
fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult {
let data: KeyData = data.into_inner().data;
if !headers.user.check_valid_password(&data.MasterPasswordHash) {
@ -294,7 +294,15 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, ws:
err!("The cipher is not owned by the user")
}
update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &ws, UpdateType::SyncCipherUpdate)?
update_cipher_from_data(
&mut saved_cipher,
cipher_data,
&headers,
false,
&conn,
&nt,
UpdateType::CipherUpdate,
)?
}
// Update user data

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
use rocket::State;
use rocket_contrib::json::Json;
use serde_json::Value;
use crate::db::DbConn;
use crate::db::models::*;
use crate::db::DbConn;
use crate::api::{JsonResult, EmptyResult, JsonUpcase, WebSocketUsers, UpdateType};
use crate::api::{EmptyResult, JsonResult, JsonUpcase, Notify, UpdateType};
use crate::auth::Headers;
use rocket::Route;
@ -39,7 +38,7 @@ fn get_folders(headers: Headers, conn: DbConn) -> JsonResult {
fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
let folder = match Folder::find_by_uuid(&uuid, &conn) {
Some(folder) => folder,
_ => err!("Invalid folder")
_ => err!("Invalid folder"),
};
if folder.user_uuid != headers.user.uuid {
@ -53,33 +52,33 @@ fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
#[allow(non_snake_case)]
pub struct FolderData {
pub Name: String
pub Name: String,
}
#[post("/folders", data = "<data>")]
fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
let data: FolderData = data.into_inner().data;
let mut folder = Folder::new(headers.user.uuid.clone(), data.Name);
folder.save(&conn)?;
ws.send_folder_update(UpdateType::SyncFolderCreate, &folder);
nt.send_folder_update(UpdateType::FolderCreate, &folder);
Ok(Json(folder.to_json()))
}
#[post("/folders/<uuid>", data = "<data>")]
fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
put_folder(uuid, data, headers, conn, ws)
fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
put_folder(uuid, data, headers, conn, nt)
}
#[put("/folders/<uuid>", data = "<data>")]
fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
let data: FolderData = data.into_inner().data;
let mut folder = match Folder::find_by_uuid(&uuid, &conn) {
Some(folder) => folder,
_ => err!("Invalid folder")
_ => err!("Invalid folder"),
};
if folder.user_uuid != headers.user.uuid {
@ -89,21 +88,21 @@ fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn
folder.name = data.Name;
folder.save(&conn)?;
ws.send_folder_update(UpdateType::SyncFolderUpdate, &folder);
nt.send_folder_update(UpdateType::FolderUpdate, &folder);
Ok(Json(folder.to_json()))
}
#[post("/folders/<uuid>/delete")]
fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
delete_folder(uuid, headers, conn, ws)
fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult {
delete_folder(uuid, headers, conn, nt)
}
#[delete("/folders/<uuid>")]
fn delete_folder(uuid: String, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult {
let folder = match Folder::find_by_uuid(&uuid, &conn) {
Some(folder) => folder,
_ => err!("Invalid folder")
_ => err!("Invalid folder"),
};
if folder.user_uuid != headers.user.uuid {
@ -113,6 +112,6 @@ fn delete_folder(uuid: String, headers: Headers, conn: DbConn, ws: State<WebSock
// Delete the actual folder entry
folder.delete(&conn)?;
ws.send_folder_update(UpdateType::SyncFolderDelete, &folder);
nt.send_folder_update(UpdateType::FolderDelete, &folder);
Ok(())
}

View File

@ -8,7 +8,6 @@ pub fn routes() -> Vec<Route> {
let mut mod_routes = routes![
clear_device_token,
put_device_token,
get_eq_domains,
post_eq_domains,
put_eq_domains,
@ -25,9 +24,9 @@ pub fn routes() -> Vec<Route> {
routes
}
///
/// Move this somewhere else
///
//
// Move this somewhere else
//
use rocket::Route;
use rocket_contrib::json::Json;

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,6 @@ use rocket_contrib::json::Json;
use serde_json;
use serde_json::Value;
use crate::db::{
models::{TwoFactor, TwoFactorType, User},
DbConn,
@ -111,11 +110,7 @@ struct DisableTwoFactorData {
}
#[post("/two-factor/disable", data = "<data>")]
fn disable_twofactor(
data: JsonUpcase<DisableTwoFactorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: DisableTwoFactorData = data.into_inner().data;
let password_hash = data.MasterPasswordHash;
@ -137,20 +132,12 @@ fn disable_twofactor(
}
#[put("/two-factor/disable", data = "<data>")]
fn disable_twofactor_put(
data: JsonUpcase<DisableTwoFactorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
fn disable_twofactor_put(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult {
disable_twofactor(data, headers, conn)
}
#[post("/two-factor/get-authenticator", data = "<data>")]
fn generate_authenticator(
data: JsonUpcase<PasswordData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: PasswordData = data.into_inner().data;
if !headers.user.check_valid_password(&data.MasterPasswordHash) {
@ -181,11 +168,7 @@ struct EnableAuthenticatorData {
}
#[post("/two-factor/authenticator", data = "<data>")]
fn activate_authenticator(
data: JsonUpcase<EnableAuthenticatorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
fn activate_authenticator(data: JsonUpcase<EnableAuthenticatorData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: EnableAuthenticatorData = data.into_inner().data;
let password_hash = data.MasterPasswordHash;
let key = data.Key;
@ -228,11 +211,7 @@ fn activate_authenticator(
}
#[put("/two-factor/authenticator", data = "<data>")]
fn activate_authenticator_put(
data: JsonUpcase<EnableAuthenticatorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
fn activate_authenticator_put(data: JsonUpcase<EnableAuthenticatorData>, headers: Headers, conn: DbConn) -> JsonResult {
activate_authenticator(data, headers, conn)
}
@ -338,11 +317,8 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn)
err!("Invalid password");
}
let tf_challenge = TwoFactor::find_by_user_and_type(
&headers.user.uuid,
TwoFactorType::U2fRegisterChallenge as i32,
&conn,
);
let tf_challenge =
TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2fRegisterChallenge as i32, &conn);
if let Some(tf_challenge) = tf_challenge {
let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?;
@ -394,17 +370,14 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn)
#[put("/two-factor/u2f", data = "<data>")]
fn activate_u2f_put(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult {
activate_u2f(data,headers, conn)
activate_u2f(data, headers, conn)
}
fn _create_u2f_challenge(user_uuid: &str, type_: TwoFactorType, conn: &DbConn) -> Challenge {
let challenge = U2F.generate_challenge().unwrap();
TwoFactor::new(
user_uuid.into(),
type_,
serde_json::to_string(&challenge).unwrap(),
).save(conn)
TwoFactor::new(user_uuid.into(), type_, serde_json::to_string(&challenge).unwrap())
.save(conn)
.expect("Error saving challenge");
challenge
@ -478,8 +451,7 @@ pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> Emp
let mut _counter: u32 = 0;
for registration in registrations {
let response =
U2F.sign_response(challenge.clone(), registration, response.clone(), _counter);
let response = U2F.sign_response(challenge.clone(), registration, response.clone(), _counter);
match response {
Ok(new_counter) => {
_counter = new_counter;
@ -495,7 +467,6 @@ pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> Emp
err!("error verifying response")
}
#[derive(Deserialize, Debug)]
#[allow(non_snake_case)]
struct EnableYubikeyData {
@ -515,8 +486,8 @@ pub struct YubikeyMetadata {
pub Nfc: bool,
}
use yubico::Yubico;
use yubico::config::Config;
use yubico::Yubico;
fn parse_yubikeys(data: &EnableYubikeyData) -> Vec<String> {
let mut yubikeys: Vec<String> = Vec::new();
@ -548,7 +519,7 @@ fn jsonify_yubikeys(yubikeys: Vec<String>) -> serde_json::Value {
let mut result = json!({});
for (i, key) in yubikeys.into_iter().enumerate() {
result[format!("Key{}", i+1)] = Value::String(key);
result[format!("Key{}", i + 1)] = Value::String(key);
}
result
@ -556,16 +527,17 @@ fn jsonify_yubikeys(yubikeys: Vec<String>) -> serde_json::Value {
fn verify_yubikey_otp(otp: String) -> JsonResult {
if !CONFIG.yubico_cred_set {
err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. \
Yubikey OTP Disabled")
err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled")
}
let yubico = Yubico::new();
let config = Config::default().set_client_id(CONFIG.yubico_client_id.to_owned()).set_key(CONFIG.yubico_secret_key.to_owned());
let config = Config::default()
.set_client_id(CONFIG.yubico_client_id.to_owned())
.set_key(CONFIG.yubico_secret_key.to_owned());
let result = match CONFIG.yubico_server {
Some(ref server) => yubico.verify(otp, config.set_api_hosts(vec![server.to_owned()])),
None => yubico.verify(otp, config)
None => yubico.verify(otp, config),
};
match result {
@ -577,8 +549,7 @@ fn verify_yubikey_otp(otp: String) -> JsonResult {
#[post("/two-factor/get-yubikey", data = "<data>")]
fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
if !CONFIG.yubico_cred_set {
err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. \
Yubikey OTP Disabled")
err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled")
}
let data: PasswordData = data.into_inner().data;
@ -619,11 +590,7 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn:
}
// Check if we already have some data
let yubikey_data = TwoFactor::find_by_user_and_type(
&headers.user.uuid,
TwoFactorType::YubiKey as i32,
&conn,
);
let yubikey_data = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::YubiKey as i32, &conn);
if let Some(yubikey_data) = yubikey_data {
yubikey_data.delete(&conn)?;
@ -642,7 +609,7 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn:
for yubikey in &yubikeys {
if yubikey.len() == 12 {
// YubiKey ID
continue
continue;
}
let result = verify_yubikey_otp(yubikey.to_owned());
@ -692,7 +659,8 @@ pub fn validate_yubikey_login(user_uuid: &str, response: &str, conn: &DbConn) ->
None => err!("No YubiKey devices registered"),
};
let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&twofactor.data).expect("Can't parse Yubikey Metadata");
let yubikey_metadata: YubikeyMetadata =
serde_json::from_str(&twofactor.data).expect("Can't parse Yubikey Metadata");
let response_id = &response[..12];
if !yubikey_metadata.Keys.contains(&response_id.to_owned()) {

View File

@ -145,7 +145,12 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
Ok(Json(result))
}
fn twofactor_auth(user_uuid: &str, data: &ConnectData, device: &mut Device, conn: &DbConn) -> ApiResult<Option<String>> {
fn twofactor_auth(
user_uuid: &str,
data: &ConnectData,
device: &mut Device,
conn: &DbConn,
) -> ApiResult<Option<String>> {
let twofactors_raw = TwoFactor::find_by_user(user_uuid, conn);
// Remove u2f challenge twofactors (impl detail)
let twofactors: Vec<_> = twofactors_raw.iter().filter(|tf| tf.type_ < 1000).collect();
@ -252,13 +257,14 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api
result["TwoFactorProviders2"][provider.to_string()] = Value::Object(map);
}
Some(TwoFactorType::YubiKey) => {
let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::YubiKey as i32, &conn) {
Some(tf_type @ TwoFactorType::YubiKey) => {
let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, &conn) {
Some(tf) => tf,
None => err!("No YubiKey devices registered"),
};
let yubikey_metadata: two_factor::YubikeyMetadata = serde_json::from_str(&twofactor.data).expect("Can't parse Yubikey Metadata");
let yubikey_metadata: two_factor::YubikeyMetadata =
serde_json::from_str(&twofactor.data).expect("Can't parse Yubikey Metadata");
let mut map = JsonMap::new();
map.insert("Nfc".into(), Value::Bool(yubikey_metadata.Nfc));

View File

@ -1,17 +1,17 @@
pub(crate) mod core;
mod admin;
pub(crate) mod core;
mod icons;
mod identity;
mod web;
mod notifications;
mod web;
pub use self::core::routes as core_routes;
pub use self::admin::routes as admin_routes;
pub use self::core::routes as core_routes;
pub use self::icons::routes as icons_routes;
pub use self::identity::routes as identity_routes;
pub use self::web::routes as web_routes;
pub use self::notifications::routes as notifications_routes;
pub use self::notifications::{start_notification_server, WebSocketUsers, UpdateType};
pub use self::notifications::{start_notification_server, Notify, UpdateType};
pub use self::web::routes as web_routes;
use rocket_contrib::json::Json;
use serde_json::Value;
@ -28,7 +28,7 @@ type JsonUpcase<T> = Json<util::UpCase<T>>;
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct PasswordData {
MasterPasswordHash: String
MasterPasswordHash: String,
}
#[derive(Deserialize, Debug, Clone)]
@ -42,14 +42,14 @@ impl NumberOrString {
fn into_string(self) -> String {
match self {
NumberOrString::Number(n) => n.to_string(),
NumberOrString::String(s) => s
NumberOrString::String(s) => s,
}
}
fn into_i32(self) -> Option<i32> {
match self {
NumberOrString::Number(n) => Some(n),
NumberOrString::String(s) => s.parse().ok()
}
NumberOrString::String(s) => s.parse().ok(),
}
}
}

View File

@ -14,7 +14,7 @@ pub fn routes() -> Vec<Route> {
#[get("/hub")]
fn websockets_err() -> JsonResult {
err!("'/notifications/hub' should be proxied towards the websocket server, otherwise notifications will not work. Go to the README for more info.")
err!("'/notifications/hub' should be proxied to the websocket server or notifications won't work. Go to the README for more info.")
}
#[post("/hub/negotiate")]
@ -40,9 +40,9 @@ fn negotiate(_headers: Headers, _conn: DbConn) -> JsonResult {
})))
}
///
/// Websockets server
///
//
// Websockets server
//
use std::sync::Arc;
use std::thread;
@ -94,9 +94,7 @@ fn serialize_date(date: NaiveDateTime) -> Value {
use byteorder::{BigEndian, WriteBytesExt};
let mut bs = [0u8; 8];
bs.as_mut()
.write_i64::<BigEndian>(timestamp)
.expect("Unable to write");
bs.as_mut().write_i64::<BigEndian>(timestamp).expect("Unable to write");
// -1 is Timestamp
// https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
@ -142,12 +140,7 @@ impl Handler for WSHandler {
use crate::auth;
let claims = match auth::decode_jwt(access_token) {
Ok(claims) => claims,
Err(_) => {
return Err(ws::Error::new(
ws::ErrorKind::Internal,
"Invalid access token provided",
))
}
Err(_) => return Err(ws::Error::new(ws::ErrorKind::Internal, "Invalid access token provided")),
};
// Assign the user to the handler
@ -158,11 +151,9 @@ impl Handler for WSHandler {
let handler_insert = self.out.clone();
let handler_update = self.out.clone();
self.users.map.upsert(
user_uuid,
|| vec![handler_insert],
|ref mut v| v.push(handler_update),
);
self.users
.map
.upsert(user_uuid, || vec![handler_insert], |ref mut v| v.push(handler_update));
// Schedule a ping to keep the connection alive
self.out.timeout(PING_MS, PING)
@ -238,7 +229,7 @@ impl Factory for WSFactory {
#[derive(Clone)]
pub struct WebSocketUsers {
pub map: Arc<CHashMap<String, Vec<Sender>>>,
map: Arc<CHashMap<String, Vec<Sender>>>,
}
impl WebSocketUsers {
@ -338,32 +329,32 @@ fn create_ping() -> Vec<u8> {
#[allow(dead_code)]
pub enum UpdateType {
SyncCipherUpdate = 0,
SyncCipherCreate = 1,
SyncLoginDelete = 2,
SyncFolderDelete = 3,
SyncCiphers = 4,
CipherUpdate = 0,
CipherCreate = 1,
LoginDelete = 2,
FolderDelete = 3,
Ciphers = 4,
SyncVault = 5,
SyncOrgKeys = 6,
SyncFolderCreate = 7,
SyncFolderUpdate = 8,
SyncCipherDelete = 9,
Vault = 5,
OrgKeys = 6,
FolderCreate = 7,
FolderUpdate = 8,
CipherDelete = 9,
SyncSettings = 10,
LogOut = 11,
}
use rocket::State;
pub type Notify<'a> = State<'a, WebSocketUsers>;
pub fn start_notification_server() -> WebSocketUsers {
let factory = WSFactory::init();
let users = factory.users.clone();
if CONFIG.websocket_enabled {
thread::spawn(move || {
WebSocket::new(factory)
.unwrap()
.listen(&CONFIG.websocket_url)
.unwrap();
WebSocket::new(factory).unwrap().listen(&CONFIG.websocket_url).unwrap();
});
}

View File

@ -1,6 +1,6 @@
///
/// JWT Handling
///
//
// JWT Handling
//
use crate::util::read_file;
use chrono::Duration;
@ -15,17 +15,20 @@ const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
lazy_static! {
pub static ref DEFAULT_VALIDITY: Duration = Duration::hours(2);
pub static ref JWT_ISSUER: String = CONFIG.domain.clone();
static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM);
static ref PRIVATE_RSA_KEY: Vec<u8> = match read_file(&CONFIG.private_rsa_key) {
Ok(key) => key,
Err(e) => panic!("Error loading private RSA Key from {}\n Error: {}", CONFIG.private_rsa_key, e)
Err(e) => panic!(
"Error loading private RSA Key from {}\n Error: {}",
CONFIG.private_rsa_key, e
),
};
static ref PUBLIC_RSA_KEY: Vec<u8> = match read_file(&CONFIG.public_rsa_key) {
Ok(key) => key,
Err(e) => panic!("Error loading public RSA Key from {}\n Error: {}", CONFIG.public_rsa_key, e)
Err(e) => panic!(
"Error loading public RSA Key from {}\n Error: {}",
CONFIG.public_rsa_key, e
),
};
}
@ -65,7 +68,7 @@ pub fn decode_invite_jwt(token: &str) -> Result<InviteJWTClaims, Error> {
algorithms: vec![JWT_ALGORITHM],
};
jsonwebtoken::decode(token, &PUBLIC_RSA_KEY, &validation)
jsonwebtoken::decode(token, &PUBLIC_RSA_KEY, &validation)
.map(|d| d.claims)
.map_res("Error decoding invite JWT")
}
@ -117,14 +120,14 @@ pub struct InviteJWTClaims {
pub user_org_id: Option<String>,
}
///
/// Bearer token authentication
///
//
// Bearer token authentication
//
use rocket::request::{self, FromRequest, Request};
use rocket::Outcome;
use rocket::request::{self, Request, FromRequest};
use crate::db::models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization};
use crate::db::DbConn;
use crate::db::models::{User, UserOrganization, UserOrgType, UserOrgStatus, Device};
pub struct Headers {
pub host: String,
@ -227,10 +230,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
Some(Ok(org_id)) => {
let conn = match request.guard::<DbConn>() {
Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB")
_ => err_handler!("Error getting DB"),
};
let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
let user = headers.user;
let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn) {
Some(user) => {
if user.status == UserOrgStatus::Confirmed as i32 {
user
@ -238,17 +242,18 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
err_handler!("The current user isn't confirmed member of the organization")
}
}
None => err_handler!("The current user isn't member of the organization")
None => err_handler!("The current user isn't member of the organization"),
};
Outcome::Success(Self {
host: headers.host,
device: headers.device,
user: headers.user,
user,
org_user_type: {
if let Some(org_usr_type) = UserOrgType::from_i32(org_user.type_) {
org_usr_type
} else { // This should only happen if the DB is corrupted
} else {
// This should only happen if the DB is corrupted
err_handler!("Unknown user type in the database")
}
},
@ -319,9 +324,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for OwnerHeaders {
}
}
///
/// Client IP address detection
///
//
// Client IP address detection
//
use std::net::IpAddr;
pub struct ClientIp {

View File

@ -1,6 +1,6 @@
///
/// PBKDF2 derivation
///
//
// PBKDF2 derivation
//
use ring::{digest, pbkdf2};
@ -19,9 +19,9 @@ pub fn verify_password_hash(secret: &[u8], salt: &[u8], previous: &[u8], iterati
pbkdf2::verify(DIGEST_ALG, iterations, salt, secret, previous).is_ok()
}
///
/// Random values
///
//
// Random values
//
pub fn get_random_64() -> Vec<u8> {
get_random(vec![0u8; 64])
@ -30,7 +30,9 @@ pub fn get_random_64() -> Vec<u8> {
pub fn get_random(mut array: Vec<u8>) -> Vec<u8> {
use ring::rand::{SecureRandom, SystemRandom};
SystemRandom::new().fill(&mut array).expect("Error generating random values");
SystemRandom::new()
.fill(&mut array)
.expect("Error generating random values");
array
}

View File

@ -1,9 +1,9 @@
use std::ops::Deref;
use diesel::{Connection as DieselConnection, ConnectionError};
use diesel::sqlite::SqliteConnection;
use diesel::r2d2;
use diesel::r2d2::ConnectionManager;
use diesel::sqlite::SqliteConnection;
use diesel::{Connection as DieselConnection, ConnectionError};
use rocket::http::Status;
use rocket::request::{self, FromRequest};
@ -20,16 +20,14 @@ type Pool = r2d2::Pool<ConnectionManager<Connection>>;
/// Connection request guard type: a wrapper around an r2d2 pooled connection.
pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<Connection>>);
pub mod schema;
pub mod models;
pub mod schema;
/// Initializes a database pool.
pub fn init_pool() -> Pool {
let manager = ConnectionManager::new(&*CONFIG.database_url);
r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool")
r2d2::Pool::builder().build(manager).expect("Failed to create pool")
}
pub fn get_connection() -> Result<Connection, ConnectionError> {
@ -46,7 +44,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
let pool = request.guard::<State<Pool>>()?;
match pool.get() {
Ok(conn) => Outcome::Success(DbConn(conn)),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ()))
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
}
}
}

View File

@ -49,10 +49,10 @@ impl Attachment {
}
}
use crate::db::schema::attachments;
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
use crate::db::DbConn;
use crate::db::schema::attachments;
use crate::api::EmptyResult;
use crate::error::MapResult;
@ -68,12 +68,11 @@ impl Attachment {
pub fn delete(self, conn: &DbConn) -> EmptyResult {
crate::util::retry(
|| diesel::delete(attachments::table.filter(attachments::id.eq(&self.id)))
.execute(&**conn),
|| diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))).execute(&**conn),
10,
)
.map_res("Error deleting attachment")?;
crate::util::delete_file(&self.get_file_path())?;
Ok(())
}
@ -86,7 +85,10 @@ impl Attachment {
}
pub fn find_by_id(id: &str, conn: &DbConn) -> Option<Self> {
attachments::table.filter(attachments::id.eq(id)).first::<Self>(&**conn).ok()
attachments::table
.filter(attachments::id.eq(id))
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_cipher(cipher_uuid: &str, conn: &DbConn) -> Vec<Self> {

View File

@ -1,7 +1,9 @@
use chrono::{NaiveDateTime, Utc};
use serde_json::Value;
use super::{Attachment, CollectionCipher, FolderCipher, Organization, User, UserOrgStatus, UserOrgType, UserOrganization};
use super::{
Attachment, CollectionCipher, FolderCipher, Organization, User, UserOrgStatus, UserOrgType, UserOrganization,
};
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
#[table_name = "ciphers"]
@ -79,11 +81,15 @@ impl Cipher {
let fields_json: Value = if let Some(ref fields) = self.fields {
serde_json::from_str(fields).unwrap()
} else { Value::Null };
} else {
Value::Null
};
let password_history_json: Value = if let Some(ref password_history) = self.password_history {
serde_json::from_str(password_history).unwrap()
} else { Value::Null };
} else {
Value::Null
};
let mut data_json: Value = serde_json::from_str(&self.data).unwrap();
@ -137,15 +143,16 @@ impl Cipher {
Some(ref user_uuid) => {
User::update_uuid_revision(&user_uuid, conn);
user_uuids.push(user_uuid.clone())
},
None => { // Belongs to Organization, need to update affected users
}
None => {
// Belongs to Organization, need to update affected users
if let Some(ref org_uuid) = self.organization_uuid {
UserOrganization::find_by_cipher_and_org(&self.uuid, &org_uuid, conn)
.iter()
.for_each(|user_org| {
User::update_uuid_revision(&user_org.user_uuid, conn);
user_uuids.push(user_org.user_uuid.clone())
});
.iter()
.for_each(|user_org| {
User::update_uuid_revision(&user_org.user_uuid, conn);
user_uuids.push(user_org.user_uuid.clone())
});
}
}
};
@ -207,7 +214,9 @@ impl Cipher {
Ok(()) //nothing to do
} else {
self.update_users_revision(conn);
if let Some(current_folder) = FolderCipher::find_by_folder_and_cipher(&current_folder, &self.uuid, &conn) {
if let Some(current_folder) =
FolderCipher::find_by_folder_and_cipher(&current_folder, &self.uuid, &conn)
{
current_folder.delete(&conn)?;
}
FolderCipher::new(&new_folder, &self.uuid).save(&conn)
@ -227,64 +236,79 @@ impl Cipher {
pub fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
ciphers::table
.filter(ciphers::uuid.eq(&self.uuid))
.left_join(users_organizations::table.on(
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()).and(
users_organizations::user_uuid.eq(user_uuid)
.filter(ciphers::uuid.eq(&self.uuid))
.left_join(
users_organizations::table.on(ciphers::organization_uuid
.eq(users_organizations::org_uuid.nullable())
.and(users_organizations::user_uuid.eq(user_uuid))),
)
))
.left_join(ciphers_collections::table)
.left_join(users_collections::table.on(
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
))
.filter(ciphers::user_uuid.eq(user_uuid).or( // Cipher owner
users_organizations::access_all.eq(true).or( // access_all in Organization
users_organizations::type_.le(UserOrgType::Admin as i32).or( // Org admin or owner
users_collections::user_uuid.eq(user_uuid).and(
users_collections::read_only.eq(false) //R/W access to collection
)
)
.left_join(ciphers_collections::table)
.left_join(
users_collections::table
.on(ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)),
)
))
.select(ciphers::all_columns)
.first::<Self>(&**conn).ok().is_some()
.filter(ciphers::user_uuid.eq(user_uuid).or(
// Cipher owner
users_organizations::access_all.eq(true).or(
// access_all in Organization
users_organizations::type_.le(UserOrgType::Admin as i32).or(
// Org admin or owner
users_collections::user_uuid.eq(user_uuid).and(
users_collections::read_only.eq(false), //R/W access to collection
),
),
),
))
.select(ciphers::all_columns)
.first::<Self>(&**conn)
.ok()
.is_some()
}
pub fn is_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
ciphers::table
.filter(ciphers::uuid.eq(&self.uuid))
.left_join(users_organizations::table.on(
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()).and(
users_organizations::user_uuid.eq(user_uuid)
.filter(ciphers::uuid.eq(&self.uuid))
.left_join(
users_organizations::table.on(ciphers::organization_uuid
.eq(users_organizations::org_uuid.nullable())
.and(users_organizations::user_uuid.eq(user_uuid))),
)
))
.left_join(ciphers_collections::table)
.left_join(users_collections::table.on(
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
))
.filter(ciphers::user_uuid.eq(user_uuid).or( // Cipher owner
users_organizations::access_all.eq(true).or( // access_all in Organization
users_organizations::type_.le(UserOrgType::Admin as i32).or( // Org admin or owner
users_collections::user_uuid.eq(user_uuid) // Access to Collection
)
.left_join(ciphers_collections::table)
.left_join(
users_collections::table
.on(ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)),
)
))
.select(ciphers::all_columns)
.first::<Self>(&**conn).ok().is_some()
.filter(ciphers::user_uuid.eq(user_uuid).or(
// Cipher owner
users_organizations::access_all.eq(true).or(
// access_all in Organization
users_organizations::type_.le(UserOrgType::Admin as i32).or(
// Org admin or owner
users_collections::user_uuid.eq(user_uuid), // Access to Collection
),
),
))
.select(ciphers::all_columns)
.first::<Self>(&**conn)
.ok()
.is_some()
}
pub fn get_folder_uuid(&self, user_uuid: &str, conn: &DbConn) -> Option<String> {
folders_ciphers::table.inner_join(folders::table)
folders_ciphers::table
.inner_join(folders::table)
.filter(folders::user_uuid.eq(&user_uuid))
.filter(folders_ciphers::cipher_uuid.eq(&self.uuid))
.select(folders_ciphers::folder_uuid)
.first::<String>(&**conn).ok()
.first::<String>(&**conn)
.ok()
}
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
ciphers::table
.filter(ciphers::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
// Find all ciphers accessible to user

View File

@ -1,6 +1,6 @@
use serde_json::Value;
use super::{Organization, UserOrganization, UserOrgType, UserOrgStatus};
use super::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
#[table_name = "collections"]
@ -33,10 +33,10 @@ impl Collection {
}
}
use crate::db::schema::*;
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
use crate::db::DbConn;
use crate::db::schema::*;
use crate::api::EmptyResult;
use crate::error::MapResult;
@ -46,27 +46,24 @@ impl Collection {
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
// Update affected users revision
UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn)
.iter()
.for_each(|user_org| {
User::update_uuid_revision(&user_org.user_uuid, conn);
});
.iter()
.for_each(|user_org| {
User::update_uuid_revision(&user_org.user_uuid, conn);
});
diesel::replace_into(collections::table)
.values(&*self)
.execute(&**conn)
.map_res("Error saving collection")
.values(&*self)
.execute(&**conn)
.map_res("Error saving collection")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
CollectionCipher::delete_all_by_collection(&self.uuid, &conn)?;
CollectionUser::delete_all_by_collection(&self.uuid, &conn)?;
diesel::delete(
collections::table.filter(
collections::uuid.eq(self.uuid)
)
).execute(&**conn)
.map_res("Error deleting collection")
diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error deleting collection")
}
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
@ -79,7 +76,8 @@ impl Collection {
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
collections::table
.filter(collections::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
@ -106,21 +104,26 @@ impl Collection {
}
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
Self::find_by_user_uuid(user_uuid, conn).into_iter().filter(|c| c.org_uuid == org_uuid).collect()
Self::find_by_user_uuid(user_uuid, conn)
.into_iter()
.filter(|c| c.org_uuid == org_uuid)
.collect()
}
pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
collections::table
.filter(collections::org_uuid.eq(org_uuid))
.load::<Self>(&**conn).expect("Error loading collections")
.load::<Self>(&**conn)
.expect("Error loading collections")
}
pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
collections::table
.filter(collections::uuid.eq(uuid))
.filter(collections::org_uuid.eq(org_uuid))
.select(collections::all_columns)
.first::<Self>(&**conn).ok()
.filter(collections::uuid.eq(uuid))
.filter(collections::org_uuid.eq(org_uuid))
.select(collections::all_columns)
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
@ -150,22 +153,25 @@ impl Collection {
match UserOrganization::find_by_user_and_org(&user_uuid, &self.org_uuid, &conn) {
None => false, // Not in Org
Some(user_org) => {
if user_org.access_all {
true
} else {
users_collections::table.inner_join(collections::table)
.filter(users_collections::collection_uuid.eq(&self.uuid))
.filter(users_collections::user_uuid.eq(&user_uuid))
.filter(users_collections::read_only.eq(false))
.select(collections::all_columns)
.first::<Self>(&**conn).ok().is_some() // Read only or no access to collection
}
if user_org.access_all {
true
} else {
users_collections::table
.inner_join(collections::table)
.filter(users_collections::collection_uuid.eq(&self.uuid))
.filter(users_collections::user_uuid.eq(&user_uuid))
.filter(users_collections::read_only.eq(false))
.select(collections::all_columns)
.first::<Self>(&**conn)
.ok()
.is_some() // Read only or no access to collection
}
}
}
}
}
use super::User;
use super::User;
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
#[table_name = "users_collections"]
@ -186,70 +192,72 @@ impl CollectionUser {
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
.filter(collections::org_uuid.eq(org_uuid))
.select(users_collections::all_columns)
.load::<Self>(&**conn).expect("Error loading users_collections")
.load::<Self>(&**conn)
.expect("Error loading users_collections")
}
pub fn save(user_uuid: &str, collection_uuid: &str, read_only:bool, conn: &DbConn) -> EmptyResult {
pub fn save(user_uuid: &str, collection_uuid: &str, read_only: bool, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&user_uuid, conn);
diesel::replace_into(users_collections::table)
.values((
users_collections::user_uuid.eq(user_uuid),
users_collections::collection_uuid.eq(collection_uuid),
users_collections::read_only.eq(read_only),
)).execute(&**conn)
.map_res("Error adding user to collection")
.values((
users_collections::user_uuid.eq(user_uuid),
users_collections::collection_uuid.eq(collection_uuid),
users_collections::read_only.eq(read_only),
))
.execute(&**conn)
.map_res("Error adding user to collection")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn);
diesel::delete(users_collections::table
.filter(users_collections::user_uuid.eq(&self.user_uuid))
.filter(users_collections::collection_uuid.eq(&self.collection_uuid)))
diesel::delete(
users_collections::table
.filter(users_collections::user_uuid.eq(&self.user_uuid))
.filter(users_collections::collection_uuid.eq(&self.collection_uuid)),
)
.execute(&**conn)
.map_res("Error removing user from collection")
}
pub fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid))
.select(users_collections::all_columns)
.load::<Self>(&**conn).expect("Error loading users_collections")
.filter(users_collections::collection_uuid.eq(collection_uuid))
.select(users_collections::all_columns)
.load::<Self>(&**conn)
.expect("Error loading users_collections")
}
pub fn find_by_collection_and_user(collection_uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid))
.filter(users_collections::user_uuid.eq(user_uuid))
.select(users_collections::all_columns)
.first::<Self>(&**conn).ok()
.filter(users_collections::collection_uuid.eq(collection_uuid))
.filter(users_collections::user_uuid.eq(user_uuid))
.select(users_collections::all_columns)
.first::<Self>(&**conn)
.ok()
}
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
CollectionUser::find_by_collection(&collection_uuid, conn)
.iter()
.for_each(|collection| {
User::update_uuid_revision(&collection.user_uuid, conn)
});
.iter()
.for_each(|collection| User::update_uuid_revision(&collection.user_uuid, conn));
diesel::delete(users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid))
).execute(&**conn)
.map_res("Error deleting users from collection")
diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid)))
.execute(&**conn)
.map_res("Error deleting users from collection")
}
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&user_uuid, conn);
diesel::delete(users_collections::table
.filter(users_collections::user_uuid.eq(user_uuid))
).execute(&**conn)
.map_res("Error removing user from collections")
diesel::delete(users_collections::table.filter(users_collections::user_uuid.eq(user_uuid)))
.execute(&**conn)
.map_res("Error removing user from collections")
}
}
use super::Cipher;
use super::Cipher;
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
#[table_name = "ciphers_collections"]
@ -268,29 +276,30 @@ impl CollectionCipher {
.values((
ciphers_collections::cipher_uuid.eq(cipher_uuid),
ciphers_collections::collection_uuid.eq(collection_uuid),
)).execute(&**conn)
))
.execute(&**conn)
.map_res("Error adding cipher to collection")
}
pub fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(ciphers_collections::table
.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))
.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
.execute(&**conn)
.map_res("Error deleting cipher from collection")
diesel::delete(
ciphers_collections::table
.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))
.filter(ciphers_collections::collection_uuid.eq(collection_uuid)),
)
.execute(&**conn)
.map_res("Error deleting cipher from collection")
}
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(ciphers_collections::table
.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))
).execute(&**conn)
.map_res("Error removing cipher from collections")
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
.execute(&**conn)
.map_res("Error removing cipher from collections")
}
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(ciphers_collections::table
.filter(ciphers_collections::collection_uuid.eq(collection_uuid))
).execute(&**conn)
.map_res("Error removing ciphers from collection")
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
.execute(&**conn)
.map_res("Error removing ciphers from collection")
}
}
}

View File

@ -44,8 +44,8 @@ impl Device {
}
pub fn refresh_twofactor_remember(&mut self) -> String {
use data_encoding::BASE64;
use crate::crypto;
use data_encoding::BASE64;
let twofactor_remember = BASE64.encode(&crypto::get_random(vec![0u8; 180]));
self.twofactor_remember = Some(twofactor_remember.clone());
@ -57,12 +57,11 @@ impl Device {
self.twofactor_remember = None;
}
pub fn refresh_tokens(&mut self, user: &super::User, orgs: Vec<super::UserOrganization>) -> (String, i64) {
// If there is no refresh token, we create one
if self.refresh_token.is_empty() {
use data_encoding::BASE64URL;
use crate::crypto;
use data_encoding::BASE64URL;
self.refresh_token = BASE64URL.encode(&crypto::get_random_64());
}
@ -105,10 +104,10 @@ impl Device {
}
}
use crate::db::schema::devices;
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
use crate::db::DbConn;
use crate::db::schema::devices;
use crate::api::EmptyResult;
use crate::error::MapResult;
@ -119,21 +118,16 @@ impl Device {
self.updated_at = Utc::now().naive_utc();
crate::util::retry(
|| {
diesel::replace_into(devices::table)
.values(&*self)
.execute(&**conn)
},
|| diesel::replace_into(devices::table).values(&*self).execute(&**conn),
10,
)
.map_res("Error saving device")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
diesel::delete(devices::table.filter(
devices::uuid.eq(self.uuid)
)).execute(&**conn)
.map_res("Error removing device")
diesel::delete(devices::table.filter(devices::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error removing device")
}
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
@ -146,18 +140,21 @@ impl Device {
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
devices::table
.filter(devices::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_refresh_token(refresh_token: &str, conn: &DbConn) -> Option<Self> {
devices::table
.filter(devices::refresh_token.eq(refresh_token))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
devices::table
.filter(devices::user_uuid.eq(user_uuid))
.load::<Self>(&**conn).expect("Error loading devices")
.load::<Self>(&**conn)
.expect("Error loading devices")
}
}

View File

@ -1,7 +1,7 @@
use chrono::{NaiveDateTime, Utc};
use serde_json::Value;
use super::{User, Cipher};
use super::{Cipher, User};
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
#[table_name = "folders"]
@ -61,10 +61,10 @@ impl FolderCipher {
}
}
use crate::db::schema::{folders, folders_ciphers};
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
use crate::db::DbConn;
use crate::db::schema::{folders, folders_ciphers};
use crate::api::EmptyResult;
use crate::error::MapResult;
@ -76,20 +76,18 @@ impl Folder {
self.updated_at = Utc::now().naive_utc();
diesel::replace_into(folders::table)
.values(&*self).execute(&**conn)
.map_res("Error saving folder")
.values(&*self)
.execute(&**conn)
.map_res("Error saving folder")
}
pub fn delete(&self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn);
FolderCipher::delete_all_by_folder(&self.uuid, &conn)?;
diesel::delete(
folders::table.filter(
folders::uuid.eq(&self.uuid)
)
).execute(&**conn)
.map_res("Error deleting folder")
diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid)))
.execute(&**conn)
.map_res("Error deleting folder")
}
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
@ -102,56 +100,60 @@ impl Folder {
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
folders::table
.filter(folders::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
folders::table
.filter(folders::user_uuid.eq(user_uuid))
.load::<Self>(&**conn).expect("Error loading folders")
.load::<Self>(&**conn)
.expect("Error loading folders")
}
}
impl FolderCipher {
pub fn save(&self, conn: &DbConn) -> EmptyResult {
diesel::replace_into(folders_ciphers::table)
.values(&*self)
.execute(&**conn)
.map_res("Error adding cipher to folder")
.values(&*self)
.execute(&**conn)
.map_res("Error adding cipher to folder")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
diesel::delete(folders_ciphers::table
.filter(folders_ciphers::cipher_uuid.eq(self.cipher_uuid))
.filter(folders_ciphers::folder_uuid.eq(self.folder_uuid))
).execute(&**conn)
diesel::delete(
folders_ciphers::table
.filter(folders_ciphers::cipher_uuid.eq(self.cipher_uuid))
.filter(folders_ciphers::folder_uuid.eq(self.folder_uuid)),
)
.execute(&**conn)
.map_res("Error removing cipher from folder")
}
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(folders_ciphers::table
.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))
).execute(&**conn)
.map_res("Error removing cipher from folders")
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
.execute(&**conn)
.map_res("Error removing cipher from folders")
}
pub fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
).execute(&**conn)
.map_res("Error removing ciphers from folder")
diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid)))
.execute(&**conn)
.map_res("Error removing ciphers from folder")
}
pub fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> Option<Self> {
folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
.load::<Self>(&**conn).expect("Error loading folders")
.load::<Self>(&**conn)
.expect("Error loading folders")
}
}

View File

@ -10,10 +10,10 @@ mod two_factor;
pub use self::attachment::Attachment;
pub use self::cipher::Cipher;
pub use self::collection::{Collection, CollectionCipher, CollectionUser};
pub use self::device::Device;
pub use self::folder::{Folder, FolderCipher};
pub use self::user::{User, Invitation};
pub use self::organization::Organization;
pub use self::organization::{UserOrganization, UserOrgStatus, UserOrgType};
pub use self::collection::{Collection, CollectionUser, CollectionCipher};
pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::organization::{UserOrgStatus, UserOrgType, UserOrganization};
pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::user::{Invitation, User};

View File

@ -1,7 +1,7 @@
use std::cmp::Ordering;
use serde_json::Value;
use std::cmp::Ordering;
use super::{User, CollectionUser};
use super::{CollectionUser, User};
#[derive(Debug, Identifiable, Queryable, Insertable)]
#[table_name = "organizations"]
@ -32,9 +32,7 @@ pub enum UserOrgStatus {
Confirmed = 2,
}
#[derive(Copy, Clone)]
#[derive(PartialEq)]
#[derive(Eq)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum UserOrgType {
Owner = 0,
Admin = 1,
@ -51,13 +49,13 @@ impl Ord for UserOrgType {
UserOrgType::Owner => Ordering::Greater,
UserOrgType::Admin => match other {
UserOrgType::Owner => Ordering::Less,
_ => Ordering::Greater
_ => Ordering::Greater,
},
UserOrgType::Manager => match other {
UserOrgType::Owner | UserOrgType::Admin => Ordering::Less,
_ => Ordering::Greater
_ => Ordering::Greater,
},
UserOrgType::User => Ordering::Less
UserOrgType::User => Ordering::Less,
}
}
}
@ -78,7 +76,7 @@ impl PartialEq<i32> for UserOrgType {
impl PartialOrd<i32> for UserOrgType {
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
if let Some(other) = Self::from_i32(*other) {
return Some(self.cmp(&other))
return Some(self.cmp(&other));
}
None
}
@ -96,7 +94,6 @@ impl PartialOrd<i32> for UserOrgType {
_ => true,
}
}
}
impl PartialEq<UserOrgType> for i32 {
@ -108,7 +105,7 @@ impl PartialEq<UserOrgType> for i32 {
impl PartialOrd<UserOrgType> for i32 {
fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> {
if let Some(self_type) = UserOrgType::from_i32(*self) {
return Some(self_type.cmp(other))
return Some(self_type.cmp(other));
}
None
}
@ -126,7 +123,6 @@ impl PartialOrd<UserOrgType> for i32 {
_ => false,
}
}
}
impl UserOrgType {
@ -149,7 +145,6 @@ impl UserOrgType {
_ => None,
}
}
}
/// Local methods
@ -208,11 +203,10 @@ impl UserOrganization {
}
}
use crate::db::schema::{ciphers_collections, organizations, users_collections, users_organizations};
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
use crate::db::DbConn;
use crate::db::schema::{organizations, users_organizations, users_collections, ciphers_collections};
use crate::api::EmptyResult;
use crate::error::MapResult;
@ -221,13 +215,14 @@ use crate::error::MapResult;
impl Organization {
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
UserOrganization::find_by_org(&self.uuid, conn)
.iter()
.for_each(|user_org| {
User::update_uuid_revision(&user_org.user_uuid, conn);
});
.iter()
.for_each(|user_org| {
User::update_uuid_revision(&user_org.user_uuid, conn);
});
diesel::replace_into(organizations::table)
.values(&*self).execute(&**conn)
.values(&*self)
.execute(&**conn)
.map_res("Error saving organization")
}
@ -238,18 +233,16 @@ impl Organization {
Collection::delete_all_by_organization(&self.uuid, &conn)?;
UserOrganization::delete_all_by_organization(&self.uuid, &conn)?;
diesel::delete(
organizations::table.filter(
organizations::uuid.eq(self.uuid)
)
).execute(&**conn)
.map_res("Error saving organization")
diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error saving organization")
}
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
organizations::table
.filter(organizations::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
}
@ -314,12 +307,15 @@ impl UserOrganization {
})
}
pub fn to_json_details(&self, conn: &DbConn) -> Value {
let coll_uuids = if self.access_all {
pub fn to_json_details(&self, conn: &DbConn) -> Value {
let coll_uuids = if self.access_all {
vec![] // If we have complete access, no need to fill the array
} else {
let collections = CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn);
collections.iter().map(|c| json!({"Id": c.collection_uuid, "ReadOnly": c.read_only})).collect()
collections
.iter()
.map(|c| json!({"Id": c.collection_uuid, "ReadOnly": c.read_only}))
.collect()
};
json!({
@ -339,8 +335,9 @@ impl UserOrganization {
User::update_uuid_revision(&self.user_uuid, conn);
diesel::replace_into(users_organizations::table)
.values(&*self).execute(&**conn)
.map_res("Error adding user to organization")
.values(&*self)
.execute(&**conn)
.map_res("Error adding user to organization")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
@ -348,12 +345,9 @@ impl UserOrganization {
CollectionUser::delete_all_by_user(&self.user_uuid, &conn)?;
diesel::delete(
users_organizations::table.filter(
users_organizations::uuid.eq(self.uuid)
)
).execute(&**conn)
.map_res("Error removing user from organization")
diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error removing user from organization")
}
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
@ -377,54 +371,62 @@ impl UserOrganization {
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
users_organizations::table
.filter(users_organizations::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
users_organizations::table
.filter(users_organizations::uuid.eq(uuid))
.filter(users_organizations::org_uuid.eq(org_uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
.load::<Self>(&**conn).unwrap_or_default()
.load::<Self>(&**conn)
.unwrap_or_default()
}
pub fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Invited as i32))
.load::<Self>(&**conn).unwrap_or_default()
.load::<Self>(&**conn)
.unwrap_or_default()
}
pub fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.load::<Self>(&**conn).unwrap_or_default()
.load::<Self>(&**conn)
.unwrap_or_default()
}
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid))
.load::<Self>(&**conn).expect("Error loading user organizations")
.load::<Self>(&**conn)
.expect("Error loading user organizations")
}
pub fn find_by_org_and_type(org_uuid: &str, type_: i32, conn: &DbConn) -> Vec<Self> {
users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid))
.filter(users_organizations::type_.eq(type_))
.load::<Self>(&**conn).expect("Error loading user organizations")
.load::<Self>(&**conn)
.expect("Error loading user organizations")
}
pub fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::org_uuid.eq(org_uuid))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
@ -461,7 +463,4 @@ impl UserOrganization {
.select(users_organizations::all_columns)
.load::<Self>(&**conn).expect("Error loading user organizations")
}
}

View File

@ -50,7 +50,7 @@ impl TwoFactor {
let decoded_secret = match BASE32.decode(totp_secret) {
Ok(s) => s,
Err(_) => return false
Err(_) => return false,
};
let generated = totp_raw_now(&decoded_secret, 6, 0, 30, &HashType::SHA1);
@ -74,10 +74,10 @@ impl TwoFactor {
}
}
use crate::db::schema::twofactor;
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
use crate::db::DbConn;
use crate::db::schema::twofactor;
use crate::api::EmptyResult;
use crate::error::MapResult;
@ -92,33 +92,29 @@ impl TwoFactor {
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
diesel::delete(
twofactor::table.filter(
twofactor::uuid.eq(self.uuid)
)
).execute(&**conn)
.map_res("Error deleting twofactor")
diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error deleting twofactor")
}
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
twofactor::table
.filter(twofactor::user_uuid.eq(user_uuid))
.load::<Self>(&**conn).expect("Error loading twofactor")
.load::<Self>(&**conn)
.expect("Error loading twofactor")
}
pub fn find_by_user_and_type(user_uuid: &str, type_: i32, conn: &DbConn) -> Option<Self> {
twofactor::table
.filter(twofactor::user_uuid.eq(user_uuid))
.filter(twofactor::type_.eq(type_))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
diesel::delete(
twofactor::table.filter(
twofactor::user_uuid.eq(user_uuid)
)
).execute(&**conn)
.map_res("Error deleting twofactors")
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
.execute(&**conn)
.map_res("Error deleting twofactors")
}
}

View File

@ -4,7 +4,6 @@ use serde_json::Value;
use crate::crypto;
use crate::CONFIG;
#[derive(Debug, Identifiable, Queryable, Insertable)]
#[table_name = "users"]
#[primary_key(uuid)]
@ -24,7 +23,7 @@ pub struct User {
pub key: String,
pub private_key: Option<String>,
pub public_key: Option<String>,
#[column_name = "totp_secret"]
_totp_secret: Option<String>,
pub totp_recover: Option<String>,
@ -33,7 +32,7 @@ pub struct User {
pub equivalent_domains: String,
pub excluded_globals: String,
pub client_kdf_type: i32,
pub client_kdf_iter: i32,
}
@ -64,23 +63,25 @@ impl User {
password_hint: None,
private_key: None,
public_key: None,
_totp_secret: None,
totp_recover: None,
equivalent_domains: "[]".to_string(),
excluded_globals: "[]".to_string(),
client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
}
}
pub fn check_valid_password(&self, password: &str) -> bool {
crypto::verify_password_hash(password.as_bytes(),
&self.salt,
&self.password_hash,
self.password_iterations as u32)
crypto::verify_password_hash(
password.as_bytes(),
&self.salt,
&self.password_hash,
self.password_iterations as u32,
)
}
pub fn check_valid_recovery_code(&self, recovery_code: &str) -> bool {
@ -92,9 +93,7 @@ impl User {
}
pub fn set_password(&mut self, password: &str) {
self.password_hash = crypto::hash_password(password.as_bytes(),
&self.salt,
self.password_iterations as u32);
self.password_hash = crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations as u32);
}
pub fn reset_security_stamp(&mut self) {
@ -102,11 +101,11 @@ impl User {
}
}
use super::{Cipher, Device, Folder, TwoFactor, UserOrgType, UserOrganization};
use crate::db::schema::{invitations, users};
use crate::db::DbConn;
use diesel;
use diesel::prelude::*;
use crate::db::DbConn;
use crate::db::schema::{users, invitations};
use super::{Cipher, Folder, Device, UserOrganization, UserOrgType, TwoFactor};
use crate::api::EmptyResult;
use crate::error::MapResult;
@ -114,7 +113,7 @@ use crate::error::MapResult;
/// Database methods
impl User {
pub fn to_json(&self, conn: &DbConn) -> Value {
use super::{UserOrganization, TwoFactor};
use super::{TwoFactor, UserOrganization};
let orgs = UserOrganization::find_by_user(&self.uuid, conn);
let orgs_json: Vec<Value> = orgs.iter().map(|c| c.to_json(&conn)).collect();
@ -137,22 +136,20 @@ impl User {
})
}
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.updated_at = Utc::now().naive_utc();
diesel::replace_into(users::table) // Insert or update
.values(&*self).execute(&**conn)
.map_res("Error saving user")
.values(&*self)
.execute(&**conn)
.map_res("Error saving user")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
for user_org in UserOrganization::find_by_user(&self.uuid, &*conn) {
if user_org.type_ == UserOrgType::Owner {
if UserOrganization::find_by_org_and_type(
&user_org.org_uuid,
UserOrgType::Owner as i32, &conn
).len() <= 1 {
let owner_type = UserOrgType::Owner as i32;
if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, &conn).len() <= 1 {
err!("Can't delete last owner")
}
}
@ -165,15 +162,14 @@ impl User {
TwoFactor::delete_all_by_user(&self.uuid, &*conn)?;
Invitation::take(&self.email, &*conn); // Delete invitation if any
diesel::delete(users::table.filter(
users::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error deleting user")
diesel::delete(users::table.filter(users::uuid.eq(self.uuid)))
.execute(&**conn)
.map_res("Error deleting user")
}
pub fn update_uuid_revision(uuid: &str, conn: &DbConn) {
if let Some(mut user) = User::find_by_uuid(&uuid, conn) {
if user.update_revision(conn).is_err(){
if user.update_revision(conn).is_err() {
warn!("Failed to update revision for {}", user.email);
};
};
@ -181,32 +177,26 @@ impl User {
pub fn update_revision(&mut self, conn: &DbConn) -> EmptyResult {
self.updated_at = Utc::now().naive_utc();
diesel::update(
users::table.filter(
users::uuid.eq(&self.uuid)
)
)
.set(users::updated_at.eq(&self.updated_at))
.execute(&**conn)
.map_res("Error updating user revision")
diesel::update(users::table.filter(users::uuid.eq(&self.uuid)))
.set(users::updated_at.eq(&self.updated_at))
.execute(&**conn)
.map_res("Error updating user revision")
}
pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
let lower_mail = mail.to_lowercase();
users::table
.filter(users::email.eq(lower_mail))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
users::table
.filter(users::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
users::table.filter(users::uuid.eq(uuid)).first::<Self>(&**conn).ok()
}
pub fn get_all(conn: &DbConn) -> Vec<Self> {
users::table
.load::<Self>(&**conn).expect("Error loading users")
users::table.load::<Self>(&**conn).expect("Error loading users")
}
}
@ -219,37 +209,35 @@ pub struct Invitation {
impl Invitation {
pub fn new(email: String) -> Self {
Self {
email
}
Self { email }
}
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
diesel::replace_into(invitations::table)
.values(&*self)
.execute(&**conn)
.map_res("Error saving invitation")
.values(&*self)
.execute(&**conn)
.map_res("Error saving invitation")
}
pub fn delete(self, conn: &DbConn) -> EmptyResult {
diesel::delete(invitations::table.filter(
invitations::email.eq(self.email)))
.execute(&**conn)
.map_res("Error deleting invitation")
diesel::delete(invitations::table.filter(invitations::email.eq(self.email)))
.execute(&**conn)
.map_res("Error deleting invitation")
}
pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
let lower_mail = mail.to_lowercase();
invitations::table
.filter(invitations::email.eq(lower_mail))
.first::<Self>(&**conn).ok()
.first::<Self>(&**conn)
.ok()
}
pub fn take(mail: &str, conn: &DbConn) -> bool {
CONFIG.invitations_allowed &&
match Self::find_by_mail(mail, &conn) {
Some(invitation) => invitation.delete(&conn).is_ok(),
None => false
}
CONFIG.invitations_allowed
&& match Self::find_by_mail(mail, &conn) {
Some(invitation) => invitation.delete(&conn).is_ok(),
None => false,
}
}
}
}

View File

@ -8,7 +8,7 @@ macro_rules! make_error {
#[derive(Display)]
enum ErrorKind { $($name( $ty )),+ }
pub struct Error { message: String, error: ErrorKind }
$(impl From<$ty> for Error {
fn from(err: $ty) -> Self { Error::from((stringify!($name), err)) }
})+
@ -140,9 +140,9 @@ impl<'r> Responder<'r> for Error {
}
}
///
/// Error return macros
///
//
// Error return macros
//
#[macro_export]
macro_rules! err {
($msg:expr) => {{

View File

@ -1,8 +1,8 @@
use native_tls::{Protocol, TlsConnector};
use lettre::{Transport, SmtpTransport, SmtpClient, ClientTlsParameters, ClientSecurity};
use lettre::smtp::ConnectionReuseParameters;
use lettre::smtp::authentication::Credentials;
use lettre::smtp::ConnectionReuseParameters;
use lettre::{ClientSecurity, ClientTlsParameters, SmtpClient, SmtpTransport, Transport};
use lettre_email::EmailBuilder;
use native_tls::{Protocol, TlsConnector};
use crate::MailConfig;
use crate::CONFIG;
@ -22,10 +22,7 @@ fn mailer(config: &MailConfig) -> SmtpTransport {
ClientSecurity::None
};
let smtp_client = SmtpClient::new(
(config.smtp_host.as_str(), config.smtp_port),
client_security,
).unwrap();
let smtp_client = SmtpClient::new((config.smtp_host.as_str(), config.smtp_port), client_security).unwrap();
let smtp_client = match (&config.smtp_username, &config.smtp_password) {
(Some(user), Some(pass)) => smtp_client.credentials(Credentials::new(user.clone(), pass.clone())),
@ -40,15 +37,20 @@ fn mailer(config: &MailConfig) -> SmtpTransport {
pub fn send_password_hint(address: &str, hint: Option<String>, config: &MailConfig) -> EmptyResult {
let (subject, body) = if let Some(hint) = hint {
("Your master password hint",
format!(
"You (or someone) recently requested your master password hint.\n\n\
Your hint is: \"{}\"\n\n\
If you did not request your master password hint you can safely ignore this email.\n",
hint))
(
"Your master password hint",
format!(
"You (or someone) recently requested your master password hint.\n\n\
Your hint is: \"{}\"\n\n\
If you did not request your master password hint you can safely ignore this email.\n",
hint
),
)
} else {
("Sorry, you have no password hint...",
"Sorry, you have not specified any password hint...\n".into())
(
"Sorry, you have no password hint...",
"Sorry, you have not specified any password hint...\n".into(),
)
};
let email = EmailBuilder::new()
@ -65,8 +67,15 @@ pub fn send_password_hint(address: &str, hint: Option<String>, config: &MailConf
.and(Ok(()))
}
pub fn send_invite(address: &str, org_id: &str, org_user_id: &str, token: &str, org_name: &str, config: &MailConfig) -> EmptyResult {
let (subject, body) = {
pub fn send_invite(
address: &str,
org_id: &str,
org_user_id: &str,
token: &str,
org_name: &str,
config: &MailConfig,
) -> EmptyResult {
let (subject, body) = {
(format!("Join {}", &org_name),
format!(
"<html>
@ -91,4 +100,4 @@ pub fn send_invite(address: &str, org_id: &str, org_user_id: &str, token: &str,
.send(email.into())
.map_err(|e| Error::new("Error sending invite email", e.to_string()))
.and(Ok(()))
}
}

View File

@ -2,26 +2,39 @@
#![recursion_limit = "128"]
#![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings
#[macro_use] extern crate rocket;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate serde_json;
#[macro_use] extern crate log;
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate lazy_static;
#[macro_use] extern crate derive_more;
#[macro_use] extern crate num_derive;
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate log;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate derive_more;
#[macro_use]
extern crate num_derive;
use std::{path::Path, process::{exit, Command}};
use rocket::Rocket;
use std::{
path::Path,
process::{exit, Command},
};
#[macro_use] mod error;
mod util;
#[macro_use]
mod error;
mod api;
mod db;
mod crypto;
mod auth;
mod crypto;
mod db;
mod mail;
mod util;
fn init_rocket() -> Rocket {
rocket::ignite()
@ -67,20 +80,20 @@ fn main() {
fn init_logging() -> Result<(), fern::InitError> {
let mut logger = fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d %H:%M:%S]"),
record.target(),
record.level(),
message
))
})
.level(log::LevelFilter::Debug)
.level_for("hyper", log::LevelFilter::Warn)
.level_for("ws", log::LevelFilter::Info)
.level_for("multipart", log::LevelFilter::Info)
.chain(std::io::stdout());
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d %H:%M:%S]"),
record.target(),
record.level(),
message
))
})
.level(log::LevelFilter::Debug)
.level_for("hyper", log::LevelFilter::Warn)
.level_for("ws", log::LevelFilter::Info)
.level_for("multipart", log::LevelFilter::Info)
.chain(std::io::stdout());
if let Some(log_file) = CONFIG.log_file.as_ref() {
logger = logger.chain(fern::log_file(log_file)?);
@ -93,7 +106,9 @@ fn init_logging() -> Result<(), fern::InitError> {
}
#[cfg(not(feature = "enable_syslog"))]
fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch { logger }
fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch {
logger
}
#[cfg(feature = "enable_syslog")]
fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch {
@ -127,44 +142,60 @@ fn check_db() {
// Turn on WAL in SQLite
use diesel::RunQueryDsl;
let connection = db::get_connection().expect("Can't conect to DB");
diesel::sql_query("PRAGMA journal_mode=wal").execute(&connection).expect("Failed to turn on WAL");
diesel::sql_query("PRAGMA journal_mode=wal")
.execute(&connection)
.expect("Failed to turn on WAL");
}
fn check_rsa_keys() {
// If the RSA keys don't exist, try to create them
if !util::file_exists(&CONFIG.private_rsa_key)
|| !util::file_exists(&CONFIG.public_rsa_key) {
if !util::file_exists(&CONFIG.private_rsa_key) || !util::file_exists(&CONFIG.public_rsa_key) {
info!("JWT keys don't exist, checking if OpenSSL is available...");
Command::new("openssl")
.arg("version")
.output().unwrap_or_else(|_| {
Command::new("openssl").arg("version").output().unwrap_or_else(|_| {
info!("Can't create keys because OpenSSL is not available, make sure it's installed and available on the PATH");
exit(1);
});
info!("OpenSSL detected, creating keys...");
let mut success = Command::new("openssl").arg("genrsa")
.arg("-out").arg(&CONFIG.private_rsa_key_pem)
.output().expect("Failed to create private pem file")
.status.success();
let mut success = Command::new("openssl")
.arg("genrsa")
.arg("-out")
.arg(&CONFIG.private_rsa_key_pem)
.output()
.expect("Failed to create private pem file")
.status
.success();
success &= Command::new("openssl").arg("rsa")
.arg("-in").arg(&CONFIG.private_rsa_key_pem)
.arg("-outform").arg("DER")
.arg("-out").arg(&CONFIG.private_rsa_key)
.output().expect("Failed to create private der file")
.status.success();
success &= Command::new("openssl")
.arg("rsa")
.arg("-in")
.arg(&CONFIG.private_rsa_key_pem)
.arg("-outform")
.arg("DER")
.arg("-out")
.arg(&CONFIG.private_rsa_key)
.output()
.expect("Failed to create private der file")
.status
.success();
success &= Command::new("openssl").arg("rsa")
.arg("-in").arg(&CONFIG.private_rsa_key)
.arg("-inform").arg("DER")
success &= Command::new("openssl")
.arg("rsa")
.arg("-in")
.arg(&CONFIG.private_rsa_key)
.arg("-inform")
.arg("DER")
.arg("-RSAPublicKey_out")
.arg("-outform").arg("DER")
.arg("-out").arg(&CONFIG.public_rsa_key)
.output().expect("Failed to create public der file")
.status.success();
.arg("-outform")
.arg("DER")
.arg("-out")
.arg(&CONFIG.public_rsa_key)
.output()
.expect("Failed to create public der file")
.status
.success();
if success {
info!("Keys created correctly.");
@ -219,13 +250,7 @@ impl MailConfig {
});
let smtp_ssl = get_env_or("SMTP_SSL", true);
let smtp_port = get_env("SMTP_PORT").unwrap_or_else(||
if smtp_ssl {
587u16
} else {
25u16
}
);
let smtp_port = get_env("SMTP_PORT").unwrap_or_else(|| if smtp_ssl { 587u16 } else { 25u16 });
let smtp_username = get_env("SMTP_USERNAME");
let smtp_password = get_env("SMTP_PASSWORD").or_else(|| {
@ -319,8 +344,12 @@ impl Config {
web_vault_enabled: get_env_or("WEB_VAULT_ENABLED", true),
websocket_enabled: get_env_or("WEBSOCKET_ENABLED", false),
websocket_url: format!("{}:{}", get_env_or("WEBSOCKET_ADDRESS", "0.0.0.0".to_string()), get_env_or("WEBSOCKET_PORT", 3012)),
websocket_url: format!(
"{}:{}",
get_env_or("WEBSOCKET_ADDRESS", "0.0.0.0".to_string()),
get_env_or("WEBSOCKET_PORT", 3012)
),
extended_logging: get_env_or("EXTENDED_LOGGING", true),
log_file: get_env("LOG_FILE"),

View File

@ -1,10 +1,10 @@
///
/// Web Headers
///
//
// Web Headers
//
use rocket::fairing::{Fairing, Info, Kind};
use rocket::{Request, Response};
pub struct AppHeaders ();
pub struct AppHeaders();
impl Fairing for AppHeaders {
fn info(&self) -> Info {
@ -29,10 +29,9 @@ impl Fairing for AppHeaders {
}
}
///
/// File handling
///
//
// File handling
//
use std::fs::{self, File};
use std::io::{Read, Result as IOResult};
use std::path::Path;
@ -43,7 +42,7 @@ pub fn file_exists(path: &str) -> bool {
pub fn read_file(path: &str) -> IOResult<Vec<u8>> {
let mut contents: Vec<u8> = Vec::new();
let mut file = File::open(Path::new(path))?;
file.read_to_end(&mut contents)?;
@ -75,7 +74,7 @@ pub fn get_display_size(size: i32) -> String {
} else {
break;
}
};
}
// Round to two decimals
size = (size * 100.).round() / 100.;
@ -86,13 +85,12 @@ pub fn get_uuid() -> String {
uuid::Uuid::new_v4().to_string()
}
//
// String util methods
//
///
/// String util methods
///
use std::str::FromStr;
use std::ops::Try;
use std::str::FromStr;
pub fn upcase_first(s: &str) -> String {
let mut c = s.chars();
@ -102,7 +100,11 @@ pub fn upcase_first(s: &str) -> String {
}
}
pub fn try_parse_string<S, T, U>(string: impl Try<Ok = S, Error=U>) -> Option<T> where S: AsRef<str>, T: FromStr {
pub fn try_parse_string<S, T, U>(string: impl Try<Ok = S, Error = U>) -> Option<T>
where
S: AsRef<str>,
T: FromStr,
{
if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::<T>()) {
Some(value)
} else {
@ -110,7 +112,11 @@ pub fn try_parse_string<S, T, U>(string: impl Try<Ok = S, Error=U>) -> Option<T>
}
}
pub fn try_parse_string_or<S, T, U>(string: impl Try<Ok = S, Error=U>, default: T) -> T where S: AsRef<str>, T: FromStr {
pub fn try_parse_string_or<S, T, U>(string: impl Try<Ok = S, Error = U>, default: T) -> T
where
S: AsRef<str>,
T: FromStr,
{
if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::<T>()) {
value
} else {
@ -118,24 +124,29 @@ pub fn try_parse_string_or<S, T, U>(string: impl Try<Ok = S, Error=U>, default:
}
}
///
/// Env methods
///
//
// Env methods
//
use std::env;
pub fn get_env<V>(key: &str) -> Option<V> where V: FromStr {
pub fn get_env<V>(key: &str) -> Option<V>
where
V: FromStr,
{
try_parse_string(env::var(key))
}
pub fn get_env_or<V>(key: &str, default: V) -> V where V: FromStr {
pub fn get_env_or<V>(key: &str, default: V) -> V
where
V: FromStr,
{
try_parse_string_or(env::var(key), default)
}
///
/// Date util methods
///
//
// Date util methods
//
use chrono::NaiveDateTime;
@ -145,9 +156,9 @@ pub fn format_date(date: &NaiveDateTime) -> String {
date.format(DATETIME_FORMAT).to_string()
}
///
/// Deserialization methods
///
//
// Deserialization methods
//
use std::fmt;
@ -163,10 +174,11 @@ pub struct UpCase<T: DeserializeOwned> {
pub data: T,
}
/// https://github.com/serde-rs/serde/issues/586
// https://github.com/serde-rs/serde/issues/586
pub fn upcase_deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where T: DeserializeOwned,
D: Deserializer<'de>
where
T: DeserializeOwned,
D: Deserializer<'de>,
{
let d = deserializer.deserialize_any(UpCaseVisitor)?;
T::deserialize(d).map_err(de::Error::custom)
@ -182,7 +194,8 @@ impl<'de> Visitor<'de> for UpCaseVisitor {
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where A: MapAccess<'de>
where
A: MapAccess<'de>,
{
let mut result_map = JsonMap::new();
@ -194,7 +207,9 @@ impl<'de> Visitor<'de> for UpCaseVisitor {
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where A: SeqAccess<'de> {
where
A: SeqAccess<'de>,
{
let mut result_seq = Vec::<Value>::new();
while let Some(value) = seq.next_element()? {
@ -208,13 +223,12 @@ impl<'de> Visitor<'de> for UpCaseVisitor {
fn upcase_value(value: &Value) -> Value {
if let Some(map) = value.as_object() {
let mut new_value = json!({});
for (key, val) in map {
let processed_key = _process_key(key);
new_value[processed_key] = upcase_value(val);
}
new_value
} else if let Some(array) = value.as_array() {
// Initialize array with null values
let mut new_value = json!(vec![Value::Null; array.len()]);
@ -223,7 +237,6 @@ fn upcase_value(value: &Value) -> Value {
new_value[index] = upcase_value(val);
}
new_value
} else {
value.clone()
}