/*******************************************************************************
* Copyright (c) 2013 aegif.
*
* This file is part of NemakiWare.
*
* NemakiWare is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NemakiWare is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with NemakiWare.
* If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* linzhixing(https://github.com/linzhixing) - initial API and implementation
******************************************************************************/
package jp.aegif.nemaki.cmis.aspect.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jp.aegif.nemaki.businesslogic.ContentService;
import jp.aegif.nemaki.businesslogic.PrincipalService;
import jp.aegif.nemaki.cmis.aspect.PermissionService;
import jp.aegif.nemaki.cmis.aspect.type.TypeManager;
import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap;
import jp.aegif.nemaki.model.Ace;
import jp.aegif.nemaki.model.Acl;
import jp.aegif.nemaki.model.Content;
import jp.aegif.nemaki.model.Document;
import jp.aegif.nemaki.model.Folder;
import jp.aegif.nemaki.model.Relationship;
import jp.aegif.nemaki.model.User;
import jp.aegif.nemaki.model.VersionSeries;
import jp.aegif.nemaki.util.PropertyManager;
import jp.aegif.nemaki.util.constant.CmisPermission;
import jp.aegif.nemaki.util.constant.PropertyKey;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Permission Service implementation.
*
*/
public class PermissionServiceImpl implements PermissionService {
private static final Log log = LogFactory.getLog(PermissionServiceImpl.class);
private PrincipalService principalService;
private ContentService contentService;
private TypeManager typeManager;
private RepositoryInfoMap repositoryInfoMap;
private PropertyManager propertyManager;
private List<String> topLevelAllowableKeys;
private List<String> topLevelNotAllowableKeysWithFolder;
public void init(){
topLevelAllowableKeys = new ArrayList<String>();
Collections.addAll(topLevelAllowableKeys,
PermissionMapping.CAN_GET_ACL_OBJECT,
PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES,
PermissionMapping.CAN_GET_APPLIED_POLICIES_OBJECT,
PermissionMapping.CAN_GET_CHILDREN_FOLDER,
PermissionMapping.CAN_GET_DESCENDENTS_FOLDER,
PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT,
PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT,
PermissionMapping.CAN_GET_PARENTS_FOLDER,
PermissionMapping.CAN_GET_PROPERTIES_OBJECT,
PermissionMapping.CAN_VIEW_CONTENT_OBJECT);
topLevelNotAllowableKeysWithFolder = new ArrayList<String>();
Collections.addAll(topLevelNotAllowableKeysWithFolder,
PermissionMapping.CAN_ADD_TO_FOLDER_FOLDER,
PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT,
PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER,
PermissionMapping.CAN_CREATE_FOLDER_FOLDER,
PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER,
PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT);
}
// //////////////////////////////////////////////////////////////////////////
// Permission Check called from each CMIS method
// //////////////////////////////////////////////////////////////////////////
@Override
public boolean checkPermission(CallContext callContext, Action action, ObjectData objectData){
AllowableActions _actions = objectData.getAllowableActions();
if(_actions == null){
return false;
}else{
Set<Action> actions = _actions.getAllowableActions();
if(CollectionUtils.isEmpty(actions)){
return false;
}else{
return actions.contains(action);
}
}
}
/**
*
*/
//TODO Merge arguments(acl, content)
//FIXME Refactor duplicate isAllowableBaseType
@Override
public Boolean checkPermission(CallContext callContext, String repositoryId, String key,
Acl acl, String baseType, Content content) {
//All permission checks must go through baseType check
if(!isAllowableBaseType(key, baseType, content, repositoryId)) return false;
// Admin always pass a permission check
String userName = callContext.getUsername();
User u = principalService.getUserById(repositoryId, userName);
if (u != null && u.isAdmin()) {
return true;
}
//PWC doesn't accept any actions from a non-owner user
//TODO admin can manipulate PWC even when it is checked out ?
if(content.isDocument()){
Document document = (Document)content;
if(document.isPrivateWorkingCopy()){
VersionSeries vs = contentService.getVersionSeries(repositoryId, document);
if(!callContext.getUsername().equals(vs.getVersionSeriesCheckedOutBy())){
return false;
}
}
}
// Relation has no ACL stored in DB.
// Though some actions are defined in the specs,
// Some other direct actions is needed to be set here.
if(content.isRelationship()){
Relationship relationship = (Relationship)content;
boolean hasRelationshipPermission = checkRelationshipPermission(callContext, repositoryId, key, relationship);
return hasRelationshipPermission;
}
// Void Acl fails(but Admin can do an action)
if (acl == null){
return false;
}
// Even if a user has multiple ACEs, the permissions is pushed into
// Set<String> and remain unique.
// Get ACL for the current user
List<Ace> aces = acl.getAllAces();
Set<String> userPermissions = new HashSet<String>();
Set<String> groups = principalService.getGroupIdsContainingUser(repositoryId, userName);
for (Ace ace : aces) {
// Filter ace which has not permissions
if (ace.getPermissions() == null)
continue;
// Add user permissions
if (ace.getPrincipalId().equals(userName)) {
userPermissions.addAll(ace.getPermissions());
}
// Add inherited permissions which user inherits
if(CollectionUtils.isNotEmpty(groups) && groups.contains(ace.getPrincipalId())){
userPermissions.addAll(ace.getPermissions());
}
}
// Check mapping between the user and the content
boolean calcPermission = checkCalculatedPermissions(repositoryId, key, userPermissions);
return calcPermission;
}
/**
*
* @param repositoryId TODO
* @param key
* @param userPermissions
* @return
*/
private boolean checkCalculatedPermissions(String repositoryId, String key, Set<String> userPermissions) {
Map<String, PermissionMapping> table = repositoryInfoMap.get(repositoryId).getAclCapabilities().getPermissionMapping();
List<String> actionPermissions = table.get(key).getPermissions();
for (String up : userPermissions) {
if (actionPermissions.contains(up) ||
CmisPermission.ALL.equals(up)) {
//If any one of user permissions is contained, action is allowed.
return true;
}
}
return false;
}
/**
* TODO In the future, enable different configuration for Read/Update/Delete.
* @param callContext
* @param repositoryId TODO
* @param key
* @param relationship
* @return
*/
private Boolean checkRelationshipPermission(CallContext callContext, String repositoryId, String key, Relationship relationship){
Content source = contentService.getRelationship(repositoryId, relationship.getSourceId());
Content target = contentService.getRelationship(repositoryId, relationship.getTargetId());
if(source == null || target == null){
log.warn("[objectId=" + relationship.getId() + "]Source or target of this relationship is missing");
return false;
}
//Read action when a relationship is specified directly
if(PermissionMapping.CAN_GET_PROPERTIES_OBJECT.equals(key)){
boolean readSource =
checkPermission(callContext, repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, contentService.calculateAcl(repositoryId, source), source.getType(), source);
boolean readTarget =
checkPermission(callContext, repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, contentService.calculateAcl(repositoryId, target), target.getType(), target);
return readSource | readTarget;
}
//Update action
if(PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT.equals(key)){
boolean updateSource =
checkPermission(callContext, repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, contentService.calculateAcl(repositoryId, source), source.getType(), source);
boolean updateTarget =
checkPermission(callContext, repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, contentService.calculateAcl(repositoryId, target), target.getType(), target);
return updateSource | updateTarget;
}
//Delete action
if(PermissionMapping.CAN_DELETE_OBJECT.equals(key)){
boolean deleteSource =
checkPermission(callContext, repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, contentService.calculateAcl(repositoryId, source), source.getType(), source);
boolean deleteTarget =
checkPermission(callContext, repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, contentService.calculateAcl(repositoryId, target), target.getType(), target);
return deleteSource | deleteTarget;
}
return false;
}
private Boolean isAllowableBaseType(String key, String baseType, Content content, String repositoryId) {
// NavigationServices
if (PermissionMapping.CAN_GET_DESCENDENTS_FOLDER.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
if (PermissionMapping.CAN_GET_CHILDREN_FOLDER.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
if (PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT.equals(key))
if(contentService.isRoot(repositoryId, content)){
return false;
}else{
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
}
if (PermissionMapping.CAN_GET_PARENTS_FOLDER.equals(key))
if(contentService.isRoot(repositoryId, content)){
return false;
}else{
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType) || BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM.value().equals(baseType));
}
// Object Services
if (PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
if (PermissionMapping.CAN_CREATE_FOLDER_FOLDER.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
if (PermissionMapping.CAN_CREATE_POLICY_FOLDER.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
if (PermissionMapping.CAN_CREATE_RELATIONSHIP_SOURCE.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.equals(baseType));
if (PermissionMapping.CAN_CREATE_RELATIONSHIP_TARGET.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
if (PermissionMapping.CAN_GET_PROPERTIES_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
if (PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
if (PermissionMapping.CAN_MOVE_OBJECT.equals(key))
if(contentService.isRoot(repositoryId, content)){
return false;
}else{
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
}
if (PermissionMapping.CAN_MOVE_TARGET.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
if (PermissionMapping.CAN_MOVE_SOURCE.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
if (PermissionMapping.CAN_DELETE_OBJECT.equals(key))
if(contentService.isRoot(repositoryId, content)){
return false;
}else{
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
}
if (PermissionMapping.CAN_VIEW_CONTENT_OBJECT.equals(key))
return BaseTypeId.CMIS_DOCUMENT.value().equals(baseType);
if (PermissionMapping.CAN_SET_CONTENT_DOCUMENT.equals(key))
return BaseTypeId.CMIS_DOCUMENT.value().equals(baseType);
if (PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT.equals(key))
return BaseTypeId.CMIS_DOCUMENT.value().equals(baseType);
if (PermissionMapping.CAN_DELETE_TREE_FOLDER.equals(key))
return BaseTypeId.CMIS_FOLDER.value().equals(baseType);
// Filing Services
if (PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
if (PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
if (PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
if (PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType) || BaseTypeId.CMIS_POLICY
.value().equals(baseType));
// Versioning Services
if (PermissionMapping.CAN_CHECKOUT_DOCUMENT.equals(key))
return BaseTypeId.CMIS_DOCUMENT.value().equals(baseType);
if (PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT.equals(key))
return BaseTypeId.CMIS_DOCUMENT.value().equals(baseType);
if (PermissionMapping.CAN_CHECKIN_DOCUMENT.equals(key))
return BaseTypeId.CMIS_DOCUMENT.value().equals(baseType);
if (PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES.equals(key))
return BaseTypeId.CMIS_DOCUMENT.value().equals(baseType);
// Relationship Services
if (PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
// Policy Services
if (PermissionMapping.CAN_ADD_POLICY_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM
.value().equals(baseType));
if (PermissionMapping.CAN_ADD_POLICY_POLICY.equals(key))
return BaseTypeId.CMIS_POLICY.value().equals(baseType);
if (PermissionMapping.CAN_REMOVE_POLICY_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM.value()
.equals(baseType));
if (PermissionMapping.CAN_REMOVE_POLICY_POLICY.equals(key))
return BaseTypeId.CMIS_POLICY.value().equals(baseType);
if (PermissionMapping.CAN_GET_APPLIED_POLICIES_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM.value()
.equals(baseType));
// ACL Services
if (PermissionMapping.CAN_GET_ACL_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM.value()
.equals(baseType));
if (PermissionMapping.CAN_APPLY_ACL_OBJECT.equals(key))
return (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)
|| BaseTypeId.CMIS_FOLDER.value().equals(baseType)
|| BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)
|| BaseTypeId.CMIS_POLICY.value().equals(baseType) || BaseTypeId.CMIS_ITEM.value()
.equals(baseType));
return false;
}
/**
* Filtering check to a list of contents based on the permission
*/
@Override
public <T> List<T> getFiltered(CallContext callContext,
String repositoryId, List<T> contents) {
List<T> result = new ArrayList<T>();
// Validation
// TODO refine the logic
if (CollectionUtils.isEmpty(contents)){
return null;
}
// Filtering
for (T _content : contents) {
Content content = (Content) _content;
Acl acl = contentService.calculateAcl(repositoryId, content);
Boolean filtered = checkPermission(callContext,
repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, acl, content.getType(), content);
if (filtered) {
result.add(_content);
}
}
return result;
}
@Override
public boolean checkPermissionAtTopLevel(CallContext context, String repositoryId, String key, Content content){
boolean capability = propertyManager.readBoolean(PropertyKey.CAPABILITY_EXTENDED_PERMISSION_TOPLEVEL);
if(capability){
if(!topLevelAllowableKeys.contains(key)){
Folder folderChecked;
if(topLevelNotAllowableKeysWithFolder.contains(key)){
//canCreateDocument.Folder type
folderChecked = (Folder)content;
}else{
//canDelete.Object type
folderChecked = contentService.getFolder(repositoryId, content.getParentId());
}
String rootId = repositoryInfoMap.get(repositoryId).getRootFolderId();
//Check top level or not
if(folderChecked == null || rootId.equals(folderChecked.getId())){
User user = principalService.getUserById(repositoryId, context.getUsername());
if(!user.isAdmin()){
return false;
}
}
}
}
return true;
}
public void setNemakiPermissions(List<Map<String, ?>> nemakiPermissions) {
}
public void setPrincipalService(PrincipalService principalService) {
this.principalService = principalService;
}
public void setContentService(ContentService contentService) {
this.contentService = contentService;
}
public TypeManager getTypeManager() {
return typeManager;
}
public void setTypeManager(TypeManager typeManager) {
this.typeManager = typeManager;
}
public void setRepositoryInfoMap(RepositoryInfoMap repositoryInfoMap) {
this.repositoryInfoMap = repositoryInfoMap;
}
public void setPropertyManager(PropertyManager propertyManager) {
this.propertyManager = propertyManager;
}
}