Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TASK-5804 - Organization updates can be performed by any user #2424

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ static EnumSet<StudyPermissions.Permissions> getLockedAcls() {
return EnumSet.noneOf(StudyPermissions.Permissions.class);
}

void checkCanViewOrganization(String organizationId, String userId) throws CatalogException;

void checkCanViewProject(String organizationId, long projectId, String userId) throws CatalogException;

void checkCanEditProject(String organizationId, long projectId, String userId) throws CatalogException;
Expand Down Expand Up @@ -132,9 +134,18 @@ default void checkIsOpencgaAdministrator(String organization, String userId) thr

void checkIsOpencgaAdministrator(String organization, String userId, String action) throws CatalogException;

/**
* Check if the given user is the organization owner.
* If the user is not the owner, it will throw an exception.
*
* @param organizationId Organization id
* @param userId User id
* @throws CatalogException CatalogException
*/
void checkIsAtLeastOrganizationOwner(String organizationId, String userId) throws CatalogException;

/**
* Check if the given user is the owner of the organization or if it is an admin.
* It does not include the opencga admins.
* If the user is not the owner or an admin, it will throw an exception.
*
* @param organizationId Organization id
Expand All @@ -144,7 +155,17 @@ default void checkIsOpencgaAdministrator(String organization, String userId) thr
void checkIsAtLeastOrganizationOwnerOrAdmin(String organizationId, String userId) throws CatalogException;

/**
* Check if the given user is the owner or one of the admins of the organization or or one of the Opencga administrators.
* Check if the given user is the organization owner or one of the Opencga administrators.
*
* @param organization Organization
* @param userId User id
* @return true if the user is the organization owner or one of the Opencga administrators.
* @throws CatalogException CatalogDBException
*/
boolean isAtLeastOrganizationOwner(String organization, String userId) throws CatalogException;

/**
* Check if the given user is the owner or one of the admins of the organization or one of the Opencga administrators.
*
* @param organization Organization
* @param userId User id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ public void checkCanEditProject(String organizationId, long projectId, String us
throw new CatalogAuthorizationException("Permission denied: Only the organization owner or administrators can update the project.");
}

@Override
public void checkCanViewOrganization(String organizationId, String userId) throws CatalogException {
if (isOpencgaAdministrator(organizationId, userId)) {
return;
}
// Check user belongs to organization
dbAdaptorFactory.getCatalogUserDBAdaptor(organizationId).checkId(userId);
}

@Override
public void checkCanViewProject(String organizationId, long projectId, String userId) throws CatalogException {
if (isAtLeastOrganizationOwnerOrAdmin(organizationId, userId)) {
Expand Down Expand Up @@ -226,13 +235,30 @@ public void checkIsOpencgaAdministrator(String organizationId, String userId, St
}
}

@Override
public void checkIsAtLeastOrganizationOwner(String organizationId, String userId) throws CatalogException {
if (!isAtLeastOrganizationOwner(organizationId, userId)) {
throw CatalogAuthorizationException.notOrganizationOwner();
}
}

@Override
public void checkIsAtLeastOrganizationOwnerOrAdmin(String organizationId, String userId) throws CatalogException {
if (!isAtLeastOrganizationOwnerOrAdmin(organizationId, userId)) {
throw CatalogAuthorizationException.notOrganizationOwnerOrAdmin();
}
}

@Override
public boolean isAtLeastOrganizationOwner(String organizationId, String userId) throws CatalogException {
if (isOpencgaAdministrator(organizationId, userId)) {
return true;
}
OrganizationDBAdaptor organizationDBAdaptor = dbAdaptorFactory.getCatalogOrganizationDBAdaptor(organizationId);
Organization organization = organizationDBAdaptor.get(OrganizationManager.INCLUDE_ORGANIZATION_ADMINS).first();
return organization.getOwner().equals(userId);
}

@Override
public boolean isAtLeastOrganizationOwnerOrAdmin(String organizationId, String userId) throws CatalogException {
if (isOpencgaAdministrator(organizationId, userId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mongodb.client.ClientSession;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
Expand All @@ -21,6 +22,7 @@
import org.opencb.opencga.catalog.utils.Constants;
import org.opencb.opencga.catalog.utils.ParamUtils;
import org.opencb.opencga.catalog.utils.UuidUtils;
import org.opencb.opencga.core.api.ParamConstants;
import org.opencb.opencga.core.common.TimeUtils;
import org.opencb.opencga.core.config.Configuration;
import org.opencb.opencga.core.models.organizations.Organization;
Expand Down Expand Up @@ -137,8 +139,11 @@ private OpenCGAResult<Organization> privateUpdate(ClientSession clientSession, S
List<Event> events = new ArrayList<>();
logger.debug("Update organization. Query: {}, Update: {}", queryBson.toBsonDocument(), organizationUpdate.toBsonDocument());

// Update study admins need to be before because we need to fetch the previous owner/admins in case of an update on these fields.
updateStudyAdmins(clientSession, parameters, queryOptions);
DataResult<?> updateResult = organizationCollection.update(clientSession, queryBson, organizationUpdate, null);


if (updateResult.getNumMatches() == 0) {
throw new CatalogDBException("Organization not found");
}
Expand All @@ -151,6 +156,61 @@ private OpenCGAResult<Organization> privateUpdate(ClientSession clientSession, S
return endWrite(tmpStartTime, 1, 1, events);
}

private void updateStudyAdmins(ClientSession clientSession, ObjectMap parameters, QueryOptions options) throws CatalogDBException {
if (!parameters.containsKey(QueryParams.OWNER.key()) && !parameters.containsKey(QueryParams.ADMINS.key())) {
return;
}

Organization organization = get(clientSession, OrganizationManager.INCLUDE_ORGANIZATION_ADMINS).first();

if (parameters.containsKey(QueryParams.OWNER.key())) {
// Owner has changed
String newOwner = parameters.getString(QueryParams.OWNER.key());
// Only do changes if the owner actually changes
if (!newOwner.equals(organization.getOwner())) {
if (!organization.getAdmins().contains(organization.getOwner())) {
// Remove Owner from all @admins groups
logger.info("Removing old owner '{}' from all '{}' groups", organization.getOwner(), ParamConstants.ADMINS_GROUP);
dbAdaptorFactory.getCatalogStudyDBAdaptor().removeUsersFromAdminsGroup(clientSession,
Collections.singletonList(organization.getOwner()));
}
// Add new owner to @admins group
logger.info("Adding new owner '{}' to all '{}' groups", newOwner, ParamConstants.ADMINS_GROUP);
dbAdaptorFactory.getCatalogStudyDBAdaptor().addUsersToAdminsAndMembersGroup(clientSession,
Collections.singletonList(newOwner));
}
}

if (parameters.containsKey(QueryParams.ADMINS.key())) {
List<String> admins = parameters.getAsStringList(QueryParams.ADMINS.key());
if (CollectionUtils.isNotEmpty(admins)) {
Map<String, Object> actionMap = options.getMap(Constants.ACTIONS, new HashMap<>());
ParamUtils.AddRemoveAction operation = ParamUtils.AddRemoveAction.from(actionMap, QueryParams.ADMINS.key(),
ParamUtils.AddRemoveAction.ADD);

switch (operation) {
case ADD:
// Add new admins to @admins group
logger.info("Adding new admins '{}' to all '{}' groups", admins, ParamConstants.ADMINS_GROUP);
dbAdaptorFactory.getCatalogStudyDBAdaptor().addUsersToAdminsAndMembersGroup(clientSession, admins);
break;
case REMOVE:
// Fetch current organization owner
String newOwner = parameters.getString(QueryParams.OWNER.key());
String owner = StringUtils.isNotEmpty(newOwner) ? newOwner : organization.getOwner();

// Remove organization owner in case is one of the removed admins
admins = admins.stream().filter(a -> !owner.equals(a)).collect(Collectors.toList());
logger.info("Removing old admins '{}' from all '{}' groups", admins, ParamConstants.ADMINS_GROUP);
dbAdaptorFactory.getCatalogStudyDBAdaptor().removeUsersFromAdminsGroup(clientSession, admins);
break;
default:
throw new CatalogDBException("Unexpected " + QueryParams.ADMINS.key() + " action");
}
}
}
}

private UpdateDocument getValidatedUpdateParams(ClientSession clientSession, ObjectMap parameters, QueryOptions queryOptions)
throws CatalogParameterException, CatalogDBException {
checkUpdatedParams(parameters, Arrays.asList(QueryParams.NAME.key(), QueryParams.OWNER.key(),
Expand All @@ -159,9 +219,7 @@ private UpdateDocument getValidatedUpdateParams(ClientSession clientSession, Obj

UpdateDocument document = new UpdateDocument();

String[] acceptedParams = {
QueryParams.NAME.key(), QueryParams.OWNER.key(),
};
String[] acceptedParams = { QueryParams.NAME.key() };
filterStringParams(parameters, document.getSet(), acceptedParams);

String[] acceptedObjectParams = { QueryParams.CONFIGURATION.key() };
Expand All @@ -175,6 +233,13 @@ private UpdateDocument getValidatedUpdateParams(ClientSession clientSession, Obj
if (count.getNumMatches() == 0) {
throw new CatalogDBException("Could not update owner. User not found.");
}
// Fetch current owner
Organization organization = get(clientSession, OrganizationManager.INCLUDE_ORGANIZATION_ADMINS).first();
if (owner.equals(organization.getOwner())) {
logger.warn("Organization owner is already '{}'.", owner);
} else {
document.getSet().put(QueryParams.OWNER.key(), owner);
}
}

if (StringUtils.isNotEmpty(parameters.getString(QueryParams.CREATION_DATE.key()))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,19 @@ public OpenCGAResult<Group> addUsersToGroup(long studyId, String groupId, List<S
return new OpenCGAResult<>(result);
}

void addUsersToAdminsAndMembersGroup(ClientSession clientSession, List<String> members) throws CatalogDBException {
if (CollectionUtils.isEmpty(members)) {
throw new CatalogDBException("List of 'members' is missing or empty.");
}

Document query = new Document(QueryParams.GROUP_ID.key(), ParamConstants.ADMINS_GROUP);
Document update = new Document("$addToSet", new Document("groups.$.userIds", new Document("$each", members)));
studyCollection.update(clientSession, query, update, new QueryOptions(MongoDBCollection.MULTI, true));

query = new Document(QueryParams.GROUP_ID.key(), ParamConstants.MEMBERS_GROUP);
studyCollection.update(clientSession, query, update, new QueryOptions(MongoDBCollection.MULTI, true));
}

@Override
public OpenCGAResult<Group> removeUsersFromGroup(long studyId, String groupId, List<String> members) throws CatalogDBException {
if (CollectionUtils.isEmpty(members)) {
Expand All @@ -504,6 +517,18 @@ public OpenCGAResult<Group> removeUsersFromGroup(long studyId, String groupId, L
return new OpenCGAResult<>(update);
}

OpenCGAResult<Group> removeUsersFromAdminsGroup(ClientSession clientSession, List<String> members) throws CatalogDBException {
if (CollectionUtils.isEmpty(members)) {
throw new CatalogDBException("Unable to remove members from group. List of members is empty.");
}

Document query = new Document()
.append(QueryParams.GROUP_ID.key(), ParamConstants.ADMINS_GROUP);
Bson pull = Updates.pullAll("groups.$.userIds", members);
DataResult update = studyCollection.update(clientSession, query, pull, new QueryOptions(MongoDBCollection.MULTI, true));
return new OpenCGAResult<>(update);
}

@Override
public OpenCGAResult<Group> removeUsersFromAllGroups(long studyId, List<String> users)
throws CatalogDBException, CatalogParameterException, CatalogAuthorizationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ public static CatalogAuthorizationException denyAny(String userId, String permis
+ " cannot " + permission + " any " + resource + ".");
}

public static CatalogAuthorizationException notOrganizationOwner() {
return notOrganizationOwner("perform this action");
}

public static CatalogAuthorizationException notOrganizationOwner(String action) {
return new CatalogAuthorizationException("Permission denied: Only the organization owner can " + action);
}

public static CatalogAuthorizationException notOrganizationOwnerOrAdmin() {
return notOrganizationOwnerOrAdmin("perform this action");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public OpenCGAResult<Organization> get(String organizationId, QueryOptions optio

OpenCGAResult<Organization> queryResult;
try {
authorizationManager.checkCanViewOrganization(organizationId, userId);
queryResult = getOrganizationDBAdaptor(organizationId).get(options);
} catch (CatalogException e) {
auditManager.auditInfo(organizationId, userId, Enums.Resource.ORGANIZATION, organizationId, "", "", "", auditParams,
Expand Down Expand Up @@ -235,6 +236,11 @@ public OpenCGAResult<Organization> update(String organizationId, OrganizationUpd
OpenCGAResult<Organization> result = OpenCGAResult.empty(Organization.class);
try {
ParamUtils.checkObj(updateParams, "OrganizationUpdateParams");
if (StringUtils.isNotEmpty(updateParams.getOwner()) || CollectionUtils.isNotEmpty(updateParams.getAdmins())) {
authorizationManager.checkIsAtLeastOrganizationOwner(organizationId, userId);
} else {
authorizationManager.checkIsAtLeastOrganizationOwnerOrAdmin(organizationId, userId);
}

OpenCGAResult<Organization> internalResult = get(organizationId, INCLUDE_ORGANIZATION_ADMINS, token);
if (internalResult.getNumResults() == 0) {
Expand Down
Loading
Loading