/* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Astroboa 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.engine.service.security.aspect; import java.util.ArrayList; import java.util.List; import javax.jcr.Node; import javax.jcr.Value; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.betaconceptframework.astroboa.api.model.CmsRepository; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.model.query.ContentAccessMode; import org.betaconceptframework.astroboa.api.security.CmsRole; import org.betaconceptframework.astroboa.api.security.exception.CmsUnauthorizedAccessException; import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder; import org.betaconceptframework.astroboa.context.SecurityContext; import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem; import org.betaconceptframework.astroboa.security.CmsRoleAffiliationFactory; import org.springframework.transaction.annotation.Transactional; /** * This aspect intercepts all content service calls responsible to * save content objects. * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ @Aspect @Transactional(readOnly = false, rollbackFor = CmsException.class) public class SecureContentObjectDeleteAspect extends AbstractSecureContentObjectAspect{ @Pointcut("execution(public * org.betaconceptframework.astroboa.engine.service.jcr.ContentServiceImpl.deleteContentObject(..))") private void deleteContentObject(){} @Around("deleteContentObject() && args(contentObjectId)") public Object checkDeleteContentObject(ProceedingJoinPoint proceedingJoinPoint, String contentObjectId) { grantOrDenyContentObjectDelete(contentObjectId); try{ return proceedingJoinPoint.proceed(new Object[]{contentObjectId}); } catch(CmsException e) { throw e; } catch (Throwable e) { throw new CmsException(e); } } public void grantOrDenyContentObjectDelete( String contentObjectId) { checkIfUserIsAuthorizedToDeleteContentObject(contentObjectId); miscallenuousRestrictions(contentObjectId); } /* * Specific restrictions * * */ private void miscallenuousRestrictions(String objectIdOrSystemName) { try{ Node contentObjectNode = getContentObjectNodeByIdOrSystemName(objectIdOrSystemName); if (contentObjectNode==null) { return; } if (! contentObjectNode.hasProperty(CmsBuiltInItem.ContentObjectTypeName.getJcrName())) { //THIS SHOULD NEVER HAPPEN logger.error("Found content object node "+ contentObjectNode.getPath() + " without property "+ CmsBuiltInItem.ContentObjectTypeName.getJcrName()+ " This means that a content object was saved without content type..."); throw new CmsException("System error"); } String contentType = contentObjectNode.getProperty(CmsBuiltInItem.ContentObjectTypeName.getJcrName()).getString(); //Logged in user cannot delete Person object representing it self !!! if (StringUtils.equals("personObject",contentType)) { SecurityContext activeSecurityContext = retrieveSecurityContext(); String identity = activeSecurityContext.getIdentity(); String username = null; if (contentObjectNode.hasProperty("personAuthentication/username")) { username = contentObjectNode.getProperty("personAuthentication/username").getString(); } if (StringUtils.equals(identity, username)) { CmsRepository activeRepository = AstroboaClientContextHolder.getActiveCmsRepository(); if (activeRepository != null && ( activeRepository.getIdentityStoreRepositoryId() == null || StringUtils.equals(activeRepository.getIdentityStoreRepositoryId(), activeRepository.getId()) ) ) { throw new CmsUnauthorizedAccessException("User "+activeSecurityContext.getIdentity()+" is not authorized to delete person content object " + " which represent her self"); } } } }catch(Exception e) { logger.error("",e); throw new CmsException(e); } } /* * In order to grant access to user to delete a content object the following rules must apply * * * * 1. ContentObject Id or SystemName is not null * 2. User has ROLE_ADMIN or is SYSTEM user (It is supposed that user SYSTEM has all roles) * 3. User is the owner of the object * 4. User is not the owner of the object but content object has accessibility.canBeDeletedBy * whose value is REPOSITORY, or contains at least one of the roles user possesses, or explicitly contains user's id */ private void checkIfUserIsAuthorizedToDeleteContentObject(String contentObjectIdOrSystemName) { SecurityContext activeSecurityContext = retrieveSecurityContext(); String userId = activeSecurityContext.getIdentity(); //1. ContentObjectId is not null checkObjectIdOrNameIsNotNull(contentObjectIdOrSystemName, userId); //2. User has ROLE_ADMIN final String roleAdminForActiveRepository = CmsRoleAffiliationFactory.INSTANCE.getCmsRoleAffiliationForActiveRepository(CmsRole.ROLE_ADMIN); if (userHasRole(activeSecurityContext, roleAdminForActiveRepository)) { logger.debug("User {} is authorized to delete contentObject with id/name '{}' because she has role {}", new Object[]{userId, contentObjectIdOrSystemName, roleAdminForActiveRepository}); return; } try{ Node contentObjectNode = getContentObjectNodeByIdOrSystemName(contentObjectIdOrSystemName); if (contentObjectNode==null) { logger.debug("Jcr node corresponding to contentObject with id/name '{}' was not found. Deletion cannot procceed", contentObjectIdOrSystemName); throw new CmsException("ContentObject with identifier/name '"+contentObjectIdOrSystemName+"' could not be deleted because no such content object exists"); } /* * 3. User is the owner of the object */ String ownerIdFoundInRepository = retrieveOwnerFromContentObjectNode(contentObjectNode); //Retrieve repository user id from Subject String repositoryUserIdOfLoggedInUser = retrieveRepositoryUserIdForLoggedInUser(activeSecurityContext, userId, contentObjectNode); if ( StringUtils.equals(ownerIdFoundInRepository, repositoryUserIdOfLoggedInUser)) { logger.debug("User {} is authorized to delete contentObject with id/name '{}' because she owns contentObject", new Object[]{userId, contentObjectIdOrSystemName}); return; } /* * 4. User is not the owner of the object but content object has accessibility.canBeUpdated * whose value is REPOSITORY, or contains at least one of the roles user possesses, or explicitly contains user's id */ //Get values for property accessibility.canBeDeletedBy boolean isUserAuthorized = false; if (!contentObjectNode.hasProperty("accessibility/canBeDeletedBy")) { logger.debug("User {} is not authorized to delete contentObject with id/name '{}' because she does not own contentObject " + " and property 'accessibility.canBeDeletedBy' does not have any values at all", new Object[]{userId, contentObjectIdOrSystemName}); isUserAuthorized = false; } else { Value[] canBeDeletedByAuthorizationList = contentObjectNode.getProperty("accessibility/canBeDeletedBy").getValues(); for (Value authorizedCanBeDeletedByValue : canBeDeletedByAuthorizationList) { //If value REPOSITORY is found then user is authorized final String userOrRoleAuthorizedToDelete = authorizedCanBeDeletedByValue.getString(); if (ContentAccessMode.ALL.toString().equals(userOrRoleAuthorizedToDelete)){ isUserAuthorized=true; logger.debug("User {} is authorized to delete contentObject with id/name '{}' because she does not own contentObject " + " but property 'accessibility.canBeDeletedBy' contains value {}", new Object[]{userId, contentObjectIdOrSystemName, ContentAccessMode.ALL}); break; } else { //Check if authorizedCanBeDeletedBy value corresponds to user id if (StringUtils.equals(userOrRoleAuthorizedToDelete, userId)){ //User Id has been saved as is to canBeDeletedBy property logger.debug("User {} is authorized to delete contentObject with id/name '{}' because she does not own contentObject " + " but property 'accessibility.canBeDeletedBy' contains value {} which is user's Id {}", new Object[]{userId, contentObjectIdOrSystemName, userOrRoleAuthorizedToDelete, userId}); isUserAuthorized= true; break; } else if (userHasRole(activeSecurityContext, userOrRoleAuthorizedToDelete)){ //User Role has been saved as is to canBeDeletedBy property logger.debug("User {} is authorized to delete contentObject with id/name '{}' because she does not own contentObject " + " but property 'accessibility.canBeDeletedBy' contains role {} which is assigned to user as well. " , new Object[]{userId, contentObjectIdOrSystemName, userOrRoleAuthorizedToDelete}); isUserAuthorized= true; break; } } } if (! isUserAuthorized) { List<String> canBeDeletedList = new ArrayList<String>(); for (Value authorizedCanBeDeletedByValue : canBeDeletedByAuthorizationList){ canBeDeletedList.add(authorizedCanBeDeletedByValue.getString()); } logger.warn("User {} is not authorized to delete contentObject with id/name '{}' because she does not own contentObject " + " and property 'accessibility.canBeDeletedBy' has values {}. \nThese values do not include value {} " + " or user's id {} or any of user's assigned roles {}", new Object[]{userId, contentObjectIdOrSystemName, canBeDeletedList, ContentAccessMode.ALL.toString(), userId, activeSecurityContext.getAllRoles()}); throw new CmsUnauthorizedAccessException("User "+ userId + " is not authorized to delete content object "+ contentObjectNode.getPath()); } } }catch(Exception e) { throw new CmsException(e); } } private void checkObjectIdOrNameIsNotNull(String objectIdOrName, String userId) { if (StringUtils.isBlank(objectIdOrName)){ throw new CmsException("No object id or name provided. User "+ userId); } } }