org.sakaiproject.authz.tool.PermissionsHelperAction.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.authz.tool.PermissionsHelperAction.java

Source

/**********************************************************************************
 * $URL$
 * $Id$
 ***********************************************************************************
 *
 * Copyright (c) 2005, 2006, 2008 The Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **********************************************************************************/

package org.sakaiproject.authz.tool;

import java.io.IOException;
import java.util.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.AuthzPermissionException;
import org.sakaiproject.authz.api.GroupAlreadyDefinedException;
import org.sakaiproject.authz.api.GroupIdInvalidException;
import org.sakaiproject.authz.api.GroupNotDefinedException;
import org.sakaiproject.authz.api.PermissionsHelper;
import org.sakaiproject.authz.api.Role;
import org.sakaiproject.authz.api.RoleAlreadyDefinedException;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.authz.api.FunctionManager;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.cheftool.Context;
import org.sakaiproject.cheftool.JetspeedRunData;
import org.sakaiproject.cheftool.RunData;
import org.sakaiproject.cheftool.VelocityPortlet;
import org.sakaiproject.cheftool.VelocityPortletPaneledAction;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.entity.api.Reference;
import org.sakaiproject.entity.api.EntityManager;
import org.sakaiproject.event.api.SessionState;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.tool.api.ToolException;
import org.sakaiproject.tool.api.ToolSession;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.util.ResourceLoader;

/**
 * This is a helper interface to the Permissions tool.
 */
public class PermissionsHelperAction extends VelocityPortletPaneledAction {
    /** Our logger. */
    private static Log M_log = LogFactory.getLog(PermissionsHelperAction.class);

    private static ResourceLoader rb = new ResourceLoader("authz-tool");

    private static final String STARTED = "sakaiproject.permissions.started";

    /** State attributes for Permissions mode - when it is MODE_DONE the tool can process the results. */
    public static final String STATE_MODE = "pemissions.mode";

    /** State attribute for the realm id - users should set before starting. */
    public static final String STATE_REALM_ID = "permission.realmId";

    /** State attribute for the realm id - users should set before starting. */
    public static final String STATE_REALM_ROLES_IDS = "permission.realmRolesId";

    /** State attribute for the description of what's being edited - users should set before starting. */
    public static final String STATE_DESCRIPTION = "permission.description";

    /** State attribute for the lock/ability string prefix to be presented / edited - users should set before starting. */
    public static final String STATE_PREFIX = "permission.prefix";

    /** State attributes for storing the realm being edited. */
    private static final String STATE_REALM_EDIT = "permission.realm";

    /** State attributes for storing the current selected realm being edited. */
    private static final String STATE_VIEW_REALM_EDIT = "permission.view.realm";

    /** State attributes for storing the abilities, filtered by the prefix. */
    private static final String STATE_ABILITIES = "permission.abilities";

    /** State attribute for storing the roles to display. */
    private static final String STATE_ROLES = "permission.roles";

    /** State attribute for storing the abilities of each role for this resource. */
    private static final String STATE_ROLE_ABILITIES = "permission.rolesAbilities";

    /** State attribute for permission description */
    public static final String STATE_PERMISSION_DESCRIPTIONS = "permission.descriptions";

    /** the prefix to permission title for permission description entry in bundle file */
    public static final String PREFIX_PERMISSION_DESCRIPTION = "desc-";

    /** Modes. */
    public static final String MODE_MAIN = "main";

    /** vm files for each mode. TODO: path too hard coded */
    private static final String TEMPLATE_MAIN = "helper/chef_permissions";

    private static final String STATE_GROUP_AWARE = "state_group_aware";

    private AuthzGroupService authzGroupService;
    private FunctionManager functionManager;
    private SecurityService securityService;
    private EntityManager entityManager;
    private SiteService siteService;
    private SessionManager sessionManager;
    private ToolManager toolManager;
    private ServerConfigurationService serverConfigurationService;

    public PermissionsHelperAction() {
        super();
        authzGroupService = ComponentManager.get(AuthzGroupService.class);
        functionManager = ComponentManager.get(FunctionManager.class);
        securityService = ComponentManager.get(SecurityService.class);
        entityManager = ComponentManager.get(EntityManager.class);
        siteService = ComponentManager.get(SiteService.class);
        sessionManager = ComponentManager.get(SessionManager.class);
        toolManager = ComponentManager.get(ToolManager.class);
        serverConfigurationService = ComponentManager.get(ServerConfigurationService.class);
    }

    protected void toolModeDispatch(String methodBase, String methodExt, HttpServletRequest req,
            HttpServletResponse res) throws ToolException {
        SessionState sstate = getState(req);
        ToolSession toolSession = sessionManager.getCurrentToolSession();

        String mode = (String) sstate.getAttribute(STATE_MODE);
        Object started = toolSession.getAttribute(STARTED);

        if (mode == null && started != null) {
            toolSession.removeAttribute(STARTED);
            Tool tool = toolManager.getCurrentTool();

            String url = (String) sessionManager.getCurrentToolSession()
                    .getAttribute(tool.getId() + Tool.HELPER_DONE_URL);

            sessionManager.getCurrentToolSession().removeAttribute(tool.getId() + Tool.HELPER_DONE_URL);

            try {
                res.sendRedirect(url);
            } catch (IOException e) {
                Log.warn("chef", this + " : ", e);
            }
            return;
        }

        super.toolModeDispatch(methodBase, methodExt, req, res);
    }

    /**
     * Allow extension classes to control which build method gets called for this pannel
     * @param panel
     * @return
     */
    protected String panelMethodName(String panel) {
        // we are always calling buildMainPanelContext
        return "buildMainPanelContext";
    }

    /**
     * Default is to use when Portal starts up
     */
    public String buildMainPanelContext(VelocityPortlet portlet, Context context, RunData rundata,
            SessionState sstate) {
        String mode = (String) sstate.getAttribute(STATE_MODE);

        if (mode == null) {
            initHelper(portlet, context, rundata, sstate);
        }

        String template = buildHelperContext(portlet, context, rundata, sstate);
        // May be null.
        return template;
    }

    protected void initHelper(VelocityPortlet portlet, Context context, RunData rundata, SessionState state) {
        ToolSession toolSession = sessionManager.getCurrentToolSession();

        String prefix = (String) toolSession.getAttribute(PermissionsHelper.PREFIX);
        String targetRef = (String) toolSession.getAttribute(PermissionsHelper.TARGET_REF);
        String description = (String) toolSession.getAttribute(PermissionsHelper.DESCRIPTION);
        Object rolesRef = toolSession.getAttribute(PermissionsHelper.ROLES_REF);
        if (rolesRef == null)
            rolesRef = targetRef;

        Collection<String> rolesRefs;
        if (rolesRef instanceof Collection) {
            rolesRefs = (Collection<String>) rolesRef;
        } else {
            rolesRefs = Collections.singletonList((String) rolesRef);
        }

        toolSession.setAttribute(STARTED, Boolean.valueOf(true));

        // setup for editing the permissions of the site for this tool, using the roles of this site, too
        state.setAttribute(STATE_REALM_ID, targetRef);

        // use the roles from this ref's AuthzGroup
        state.setAttribute(STATE_REALM_ROLES_IDS, rolesRefs);

        // ... with this description
        state.setAttribute(STATE_DESCRIPTION, description);

        // ... showing only locks that are prpefixed with this
        state.setAttribute(STATE_PREFIX, prefix);

        // ... set the ResourceLoader object
        state.setAttribute(STATE_PERMISSION_DESCRIPTIONS,
                toolSession.getAttribute(PermissionsHelper.PERMISSION_DESCRIPTION));

        // start the helper
        state.setAttribute(STATE_MODE, MODE_MAIN);

        state.setAttribute(STATE_GROUP_AWARE, toolSession.getAttribute("groupAware"));
    }

    /**
     * build the context.
     * 
     * @return The name of the template to use. <code>null</code> can be returned.
     */
    public String buildHelperContext(VelocityPortlet portlet, Context context, RunData rundata,
            SessionState state) {
        // in state is the realm id
        context.put("thelp", rb);
        String realmId = (String) state.getAttribute(STATE_REALM_ID);

        // in state is the realm to use for roles - if not, use realmId
        Collection<String> realmRolesIds = (Collection<String>) state.getAttribute(STATE_REALM_ROLES_IDS);
        context.put("viewRealmIds", realmRolesIds);

        // get the realm locked for editing
        AuthzGroup edit = (AuthzGroup) state.getAttribute(STATE_REALM_EDIT);
        if (edit == null) {
            if (authzGroupService.allowUpdate(realmId)) {
                try {
                    edit = authzGroupService.getAuthzGroup(realmId);
                    state.setAttribute(STATE_REALM_EDIT, edit);
                } catch (GroupNotDefinedException e) {
                    try {
                        // we can create the realm
                        edit = authzGroupService.addAuthzGroup(realmId);
                        state.setAttribute(STATE_REALM_EDIT, edit);
                    } catch (GroupIdInvalidException ee) {
                        M_log.warn("PermissionsAction.buildHelperContext: addRealm: " + ee);
                        cleanupState(state);
                        return null;
                    } catch (GroupAlreadyDefinedException ee) {
                        M_log.warn("PermissionsAction.buildHelperContext: addRealm: " + ee);
                        cleanupState(state);
                        return null;
                    } catch (AuthzPermissionException ee) {
                        M_log.warn("PermissionsAction.buildHelperContext: addRealm: " + ee);
                        cleanupState(state);
                        return null;
                    }
                }
            }

            // no permission
            else {
                M_log.warn("PermissionsAction.buildHelperContext: no permission: " + realmId);
                cleanupState(state);
                addAlert(state, rb.getFormattedMessage("alert_permission", new Object[] { realmId }));
                return null;
            }
        }

        AuthzGroup viewEdit = null;
        // check wither the current realm id is of site group type
        if (realmId.indexOf(SiteService.REFERENCE_ROOT) != -1) {
            String siteId = realmId.replaceAll(SiteService.REFERENCE_ROOT + "/", "");
            context.put("siteRef", realmId);

            if (state.getAttribute(STATE_GROUP_AWARE) != null
                    && ((Boolean) state.getAttribute(STATE_GROUP_AWARE)).booleanValue()) {
                // only show groups for group-aware tools
                try {
                    Site site = siteService.getSite(siteId);
                    Collection groups = site.getGroups();
                    if (groups != null && !groups.isEmpty()) {
                        Iterator iGroups = groups.iterator();
                        for (; iGroups.hasNext();) {
                            Group group = (Group) iGroups.next();
                            // need to either have realm update permission on the group level or better at the site level
                            if (!authzGroupService.allowUpdate(group.getReference())) {
                                iGroups.remove();
                            }
                        }
                        context.put("groups", groups);
                    }

                } catch (Exception siteException) {
                    M_log.warn("PermissionsAction.buildHelperContext: getsite of realm id =  " + realmId
                            + siteException);
                }
            }

            // get the realm locked for editing
            viewEdit = (AuthzGroup) state.getAttribute(STATE_VIEW_REALM_EDIT);
            if (viewEdit == null) {
                // I have no idea why this step is performed since we should never edit the template realm, so let's not be too serious about it
                String realmRolesId = realmRolesIds.iterator().next();

                if (authzGroupService.allowUpdate(realmRolesId)
                        || authzGroupService.allowUpdate(siteService.siteReference(siteId))) {
                    try {
                        viewEdit = authzGroupService.getAuthzGroup(realmRolesId);
                        state.setAttribute(STATE_VIEW_REALM_EDIT, viewEdit);
                    } catch (GroupNotDefinedException e) {
                        M_log.warn("PermissionsAction.buildHelperContext: getRealm with id= " + realmRolesId + " : "
                                + e);
                        cleanupState(state);
                        return null;
                    }
                }

                // no permission
                else {
                    M_log.warn("PermissionsAction.buildHelperContext: no permission: " + realmId);
                    cleanupState(state);
                    return null;
                }
            }
        }

        // in state is the prefix for abilities to present
        String prefix = (String) state.getAttribute(STATE_PREFIX);

        // in state is the list of abilities we will present
        List functions = (List) state.getAttribute(STATE_ABILITIES);
        if (functions == null) {
            // get all functions prefixed with our prefix
            functions = functionManager.getRegisteredFunctions(prefix);
        }

        if (functions != null && !functions.isEmpty()) {
            List<String> nFunctions = new Vector<String>();
            if (!realmRolesIds.contains(realmId)) {
                // editing groups within site, need to filter out those permissions only applicable to site level
                for (Iterator iFunctions = functions.iterator(); iFunctions.hasNext();) {
                    String function = (String) iFunctions.next();
                    if (function.indexOf("all.groups") == -1) {
                        nFunctions.add(function);
                    }
                }
            } else {
                nFunctions.addAll(functions);
            }
            state.setAttribute(STATE_ABILITIES, nFunctions);
            context.put("abilities", nFunctions);

            // get function description from passed in HashMap
            // output permission descriptions
            Map<String, String> functionDescriptions = (Map<String, String>) state
                    .getAttribute(STATE_PERMISSION_DESCRIPTIONS);
            if (functionDescriptions != null) {
                Set keySet = functionDescriptions.keySet();
                for (Object function : functions) {
                    String desc = (String) function;
                    String descKey = PREFIX_PERMISSION_DESCRIPTION + function;
                    if (keySet.contains(descKey)) {
                        // use function description
                        desc = (String) functionDescriptions.get(descKey);
                    }

                    functionDescriptions.put((String) function, desc);
                }
                context.put("functionDescriptions", functionDescriptions);
            }
        }

        // in state is the description of the edit
        String description = (String) state.getAttribute(STATE_DESCRIPTION);

        // the list of roles
        List roles = (List) state.getAttribute(STATE_ROLES);
        if (roles == null && realmRolesIds != null) {
            // get the roles from the edit, unless another is specified
            AuthzGroup roleRealm = viewEdit != null ? viewEdit : edit;
            Collection<Role> rolesUnique = new HashSet<Role>();
            for (String realmRoleId : realmRolesIds) {
                if (realmRoleId != null) {
                    try {
                        roleRealm = authzGroupService.getAuthzGroup(realmRoleId);
                    } catch (Exception e) {
                        M_log.warn("PermissionsHelperAction.buildHelperContext: getRolesRealm: " + realmRoleId
                                + " : " + e);
                    }
                }

                rolesUnique.addAll(roleRealm.getRoles());
            }
            roles = new Vector(rolesUnique);
            Collections.sort(roles);
            state.setAttribute(STATE_ROLES, roles);
        }

        // the abilities not including this realm for each role
        Map rolesAbilities = (Map) state.getAttribute(STATE_ROLE_ABILITIES);
        if (rolesAbilities == null) {
            rolesAbilities = new Hashtable();
            state.setAttribute(STATE_ROLE_ABILITIES, rolesAbilities);

            // get this resource's role Realms,those that refine the role definitions, but not it's own
            Reference ref = entityManager.newReference(viewEdit != null ? viewEdit.getId() : edit.getId());
            Collection realms = ref.getAuthzGroups();
            realms.remove(ref.getReference());

            for (Iterator iRoles = roles.iterator(); iRoles.hasNext();) {
                Role role = (Role) iRoles.next();
                Set locks = authzGroupService.getAllowedFunctions(role.getId(), realms);
                rolesAbilities.put(role.getId(), locks);
            }
        }

        PermissionLimiter limiter = getPermissionLimiter();

        context.put("limiter", limiter);

        context.put("roleName", new RoleNameLookup());
        context.put("realm", viewEdit != null ? viewEdit : edit);
        context.put("prefix", prefix);
        context.put("description", description);
        if (roles.size() > 0) {
            context.put("roles", roles);
        }
        context.put("rolesAbilities", rolesAbilities);

        // make sure observers are disabled
        VelocityPortletPaneledAction.disableObservers(state);

        return TEMPLATE_MAIN;
    }

    /**
     * Find a map of permissions based on a config prefix.
     * If there aren't any permissions list all are allowed.
     *
     * @param configPrefix The prefix to get permissions for.
     */
    private Map<String, Set<String>> getPermissions(String configPrefix) {
        Map<String, Set<String>> roleMap = new HashMap<String, Set<String>>();
        String roleList = serverConfigurationService.getString(configPrefix + "roles", "");
        Set<String> defaultPermissionSet = createPermissionSet(configPrefix, "default");
        for (String roleName : roleList.split(",")) {
            roleName = roleName.trim();
            if (roleName.length() == 0) {
                continue;
            }
            Set<String> permissionSet = createPermissionSet(configPrefix, roleName);
            roleMap.put(roleName, (permissionSet.size() > 0) ? permissionSet : defaultPermissionSet);

        }
        return roleMap;
    }

    private Set<String> createPermissionSet(String config, String roleName) {
        String permissionList = org.sakaiproject.component.cover.ServerConfigurationService
                .getString(config + roleName, "");
        Set<String> permissionSet = new HashSet<String>();
        for (String permissionName : permissionList.split(",")) {
            permissionName = permissionName.trim();
            if (permissionName.length() > 0) {
                permissionSet.add(permissionName);
            }
        }
        return permissionSet;
    }

    /**
     * Remove the state variables used internally, on the way out.
     */
    private void cleanupState(SessionState state) {
        state.removeAttribute(STATE_REALM_ID);
        state.removeAttribute(STATE_REALM_ROLES_IDS);
        state.removeAttribute(STATE_REALM_EDIT);
        state.removeAttribute(STATE_VIEW_REALM_EDIT);
        state.removeAttribute(STATE_PREFIX);
        state.removeAttribute(STATE_ABILITIES);
        state.removeAttribute(STATE_DESCRIPTION);
        state.removeAttribute(STATE_ROLES);
        state.removeAttribute(STATE_ROLE_ABILITIES);
        state.removeAttribute(STATE_PERMISSION_DESCRIPTIONS);
        state.removeAttribute(STATE_MODE);
        state.removeAttribute(VelocityPortletPaneledAction.STATE_HELPER);
        state.removeAttribute(STATE_GROUP_AWARE);

        // re-enable observers
        VelocityPortletPaneledAction.enableObservers(state);
    }

    /**
     * to show different permission settings based on user selection of authz group
     * @param data
     */
    public void doView_permission_option(RunData data) {
        String viewAuthzId = data.getParameters().getString("authzGroupSelection");
        Collection<String> viewAuthzIds = Collections.singletonList(viewAuthzId);
        SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid());
        // reset attributes
        state.setAttribute(STATE_REALM_ROLES_IDS, viewAuthzId);
        state.removeAttribute(STATE_VIEW_REALM_EDIT);
        state.removeAttribute(STATE_ABILITIES);
        state.removeAttribute(STATE_ROLES);
        state.removeAttribute(STATE_ROLE_ABILITIES);
    }

    /**
     * Handle the eventSubmit_doSave command to save the edited permissions.
     */
    public void doSave(RunData data) {
        SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid());

        if (!"POST".equals(data.getRequest().getMethod())) {
            M_log.warn("PermissionsAction.doSave: user did not submit with a POST! IP="
                    + data.getRequest().getRemoteAddr());
            return;
        }

        // only save the view realm's roles
        AuthzGroup edit = (AuthzGroup) state.getAttribute(STATE_VIEW_REALM_EDIT);
        if (edit == null) {
            edit = (AuthzGroup) state.getAttribute(STATE_REALM_EDIT);
        }

        if (edit != null) {
            // read the form, updating the edit
            readForm(data, edit, state);

            // commit the change
            try {
                removeEmptyRoles(edit);

                if (hasNothingSet(edit)) {
                    authzGroupService.removeAuthzGroup(edit);
                } else {
                    authzGroupService.save(edit);
                }
            } catch (GroupNotDefinedException e) {
                addAlert(state,
                        rb.getFormattedMessage("alert_sitegroupnotdefined", new Object[] { edit.getReference() }));
            } catch (AuthzPermissionException e) {
                addAlert(state, rb.getFormattedMessage("alert_permission", new Object[] { edit.getReference() }));
            }
        }

        // clean up state
        cleanupState(state);
    }

    /**
     * Removes all the roles in an AuthzGroup that don't have any permissions set on them.
     * @param edit The AuthzGroup to cleanup.
     */
    private void removeEmptyRoles(AuthzGroup edit) {
        for (Role role : edit.getRoles()) {
            if (role.getAllowedFunctions().isEmpty()) {
                edit.removeRole(role.getId());
            }
        }
    }

    /**
     * @param edit The AuthzGroup to check.
     * @return <code>true</code> if there are no roles and no members in this AuthzGroup.
     */
    private boolean hasNothingSet(AuthzGroup edit) {
        return edit.getRoles().isEmpty() && edit.getMembers().isEmpty();
    }

    /**
     * Handle the eventSubmit_doCancel command to abort the edits.
     */
    public void doCancel(RunData data) {
        SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid());

        // clean up state
        cleanupState(state);
    }

    /**
     * Read the permissions form.
     */
    private void readForm(RunData data, AuthzGroup edit, SessionState state) {
        List abilities = (List) state.getAttribute(STATE_ABILITIES);
        List roles = (List) state.getAttribute(STATE_ROLES);

        PermissionLimiter limiter = getPermissionLimiter();
        // look for each role's ability field
        for (Iterator iRoles = roles.iterator(); iRoles.hasNext();) {
            Role role = (Role) iRoles.next();

            for (Iterator iLocks = abilities.iterator(); iLocks.hasNext();) {
                String lock = (String) iLocks.next();
                boolean checked = (data.getParameters().getString(role.getId() + lock) != null);
                // Don't allow changes to some permissions.
                if (!(limiter.isEnabled(role.getId(), lock, role.isAllowed(lock)))) {
                    M_log.debug("Can't change permission '" + lock + "' on role '" + role.getId() + "'.");
                    continue;
                }

                if (checked) {
                    // we have an ability! Make sure there's a role
                    Role myRole = edit.getRole(role.getId());
                    if (myRole == null) {
                        try {
                            myRole = edit.addRole(role.getId());
                        } catch (RoleAlreadyDefinedException e) {
                            M_log.warn("PermissionsAction.readForm: addRole after getRole null: " + role.getId()
                                    + " : " + e);
                        }
                    }
                    if (myRole != null) {
                        myRole.allowFunction(lock);
                    }
                } else {
                    // if we do have this role, make sure there's not this lock
                    Role myRole = edit.getRole(role.getId());
                    if (myRole != null) {
                        myRole.disallowFunction(lock);
                    }
                }
            }
        }
    }

    public PermissionLimiter getPermissionLimiter() {
        Map allowedPermissions = getPermissions("realm.allowed."); // Whitelisted permissions for some roles
        Map frozenPermissions = getPermissions("realm.frozen."); // Permissions that can't be changed
        Map addOnlyPermissions = getPermissions("realm.add.only."); // Permissions that can only be added.   }
        return new PermissionLimiter(allowedPermissions, frozenPermissions, addOnlyPermissions);
    }

    /**
     * The class is put into the velocity context to limit the permission that can be set.
     */
    public class PermissionLimiter {
        private Map<String, Set<String>> allowedPermissions;
        private Map<String, Set<String>> frozenPermissions;
        private Map<String, Set<String>> addOnlyPermissions;

        /**
         * Create a permission limiter. This is put into the velocity context to remove complex logic from the template.
         *
         * @param allowedPermissions A complete set of permissions for a role. If the role exists in this map but the
         *                           permission isn't present the user can't set it.
         * @param frozenPermissions A set of permissions that can't be changed for each role.
         * @param addOnlyPermissions A set of permissions which the user can only grant and can't take away.
         */
        public PermissionLimiter(Map<String, Set<String>> allowedPermissions,
                Map<String, Set<String>> frozenPermissions, Map<String, Set<String>> addOnlyPermissions) {
            this.allowedPermissions = allowedPermissions;
            this.frozenPermissions = frozenPermissions;
            this.addOnlyPermissions = addOnlyPermissions;
        }

        public boolean isEnabled(String roleId, String permission, boolean enabled) {
            // Sysadmin doesn't have any restrictions
            if (securityService.isSuperUser()) {
                return true;
            }
            if (frozenPermissions.containsKey(roleId)) {
                if (frozenPermissions.get(roleId).contains(permission)) {
                    return false;
                }
            }
            // Only check when permission is enabled.
            if (enabled && addOnlyPermissions.containsKey(roleId)) {
                if (addOnlyPermissions.get(roleId).contains(permission)) {
                    return false;
                }
            }
            if (allowedPermissions.containsKey(roleId)) {
                return allowedPermissions.get(roleId).contains(permission);
            }
            return true;
        }
    }

    public class RoleNameLookup {

        public String getName(String roleId) {
            return authzGroupService.getRoleName(roleId);
        }
    }
}