/*
* 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 javax.security.auth.Subject;
import org.apache.commons.collections.CollectionUtils;
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.exception.CmsException;
import org.betaconceptframework.astroboa.api.model.io.FetchLevel;
import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType;
import org.betaconceptframework.astroboa.api.model.query.CacheRegion;
import org.betaconceptframework.astroboa.api.model.query.Condition;
import org.betaconceptframework.astroboa.api.model.query.ContentAccessMode;
import org.betaconceptframework.astroboa.api.model.query.criteria.ContentObjectCriteria;
import org.betaconceptframework.astroboa.api.model.query.criteria.Criterion;
import org.betaconceptframework.astroboa.api.security.CmsRole;
import org.betaconceptframework.astroboa.api.security.RepositoryUserIdPrincipal;
import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder;
import org.betaconceptframework.astroboa.context.SecurityContext;
import org.betaconceptframework.astroboa.engine.service.jcr.ContentServiceImpl;
import org.betaconceptframework.astroboa.engine.service.security.exception.NonAuthenticatedOperationException;
import org.betaconceptframework.astroboa.model.factory.CmsCriteriaFactory;
import org.betaconceptframework.astroboa.model.factory.CriterionFactory;
import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem;
import org.betaconceptframework.astroboa.model.impl.query.CmsOutcomeImpl;
import org.betaconceptframework.astroboa.security.CmsRoleAffiliationFactory;
import org.betaconceptframework.astroboa.util.CmsConstants;
import org.betaconceptframework.astroboa.util.CmsConstants.ContentObjectStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An aspect which intercepts content access calls and secures them.
* It first checks whether a user has been already authenticated for access to the repository.
* If no authenticated user is found the it throws an authentication exception.
* If an authenticated user is found, it retrieves user information (user id, groups, roles) and
* creates security criteria which are added to the search criteria in order to perform a search that
* returns only the objects that the user is authorized to access
* If a specific object is accessed through its id it inspect the security configuration of the object and if it
* is not readable by the user it throws an authorization exception otherwise it allows the method to return the object
*
* @author Gregory Chomatas (gchomatas@betaconcept.com)
* @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
*
*/
@Aspect
public class SecureContentServiceAspect{
private final static Logger logger = LoggerFactory.getLogger(SecureContentServiceAspect.class);
/**
* This Pointcut is triggered when the getContentObject() method is used
*
*/
@Pointcut("execution(public * org.betaconceptframework.astroboa.engine.service.jcr.ContentServiceImpl.getContentObject(..))")
private void getContentObject(){}
/**
* This Pointcut is triggered when the getContentObjectByVersionName() method is used
*
*/
@Pointcut("execution(public * org.betaconceptframework.astroboa.engine.service.jcr.ContentServiceImpl.getContentObjectByVersionName(..))")
private void getContentObjectByVersionName(){}
/**
* This Pointcut is triggered when the searchContentObject() method is used
*
*/
@Pointcut("execution(public * org.betaconceptframework.astroboa.engine.service.jcr.ContentServiceImpl.searchContentObjects(..))")
private void searchContentObjects(){}
@Pointcut("execution(public * org.betaconceptframework.astroboa.engine.service.jcr.ContentServiceImpl.copyContentObject(..))")
private void copyContentObject(){}
@Around("getContentObject() && args(contentObjectId, output, fetchLevel, cacheRegion, propertyPathsToInclude, serializeBinaryContent)")
public <T> T checkGetContentObject(ProceedingJoinPoint proceedingJoinPoint, String contentObjectId, ResourceRepresentationType<T> output, FetchLevel fetchLevel, CacheRegion cacheRegion, List<String> propertyPathsToInclude,boolean serializeBinaryContent){
return (T) grantOrDenyAccessToContentObject(proceedingJoinPoint, contentObjectId, new Object[]{contentObjectId, output, fetchLevel, cacheRegion, propertyPathsToInclude, serializeBinaryContent}, output);
}
@Around("getContentObjectByVersionName() && args(contentObjectId, versionName, locale, cacheRegion)")
public Object checkGetContentObjectByVersionName(ProceedingJoinPoint proceedingJoinPoint, String contentObjectId, String versionName, String locale,CacheRegion cacheRegion){
return grantOrDenyAccessToContentObject(proceedingJoinPoint, contentObjectId, new Object[]{contentObjectId, versionName, locale, cacheRegion}, ResourceRepresentationType.CONTENT_OBJECT_INSTANCE);
}
@Around("copyContentObject() && args(contentObjectId)")
public Object checkCopyContentObject(ProceedingJoinPoint proceedingJoinPoint, String contentObjectId)
{
return grantOrDenyAccessToContentObject(proceedingJoinPoint, contentObjectId, new Object[]{contentObjectId}, ResourceRepresentationType.CONTENT_OBJECT_INSTANCE);
}
private Object grantOrDenyAccessToContentObject(ProceedingJoinPoint proceedingJoinPoint, String contentObjectIdOrSystemName, Object[] methodParameters, ResourceRepresentationType contentObjectOutput)
{
if ( contentObjectIdIsNotNull(contentObjectIdOrSystemName))
{
SecurityContext activeSecurityContext = AbstractSecureContentObjectAspect.retrieveSecurityContext();
//Retrieve jcr node which corresponds to requested content object
Node contentObjectNode = null;
try {
if (! (proceedingJoinPoint.getTarget() instanceof ContentServiceImpl)){
return generateEmptyOutcome(contentObjectOutput);
}
contentObjectNode = ((ContentServiceImpl)proceedingJoinPoint.getTarget()).getContentObjectNodeByIdOrSystemName(contentObjectIdOrSystemName);
//contentObject = (ContentObject) proceedingJoinPoint.proceed(methodParameters);
if (contentObjectNode == null){
return generateEmptyOutcome(contentObjectOutput);
}
String userId = activeSecurityContext.getIdentity();
// if the authenticated user has not been granted the role: ROLE_CMS_INTERNAL_VIEWER
// then we allow her to read only published content objects i.e. those that their status is equal to "published" or "publishedAndArchived".
// As of today a published content object overrules any "read" security option to prevent complexities in security rule handling
// and remove the extra effort required for the publisher of content objects
// It is not very convenient to require to change the "read" security option of published content objects to "ALL" to allow to be read by REPOSITORY
// and then when publication status ends revert back to previous security settings.
// Additionally "ALL" should be interpreted as "REPOSITORY PHYSICAL PERSONS or REPOSITORY USERS WHICH ARE EXPLICITLY GRANTED THE PERMISSION TO VIEW THE REPOSITORY,
// i.e. those in role ROLE_CMS_INTERNAL_VIEWER".
// Anonymous is not an actual user registered in the identity store. It is a convention introduced in order to cope with the security rule that we always need some user
// in order to permit access to content. So for any user that tries to see the repository without logging in, the front-end system, e.g. the web application, should
// silently perform a virtual login as the anonymous user.
//
// Through the above convention it becomes really easy to publish and un-publish content objects.
// The idea is that if someone publishes a content object then she implicitly removes any read restrictions.
// All other restrictions apply and furthermore read restrictions are still there and remain valid when status is not set to "published" any more.
// We may revisit this convention if the use of the repository reveals another way of interpreting anonymous requests and published objects
//
// Be aware that since the anonymous is a virtual user it is not granted any roles. So allowing users that are not granted the role:ROLE_CMS_INTERNAL_VIEWER
// to view only published content objects is sufficient and we do not actually need to check whether the user is the anonymous.
// However we explicitly check if the user identity is the anonymous in order to prevent cases where some administrator by mistake or on purpose
// registers the anonymous as a real user and assigns it the role ROLE_CMS_INTERNAL_VIEWER.
// This would result in letting all not logged in Internet users to view internal unpublished content. So we introduce the extra rule that
// anonymous is only viewing published objects despite any roles that may have been assigned to it.
//if (StringUtils.equals(userId, IdentityPrincipal.ANONYMOUS) ||
if (! AbstractSecureContentObjectAspect.userHasRole(activeSecurityContext,CmsRoleAffiliationFactory.INSTANCE.getCmsRoleAffiliationForActiveRepository(CmsRole.ROLE_CMS_INTERNAL_VIEWER))){
//Any user that is NOT GRANTED the role:ROLE_CMS_INTERNAL_VIEWER can access ONLY PUBLISHED or PublishedAndArchived content objects
//StringProperty profileContentObjectStatusProperty = (StringProperty)contentObject.getCmsProperty("profile.contentObjectStatus");
//if (profileContentObjectStatusProperty == null || profileContentObjectStatusProperty.hasNoValues()){
if (! contentObjectNode.hasProperty("profile/contentObjectStatus")){
logger.debug("User {} has not been granted access to content object {} because she has not been granted role ROLE_CMS_INTERNAL_VIEWER and " +
" content object status is either null or has no values", userId, contentObjectIdOrSystemName);
return generateEmptyOutcome(contentObjectOutput);
}
//String profileContentObjectStatus = profileContentObjectStatusProperty.getSimpleTypeValue();
String profileContentObjectStatus = contentObjectNode.getProperty("profile/contentObjectStatus").getString();
if (StringUtils.equals(ContentObjectStatus.published.toString(), profileContentObjectStatus) ||
StringUtils.equals(ContentObjectStatus.publishedAndArchived.toString(), profileContentObjectStatus)){
logger.debug("User {} has been granted access to content object {} because she has not been granted role ROLE_CMS_INTERNAL_VIEWER but " +
" content object status is {}", new Object[]{userId, contentObjectIdOrSystemName, profileContentObjectStatus});
//
if (! CmsConstants.UUIDPattern.matcher(contentObjectIdOrSystemName).matches() && methodParameters != null && methodParameters.length > 1 &&
contentObjectNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){
//User has provided object system name. replace it with object identifier
methodParameters[0] = contentObjectNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString();
}
return proceedingJoinPoint.proceed(methodParameters);
}
logger.debug("User {} has not been granted access to content object {} because she has not been granted role ROLE_CMS_INTERNAL_VIEWER and " +
" content object status '{}' is not published or published and archived", new Object[]{userId, contentObjectIdOrSystemName, profileContentObjectStatus});
return generateEmptyOutcome(contentObjectOutput);
}
else if (! AbstractSecureContentObjectAspect.userHasRole(activeSecurityContext, CmsRoleAffiliationFactory.INSTANCE.getCmsRoleAffiliationForActiveRepository(CmsRole.ROLE_ADMIN))){ // for USER with ROLE_ADMIN we do not impose any security constraint
// we will generate criteria to generate the following security restriction
// (@betaconcept:OwnerCmsIdentifier = UUIDOfUserExecutingTheQuery OR
// betaconcept:CanBeReadBy = 'REPOSITORY' OR (betaconcept:CanBeReadBy != 'NONE'
// AND (betaconcept:CanBeReadBy = "USR_" + userId OR
// betaconcept:CanBeReadBy = "GRP_" + userGroupId1 OR
// betaconcept:CanBeReadBy = "GRP_" + userGroupId2 ....)))
//User has role ROLE_CMS_INTERNAL_VIEWER
//Check if user owns this content object
//RepositoryUser owner = contentObject.getOwner();
Subject subject = activeSecurityContext.getSubject();
if (subject != null && CollectionUtils.isNotEmpty(subject.getPrincipals(RepositoryUserIdPrincipal.class))
&& contentObjectNode.hasProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName())){
RepositoryUserIdPrincipal ownerIdPrincipal = subject.getPrincipals(RepositoryUserIdPrincipal.class).iterator().next();
String ownerId = contentObjectNode.getProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName()).getString();
if (StringUtils.equals(ownerId, ownerIdPrincipal.getName())){
logger.debug("User {} has been granted access to content object {} because she owns the content object", userId, contentObjectIdOrSystemName);
if (! CmsConstants.UUIDPattern.matcher(contentObjectIdOrSystemName).matches() && methodParameters != null && methodParameters.length > 1 &&
contentObjectNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){
//User has provided object system name. replace it with object identifier
methodParameters[0] = contentObjectNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString();
}
return proceedingJoinPoint.proceed(methodParameters);
}
}
//TODO : In case RepositoryUserIdPrincipal is not available, is it safe to just do the following check
// owner.getExternalId() == userId
//User does not own content object. Check access right defined in property
//accessibility.canBeReadBy
//StringProperty accessibilityCanBeReadByProperty = (StringProperty) contentObject.getCmsProperty("accessibility.canBeReadBy");
//if (accessibilityCanBeReadByProperty == null || accessibilityCanBeReadByProperty.hasNoValues()){
if (! contentObjectNode.hasProperty("accessibility/canBeReadBy")){
logger.debug("User {} has not been granted access to content object {} because although she does not own content objects and " +
" content object does not have any value to property accessibility.canBeReadBy ", userId, contentObjectIdOrSystemName);
return generateEmptyOutcome(contentObjectOutput);
}
//List<String> canBeReadBy = accessibilityCanBeReadByProperty.getSimpleTypeValues();
Value[] canBeReadByArr = contentObjectNode.getProperty("accessibility/canBeReadBy").getValues();
List<String> canBeReadBy = new ArrayList<String>();
for (Value value : canBeReadByArr){
canBeReadBy.add(value.getString());
}
//If canBeReadBy contains REPOSITORY value then access is granted
if (canBeReadBy.contains(ContentAccessMode.ALL.toString())){
logger.debug("User {} has been granted access to content object {} because although she does not own content object, " +
" content object property accessibility.canBeReadBy contains value REPOSITORY :{}", new Object[]{userId, contentObjectIdOrSystemName, canBeReadBy.toString()});
if (! CmsConstants.UUIDPattern.matcher(contentObjectIdOrSystemName).matches() && methodParameters != null && methodParameters.length > 1 &&
contentObjectNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){
//User has provided object system name. replace it with object identifier
methodParameters[0] = contentObjectNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString();
}
return proceedingJoinPoint.proceed(methodParameters);
}
//If canBeReadBy contains NONE value then access is denied
if (canBeReadBy.contains(ContentAccessMode.NONE.toString())){
logger.debug("User {} has not been granted access to content object {} because she does not own content object and " +
" content object property accessibility.canBeReadBy contains value NONE :{}", new Object[]{userId, contentObjectIdOrSystemName, canBeReadBy.toString()});
return generateEmptyOutcome(contentObjectOutput);
}
//canBeReadBy contains neither REPOSITORY nor NONE
//access is granted only if either to any of the user groups or explicitly to the user itself
// so we add the user id into the list of group ids. All ids are appropriately prefixed by either URS_ or GRP_ to
// distinguish between user and group ids
// Security in each content object is defined by four lists stored as part of each object (i.e. a special complex property of each content object).
// The four lists define which user or role (role may be a role group also) can respectively read, update, delete and tag the object.
// Each of the four lists inside each object contain a mixed set of the userIds and Roles which
// So we get the user roles prefixed by "GRP_" in order to discriminate them from user ids which are prefixed with "USR_"
List<String> prefixedRoles = activeSecurityContext.getAllRoles();
prefixedRoles.add(userId);
for (String prefixedRole : prefixedRoles)
{
if (canBeReadBy.contains(prefixedRole))
{
logger.debug("User {} has been granted access to content object {} because although she does not own content object, " +
" content object property accessibility.canBeReadBy contains role {} ", new Object[]{userId, contentObjectIdOrSystemName, prefixedRole});
if (! CmsConstants.UUIDPattern.matcher(contentObjectIdOrSystemName).matches() && methodParameters != null && methodParameters.length > 1 &&
contentObjectNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){
//User has provided object system name. replace it with object identifier
methodParameters[0] = contentObjectNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString();
}
return proceedingJoinPoint.proceed(methodParameters);
}
}
logger.debug("User {} has not been granted access to content object {} because she does not own content object and " +
" content object property accessibility.canBeReadBy does not contain any role which has been assigned to user. \nAccessibility.CanBeReadBy values {}" +
"\n Granted Roles to user {}", new Object[]{userId, contentObjectIdOrSystemName, canBeReadBy, prefixedRoles});
return generateEmptyOutcome(contentObjectOutput);
}
else{
logger.debug("User {} has been granted access to content object {} because she has been granted role ROLE_ADMIN ",
new Object[]{userId, contentObjectIdOrSystemName});
if (! CmsConstants.UUIDPattern.matcher(contentObjectIdOrSystemName).matches() && methodParameters != null && methodParameters.length > 1 &&
contentObjectNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){
//User has provided object system name. replace it with object identifier
methodParameters[0] = contentObjectNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString();
}
return proceedingJoinPoint.proceed(methodParameters);
}
}
catch(CmsException e)
{
throw e;
}
catch (Throwable e) {
throw new CmsException(e);
}
}
else{
//No point to proceed to actual method since no content object id is provided
logger.debug("No content object exists with id {} therefore no restrictions are imposed", contentObjectIdOrSystemName);
return generateEmptyOutcome(contentObjectOutput);
}
}
private boolean contentObjectIdIsNotNull(String contentObjectId) {
return StringUtils.isNotBlank(contentObjectId);
}
@Around(" searchContentObjects() && args(contentObjectCriteria,output)")
public Object addSecurityCriteriaToContentObjectCriteria(ProceedingJoinPoint proceedingJoinPoint, ContentObjectCriteria contentObjectCriteria, ResourceRepresentationType output) {
return addSecurityCriteria(proceedingJoinPoint, contentObjectCriteria, output);
}
private Object addSecurityCriteria(ProceedingJoinPoint proceedingJoinPoint, ContentObjectCriteria contentObjectCriteria,
ResourceRepresentationType output) {
long start = System.currentTimeMillis();
ContentObjectCriteria localCopyOfContentObjectCriteria = CmsCriteriaFactory.newContentObjectCriteria();
//if (logger.isDebugEnabled())
logger.debug("INTERCEPTION of CONTENT SEARCH ACTIVATED");
try{
SecurityContext activeSecurityContext = AstroboaClientContextHolder.getActiveSecurityContext();
if (activeSecurityContext == null){
logger.warn("No security context found.");
throw new NonAuthenticatedOperationException();
}
String userId = activeSecurityContext.getIdentity();
if (StringUtils.isBlank(userId)){
logger.error("no authenticated user found. Please authenticate before using repository services");
throw new NonAuthenticatedOperationException();
}
else { // a user is already authenticated
//if (logger.isDebugEnabled())
logger.debug("user: {} is currently authenticated", userId);
// Security in each content object is defined by four lists stored as part of each object (i.e. a special complex property of each content object).
// The four lists define which user or role (role may be a role group also) can respectively read, update, delete and tag the object.
// Each of the four lists inside each object contain a mixed set of the userIds and Roles which
// So we get the user roles prefixed by "GRP_" in order to discriminate them from user ids which are prefixed with "USR_"
//List<String> prefixedRoles = getPrefixedRoles(activeSecurityContext.getAllRoles());
// We should keep a local copy of content object criteria into which we will add the security criteria so that the original criteria object will not be affected.
// In this way the security operations meant to be provided transparently by this aspect will remain transparent to the user of repository services.
contentObjectCriteria.copyTo(localCopyOfContentObjectCriteria);
addSecurityCriteriaToContentObjectCriteria(localCopyOfContentObjectCriteria, userId, activeSecurityContext.getAllRoles(), activeSecurityContext);
}
if (logger.isDebugEnabled())
logger.debug("INSIDE SecutiryContentServiceAspect, method addSecurityCriteriaToContentObjectCriteria took " + (System.currentTimeMillis() - start) + " ms");
if (output == null){
return proceedingJoinPoint.proceed(new Object[]{localCopyOfContentObjectCriteria});
}
else{
return proceedingJoinPoint.proceed(new Object[]{localCopyOfContentObjectCriteria, output});
}
}
catch(CmsException e)
{
throw e;
}
catch (Throwable e) {
throw new CmsException(e);
}
}
private void addSecurityCriteriaToContentObjectCriteria(ContentObjectCriteria localCopyOfContentObjectCriteria, String userId, List<String> prefixedRoles, SecurityContext activeSecurityContext) {
// if the authenticated user has not been granted the role: ROLE_CMS_INTERNAL_VIEWER
// then we allow her to read only published content objects i.e. those that their status is equal to "published" or "publishedAndArchived".
// As of today a published content object overrules any "read" security option to prevent complexities in security rule handling
// and remove the extra effort required for the publisher of content objects
// It is not very convenient to require to change the "read" security option of published content objects to "ALL" to allow to be read by REPOSITORY
// and then when publication status ends revert back to previous security settings.
// Additionally "ALL" should be interpreted as "REPOSITORY PHYSICAL PERSONS or REPOSITORY USERS WHICH ARE EXPLICITLY GRANTED THE PERMISSION TO VIEW THE REPOSITORY,
// i.e. those in role ROLE_CMS_INTERNAL_VIEWER".
// Anonymous is not an actual user registered in the identity store. It is a convention introduced in order to cope with the security rule that we always need some user
// in order to permit access to content. So for any user that tries to see the repository without logging in, the front-end system, e.g. the web application, should
// silently perform a virtual login as the anonymous user.
//
// Through the above convention it becomes really easy to publish and un-publish content objects.
// The idea is that if someone publishes a content object then she implicitly removes any read restrictions.
// All other restrictions apply and furthermore read restrictions are still there and remain valid when status is not set to "published" any more.
// We may revisit this convention if the use of the repository reveals another way of interpreting anonymous requests and published objects
//
// Be aware that since the anonymous is a virtual user it is not granted any roles. So allowing users that are not granted the role:ROLE_CMS_INTERNAL_VIEWER
// to view only published content objects is sufficient and we do not actually need to check whether the user is the anonymous.
// However we explicitly check if the user identity is the anonymous in order to prevent cases where some administrator by mistake or on purpose
// registers the anonymous as a real user and assigns it the role ROLE_CMS_INTERNAL_VIEWER.
// This would result in letting all not logged in Internet users to view internal unpublished content. So we introduce the extra rule that
// anonymous is only viewing published objects despite any roles that may have been assigned to it.
//if (StringUtils.equals(userId, IdentityPrincipal.ANONYMOUS) ||
if (! activeSecurityContext.hasRole(CmsRoleAffiliationFactory.INSTANCE.getCmsRoleAffiliationForActiveRepository(CmsRole.ROLE_CMS_INTERNAL_VIEWER))){
//The anonymous user or any other user that is NOT GRANTED the role:ROLE_CMS_INTERNAL_VIEWER can access ONLY PUBLISHED content objects
//In case where criteria already contains criterion that requires content object status to equal "publishedAndArchived",
// there is no need to add criterion about published
Criterion contentObjectIsPublishedCriterion = null;
if (! criteriaContainsContentObjectStatusArchivedCriterion(localCopyOfContentObjectCriteria.getCriteria())){
contentObjectIsPublishedCriterion = CriterionFactory.equals("profile.contentObjectStatus", ContentObjectStatus.published.toString());
localCopyOfContentObjectCriteria.addCriterion(contentObjectIsPublishedCriterion);
}
//TODO: we should check whether user has already include a criterion concerning content object status and prohibit the query execution if a status other than published has been requested
// additionally if there s already a criterion asking for published content objects we should keep it only once and not add it again
// generate a warning that only published content objects have been returned
//logger.warn("The authenticated user is the 'anonymous' user. Be aware that the queries issued by anonymous user return only published content objects");
}
else if (! AbstractSecureContentObjectAspect.userHasRole(activeSecurityContext, CmsRoleAffiliationFactory.INSTANCE.getCmsRoleAffiliationForActiveRepository(CmsRole.ROLE_ADMIN))){ // for USER with role ROLE_ADMIN we do not impose any security constraint
// we will generate criteria to generate the following security restriction
// (@betaconcept:OwnerCmsIdentifier = UUIDOfUserExecutingTheQuery OR
// betaconcept:CanBeReadBy = 'REPOSITORY' OR (betaconcept:CanBeReadBy != 'NONE'
// AND (betaconcept:CanBeReadBy = "USR_" + userId OR
// betaconcept:CanBeReadBy = "GRP_" + userGroupId1 OR
// betaconcept:CanBeReadBy = "GRP_" + userGroupId2 ....)))
Criterion ownerCriterion = null;
RepositoryUserIdPrincipal ownerIdPrincipal = null;
Subject subject = activeSecurityContext.getSubject();
if (subject != null && CollectionUtils.isNotEmpty(subject.getPrincipals(RepositoryUserIdPrincipal.class))){
ownerIdPrincipal = subject.getPrincipals(RepositoryUserIdPrincipal.class).iterator().next();
ownerCriterion = CriterionFactory.equals("owner", ownerIdPrincipal.getName());
}
Criterion canBeReadByAllCriterion = CriterionFactory.equals("accessibility.canBeReadBy", ContentAccessMode.ALL.toString());
Criterion canBeReadByNotEQNoneCriterion = CriterionFactory.notEquals("accessibility.canBeReadBy", ContentAccessMode.NONE.toString());
// we check whether read access is permitted either to any of the user groups or explicitly to the user itself
// so we add the user id into the list of group ids. All ids are appropriately prefixed by either URS_ or GRP_ to
// distinguish between user and group ids
prefixedRoles.add(userId);
Criterion canBeReadByUserHerselfOrUserGroupsCriterion = CriterionFactory.equals("accessibility.canBeReadBy", Condition.OR, prefixedRoles);
// build query parts
Criterion firstPart = null;
if (ownerCriterion != null){
firstPart = CriterionFactory.or(ownerCriterion, canBeReadByAllCriterion);
}
else{
firstPart = canBeReadByAllCriterion;
}
Criterion secondPart = CriterionFactory.and(canBeReadByNotEQNoneCriterion, canBeReadByUserHerselfOrUserGroupsCriterion);
// connect the parts
Criterion securityCriterion = CriterionFactory.or(firstPart, secondPart);
// add the security criteria to user provided search criteria
localCopyOfContentObjectCriteria.addCriterion(securityCriterion);
}
}
private boolean criteriaContainsContentObjectStatusArchivedCriterion(
List<Criterion> criteria) {
if (CollectionUtils.isNotEmpty(criteria)){
for (Criterion criterion : criteria){
String xpath = criterion.getXPath();
if (StringUtils.contains(xpath, "profile/@contentObjectStatus = '"+ContentObjectStatus.publishedAndArchived.toString()+"'")){
return true;
}
}
}
return false;
}
private <T> T generateEmptyOutcome(
ResourceRepresentationType<T> contentObjectOutput) {
if (contentObjectOutput != null && contentObjectOutput.equals(ResourceRepresentationType.CONTENT_OBJECT_LIST)){
return (T) new CmsOutcomeImpl<T>(0, 0, 0);
}
else{
return null;
}
}
}