nl.strohalm.cyclos.services.permissions.PermissionServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.permissions.PermissionServiceImpl.java

Source

/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
    
Cyclos 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 2 of the License, or
(at your option) any later version.
    
Cyclos 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 Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.services.permissions;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import nl.strohalm.cyclos.access.AdminAdminPermission;
import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.access.Module;
import nl.strohalm.cyclos.access.ModuleType;
import nl.strohalm.cyclos.access.OperatorPermission;
import nl.strohalm.cyclos.access.Permission;
import nl.strohalm.cyclos.access.PermissionCheck;
import nl.strohalm.cyclos.entities.Entity;
import nl.strohalm.cyclos.entities.groups.AdminGroup;
import nl.strohalm.cyclos.entities.groups.BrokerGroup;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.GroupQuery;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.groups.OperatorGroup;
import nl.strohalm.cyclos.entities.members.Administrator;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.Operator;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.access.AbstractPermissionCheck;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.groups.GroupServiceLocal;
import nl.strohalm.cyclos.services.permissions.exceptions.PermissionCatalogInitializationException;
import nl.strohalm.cyclos.utils.DataIteratorHelper;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.access.LoggedUser.AccessType;
import nl.strohalm.cyclos.utils.access.PermissionCatalogHandler;
import nl.strohalm.cyclos.utils.access.PermissionHelper;
import nl.strohalm.cyclos.utils.cache.Cache;
import nl.strohalm.cyclos.utils.cache.CacheCallback;
import nl.strohalm.cyclos.utils.cache.CacheManager;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * Implementation class for permission services.
 * @author rafael
 */
public class PermissionServiceImpl implements PermissionServiceLocal, ApplicationContextAware {

    private static class CacheKey implements Serializable {
        private static final long serialVersionUID = -7967894427520131064L;

        public static CacheKey fromLoggedUser() {
            return fromLoggedUser(null);
        }

        public static CacheKey fromLoggedUser(final Serializable qualifier) {
            AccessType accessType = LoggedUser.getAccessType();
            Group group = LoggedUser.hasUser() ? LoggedUser.group() : null;
            return new CacheKey(accessType, group, qualifier);
        }

        private final AccessType accessType;
        private final Group group;
        private final Serializable qualifier;

        public CacheKey(final AccessType accessType, final Group group, final Serializable qualifier) {
            this.accessType = accessType;
            this.group = group;
            this.qualifier = qualifier;
        }

        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof CacheKey)) {
                return false;
            }
            CacheKey key = (CacheKey) obj;
            return accessType == key.accessType && ObjectUtils.equals(group, key.group)
                    && ObjectUtils.equals(qualifier, key.qualifier);
        }

        @Override
        public int hashCode() {
            return 13 * (accessType == null ? 1 : accessType.hashCode())
                    * (group == null ? 1 : group.hashCode() * (qualifier == null ? 1 : qualifier.hashCode()));
        }

        @Override
        public String toString() {
            return (accessType == null ? "Guest" : accessType.name()) + (group == null ? "" : "#" + group.getId());
        }
    }

    private static final Log LOG = LogFactory.getLog(PermissionServiceImpl.class);

    private static void debug(final Group group, final List<Permission> permissions, final boolean result) {
        if (LOG.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder("Checked [");
            for (int i = 0; i < permissions.size(); i++) {
                if (i > 0) {
                    sb.append(", ");
                }
                sb.append(permissions.get(i).getValue());
            }
            sb.append("] for group id=" + group.getId() + " (" + group.getName() + ")");
            sb.append(" with result ").append(result);
            LOG.debug(sb.toString());
        }
    }

    private static void debug(final String message) {
        if (LOG.isDebugEnabled()) {
            LOG.debug(message);
        }
    }

    private FetchServiceLocal fetchService;
    private GroupServiceLocal groupService;
    private CacheManager cacheManager;
    private ApplicationContext applicationContext;

    @Override
    public void checkManages(final Element element) throws PermissionDeniedException {
        if (!manages(element)) {
            throw new PermissionDeniedException();
        }
    }

    @Override
    public void checkManages(final Group group) throws PermissionDeniedException {
        if (!manages(group)) {
            throw new PermissionDeniedException();
        }
    }

    @Override
    public void checkRelatesTo(final Element element) throws PermissionDeniedException {
        if (!relatesTo(element)) {
            throw new PermissionDeniedException();
        }
    }

    @Override
    public void evictCache(Group group) {
        // We must invalidate all other caches (for visibility / management) whenever a group changes
        getAllVisibleGroupsCache().clear();
        getManagedMemberGroupsCache().clear();
        getVisibleMemberGroupsCache().clear();

        // Evict this group's permission cache
        group = fetchService.fetch(group);
        Cache cache = getPermissionsCache();
        cache.remove(group.getId());
        if (group instanceof MemberGroup) {
            // When a member group, refresh the cache for all operator groups of members of that group
            final MemberGroup memberGroup = (MemberGroup) group;
            final List<OperatorGroup> operatorGroups = groupService.iterateOperatorGroups(memberGroup);
            try {
                for (final OperatorGroup operatorGroup : operatorGroups) {
                    cache.remove(operatorGroup.getId());
                }
            } finally {
                DataIteratorHelper.close(operatorGroups);
            }
        }
    }

    @Override
    public Collection<Group> getAllVisibleGroups() {
        // Get from cache all visible groups, except for operator groups
        CacheKey cacheKey = CacheKey.fromLoggedUser();
        Collection<Group> groups = getAllVisibleGroupsCache().get(cacheKey, new CacheCallback() {
            @Override
            public Object retrieve() {
                if (LoggedUser.getAccessType() == null) {
                    // Guests can only see initial groups
                    return groupService.getPossibleInitialGroups(null);
                }

                Collection<Group> result = new ArrayList<Group>();
                if (LoggedUser.isSystem() || hasPermission(AdminAdminPermission.ADMINS_REGISTER,
                        AdminAdminPermission.ADMINS_CHANGE_PROFILE)) {
                    GroupQuery query = new GroupQuery();
                    query.setNatures(Group.Nature.ADMIN);
                    result.addAll(groupService.search(query));
                }
                if (LoggedUser.hasUser()) {
                    // No matter what, the own group is always visible
                    result.add(LoggedUser.group());
                }
                result.addAll(getVisibleMemberGroups());
                return result;
            }
        });

        if (hasPermission(MemberPermission.OPERATORS_MANAGE)) {
            // As the cache is by logged user's group, and operator groups are per logged member,
            // we simply list the operator groups for members which can manage operators
            GroupQuery query = new GroupQuery();
            query.setNature(Group.Nature.OPERATOR);
            query.setMember(LoggedUser.member());
            groups.addAll(groupService.search(query));
        }
        return groups;
    }

    @Override
    public Collection<MemberGroup> getManagedMemberGroups() {
        CacheKey cacheKey = CacheKey.fromLoggedUser();
        return getManagedMemberGroupsCache().get(cacheKey, new CacheCallback() {
            @Override
            public Object retrieve() {
                if (LoggedUser.isSystemOrUnrestrictedClient() || LoggedUser.isAdministrator()) {
                    return getVisibleMemberGroups();
                } else if (LoggedUser.isBroker()) {
                    // Make sure the own group, plus the initial groups are present
                    Set<MemberGroup> groups = new HashSet<MemberGroup>();
                    groups.addAll(getVisibleMemberGroups());
                    MemberGroup group = LoggedUser.group();
                    groups.add(group);
                    return groups;
                } else { // member or operator
                    return Collections.<MemberGroup>singleton(LoggedUser.member().getMemberGroup());
                }
            }
        });
    }

    @Override
    public PermissionCatalogHandler getPermissionCatalogHandler(final Group group) {
        final AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory();
        try {
            PermissionCatalogHandlerImpl handler = (PermissionCatalogHandlerImpl) factory.createBean(
                    PermissionCatalogHandlerImpl.class, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);
            handler.load(group);
            return handler;
        } catch (Exception e) {
            throw new PermissionCatalogInitializationException(group.getNature(), e.getMessage(), e);
        }
    }

    @Override
    public Collection<MemberGroup> getVisibleMemberGroups() {
        return getVisibleMemberGroups(true);
    }

    @Override
    public Collection<MemberGroup> getVisibleMemberGroups(final boolean addLoggedMemberGroup) {
        CacheKey cacheKey = CacheKey.fromLoggedUser(addLoggedMemberGroup);
        return getVisibleMemberGroupsCache().get(cacheKey, new CacheCallback() {
            @Override
            public Object retrieve() {
                if (LoggedUser.getAccessType() == null) {
                    // Guests can only see initial groups
                    return groupService.getPossibleInitialGroups(null);
                }

                // Will show all groups for either
                boolean isSystem = LoggedUser.isSystem();
                boolean isUnrestrictedClient = LoggedUser.isUnrestrictedClient();
                if (isSystem || isUnrestrictedClient) {
                    // System can view all member / broker groups
                    GroupQuery query = new GroupQuery();
                    query.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
                    if (isUnrestrictedClient) {
                        // For web services, removed groups don't count
                        query.setStatus(Group.Status.NORMAL);
                        query.setOnlyActive(true);
                    }
                    return groupService.search(query);
                }
                Group group = LoggedUser.group();
                if (group instanceof AdminGroup) {
                    return fetchService.fetch((AdminGroup) group, AdminGroup.Relationships.MANAGES_GROUPS)
                            .getManagesGroups();
                } else {
                    Set<MemberGroup> memberGroups = new HashSet<MemberGroup>();
                    MemberGroup memberGroup = fetchService.fetch(LoggedUser.member().getMemberGroup(),
                            MemberGroup.Relationships.CAN_VIEW_PROFILE_OF_GROUPS);
                    if (addLoggedMemberGroup) {
                        memberGroups.add(memberGroup);
                    }
                    memberGroups.addAll(memberGroup.getCanViewProfileOfGroups());

                    // For brokers, ensure all possible initial groups are visible
                    if (LoggedUser.isBroker()) {
                        BrokerGroup brokerGroup = fetchService.fetch(LoggedUser.<BrokerGroup>group(),
                                BrokerGroup.Relationships.POSSIBLE_INITIAL_GROUPS);
                        memberGroups.addAll(brokerGroup.getPossibleInitialGroups());
                    }

                    return memberGroups;
                }
            }
        });
    }

    @Override
    public boolean hasPermission(final Group group, final Module module) {
        return ensureGroupPermissions(group).containsKey(module);
    }

    @Override
    public boolean hasPermission(final Group group, final Permission... permissions) {
        return hasPermission(group, Arrays.asList(permissions),
                Collections.<Permission, AbstractPermissionCheck.RequiredValuesBean>emptyMap());
    }

    @Override
    public boolean hasPermission(final Module module) {
        if (LoggedUser.isSystem()) {
            return true;
        } else if (LoggedUser.isUnrestrictedClient()) {
            return module.getType() == ModuleType.MEMBER;
        } else {
            return hasPermission(LoggedUser.group(), module);
        }
    }

    @Override
    public boolean hasPermission(final Permission... permissions) {
        if (LoggedUser.isSystem()) {
            return true;
        } else if (LoggedUser.isUnrestrictedClient()) {
            if (permissions == null || permissions.length == 0) {
                return false;
            } else {
                // at least one permission must be a member permission
                for (Permission p : permissions) {
                    if (p.getModule().getType() == ModuleType.MEMBER) {
                        return true;
                    }
                }
                return false;
            }
        } else {
            return hasPermission(LoggedUser.group(), permissions);
        }
    }

    @Override
    public boolean hasPermissionFor(final Group group, final Permission permission, final Entity... required) {
        if (permission.relationship() == null) {
            throw new IllegalArgumentException(String.format(
                    "Invalid permission: %1$s.%2$s. The permission must has a relationship to allow ensuring entity membership",
                    permission.getClass().getSimpleName(), permission));
        }

        return hasPermission(group, Collections.singletonList(permission), Collections.singletonMap(permission,
                new AbstractPermissionCheck.RequiredValuesBean(permission, required)));
    }

    @Override
    public boolean hasPermissionFor(final Permission permission, final Entity... required) {
        if (permission.relationship() == null) {
            throw new IllegalArgumentException(String.format(
                    "Invalid permission: %1$s.%2$s. The permission must has a relationship to allow ensuring entity membership",
                    permission.getClass().getSimpleName(), permission));
        }

        boolean hasPermission = hasPermission(permission);
        if (!hasPermission) {
            return false;
        } else if (LoggedUser.isSystemOrUnrestrictedClient()) {
            return true; // nothing to do with the required in this case
        } else {
            return checkRequiredValues(LoggedUser.group(), permission, required);
        }
    }

    @Override
    public boolean manages(Element element) {
        if (element == null) {
            throw new NullPointerException();
        }

        // System access manages all users
        if (LoggedUser.isSystem()) {
            return true;
        }

        element = fetchService.fetch(element, Element.Relationships.GROUP);
        if (LoggedUser.isUnrestrictedClient()) {
            return element instanceof Member && ((Member) element).isActive();
        }

        // Check the logged user
        Element logged = LoggedUser.element();
        if (logged.equals(element)) {
            // Logged as the user himself
            return true;
        } else if (logged instanceof Administrator) {
            // Logged as admin
            AdminGroup group = LoggedUser.group();
            if (element instanceof Administrator) {
                // For other admins, there is no group restriction - only a permission check
                return hasPermission(group, AdminAdminPermission.ADMINS_VIEW);
            } else if (element instanceof Member) {
                // Administrators can view or manage specific member groups
                Collection<MemberGroup> managedGroups = fetchService
                        .fetch(group, AdminGroup.Relationships.MANAGES_GROUPS).getManagesGroups();
                return managedGroups.contains(element.getGroup());
            }
        } else if (logged instanceof Member) {
            // Logged as member - may manage other member if is his broker, or can manage his own operators
            if (element instanceof Member) {
                return logged.equals(((Member) element).getBroker());
            } else if (element instanceof Operator) {
                return logged.equals(((Operator) element).getMember());
            }
        } else if (logged instanceof Operator) {
            // Logged as operator - only manages his own member
            return ((Operator) logged).getMember().equals(element);
        }
        return false;
    }

    @Override
    public boolean manages(final Group group) {
        if (group instanceof AdminGroup) {
            return permission()
                    .admin(AdminAdminPermission.ADMINS_REGISTER, AdminAdminPermission.ADMINS_CHANGE_PROFILE)
                    .hasPermission();
        } else if (group instanceof MemberGroup) {
            return getManagedMemberGroups().contains(group);
        } else if (group instanceof OperatorGroup) {
            OperatorGroup operatorGroup = (OperatorGroup) group;
            return LoggedUser.isSystem()
                    || (LoggedUser.isMember() && LoggedUser.element().equals(operatorGroup.getMember()));
        }
        return false;
    }

    @Override
    public PermissionCheck permission() {
        if (LoggedUser.isSystemOrUnrestrictedClient()) {
            return new AbstractPermissionCheck() {
                @Override
                protected boolean doHasPermission() {
                    if (LoggedUser.isSystem()) {
                        // System have all permissions
                        return true;
                    } else { // unrestricted client
                        // unrestricted clients has only member permissions
                        return memberPermissions != null;
                    }
                }
            };
        } else {
            // Get the permissions for the logged user's group
            return permission(LoggedUser.group());
        }
    }

    @Override
    public PermissionCheck permission(final Element element) {
        return new AbstractPermissionCheck() {
            @Override
            public boolean doHasPermission() {
                if (element == null) {
                    throw new NullPointerException("Checking permission over a null element");
                }
                // There is no need to check permission as system
                if (LoggedUser.isSystem()) {
                    debug("Checking permission as system. Assuming true");
                    return true;
                }
                // Check management over the given element
                boolean canManage = manages(element);
                debug("Checking management over " + element + (LoggedUser.isWebService() ? " (as webservice)" : "")
                        + ". Result is " + canManage);

                if (!canManage) {
                    return false;
                } else if (LoggedUser.isUnrestrictedClient()) {
                    return memberPermissions != null;
                } else {
                    // Get the logged user's group, which is used to check the permissions
                    Element logged = LoggedUser.element();
                    Group group = logged.getGroup();
                    ModuleType onlyOfType = getModuleTypeFilter(logged, element);
                    List<Permission> permissions = getPermissions(group.getNature(), onlyOfType);
                    return hasPermissionOrIsEmpty(group, permissions, requiredValuesMap);
                }
            }

            /**
             * According to the given logged user and reference element, returns the ModuleType which should be used to filter the permissions to
             * check, or null if no filter should be applied
             */
            private ModuleType getModuleTypeFilter(final Element logged, final Element element) {
                if (logged instanceof Administrator) {
                    if (element instanceof Administrator) {
                        return ModuleType.ADMIN_ADMIN;
                    } else if (element instanceof Member) {
                        return ModuleType.ADMIN_MEMBER;
                    }
                } else if (logged instanceof Member) {
                    if (element instanceof Operator) {
                        return ModuleType.MEMBER;
                    }
                    Member toCheck = fetchService.fetch((Member) element, Member.Relationships.BROKER);
                    if (logged.equals(toCheck.getBroker())) {
                        return ModuleType.BROKER;
                    } else {
                        return ModuleType.MEMBER;
                    }
                }
                return null;
            }
        };
    }

    @Override
    public PermissionCheck permission(final Group group) {
        return new AbstractPermissionCheck() {
            @Override
            public boolean doHasPermission() {
                return hasPermissionOrIsEmpty(group, getPermissions(group.getNature(), null), requiredValuesMap);
            }
        };
    }

    @Override
    public boolean relatesTo(Element element) {
        if (manages(element)) {
            return true;
        } else if (LoggedUser.isUnrestrictedClient()) {
            // in case of unrestricted clients the manages and the relatesTo relationships are the same
            return false;
        }

        element = fetchService.fetch(element, Element.Relationships.GROUP);

        // When not manages, there are a few other cases where an used can be related to another one...
        Element logged = LoggedUser.element();

        if (element instanceof Member) {
            // A member or his operators are allowed to view other member's by group
            Member loggedMember = null;
            if (logged instanceof Member) {
                loggedMember = (Member) logged;
            } else if (logged instanceof Operator) {
                loggedMember = ((Operator) logged).getMember();
            }
            if (loggedMember != null) {
                MemberGroup memberGroup = fetchService.fetch(loggedMember, RelationshipHelper
                        .nested(Element.Relationships.GROUP, MemberGroup.Relationships.CAN_VIEW_PROFILE_OF_GROUPS))
                        .getMemberGroup();
                return memberGroup.getCanViewProfileOfGroups().contains(element.getGroup());
            }
        } else if (element instanceof Operator && logged instanceof Operator) {
            // Operators of the same member are related
            return ((Operator) element).getMember().equals(((Operator) logged).getMember());
        }
        return false;
    }

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void setCacheManager(final CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
        this.fetchService = fetchService;
    }

    public void setGroupServiceLocal(final GroupServiceLocal groupService) {
        this.groupService = groupService;
    }

    /**
     * Checks if every required entity is an allowed one. If permission is a MemberPermission then it ensure the required entities for the memberGroup
     */
    private boolean checkRequiredValues(Group group, final MemberGroup memberGroup, final Permission permission,
            final Map<Permission, AbstractPermissionCheck.RequiredValuesBean> requiredValues) {
        if (requiredValues == null) {
            return true;
        } else {
            AbstractPermissionCheck.RequiredValuesBean required = requiredValues.get(permission);
            if (required == null) {
                return true;
            } else {
                if (group.getNature() == Group.Nature.OPERATOR
                        && required.getPermission().getModule().getType() == ModuleType.MEMBER) {
                    group = memberGroup;
                }
                return checkRequiredValues(group, required.getPermission(), required.getEntities());
            }
        }
    }

    /**
     * Checks if every required entity is an allowed one
     */
    private boolean checkRequiredValues(Group group, final Permission permission, final Entity[] required) {
        if (required == null || required.length == 0) {
            return true;
        }

        group = fetchService.fetch(group, permission.relationship());
        Collection<? extends Entity> allowed = PermissionHelper.getAllowedValues(group, permission);
        boolean found = true;
        for (int i = 0; i < required.length && found; i++) {
            found = allowed.contains(required[i]);
        }
        return found;
    }

    /**
     * Returns the permissions, keyed by module, for the given group
     */
    private Map<Module, SortedSet<Permission>> ensureGroupPermissions(final Group group) {
        final Long id = group.getId();
        return getPermissionsCache().get(id, new CacheCallback() {
            @Override
            public Object retrieve() {
                Map<Module, SortedSet<Permission>> groupPermissions = new HashMap<Module, SortedSet<Permission>>();

                Group group = EntityHelper.reference(Group.class, id);
                group = fetchService.reload(group, Group.Relationships.PERMISSIONS,
                        RelationshipHelper.nested(OperatorGroup.Relationships.MEMBER, Element.Relationships.GROUP));
                boolean isOperatorGroup = group.getNature() == Group.Nature.OPERATOR;

                for (final Permission permission : group.getPermissions()) {
                    SortedSet<Permission> modulePermissions = groupPermissions.get(permission.getModule());
                    if (modulePermissions == null) {
                        modulePermissions = new TreeSet<Permission>();
                        groupPermissions.put(permission.getModule(), modulePermissions);
                    }
                    boolean addPermission = true;
                    if (isOperatorGroup) {
                        if (!ModuleType.getModuleTypes(group.getNature())
                                .contains(permission.getModule().getType())) {
                            throw new IllegalStateException("Invalid permission for operator group: " + permission);
                        } else {
                            final MemberGroup memberGroup = ((OperatorGroup) group).getMember().getMemberGroup();
                            if (hasPermission(memberGroup, MemberPermission.OPERATORS_MANAGE)) { // if the member doesn't have the manage permission
                                                                                                 // then his operators can't operate!
                                if (permission instanceof OperatorPermission) {
                                    for (Permission memberPermission : ((OperatorPermission) permission)
                                            .getParentPermissions()) {
                                        addPermission = hasPermission(memberGroup, memberPermission);
                                        if (addPermission) { // if the member has one of them then the operator too
                                            break;
                                        }
                                    }
                                }
                            } else {
                                addPermission = false;
                            }
                        }
                    }
                    if (addPermission) {
                        modulePermissions.add(permission);
                    }
                }

                return groupPermissions;
            }
        });
    }

    private Cache getAllVisibleGroupsCache() {
        return cacheManager.getCache("cyclos.AllVisibleGroups");
    }

    private Cache getManagedMemberGroupsCache() {
        return cacheManager.getCache("cyclos.ManagedMemberGroups");
    }

    private Cache getPermissionsCache() {
        return cacheManager.getCache("cyclos.Permissions");
    }

    private Cache getVisibleMemberGroupsCache() {
        return cacheManager.getCache("cyclos.VisibleMemberGroups");
    }

    private boolean hasPermission(final Group group, final List<Permission> permissions,
            final Map<Permission, AbstractPermissionCheck.RequiredValuesBean> requiredValues) {
        boolean result = false;
        Group groupToCheck;
        if (permissions != null) {
            MemberGroup memberGroup = null;
            if (group.getNature() == Group.Nature.OPERATOR) {
                // fetch the operator's owner member group outside the loop
                final OperatorGroup operatorGroup = fetchService.fetch((OperatorGroup) group,
                        RelationshipHelper.nested(OperatorGroup.Relationships.MEMBER, Element.Relationships.GROUP));
                memberGroup = operatorGroup.getMember().getMemberGroup();
            }
            for (Permission permission : permissions) {
                groupToCheck = group;
                // When an operator is logged in and the module is member, test the operator's member's permissions
                if (group.getNature() == Group.Nature.OPERATOR
                        && permission.getModule().getType() == ModuleType.MEMBER) {
                    groupToCheck = memberGroup;
                }

                // Get the permissions from the cache
                Map<Module, SortedSet<Permission>> groupPermissions = ensureGroupPermissions(groupToCheck);
                SortedSet<Permission> permissionsSet = groupPermissions.get(permission.getModule());
                if (permissionsSet != null && permissionsSet.contains(permission)) {
                    // at this point the group has the permission, now we must ensure the permission was granted to the required entities (if any)
                    result = checkRequiredValues(groupToCheck, memberGroup, permission, requiredValues);
                    if (result) {
                        break;
                    }
                }
            }
        }
        return result;
    }

    /**
     * Like {@link #hasPermission(Permission...)}, but returns true if permissions is not null and empty. It stills returns false if permissions is
     * null, as the semantics is that there is no possible permissions, while if empty is like no permission check is needed
     */
    private boolean hasPermissionOrIsEmpty(final Group group, final List<Permission> permissions,
            final Map<Permission, AbstractPermissionCheck.RequiredValuesBean> requiredValues) {
        if (permissions == null) {
            return false;
        } else if (permissions.isEmpty()) {
            debug("Passing with empty permissions on group id=" + group.getId() + " (" + group.getName() + ")");
            return true;
        } else {
            boolean result = hasPermission(group, permissions, requiredValues);
            debug(group, permissions, result);
            return result;
        }
    }
}