1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2025-03-13 16:57:01 +01:00

Added claims to the Headers to verify user type.

The claims contains the current rights of which orgs the user belongs
to and what rights it has. This is usefull for requests which do not
provide any organization ID in the requests, but we need it to verify if
that request is allowed to be executed. This saves complex database
calls since it is already in the claims.

There is one downside. If the user level has been changed, or is added
to a new org, the claims are not updated instantly. A user should log
out and back in to receive the correct claims/rights.
Dieser Commit ist enthalten in:
BlackDex 2020-03-31 18:15:06 +02:00
Ursprung 5471088e93
Commit a0ad3b952b
2 geänderte Dateien mit 72 neuen und 27 gelöschten Zeilen

Datei anzeigen

@ -472,7 +472,7 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade
None => err!("Invalid type"),
};
if new_type != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
if new_type != UserOrgType::User && !headers.claims.is_organization_owner(&org_id) {
err!("Only Owners can invite Managers, Admins or Owners")
}
@ -662,7 +662,7 @@ fn confirm_invite(
None => err!("The specified user isn't a member of the organization"),
};
if user_to_confirm.atype != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
if user_to_confirm.atype != UserOrgType::User && !headers.claims.is_organization_owner(&org_id) {
err!("Only Owners can confirm Managers, Admins or Owners")
}
@ -742,12 +742,12 @@ fn edit_user(
if new_type != user_to_edit.atype
&& (user_to_edit.atype >= UserOrgType::Admin || new_type >= UserOrgType::Admin)
&& headers.org_user_type != UserOrgType::Owner
&& !headers.claims.is_organization_owner(&org_id)
{
err!("Only Owners can grant and remove Admin or Owner privileges")
}
if user_to_edit.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
if user_to_edit.atype == UserOrgType::Owner && !headers.claims.is_organization_owner(&org_id) {
err!("Only Owners can edit Owner users")
}
@ -790,7 +790,7 @@ fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn:
None => err!("User to delete isn't member of the organization"),
};
if user_to_delete.atype != UserOrgType::User && headers.org_user_type != UserOrgType::Owner {
if user_to_delete.atype != UserOrgType::User && !headers.claims.is_organization_owner(&org_id) {
err!("Only Owners can delete Admins or Owners")
}

Datei anzeigen

@ -4,7 +4,6 @@
use crate::util::read_file;
use chrono::{Duration, Utc};
use once_cell::sync::Lazy;
use num_traits::FromPrimitive;
use jsonwebtoken::{self, Algorithm, Header, EncodingKey, DecodingKey};
use serde::de::DeserializeOwned;
@ -76,7 +75,7 @@ pub fn decode_admin(token: &str) -> Result<AdminJWTClaims, Error> {
decode_jwt(token, JWT_ADMIN_ISSUER.to_string())
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LoginJWTClaims {
// Not before
pub nbf: i64,
@ -107,6 +106,42 @@ pub struct LoginJWTClaims {
pub amr: Vec<String>,
}
impl LoginJWTClaims {
pub fn is_organization_owner(&self, org_uuid: &str) -> bool {
if self.orgowner.contains(&org_uuid.to_string()) {
return true;
}
false
}
pub fn is_organization_admin(&self, org_uuid: &str) -> bool {
if self.orgadmin.contains(&org_uuid.to_string()) {
return true;
}
self.is_organization_owner(&org_uuid)
}
#[allow(dead_code)]
pub fn is_organization_manager(&self, org_uuid: &str) -> bool {
if self.orgmanager.contains(&org_uuid.to_string()) {
return true;
}
self.is_organization_admin(&org_uuid)
}
#[allow(dead_code)]
pub fn is_organization_user(&self, org_uuid: &str) -> bool {
if self.orguser.contains(&org_uuid.to_string()) {
return true;
}
self.is_organization_manager(&org_uuid)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InviteJWTClaims {
// Not before
@ -216,13 +251,14 @@ pub fn generate_admin_claims() -> AdminJWTClaims {
use rocket::request::{self, FromRequest, Request};
use rocket::Outcome;
use crate::db::models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization};
use crate::db::models::{Device, User, UserOrgStatus, UserOrganization};
use crate::db::DbConn;
pub struct Headers {
pub host: String,
pub device: Device,
pub user: User,
pub claims: LoginJWTClaims,
}
impl<'a, 'r> FromRequest<'a, 'r> for Headers {
@ -273,9 +309,10 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
Ok(claims) => claims,
Err(_) => err_handler!("Invalid claim"),
};
let claim = claims.clone();
let device_uuid = claims.device;
let user_uuid = claims.sub;
let device_uuid = claim.device;
let user_uuid = claim.sub;
let conn = match request.guard::<DbConn>() {
Outcome::Success(conn) => conn,
@ -292,11 +329,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
None => err_handler!("Device has no user associated"),
};
if user.security_stamp != claims.sstamp {
if user.security_stamp != claim.sstamp {
err_handler!("Invalid security stamp")
}
Outcome::Success(Headers { host, device, user })
Outcome::Success(Headers { host, device, user, claims })
}
}
@ -304,7 +341,8 @@ pub struct OrgHeaders {
pub host: String,
pub device: Device,
pub user: User,
pub org_user_type: UserOrgType,
pub claims: LoginJWTClaims,
pub context_org_uuid: String,
}
// org_id is usually the second param ("/organizations/<org_id>")
@ -336,13 +374,19 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
Outcome::Success(headers) => {
match get_org_id(request) {
Some(org_id) => {
// This check would have been sufficient if the claims would be updated.
// Since this is not the case, we keep the database check here and disable this check for now.
// if !headers.claims.is_organization_user(&org_id) {
// err_handler!("The current user isn't member of the organization")
// }
let conn = match request.guard::<DbConn>() {
Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"),
};
let user = headers.user;
let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn) {
match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn) {
Some(user) => {
if user.status == UserOrgStatus::Confirmed as i32 {
user
@ -357,14 +401,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
host: headers.host,
device: headers.device,
user,
org_user_type: {
if let Some(org_usr_type) = UserOrgType::from_i32(org_user.atype) {
org_usr_type
} else {
// This should only happen if the DB is corrupted
err_handler!("Unknown user type in the database")
}
},
claims: headers.claims,
context_org_uuid: org_id,
})
},
_ => err_handler!("Error getting the organization id"),
@ -378,7 +416,8 @@ pub struct AdminHeaders {
pub host: String,
pub device: Device,
pub user: User,
pub org_user_type: UserOrgType,
pub claims: LoginJWTClaims,
pub context_org_uuid: String,
}
impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
@ -389,12 +428,13 @@ impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
Outcome::Forward(_) => Outcome::Forward(()),
Outcome::Failure(f) => Outcome::Failure(f),
Outcome::Success(headers) => {
if headers.org_user_type >= UserOrgType::Admin {
if headers.claims.is_organization_admin(&headers.context_org_uuid) {
Outcome::Success(Self {
host: headers.host,
device: headers.device,
user: headers.user,
org_user_type: headers.org_user_type,
claims: headers.claims,
context_org_uuid: headers.context_org_uuid,
})
} else {
err_handler!("You need to be Admin or Owner to call this endpoint")
@ -409,7 +449,8 @@ impl Into<Headers> for AdminHeaders {
Headers {
host: self.host,
device: self.device,
user: self.user
user: self.user,
claims: self.claims
}
}
}
@ -418,6 +459,8 @@ pub struct OwnerHeaders {
pub host: String,
pub device: Device,
pub user: User,
pub claims: LoginJWTClaims,
pub context_org_uuid: String,
}
impl<'a, 'r> FromRequest<'a, 'r> for OwnerHeaders {
@ -428,11 +471,13 @@ impl<'a, 'r> FromRequest<'a, 'r> for OwnerHeaders {
Outcome::Forward(_) => Outcome::Forward(()),
Outcome::Failure(f) => Outcome::Failure(f),
Outcome::Success(headers) => {
if headers.org_user_type == UserOrgType::Owner {
if headers.claims.is_organization_owner(&headers.context_org_uuid) {
Outcome::Success(Self {
host: headers.host,
device: headers.device,
user: headers.user,
claims: headers.claims,
context_org_uuid: headers.context_org_uuid,
})
} else {
err_handler!("You need to be Owner to call this endpoint")