Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2024-11-26 05:50:29 +01:00
Adding Manager Role support
This has been requested a few times (#1136 & #246 & forum), and there already were two (1:1 duplicate) PR's (#1222 & #1223) which needed some changes and no followups or further comments unfortunally. This PR adds two auth headers. - ManagerHeaders Checks if the user-type is Manager or higher and if the manager is part of that collection or not. - ManagerHeadersLoose Check if the user-type is Manager or higher, but does not check if the user is part of the collection, needed for a few features like retreiving all the users of an org. I think this is the safest way to implement this instead of having to check this within every function which needs this manually. Also some extra checks if a manager has access to all collections or just a selection. fixes #1136
Dieser Commit ist enthalten in:
Ursprung
9824d94a1c
Commit
7cf8809d77
2 geänderte Dateien mit 153 neuen und 11 gelöschten Zeilen
|
@ -5,7 +5,7 @@ use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType},
|
api::{EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType},
|
||||||
auth::{decode_invite, AdminHeaders, Headers, OwnerHeaders},
|
auth::{decode_invite, AdminHeaders, Headers, OwnerHeaders, ManagerHeaders, ManagerHeadersLoose},
|
||||||
db::{models::*, DbConn},
|
db::{models::*, DbConn},
|
||||||
mail, CONFIG,
|
mail, CONFIG,
|
||||||
};
|
};
|
||||||
|
@ -217,7 +217,7 @@ fn get_org_collections(org_id: String, _headers: AdminHeaders, conn: DbConn) ->
|
||||||
#[post("/organizations/<org_id>/collections", data = "<data>")]
|
#[post("/organizations/<org_id>/collections", data = "<data>")]
|
||||||
fn post_organization_collections(
|
fn post_organization_collections(
|
||||||
org_id: String,
|
org_id: String,
|
||||||
_headers: AdminHeaders,
|
headers: ManagerHeadersLoose,
|
||||||
data: JsonUpcase<NewCollectionData>,
|
data: JsonUpcase<NewCollectionData>,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
|
@ -228,9 +228,22 @@ fn post_organization_collections(
|
||||||
None => err!("Can't find organization details"),
|
None => err!("Can't find organization details"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get the user_organization record so that we can check if the user has access to all collections.
|
||||||
|
let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
||||||
|
Some(u) => u,
|
||||||
|
None => err!("User is not part of organization"),
|
||||||
|
};
|
||||||
|
|
||||||
let collection = Collection::new(org.uuid, data.Name);
|
let collection = Collection::new(org.uuid, data.Name);
|
||||||
collection.save(&conn)?;
|
collection.save(&conn)?;
|
||||||
|
|
||||||
|
// If the user doesn't have access to all collections, only in case of a Manger,
|
||||||
|
// then we need to save the creating user uuid (Manager) to the users_collection table.
|
||||||
|
// Else the user will not have access to his own created collection.
|
||||||
|
if !user_org.access_all {
|
||||||
|
CollectionUser::save(&headers.user.uuid, &collection.uuid, false, false, &conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Json(collection.to_json()))
|
Ok(Json(collection.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +251,7 @@ fn post_organization_collections(
|
||||||
fn put_organization_collection_update(
|
fn put_organization_collection_update(
|
||||||
org_id: String,
|
org_id: String,
|
||||||
col_id: String,
|
col_id: String,
|
||||||
headers: AdminHeaders,
|
headers: ManagerHeaders,
|
||||||
data: JsonUpcase<NewCollectionData>,
|
data: JsonUpcase<NewCollectionData>,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
|
@ -249,7 +262,7 @@ fn put_organization_collection_update(
|
||||||
fn post_organization_collection_update(
|
fn post_organization_collection_update(
|
||||||
org_id: String,
|
org_id: String,
|
||||||
col_id: String,
|
col_id: String,
|
||||||
_headers: AdminHeaders,
|
_headers: ManagerHeaders,
|
||||||
data: JsonUpcase<NewCollectionData>,
|
data: JsonUpcase<NewCollectionData>,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
|
@ -317,7 +330,7 @@ fn post_organization_collection_delete_user(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/organizations/<org_id>/collections/<col_id>")]
|
#[delete("/organizations/<org_id>/collections/<col_id>")]
|
||||||
fn delete_organization_collection(org_id: String, col_id: String, _headers: AdminHeaders, conn: DbConn) -> EmptyResult {
|
fn delete_organization_collection(org_id: String, col_id: String, _headers: ManagerHeaders, conn: DbConn) -> EmptyResult {
|
||||||
match Collection::find_by_uuid(&col_id, &conn) {
|
match Collection::find_by_uuid(&col_id, &conn) {
|
||||||
None => err!("Collection not found"),
|
None => err!("Collection not found"),
|
||||||
Some(collection) => {
|
Some(collection) => {
|
||||||
|
@ -341,7 +354,7 @@ struct DeleteCollectionData {
|
||||||
fn post_organization_collection_delete(
|
fn post_organization_collection_delete(
|
||||||
org_id: String,
|
org_id: String,
|
||||||
col_id: String,
|
col_id: String,
|
||||||
headers: AdminHeaders,
|
headers: ManagerHeaders,
|
||||||
_data: JsonUpcase<DeleteCollectionData>,
|
_data: JsonUpcase<DeleteCollectionData>,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
|
@ -349,7 +362,7 @@ fn post_organization_collection_delete(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/collections/<coll_id>/details")]
|
#[get("/organizations/<org_id>/collections/<coll_id>/details")]
|
||||||
fn get_org_collection_detail(org_id: String, coll_id: String, headers: AdminHeaders, conn: DbConn) -> JsonResult {
|
fn get_org_collection_detail(org_id: String, coll_id: String, headers: ManagerHeaders, conn: DbConn) -> JsonResult {
|
||||||
match Collection::find_by_uuid_and_user(&coll_id, &headers.user.uuid, &conn) {
|
match Collection::find_by_uuid_and_user(&coll_id, &headers.user.uuid, &conn) {
|
||||||
None => err!("Collection not found"),
|
None => err!("Collection not found"),
|
||||||
Some(collection) => {
|
Some(collection) => {
|
||||||
|
@ -363,7 +376,7 @@ fn get_org_collection_detail(org_id: String, coll_id: String, headers: AdminHead
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/collections/<coll_id>/users")]
|
#[get("/organizations/<org_id>/collections/<coll_id>/users")]
|
||||||
fn get_collection_users(org_id: String, coll_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
|
fn get_collection_users(org_id: String, coll_id: String, _headers: ManagerHeaders, conn: DbConn) -> JsonResult {
|
||||||
// Get org and collection, check that collection is from org
|
// Get org and collection, check that collection is from org
|
||||||
let collection = match Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn) {
|
let collection = match Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn) {
|
||||||
None => err!("Collection not found in Organization"),
|
None => err!("Collection not found in Organization"),
|
||||||
|
@ -388,7 +401,7 @@ fn put_collection_users(
|
||||||
org_id: String,
|
org_id: String,
|
||||||
coll_id: String,
|
coll_id: String,
|
||||||
data: JsonUpcaseVec<CollectionData>,
|
data: JsonUpcaseVec<CollectionData>,
|
||||||
_headers: AdminHeaders,
|
_headers: ManagerHeaders,
|
||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
// Get org and collection, check that collection is from org
|
// Get org and collection, check that collection is from org
|
||||||
|
@ -440,7 +453,7 @@ fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> Jso
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/users")]
|
#[get("/organizations/<org_id>/users")]
|
||||||
fn get_org_users(org_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
|
fn get_org_users(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult {
|
||||||
let users = UserOrganization::find_by_org(&org_id, &conn);
|
let users = UserOrganization::find_by_org(&org_id, &conn);
|
||||||
let users_json: Vec<Value> = users.iter().map(|c| c.to_json_user_details(&conn)).collect();
|
let users_json: Vec<Value> = users.iter().map(|c| c.to_json_user_details(&conn)).collect();
|
||||||
|
|
||||||
|
|
131
src/auth.rs
131
src/auth.rs
|
@ -220,7 +220,7 @@ use rocket::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::db::{
|
use crate::db::{
|
||||||
models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization},
|
models::{Device, User, UserOrgStatus, UserOrgType, UserOrganization, CollectionUser},
|
||||||
DbConn,
|
DbConn,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -310,6 +310,8 @@ pub struct OrgHeaders {
|
||||||
pub device: Device,
|
pub device: Device,
|
||||||
pub user: User,
|
pub user: User,
|
||||||
pub org_user_type: UserOrgType,
|
pub org_user_type: UserOrgType,
|
||||||
|
pub org_user: UserOrganization,
|
||||||
|
pub org_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// org_id is usually the second param ("/organizations/<org_id>")
|
// org_id is usually the second param ("/organizations/<org_id>")
|
||||||
|
@ -370,6 +372,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
|
||||||
err_handler!("Unknown user type in the database")
|
err_handler!("Unknown user type in the database")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
org_user,
|
||||||
|
org_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => err_handler!("Error getting the organization id"),
|
_ => err_handler!("Error getting the organization id"),
|
||||||
|
@ -419,6 +423,131 @@ impl Into<Headers> for AdminHeaders {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// col_id is usually the forth param ("/organizations/<org_id>/collections/<col_id>")
|
||||||
|
// But there cloud be cases where it is located in a query value.
|
||||||
|
// First check the param, if this is not a valid uuid, we will try the query value.
|
||||||
|
fn get_col_id(request: &Request) -> Option<String> {
|
||||||
|
if let Some(Ok(col_id)) = request.get_param::<String>(3) {
|
||||||
|
if uuid::Uuid::parse_str(&col_id).is_ok() {
|
||||||
|
return Some(col_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Ok(col_id)) = request.get_query_value::<String>("collectionId") {
|
||||||
|
if uuid::Uuid::parse_str(&col_id).is_ok() {
|
||||||
|
return Some(col_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ManagerHeaders are used to check if you are at least a Manager
|
||||||
|
/// and have access to the specific collection provided via the <col_id>/collections/collectionId.
|
||||||
|
/// This does strict checking on the collection_id, ManagerHeadersLoose does not.
|
||||||
|
pub struct ManagerHeaders {
|
||||||
|
pub host: String,
|
||||||
|
pub device: Device,
|
||||||
|
pub user: User,
|
||||||
|
pub org_user_type: UserOrgType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for ManagerHeaders {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||||
|
match request.guard::<OrgHeaders>() {
|
||||||
|
Outcome::Forward(_) => Outcome::Forward(()),
|
||||||
|
Outcome::Failure(f) => Outcome::Failure(f),
|
||||||
|
Outcome::Success(headers) => {
|
||||||
|
if headers.org_user_type >= UserOrgType::Manager {
|
||||||
|
match get_col_id(request) {
|
||||||
|
Some(col_id) => {
|
||||||
|
let conn = match request.guard::<DbConn>() {
|
||||||
|
Outcome::Success(conn) => conn,
|
||||||
|
_ => err_handler!("Error getting DB"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !headers.org_user.access_all {
|
||||||
|
match CollectionUser::find_by_collection_and_user(&col_id, &headers.org_user.user_uuid, &conn) {
|
||||||
|
Some(_) => (),
|
||||||
|
None => err_handler!("The current user isn't a manager for this collection"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => err_handler!("Error getting the collection id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Outcome::Success(Self {
|
||||||
|
host: headers.host,
|
||||||
|
device: headers.device,
|
||||||
|
user: headers.user,
|
||||||
|
org_user_type: headers.org_user_type,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err_handler!("You need to be a Manager, Admin or Owner to call this endpoint")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Headers> for ManagerHeaders {
|
||||||
|
fn into(self) -> Headers {
|
||||||
|
Headers {
|
||||||
|
host: self.host,
|
||||||
|
device: self.device,
|
||||||
|
user: self.user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ManagerHeadersLoose is used when you at least need to be a Manager,
|
||||||
|
/// but there is no collection_id sent with the request (either in the path or as form data).
|
||||||
|
pub struct ManagerHeadersLoose {
|
||||||
|
pub host: String,
|
||||||
|
pub device: Device,
|
||||||
|
pub user: User,
|
||||||
|
pub org_user_type: UserOrgType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for ManagerHeadersLoose {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||||
|
match request.guard::<OrgHeaders>() {
|
||||||
|
Outcome::Forward(_) => Outcome::Forward(()),
|
||||||
|
Outcome::Failure(f) => Outcome::Failure(f),
|
||||||
|
Outcome::Success(headers) => {
|
||||||
|
if headers.org_user_type >= UserOrgType::Manager {
|
||||||
|
Outcome::Success(Self {
|
||||||
|
host: headers.host,
|
||||||
|
device: headers.device,
|
||||||
|
user: headers.user,
|
||||||
|
org_user_type: headers.org_user_type,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err_handler!("You need to be a Manager, Admin or Owner to call this endpoint")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Headers> for ManagerHeadersLoose {
|
||||||
|
fn into(self) -> Headers {
|
||||||
|
Headers {
|
||||||
|
host: self.host,
|
||||||
|
device: self.device,
|
||||||
|
user: self.user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OwnerHeaders {
|
pub struct OwnerHeaders {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub device: Device,
|
pub device: Device,
|
||||||
|
|
Laden …
In neuem Issue referenzieren