group support

This commit is contained in:
MFijak
2022-10-20 15:31:53 +02:00
parent 4cb5122e90
commit 21bc3bfd53
17 changed files with 1188 additions and 19 deletions

View File

@@ -1499,6 +1499,8 @@ pub struct CipherSyncData {
pub cipher_collections: HashMap<String, Vec<String>>,
pub user_organizations: HashMap<String, UserOrganization>,
pub user_collections: HashMap<String, CollectionUser>,
pub user_collections_groups: HashMap<String, CollectionGroup>,
pub user_group_full_access_for_organizations: HashSet<String>,
}
pub enum CipherSyncType {
@@ -1554,6 +1556,16 @@ impl CipherSyncData {
.collect()
.await;
// Generate a HashMap with the collections_uuid as key and the CollectionGroup record
let user_collections_groups = stream::iter(CollectionGroup::find_by_user(user_uuid, conn).await)
.map(|collection_group| (collection_group.collections_uuid.clone(), collection_group))
.collect()
.await;
// Get all organizations that the user has full access to via group assignement
let user_group_full_access_for_organizations =
stream::iter(Group::gather_user_organizations_full_access(user_uuid, conn).await).collect().await;
Self {
cipher_attachments,
cipher_folders,
@@ -1561,6 +1573,8 @@ impl CipherSyncData {
cipher_collections,
user_organizations,
user_collections,
user_collections_groups,
user_group_full_access_for_organizations,
}
}
}

View File

@@ -6,7 +6,8 @@ use serde_json::Value;
use crate::{
api::{
core::{CipherSyncData, CipherSyncType},
EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType,
ApiResult, EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, JsonVec, Notify, NumberOrString, PasswordData,
UpdateType,
},
auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
db::{models::*, DbConn},
@@ -71,6 +72,21 @@ pub fn routes() -> Vec<Route> {
bulk_activate_organization_user,
restore_organization_user,
bulk_restore_organization_user,
get_groups,
post_groups,
get_group,
put_group,
post_group,
get_group_details,
delete_group,
post_delete_group,
get_group_users,
put_group_users,
get_user_groups,
post_user_groups,
put_user_groups,
delete_group_user,
post_delete_group_user,
get_org_export
]
}
@@ -94,10 +110,19 @@ struct OrganizationUpdateData {
Name: String,
}
#[derive(Deserialize, Debug)]
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct NewCollectionData {
Name: String,
Groups: Vec<NewCollectionGroupData>,
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct NewCollectionGroupData {
HidePasswords: bool,
Id: String,
ReadOnly: bool,
}
#[derive(Deserialize)]
@@ -287,6 +312,12 @@ async fn post_organization_collections(
let collection = Collection::new(org.uuid, data.Name);
collection.save(&conn).await?;
for group in data.Groups {
CollectionGroup::new(collection.uuid.clone(), group.Id, group.ReadOnly, group.HidePasswords)
.save(&conn)
.await?;
}
// 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.
@@ -335,6 +366,12 @@ async fn post_organization_collection_update(
collection.name = data.Name;
collection.save(&conn).await?;
CollectionGroup::delete_all_by_collection(&col_id, &conn).await?;
for group in data.Groups {
CollectionGroup::new(col_id.clone(), group.Id, group.ReadOnly, group.HidePasswords).save(&conn).await?;
}
Ok(Json(collection.to_json()))
}
@@ -430,7 +467,19 @@ async fn get_org_collection_detail(
err!("Collection is not owned by organization")
}
Ok(Json(collection.to_json()))
let groups: Vec<Value> = CollectionGroup::find_by_collection(&collection.uuid, &conn)
.await
.iter()
.map(|collection_group| {
SelectionReadOnly::to_collection_group_details_read_only(collection_group).to_json()
})
.collect();
let mut json_object = collection.to_json();
json_object["Groups"] = json!(groups);
json_object["Object"] = json!("collectionGroupDetails");
Ok(Json(json_object))
}
}
}
@@ -1704,6 +1753,324 @@ async fn _restore_organization_user(
Ok(())
}
#[get("/organizations/<org_id>/groups")]
async fn get_groups(org_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let groups = Group::find_by_organization(&org_id, &conn).await.iter().map(Group::to_json).collect::<Value>();
Ok(Json(json!({
"Data": groups,
"Object": "list",
"ContinuationToken": null,
})))
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct GroupRequest {
Name: String,
AccessAll: Option<bool>,
ExternalId: Option<String>,
Collections: Vec<SelectionReadOnly>,
}
impl GroupRequest {
pub fn to_group(&self, organizations_uuid: &str) -> ApiResult<Group> {
match self.AccessAll {
Some(access_all_value) => Ok(Group::new(
organizations_uuid.to_owned(),
self.Name.clone(),
access_all_value,
self.ExternalId.clone(),
)),
_ => err!("Could not convert GroupRequest to Group, because AccessAll has no value!"),
}
}
pub fn update_group(&self, mut group: Group) -> ApiResult<Group> {
match self.AccessAll {
Some(access_all_value) => {
group.name = self.Name.clone();
group.access_all = access_all_value;
group.set_external_id(self.ExternalId.clone());
Ok(group)
}
_ => err!("Could not update group, because AccessAll has no value!"),
}
}
}
#[derive(Deserialize, Serialize)]
#[allow(non_snake_case)]
struct SelectionReadOnly {
Id: String,
ReadOnly: bool,
HidePasswords: bool,
}
impl SelectionReadOnly {
pub fn to_collection_group(&self, groups_uuid: String) -> CollectionGroup {
CollectionGroup::new(self.Id.clone(), groups_uuid, self.ReadOnly, self.HidePasswords)
}
pub fn to_group_details_read_only(collection_group: &CollectionGroup) -> SelectionReadOnly {
SelectionReadOnly {
Id: collection_group.collections_uuid.clone(),
ReadOnly: collection_group.read_only,
HidePasswords: collection_group.hide_passwords,
}
}
pub fn to_collection_group_details_read_only(collection_group: &CollectionGroup) -> SelectionReadOnly {
SelectionReadOnly {
Id: collection_group.groups_uuid.clone(),
ReadOnly: collection_group.read_only,
HidePasswords: collection_group.hide_passwords,
}
}
pub fn to_json(&self) -> Value {
json!(self)
}
}
#[post("/organizations/<_org_id>/groups/<group_id>", data = "<data>")]
async fn post_group(
_org_id: String,
group_id: String,
data: JsonUpcase<GroupRequest>,
_headers: AdminHeaders,
conn: DbConn,
) -> JsonResult {
put_group(_org_id, group_id, data, _headers, conn).await
}
#[post("/organizations/<org_id>/groups", data = "<data>")]
async fn post_groups(
org_id: String,
_headers: AdminHeaders,
data: JsonUpcase<GroupRequest>,
conn: DbConn,
) -> JsonResult {
let group_request = data.into_inner().data;
let group = group_request.to_group(&org_id)?;
add_update_group(group, group_request.Collections, &conn).await
}
#[put("/organizations/<_org_id>/groups/<group_id>", data = "<data>")]
async fn put_group(
_org_id: String,
group_id: String,
data: JsonUpcase<GroupRequest>,
_headers: AdminHeaders,
conn: DbConn,
) -> JsonResult {
let group = match Group::find_by_uuid(&group_id, &conn).await {
Some(group) => group,
None => err!("Group not found"),
};
let group_request = data.into_inner().data;
let updated_group = group_request.update_group(group)?;
CollectionGroup::delete_all_by_group(&group_id, &conn).await?;
add_update_group(updated_group, group_request.Collections, &conn).await
}
async fn add_update_group(mut group: Group, collections: Vec<SelectionReadOnly>, conn: &DbConn) -> JsonResult {
group.save(conn).await?;
for selection_read_only_request in collections {
let mut collection_group = selection_read_only_request.to_collection_group(group.uuid.clone());
collection_group.save(conn).await?;
}
Ok(Json(json!({
"Id": group.uuid,
"OrganizationId": group.organizations_uuid,
"Name": group.name,
"AccessAll": group.access_all,
"ExternalId": group.get_external_id()
})))
}
#[get("/organizations/<_org_id>/groups/<group_id>/details")]
async fn get_group_details(_org_id: String, group_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let group = match Group::find_by_uuid(&group_id, &conn).await {
Some(group) => group,
_ => err!("Group could not be found!"),
};
let collections_groups = CollectionGroup::find_by_group(&group_id, &conn)
.await
.iter()
.map(|entry| SelectionReadOnly::to_group_details_read_only(entry).to_json())
.collect::<Value>();
Ok(Json(json!({
"Id": group.uuid,
"OrganizationId": group.organizations_uuid,
"Name": group.name,
"AccessAll": group.access_all,
"ExternalId": group.get_external_id(),
"Collections": collections_groups
})))
}
#[post("/organizations/<org_id>/groups/<group_id>/delete")]
async fn post_delete_group(org_id: String, group_id: String, _headers: AdminHeaders, conn: DbConn) -> EmptyResult {
delete_group(org_id, group_id, _headers, conn).await
}
#[delete("/organizations/<_org_id>/groups/<group_id>")]
async fn delete_group(_org_id: String, group_id: String, _headers: AdminHeaders, conn: DbConn) -> EmptyResult {
let group = match Group::find_by_uuid(&group_id, &conn).await {
Some(group) => group,
_ => err!("Group not found"),
};
group.delete(&conn).await
}
#[get("/organizations/<_org_id>/groups/<group_id>")]
async fn get_group(_org_id: String, group_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let group = match Group::find_by_uuid(&group_id, &conn).await {
Some(group) => group,
_ => err!("Group not found"),
};
Ok(Json(group.to_json()))
}
#[get("/organizations/<_org_id>/groups/<group_id>/users")]
async fn get_group_users(_org_id: String, group_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
match Group::find_by_uuid(&group_id, &conn).await {
Some(_) => { /* Do nothing */ }
_ => err!("Group could not be found!"),
};
let group_users: Vec<String> = GroupUser::find_by_group(&group_id, &conn)
.await
.iter()
.map(|entry| entry.users_organizations_uuid.clone())
.collect();
Ok(Json(json!(group_users)))
}
#[put("/organizations/<_org_id>/groups/<group_id>/users", data = "<data>")]
async fn put_group_users(
_org_id: String,
group_id: String,
_headers: AdminHeaders,
data: JsonVec<String>,
conn: DbConn,
) -> EmptyResult {
match Group::find_by_uuid(&group_id, &conn).await {
Some(_) => { /* Do nothing */ }
_ => err!("Group could not be found!"),
};
GroupUser::delete_all_by_group(&group_id, &conn).await?;
let assigned_user_ids = data.into_inner();
for assigned_user_id in assigned_user_ids {
let mut user_entry = GroupUser::new(group_id.clone(), assigned_user_id);
user_entry.save(&conn).await?;
}
Ok(())
}
#[get("/organizations/<_org_id>/users/<user_id>/groups")]
async fn get_user_groups(_org_id: String, user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
match UserOrganization::find_by_uuid(&user_id, &conn).await {
Some(_) => { /* Do nothing */ }
_ => err!("User could not be found!"),
};
let user_groups: Vec<String> =
GroupUser::find_by_user(&user_id, &conn).await.iter().map(|entry| entry.groups_uuid.clone()).collect();
Ok(Json(json!(user_groups)))
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct OrganizationUserUpdateGroupsRequest {
GroupIds: Vec<String>,
}
#[post("/organizations/<_org_id>/users/<user_id>/groups", data = "<data>")]
async fn post_user_groups(
_org_id: String,
user_id: String,
data: JsonUpcase<OrganizationUserUpdateGroupsRequest>,
_headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
put_user_groups(_org_id, user_id, data, _headers, conn).await
}
#[put("/organizations/<_org_id>/users/<user_id>/groups", data = "<data>")]
async fn put_user_groups(
_org_id: String,
user_id: String,
data: JsonUpcase<OrganizationUserUpdateGroupsRequest>,
_headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
match UserOrganization::find_by_uuid(&user_id, &conn).await {
Some(_) => { /* Do nothing */ }
_ => err!("User could not be found!"),
};
GroupUser::delete_all_by_user(&user_id, &conn).await?;
let assigned_group_ids = data.into_inner().data;
for assigned_group_id in assigned_group_ids.GroupIds {
let mut group_user = GroupUser::new(assigned_group_id.clone(), user_id.clone());
group_user.save(&conn).await?;
}
Ok(())
}
#[post("/organizations/<org_id>/groups/<group_id>/delete-user/<user_id>")]
async fn post_delete_group_user(
org_id: String,
group_id: String,
user_id: String,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
delete_group_user(org_id, group_id, user_id, headers, conn).await
}
#[delete("/organizations/<_org_id>/groups/<group_id>/users/<user_id>")]
async fn delete_group_user(
_org_id: String,
group_id: String,
user_id: String,
_headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
match UserOrganization::find_by_uuid(&user_id, &conn).await {
Some(_) => { /* Do nothing */ }
_ => err!("User could not be found!"),
};
match Group::find_by_uuid(&group_id, &conn).await {
Some(_) => { /* Do nothing */ }
_ => err!("Group could not be found!"),
};
GroupUser::delete_by_group_id_and_user_id(&group_id, &user_id, &conn).await
}
// This is a new function active since the v2022.9.x clients.
// It combines the previous two calls done before.
// We call those two functions here and combine them our selfs.

View File

@@ -33,6 +33,7 @@ pub type EmptyResult = ApiResult<()>;
type JsonUpcase<T> = Json<util::UpCase<T>>;
type JsonUpcaseVec<T> = Json<Vec<util::UpCase<T>>>;
type JsonVec<T> = Json<Vec<T>>;
// Common structs representing JSON data received
#[derive(Deserialize)]