commit 0e2e730565124bb65036175b6e77f7fa08b59f98
parent 9a87edc1ff68043fe007db990490ac8e5cdba2b2
Author: Zack Newman <zack@philomathiclife.com>
Date: Wed, 6 Dec 2023 16:19:27 -0700
use checked arithmetic and safe conversions
Diffstat:
28 files changed, 551 insertions(+), 548 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
authors = ["Daniel GarcĂa <dani-garcia@users.noreply.github.com>", "Zack Newman <zack@philomathiclife.com>"]
categories = ["api-bindings", "web-programming::http-server"]
-description = "Fork of Vaultwarden with fewer features and pledge(2) and unveil(2) support."
+description = "Hardened fork of Vaultwarden with fewer features and pledge(2) and unveil(2) support."
documentation = "https://github.com/dani-garcia/vaultwarden/wiki"
edition = "2021"
keywords = ["password", "vaultwarden"]
@@ -9,7 +9,7 @@ license = "AGPL-3.0-only"
name = "vw_small"
publish = false
repository = "https://git.philomathiclife.com/repos/vw_small/"
-version = "1.30.0"
+version = "2.0.0"
[features]
priv_sep = ["dep:priv_sep"]
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
@@ -88,7 +88,7 @@ struct KeysData {
fn clean_password_hint(password_hint: &Option<String>) -> Option<String> {
password_hint.as_ref().and_then(|h| match h.trim() {
"" => None,
- ht => Some(ht.to_string()),
+ ht => Some(ht.to_owned()),
})
}
@@ -124,17 +124,14 @@ async fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbCo
#[post("/accounts/profile", data = "<data>")]
async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
- let data: ProfileData = data.into_inner().data;
-
+ let prof_data: ProfileData = data.into_inner().data;
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419
- if data.Name.len() > 50 {
+ if prof_data.Name.len() > 50 {
err!("The field Name must be a string with a maximum length of 50.");
}
-
let mut user = headers.user;
- user.name = data.Name;
-
+ user.name = prof_data.Name;
user.save(&conn).await?;
Ok(Json(user.to_json(&conn).await))
}
@@ -147,12 +144,11 @@ struct AvatarData {
#[put("/accounts/avatar", data = "<data>")]
async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, conn: DbConn) -> JsonResult {
- let data: AvatarData = data.into_inner().data;
-
+ let av_data: AvatarData = data.into_inner().data;
// It looks like it only supports the 6 hex color format.
// If you try to add the short value it will not show that color.
// Check and force 7 chars, including the #.
- if let Some(color) = &data.AvatarColor {
+ if let Some(ref color) = av_data.AvatarColor {
if color.len() != 7 {
err!(
"The field AvatarColor must be a HTML/Hex color code with a length of 7 characters"
@@ -161,8 +157,7 @@ async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, conn: DbConn
}
let mut user = headers.user;
- user.avatar_color = data.AvatarColor;
-
+ user.avatar_color = av_data.AvatarColor;
user.save(&conn).await?;
Ok(Json(user.to_json(&conn).await))
}
@@ -181,10 +176,10 @@ async fn get_public_keys(uuid: &str, _headers: Headers, conn: DbConn) -> JsonRes
#[post("/accounts/keys", data = "<data>")]
async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult {
- let data: KeysData = data.into_inner().data;
+ let key_data: KeysData = data.into_inner().data;
let mut user = headers.user;
- user.private_key = Some(data.EncryptedPrivateKey);
- user.public_key = Some(data.PublicKey);
+ user.private_key = Some(key_data.EncryptedPrivateKey);
+ user.public_key = Some(key_data.PublicKey);
user.save(&conn).await?;
Ok(Json(json!({
"PrivateKey": user.private_key,
@@ -209,17 +204,16 @@ async fn post_password(
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
- let data: ChangePassData = data.into_inner().data;
+ let pass_data: ChangePassData = data.into_inner().data;
let mut user = headers.user;
-
- if !user.check_valid_password(&data.MasterPasswordHash) {
+ if !user.check_valid_password(&pass_data.MasterPasswordHash) {
err!("Invalid password")
}
- user.password_hint = clean_password_hint(&data.MasterPasswordHint);
+ user.password_hint = clean_password_hint(&pass_data.MasterPasswordHint);
enforce_password_hint_setting(&user.password_hint)?;
user.set_password(
- &data.NewMasterPasswordHash,
- Some(data.Key),
+ &pass_data.NewMasterPasswordHash,
+ Some(pass_data.Key),
true,
Some(vec![
String::from("post_rotatekey"),
@@ -229,12 +223,10 @@ async fn post_password(
);
let save_result = user.save(&conn).await;
-
// Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side.
// Adding the device uuid will prevent this.
nt.send_logout(&user, Some(headers.device.uuid)).await;
-
save_result
}
@@ -245,7 +237,6 @@ struct ChangeKdfData {
KdfIterations: i32,
KdfMemory: Option<i32>,
KdfParallelism: Option<i32>,
-
MasterPasswordHash: String,
NewMasterPasswordHash: String,
Key: String,
@@ -258,34 +249,33 @@ async fn post_kdf(
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
- let data: ChangeKdfData = data.into_inner().data;
+ let kdf_data: ChangeKdfData = data.into_inner().data;
let mut user = headers.user;
-
- if !user.check_valid_password(&data.MasterPasswordHash) {
+ if !user.check_valid_password(&kdf_data.MasterPasswordHash) {
err!("Invalid password")
}
- if data.Kdf == UserKdfType::Pbkdf2 as i32 && data.KdfIterations < 100_000 {
+ if kdf_data.Kdf == i32::from(UserKdfType::Pbkdf2) && kdf_data.KdfIterations < 100_000i32 {
err!("PBKDF2 KDF iterations must be at least 100000.")
}
- if data.Kdf == UserKdfType::Argon2id as i32 {
- if data.KdfIterations < 1 {
+ if kdf_data.Kdf == i32::from(UserKdfType::Argon2id) {
+ if kdf_data.KdfIterations < 1i32 {
err!("Argon2 KDF iterations must be at least 1.")
}
- if let Some(m) = data.KdfMemory {
- if !(15..=1024).contains(&m) {
+ if let Some(m) = kdf_data.KdfMemory {
+ if !(15i32..=1024i32).contains(&m) {
err!("Argon2 memory must be between 15 MB and 1024 MB.")
}
- user.client_kdf_memory = data.KdfMemory;
+ user.client_kdf_memory = kdf_data.KdfMemory;
} else {
err!("Argon2 memory parameter is required.")
}
- if let Some(p) = data.KdfParallelism {
- if !(1..=16).contains(&p) {
+ if let Some(p) = kdf_data.KdfParallelism {
+ if !(1i32..=16i32).contains(&p) {
err!("Argon2 parallelism must be between 1 and 16.")
}
- user.client_kdf_parallelism = data.KdfParallelism;
+ user.client_kdf_parallelism = kdf_data.KdfParallelism;
} else {
err!("Argon2 parallelism parameter is required.")
}
@@ -293,13 +283,16 @@ async fn post_kdf(
user.client_kdf_memory = None;
user.client_kdf_parallelism = None;
}
- user.client_kdf_iter = data.KdfIterations;
- user.client_kdf_type = data.Kdf;
- user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
+ user.client_kdf_iter = kdf_data.KdfIterations;
+ user.client_kdf_type = kdf_data.Kdf;
+ user.set_password(
+ &kdf_data.NewMasterPasswordHash,
+ Some(kdf_data.Key),
+ true,
+ None,
+ );
let save_result = user.save(&conn).await;
-
nt.send_logout(&user, Some(headers.device.uuid)).await;
-
save_result
}
@@ -329,22 +322,21 @@ async fn post_rotatekey(
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
- let data: KeyData = data.into_inner().data;
-
- if !headers.user.check_valid_password(&data.MasterPasswordHash) {
+ let key_data: KeyData = data.into_inner().data;
+ if !headers
+ .user
+ .check_valid_password(&key_data.MasterPasswordHash)
+ {
err!("Invalid password")
}
-
// Validate the import before continuing
// Bitwarden does not process the import if there is one item invalid.
// Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
- Cipher::validate_notes(&data.Ciphers)?;
-
+ Cipher::validate_notes(&key_data.Ciphers)?;
let user_uuid = &headers.user.uuid;
-
// Update folder data
- for folder_data in data.Folders {
+ for folder_data in key_data.Folders {
let Some(mut saved_folder) = Folder::find_by_uuid(&folder_data.Id, &conn).await else {
err!("Folder doesn't exist")
};
@@ -356,21 +348,17 @@ async fn post_rotatekey(
saved_folder.name = folder_data.Name;
saved_folder.save(&conn).await?;
}
-
// Update cipher data
use super::ciphers::update_cipher_from_data;
-
- for cipher_data in data.Ciphers {
+ for cipher_data in key_data.Ciphers {
let Some(mut saved_cipher) =
Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn).await
else {
err!("Cipher doesn't exist")
};
-
if saved_cipher.user_uuid.as_ref().unwrap() != user_uuid {
err!("The cipher is not owned by the user")
}
-
// Prevent triggering cipher updates via WebSockets by settings UpdateType::None
// The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues.
// We force the users to logout after the user has been saved to try and prevent these issues.
@@ -388,18 +376,14 @@ async fn post_rotatekey(
// Update user data
let mut user = headers.user;
-
- user.akey = data.Key;
- user.private_key = Some(data.PrivateKey);
+ user.akey = key_data.Key;
+ user.private_key = Some(key_data.PrivateKey);
user.reset_security_stamp();
-
let save_result = user.save(&conn).await;
-
// Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side.
// Adding the device uuid will prevent this.
nt.send_logout(&user, Some(headers.device.uuid)).await;
-
save_result
}
@@ -410,17 +394,13 @@ async fn post_sstamp(
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
- let data: PasswordOrOtpData = data.into_inner().data;
+ let otp_data: PasswordOrOtpData = data.into_inner().data;
let mut user = headers.user;
-
- data.validate(&user, true, &conn).await?;
-
+ otp_data.validate(&user, true, &conn).await?;
Device::delete_all_by_user(&user.uuid, &conn).await?;
user.reset_security_stamp();
let save_result = user.save(&conn).await;
-
nt.send_logout(&user, None).await;
-
save_result
}
@@ -479,13 +459,11 @@ async fn post_verify_email_token(
data: JsonUpcase<VerifyEmailTokenData>,
conn: DbConn,
) -> EmptyResult {
- let data: VerifyEmailTokenData = data.into_inner().data;
-
- let Some(mut user) = User::find_by_uuid(&data.UserId, &conn).await else {
+ let token_data: VerifyEmailTokenData = data.into_inner().data;
+ let Some(mut user) = User::find_by_uuid(&token_data.UserId, &conn).await else {
err!("User doesn't exist")
};
-
- let Ok(claims) = decode_verify_email(&data.Token) else {
+ let Ok(claims) = decode_verify_email(&token_data.Token) else {
err!("Invalid claim")
};
if claims.sub != user.uuid {
@@ -493,7 +471,7 @@ async fn post_verify_email_token(
}
user.verified_at = Some(Utc::now().naive_utc());
user.last_verifying_at = None;
- user.login_verify_count = 0;
+ user.login_verify_count = 0i32;
user.save(&conn).await
}
@@ -521,13 +499,11 @@ async fn post_delete_recover_token(
data: JsonUpcase<DeleteRecoverTokenData>,
conn: DbConn,
) -> EmptyResult {
- let data: DeleteRecoverTokenData = data.into_inner().data;
-
- let Some(user) = User::find_by_uuid(&data.UserId, &conn).await else {
+ let token_data: DeleteRecoverTokenData = data.into_inner().data;
+ let Some(user) = User::find_by_uuid(&token_data.UserId, &conn).await else {
err!("User doesn't exist")
};
-
- let Ok(claims) = decode_delete(&data.Token) else {
+ let Ok(claims) = decode_delete(&token_data.Token) else {
err!("Invalid claim")
};
if claims.sub != user.uuid {
@@ -551,9 +527,9 @@ async fn delete_account(
headers: Headers,
conn: DbConn,
) -> EmptyResult {
- let data: PasswordOrOtpData = data.into_inner().data;
+ let otp_data: PasswordOrOtpData = data.into_inner().data;
let user = headers.user;
- data.validate(&user, true, &conn).await?;
+ otp_data.validate(&user, true, &conn).await?;
user.delete(&conn).await
}
#[allow(clippy::unnecessary_wraps)]
@@ -587,23 +563,22 @@ async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
}
pub async fn _prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
- let data: PreloginData = data.into_inner().data;
-
- let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.Email, &conn).await
- {
- Some(user) => (
- user.client_kdf_type,
- user.client_kdf_iter,
- user.client_kdf_memory,
- user.client_kdf_parallelism,
- ),
- None => (
- User::CLIENT_KDF_TYPE_DEFAULT,
- User::CLIENT_KDF_ITER_DEFAULT,
- None,
- None,
- ),
- };
+ let login_data: PreloginData = data.into_inner().data;
+ let (kdf_type, kdf_iter, kdf_mem, kdf_para) =
+ match User::find_by_mail(&login_data.Email, &conn).await {
+ Some(user) => (
+ user.client_kdf_type,
+ user.client_kdf_iter,
+ user.client_kdf_memory,
+ user.client_kdf_parallelism,
+ ),
+ None => (
+ User::client_kdf_type_default(),
+ User::CLIENT_KDF_ITER_DEFAULT,
+ None,
+ None,
+ ),
+ };
let result = json!({
"Kdf": kdf_type,
@@ -611,7 +586,6 @@ pub async fn _prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Val
"KdfMemory": kdf_mem,
"KdfParallelism": kdf_para,
});
-
Json(result)
}
@@ -624,13 +598,11 @@ struct SecretVerificationRequest {
#[post("/accounts/verify-password", data = "<data>")]
fn verify_password(data: JsonUpcase<SecretVerificationRequest>, headers: Headers) -> EmptyResult {
- let data: SecretVerificationRequest = data.into_inner().data;
+ let req: SecretVerificationRequest = data.into_inner().data;
let user = headers.user;
-
- if !user.check_valid_password(&data.MasterPasswordHash) {
+ if !user.check_valid_password(&req.MasterPasswordHash) {
err!("Invalid password")
}
-
Ok(())
}
@@ -641,17 +613,13 @@ async fn _api_key(
conn: DbConn,
) -> JsonResult {
use crate::util::format_date;
-
- let data: PasswordOrOtpData = data.into_inner().data;
+ let otp_data: PasswordOrOtpData = data.into_inner().data;
let mut user = headers.user;
-
- data.validate(&user, true, &conn).await?;
-
+ otp_data.validate(&user, true, &conn).await?;
if rotate || user.api_key.is_none() {
user.api_key = Some(crypto::generate_api_key());
user.save(&conn).await.expect("Error saving API key");
}
-
Ok(Json(json!({
"ApiKey": user.api_key,
"RevisionDate": format_date(&user.updated_at),
@@ -703,7 +671,6 @@ struct KnownDevice {
#[rocket::async_trait]
impl<'r> FromRequest<'r> for KnownDevice {
type Error = &'static str;
-
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let email = if let Some(email_b64) = req.headers().get_one("X-Request-Email") {
let Ok(email_bytes) = data_encoding::BASE64URL_NOPAD.decode(email_b64.as_bytes())
@@ -727,7 +694,7 @@ impl<'r> FromRequest<'r> for KnownDevice {
};
let uuid = if let Some(uuid) = req.headers().get_one("X-Device-Identifier") {
- uuid.to_string()
+ uuid.to_owned()
} else {
return Outcome::Error((Status::BadRequest, "X-Device-Identifier value is required"));
};
@@ -789,26 +756,21 @@ async fn post_auth_request(
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
- let data = data.into_inner();
- let Some(user) = User::find_by_mail(&data.email, &conn).await else {
+ let inner_data = data.into_inner();
+ let Some(user) = User::find_by_mail(&inner_data.email, &conn).await else {
err!("AuthRequest doesn't exist")
};
let mut auth_request = AuthRequest::new(
user.uuid.clone(),
- data.deviceIdentifier.clone(),
+ inner_data.deviceIdentifier.clone(),
headers.device_type,
headers.ip.ip.to_string(),
- data.accessCode,
- data.publicKey,
+ inner_data.accessCode,
+ inner_data.publicKey,
);
auth_request.save(&conn).await?;
- nt.send_auth_request(
- &user.uuid,
- &auth_request.uuid,
- &data.deviceIdentifier,
- &conn,
- )
- .await;
+ nt.send_auth_request(&user.uuid, &auth_request.uuid, &inner_data.deviceIdentifier)
+ .await;
Ok(Json(json!({
"id": auth_request.uuid,
"publicKey": auth_request.public_key,
@@ -868,17 +830,17 @@ async fn put_auth_request(
ant: AnonymousNotify<'_>,
nt: Notify<'_>,
) -> JsonResult {
- let data = data.into_inner();
+ let inner_data = data.into_inner();
let mut auth_request: AuthRequest = match AuthRequest::find_by_uuid(uuid, &conn).await {
Some(auth_request) => auth_request,
None => {
err!("AuthRequest doesn't exist")
}
};
- auth_request.approved = Some(data.requestApproved);
- auth_request.enc_key = Some(data.key);
- auth_request.master_password_hash = data.masterPasswordHash;
- auth_request.response_device_id = Some(data.deviceIdentifier.clone());
+ auth_request.approved = Some(inner_data.requestApproved);
+ auth_request.enc_key = Some(inner_data.key);
+ auth_request.master_password_hash = inner_data.masterPasswordHash;
+ auth_request.response_device_id = Some(inner_data.deviceIdentifier.clone());
auth_request.save(&conn).await?;
if auth_request.approved.unwrap_or(false) {
ant.send_auth_response(&auth_request.user_uuid, &auth_request.uuid)
@@ -886,8 +848,7 @@ async fn put_auth_request(
nt.send_auth_response(
&auth_request.user_uuid,
&auth_request.uuid,
- data.deviceIdentifier,
- &conn,
+ inner_data.deviceIdentifier,
)
.await;
}
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
@@ -397,7 +397,7 @@ pub async fn update_cipher_from_data(
err!("Organization mismatch. Please resync the client before updating the cipher")
}
- if let Some(note) = &data.Notes {
+ if let Some(ref note) = data.Notes {
if note.len() > 10_000 {
err!("The field Notes exceeds the maximum encrypted value length of 10000 characters.")
}
@@ -457,22 +457,22 @@ pub async fn update_cipher_from_data(
}
let type_data_opt = match data.Type {
- 1 => data.Login,
- 2 => data.SecureNote,
- 3 => data.Card,
- 4 => data.Identity,
+ 1i32 => data.Login,
+ 2i32 => data.SecureNote,
+ 3i32 => data.Card,
+ 4i32 => data.Identity,
_ => err!("Invalid type"),
};
let type_data = match type_data_opt {
- Some(mut data) => {
+ Some(mut data_in_type) => {
// Remove the 'Response' key from the base object.
- data.as_object_mut().unwrap().remove("Response");
+ data_in_type.as_object_mut().unwrap().remove("Response");
// Remove the 'Response' key from every Uri.
- if data["Uris"].is_array() {
- data["Uris"] = _clean_cipher_data(data["Uris"].clone());
+ if data_in_type["Uris"].is_array() {
+ data_in_type["Uris"] = _clean_cipher_data(data_in_type["Uris"].clone());
}
- data
+ data_in_type
}
None => err!("Data missing"),
};
@@ -500,7 +500,6 @@ pub async fn update_cipher_from_data(
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
- conn,
)
.await;
}
@@ -800,14 +799,12 @@ async fn post_collections_admin(
}
}
}
-
nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&cipher.update_users_revision(&conn).await,
&headers.device.uuid,
Some(Vec::from_iter(posted_collections)),
- &conn,
)
.await;
Ok(())
@@ -910,9 +907,9 @@ async fn share_cipher_by_uuid(
let mut shared_to_collection = false;
- if let Some(organization_uuid) = &data.Cipher.OrganizationId {
- for uuid in &data.CollectionIds {
- match Collection::find_by_uuid_and_org(uuid, organization_uuid, conn).await {
+ if let Some(ref organization_uuid) = data.Cipher.OrganizationId {
+ for col_uuid in &data.CollectionIds {
+ match Collection::find_by_uuid_and_org(col_uuid, organization_uuid, conn).await {
None => err!("Invalid collection ID provided"),
Some(collection) => {
if collection
@@ -1299,19 +1296,16 @@ async fn move_cipher_selected(
if !cipher.is_accessible_to_user(&user_uuid, &conn).await {
err!("Cipher is not accessible by user")
}
-
// Move cipher
cipher
.move_to_folder(data.FolderId.clone(), &user_uuid, &conn)
.await?;
-
nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&[user_uuid.clone()],
&headers.device.uuid,
None,
- &conn,
)
.await;
}
@@ -1408,7 +1402,6 @@ async fn _delete_cipher_by_uuid(
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
- conn,
)
.await;
} else {
@@ -1419,7 +1412,6 @@ async fn _delete_cipher_by_uuid(
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
- conn,
)
.await;
}
@@ -1473,14 +1465,12 @@ async fn _restore_cipher_by_uuid(
cipher.deleted_at = None;
cipher.save(conn).await?;
-
nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
None,
- conn,
)
.await;
Ok(Json(
@@ -1566,7 +1556,7 @@ impl CipherSyncData {
}
// Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's
let user_cipher_collections =
- Cipher::get_collections_with_cipher_by_user(user_uuid.to_string(), conn).await;
+ Cipher::get_collections_with_cipher_by_user(user_uuid.to_owned(), conn).await;
let mut cipher_collections: HashMap<String, Vec<String>> =
HashMap::with_capacity(user_cipher_collections.len());
for (cipher, collection) in user_cipher_collections {
diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs
@@ -61,13 +61,8 @@ async fn post_folders(
let mut folder = Folder::new(headers.user.uuid, data.Name);
folder.save(&conn).await?;
- nt.send_folder_update(
- UpdateType::SyncFolderCreate,
- &folder,
- &headers.device.uuid,
- &conn,
- )
- .await;
+ nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid)
+ .await;
Ok(Json(folder.to_json()))
}
@@ -104,13 +99,8 @@ async fn put_folder(
folder.name = data.Name;
folder.save(&conn).await?;
- nt.send_folder_update(
- UpdateType::SyncFolderUpdate,
- &folder,
- &headers.device.uuid,
- &conn,
- )
- .await;
+ nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid)
+ .await;
Ok(Json(folder.to_json()))
}
@@ -134,16 +124,9 @@ async fn delete_folder(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_
if folder.user_uuid != headers.user.uuid {
err!("Folder belongs to another user")
}
-
// Delete the actual folder entry
folder.delete(&conn).await?;
-
- nt.send_folder_update(
- UpdateType::SyncFolderDelete,
- &folder,
- &headers.device.uuid,
- &conn,
- )
- .await;
+ nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid)
+ .await;
Ok(())
}
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
@@ -110,8 +110,8 @@ async fn post_eq_domains(
let mut user = headers.user;
use serde_json::to_string;
- user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_string());
- user.equivalent_domains = to_string(&equivalent_domains).unwrap_or_else(|_| "[]".to_string());
+ user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_owned());
+ user.equivalent_domains = to_string(&equivalent_domains).unwrap_or_else(|_| "[]".to_owned());
user.save(&conn).await?;
Ok(Json(json!({})))
@@ -190,7 +190,7 @@ pub fn catchers() -> Vec<Catcher> {
fn api_not_found() -> Json<Value> {
Json(json!({
"error": {
- "code": 404,
+ "code": 404i32,
"reason": "Not Found",
"description": "The requested resource could not be found."
}
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
@@ -390,7 +390,6 @@ async fn post_organization_collection_update(
conn: DbConn,
) -> JsonResult {
let data: NewCollectionData = data.into_inner().data;
-
let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
err!("Can't find organization details")
};
@@ -724,12 +723,8 @@ async fn get_org_users(
let mut users_json = Vec::new();
for u in UserOrganization::find_by_org(org_id, &conn).await {
users_json.push(
- u.to_json_user_details(
- data.include_collections.unwrap_or(false),
- data.include_groups.unwrap_or(false),
- &conn,
- )
- .await,
+ u.to_json_user_details(data.include_collections.unwrap_or(false), &conn)
+ .await,
);
}
@@ -889,12 +884,8 @@ async fn get_user(
// Else these will not be shown in the interface, and could lead to missing collections when saved.
let include_groups = data.include_groups.unwrap_or(false);
Ok(Json(
- user.to_json_user_details(
- data.include_collections.unwrap_or(include_groups),
- include_groups,
- &conn,
- )
- .await,
+ user.to_json_user_details(data.include_collections.unwrap_or(include_groups), &conn)
+ .await,
))
}
@@ -959,7 +950,7 @@ async fn edit_user(
if user_to_edit.atype == UserOrgType::Owner
&& new_type != UserOrgType::Owner
- && user_to_edit.status == UserOrgStatus::Confirmed as i32
+ && user_to_edit.status == i32::from(UserOrgStatus::Confirmed)
{
// Removing owner permission, check that there is at least one other confirmed owner
if UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, &conn)
@@ -983,10 +974,8 @@ async fn edit_user(
}
}
}
-
user_to_edit.access_all = data.AccessAll;
- user_to_edit.atype = new_type as i32;
-
+ user_to_edit.atype = i32::from(new_type);
// Delete all the odd collections
for c in
CollectionUser::find_by_organization_and_user_uuid(org_id, &user_to_edit.user_uuid, &conn)
@@ -1089,7 +1078,7 @@ async fn _delete_user(
}
if user_to_delete.atype == UserOrgType::Owner
- && user_to_delete.status == UserOrgStatus::Confirmed as i32
+ && user_to_delete.status == i32::from(UserOrgStatus::Confirmed)
{
// Removing owner, check that there is at least one other confirmed owner
if UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, conn).await
@@ -1225,8 +1214,8 @@ async fn post_org_import(
for (cipher_index, coll_index) in relations {
let cipher_id = &ciphers[cipher_index].uuid;
let coll = &collections[coll_index];
- let coll_id = match coll {
- Ok(coll) => coll.uuid.as_str(),
+ let coll_id = match *coll {
+ Ok(ref coll) => coll.uuid.as_str(),
Err(_) => err!("Failed to assign to collection"),
};
@@ -1282,7 +1271,7 @@ async fn get_policy(
err!("Invalid or unsupported policy type")
};
let policy = (OrgPolicy::find_by_org_and_type(org_id, pol_type_enum, &conn).await).map_or_else(
- || OrgPolicy::new(String::from(org_id), pol_type_enum, "null".to_string()),
+ || OrgPolicy::new(String::from(org_id), pol_type_enum, "null".to_owned()),
core::convert::identity,
);
Ok(Json(policy.to_json()))
@@ -1321,7 +1310,7 @@ async fn put_policy(
// Invited users still need to accept the invite and will get an error when they try to accept the invite.
if user_twofactor_disabled
&& member.atype < UserOrgType::Admin
- && member.status != UserOrgStatus::Invited as i32
+ && member.status != i32::from(UserOrgStatus::Invited)
{
member.delete(&conn).await?;
}
@@ -1336,7 +1325,7 @@ async fn put_policy(
// Those users will not be allowed to accept or be activated because of the policy checks done there.
// We check if the count is larger then 1, because it includes this organization also.
if member.atype < UserOrgType::Admin
- && member.status != UserOrgStatus::Invited as i32
+ && member.status != i32::from(UserOrgStatus::Invited)
&& UserOrganization::count_accepted_and_confirmed_by_user(&member.user_uuid, &conn)
.await
> 1
@@ -1347,7 +1336,7 @@ async fn put_policy(
}
let mut policy = (OrgPolicy::find_by_org_and_type(org_id, pol_type_enum, &conn).await)
.map_or_else(
- || OrgPolicy::new(String::from(org_id), pol_type_enum, "{}".to_string()),
+ || OrgPolicy::new(String::from(org_id), pol_type_enum, "{}".to_owned()),
|p| p,
);
policy.enabled = data.enabled;
@@ -1373,21 +1362,21 @@ fn get_plans() -> Json<Value> {
"Object": "list",
"Data": [{
"Object": "plan",
- "Type": 0,
- "Product": 0,
+ "Type": 0i32,
+ "Product": 0i32,
"Name": "Free",
"NameLocalizationKey": "planNameFree",
- "BitwardenProduct": 0,
- "MaxUsers": 0,
+ "BitwardenProduct": 0i32,
+ "MaxUsers": 0i32,
"DescriptionLocalizationKey": "planDescFree"
},{
"Object": "plan",
- "Type": 0,
- "Product": 1,
+ "Type": 0i32,
+ "Product": 1i32,
"Name": "Free",
"NameLocalizationKey": "planNameFree",
- "BitwardenProduct": 1,
- "MaxUsers": 0,
+ "BitwardenProduct": 1i32,
+ "MaxUsers": 0i32,
"DescriptionLocalizationKey": "planDescFree"
}],
"ContinuationToken": null
@@ -1475,15 +1464,16 @@ async fn import(
.is_none()
{
if let Some(user) = User::find_by_mail(&user_data.Email, &conn).await {
- let user_org_status = UserOrgStatus::Accepted as i32;
-
+ let user_org_status = i32::from(UserOrgStatus::Accepted);
let mut new_org_user =
UserOrganization::new(user.uuid.clone(), String::from(org_id));
new_org_user.access_all = false;
- new_org_user.atype = UserOrgType::User as i32;
+ new_org_user.atype = i32::from(UserOrgType::User);
new_org_user.status = user_org_status;
new_org_user.save(&conn).await?;
}
+ } else {
+ // We don't care.
}
}
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
@@ -1501,7 +1491,6 @@ async fn import(
}
}
}
-
Ok(())
}
@@ -1583,7 +1572,7 @@ async fn _revoke_organization_user(
conn: &DbConn,
) -> EmptyResult {
match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
- Some(mut user_org) if user_org.status > UserOrgStatus::Revoked as i32 => {
+ Some(mut user_org) if user_org.status > i32::from(UserOrgStatus::Revoked) => {
if user_org.user_uuid == headers.user.uuid {
err!("You cannot revoke yourself")
}
@@ -1688,7 +1677,7 @@ async fn _restore_organization_user(
conn: &DbConn,
) -> EmptyResult {
match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
- Some(mut user_org) if user_org.status < UserOrgStatus::Accepted as i32 => {
+ Some(mut user_org) if user_org.status < i32::from(UserOrgStatus::Accepted) => {
if user_org.user_uuid == headers.user.uuid {
err!("You cannot restore yourself")
}
diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs
@@ -28,7 +28,7 @@ async fn generate_authenticator(
let data: PasswordOrOtpData = data.into_inner().data;
let user = headers.user;
data.validate(&user, false, &conn).await?;
- let type_ = TwoFactorType::Authenticator as i32;
+ let type_ = i32::from(TwoFactorType::Authenticator);
let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await;
let (enabled, key) = match twofactor {
@@ -110,7 +110,7 @@ pub async fn validate_totp_code_str(
validate_totp_code(user_uuid, totp_code, secret, ip, conn).await
}
-
+#[allow(clippy::integer_division, clippy::redundant_else)]
async fn validate_totp_code(
user_uuid: &str,
totp_code: &str,
@@ -123,71 +123,51 @@ async fn validate_totp_code(
let Ok(decoded_secret) = BASE32.decode(secret.as_bytes()) else {
err!("Invalid TOTP secret")
};
- let mut twofactor =
- (TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Authenticator as i32, conn)
- .await)
- .map_or_else(
- || {
- TwoFactor::new(
- user_uuid.to_string(),
- TwoFactorType::Authenticator,
- secret.to_string(),
- )
- },
- |tf| tf,
- );
- let steps = 0;
+ let mut twofactor = (TwoFactor::find_by_user_and_type(
+ user_uuid,
+ i32::from(TwoFactorType::Authenticator),
+ conn,
+ )
+ .await)
+ .map_or_else(
+ || {
+ TwoFactor::new(
+ user_uuid.to_owned(),
+ TwoFactorType::Authenticator,
+ secret.to_owned(),
+ )
+ },
+ |tf| tf,
+ );
// Get the current system time in UNIX Epoch (UTC)
let current_time = chrono::Utc::now();
let current_timestamp = current_time.timestamp();
-
- for step in -steps..=steps {
- let time_step = (current_timestamp / 30i64)
- .checked_add(step)
- .expect("overflow during addition of TOTP");
- // We need to calculate the time offsite and cast it as an u64.
- // Since we only have times into the future and the totp generator needs an u64 instead of the default i64.
- let time = u64::try_from(
- current_timestamp
- .checked_add(
- step.checked_mul(30i64)
- .expect("overflow during multiplication in TOTP"),
- )
- .expect("overflow during addition in TOTP"),
- )
- .expect("underflow when casting to a u64 in TOTP");
- let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, time);
-
- // Check the the given code equals the generated and if the time_step is larger then the one last used.
- if generated == totp_code && time_step > i64::from(twofactor.last_used) {
- // If the step does not equals 0 the time is drifted either server or client side.
- if step != 0 {
- warn!("TOTP Time drift detected. The step offset is {}", step);
- }
-
- // Save the last used time step so only totp time steps higher then this one are allowed.
- // This will also save a newly created twofactor if the code is correct.
- twofactor.last_used = i32::try_from(time_step)
- .expect("overflow or underflow when casting to an i32 in TOTP");
- twofactor.save(conn).await?;
- return Ok(());
- } else if generated == totp_code && time_step <= i64::from(twofactor.last_used) {
- warn!(
- "This TOTP or a TOTP code within {} steps back or forward has already been used!",
- steps
- );
- err!(format!(
- "Invalid TOTP code! Server time: {} IP: {}",
- current_time.format("%F %T UTC"),
- ip.ip
- ));
- }
+ let time_step = current_timestamp / 30i64;
+ // We need to calculate the time offsite and cast it as an u64.
+ // Since we only have times into the future and the totp generator needs an u64 instead of the default i64.
+ let time = u64::try_from(current_timestamp).expect("underflow when casting to a u64 in TOTP");
+ let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, time);
+ // Check the the given code equals the generated and if the time_step is larger then the one last used.
+ if generated == totp_code && time_step > i64::from(twofactor.last_used) {
+ // Save the last used time step so only totp time steps higher then this one are allowed.
+ // This will also save a newly created twofactor if the code is correct.
+ twofactor.last_used =
+ i32::try_from(time_step).expect("overflow or underflow when casting to an i32 in TOTP");
+ twofactor.save(conn).await?;
+ Ok(())
+ } else if generated == totp_code && time_step <= i64::from(twofactor.last_used) {
+ warn!("This TOTP or a TOTP code within 0 steps back or forward has already been used!");
+ err!(format!(
+ "Invalid TOTP code! Server time: {} IP: {}",
+ current_time.format("%F %T UTC"),
+ ip.ip
+ ));
+ } else {
+ // Else no valid code received, deny access
+ err!(format!(
+ "Invalid TOTP code! Server time: {} IP: {}",
+ current_time.format("%F %T UTC"),
+ ip.ip
+ ));
}
-
- // Else no valid code received, deny access
- err!(format!(
- "Invalid TOTP code! Server time: {} IP: {}",
- current_time.format("%F %T UTC"),
- ip.ip
- ));
}
diff --git a/src/api/core/two_factor/protected_actions.rs b/src/api/core/two_factor/protected_actions.rs
@@ -34,9 +34,11 @@ impl ProtectedActionData {
Err(_) => err!("Could not decode ProtectedActionData from string"),
}
}
-
fn add_attempt(&mut self) {
- self.attempts += 1;
+ self.attempts = self
+ .attempts
+ .checked_add(1)
+ .expect("add attempts overflowed");
}
}
@@ -69,7 +71,7 @@ pub async fn validate_protected_action_otp(
) -> EmptyResult {
let pa = TwoFactor::find_by_user_and_type(
user_uuid,
- TwoFactorType::ProtectedActions as i32,
+ i32::from(TwoFactorType::ProtectedActions),
conn,
)
.await
@@ -85,24 +87,24 @@ pub async fn validate_protected_action_otp(
pa.delete(conn).await?;
err!("Token has expired")
}
-
// Check if the token has expired (Using the email 2fa expiration time)
let date = NaiveDateTime::from_timestamp_opt(pa_data.token_sent, 0)
.expect("Protected Action token timestamp invalid.");
let max_time = 600;
- if date + Duration::seconds(max_time) < Utc::now().naive_utc() {
+ if date
+ .checked_add_signed(Duration::seconds(max_time))
+ .expect("Duration add overflowed")
+ < Utc::now().naive_utc()
+ {
pa.delete(conn).await?;
err!("Token has expired")
}
-
if !crypto::ct_eq(&pa_data.token, otp) {
pa.save(conn).await?;
err!("Token is invalid")
}
-
if delete_if_valid {
pa.delete(conn).await?;
}
-
Ok(())
}
diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs
@@ -274,7 +274,7 @@ async fn activate_webauthn(
.await?;
// Retrieve and delete the saved challenge state
- let type_ = TwoFactorType::WebauthnRegisterChallenge as i32;
+ let type_ = i32::from(TwoFactorType::WebauthnRegisterChallenge);
let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
Some(tf) => {
let state: RegistrationState = serde_json::from_str(&tf.data)?;
@@ -345,17 +345,23 @@ async fn delete_webauthn(
{
err!("Invalid password");
}
- let Some(mut tf) =
- TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &conn)
- .await
+ let Some(mut tf) = TwoFactor::find_by_user_and_type(
+ &headers.user.uuid,
+ i32::from(TwoFactorType::Webauthn),
+ &conn,
+ )
+ .await
else {
err!("Webauthn data not found!")
};
- let data: Vec<WebauthnRegistration> = serde_json::from_str(&tf.data)?;
- tf.data = serde_json::to_string(&data)?;
+ let web_authn_data: Vec<WebauthnRegistration> = serde_json::from_str(&tf.data)?;
+ tf.data = serde_json::to_string(&web_authn_data)?;
tf.save(&conn).await?;
drop(tf);
- let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect();
+ let keys_json: Vec<Value> = web_authn_data
+ .iter()
+ .map(WebauthnRegistration::to_json)
+ .collect();
Ok(Json(json!({
"Enabled": true,
"Keys": keys_json,
@@ -367,7 +373,7 @@ async fn get_webauthn_registrations(
user_uuid: &str,
conn: &DbConn,
) -> Result<(bool, Vec<WebauthnRegistration>), Error> {
- let type_ = TwoFactorType::Webauthn as i32;
+ let type_ = i32::from(TwoFactorType::Webauthn);
match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await {
Some(tf) => Ok((tf.enabled, serde_json::from_str(&tf.data)?)),
None => Ok((false, Vec::new())), // If no data, return empty list
@@ -412,7 +418,7 @@ pub async fn validate_webauthn_login(
response: &str,
conn: &DbConn,
) -> EmptyResult {
- let type_ = TwoFactorType::WebauthnLoginChallenge as i32;
+ let type_ = i32::from(TwoFactorType::WebauthnLoginChallenge);
let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await {
Some(tf) => {
let state: AuthenticationState = serde_json::from_str(&tf.data)?;
@@ -432,7 +438,7 @@ pub async fn validate_webauthn_login(
reg.credential.counter = auth_data.counter;
TwoFactor::new(
- user_uuid.to_string(),
+ user_uuid.to_owned(),
TwoFactorType::Webauthn,
serde_json::to_string(®istrations)?,
)
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -102,7 +102,7 @@ async fn _refresh_login(data: ConnectData, conn: &DbConn) -> JsonResult {
Ok(Json(result))
}
-
+#[allow(clippy::else_if_without_else)]
async fn _password_login(
data: ConnectData,
user_uuid: &mut Option<String>,
@@ -126,7 +126,6 @@ async fn _password_login(
// Set the user_uuid here to be passed back used for event logging.
*user_uuid = Some(user.uuid.clone());
-
// Check password
let password = data.password.as_ref().unwrap();
if let Some(auth_request_uuid) = data.auth_request.clone() {
@@ -151,7 +150,6 @@ async fn _password_login(
format!("IP: {}. Username: {}.", ip.ip, username)
)
}
-
// Change the KDF Iterations
if user.password_iterations != config::get_config().password_iterations {
user.password_iterations = config::get_config().password_iterations;
@@ -314,7 +312,7 @@ async fn _organization_api_key_login(
Ok(Json(json!({
"access_token": access_token,
- "expires_in": 3600,
+ "expires_in": 3600i32,
"token_type": "Bearer",
"scope": "api.organization",
"unofficialServer": true,
@@ -325,7 +323,7 @@ async fn _organization_api_key_login(
async fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) {
// On iOS, device_type sends "iOS", on others it sends a number
// When unknown or unable to parse, return 14, which is 'Unknown Browser'
- let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(14);
+ let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(14i32);
let device_id = data
.device_identifier
.clone()
diff --git a/src/api/mod.rs b/src/api/mod.rs
@@ -82,9 +82,9 @@ impl NumberOrString {
#[allow(clippy::wrong_self_convention)]
fn into_i32(&self) -> ApiResult<i32> {
use std::num::ParseIntError as PIE;
- match self {
- Self::Number(n) => Ok(*n),
- Self::String(s) => s
+ match *self {
+ Self::Number(n) => Ok(n),
+ Self::String(ref s) => s
.parse()
.map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string())),
}
diff --git a/src/api/notifications.rs b/src/api/notifications.rs
@@ -1,9 +1,6 @@
use crate::{
auth::{ClientIp, WsAccessTokenHeader},
- db::{
- models::{Cipher, Folder, User},
- DbConn,
- },
+ db::models::{Cipher, Folder, User},
Error,
};
use chrono::{NaiveDateTime, Utc};
@@ -78,7 +75,7 @@ impl WSEntryMapGuard {
impl Drop for WSEntryMapGuard {
fn drop(&mut self) {
if let Some(mut entry) = self.users.map.get_mut(&self.user_uuid) {
- entry.retain(|(uuid, _)| uuid != &self.entry_uuid);
+ entry.retain(|tup| tup.0 != self.entry_uuid);
}
}
}
@@ -140,28 +137,30 @@ fn websockets_hub<'r>(
Ok({
rocket_ws::Stream! { ws => {
- let mut ws = ws;
+ let mut ws_copy = ws;
let _guard = guard;
let mut interval = tokio::time::interval(Duration::from_secs(15));
loop {
tokio::select! {
- res = ws.next() => {
+ res = ws_copy.next() => {
match res {
Some(Ok(message)) => {
match message {
// Respond to any pings
Message::Ping(ping) => yield Message::Pong(ping),
Message::Pong(_) => {/* Ignored */},
-
// We should receive an initial message with the protocol and version, and we will reply to it
Message::Text(ref message) => {
- let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
+ let msg = message.strip_suffix(char::from(RECORD_SEPARATOR)).unwrap_or(message);
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
yield Message::binary(INITIAL_RESPONSE);
continue;
}
}
+ // Prevent sending anything back when a `Close` Message is received.
+ // Just break the loop
+ Message::Close(_) => break,
// Just echo anything else the client sends
_ => yield message,
}
@@ -203,28 +202,29 @@ fn anonymous_websockets_hub<'r>(
Ok({
rocket_ws::Stream! { ws => {
- let mut ws = ws;
+ let mut ws_copy = ws;
let _guard = guard;
let mut interval = tokio::time::interval(Duration::from_secs(15));
loop {
tokio::select! {
- res = ws.next() => {
+ res = ws_copy.next() => {
match res {
Some(Ok(message)) => {
match message {
// Respond to any pings
Message::Ping(ping) => yield Message::Pong(ping),
Message::Pong(_) => {/* Ignored */},
-
// We should receive an initial message with the protocol and version, and we will reply to it
Message::Text(ref message) => {
- let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
-
+ let msg = message.strip_suffix(char::from(RECORD_SEPARATOR)).unwrap_or(message);
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
yield Message::binary(INITIAL_RESPONSE);
continue;
}
}
+ // Prevent sending anything back when a `Close` Message is received.
+ // Just break the loop
+ Message::Close(_) => break,
// Just echo anything else the client sends
_ => yield message,
}
@@ -250,7 +250,6 @@ fn anonymous_websockets_hub<'r>(
//
// Websockets server
//
-#[allow(clippy::cast_possible_truncation)]
fn serialize(val: Value) -> Vec<u8> {
use rmpv::encode::write_value;
let mut buf = Vec::new();
@@ -261,11 +260,11 @@ fn serialize(val: Value) -> Vec<u8> {
let mut len_buf: Vec<u8> = Vec::new();
loop {
let mut size_part = size & 0x7f;
- size >>= 7;
+ size >>= 7i32;
if size > 0 {
size_part |= 0x80;
}
- len_buf.push(size_part as u8);
+ len_buf.push(u8::try_from(size_part).unwrap());
if size == 0 {
break;
}
@@ -273,11 +272,11 @@ fn serialize(val: Value) -> Vec<u8> {
len_buf.append(&mut buf);
len_buf
}
-
+#[allow(clippy::big_endian_bytes)]
fn serialize_date(date: NaiveDateTime) -> Value {
let seconds: i64 = date.timestamp();
let nanos: i64 = date.timestamp_subsec_nanos().into();
- let timestamp = nanos << 34 | seconds;
+ let timestamp = nanos << 34i32 | seconds;
let bs = timestamp.to_be_bytes();
// -1 is Timestamp
// https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
@@ -312,8 +311,10 @@ pub struct WebSocketUsers {
impl WebSocketUsers {
async fn send_update(&self, user_uuid: &str, data: &[u8]) {
if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) {
- for (_, sender) in &user {
- _ = sender.send(Message::binary(data)).await;
+ for tup in user {
+ if let Err(e) = tup.1.send(Message::binary(data)).await {
+ error!("Error sending WS update {e}");
+ }
}
}
}
@@ -350,7 +351,6 @@ impl WebSocketUsers {
ut: UpdateType,
folder: &Folder,
acting_device_uuid: &String,
- _: &DbConn,
) {
let data = create_update(
vec![
@@ -372,7 +372,6 @@ impl WebSocketUsers {
user_uuids: &[String],
acting_device_uuid: &String,
collection_uuids: Option<Vec<String>>,
- _: &DbConn,
) {
let org_uuid = convert_option(cipher.organization_uuid.clone());
// Depending if there are collections provided or not, we need to have different values for the following variables.
@@ -385,11 +384,11 @@ impl WebSocketUsers {
serialize_date(cipher.updated_at),
)
},
- |collection_uuids| {
+ |col_uuids| {
(
Value::Nil,
Value::Array(
- collection_uuids
+ col_uuids
.into_iter()
.map(core::convert::Into::into)
.collect::<Vec<rmpv::Value>>(),
@@ -420,7 +419,6 @@ impl WebSocketUsers {
user_uuid: &str,
auth_request_uuid: &str,
acting_device_uuid: &str,
- _: &DbConn,
) {
let data = create_update(
vec![
@@ -428,7 +426,7 @@ impl WebSocketUsers {
("UserId".into(), user_uuid.to_owned().into()),
],
UpdateType::AuthRequest,
- Some(acting_device_uuid.to_string()),
+ Some(acting_device_uuid.to_owned()),
);
self.send_update(user_uuid, &data).await;
}
@@ -438,7 +436,6 @@ impl WebSocketUsers {
user_uuid: &str,
auth_response_uuid: &str,
approving_device_uuid: String,
- _: &DbConn,
) {
let data = create_update(
vec![
@@ -460,7 +457,9 @@ pub struct AnonymousWebSocketSubscriptions {
impl AnonymousWebSocketSubscriptions {
async fn send_update(&self, token: &str, data: &[u8]) {
if let Some(sender) = self.map.get(token).map(|v| v.clone()) {
- _ = sender.send(Message::binary(data)).await;
+ if let Err(e) = sender.send(Message::binary(data)).await {
+ error!("Error sending WS update {e}");
+ };
}
}
@@ -499,7 +498,7 @@ fn create_update(
) -> Vec<u8> {
use rmpv::Value as V;
let value = V::Array(vec![
- 1.into(),
+ 1i32.into(),
V::Map(vec![]),
V::Nil,
"ReceiveMessage".into(),
@@ -508,7 +507,7 @@ fn create_update(
"ContextId".into(),
acting_device_uuid.map_or(V::Nil, core::convert::Into::into),
),
- ("Type".into(), (ut as i32).into()),
+ ("Type".into(), (i32::from(ut)).into()),
("Payload".into(), payload.into()),
])]),
]);
@@ -523,12 +522,12 @@ fn create_anonymous_update(
use rmpv::Value as V;
let value = V::Array(vec![
- 1.into(),
+ 1i32.into(),
V::Map(vec![]),
V::Nil,
"AuthRequestResponseRecieved".into(),
V::Array(vec![V::Map(vec![
- ("Type".into(), (ut as i32).into()),
+ ("Type".into(), (i32::from(ut)).into()),
("Payload".into(), payload.into()),
("UserId".into(), user_id.into()),
])]),
@@ -538,7 +537,7 @@ fn create_anonymous_update(
}
fn create_ping() -> Vec<u8> {
- serialize(Value::Array(vec![6.into()]))
+ serialize(Value::Array(vec![6i32.into()]))
}
#[allow(dead_code)]
@@ -563,6 +562,30 @@ pub enum UpdateType {
AuthRequestResponse = 16,
None = 100,
}
+impl From<UpdateType> for i32 {
+ fn from(value: UpdateType) -> Self {
+ match value {
+ UpdateType::SyncCipherUpdate => 0i32,
+ UpdateType::SyncCipherCreate => 1i32,
+ UpdateType::SyncLoginDelete => 2i32,
+ UpdateType::SyncFolderDelete => 3i32,
+ UpdateType::SyncCiphers => 4i32,
+ UpdateType::SyncVault => 5i32,
+ UpdateType::SyncOrgKeys => 6i32,
+ UpdateType::SyncFolderCreate => 7i32,
+ UpdateType::SyncFolderUpdate => 8i32,
+ UpdateType::SyncCipherDelete => 9i32,
+ UpdateType::SyncSettings => 10i32,
+ UpdateType::LogOut => 11i32,
+ UpdateType::SyncSendCreate => 12i32,
+ UpdateType::SyncSendUpdate => 13i32,
+ UpdateType::SyncSendDelete => 14i32,
+ UpdateType::AuthRequest => 15i32,
+ UpdateType::AuthRequestResponse => 16i32,
+ UpdateType::None => 100i32,
+ }
+ }
+}
pub type Notify<'a> = &'a rocket::State<Arc<WebSocketUsers>>;
pub type AnonymousNotify<'a> = &'a rocket::State<Arc<AnonymousWebSocketSubscriptions>>;
diff --git a/src/api/web.rs b/src/api/web.rs
@@ -63,7 +63,7 @@ fn app_id() -> Cached<(ContentType, Json<Value>)> {
Json(json!({
"trustedFacets": [
{
- "version": { "major": 1, "minor": 0 },
+ "version": { "major": 1i32, "minor": 0i32 },
"ids": [
// Per <https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application>:
//
diff --git a/src/auth.rs b/src/auth.rs
@@ -214,27 +214,27 @@ fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Err
}
pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> {
- decode_jwt(token, get_jwt_login_issuer().to_string())
+ decode_jwt(token, get_jwt_login_issuer().to_owned())
}
pub fn decode_invite(token: &str) -> Result<InviteJwtClaims, Error> {
- decode_jwt(token, get_jwt_invite_issuer().to_string())
+ decode_jwt(token, get_jwt_invite_issuer().to_owned())
}
pub fn decode_delete(token: &str) -> Result<BasicJwtClaims, Error> {
- decode_jwt(token, get_jwt_delete_issuer().to_string())
+ decode_jwt(token, get_jwt_delete_issuer().to_owned())
}
pub fn decode_verify_email(token: &str) -> Result<BasicJwtClaims, Error> {
- decode_jwt(token, get_jwt_verifyemail_issuer().to_string())
+ decode_jwt(token, get_jwt_verifyemail_issuer().to_owned())
}
pub fn decode_api_org(token: &str) -> Result<OrgApiKeyLoginJwtClaims, Error> {
- decode_jwt(token, get_jwt_org_api_key_issuer().to_string())
+ decode_jwt(token, get_jwt_org_api_key_issuer().to_owned())
}
pub fn decode_file_download(token: &str) -> Result<FileDownloadClaims, Error> {
- decode_jwt(token, get_jwt_file_download_issuer().to_string())
+ decode_jwt(token, get_jwt_file_download_issuer().to_owned())
}
#[derive(Debug, Serialize, Deserialize)]
@@ -319,8 +319,10 @@ pub fn generate_organization_api_key_login_claims(
let time_now = Utc::now().naive_utc();
OrgApiKeyLoginJwtClaims {
nbf: time_now.timestamp(),
- exp: (time_now + Duration::hours(1)).timestamp(),
- iss: get_jwt_org_api_key_issuer().to_string(),
+ exp: (time_now.checked_add_signed(Duration::hours(1)))
+ .expect("Duration add overflowed")
+ .timestamp(),
+ iss: get_jwt_org_api_key_issuer().to_owned(),
sub: uuid,
client_id: format!("organization.{org_id}"),
client_sub: org_id,
@@ -399,7 +401,7 @@ impl<'r> FromRequest<'r> for ClientHeaders {
let device_type: i32 = request
.headers()
.get_one("device-type")
- .map_or(14, |d| d.parse().unwrap_or(14));
+ .map_or(14i32, |d| d.parse().unwrap_or(14i32));
Outcome::Success(Self {
host,
@@ -415,7 +417,7 @@ pub struct Headers {
pub user: User,
pub ip: ClientIp,
}
-
+#[allow(clippy::else_if_without_else)]
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Headers {
type Error = &'static str;
@@ -464,7 +466,6 @@ impl<'r> FromRequest<'r> for Headers {
let Some(current_route) = request.route().and_then(|r| r.name.as_deref()) else {
err_handler!("Error getting current route for stamp exception")
};
-
// Check if the stamp exception has expired first.
// Then, check if the current route matches any of the allowed routes.
// After that check the stamp in exception matches the one in the claims.
@@ -473,9 +474,11 @@ impl<'r> FromRequest<'r> for Headers {
// This prevents checking this stamp exception for new requests.
let mut user = user;
user.reset_stamp_exception();
- _ = user.save(&conn).await;
+ if let Err(e) = user.save(&conn).await {
+ error!("Error updating user: {:#?}", e);
+ }
err_handler!("Stamp exception is expired")
- } else if !stamp_exception.routes.contains(¤t_route.to_string()) {
+ } else if !stamp_exception.routes.contains(¤t_route.to_owned()) {
err_handler!(
"Invalid security stamp: Current route and exception route do not match"
)
@@ -486,7 +489,6 @@ impl<'r> FromRequest<'r> for Headers {
err_handler!("Invalid security stamp")
}
}
-
Outcome::Success(Self {
host,
device,
@@ -542,7 +544,7 @@ impl<'r> FromRequest<'r> for OrgHeaders {
.await
{
Some(user) => {
- if user.status == UserOrgStatus::Confirmed as i32 {
+ if user.status == i32::from(UserOrgStatus::Confirmed) {
user
} else {
err_handler!(
@@ -809,7 +811,7 @@ pub struct ClientIp {
#[rocket::async_trait]
impl<'r> FromRequest<'r> for ClientIp {
type Error = ();
-
+ #[allow(clippy::map_err_ignore)]
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let ip = req.headers().get_one("X-Real-IP").and_then(|ip| {
ip.find(',')
diff --git a/src/config.rs b/src/config.rs
@@ -118,9 +118,9 @@ impl Config {
address: config_file.ip,
cli_colors: false,
limits: Limits::new()
- .limit("json", 20.megabytes())
- .limit("data-form", 525.megabytes())
- .limit("file", 525.megabytes()),
+ .limit("json", 20i32.megabytes())
+ .limit("data-form", 525i32.megabytes())
+ .limit("file", 525i32.megabytes()),
log_level: LogLevel::Off,
port: config_file.port,
temp_dir: Self::TMP_FOLDER.into(),
@@ -128,7 +128,7 @@ impl Config {
..Default::default()
};
if let Some(count) = config_file.workers {
- rocket.workers = count.get() as usize;
+ rocket.workers = usize::from(count.get());
}
let domain = Url::parse(
format!(
@@ -157,7 +157,7 @@ impl Config {
password_iterations: match config_file.password_iterations {
None => 600_000,
Some(count) => {
- if count < 100_000 {
+ if count < 100_000i32 {
return Err(ConfigErr::InvalidPasswordIterations(count));
}
count
diff --git a/src/crypto.rs b/src/crypto.rs
@@ -48,7 +48,7 @@ fn get_random_string(alphabet: &[u8], num_chars: usize) -> String {
(0..num_chars)
.map(|_| {
let i = rng.gen_range(0..alphabet.len());
- alphabet[i] as char
+ char::from(alphabet[i])
})
.collect()
}
diff --git a/src/db/mod.rs b/src/db/mod.rs
@@ -78,30 +78,28 @@ impl Drop for DbPool {
}
impl DbPool {
// For the given database URL, guess its type, run migrations, create pool, and return it
- #[allow(clippy::cast_lossless)]
pub fn from_config() -> Result<Self, Error> {
let url = Config::DATABASE_URL;
paste::paste! {sqlite_migrations::run_migrations()?; }
let manager = ConnectionManager::new(url);
let pool = Pool::builder()
- .max_size(config::get_config().database_max_conns.get() as u32)
- .connection_timeout(Duration::from_secs(
- config::get_config().database_timeout as u64,
- ))
+ .max_size(u32::from(config::get_config().database_max_conns.get()))
+ .connection_timeout(Duration::from_secs(u64::from(
+ config::get_config().database_timeout,
+ )))
.connection_customizer(Box::new(DbConnOptions))
.build(manager)
.map_res("Failed to create pool")?;
Ok(Self {
pool: Some(pool),
- semaphore: Arc::new(Semaphore::new(
- config::get_config().database_max_conns.get() as usize,
- )),
+ semaphore: Arc::new(Semaphore::new(usize::from(
+ config::get_config().database_max_conns.get(),
+ ))),
})
}
// Get a connection from the pool
- #[allow(clippy::cast_lossless)]
async fn get(&self) -> Result<DbConn, Error> {
- let duration = Duration::from_secs(config::get_config().database_timeout as u64);
+ let duration = Duration::from_secs(u64::from(config::get_config().database_timeout));
let permit = match timeout(duration, Arc::clone(&self.semaphore).acquire_owned()).await {
Ok(p) => p.expect("Semaphore should be open"),
Err(_) => {
diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs
@@ -29,10 +29,15 @@ db_object! {
}
}
-#[allow(dead_code)]
enum RepromptType {
None = 0,
- Password = 1, // not currently used in server
+}
+impl From<RepromptType> for i32 {
+ fn from(value: RepromptType) -> Self {
+ match value {
+ RepromptType::None => 0i32,
+ }
+ }
}
/// Local methods
@@ -66,7 +71,7 @@ impl Cipher {
pub fn validate_notes(cipher_data: &[CipherData]) -> EmptyResult {
let mut validation_errors = serde_json::Map::new();
for (index, cipher) in cipher_data.iter().enumerate() {
- if let Some(note) = &cipher.Notes {
+ if let Some(ref note) = cipher.Notes {
if note.len() > 10_000 {
validation_errors.insert(
format!("Ciphers[{index}].Notes"),
@@ -134,7 +139,7 @@ impl Cipher {
// NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream
// Set the first element of the Uris array as Uri, this is needed several (mobile) clients.
- if self.atype == 1 {
+ if self.atype == 1i32 {
if type_data_json["Uris"].is_array() {
let uri = type_data_json["Uris"][0]["Uri"].clone();
type_data_json["Uri"] = uri;
@@ -159,7 +164,7 @@ impl Cipher {
.get(&self.uuid)
.map_or_else(|| Cow::from(Vec::with_capacity(0)), Cow::from)
} else {
- Cow::from(self.get_collections(user_uuid.to_string(), conn).await)
+ Cow::from(self.get_collections(user_uuid.to_owned(), conn).await)
};
// There are three types of cipher response models in upstream
@@ -176,7 +181,7 @@ impl Cipher {
"CreationDate": format_date(&self.created_at),
"RevisionDate": format_date(&self.updated_at),
"DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(format_date(&d))),
- "Reprompt": self.reprompt.unwrap_or(RepromptType::None as i32),
+ "Reprompt": self.reprompt.unwrap_or_else(|| i32::from(RepromptType::None)),
"OrganizationId": self.organization_uuid,
"Key": self.key,
"Attachments": Value::Null,
@@ -227,10 +232,10 @@ impl Cipher {
}
let key = match self.atype {
- 1 => "Login",
- 2 => "SecureNote",
- 3 => "Card",
- 4 => "Identity",
+ 1i32 => "Login",
+ 2i32 => "SecureNote",
+ 3i32 => "Card",
+ 4i32 => "Identity",
_ => panic!("Wrong type"),
};
@@ -356,6 +361,7 @@ impl Cipher {
}
/// Returns whether this cipher is owned by an org in which the user has full access.
+ #[allow(clippy::else_if_without_else)]
async fn is_in_full_access_org(
&self,
user_uuid: &str,
@@ -429,9 +435,9 @@ impl Cipher {
// booleans and this behavior isn't portable anyway.
let mut read_only = true;
let mut hide_passwords = true;
- for (ro, hp) in &rows {
- read_only &= ro;
- hide_passwords &= hp;
+ for tup in rows {
+ read_only &= tup.0;
+ hide_passwords &= tup.1;
}
Some((read_only, hide_passwords))
@@ -532,7 +538,7 @@ impl Cipher {
.left_join(users_organizations::table.on(
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
.and(users_organizations::user_uuid.eq(user_uuid))
- .and(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
+ .and(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
))
.left_join(users_collections::table.on(
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
@@ -546,7 +552,7 @@ impl Cipher {
if !visible_only {
query = query.or_filter(
- users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin/owner
+ users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin/owner
);
}
@@ -641,7 +647,7 @@ impl Cipher {
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
.filter(users_collections::user_uuid.eq(user_id).or( // User has access to collection
users_organizations::access_all.eq(true).or( // User has access all
- users_organizations::atype.le(UserOrgType::Admin as i32) // User is admin or owner
+ users_organizations::atype.le(i32::from(UserOrgType::Admin)) // User is admin or owner
)
))
.select(ciphers_collections::collection_uuid)
@@ -672,7 +678,7 @@ impl Cipher {
))
.or_filter(users_collections::user_uuid.eq(user_id)) // User has access to collection
.or_filter(users_organizations::access_all.eq(true)) // User has access all
- .or_filter(users_organizations::atype.le(UserOrgType::Admin as i32)) // User is admin or owner
+ .or_filter(users_organizations::atype.le(i32::from(UserOrgType::Admin))) // User is admin or owner
.select(ciphers_collections::all_columns)
.distinct()
.load::<(String, String)>(conn).unwrap_or_default()
diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs
@@ -182,7 +182,7 @@ impl Collection {
)
))
.filter(
- users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
+ users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
)
.filter(
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
@@ -275,7 +275,7 @@ impl Collection {
.filter(
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
users_organizations::access_all.eq(true).or( // access_all in Organization
- users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
+ users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner
))).select(collections::all_columns)
.first::<CollectionDb>(conn).ok()
.from_db()
@@ -283,7 +283,7 @@ impl Collection {
}
pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
- let user_uuid = user_uuid.to_string();
+ let user_uuid = user_uuid.to_owned();
db_run! { conn: {
collections::table
.left_join(users_collections::table.on(
@@ -300,7 +300,7 @@ impl Collection {
.filter(
users_collections::collection_uuid.eq(&self.uuid).and(users_collections::read_only.eq(false)).or(// Directly accessed collection
users_organizations::access_all.eq(true).or( // access_all in Organization
- users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
+ users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner
))
)
.count()
@@ -311,7 +311,7 @@ impl Collection {
}
async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
- let user_uuid = user_uuid.to_string();
+ let user_uuid = user_uuid.to_owned();
db_run! { conn: {
collections::table
.left_join(users_collections::table.on(
@@ -328,7 +328,7 @@ impl Collection {
.filter(
users_collections::collection_uuid.eq(&self.uuid).and(users_collections::hide_passwords.eq(true)).or(// Directly accessed collection
users_organizations::access_all.eq(true).or( // access_all in Organization
- users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
+ users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner
))
)
.count()
diff --git a/src/db/models/device.rs b/src/db/models/device.rs
@@ -64,22 +64,22 @@ impl Device {
let orgowner: Vec<_> = orgs
.iter()
- .filter(|o| o.atype == 0)
+ .filter(|o| o.atype == 0i32)
.map(|o| o.org_uuid.clone())
.collect();
let orgadmin: Vec<_> = orgs
.iter()
- .filter(|o| o.atype == 1)
+ .filter(|o| o.atype == 1i32)
.map(|o| o.org_uuid.clone())
.collect();
let orguser: Vec<_> = orgs
.iter()
- .filter(|o| o.atype == 2)
+ .filter(|o| o.atype == 2i32)
.map(|o| o.org_uuid.clone())
.collect();
let orgmanager: Vec<_> = orgs
.iter()
- .filter(|o| o.atype == 3)
+ .filter(|o| o.atype == 3i32)
.map(|o| o.org_uuid.clone())
.collect();
@@ -87,20 +87,19 @@ impl Device {
use crate::auth::{self, encode_jwt, LoginJwtClaims};
let claims = LoginJwtClaims {
nbf: time_now.timestamp(),
- exp: (time_now + *auth::get_default_validity()).timestamp(),
- iss: auth::get_jwt_login_issuer().to_string(),
+ exp: (time_now.checked_add_signed(*auth::get_default_validity()))
+ .expect("Duration add overflowed")
+ .timestamp(),
+ iss: auth::get_jwt_login_issuer().to_owned(),
sub: user.uuid.clone(),
-
premium: true,
name: user.name.clone(),
email: user.email.clone(),
email_verified: true,
-
orgowner,
orgadmin,
orguser,
orgmanager,
-
sstamp: user.security_stamp.clone(),
device: self.uuid.clone(),
scope,
@@ -203,7 +202,7 @@ pub enum DeviceType {
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
+ match *self {
Self::Android => write!(f, "Android"),
Self::Ios => write!(f, "iOS"),
Self::ChromeExtension => write!(f, "Chrome Extension"),
@@ -234,28 +233,28 @@ impl fmt::Display for DeviceType {
impl DeviceType {
pub const fn from_i32(value: i32) -> Self {
match value {
- 0 => Self::Android,
- 1 => Self::Ios,
- 2 => Self::ChromeExtension,
- 3 => Self::FirefoxExtension,
- 4 => Self::OperaExtension,
- 5 => Self::EdgeExtension,
- 6 => Self::WindowsDesktop,
- 7 => Self::MacOsDesktop,
- 8 => Self::LinuxDesktop,
- 9 => Self::ChromeBrowser,
- 10 => Self::FirefoxBrowser,
- 11 => Self::OperaBrowser,
- 12 => Self::EdgeBrowser,
- 13 => Self::IEBrowser,
- 15 => Self::AndroidAmazon,
- 16 => Self::Uwp,
- 17 => Self::SafariBrowser,
- 18 => Self::VivaldiBrowser,
- 19 => Self::VivaldiExtension,
- 20 => Self::SafariExtension,
- 21 => Self::Sdk,
- 22 => Self::Server,
+ 0i32 => Self::Android,
+ 1i32 => Self::Ios,
+ 2i32 => Self::ChromeExtension,
+ 3i32 => Self::FirefoxExtension,
+ 4i32 => Self::OperaExtension,
+ 5i32 => Self::EdgeExtension,
+ 6i32 => Self::WindowsDesktop,
+ 7i32 => Self::MacOsDesktop,
+ 8i32 => Self::LinuxDesktop,
+ 9i32 => Self::ChromeBrowser,
+ 10i32 => Self::FirefoxBrowser,
+ 11i32 => Self::OperaBrowser,
+ 12i32 => Self::EdgeBrowser,
+ 13i32 => Self::IEBrowser,
+ 15i32 => Self::AndroidAmazon,
+ 16i32 => Self::Uwp,
+ 17i32 => Self::SafariBrowser,
+ 18i32 => Self::VivaldiBrowser,
+ 19i32 => Self::VivaldiExtension,
+ 20i32 => Self::SafariExtension,
+ 21i32 => Self::Sdk,
+ 22i32 => Self::Server,
_ => Self::UnknownBrowser,
}
}
diff --git a/src/db/models/folder.rs b/src/db/models/folder.rs
@@ -52,8 +52,8 @@ impl Folder {
impl FolderCipher {
pub fn new(folder_uuid: &str, cipher_uuid: &str) -> Self {
Self {
- folder_uuid: folder_uuid.to_string(),
- cipher_uuid: cipher_uuid.to_string(),
+ folder_uuid: folder_uuid.to_owned(),
+ cipher_uuid: cipher_uuid.to_owned(),
}
}
}
diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs
@@ -31,6 +31,20 @@ pub enum OrgPolicyType {
SendOptions = 7,
ResetPassword = 8,
}
+impl From<OrgPolicyType> for i32 {
+ fn from(value: OrgPolicyType) -> Self {
+ match value {
+ OrgPolicyType::TwoFactorAuthentication => 0i32,
+ OrgPolicyType::MasterPassword => 1i32,
+ OrgPolicyType::PasswordGenerator => 2i32,
+ OrgPolicyType::SingleOrg => 3i32,
+ OrgPolicyType::PersonalOwnership => 5i32,
+ OrgPolicyType::DisableSend => 6i32,
+ OrgPolicyType::SendOptions => 7i32,
+ OrgPolicyType::ResetPassword => 8i32,
+ }
+ }
+}
// https://github.com/bitwarden/server/blob/5cbdee137921a19b1f722920f0fa3cd45af2ef0f/src/Core/Models/Data/Organizations/Policies/ResetPasswordDataModel.cs
#[derive(Deserialize)]
@@ -51,7 +65,7 @@ impl OrgPolicy {
Self {
uuid: crate::util::get_uuid(),
org_uuid,
- atype: atype as i32,
+ atype: i32::from(atype),
enabled: false,
data,
}
@@ -112,7 +126,7 @@ impl OrgPolicy {
.and(users_organizations::user_uuid.eq(user_uuid)))
)
.filter(
- users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
+ users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
)
.select(org_policies::all_columns)
.load::<OrgPolicyDb>(conn)
@@ -129,7 +143,7 @@ impl OrgPolicy {
db_run! { conn: {
org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid))
- .filter(org_policies::atype.eq(policy_type as i32))
+ .filter(org_policies::atype.eq(i32::from(policy_type)))
.first::<OrgPolicyDb>(conn)
.ok()
.from_db()
@@ -157,12 +171,12 @@ impl OrgPolicy {
.and(users_organizations::user_uuid.eq(user_uuid)))
)
.filter(
- users_organizations::status.eq(UserOrgStatus::Accepted as i32)
+ users_organizations::status.eq(i32::from(UserOrgStatus::Accepted))
)
.or_filter(
- users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
+ users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
)
- .filter(org_policies::atype.eq(policy_type as i32))
+ .filter(org_policies::atype.eq(i32::from(policy_type)))
.filter(org_policies::enabled.eq(true))
.select(org_policies::all_columns)
.load::<OrgPolicyDb>(conn)
diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
@@ -48,6 +48,16 @@ pub enum UserOrgStatus {
Accepted = 1,
Confirmed = 2,
}
+impl From<UserOrgStatus> for i32 {
+ fn from(value: UserOrgStatus) -> Self {
+ match value {
+ UserOrgStatus::Revoked => -1i32,
+ UserOrgStatus::Invited => 0i32,
+ UserOrgStatus::Accepted => 1i32,
+ UserOrgStatus::Confirmed => 2i32,
+ }
+ }
+}
#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
pub enum UserOrgType {
@@ -56,6 +66,26 @@ pub enum UserOrgType {
User = 2,
Manager = 3,
}
+impl From<UserOrgType> for i32 {
+ fn from(value: UserOrgType) -> Self {
+ match value {
+ UserOrgType::Owner => 0i32,
+ UserOrgType::Admin => 1i32,
+ UserOrgType::User => 2i32,
+ UserOrgType::Manager => 3i32,
+ }
+ }
+}
+impl From<UserOrgType> for usize {
+ fn from(value: UserOrgType) -> Self {
+ match value {
+ UserOrgType::Owner => 0,
+ UserOrgType::Admin => 1,
+ UserOrgType::User => 2,
+ UserOrgType::Manager => 3,
+ }
+ }
+}
impl UserOrgType {
pub fn from_str(s: &str) -> Option<Self> {
@@ -71,14 +101,26 @@ impl UserOrgType {
impl Ord for UserOrgType {
fn cmp(&self, other: &Self) -> Ordering {
- // For easy comparison, map each variant to an access level (where 0 is lowest).
- static ACCESS_LEVEL: [i32; 4] = [
- 3, // Owner
- 2, // Admin
- 0, // User
- 1, // Manager
- ];
- ACCESS_LEVEL[*self as usize].cmp(&ACCESS_LEVEL[*other as usize])
+ match *self {
+ Self::Owner => match *other {
+ Self::Owner => Ordering::Equal,
+ Self::Admin | Self::User | Self::Manager => Ordering::Greater,
+ },
+ Self::Admin => match *other {
+ Self::Owner => Ordering::Less,
+ Self::Admin => Ordering::Equal,
+ Self::User | Self::Manager => Ordering::Greater,
+ },
+ Self::User => match *other {
+ Self::User => Ordering::Equal,
+ Self::Owner | Self::Admin | Self::Manager => Ordering::Less,
+ },
+ Self::Manager => match *other {
+ Self::Owner | Self::Admin => Ordering::Less,
+ Self::User => Ordering::Greater,
+ Self::Manager => Ordering::Equal,
+ },
+ }
}
}
@@ -90,7 +132,7 @@ impl PartialOrd for UserOrgType {
impl PartialEq<i32> for UserOrgType {
fn eq(&self, other: &i32) -> bool {
- *other == *self as i32
+ *other == i32::from(*self)
}
}
@@ -116,7 +158,7 @@ impl PartialOrd<i32> for UserOrgType {
impl PartialEq<UserOrgType> for i32 {
fn eq(&self, other: &UserOrgType) -> bool {
- *self == *other as Self
+ *self == Self::from(*other)
}
}
@@ -148,10 +190,10 @@ impl Organization {
"Id": self.uuid,
"Identifier": null, // not supported by us
"Name": self.name,
- "Seats": 10, // The value doesn't matter, we don't check server-side
+ "Seats": 10i32, // The value doesn't matter, we don't check server-side
// "MaxAutoscaleSeats": null, // The value doesn't matter, we don't check server-side
- "MaxCollections": 10, // The value doesn't matter, we don't check server-side
- "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
+ "MaxCollections": 10i32, // The value doesn't matter, we don't check server-side
+ "MaxStorageGb": 10i32, // The value doesn't matter, we don't check server-side
"Use2fa": true,
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
"UseEvents": false,
@@ -165,17 +207,15 @@ impl Organization {
"UseApi": true,
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
"UseResetPassword": false,
-
"BusinessName": null,
"BusinessAddress1": null,
"BusinessAddress2": null,
"BusinessAddress3": null,
"BusinessCountry": null,
"BusinessTaxNumber": null,
-
"BillingEmail": self.billing_email,
"Plan": "TeamsAnnually",
- "PlanType": 5, // TeamsAnnually plan
+ "PlanType": 5i32, // TeamsAnnually plan
"UsersGetPremium": true,
"Object": "organization",
})
@@ -185,7 +225,7 @@ impl Organization {
// The number 128 should be fine, it is well within the range of an i32
// The same goes for the database where we only use INTEGER (the same as an i32)
// It should also provide enough room for 100+ types, which i doubt will ever happen.
-static ACTIVATE_REVOKE_DIFF: i32 = 128;
+static ACTIVATE_REVOKE_DIFF: i32 = 128i32;
impl UserOrganization {
pub fn new(user_uuid: String, org_uuid: String) -> Self {
@@ -197,27 +237,35 @@ impl UserOrganization {
access_all: false,
akey: String::new(),
- status: UserOrgStatus::Accepted as i32,
- atype: UserOrgType::User as i32,
+ status: i32::from(UserOrgStatus::Accepted),
+ atype: i32::from(UserOrgType::User),
reset_password_key: None,
external_id: None,
}
}
pub fn restore(&mut self) -> bool {
- if self.status < UserOrgStatus::Accepted as i32 {
- self.status += ACTIVATE_REVOKE_DIFF;
- return true;
+ if self.status < i32::from(UserOrgStatus::Invited) {
+ self.status = self
+ .status
+ .checked_add(ACTIVATE_REVOKE_DIFF)
+ .expect("i32 add overflowed");
+ true
+ } else {
+ false
}
- false
}
pub fn revoke(&mut self) -> bool {
- if self.status > UserOrgStatus::Revoked as i32 {
- self.status -= ACTIVATE_REVOKE_DIFF;
- return true;
+ if self.status > i32::from(UserOrgStatus::Revoked) {
+ self.status = self
+ .status
+ .checked_sub(ACTIVATE_REVOKE_DIFF)
+ .expect("i32 sub underflowed");
+ true
+ } else {
+ false
}
- false
}
pub fn set_external_id(&mut self, external_id: Option<String>) -> bool {
@@ -320,8 +368,8 @@ impl UserOrganization {
"Id": self.org_uuid,
"Identifier": null, // Not supported
"Name": org.name,
- "Seats": 10, // The value doesn't matter, we don't check server-side
- "MaxCollections": 10, // The value doesn't matter, we don't check server-side
+ "Seats": 10i32, // The value doesn't matter, we don't check server-side
+ "MaxCollections": 10i32, // The value doesn't matter, we don't check server-side
"UsersGetPremium": true,
"Use2fa": true,
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
@@ -339,7 +387,7 @@ impl UserOrganization {
"UseSso": false, // Not supported
"ProviderId": null,
"ProviderName": null,
- "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
+ "MaxStorageGb": 10i32, // The value doesn't matter, we don't check server-side
"UserId": self.user_uuid,
"Key": self.akey,
"Status": self.status,
@@ -348,18 +396,13 @@ impl UserOrganization {
"Object": "profileOrganization",
})
}
- pub async fn to_json_user_details(
- &self,
- include_collections: bool,
- _: bool,
- conn: &DbConn,
- ) -> Value {
+ pub async fn to_json_user_details(&self, include_collections: bool, conn: &DbConn) -> Value {
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
// We subtract/add a number so we can restore/activate the user to it's previous state again.
- let status = if self.status < UserOrgStatus::Revoked as i32 {
- UserOrgStatus::Revoked as i32
+ let status = if self.status < i32::from(UserOrgStatus::Revoked) {
+ i32::from(UserOrgStatus::Revoked)
} else {
self.status
};
@@ -473,8 +516,8 @@ impl UserOrganization {
None
}
- const fn has_status(&self, status: UserOrgStatus) -> bool {
- self.status == status as i32
+ fn has_status(&self, status: UserOrgStatus) -> bool {
+ self.status == i32::from(status)
}
pub fn has_full_access(&self) -> bool {
@@ -505,7 +548,7 @@ impl UserOrganization {
db_run! { conn: {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
- .filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
+ .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
.load::<UserOrganizationDb>(conn)
.unwrap_or_default().from_db()
}}
@@ -515,7 +558,7 @@ impl UserOrganization {
db_run! { conn: {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
- .filter(users_organizations::status.eq(UserOrgStatus::Invited as i32))
+ .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Invited)))
.load::<UserOrganizationDb>(conn)
.unwrap_or_default().from_db()
}}
@@ -534,8 +577,8 @@ impl UserOrganization {
db_run! { conn: {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
- .filter(users_organizations::status.eq(UserOrgStatus::Accepted as i32))
- .or_filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
+ .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Accepted)))
+ .or_filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
.count()
.first::<i64>(conn)
.unwrap_or(0)
@@ -559,7 +602,7 @@ impl UserOrganization {
db_run! { conn: {
users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid))
- .filter(users_organizations::atype.eq(atype as i32))
+ .filter(users_organizations::atype.eq(i32::from(atype)))
.load::<UserOrganizationDb>(conn)
.expect("Error loading user organizations").from_db()
}}
@@ -573,8 +616,8 @@ impl UserOrganization {
db_run! { conn: {
users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid))
- .filter(users_organizations::atype.eq(atype as i32))
- .filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
+ .filter(users_organizations::atype.eq(i32::from(atype)))
+ .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
.count()
.first::<i64>(conn)
.unwrap_or(0)
@@ -615,11 +658,11 @@ impl UserOrganization {
org_policies::table.on(
org_policies::org_uuid.eq(users_organizations::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid))
- .and(org_policies::atype.eq(policy_type as i32))
+ .and(org_policies::atype.eq(i32::from(policy_type)))
.and(org_policies::enabled.eq(true)))
)
.filter(
- users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
+ users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
)
.select(users_organizations::all_columns)
.load::<UserOrganizationDb>(conn)
diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs
@@ -15,7 +15,6 @@ db_object! {
}
}
-#[allow(dead_code)]
#[derive(num_derive::FromPrimitive)]
pub enum TwoFactorType {
Authenticator = 0,
@@ -24,6 +23,17 @@ pub enum TwoFactorType {
WebauthnLoginChallenge = 1004,
ProtectedActions = 2000,
}
+impl From<TwoFactorType> for i32 {
+ fn from(value: TwoFactorType) -> Self {
+ match value {
+ TwoFactorType::Authenticator => 0i32,
+ TwoFactorType::Webauthn => 7i32,
+ TwoFactorType::WebauthnRegisterChallenge => 1003i32,
+ TwoFactorType::WebauthnLoginChallenge => 1004i32,
+ TwoFactorType::ProtectedActions => 2000i32,
+ }
+ }
+}
/// Local methods
impl TwoFactor {
@@ -31,7 +41,7 @@ impl TwoFactor {
Self {
uuid: crate::util::get_uuid(),
user_uuid,
- atype: atype as i32,
+ atype: i32::from(atype),
enabled: true,
data,
last_used: 0,
@@ -83,7 +93,7 @@ impl TwoFactor {
db_run! { conn: {
twofactor::table
.filter(twofactor::user_uuid.eq(user_uuid))
- .filter(twofactor::atype.lt(1000)) // Filter implementation types
+ .filter(twofactor::atype.lt(1000i32)) // Filter implementation types
.load::<TwoFactorDb>(conn)
.expect("Error loading twofactor")
.from_db()
diff --git a/src/db/models/user.rs b/src/db/models/user.rs
@@ -49,12 +49,29 @@ pub enum UserKdfType {
Pbkdf2 = 0,
Argon2id = 1,
}
+impl From<UserKdfType> for i32 {
+ fn from(value: UserKdfType) -> Self {
+ match value {
+ UserKdfType::Pbkdf2 => 0i32,
+ UserKdfType::Argon2id => 1i32,
+ }
+ }
+}
enum UserStatus {
Enabled = 0,
Invited = 1,
_Disabled = 2,
}
+impl From<UserStatus> for i32 {
+ fn from(value: UserStatus) -> Self {
+ match value {
+ UserStatus::Enabled => 0i32,
+ UserStatus::Invited => 1i32,
+ UserStatus::_Disabled => 2i32,
+ }
+ }
+}
#[derive(Serialize, Deserialize)]
pub struct UserStampException {
@@ -65,13 +82,14 @@ pub struct UserStampException {
/// Local methods
impl User {
- pub const CLIENT_KDF_TYPE_DEFAULT: i32 = UserKdfType::Pbkdf2 as i32;
- pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000;
+ pub fn client_kdf_type_default() -> i32 {
+ i32::from(UserKdfType::Pbkdf2)
+ }
+ pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000i32;
pub fn new(email: String) -> Self {
let now = Utc::now().naive_utc();
let email = email.to_lowercase();
-
Self {
uuid: crate::util::get_uuid(),
enabled: true,
@@ -85,33 +103,24 @@ impl User {
akey: String::new(),
email_new: None,
email_new_token: None,
-
password_hash: Vec::new(),
salt: crypto::get_random_bytes::<64>().to_vec(),
- password_iterations: config::get_config().password_iterations as i32,
-
+ password_iterations: config::get_config().password_iterations,
security_stamp: crate::util::get_uuid(),
stamp_exception: None,
-
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,
+ equivalent_domains: "[]".to_owned(),
+ excluded_globals: "[]".to_owned(),
+ client_kdf_type: Self::client_kdf_type_default(),
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
client_kdf_memory: None,
client_kdf_parallelism: None,
-
api_key: None,
-
avatar_color: None,
-
external_id: None, // Todo: Needs to be removed in the future, this is not used anymore.
}
}
@@ -189,7 +198,11 @@ impl User {
let stamp_exception = UserStampException {
routes: route_exception,
security_stamp: self.security_stamp.clone(),
- expire: (Utc::now().naive_utc() + Duration::minutes(2)).timestamp(),
+ expire: (Utc::now()
+ .naive_utc()
+ .checked_add_signed(Duration::minutes(2)))
+ .expect("Duration add overflowed")
+ .timestamp(),
};
self.stamp_exception = Some(serde_json::to_string(&stamp_exception).unwrap_or_default());
}
@@ -223,7 +236,7 @@ impl User {
};
json!({
- "_Status": status as i32,
+ "_Status": i32::from(status),
"Id": self.uuid,
"Name": self.name,
"Email": self.email,
diff --git a/src/error.rs b/src/error.rs
@@ -19,13 +19,13 @@ macro_rules! make_error {
})+
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
- match &self.error {$( ErrorKind::$name(e) => $src_fn(e), )+}
+ match self.error {$( ErrorKind::$name(ref e) => $src_fn(e), )+}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match &self.error {$(
- ErrorKind::$name(e) => f.write_str(&$usr_msg_fun(e, &self.message)),
+ match self.error {$(
+ ErrorKind::$name(ref e) => f.write_str(&$usr_msg_fun(e, &self.message)),
)+}
}
}
diff --git a/src/main.rs b/src/main.rs
@@ -15,23 +15,14 @@
)]
#![allow(
clippy::absolute_paths,
- clippy::arithmetic_side_effects,
- clippy::as_conversions,
- clippy::big_endian_bytes,
clippy::blanket_clippy_restriction_lints,
- clippy::default_numeric_fallback,
clippy::doc_markdown,
- clippy::else_if_without_else,
clippy::error_impl_error,
clippy::expect_used,
clippy::if_then_some_else_none,
clippy::implicit_return,
clippy::indexing_slicing,
- clippy::integer_division,
clippy::items_after_statements,
- clippy::let_underscore_must_use,
- clippy::let_underscore_untyped,
- clippy::map_err_ignore,
clippy::min_ident_chars,
clippy::missing_docs_in_private_items,
clippy::missing_errors_doc,
@@ -46,22 +37,18 @@
clippy::panic,
clippy::panic_in_result_fn,
clippy::partial_pub_fields,
- clippy::pattern_type_mismatch,
clippy::pub_use,
clippy::question_mark_used,
clippy::redundant_type_annotations,
clippy::ref_patterns,
clippy::shadow_reuse,
- clippy::shadow_unrelated,
clippy::significant_drop_in_scrutinee,
clippy::significant_drop_tightening,
clippy::single_call_fn,
clippy::single_char_lifetime_names,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
- clippy::string_add,
clippy::string_slice,
- clippy::str_to_string,
clippy::too_many_lines,
clippy::unreachable,
clippy::unseparated_literal_suffix,
@@ -179,11 +166,11 @@ fn check_web_vault() {
exit(1);
}
}
-#[allow(clippy::as_conversions, clippy::cast_lossless, clippy::exit)]
+#[allow(clippy::exit)]
async fn create_db_pool() -> db::DbPool {
(util::retry_db(
db::DbPool::from_config,
- config::get_config().db_connection_retries.get() as u32,
+ u32::from(config::get_config().db_connection_retries.get()),
)
.await)
.map_or_else(
@@ -224,6 +211,6 @@ async fn launch_rocket(pool: db::DbPool) -> Result<(), Error> {
.expect("Error setting Ctrl-C handler");
shutdown.notify();
});
- let _ = instance.launch().await?;
+ instance.launch().await?;
Ok(())
}
diff --git a/src/util.rs b/src/util.rs
@@ -217,7 +217,9 @@ impl<'r, R: 'r + Responder<'r, 'static> + Send> Responder<'r, 'static> for Cache
res.set_raw_header("Cache-Control", cache_control_header);
let time_now = chrono::Local::now();
- let expiry_time = time_now + chrono::Duration::seconds(self.ttl.try_into().unwrap());
+ let expiry_time = time_now
+ .checked_add_signed(chrono::Duration::seconds(self.ttl.try_into().unwrap()))
+ .expect("Duration add overflowed");
res.set_raw_header("Expires", format_datetime_http(&expiry_time));
Ok(res)
}
@@ -254,7 +256,7 @@ impl<'r> FromParam<'r> for SafeString {
.chars()
.all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-'))
{
- Ok(Self(param.to_string()))
+ Ok(Self(param.to_owned()))
} else {
Err(())
}
@@ -276,7 +278,9 @@ use std::str::FromStr;
fn upcase_first(s: &str) -> String {
let mut c = s.chars();
c.next().map_or_else(String::new, |f| {
- f.to_uppercase().collect::<String>() + c.as_str()
+ let mut val = f.to_uppercase().collect::<String>();
+ val.push_str(c.as_str());
+ val
})
}
@@ -284,7 +288,9 @@ fn upcase_first(s: &str) -> String {
fn lcase_first(s: &str) -> String {
let mut c = s.chars();
c.next().map_or_else(String::new, |f| {
- f.to_lowercase().collect::<String>() + c.as_str()
+ let mut val = f.to_lowercase().collect::<String>();
+ val.push_str(c.as_str());
+ val
})
}
@@ -424,14 +430,12 @@ pub fn retry<F, T, E>(mut func: F, max_tries: u32) -> Result<T, E>
where
F: FnMut() -> Result<T, E>,
{
- let mut tries = 0;
-
+ let mut tries = 0u32;
loop {
match func() {
ok @ Ok(_) => return ok,
err @ Err(_) => {
- tries += 1;
-
+ tries = tries.checked_add(1).expect("u32 add overflowed");
if tries >= max_tries {
return err;
}
@@ -446,14 +450,12 @@ where
F: FnMut() -> Result<T, E> + Send,
E: std::error::Error + Send,
{
- let mut tries = 0;
-
+ let mut tries = 0u32;
loop {
match func() {
ok @ Ok(_) => return ok,
Err(e) => {
- tries += 1;
-
+ tries = tries.checked_add(1).expect("u32 add overflowed");
if tries >= max_tries && max_tries > 0 {
return Err(e);
}
@@ -478,13 +480,12 @@ pub fn convert_json_key_lcase_first(src_json: Value) -> Value {
Value::Object(obj) => {
let mut json_map = JsonMap::new();
- for (key, value) in &obj {
- match (key, value) {
+ for tup in obj {
+ match tup {
(key, Value::Object(elm)) => {
let inner_value = convert_json_key_lcase_first(Value::Object(elm.clone()));
- json_map.insert(lcase_first(key), inner_value);
+ json_map.insert(lcase_first(key.as_str()), inner_value);
}
-
(key, Value::Array(elm)) => {
let mut inner_array: Vec<Value> = Vec::with_capacity(elm.len());
@@ -492,18 +493,16 @@ pub fn convert_json_key_lcase_first(src_json: Value) -> Value {
inner_array.push(convert_json_key_lcase_first(inner_obj.clone()));
}
- json_map.insert(lcase_first(key), Value::Array(inner_array));
+ json_map.insert(lcase_first(key.as_str()), Value::Array(inner_array));
}
-
(key, value) => {
- json_map.insert(lcase_first(key), value.clone());
+ json_map.insert(lcase_first(key.as_str()), value.clone());
}
}
}
Value::Object(json_map)
}
-
value => value,
}
}