0
Fork 0
mirror of https://github.com/dani-garcia/vaultwarden.git synced 2025-04-01 02:42:49 -05:00

Add more org_id checks

Added more org_id checks at all functions which use the org_id in there path.

Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
BlackDex 2025-01-24 18:39:59 +01:00
parent 9f66551289
commit 983c862d22
No known key found for this signature in database
GPG key ID: 58C80A2AA6C765E1
3 changed files with 176 additions and 55 deletions

View file

@ -34,9 +34,13 @@ struct EventRange {
async fn get_org_events(
org_id: OrganizationId,
data: EventRange,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
// Return an empty vec when we org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
@ -100,9 +104,12 @@ async fn get_user_events(
org_id: OrganizationId,
member_id: MembershipId,
data: EventRange,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
// Return an empty vec when we org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {

View file

@ -338,12 +338,15 @@ async fn get_user_collections(headers: Headers, mut conn: DbConn) -> Json<Value>
}
#[get("/organizations/<org_id>/collections")]
async fn get_org_collections(org_id: OrganizationId, _headers: ManagerHeadersLoose, mut conn: DbConn) -> Json<Value> {
Json(json!({
async fn get_org_collections(org_id: OrganizationId, headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult {
if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match");
}
Ok(Json(json!({
"data": _get_org_collections(&org_id, &mut conn).await,
"object": "list",
"continuationToken": null,
}))
})))
}
#[get("/organizations/<org_id>/collections/details")]
@ -352,6 +355,9 @@ async fn get_org_collections_details(
headers: ManagerHeadersLoose,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match");
}
let mut data = Vec::new();
let Some(member) = Membership::find_by_user_and_org(&headers.user.uuid, &org_id, &mut conn).await else {
@ -426,6 +432,9 @@ async fn post_organization_collections(
data: Json<NewCollectionData>,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match");
}
let data: NewCollectionData = data.into_inner();
let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else {
@ -498,6 +507,9 @@ async fn post_organization_collection_update(
data: Json<NewCollectionData>,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: NewCollectionData = data.into_inner();
if Organization::find_by_uuid(&org_id, &mut conn).await.is_none() {
@ -558,9 +570,12 @@ async fn delete_organization_collection_member(
org_id: OrganizationId,
col_id: CollectionId,
member_id: MembershipId,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> EmptyResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let Some(collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await else {
err!("Collection not found", "Collection does not exist or does not belong to this organization")
};
@ -651,6 +666,9 @@ async fn bulk_delete_organization_collections(
data: Json<BulkCollectionIds>,
mut conn: DbConn,
) -> EmptyResult {
if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match");
}
let data: BulkCollectionIds = data.into_inner();
let collections = data.ids;
@ -670,6 +688,9 @@ async fn get_org_collection_detail(
headers: ManagerHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
match Collection::find_by_uuid_and_user(&col_id, headers.user.uuid.clone(), &mut conn).await {
None => err!("Collection not found"),
Some(collection) => {
@ -731,9 +752,12 @@ async fn get_org_collection_detail(
async fn get_collection_users(
org_id: OrganizationId,
col_id: CollectionId,
_headers: ManagerHeaders,
headers: ManagerHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
// Get org and collection, check that collection is from org
let Some(collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await else {
err!("Collection not found in Organization")
@ -757,9 +781,12 @@ async fn put_collection_users(
org_id: OrganizationId,
col_id: CollectionId,
data: Json<Vec<MembershipData>>,
_headers: ManagerHeaders,
headers: ManagerHeaders,
mut conn: DbConn,
) -> EmptyResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
// Get org and collection, check that collection is from org
if Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await.is_none() {
err!("Collection not found in Organization")
@ -826,9 +853,12 @@ struct GetOrgUserData {
async fn get_members(
data: GetOrgUserData,
org_id: OrganizationId,
_headers: ManagerHeadersLoose,
headers: ManagerHeadersLoose,
mut conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match");
}
let mut users_json = Vec::new();
for u in Membership::find_by_org(&org_id, &mut conn).await {
users_json.push(
@ -841,20 +871,23 @@ async fn get_members(
);
}
Json(json!({
Ok(Json(json!({
"data": users_json,
"object": "list",
"continuationToken": null,
}))
})))
}
#[post("/organizations/<org_id>/keys", data = "<data>")]
async fn post_org_keys(
org_id: OrganizationId,
data: Json<OrgKeyData>,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: OrgKeyData = data.into_inner();
let mut org = match Organization::find_by_uuid(&org_id, &mut conn).await {
@ -917,6 +950,9 @@ async fn send_invite(
headers: AdminHeaders,
mut conn: DbConn,
) -> EmptyResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let mut data: InviteData = data.into_inner();
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
@ -1057,7 +1093,10 @@ async fn bulk_reinvite_members(
data: Json<BulkMembershipIds>,
headers: AdminHeaders,
mut conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: BulkMembershipIds = data.into_inner();
let mut bulk_response = Vec::new();
@ -1076,11 +1115,11 @@ async fn bulk_reinvite_members(
))
}
Json(json!({
Ok(Json(json!({
"data": bulk_response,
"object": "list",
"continuationToken": null
}))
})))
}
#[post("/organizations/<org_id>/users/<member_id>/reinvite")]
@ -1249,7 +1288,10 @@ async fn bulk_confirm_invite(
headers: AdminHeaders,
mut conn: DbConn,
nt: Notify<'_>,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data = data.into_inner();
let mut bulk_response = Vec::new();
@ -1275,11 +1317,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/<member_id>/confirm", data = "<data>")]
@ -1376,19 +1418,22 @@ async fn _confirm_invite(
#[get("/organizations/<org_id>/users/mini-details", rank = 1)]
async fn get_org_user_mini_details(
org_id: OrganizationId,
_headers: ManagerHeadersLoose,
headers: ManagerHeadersLoose,
mut conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match");
}
let mut members_json = Vec::new();
for m in Membership::find_by_org(&org_id, &mut conn).await {
members_json.push(m.to_json_mini_details(&mut conn).await);
}
Json(json!({
Ok(Json(json!({
"data": members_json,
"object": "list",
"continuationToken": null,
}))
})))
}
#[get("/organizations/<org_id>/users/<member_id>?<data..>", rank = 2)]
@ -1396,9 +1441,12 @@ async fn get_user(
org_id: OrganizationId,
member_id: MembershipId,
data: GetOrgUserData,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let Some(user) = Membership::find_by_uuid_and_org(&member_id, &org_id, &mut conn).await else {
err!("The specified user isn't a member of the organization")
};
@ -1442,6 +1490,9 @@ async fn edit_member(
headers: AdminHeaders,
mut conn: DbConn,
) -> EmptyResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let mut data: EditUserData = data.into_inner();
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
@ -1563,7 +1614,10 @@ async fn bulk_delete_member(
headers: AdminHeaders,
mut conn: DbConn,
nt: Notify<'_>,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: BulkMembershipIds = data.into_inner();
let mut bulk_response = Vec::new();
@ -1582,11 +1636,11 @@ async fn bulk_delete_member(
))
}
Json(json!({
Ok(Json(json!({
"data": bulk_response,
"object": "list",
"continuationToken": null
}))
})))
}
#[delete("/organizations/<org_id>/users/<member_id>")]
@ -1656,9 +1710,12 @@ async fn _delete_member(
async fn bulk_public_keys(
org_id: OrganizationId,
data: Json<BulkMembershipIds>,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: BulkMembershipIds = data.into_inner();
let mut bulk_response = Vec::new();
@ -1682,11 +1739,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;
@ -1831,15 +1888,18 @@ async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers
}
#[get("/organizations/<org_id>/policies")]
async fn list_policies(org_id: OrganizationId, _headers: AdminHeaders, mut conn: DbConn) -> Json<Value> {
async fn list_policies(org_id: OrganizationId, headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let policies = OrgPolicy::find_by_org(&org_id, &mut conn).await;
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
Json(json!({
Ok(Json(json!({
"data": policies_json,
"object": "list",
"continuationToken": null
}))
})))
}
#[get("/organizations/<org_id>/policies/token?<token>")]
@ -1867,7 +1927,10 @@ async fn list_policies_token(org_id: OrganizationId, token: &str, mut conn: DbCo
}
#[get("/organizations/<org_id>/policies/<pol_type>")]
async fn get_policy(org_id: OrganizationId, pol_type: i32, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
async fn get_policy(org_id: OrganizationId, pol_type: i32, headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let Some(pol_type_enum) = OrgPolicyType::from_i32(pol_type) else {
err!("Invalid or unsupported policy type")
};
@ -1896,6 +1959,9 @@ async fn put_policy(
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: PolicyData = data.into_inner();
let Some(pol_type_enum) = OrgPolicyType::from_i32(pol_type) else {
@ -2229,7 +2295,7 @@ async fn bulk_deactivate_members(
data: Json<BulkRevokeMembershipIds>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
bulk_revoke_members(org_id, data, headers, conn).await
}
@ -2249,7 +2315,10 @@ async fn bulk_revoke_members(
data: Json<BulkRevokeMembershipIds>,
headers: AdminHeaders,
mut conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data = data.into_inner();
let mut bulk_response = Vec::new();
@ -2273,11 +2342,11 @@ async fn bulk_revoke_members(
None => error!("No users to revoke"),
}
Json(json!({
Ok(Json(json!({
"data": bulk_response,
"object": "list",
"continuationToken": null
}))
})))
}
async fn _revoke_member(
@ -2338,7 +2407,7 @@ async fn bulk_activate_members(
data: Json<BulkMembershipIds>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
bulk_restore_members(org_id, data, headers, conn).await
}
@ -2358,7 +2427,10 @@ async fn bulk_restore_members(
data: Json<BulkMembershipIds>,
headers: AdminHeaders,
mut conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data = data.into_inner();
let mut bulk_response = Vec::new();
@ -2377,11 +2449,11 @@ async fn bulk_restore_members(
));
}
Json(json!({
Ok(Json(json!({
"data": bulk_response,
"object": "list",
"continuationToken": null
}))
})))
}
async fn _restore_member(
@ -2438,7 +2510,10 @@ async fn _restore_member(
}
#[get("/organizations/<org_id>/groups")]
async fn get_groups(org_id: OrganizationId, _headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult {
async fn get_groups(org_id: OrganizationId, headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult {
if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match");
}
let groups: Vec<Value> = if CONFIG.org_groups_enabled() {
// Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::<Value>()
let groups = Group::find_by_organization(&org_id, &mut conn).await;
@ -2626,9 +2701,12 @@ async fn add_update_group(
async fn get_group_details(
org_id: OrganizationId,
group_id: GroupId,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
if !CONFIG.org_groups_enabled() {
err!("Group support is disabled");
}
@ -2708,7 +2786,10 @@ async fn bulk_delete_groups(
}
#[get("/organizations/<org_id>/groups/<group_id>", rank = 2)]
async fn get_group(org_id: OrganizationId, group_id: GroupId, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
async fn get_group(org_id: OrganizationId, group_id: GroupId, headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
if !CONFIG.org_groups_enabled() {
err!("Group support is disabled");
}
@ -2724,9 +2805,12 @@ async fn get_group(org_id: OrganizationId, group_id: GroupId, _headers: AdminHea
async fn get_group_members(
org_id: OrganizationId,
group_id: GroupId,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
if !CONFIG.org_groups_enabled() {
err!("Group support is disabled");
}
@ -2786,9 +2870,12 @@ async fn put_group_members(
async fn get_user_groups(
org_id: OrganizationId,
member_id: MembershipId,
_headers: AdminHeaders,
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
if !CONFIG.org_groups_enabled() {
err!("Group support is disabled");
}
@ -2828,6 +2915,9 @@ async fn put_user_groups(
headers: AdminHeaders,
mut conn: DbConn,
) -> EmptyResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
if !CONFIG.org_groups_enabled() {
err!("Group support is disabled");
}
@ -2877,6 +2967,9 @@ async fn delete_group_member(
headers: AdminHeaders,
mut conn: DbConn,
) -> EmptyResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
if !CONFIG.org_groups_enabled() {
err!("Group support is disabled");
}
@ -2922,7 +3015,14 @@ struct OrganizationUserResetPasswordRequest {
// But the clients do not seem to use this at all
// Just add it here in case they will
#[get("/organizations/<org_id>/public-key")]
async fn get_organization_public_key(org_id: OrganizationId, _headers: Headers, mut conn: DbConn) -> JsonResult {
async fn get_organization_public_key(
org_id: OrganizationId,
headers: OrgMemberHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else {
err!("Organization not found")
};
@ -2936,7 +3036,7 @@ async fn get_organization_public_key(org_id: OrganizationId, _headers: Headers,
// Obsolete - Renamed to public-key (2023.8), left for backwards compatibility with older clients
// https://github.com/bitwarden/server/blob/25dc0c9178e3e3584074bbef0d4be827b7c89415/src/Api/AdminConsole/Controllers/OrganizationsController.cs#L463-L468
#[get("/organizations/<org_id>/keys")]
async fn get_organization_keys(org_id: OrganizationId, headers: Headers, conn: DbConn) -> JsonResult {
async fn get_organization_keys(org_id: OrganizationId, headers: OrgMemberHeaders, conn: DbConn) -> JsonResult {
get_organization_public_key(org_id, headers, conn).await
}
@ -2949,6 +3049,9 @@ async fn put_reset_password(
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else {
err!("Required organization not found")
};
@ -3005,6 +3108,9 @@ async fn get_reset_password_details(
headers: AdminHeaders,
mut conn: DbConn,
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else {
err!("Required organization not found")
};
@ -3126,7 +3232,10 @@ async fn get_org_export(
headers: AdminHeaders,
client_version: Option<ClientVersion>,
mut conn: DbConn,
) -> Json<Value> {
) -> JsonResult {
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
// Since version v2023.1.0 the format of the export is different.
// Also, this endpoint was created since v2022.9.0.
// Therefore, we will check for any version smaller then v2023.1.0 and return a different response.
@ -3142,7 +3251,7 @@ async fn get_org_export(
// Also both main keys here need to be lowercase, else the export will fail.
if use_list_response_model {
// Backwards compatible pre v2023.1.0 response
Json(json!({
Ok(Json(json!({
"collections": {
"data": convert_json_key_lcase_first(_get_org_collections(&org_id, &mut conn).await),
"object": "list",
@ -3153,13 +3262,13 @@ async fn get_org_export(
"object": "list",
"continuationToken": null,
}
}))
})))
} else {
// v2023.1.0 and newer response
Json(json!({
Ok(Json(json!({
"collections": convert_json_key_lcase_first(_get_org_collections(&org_id, &mut conn).await),
"ciphers": convert_json_key_lcase_first(_get_org_details(&org_id, &headers.host, &headers.user.uuid, &mut conn).await),
}))
})))
}
}

View file

@ -612,6 +612,7 @@ pub struct AdminHeaders {
pub user: User,
pub membership_type: MembershipType,
pub ip: ClientIp,
pub org_id: OrganizationId,
}
#[rocket::async_trait]
@ -627,6 +628,7 @@ impl<'r> FromRequest<'r> for AdminHeaders {
user: headers.user,
membership_type: headers.membership_type,
ip: headers.ip,
org_id: headers.membership.org_uuid,
})
} else {
err_handler!("You need to be Admin or Owner to call this endpoint")
@ -672,6 +674,7 @@ pub struct ManagerHeaders {
pub device: Device,
pub user: User,
pub ip: ClientIp,
pub org_id: OrganizationId,
}
#[rocket::async_trait]
@ -700,6 +703,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders {
device: headers.device,
user: headers.user,
ip: headers.ip,
org_id: headers.membership.org_uuid,
})
} else {
err_handler!("You need to be a Manager, Admin or Owner to call this endpoint")
@ -779,6 +783,7 @@ impl ManagerHeaders {
device: h.device,
user: h.user,
ip: h.ip,
org_id: h.membership.org_uuid,
})
}
}