org.betaconceptframework.astroboa.engine.service.security.aspect.SecureContentServiceAspect.java Source code

Java tutorial

Introduction

Here is the source code for org.betaconceptframework.astroboa.engine.service.security.aspect.SecureContentServiceAspect.java

Source

/*
 * 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;
        }
    }

}