/*
* Copyright 2015-2016 OpenCB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opencb.opencga.catalog.db.mongodb;
import com.mongodb.MongoClient;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.Updates;
import com.mongodb.client.result.UpdateResult;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.opencb.commons.datastore.core.QueryOptions;
import org.opencb.commons.datastore.core.QueryParam;
import org.opencb.commons.datastore.core.QueryResult;
import org.opencb.commons.datastore.mongodb.GenericDocumentComplexConverter;
import org.opencb.commons.datastore.mongodb.MongoDBCollection;
import org.opencb.opencga.catalog.auth.authorization.AclDBAdaptor;
import org.opencb.opencga.catalog.db.api.FileDBAdaptor;
import org.opencb.opencga.catalog.exceptions.CatalogDBException;
import org.opencb.opencga.catalog.models.acls.AbstractAcl;
import org.opencb.opencga.catalog.models.acls.permissions.AbstractAclEntry;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import static org.opencb.commons.datastore.core.QueryParam.Type.*;
import static org.opencb.opencga.catalog.db.mongodb.MongoDBAdaptor.PRIVATE_ID;
import static org.opencb.opencga.catalog.db.mongodb.MongoDBAdaptor.PRIVATE_STUDY_ID;
/**
* Created by pfurio on 29/07/16.
*/
public class AclMongoDBAdaptor<T extends AbstractAclEntry> implements AclDBAdaptor<T> {
private MongoDBCollection collection;
private GenericDocumentComplexConverter<? extends AbstractAcl> converter;
private Logger logger;
public AclMongoDBAdaptor(MongoDBCollection collection, GenericDocumentComplexConverter<? extends AbstractAcl> converter,
Logger logger) {
this.collection = collection;
this.converter = converter;
this.logger = logger;
}
enum QueryParams implements QueryParam {
ID("id", INTEGER_ARRAY, ""),
ACL("acl", TEXT_ARRAY, ""),
MEMBER("member", TEXT, ""),
PERMISSIONS("permissions", TEXT_ARRAY, ""),
ACL_MEMBER("acl.member", TEXT_ARRAY, ""),
ACL_PERMISSIONS("acl.permissions", TEXT_ARRAY, "");
private static Map<String, QueryParams> map = new HashMap<>();
static {
for (QueryParams param : QueryParams.values()) {
map.put(param.key(), param);
}
}
private final String key;
private Type type;
private String description;
QueryParams(String key, Type type, String description) {
this.key = key;
this.type = type;
this.description = description;
}
@Override
public String key() {
return key;
}
@Override
public Type type() {
return type;
}
@Override
public String description() {
return description;
}
public static Map<String, QueryParams> getMap() {
return map;
}
public static QueryParams getParam(String key) {
return map.get(key);
}
}
@Deprecated
@Override
public T createAcl(long resourceId, T acl) throws CatalogDBException {
// Push the new acl to the list of acls.
Document queryDocument = new Document(PRIVATE_ID, resourceId);
Document update = new Document("$push", new Document(QueryParams.ACL.key(),
MongoDBUtils.getMongoDBDocument(acl, "ACL")));
QueryResult<UpdateResult> updateResult = collection.update(queryDocument, update, null);
if (updateResult.first().getModifiedCount() == 0) {
throw new CatalogDBException("create Acl: An error occurred when trying to create acl for " + resourceId + " for "
+ acl.getMember());
}
logger.debug("Create Acl: {}", acl.toString());
return acl;
}
@Override
public void setAcl(Bson bsonQuery, List<T> aclEntryList) throws CatalogDBException {
// First we take out all possible acls that could be present for any of the members.
List<String> memberList = aclEntryList.stream().map(fileAclEntry -> fileAclEntry.getMember()).collect(Collectors.toList());
Document update = new Document("$pull", new Document(FileDBAdaptor.QueryParams.ACL.key(),
new Document(AclMongoDBAdaptor.QueryParams.MEMBER.key(), new Document("$in", memberList))));
logger.debug("Create Acl: Query {}, Pull {}",
bsonQuery.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()),
update.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()));
QueryResult<UpdateResult> pullUpdate = collection.update(bsonQuery, update, new QueryOptions("multi", true));
logger.debug("{} out of {} file acls removed", pullUpdate.first().getModifiedCount(), pullUpdate.first().getMatchedCount());
// Now we push all the new acls
List<Document> aclDocumentList = new ArrayList<>(aclEntryList.size());
for (T aclEntry : aclEntryList) {
aclDocumentList.add(MongoDBUtils.getMongoDBDocument(aclEntry, "ACL"));
}
update = new Document("$push", new Document(QueryParams.ACL.key(), new Document("$each", aclDocumentList)));
logger.debug("Create Acl: Query {}, Push {}",
bsonQuery.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()),
update.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()));
QueryResult<UpdateResult> pushUpdate = collection.update(bsonQuery, update, new QueryOptions("multi", true));
logger.debug("{} out of {} file acls created", pushUpdate.first().getModifiedCount(), pushUpdate.first().getMatchedCount());
if (pushUpdate.first().getModifiedCount() == 0) {
throw new CatalogDBException("Create Acl: An error occurred when trying to create acls");
}
}
@Override
public List<T> getAcl(long resourceId, List<String> members) {
List<Bson> aggregation = new ArrayList<>();
aggregation.add(Aggregates.match(Filters.eq(PRIVATE_ID, resourceId)));
aggregation.add(Aggregates.project(Projections.include(QueryParams.ID.key(), QueryParams.ACL.key())));
aggregation.add(Aggregates.unwind("$" + QueryParams.ACL.key()));
List<Bson> filters = new ArrayList<>();
if (members != null && members.size() > 0) {
filters.add(Filters.in(QueryParams.ACL_MEMBER.key(), members));
}
if (filters.size() > 0) {
Bson filter = filters.size() == 1 ? filters.get(0) : Filters.and(filters);
aggregation.add(Aggregates.match(filter));
}
for (Bson bson : aggregation) {
logger.debug("Get Acl: {}", bson.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()));
}
QueryResult<Document> aggregate = collection.aggregate(aggregation, null);
List<T> retList = new ArrayList<>();
if (aggregate.getNumResults() > 0) {
for (Document document : aggregate.getResult()) {
retList.add((T) converter.convertToDataModelType(document).getAcl().get(0));
}
}
return retList;
}
@Override
public void removeAcl(long resourceId, String member) throws CatalogDBException {
Document query = new Document()
.append(PRIVATE_ID, resourceId)
.append(QueryParams.ACL_MEMBER.key(), member);
Bson update = new Document().append("$pull", new Document("acl", new Document("member", member)));
QueryResult<UpdateResult> updateResult = collection.update(query, update, null);
if (updateResult.first().getModifiedCount() == 0) {
throw new CatalogDBException("remove ACL: An error occurred when trying to remove the ACL defined for " + member);
}
logger.debug("Remove Acl in {} for member {}", resourceId, member);
}
@Override
public void removeAclsFromStudy(long studyId, String member) throws CatalogDBException {
Document query = new Document()
.append(PRIVATE_STUDY_ID, studyId)
.append(QueryParams.ACL_MEMBER.key(), member);
Bson update = new Document("$pull", new Document("acl", new Document("member", member)));
QueryResult<UpdateResult> updateResult = collection.update(query, update, new QueryOptions(MongoDBCollection.MULTI, true));
// if (updateResult.first().getModifiedCount() == 0) {
// throw new CatalogDBException("remove ACL: An error occurred when trying to remove the ACLs defined for " + member);
// }
logger.debug("Remove all the Acls for member {} in study {}", member, studyId);
}
@Override
public T setAclsToMember(long resourceId, String member, List<String> permissions) throws CatalogDBException {
Document query = new Document()
.append(PRIVATE_ID, resourceId)
.append(QueryParams.ACL_MEMBER.key(), member);
Document update = new Document("$set", new Document("acl.$.permissions", permissions));
QueryResult<UpdateResult> queryResult = collection.update(query, update, null);
if (queryResult.first().getModifiedCount() != 1) {
throw new CatalogDBException("Unable to set the new permissions to " + member);
}
logger.debug("Set Acl for member {}: {}", member, StringUtils.join(permissions, ","));
return getAcl(resourceId, Arrays.asList(member)).get(0);
}
@Override
@Deprecated
public T addAclsToMember(long resourceId, String member, List<String> permissions) throws CatalogDBException {
Document query = new Document()
.append(PRIVATE_ID, resourceId)
.append(QueryParams.ACL_MEMBER.key(), member);
Document update = new Document("$addToSet", new Document("acl.$.permissions", new Document("$each", permissions)));
QueryResult<UpdateResult> queryResult = collection.update(query, update, null);
if (queryResult.first().getModifiedCount() != 1) {
throw new CatalogDBException("Unable to add new permissions to " + member + ". Maybe the member already had those"
+ " permissions?");
}
logger.debug("Add Acl for member {}: {}", member, StringUtils.join(permissions, ","));
return getAcl(resourceId, Arrays.asList(member)).get(0);
}
@Override
public void addAclsToMembers(List<Long> resourceIds, List<String> members, List<String> permissions) throws CatalogDBException {
for (String member : members) {
logger.debug("Adding ACLs for {}", member);
// Try to update and add the new permissions (if the member already had those permissions set)
Document queryDocument = new Document()
.append("$isolated", 1)
.append(PRIVATE_ID, new Document("$in", resourceIds))
.append(QueryParams.ACL_MEMBER.key(), member);
Document update = new Document("$addToSet", new Document("acl.$.permissions", new Document("$each", permissions)));
logger.debug("Add Acls (addToSet): Query {}, Push {}",
queryDocument.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()),
update.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()));
QueryResult<UpdateResult> pushUpdate = collection.update(queryDocument, update, new QueryOptions("multi", true));
logger.debug("{} out of {} acls added to {}", pushUpdate.first().getModifiedCount(), pushUpdate.first().getMatchedCount(),
member);
// Try to do the same but only for resources where the member was not given any permissions
queryDocument.put(QueryParams.ACL_MEMBER.key(), new Document("$ne", member));
// Create the ACL entry
Document aclEntry = new Document()
.append(QueryParams.MEMBER.key(), member)
.append(QueryParams.PERMISSIONS.key(), permissions);
update = new Document("$push", new Document(QueryParams.ACL.key(), aclEntry));
logger.debug("Add Acls (Push): Query {}, Push {}",
queryDocument.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()),
update.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()));
pushUpdate = collection.update(queryDocument, update, new QueryOptions("multi", true));
logger.debug("{} out of {} acls created for {}", pushUpdate.first().getModifiedCount(), pushUpdate.first().getMatchedCount(),
member);
}
}
@Override
public T removeAclsFromMember(long resourceId, String member, List<String> permissions) throws CatalogDBException {
Document query = new Document()
.append("$isolated", 1)
.append(PRIVATE_ID, resourceId)
.append(QueryParams.ACL_MEMBER.key(), member);
Bson pull = Updates.pullAll("acl.$.permissions", permissions);
QueryResult<UpdateResult> update = collection.update(query, pull, null);
if (update.first().getModifiedCount() != 1) {
throw new CatalogDBException("Unable to remove the permissions from " + member + ". Maybe it didn't have those permissions?");
}
logger.debug("Remove Acl for member {}: {}", member, StringUtils.join(permissions, ","));
return getAcl(resourceId, Arrays.asList(member)).get(0);
}
@Override
public void removeAclsFromMembers(List<Long> resourceIds, List<String> members, @Nullable List<String> permissions)
throws CatalogDBException {
if (permissions == null || permissions.size() == 0) {
// Remove the members from the acl table
Document queryDocument = new Document()
.append("$isolated", 1)
.append(PRIVATE_ID, new Document("$in", resourceIds));
Document update = new Document("$pull", new Document(QueryParams.ACL.key(),
new Document(QueryParams.MEMBER.key(), new Document("$in", members))));
QueryResult<UpdateResult> updateResult = collection.update(queryDocument, update, new QueryOptions("multi", true));
logger.debug("Remove Acl: {} out of {} removed for members {}", updateResult.first().getModifiedCount(),
updateResult.first().getMatchedCount(), members);
} else {
// Remove the selected permissions from the array of permissions of each member
for (String member : members) {
Document queryDocument = new Document()
.append("$isolated", 1)
.append(PRIVATE_ID, new Document("$in", resourceIds))
.append(QueryParams.ACL_MEMBER.key(), member);
Bson update = Updates.pullAll("acl.$.permissions", permissions);
logger.debug("Remove Acl: Query {}, Pull {}",
queryDocument.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()),
update.toBsonDocument(Document.class, MongoClient.getDefaultCodecRegistry()));
QueryResult<UpdateResult> pullUpdate = collection.update(queryDocument, update, new QueryOptions("multi", true));
logger.debug("Remove Acl: {} out of {} acls removed from {}", pullUpdate.first().getModifiedCount(),
pullUpdate.first().getMatchedCount(), members);
}
}
}
}