package com.constellio.app.api.cmis.requests.acl; import static com.constellio.app.api.cmis.builders.object.AclBuilder.CMIS_DELETE; import static com.constellio.app.api.cmis.builders.object.AclBuilder.CMIS_READ; import static com.constellio.app.api.cmis.builders.object.AclBuilder.CMIS_WRITE; import static com.constellio.data.utils.LangUtils.hasSameElementsNoMatterTheOrder; import static com.constellio.data.utils.LangUtils.isEqual; import static com.constellio.model.entities.security.global.AuthorizationAddRequest.authorizationInCollection; import static com.constellio.model.entities.security.global.AuthorizationDeleteRequest.authorizationDeleteRequest; import static com.constellio.model.entities.security.global.AuthorizationModificationRequest.modifyAuthorizationOnRecord; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.where; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.chemistry.opencmis.commons.data.Ace; import org.apache.chemistry.opencmis.commons.data.Acl; import org.apache.chemistry.opencmis.commons.data.ExtensionsData; import org.apache.chemistry.opencmis.commons.enums.AclPropagation; import org.apache.chemistry.opencmis.commons.enums.Action; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.app.api.cmis.binding.collection.ConstellioCollectionRepository; import com.constellio.app.api.cmis.requests.CmisCollectionRequest; import com.constellio.app.services.factories.AppLayerFactory; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.schemas.Schemas; import com.constellio.model.entities.security.Authorization; import com.constellio.model.entities.security.global.AuthorizationDetails; import com.constellio.model.entities.security.Role; import com.constellio.model.entities.security.global.AuthorizationAddRequest; import com.constellio.model.services.records.RecordServices; import com.constellio.model.services.records.RecordServicesException; import com.constellio.model.services.records.SchemasRecordsServices; import com.constellio.model.services.security.AuthorizationsServices; import com.constellio.model.services.users.UserServices; import com.constellio.model.services.users.UserServicesRuntimeException.UserServicesRuntimeException_NoSuchUser; /** * Limitations of this service : * - The propagation mode OBJECTONLY is not supported, only PROPAGATE * - Cannot remove an inherited authorisation * - Apache chemistry automatically remove duplicate an ACE if there is another one with better permissions * - Delete access and roles (ex. user/manager) are not handled */ public class ApplyAclRequest extends CmisCollectionRequest<Acl> { private static final String REMOVE_INHERITANCE_COMMAND = "constellio:removeInheritance"; private static final Logger LOGGER = LoggerFactory.getLogger(ApplyAclRequest.class); private final String repositoryId; private final String objectId; private final Acl aces; private final Acl addAces; private final Acl removeAces; private final AclPropagation aclPropagation; private final ExtensionsData extension; private final AuthorizationsServices authorizationsServices; private final RecordServices recordServices; private final String collection; public ApplyAclRequest(ConstellioCollectionRepository repository, AppLayerFactory appLayerFactory, CallContext context, String repositoryId, String objectId, Acl addAces, Acl removeAces, AclPropagation aclPropagation, ExtensionsData extension) { super(context, repository, appLayerFactory); this.repositoryId = repositoryId; this.objectId = objectId; this.aces = null; this.addAces = addAces; this.removeAces = removeAces; this.aclPropagation = aclPropagation; this.extension = extension; this.authorizationsServices = modelLayerFactory.newAuthorizationsServices(); this.recordServices = modelLayerFactory.newRecordServices(); this.collection = repository.getCollection(); } public ApplyAclRequest(ConstellioCollectionRepository repository, AppLayerFactory appLayerFactory, CallContext context, String repositoryId, String objectId, Acl aces, AclPropagation aclPropagation) { super(context, repository, appLayerFactory); this.repositoryId = repositoryId; this.objectId = objectId; this.addAces = null; this.removeAces = null; this.aces = aces; this.aclPropagation = aclPropagation; this.extension = null; this.authorizationsServices = modelLayerFactory.newAuthorizationsServices(); this.recordServices = modelLayerFactory.newRecordServices(); this.collection = repository.getCollection(); } /** * CMIS getACL. */ @Override public Acl process() { validateAces(aces); validateAces(addAces); validateAces(removeAces); Record record = recordServices.getDocumentById(objectId); ensureUserHasAllowableActionsOnRecord(record, Action.CAN_APPLY_ACL); if (hasCommandToRemoveAllInheritedAuthorizations()) { authorizationsServices.detach(record); } List<Ace> currentAces = new GetAclRequest(repository, appLayerFactory, callContext, objectId).process().getAces(); List<Ace> acesToAdd = getAcesToAdd(currentAces); List<Ace> acesToRemove = getAcesToRemove(currentAces); createNewAuthorizations(acesToAdd); removeAuthorizations(acesToRemove); return new GetAclRequest(repository, appLayerFactory, callContext, objectId).process(); } private void validateAces(Acl acl) { if (acl != null) { List<Ace> aces = acl.getAces(); for (Ace ace : aces) { if (!REMOVE_INHERITANCE_COMMAND.equals(ace.getPrincipalId())) { if (StringUtils.isBlank(ace.getPrincipalId())) { throw new RuntimeException("An ace has no specified principal"); } if (ace.getPermissions().isEmpty()) { throw new RuntimeException("An ace has no permission"); } for (String permission : ace.getPermissions()) { if (!CMIS_READ.equals(permission) && !CMIS_WRITE.equals(permission) && !CMIS_DELETE.equals(permission)) { throw new RuntimeException("An ace has unsupported permission '" + permission + "', only cmis:read/cmis:write/cmis:delete are allowed"); } } try { getPrincipalRecord(ace.getPrincipalId()); } catch (Exception e) { throw new RuntimeException( "An ace has invalid principal : No such user with username or" + " group with code : '" + ace.getPrincipalId() + "'"); } } } } } private boolean hasCommandToRemoveAllInheritedAuthorizations() { for (Ace ace : aces.getAces()) { if (REMOVE_INHERITANCE_COMMAND.equals(ace.getPrincipalId())) { return true; } } return false; } private void removeAuthorizations(List<Ace> acesToRemove) { Set<String> authorizationsPotentiallyEmpty = new HashSet<>(); for (Ace ace : acesToRemove) { List<String> permissions = toConstellioPermissions(ace.getPermissions()); Record principal = getPrincipalRecord(ace.getPrincipalId()); List<String> authorizationsIds = new ArrayList<>(principal.<String>getList(Schemas.AUTHORIZATIONS)); for (AuthorizationDetails authDetails : getObjectAuthorizationsWithPermission(objectId, permissions)) { Authorization authorization = authorizationsServices.getAuthorization(collection, authDetails.getId()); if (authorization.getGrantedToPrincipals().contains(principal.getId())) { if (authorization.getGrantedToPrincipals().size() == 1) { authorizationsServices.execute(authorizationDeleteRequest(authDetails).setExecutedBy(user)); } else { List<String> principals = new ArrayList<>(authorization.getGrantedToPrincipals()); principals.remove(principal.getId()); authorizationsServices.execute(modifyAuthorizationOnRecord(authorization, objectId) .withNewPrincipalIds(principals).setExecutedBy(user)); } } //authorizationsIds.remove(authDetails.getId()); //authorizationsPotentiallyEmpty.add(authDetails.getId()); } // try { // recordServices.updateAsync(principal.set(Schemas.AUTHORIZATIONS, authorizationsIds)); // } catch (RecordServicesException e) { // throw new RuntimeException(e); // } } // for (String auth : authorizationsPotentiallyEmpty) { // Authorization anAuthorization = authorizationsServices.getAuthorization(collection, auth); // if (anAuthorization.getGrantedToPrincipals().isEmpty()) { // authorizationsServices.execute(authorizationDeleteRequest(anAuthorization).setExecutedBy(user)); // } // } } private void createNewAuthorizations(List<Ace> acesToAdd) { for (Ace ace : acesToAdd) { if (!REMOVE_INHERITANCE_COMMAND.equals(ace.getPrincipalId())) { List<String> permissions = toConstellioPermissions(ace.getPermissions()); AuthorizationDetails authorizationDetails = getObjectAuthorizationWithPermission(objectId, permissions); Record principal = getPrincipalRecord(ace.getPrincipalId()); if (authorizationDetails == null) { AuthorizationAddRequest auth = authorizationInCollection(collection) .forPrincipalsIds(principal.getId()).on(objectId).giving(permissions); authorizationsServices.add(auth, user); } else { Authorization authorization = authorizationsServices .getAuthorization(collection, authorizationDetails.getId()); List<String> principals = new ArrayList<>(authorization.getGrantedToPrincipals()); principals.add(principal.getId()); authorizationsServices.execute(modifyAuthorizationOnRecord(authorization, objectId) .withNewPrincipalIds(principals)); // List<String> authorizations = new ArrayList<>(principal.<String>getList(Schemas.AUTHORIZATIONS)); // authorizations.add(authorizationDetails.getId()); // principal.set(Schemas.AUTHORIZATIONS, authorizations); // try { // recordServices.updateAsync(principal); // } catch (RecordServicesException e) { // throw new RuntimeException(e); // } } } } } AuthorizationDetails getObjectAuthorizationWithPermission(String objectId, List<String> permissions) { List<AuthorizationDetails> detailses = getObjectAuthorizationsWithPermission(objectId, permissions); return detailses.isEmpty() ? null : detailses.get(0); } List<AuthorizationDetails> getObjectAuthorizationsWithPermission(String objectId, List<String> permissions) { Record record = recordServices.getDocumentById(objectId); //List<String> authorizations = record.get(Schemas.AUTHORIZATIONS); List<AuthorizationDetails> detailses = new ArrayList<>(); // for (String authorization : authorizations) { // AuthorizationDetails details = modelLayerFactory.getAuthorizationDetailsManager().get(collection, authorization); // // if (details != null && hasSameElementsNoMatterTheOrder(details.getRoles(), permissions)) { // detailses.add(details); // } // } SchemasRecordsServices schemas = new SchemasRecordsServices(collection, modelLayerFactory); for (AuthorizationDetails authDetails : schemas .searchSolrAuthorizationDetailss(where(schemas.authorizationDetails.target()).isEqualTo(objectId))) { if (authDetails != null && hasSameElementsNoMatterTheOrder(authDetails.getRoles(), permissions)) { detailses.add(authDetails); } } return detailses; } private List<Ace> getAcesToAdd(List<Ace> currentAces) { List<Ace> acesToAdd = new ArrayList<>(); if (aces != null) { for (Ace ace : aces.getAces()) { boolean found = false; for (Ace newAce : currentAces) { if (areEquals(ace, newAce)) { found = true; break; } } if (!found) { acesToAdd.add(ace); } } } else { if (addAces != null) { acesToAdd.addAll(addAces.getAces()); } } return acesToAdd; } private List<Ace> getAcesToRemove(List<Ace> currentAces) { List<Ace> acesToRemove = new ArrayList<>(); if (aces != null) { for (Ace ace : currentAces) { if (ace.isDirect()) { boolean found = false; for (Ace newAce : aces.getAces()) { if (areEquals(ace, newAce)) { found = true; break; } } if (!found) { acesToRemove.add(ace); } } } } else { if (removeAces != null) { acesToRemove.addAll(removeAces.getAces()); } } return acesToRemove; } private boolean areEquals(Ace ace1, Ace ace2) { return isEqual(ace1.getPrincipalId(), ace2.getPrincipalId()) && hasSameElementsNoMatterTheOrder(ace1.getPermissions(), ace2.getPermissions()); } private List<String> toConstellioPermissions(List<String> permissions) { List<String> constellioPermissions = new ArrayList<>(); if (permissions.contains(CMIS_READ)) { constellioPermissions.add(Role.READ); } if (permissions.contains(CMIS_WRITE)) { constellioPermissions.add(Role.WRITE); } if (permissions.contains(CMIS_DELETE)) { constellioPermissions.add(Role.DELETE); } return constellioPermissions; } private Record getPrincipalRecord(String principalId) { UserServices userServices = modelLayerFactory.newUserServices(); try { return userServices.getUserInCollection(principalId, collection).getWrappedRecord(); } catch (UserServicesRuntimeException_NoSuchUser e) { return userServices.getGroupInCollection(principalId, collection).getWrappedRecord(); } } @Override protected Logger getLogger() { return LOGGER; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } }