/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/authz/trunk/authz-tool/tool/src/java/org/sakaiproject/authz/tool/PermissionsHelperAction.java $ * $Id: PermissionsHelperAction.java 123084 2013-04-22 08:53:15Z matthew.buckett@it.ox.ac.uk $ *********************************************************************************** * * 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.Collection; import java.util.Collections; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; 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.cover.AuthzGroupService; import org.sakaiproject.authz.cover.FunctionManager; 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.entity.api.Reference; import org.sakaiproject.entity.cover.EntityManager; import org.sakaiproject.event.api.SessionState; import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.tool.api.Tool; import org.sakaiproject.tool.api.ToolException; import org.sakaiproject.tool.api.ToolSession; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.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_ID = "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"; /** 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"; 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); if (template == null) { addAlert(sstate, rb.getString("java.alert.prbset")); } else { return template; } return null; } 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); String rolesRef = (String) toolSession.getAttribute(PermissionsHelper.ROLES_REF); if (rolesRef == null) rolesRef = targetRef; 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_ID, rolesRef); // ... 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. */ static 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 String realmRolesId = (String) state.getAttribute(STATE_REALM_ROLES_ID); context.put("viewRealmId", realmRolesId); // 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); 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) { 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 (!realmRolesId.equals(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 = PermissionsHelper.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) { // get the roles from the edit, unless another is specified AuthzGroup roleRealm = viewEdit != null ? viewEdit : edit; if (realmRolesId != null) { try { roleRealm = AuthzGroupService.getAuthzGroup(realmRolesId); } catch (Exception e) { M_log.warn("PermissionsHelperAction.buildHelperContext: getRolesRealm: " + realmRolesId + " : " + e); } } roles = new Vector(); roles.addAll(roleRealm.getRoles()); 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); } } 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; } /** * Remove the state variables used internally, on the way out. */ private static void cleanupState(SessionState state) { state.removeAttribute(STATE_REALM_ID); state.removeAttribute(STATE_REALM_ROLES_ID); 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"); SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); // reset attributes state.setAttribute(STATE_REALM_ROLES_ID, 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()); // 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 { 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); } /** * 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); // 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().getBoolean(role.getId() + lock); 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); } } } } } }