mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-05-15 16:13:55 +00:00
Add Read-Only Mode
This commit is contained in:
parent
d335187172
commit
3bf51d7f42
21 changed files with 450 additions and 24 deletions
|
@ -19,6 +19,7 @@ use crate::{
|
|||
unregister_push_device, ApiResult, EmptyResult, JsonResult, Notify,
|
||||
},
|
||||
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp, Secure},
|
||||
config::not_readonly,
|
||||
config::ConfigBuilder,
|
||||
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
|
||||
error::{Error, MapResult},
|
||||
|
@ -257,6 +258,7 @@ fn render_admin_page() -> ApiResult<Html<String>> {
|
|||
let settings_json = json!({
|
||||
"config": CONFIG.prepare_json(),
|
||||
"can_backup": *CAN_BACKUP,
|
||||
"readonly": CONFIG.readonly(),
|
||||
});
|
||||
let text = AdminTemplateData::new("admin/settings", settings_json).render()?;
|
||||
Ok(Html(text))
|
||||
|
@ -288,6 +290,8 @@ async fn get_user_or_404(uuid: &str, conn: &mut DbConn) -> ApiResult<User> {
|
|||
|
||||
#[post("/invite", data = "<data>")]
|
||||
async fn invite_user(data: Json<InviteData>, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: InviteData = data.into_inner();
|
||||
if User::find_by_mail(&data.email, &mut conn).await.is_some() {
|
||||
err_code!("User already exists", Status::Conflict.code)
|
||||
|
@ -363,7 +367,12 @@ async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<
|
|||
users_json.push(usr);
|
||||
}
|
||||
|
||||
let text = AdminTemplateData::new("admin/users", json!(users_json)).render()?;
|
||||
let users_json = json!({
|
||||
"users": users_json,
|
||||
"readonly": CONFIG.readonly(),
|
||||
});
|
||||
|
||||
let text = AdminTemplateData::new("admin/users", users_json).render()?;
|
||||
Ok(Html(text))
|
||||
}
|
||||
|
||||
|
@ -390,6 +399,8 @@ async fn get_user_json(uuid: &str, _token: AdminToken, mut conn: DbConn) -> Json
|
|||
|
||||
#[post("/users/<uuid>/delete")]
|
||||
async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let user = get_user_or_404(uuid, &mut conn).await?;
|
||||
|
||||
// Get the user_org records before deleting the actual user
|
||||
|
@ -466,6 +477,8 @@ async fn remove_2fa(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRes
|
|||
|
||||
#[post("/users/<uuid>/invite/resend")]
|
||||
async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
if let Some(user) = User::find_by_uuid(uuid, &mut conn).await {
|
||||
//TODO: replace this with user.status check when it will be available (PR#3397)
|
||||
if !user.password_hash.is_empty() {
|
||||
|
@ -491,6 +504,8 @@ struct UserOrgTypeData {
|
|||
|
||||
#[post("/users/org_type", data = "<data>")]
|
||||
async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: UserOrgTypeData = data.into_inner();
|
||||
|
||||
let mut user_to_edit =
|
||||
|
@ -546,6 +561,8 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
|
|||
|
||||
#[post("/users/update_revision")]
|
||||
async fn update_revision_users(_token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
User::update_all_revisions(&mut conn).await
|
||||
}
|
||||
|
||||
|
@ -565,12 +582,19 @@ async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResu
|
|||
organizations_json.push(org);
|
||||
}
|
||||
|
||||
let text = AdminTemplateData::new("admin/organizations", json!(organizations_json)).render()?;
|
||||
let organizations_json = json!({
|
||||
"organizations": organizations_json,
|
||||
"readonly": CONFIG.readonly(),
|
||||
});
|
||||
|
||||
let text = AdminTemplateData::new("admin/organizations", organizations_json).render()?;
|
||||
Ok(Html(text))
|
||||
}
|
||||
|
||||
#[post("/organizations/<uuid>/delete")]
|
||||
async fn delete_organization(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let org = Organization::find_by_uuid(uuid, &mut conn).await.map_res("Organization doesn't exist")?;
|
||||
org.delete(&mut conn).await
|
||||
}
|
||||
|
@ -735,6 +759,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
|
|||
"server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(),
|
||||
"server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the server date/time check as late as possible to minimize the time difference
|
||||
"ntp_time": get_ntp_time(has_http_access).await, // Run the ntp check as late as possible to minimize the time difference
|
||||
"readonly": CONFIG.readonly(),
|
||||
});
|
||||
|
||||
let text = AdminTemplateData::new("admin/diagnostics", diagnostics_json).render()?;
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
PasswordOrOtpData, UpdateType,
|
||||
},
|
||||
auth::{decode_delete, decode_invite, decode_verify_email, ClientHeaders, Headers},
|
||||
config::not_readonly,
|
||||
crypto,
|
||||
db::{models::*, DbConn},
|
||||
mail,
|
||||
|
@ -120,6 +121,8 @@ async fn is_email_2fa_required(org_user_uuid: Option<String>, conn: &mut DbConn)
|
|||
|
||||
#[post("/accounts/register", data = "<data>")]
|
||||
async fn register(data: Json<RegisterData>, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_register(data, conn).await
|
||||
}
|
||||
|
||||
|
@ -257,11 +260,15 @@ struct ProfileData {
|
|||
|
||||
#[put("/accounts/profile", data = "<data>")]
|
||||
async fn put_profile(data: Json<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_profile(data, headers, conn).await
|
||||
}
|
||||
|
||||
#[post("/accounts/profile", data = "<data>")]
|
||||
async fn post_profile(data: Json<ProfileData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: ProfileData = data.into_inner();
|
||||
|
||||
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
|
||||
|
@ -285,6 +292,8 @@ struct AvatarData {
|
|||
|
||||
#[put("/accounts/avatar", data = "<data>")]
|
||||
async fn put_avatar(data: Json<AvatarData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: AvatarData = data.into_inner();
|
||||
|
||||
// It looks like it only supports the 6 hex color format.
|
||||
|
@ -320,6 +329,8 @@ async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> Jso
|
|||
|
||||
#[post("/accounts/keys", data = "<data>")]
|
||||
async fn post_keys(data: Json<KeysData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: KeysData = data.into_inner();
|
||||
|
||||
let mut user = headers.user;
|
||||
|
@ -347,6 +358,8 @@ struct ChangePassData {
|
|||
|
||||
#[post("/accounts/password", data = "<data>")]
|
||||
async fn post_password(data: Json<ChangePassData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: ChangePassData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
|
@ -392,6 +405,8 @@ struct ChangeKdfData {
|
|||
|
||||
#[post("/accounts/kdf", data = "<data>")]
|
||||
async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: ChangeKdfData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
|
@ -479,6 +494,8 @@ struct KeyData {
|
|||
|
||||
#[post("/accounts/key", data = "<data>")]
|
||||
async fn post_rotatekey(data: Json<KeyData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
// TODO: See if we can wrap everything within a SQL Transaction. If something fails it should revert everything.
|
||||
let data: KeyData = data.into_inner();
|
||||
|
||||
|
@ -614,6 +631,8 @@ struct EmailTokenData {
|
|||
|
||||
#[post("/accounts/email-token", data = "<data>")]
|
||||
async fn post_email_token(data: Json<EmailTokenData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.email_change_allowed() {
|
||||
err!("Email change is not allowed.");
|
||||
}
|
||||
|
@ -661,6 +680,8 @@ struct ChangeEmailData {
|
|||
|
||||
#[post("/accounts/email", data = "<data>")]
|
||||
async fn post_email(data: Json<ChangeEmailData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.email_change_allowed() {
|
||||
err!("Email change is not allowed.");
|
||||
}
|
||||
|
@ -715,6 +736,8 @@ async fn post_email(data: Json<ChangeEmailData>, headers: Headers, mut conn: DbC
|
|||
|
||||
#[post("/accounts/verify-email")]
|
||||
async fn post_verify_email(headers: Headers) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let user = headers.user;
|
||||
|
||||
if !CONFIG.mail_enabled() {
|
||||
|
@ -737,6 +760,8 @@ struct VerifyEmailTokenData {
|
|||
|
||||
#[post("/accounts/verify-email-token", data = "<data>")]
|
||||
async fn post_verify_email_token(data: Json<VerifyEmailTokenData>, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: VerifyEmailTokenData = data.into_inner();
|
||||
|
||||
let mut user = match User::find_by_uuid(&data.user_id, &mut conn).await {
|
||||
|
@ -769,6 +794,8 @@ struct DeleteRecoverData {
|
|||
|
||||
#[post("/accounts/delete-recover", data = "<data>")]
|
||||
async fn post_delete_recover(data: Json<DeleteRecoverData>, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: DeleteRecoverData = data.into_inner();
|
||||
|
||||
if CONFIG.mail_enabled() {
|
||||
|
@ -796,6 +823,8 @@ struct DeleteRecoverTokenData {
|
|||
|
||||
#[post("/accounts/delete-recover-token", data = "<data>")]
|
||||
async fn post_delete_recover_token(data: Json<DeleteRecoverTokenData>, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: DeleteRecoverTokenData = data.into_inner();
|
||||
|
||||
let user = match User::find_by_uuid(&data.user_id, &mut conn).await {
|
||||
|
@ -815,11 +844,15 @@ async fn post_delete_recover_token(data: Json<DeleteRecoverTokenData>, mut conn:
|
|||
|
||||
#[post("/accounts/delete", data = "<data>")]
|
||||
async fn post_delete_account(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_account(data, headers, conn).await
|
||||
}
|
||||
|
||||
#[delete("/accounts", data = "<data>")]
|
||||
async fn delete_account(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
|
@ -952,11 +985,17 @@ async fn _api_key(data: Json<PasswordOrOtpData>, rotate: bool, headers: Headers,
|
|||
|
||||
#[post("/accounts/api-key", data = "<data>")]
|
||||
async fn api_key(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
if headers.user.api_key.is_none() {
|
||||
not_readonly()?;
|
||||
}
|
||||
|
||||
_api_key(data, false, headers, conn).await
|
||||
}
|
||||
|
||||
#[post("/accounts/rotate-api-key", data = "<data>")]
|
||||
async fn rotate_api_key(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_api_key(data, true, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -1017,11 +1056,15 @@ struct PushToken {
|
|||
|
||||
#[post("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||
async fn post_device_token(uuid: &str, data: Json<PushToken>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_device_token(uuid, data, headers, conn).await
|
||||
}
|
||||
|
||||
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||
async fn put_device_token(uuid: &str, data: Json<PushToken>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
let token = data.push_token;
|
||||
|
||||
|
@ -1055,6 +1098,8 @@ async fn put_device_token(uuid: &str, data: Json<PushToken>, headers: Headers, m
|
|||
|
||||
#[put("/devices/identifier/<uuid>/clear-token")]
|
||||
async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
// This only clears push token
|
||||
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
|
||||
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
|
||||
|
@ -1074,6 +1119,8 @@ async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
|
|||
// On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere
|
||||
#[post("/devices/identifier/<uuid>/clear-token")]
|
||||
async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_clear_device_token(uuid, conn).await
|
||||
}
|
||||
|
||||
|
@ -1095,6 +1142,8 @@ async fn post_auth_request(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
|
||||
let user = match User::find_by_mail(&data.email, &mut conn).await {
|
||||
|
@ -1176,6 +1225,8 @@ async fn put_auth_request(
|
|||
ant: AnonymousNotify<'_>,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
let mut auth_request: AuthRequest = match AuthRequest::find_by_uuid(uuid, &mut conn).await {
|
||||
Some(auth_request) => auth_request,
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::util::NumberOrString;
|
|||
use crate::{
|
||||
api::{self, core::log_event, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType},
|
||||
auth::Headers,
|
||||
config::not_readonly,
|
||||
crypto,
|
||||
db::{models::*, DbConn, DbPool},
|
||||
CONFIG,
|
||||
|
@ -266,6 +267,8 @@ pub struct Attachments2Data {
|
|||
/// Called when an org admin clones an org cipher.
|
||||
#[post("/ciphers/admin", data = "<data>")]
|
||||
async fn post_ciphers_admin(data: Json<ShareCipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_ciphers_create(data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -279,6 +282,8 @@ async fn post_ciphers_create(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let mut data: ShareCipherData = data.into_inner();
|
||||
|
||||
// Check if there are one more more collections selected when this cipher is part of an organization.
|
||||
|
@ -310,6 +315,8 @@ async fn post_ciphers_create(
|
|||
/// Called when creating a new user-owned cipher.
|
||||
#[post("/ciphers", data = "<data>")]
|
||||
async fn post_ciphers(data: Json<CipherData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let mut data: CipherData = data.into_inner();
|
||||
|
||||
// The web/browser clients set this field to null as expected, but the
|
||||
|
@ -554,6 +561,8 @@ async fn post_ciphers_import(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
enforce_personal_ownership_policy(None, &headers, &mut conn).await?;
|
||||
|
||||
let data: ImportData = data.into_inner();
|
||||
|
@ -612,6 +621,8 @@ async fn put_cipher_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_cipher(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -623,11 +634,15 @@ async fn post_cipher_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_cipher(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
#[post("/ciphers/<uuid>", data = "<data>")]
|
||||
async fn post_cipher(uuid: &str, data: Json<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_cipher(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -639,6 +654,8 @@ async fn put_cipher(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: CipherData = data.into_inner();
|
||||
|
||||
let mut cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
|
@ -662,6 +679,8 @@ async fn put_cipher(
|
|||
|
||||
#[post("/ciphers/<uuid>/partial", data = "<data>")]
|
||||
async fn post_cipher_partial(uuid: &str, data: Json<PartialCipherData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_cipher_partial(uuid, data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -673,6 +692,8 @@ async fn put_cipher_partial(
|
|||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: PartialCipherData = data.into_inner();
|
||||
|
||||
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
|
@ -713,6 +734,8 @@ async fn put_collections2_update(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_collections2_update(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -724,6 +747,8 @@ async fn post_collections2_update(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let cipher_details = post_collections_update(uuid, data, headers, conn, nt).await?;
|
||||
Ok(Json(json!({ // AttachmentUploadDataResponseModel
|
||||
"object": "optionalCipherDetails",
|
||||
|
@ -740,6 +765,8 @@ async fn put_collections_update(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_collections_update(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -751,6 +778,8 @@ async fn post_collections_update(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: CollectionsAdminData = data.into_inner();
|
||||
|
||||
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
|
@ -817,6 +846,8 @@ async fn put_collections_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_collections_admin(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -828,6 +859,8 @@ async fn post_collections_admin(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: CollectionsAdminData = data.into_inner();
|
||||
|
||||
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
|
@ -903,6 +936,8 @@ async fn post_cipher_share(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: ShareCipherData = data.into_inner();
|
||||
|
||||
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
|
||||
|
@ -916,6 +951,8 @@ async fn put_cipher_share(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: ShareCipherData = data.into_inner();
|
||||
|
||||
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await
|
||||
|
@ -935,6 +972,8 @@ async fn put_cipher_share_selected(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let mut data: ShareSelectedCipherData = data.into_inner();
|
||||
|
||||
if data.ciphers.is_empty() {
|
||||
|
@ -973,6 +1012,8 @@ async fn share_cipher_by_uuid(
|
|||
conn: &mut DbConn,
|
||||
nt: &Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let mut cipher = match Cipher::find_by_uuid(uuid, conn).await {
|
||||
Some(cipher) => {
|
||||
if cipher.is_write_accessible_to_user(&headers.user.uuid, conn).await {
|
||||
|
@ -1063,6 +1104,8 @@ async fn post_attachment_v2(
|
|||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await {
|
||||
Some(cipher) => cipher,
|
||||
None => err!("Cipher doesn't exist"),
|
||||
|
@ -1120,6 +1163,8 @@ async fn save_attachment(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> Result<(Cipher, DbConn), crate::error::Error> {
|
||||
not_readonly()?;
|
||||
|
||||
let mut data = data.into_inner();
|
||||
|
||||
let Some(size) = data.data.len().to_i64() else {
|
||||
|
@ -1295,6 +1340,8 @@ async fn post_attachment_v2_data(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let attachment = match Attachment::find_by_id(attachment_id, &mut conn).await {
|
||||
Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment),
|
||||
Some(_) => err!("Attachment doesn't belong to cipher"),
|
||||
|
@ -1315,6 +1362,8 @@ async fn post_attachment(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
// Setting this as None signifies to save_attachment() that it should create
|
||||
// the attachment database record as well as saving the data to disk.
|
||||
let attachment = None;
|
||||
|
@ -1332,6 +1381,8 @@ async fn post_attachment_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_attachment(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -1344,6 +1395,8 @@ async fn post_attachment_share(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await?;
|
||||
post_attachment(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
@ -1356,6 +1409,8 @@ async fn delete_attachment_post_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_attachment(uuid, attachment_id, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -1367,6 +1422,8 @@ async fn delete_attachment_post(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_attachment(uuid, attachment_id, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -1378,6 +1435,8 @@ async fn delete_attachment(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
|
@ -1389,40 +1448,54 @@ async fn delete_attachment_admin(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
#[post("/ciphers/<uuid>/delete")]
|
||||
async fn delete_cipher_post(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||
// permanent delete
|
||||
}
|
||||
|
||||
#[post("/ciphers/<uuid>/delete-admin")]
|
||||
async fn delete_cipher_post_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||
// permanent delete
|
||||
}
|
||||
|
||||
#[put("/ciphers/<uuid>/delete")]
|
||||
async fn delete_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await
|
||||
// soft delete
|
||||
}
|
||||
|
||||
#[put("/ciphers/<uuid>/delete-admin")]
|
||||
async fn delete_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await
|
||||
}
|
||||
|
||||
#[delete("/ciphers/<uuid>")]
|
||||
async fn delete_cipher(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||
// permanent delete
|
||||
}
|
||||
|
||||
#[delete("/ciphers/<uuid>/admin")]
|
||||
async fn delete_cipher_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await
|
||||
// permanent delete
|
||||
}
|
||||
|
@ -1434,6 +1507,8 @@ async fn delete_cipher_selected(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
|
||||
}
|
||||
|
||||
|
@ -1444,6 +1519,8 @@ async fn delete_cipher_selected_post(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
|
||||
}
|
||||
|
||||
|
@ -1454,6 +1531,8 @@ async fn delete_cipher_selected_put(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_multiple_ciphers(data, headers, conn, true, nt).await // soft delete
|
||||
}
|
||||
|
||||
|
@ -1464,6 +1543,8 @@ async fn delete_cipher_selected_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
|
||||
}
|
||||
|
||||
|
@ -1474,6 +1555,8 @@ async fn delete_cipher_selected_post_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
|
||||
}
|
||||
|
||||
|
@ -1484,16 +1567,22 @@ async fn delete_cipher_selected_put_admin(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_multiple_ciphers(data, headers, conn, true, nt).await // soft delete
|
||||
}
|
||||
|
||||
#[put("/ciphers/<uuid>/restore")]
|
||||
async fn restore_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
#[put("/ciphers/<uuid>/restore-admin")]
|
||||
async fn restore_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
|
@ -1504,6 +1593,8 @@ async fn restore_cipher_selected(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_restore_multiple_ciphers(data, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
|
@ -1521,6 +1612,8 @@ async fn move_cipher_selected(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
let user_uuid = headers.user.uuid;
|
||||
|
||||
|
@ -1569,6 +1662,8 @@ async fn move_cipher_selected_put(
|
|||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
move_cipher_selected(data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -1586,6 +1681,8 @@ async fn delete_all(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
EmptyResult, JsonResult,
|
||||
},
|
||||
auth::{decode_emergency_access_invite, Headers},
|
||||
config::not_readonly,
|
||||
db::{models::*, DbConn, DbPool},
|
||||
mail,
|
||||
util::NumberOrString,
|
||||
|
@ -123,6 +124,8 @@ async fn put_emergency_access(
|
|||
headers: Headers,
|
||||
conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_emergency_access(emer_id, data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -133,6 +136,8 @@ async fn post_emergency_access(
|
|||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let data: EmergencyAccessUpdateData = data.into_inner();
|
||||
|
@ -164,6 +169,8 @@ async fn post_emergency_access(
|
|||
|
||||
#[delete("/emergency-access/<emer_id>")]
|
||||
async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let emergency_access = match (
|
||||
|
@ -187,6 +194,8 @@ async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
|
|||
|
||||
#[post("/emergency-access/<emer_id>/delete")]
|
||||
async fn post_delete_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_emergency_access(emer_id, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -204,6 +213,8 @@ struct EmergencyAccessInviteData {
|
|||
|
||||
#[post("/emergency-access/invite", data = "<data>")]
|
||||
async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let data: EmergencyAccessInviteData = data.into_inner();
|
||||
|
@ -282,6 +293,8 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, mu
|
|||
|
||||
#[post("/emergency-access/<emer_id>/reinvite")]
|
||||
async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let mut emergency_access =
|
||||
|
@ -334,6 +347,8 @@ struct AcceptData {
|
|||
|
||||
#[post("/emergency-access/<emer_id>/accept", data = "<data>")]
|
||||
async fn accept_invite(emer_id: &str, data: Json<AcceptData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let data: AcceptData = data.into_inner();
|
||||
|
@ -397,6 +412,8 @@ async fn confirm_emergency_access(
|
|||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let confirming_user = headers.user;
|
||||
|
@ -447,6 +464,8 @@ async fn confirm_emergency_access(
|
|||
|
||||
#[post("/emergency-access/<emer_id>/initiate")]
|
||||
async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let initiating_user = headers.user;
|
||||
|
@ -486,6 +505,8 @@ async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
|
|||
|
||||
#[post("/emergency-access/<emer_id>/approve")]
|
||||
async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let mut emergency_access =
|
||||
|
@ -523,6 +544,8 @@ async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbC
|
|||
|
||||
#[post("/emergency-access/<emer_id>/reject")]
|
||||
async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let mut emergency_access =
|
||||
|
@ -561,6 +584,8 @@ async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
|
|||
|
||||
#[post("/emergency-access/<emer_id>/view")]
|
||||
async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let emergency_access =
|
||||
|
@ -599,6 +624,8 @@ async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn
|
|||
|
||||
#[post("/emergency-access/<emer_id>/takeover")]
|
||||
async fn takeover_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let requesting_user = headers.user;
|
||||
|
@ -643,6 +670,8 @@ async fn password_emergency_access(
|
|||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
check_emergency_access_enabled()?;
|
||||
|
||||
let data: EmergencyAccessPasswordData = data.into_inner();
|
||||
|
|
|
@ -4,6 +4,7 @@ use serde_json::Value;
|
|||
use crate::{
|
||||
api::{EmptyResult, JsonResult, Notify, UpdateType},
|
||||
auth::Headers,
|
||||
config::not_readonly,
|
||||
db::{models::*, DbConn},
|
||||
};
|
||||
|
||||
|
@ -46,6 +47,8 @@ pub struct FolderData {
|
|||
|
||||
#[post("/folders", data = "<data>")]
|
||||
async fn post_folders(data: Json<FolderData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: FolderData = data.into_inner();
|
||||
|
||||
let mut folder = Folder::new(headers.user.uuid, data.name);
|
||||
|
@ -58,6 +61,8 @@ async fn post_folders(data: Json<FolderData>, headers: Headers, mut conn: DbConn
|
|||
|
||||
#[post("/folders/<uuid>", data = "<data>")]
|
||||
async fn post_folder(uuid: &str, data: Json<FolderData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_folder(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
@ -69,6 +74,8 @@ async fn put_folder(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: FolderData = data.into_inner();
|
||||
|
||||
let mut folder = match Folder::find_by_uuid(uuid, &mut conn).await {
|
||||
|
@ -90,11 +97,15 @@ async fn put_folder(
|
|||
|
||||
#[post("/folders/<uuid>/delete")]
|
||||
async fn delete_folder_post(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_folder(uuid, headers, conn, nt).await
|
||||
}
|
||||
|
||||
#[delete("/folders/<uuid>")]
|
||||
async fn delete_folder(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let folder = match Folder::find_by_uuid(uuid, &mut conn).await {
|
||||
Some(folder) => folder,
|
||||
_ => err!("Invalid folder"),
|
||||
|
|
|
@ -15,6 +15,8 @@ pub use events::{event_cleanup_job, log_event, log_user_event};
|
|||
use reqwest::Method;
|
||||
pub use sends::purge_sends;
|
||||
|
||||
use crate::config::not_readonly;
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains];
|
||||
let mut hibp_routes = routes![hibp_breach];
|
||||
|
@ -111,6 +113,8 @@ async fn post_eq_domains(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: EquivDomainData = data.into_inner();
|
||||
|
||||
let excluded_globals = data.excluded_global_equivalent_domains.unwrap_or_default();
|
||||
|
@ -131,6 +135,8 @@ async fn post_eq_domains(
|
|||
|
||||
#[put("/settings/domains", data = "<data>")]
|
||||
async fn put_eq_domains(data: Json<EquivDomainData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_eq_domains(data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
|
||||
},
|
||||
auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
|
||||
config::not_readonly,
|
||||
db::{models::*, DbConn},
|
||||
error::Error,
|
||||
mail,
|
||||
|
@ -150,6 +151,8 @@ struct OrgBulkIds {
|
|||
|
||||
#[post("/organizations", data = "<data>")]
|
||||
async fn create_organization(headers: Headers, data: Json<OrgData>, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.is_org_creation_allowed(&headers.user.email) {
|
||||
err!("User not allowed to create organizations")
|
||||
}
|
||||
|
@ -190,6 +193,8 @@ async fn delete_organization(
|
|||
headers: OwnerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
|
||||
data.validate(&headers.user, true, &mut conn).await?;
|
||||
|
@ -207,11 +212,15 @@ async fn post_delete_organization(
|
|||
headers: OwnerHeaders,
|
||||
conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_organization(org_id, data, headers, conn).await
|
||||
}
|
||||
|
||||
#[post("/organizations/<org_id>/leave")]
|
||||
async fn leave_organization(org_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
match UserOrganization::find_by_user_and_org(&headers.user.uuid, org_id, &mut conn).await {
|
||||
None => err!("User not part of organization"),
|
||||
Some(user_org) => {
|
||||
|
@ -252,6 +261,8 @@ async fn put_organization(
|
|||
data: Json<OrganizationUpdateData>,
|
||||
conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_organization(org_id, headers, data, conn).await
|
||||
}
|
||||
|
||||
|
@ -262,6 +273,8 @@ async fn post_organization(
|
|||
data: Json<OrganizationUpdateData>,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: OrganizationUpdateData = data.into_inner();
|
||||
|
||||
let mut org = match Organization::find_by_uuid(org_id, &mut conn).await {
|
||||
|
@ -381,6 +394,8 @@ async fn post_organization_collections(
|
|||
data: Json<NewCollectionData>,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: NewCollectionData = data.into_inner();
|
||||
|
||||
let org = match Organization::find_by_uuid(org_id, &mut conn).await {
|
||||
|
@ -437,6 +452,8 @@ async fn put_organization_collection_update(
|
|||
data: Json<NewCollectionData>,
|
||||
conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
post_organization_collection_update(org_id, col_id, headers, data, conn).await
|
||||
}
|
||||
|
||||
|
@ -448,6 +465,8 @@ async fn post_organization_collection_update(
|
|||
data: Json<NewCollectionData>,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: NewCollectionData = data.into_inner();
|
||||
|
||||
let org = match Organization::find_by_uuid(org_id, &mut conn).await {
|
||||
|
@ -517,6 +536,8 @@ async fn delete_organization_collection_user(
|
|||
_headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let collection = match Collection::find_by_uuid(col_id, &mut conn).await {
|
||||
None => err!("Collection not found"),
|
||||
Some(collection) => {
|
||||
|
@ -547,6 +568,8 @@ async fn post_organization_collection_delete_user(
|
|||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_organization_collection_user(org_id, col_id, org_user_id, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -585,6 +608,8 @@ async fn delete_organization_collection(
|
|||
headers: ManagerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_organization_collection(org_id, col_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -605,6 +630,8 @@ async fn post_organization_collection_delete(
|
|||
_data: Json<DeleteCollectionData>,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_organization_collection(org_id, col_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -621,6 +648,8 @@ async fn bulk_delete_organization_collections(
|
|||
data: Json<BulkCollectionIds>,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: BulkCollectionIds = data.into_inner();
|
||||
|
||||
let collections = data.ids;
|
||||
|
@ -717,6 +746,8 @@ async fn put_collection_users(
|
|||
_headers: ManagerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
// Get org and collection, check that collection is from org
|
||||
if Collection::find_by_uuid_and_org(coll_id, org_id, &mut conn).await.is_none() {
|
||||
err!("Collection not found in Organization")
|
||||
|
@ -805,6 +836,8 @@ async fn get_org_users(
|
|||
|
||||
#[post("/organizations/<org_id>/keys", data = "<data>")]
|
||||
async fn post_org_keys(org_id: &str, data: Json<OrgKeyData>, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: OrgKeyData = data.into_inner();
|
||||
|
||||
let mut org = match Organization::find_by_uuid(org_id, &mut conn).await {
|
||||
|
@ -849,6 +882,8 @@ struct InviteData {
|
|||
|
||||
#[post("/organizations/<org_id>/users/invite", data = "<data>")]
|
||||
async fn send_invite(org_id: &str, data: Json<InviteData>, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: InviteData = data.into_inner();
|
||||
|
||||
let new_type = match UserOrgType::from_str(&data.r#type.into_string()) {
|
||||
|
@ -965,7 +1000,9 @@ async fn bulk_reinvite_user(
|
|||
data: Json<OrgBulkIds>,
|
||||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: OrgBulkIds = data.into_inner();
|
||||
|
||||
let mut bulk_response = Vec::new();
|
||||
|
@ -984,15 +1021,17 @@ async fn bulk_reinvite_user(
|
|||
))
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
Ok(Json(json!({
|
||||
"data": bulk_response,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
#[post("/organizations/<org_id>/users/<user_org>/reinvite")]
|
||||
async fn reinvite_user(org_id: &str, user_org: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_reinvite_user(org_id, user_org, &headers.user.email, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -1052,6 +1091,8 @@ struct AcceptData {
|
|||
|
||||
#[post("/organizations/<org_id>/users/<_org_user_id>/accept", data = "<data>")]
|
||||
async fn accept_invite(org_id: &str, _org_user_id: &str, data: Json<AcceptData>, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
// The web-vault passes org_id and org_user_id in the URL, but we are just reading them from the JWT instead
|
||||
let data: AcceptData = data.into_inner();
|
||||
let claims = decode_invite(&data.token)?;
|
||||
|
@ -1145,7 +1186,9 @@ async fn bulk_confirm_invite(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut bulk_response = Vec::new();
|
||||
|
@ -1171,11 +1214,11 @@ async fn bulk_confirm_invite(
|
|||
None => error!("No keys to confirm"),
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
Ok(Json(json!({
|
||||
"data": bulk_response,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
#[post("/organizations/<org_id>/users/<org_user_id>/confirm", data = "<data>")]
|
||||
|
@ -1187,6 +1230,8 @@ async fn confirm_invite(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
let user_key = data.key.unwrap_or_default();
|
||||
_confirm_invite(org_id, org_user_id, &user_key, &headers, &mut conn, &nt).await
|
||||
|
@ -1308,6 +1353,8 @@ async fn put_organization_user(
|
|||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
edit_user(org_id, org_user_id, data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -1319,6 +1366,8 @@ async fn edit_user(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: EditUserData = data.into_inner();
|
||||
|
||||
let new_type = match UserOrgType::from_str(&data.r#type.into_string()) {
|
||||
|
@ -1425,7 +1474,9 @@ async fn bulk_delete_user(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: OrgBulkIds = data.into_inner();
|
||||
|
||||
let mut bulk_response = Vec::new();
|
||||
|
@ -1444,11 +1495,11 @@ async fn bulk_delete_user(
|
|||
))
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
Ok(Json(json!({
|
||||
"data": bulk_response,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
#[delete("/organizations/<org_id>/users/<org_user_id>")]
|
||||
|
@ -1459,6 +1510,8 @@ async fn delete_user(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_user(org_id, org_user_id, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
|
@ -1470,6 +1523,8 @@ async fn post_delete_user(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_user(org_id, org_user_id, &headers, &mut conn, &nt).await
|
||||
}
|
||||
|
||||
|
@ -1520,7 +1575,9 @@ async fn bulk_public_keys(
|
|||
data: Json<OrgBulkIds>,
|
||||
_headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: OrgBulkIds = data.into_inner();
|
||||
|
||||
let mut bulk_response = Vec::new();
|
||||
|
@ -1544,11 +1601,11 @@ async fn bulk_public_keys(
|
|||
}
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
Ok(Json(json!({
|
||||
"data": bulk_response,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
use super::ciphers::update_cipher_from_data;
|
||||
|
@ -1579,6 +1636,8 @@ async fn post_org_import(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: ImportData = data.into_inner();
|
||||
let org_id = query.organization_id;
|
||||
|
||||
|
@ -1696,6 +1755,8 @@ async fn put_policy(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: PolicyData = data.into_inner();
|
||||
|
||||
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
|
||||
|
@ -1860,6 +1921,8 @@ struct OrgImportData {
|
|||
|
||||
#[post("/organizations/<org_id>/import", data = "<data>")]
|
||||
async fn import(org_id: &str, data: Json<OrgImportData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
|
||||
// TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way
|
||||
|
@ -1972,6 +2035,8 @@ async fn deactivate_organization_user(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_revoke_organization_user(org_id, org_user_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -1982,7 +2047,9 @@ async fn bulk_deactivate_organization_user(
|
|||
data: Json<OrgBulkRevokeData>,
|
||||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
bulk_revoke_organization_user(org_id, data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -1993,6 +2060,8 @@ async fn revoke_organization_user(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_revoke_organization_user(org_id, org_user_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -2008,7 +2077,9 @@ async fn bulk_revoke_organization_user(
|
|||
data: Json<OrgBulkRevokeData>,
|
||||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut bulk_response = Vec::new();
|
||||
|
@ -2032,11 +2103,11 @@ async fn bulk_revoke_organization_user(
|
|||
None => error!("No users to revoke"),
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
Ok(Json(json!({
|
||||
"data": bulk_response,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
async fn _revoke_organization_user(
|
||||
|
@ -2087,6 +2158,8 @@ async fn activate_organization_user(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_restore_organization_user(org_id, org_user_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -2097,7 +2170,9 @@ async fn bulk_activate_organization_user(
|
|||
data: Json<OrgBulkIds>,
|
||||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
bulk_restore_organization_user(org_id, data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -2108,6 +2183,8 @@ async fn restore_organization_user(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_restore_organization_user(org_id, org_user_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -2117,7 +2194,9 @@ async fn bulk_restore_organization_user(
|
|||
data: Json<OrgBulkIds>,
|
||||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> Json<Value> {
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut bulk_response = Vec::new();
|
||||
|
@ -2136,11 +2215,11 @@ async fn bulk_restore_organization_user(
|
|||
));
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
Ok(Json(json!({
|
||||
"data": bulk_response,
|
||||
"object": "list",
|
||||
"continuationToken": null
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
async fn _restore_organization_user(
|
||||
|
@ -2291,11 +2370,15 @@ async fn post_group(
|
|||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_group(org_id, group_id, data, headers, conn).await
|
||||
}
|
||||
|
||||
#[post("/organizations/<org_id>/groups", data = "<data>")]
|
||||
async fn post_groups(org_id: &str, headers: AdminHeaders, data: Json<GroupRequest>, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
|
@ -2325,6 +2408,8 @@ async fn put_group(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
|
@ -2410,11 +2495,15 @@ async fn get_group_details(_org_id: &str, group_id: &str, _headers: AdminHeaders
|
|||
|
||||
#[post("/organizations/<org_id>/groups/<group_id>/delete")]
|
||||
async fn post_delete_group(org_id: &str, group_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_group(org_id, group_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
#[delete("/organizations/<org_id>/groups/<group_id>")]
|
||||
async fn delete_group(org_id: &str, group_id: &str, headers: AdminHeaders, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
_delete_group(org_id, group_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
|
@ -2449,6 +2538,8 @@ async fn bulk_delete_groups(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
|
@ -2503,6 +2594,8 @@ async fn put_group_users(
|
|||
data: Json<Vec<String>>,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
|
@ -2565,6 +2658,8 @@ async fn post_user_groups(
|
|||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
put_user_groups(org_id, org_user_id, data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -2576,6 +2671,8 @@ async fn put_user_groups(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
|
@ -2619,6 +2716,8 @@ async fn post_delete_group_user(
|
|||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
delete_group_user(org_id, group_id, org_user_id, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -2630,6 +2729,8 @@ async fn delete_group_user(
|
|||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
|
@ -2704,6 +2805,8 @@ async fn put_reset_password(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let org = match Organization::find_by_uuid(org_id, &mut conn).await {
|
||||
Some(org) => org,
|
||||
None => err!("Required organization not found"),
|
||||
|
@ -2839,6 +2942,8 @@ async fn put_reset_password_enrollment(
|
|||
data: Json<OrganizationUserResetPasswordEnrollmentRequest>,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let mut org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, org_id, &mut conn).await {
|
||||
Some(u) => u,
|
||||
None => err!("User to enroll isn't member of required organization"),
|
||||
|
@ -2964,6 +3069,8 @@ async fn _api_key(
|
|||
|
||||
#[post("/organizations/<org_id>/api-key", data = "<data>")]
|
||||
async fn api_key(org_id: &str, data: Json<PasswordOrOtpData>, headers: AdminHeaders, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_api_key(org_id, data, false, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -2974,5 +3081,7 @@ async fn rotate_api_key(
|
|||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_api_key(org_id, data, true, headers, conn).await
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use std::collections::HashSet;
|
|||
use crate::{
|
||||
api::EmptyResult,
|
||||
auth,
|
||||
config::not_readonly,
|
||||
db::{models::*, DbConn},
|
||||
mail, CONFIG,
|
||||
};
|
||||
|
@ -45,6 +46,8 @@ struct OrgImportData {
|
|||
|
||||
#[post("/public/organization/import", data = "<data>")]
|
||||
async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
// Most of the logic for this function can be found here
|
||||
// https://github.com/bitwarden/server/blob/fd892b2ff4547648a276734fb2b14a8abae2c6f5/src/Core/Services/Implementations/OrganizationService.cs#L1797
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ use serde_json::Value;
|
|||
use crate::{
|
||||
api::{ApiResult, EmptyResult, JsonResult, Notify, UpdateType},
|
||||
auth::{ClientIp, Headers, Host},
|
||||
config::not_readonly,
|
||||
db::{models::*, DbConn, DbPool},
|
||||
util::{NumberOrString, SafeString},
|
||||
CONFIG,
|
||||
|
@ -173,6 +174,8 @@ async fn get_send(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult
|
|||
|
||||
#[post("/sends", data = "<data>")]
|
||||
async fn post_send(data: Json<SendData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
|
||||
let data: SendData = data.into_inner();
|
||||
|
@ -212,6 +215,8 @@ struct UploadDataV2<'f> {
|
|||
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167
|
||||
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
|
||||
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
|
||||
let UploadData {
|
||||
|
@ -289,6 +294,8 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
|
|||
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190
|
||||
#[post("/sends/file/v2", data = "<data>")]
|
||||
async fn post_send_file_v2(data: Json<SendData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
|
||||
let data = data.into_inner();
|
||||
|
@ -359,6 +366,8 @@ async fn post_send_file_v2_data(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
|
||||
let mut data = data.into_inner();
|
||||
|
@ -408,6 +417,8 @@ async fn post_access(
|
|||
ip: ClientIp,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let mut send = match Send::find_by_access_id(access_id, &mut conn).await {
|
||||
Some(s) => s,
|
||||
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
||||
|
@ -469,6 +480,8 @@ async fn post_access_file(
|
|||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let mut send = match Send::find_by_uuid(send_id, &mut conn).await {
|
||||
Some(s) => s,
|
||||
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
||||
|
@ -536,6 +549,8 @@ async fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Opt
|
|||
|
||||
#[put("/sends/<id>", data = "<data>")]
|
||||
async fn put_send(id: &str, data: Json<SendData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
|
||||
let data: SendData = data.into_inner();
|
||||
|
@ -611,6 +626,8 @@ pub async fn update_send_from_data(
|
|||
|
||||
#[delete("/sends/<id>")]
|
||||
async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let send = match Send::find_by_uuid(id, &mut conn).await {
|
||||
Some(s) => s,
|
||||
None => err!("Send not found"),
|
||||
|
@ -635,6 +652,8 @@ async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_
|
|||
|
||||
#[put("/sends/<id>/remove-password")]
|
||||
async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
|
||||
let mut send = match Send::find_by_uuid(id, &mut conn).await {
|
||||
|
|
|
@ -5,6 +5,7 @@ use rocket::Route;
|
|||
use crate::{
|
||||
api::{core::log_user_event, core::two_factor::_generate_recover_code, EmptyResult, JsonResult, PasswordOrOtpData},
|
||||
auth::{ClientIp, Headers},
|
||||
config::not_readonly,
|
||||
crypto,
|
||||
db::{
|
||||
models::{EventType, TwoFactor, TwoFactorType},
|
||||
|
@ -52,6 +53,8 @@ struct EnableAuthenticatorData {
|
|||
|
||||
#[post("/two-factor/authenticator", data = "<data>")]
|
||||
async fn activate_authenticator(data: Json<EnableAuthenticatorData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: EnableAuthenticatorData = data.into_inner();
|
||||
let key = data.key;
|
||||
let token = data.token.into_string();
|
||||
|
@ -91,6 +94,8 @@ async fn activate_authenticator(data: Json<EnableAuthenticatorData>, headers: He
|
|||
|
||||
#[put("/two-factor/authenticator", data = "<data>")]
|
||||
async fn activate_authenticator_put(data: Json<EnableAuthenticatorData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
activate_authenticator(data, headers, conn).await
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
PasswordOrOtpData,
|
||||
},
|
||||
auth::Headers,
|
||||
config::not_readonly,
|
||||
crypto,
|
||||
db::{
|
||||
models::{EventType, TwoFactor, TwoFactorType, User},
|
||||
|
@ -156,6 +157,8 @@ fn check_duo_fields_custom(data: &EnableDuoData) -> bool {
|
|||
|
||||
#[post("/two-factor/duo", data = "<data>")]
|
||||
async fn activate_duo(data: Json<EnableDuoData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: EnableDuoData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
|
@ -194,6 +197,8 @@ async fn activate_duo(data: Json<EnableDuoData>, headers: Headers, mut conn: DbC
|
|||
|
||||
#[put("/two-factor/duo", data = "<data>")]
|
||||
async fn activate_duo_put(data: Json<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
activate_duo(data, headers, conn).await
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
EmptyResult, JsonResult, PasswordOrOtpData,
|
||||
},
|
||||
auth::Headers,
|
||||
config::not_readonly,
|
||||
crypto,
|
||||
db::{
|
||||
models::{EventType, TwoFactor, TwoFactorType, User},
|
||||
|
@ -113,6 +114,8 @@ struct SendEmailData {
|
|||
/// Send a verification email to the specified email address to check whether it exists/belongs to user.
|
||||
#[post("/two-factor/send-email", data = "<data>")]
|
||||
async fn send_email(data: Json<SendEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: SendEmailData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
|
@ -157,6 +160,8 @@ struct EmailData {
|
|||
/// Verify email belongs to user and can be used for 2FA email codes.
|
||||
#[put("/two-factor/email", data = "<data>")]
|
||||
async fn email(data: Json<EmailData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: EmailData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
EmptyResult, JsonResult, PasswordOrOtpData,
|
||||
},
|
||||
auth::{ClientHeaders, Headers},
|
||||
config::not_readonly,
|
||||
crypto,
|
||||
db::{models::*, DbConn, DbPool},
|
||||
mail,
|
||||
|
@ -80,6 +81,8 @@ struct RecoverTwoFactor {
|
|||
|
||||
#[post("/two-factor/recover", data = "<data>")]
|
||||
async fn recover(data: Json<RecoverTwoFactor>, client_headers: ClientHeaders, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: RecoverTwoFactor = data.into_inner();
|
||||
|
||||
use crate::db::models::User;
|
||||
|
@ -137,6 +140,8 @@ struct DisableTwoFactorData {
|
|||
|
||||
#[post("/two-factor/disable", data = "<data>")]
|
||||
async fn disable_twofactor(data: Json<DisableTwoFactorData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: DisableTwoFactorData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
|
@ -169,6 +174,8 @@ async fn disable_twofactor(data: Json<DisableTwoFactorData>, headers: Headers, m
|
|||
|
||||
#[put("/two-factor/disable", data = "<data>")]
|
||||
async fn disable_twofactor_put(data: Json<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
disable_twofactor(data, headers, conn).await
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
EmptyResult, JsonResult, PasswordOrOtpData,
|
||||
},
|
||||
auth::Headers,
|
||||
config::not_readonly,
|
||||
db::{
|
||||
models::{EventType, TwoFactor, TwoFactorType},
|
||||
DbConn,
|
||||
|
@ -126,6 +127,8 @@ async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, mut conn:
|
|||
|
||||
#[post("/two-factor/get-webauthn-challenge", data = "<data>")]
|
||||
async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
|
@ -239,6 +242,8 @@ impl From<PublicKeyCredentialCopy> for PublicKeyCredential {
|
|||
|
||||
#[post("/two-factor/webauthn", data = "<data>")]
|
||||
async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: EnableWebauthnData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
|
@ -292,6 +297,8 @@ async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut
|
|||
|
||||
#[put("/two-factor/webauthn", data = "<data>")]
|
||||
async fn activate_webauthn_put(data: Json<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
activate_webauthn(data, headers, conn).await
|
||||
}
|
||||
|
||||
|
@ -304,6 +311,8 @@ struct DeleteU2FData {
|
|||
|
||||
#[delete("/two-factor/webauthn", data = "<data>")]
|
||||
async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let id = data.id.into_i32()?;
|
||||
if !headers.user.check_valid_password(&data.master_password_hash) {
|
||||
err!("Invalid password");
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
EmptyResult, JsonResult, PasswordOrOtpData,
|
||||
},
|
||||
auth::Headers,
|
||||
config::not_readonly,
|
||||
db::{
|
||||
models::{EventType, TwoFactor, TwoFactorType},
|
||||
DbConn,
|
||||
|
@ -117,6 +118,8 @@ async fn generate_yubikey(data: Json<PasswordOrOtpData>, headers: Headers, mut c
|
|||
|
||||
#[post("/two-factor/yubikey", data = "<data>")]
|
||||
async fn activate_yubikey(data: Json<EnableYubikeyData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
let data: EnableYubikeyData = data.into_inner();
|
||||
let mut user = headers.user;
|
||||
|
||||
|
@ -178,6 +181,8 @@ async fn activate_yubikey(data: Json<EnableYubikeyData>, headers: Headers, mut c
|
|||
|
||||
#[put("/two-factor/yubikey", data = "<data>")]
|
||||
async fn activate_yubikey_put(data: Json<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
activate_yubikey(data, headers, conn).await
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ use crate::{
|
|||
ApiResult, EmptyResult, JsonResult,
|
||||
},
|
||||
auth::{generate_organization_api_key_login_claims, ClientHeaders, ClientIp},
|
||||
config::not_readonly,
|
||||
db::{models::*, DbConn},
|
||||
error::MapResult,
|
||||
mail, util, CONFIG,
|
||||
|
@ -681,6 +682,8 @@ async fn prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
|
|||
|
||||
#[post("/accounts/register", data = "<data>")]
|
||||
async fn identity_register(data: Json<RegisterData>, conn: DbConn) -> JsonResult {
|
||||
not_readonly()?;
|
||||
|
||||
_register(data, conn).await
|
||||
}
|
||||
|
||||
|
|
|
@ -507,6 +507,9 @@ make_config! {
|
|||
|
||||
/// Events days retain |> Number of days to retain events stored in the database. If unset, events are kept indefinitely.
|
||||
events_days_retain: i64, false, option;
|
||||
|
||||
/// Read-Only Mode |> Prevent writing of data but logins are still allowed.
|
||||
readonly: bool, false, def, false;
|
||||
},
|
||||
|
||||
/// Advanced settings
|
||||
|
@ -1008,6 +1011,10 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
|||
}
|
||||
}
|
||||
|
||||
if cfg.readonly {
|
||||
println!("[NOTICE] Read-Only Mode is enabled");
|
||||
}
|
||||
|
||||
if cfg.increase_note_size_limit {
|
||||
println!("[WARNING] Secure Note size limit is increased to 100_000!");
|
||||
println!("[WARNING] This could cause issues with clients. Also exports will not work on Bitwarden servers!.");
|
||||
|
@ -1279,6 +1286,16 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn not_readonly() -> Result<(), Error> {
|
||||
match CONFIG.readonly() {
|
||||
false => Ok(()),
|
||||
true => {
|
||||
let msg = "The server is in read-only mode";
|
||||
Err(Error::new(msg, msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use handlebars::{
|
||||
Context, DirectorySourceOptions, Handlebars, Helper, HelperResult, Output, RenderContext, RenderErrorReason,
|
||||
Renderable,
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
<main class="container-xl">
|
||||
{{#if page_data.readonly}}
|
||||
<div id="readonly_mode" class="alert alert-info fade show">
|
||||
Read-Only Mode is enabled. This means no password changes, new accounts, or other modifications can be made.
|
||||
</div>
|
||||
{{/if}}
|
||||
<div id="diagnostics-block" class="my-3 p-3 rounded shadow">
|
||||
<h6 class="border-bottom pb-2 mb-2">Diagnostics</h6>
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
<main class="container-xl">
|
||||
{{#if page_data.readonly}}
|
||||
<div id="readonly_mode" class="alert alert-info fade show">
|
||||
Read-Only Mode is enabled. This means no password changes, new accounts, or other modifications can be made.
|
||||
</div>
|
||||
{{/if}}
|
||||
<div id="organizations-block" class="my-3 p-3 rounded shadow">
|
||||
<h6 class="border-bottom pb-2 mb-3">Organizations</h6>
|
||||
<div class="table-responsive-xl small">
|
||||
|
@ -14,7 +19,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each page_data}}
|
||||
{{#each page_data.organizations}}
|
||||
<tr>
|
||||
<td>
|
||||
<svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{id}}">
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
Please generate a secure Argon2 PHC string by using `vaultwarden hash` or `argon2`.<br>
|
||||
See: <a href="https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token" target="_blank" rel="noopener noreferrer">Enabling admin page - Secure the `ADMIN_TOKEN`</a>
|
||||
</div>
|
||||
{{#if page_data.readonly}}
|
||||
<div id="readonly_mode" class="alert alert-info fade show">
|
||||
Read-Only Mode is enabled. This means no password changes, new accounts, or other modifications can be made.
|
||||
</div>
|
||||
{{/if}}
|
||||
<div id="config-block" class="align-items-center p-3 mb-3 bg-secondary rounded shadow">
|
||||
<div>
|
||||
<h6 class="text-white mb-3">Configuration</h6>
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
<main class="container-xl">
|
||||
{{#if page_data.readonly}}
|
||||
<div id="readonly_mode" class="alert alert-info fade show">
|
||||
Read-Only Mode is enabled. This means no password changes, new accounts, or other modifications can be made.
|
||||
</div>
|
||||
{{/if}}
|
||||
<div id="users-block" class="my-3 p-3 rounded shadow">
|
||||
<h6 class="border-bottom pb-2 mb-3">Registered Users</h6>
|
||||
<div class="table-responsive-xl small">
|
||||
|
@ -15,7 +20,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each page_data}}
|
||||
{{#each page_data.users}}
|
||||
<tr>
|
||||
<td>
|
||||
<svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{email}}">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue