/* * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache 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.apache.org/licenses/LICENSE-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. * * Contributors: * Nuxeo - initial API and implementation * * $Id$ */ package org.nuxeo.ecm.webapp.security; import static org.jboss.seam.ScopeType.CONVERSATION; import java.io.Serializable; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.model.SelectItem; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.core.Events; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.international.StatusMessage; import org.nuxeo.common.utils.UserAgentMatcher; import org.nuxeo.common.utils.i18n.Labeler; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.api.security.ACP; import org.nuxeo.ecm.core.api.security.PermissionProvider; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.core.api.security.UserEntry; import org.nuxeo.ecm.core.api.security.UserVisiblePermission; import org.nuxeo.ecm.core.api.security.impl.ACPImpl; import org.nuxeo.ecm.platform.query.api.PageSelection; import org.nuxeo.ecm.platform.query.api.PageSelections; import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; import org.nuxeo.ecm.platform.usermanager.UserManager; import org.nuxeo.ecm.webapp.base.InputController; import org.nuxeo.ecm.webapp.helpers.EventNames; import org.nuxeo.runtime.api.Framework; /** * Provides security related methods. * * @author Razvan Caraghin */ @Name("securityActions") @Scope(CONVERSATION) public class SecurityActionsBean extends InputController implements SecurityActions, Serializable { private static final long serialVersionUID = -7190826911734958662L; private static final Log log = LogFactory.getLog(SecurityActionsBean.class); @In(create = true) protected transient NavigationContext navigationContext; @In(create = true, required = false) protected transient CoreSession documentManager; @In(create = true) protected PermissionActionListManager permissionActionListManager; @In(create = true) protected PermissionListManager permissionListManager; @In(create = true) protected PrincipalListManager principalListManager; @In(create = true) protected transient UserManager userManager; @In(create = true) protected NuxeoPrincipal currentUser; protected static final String[] SEED_PERMISSIONS_TO_CHECK = { SecurityConstants.WRITE_SECURITY, SecurityConstants.READ_SECURITY }; private static final Labeler labeler = new Labeler("label.security.permission"); protected String[] CACHED_PERMISSION_TO_CHECK; protected SecurityData securityData; protected boolean obsoleteSecurityData = true; protected PageSelections<String> entries; protected transient List<String> cachedValidatedUserAndGroups; protected transient List<String> cachedDeletedUserAndGroups; private Boolean blockRightInheritance; protected String selectedEntry; protected List<String> selectedEntries; @Override @Observer(value = EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED, create = false) @BypassInterceptors public void resetSecurityData() { obsoleteSecurityData = true; blockRightInheritance = null; } @Override public void rebuildSecurityData() { DocumentModel currentDocument = navigationContext.getCurrentDocument(); if (currentDocument != null) { if (securityData == null) { securityData = new SecurityData(); securityData.setDocumentType(currentDocument.getType()); } ACP acp = documentManager.getACP(currentDocument.getRef()); if (acp != null) { SecurityDataConverter.convertToSecurityData(acp, securityData); } else { securityData.clear(); } reconstructTableModel(); // Check if the inherited rights are activated List<String> deniedPerms = securityData.getCurrentDocDeny().get(SecurityConstants.EVERYONE); if (deniedPerms != null && deniedPerms.contains(SecurityConstants.EVERYTHING)) { blockRightInheritance = Boolean.TRUE; } if (blockRightInheritance == null) { blockRightInheritance = Boolean.FALSE; } obsoleteSecurityData = false; } } /** * Update the dataTableModel from the current {@link SecurityData} this method is automatically called by * rebuildSecurityData method */ protected void reconstructTableModel() { List<String> items = getCurrentDocumentUsers(); entries = new PageSelections<String>(); if (items != null) { for (String item : items) { if (SecurityConstants.EVERYONE.equals(item)) { final List<String> grantedPerms = securityData.getCurrentDocGrant().get(item); final List<String> deniedPerms = securityData.getCurrentDocDeny().get(item); if (deniedPerms != null && deniedPerms.contains(SecurityConstants.EVERYTHING) && grantedPerms == null && deniedPerms.size() == 1) { // the only perm is deny everything, there is no need to display the row continue; } } entries.add(new PageSelection<String>(item, false)); } } } @Override public PageSelections<String> getDataTableModel() { if (obsoleteSecurityData) { // lazy initialization at first time access rebuildSecurityData(); } return entries; } @Override public SecurityData getSecurityData() { if (obsoleteSecurityData) { // lazy initialization at first time access rebuildSecurityData(); } return securityData; } @Override public String updateSecurityOnDocument() { List<UserEntry> modifiableEntries = SecurityDataConverter.convertToUserEntries(securityData); ACP acp = currentDocument.getACP(); if (acp == null) { acp = new ACPImpl(); } acp.setRules(modifiableEntries.toArray(new UserEntry[0])); currentDocument.setACP(acp, true); documentManager.save(); Events.instance().raiseEvent(EventNames.DOCUMENT_SECURITY_CHANGED); // Reread data from the backend to be sure the current bean // state is uptodate w.r.t. the real backend state rebuildSecurityData(); // Type currentType = typeManager.getType(currentDocument.getType()); // return applicationController.getPageOnEditedDocumentType(currentType); // Forward to default view, that's not what we want // return navigationContext.getActionResult(currentDocument, UserAction.AFTER_EDIT); // Temporary fix, to avoid forward to default_view. // The same page is reloaded after submit. // May use UserAction, with new kind of action (AFTER_EDIT_RIGHTS)? return null; } @Override public String addPermission(String principalName, String permissionName, boolean grant) { if (securityData == null) { securityData = getSecurityData(); } String grantPerm = permissionName; String denyPerm = permissionName; List<UserVisiblePermission> uvps = getVisibleUserPermissions(securityData.getDocumentType()); if (uvps != null) { for (UserVisiblePermission uvp : uvps) { if (uvp.getId().equals(permissionName)) { grantPerm = uvp.getPermission(); denyPerm = uvp.getDenyPermission(); break; } } } else { log.debug("no entry for documentType in visibleUserPermissions this should never happend, using default mapping ..."); } if (grant) { // remove the opposite rule if any boolean removed = securityData.removeModifiablePrivilege(principalName, denyPerm, !grant); if (!removed) { removed = securityData.removeModifiablePrivilege(principalName, grantPerm, !grant); } // add rule only if none was removed if (!removed) { securityData.addModifiablePrivilege(principalName, grantPerm, grant); } } else { // remove the opposite rule if any boolean removed = securityData.removeModifiablePrivilege(principalName, grantPerm, !grant); if (!removed) { removed = securityData.removeModifiablePrivilege(principalName, denyPerm, !grant); } // add rule only if none was removed if (!removed) { securityData.addModifiablePrivilege(principalName, denyPerm, grant); } } reconstructTableModel(); return null; } @Override public String addPermission() { String permissionName = permissionListManager.getSelectedPermission(); boolean grant = permissionActionListManager.getSelectedGrant().equals("Grant"); return addPermission(selectedEntry, permissionName, grant); } @Override public String addPermissions() { if (selectedEntries == null || selectedEntries.isEmpty()) { String message = ComponentUtils.translate(FacesContext.getCurrentInstance(), "error.rightsManager.noUsersSelected"); FacesMessages.instance().add(message); return null; } String permissionName = permissionListManager.getSelectedPermission(); boolean grant = permissionActionListManager.getSelectedGrant().equals("Grant"); for (String principalName : selectedEntries) { addPermission(principalName, permissionName, grant); } return null; } @Override public String addPermissionAndUpdate() { addPermission(); updateSecurityOnDocument(); return null; } @Override public String addPermissionsAndUpdate() { addPermissions(); updateSecurityOnDocument(); selectedEntries = null; facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("message.updated.rights")); return null; } @Override public String saveSecurityUpdates() { updateSecurityOnDocument(); selectedEntries = null; facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("message.updated.rights")); return null; } @Override public String removePermission() { securityData.removeModifiablePrivilege(selectedEntry, permissionListManager.getSelectedPermission(), permissionActionListManager.getSelectedGrant().equals("Grant")); reconstructTableModel(); return null; } @Override public String removePermissionAndUpdate() { removePermission(); if (!checkPermissions()) { facesMessages.add(StatusMessage.Severity.ERROR, resourcesAccessor.getMessages().get("message.updated.rights")); return null; } updateSecurityOnDocument(); // do not redirect to the default folder view return null; } @Override public String removePermissions() { for (PageSelection<String> user : getSelectedRows()) { securityData.removeModifiablePrivilege(user.getData()); if (!checkPermissions()) { facesMessages.add(StatusMessage.Severity.ERROR, resourcesAccessor.getMessages().get("message.error.removeRight")); return null; } } reconstructTableModel(); return null; } @Override public String removePermissionsAndUpdate() { for (PageSelection<String> user : getDataTableModel().getEntries()) { securityData.removeModifiablePrivilege(user.getData()); if (!checkPermissions()) { facesMessages.add(StatusMessage.Severity.ERROR, resourcesAccessor.getMessages().get("message.error.removeRight")); return null; } } updateSecurityOnDocument(); facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("message.updated.rights")); // do not redirect to the default folder view return null; } @Override public boolean getCanAddSecurityRules() { return documentManager.hasPermission(currentDocument.getRef(), "WriteSecurity"); } @Override public boolean getCanRemoveSecurityRules() { return documentManager.hasPermission(currentDocument.getRef(), "WriteSecurity") && !getSelectedRows().isEmpty(); } /** * @return The list of selected rows in the local rights table. * @since 6.0 */ private List<PageSelection<String>> getSelectedRows() { List<PageSelection<String>> selectedRows = new ArrayList<PageSelection<String>>(); if (!getDataTableModel().isEmpty()) { for (PageSelection<String> entry : getDataTableModel().getEntries()) { if (entry.isSelected()) { selectedRows.add(entry); } } } return selectedRows; } public List<UserVisiblePermission> getVisibleUserPermissions(String documentType) { return Framework.getService(PermissionProvider.class).getUserVisiblePermissionDescriptors(documentType); } @Override public List<SelectItem> getSettablePermissions() { String documentType = navigationContext.getCurrentDocument().getType(); // BBB: use the platform service if it defines permissions (deprecated) UIPermissionService service = (UIPermissionService) Framework.getRuntime().getComponent( UIPermissionService.NAME); String[] settablePermissions = service.getUIPermissions(documentType); if (settablePermissions == null || settablePermissions.length == 0) { // new centralized permission provider at the core level List<UserVisiblePermission> visiblePerms = getVisibleUserPermissions(documentType); settablePermissions = new String[visiblePerms.size()]; int idx = 0; for (UserVisiblePermission uvp : visiblePerms) { settablePermissions[idx] = uvp.getId(); idx++; } } return asSelectItems(settablePermissions); } protected List<SelectItem> asSelectItems(String... permissions) { List<SelectItem> items = new ArrayList<SelectItem>(); for (String perm : permissions) { String label = labeler.makeLabel(perm); SelectItem it = new SelectItem(perm, resourcesAccessor.getMessages().get(label)); items.add(it); } return items; } /** * @since 7.4 */ public List<SelectItem> getUserVisiblePermissionSelectItems(String documentType) { List<UserVisiblePermission> userVisiblePermissions = getVisibleUserPermissions(documentType); List<String> permissions = new ArrayList<>(); for (UserVisiblePermission userVisiblePermission : userVisiblePermissions) { permissions.add(userVisiblePermission.getId()); } return asSelectItems(permissions.toArray(new String[permissions.size()])); } @Override public Map<String, String> getIconAltMap() { return principalListManager.iconAlt; } @Override public Map<String, String> getIconPathMap() { return principalListManager.iconPath; } @Override public Boolean getBlockRightInheritance() { return blockRightInheritance; } @Override public void setBlockRightInheritance(Boolean blockRightInheritance) { this.blockRightInheritance = blockRightInheritance; } public String blockRightInheritance() { Boolean needBlockRightInheritance = blockRightInheritance; if (needBlockRightInheritance) { // Block securityData.addModifiablePrivilege(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false); // add user to avoid lock up Principal currentUser = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal(); if (securityData.getCurrentDocumentUsers() != null && !securityData.getCurrentDocumentUsers().contains(currentUser.getName())) { securityData.addModifiablePrivilege(currentUser.getName(), SecurityConstants.EVERYTHING, true); // add administrators to avoid LockUp List<String> adminGroups = userManager.getAdministratorsGroups(); for (String adminGroup : adminGroups) { securityData.addModifiablePrivilege(adminGroup, SecurityConstants.EVERYTHING, true); } } } else { securityData.removeModifiablePrivilege(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false); } updateSecurityOnDocument(); selectedEntries = null; return null; } @Override public Boolean displayInheritedPermissions() { return getDisplayInheritedPermissions(); } @Override public boolean getDisplayInheritedPermissions() { if (blockRightInheritance == null) { rebuildSecurityData(); } if (blockRightInheritance) { return false; } return !securityData.getParentDocumentsUsers().isEmpty(); } @Override public List<String> getCurrentDocumentUsers() { List<String> currentUsers = securityData.getCurrentDocumentUsers(); return validateUserGroupList(currentUsers); } @Override public List<String> getParentDocumentsUsers() { List<String> parentUsers = securityData.getParentDocumentsUsers(); return validateUserGroupList(parentUsers); } /** * Validates user/group against userManager in order to remove obsolete entries (ie: deleted groups/users). */ private List<String> validateUserGroupList(List<String> usersGroups2Validate) { // TODO : // 1 -should add a clean cache system to avoid // calling the directory : this can be problematic for big ldaps // 2 - this filtering should at some point be applied to acp and saved // back in a batch? List<String> returnList = new ArrayList<String>(); for (String entry : usersGroups2Validate) { if (entry.equals(SecurityConstants.EVERYONE)) { returnList.add(entry); continue; } if (isUserGroupInCache(entry)) { returnList.add(entry); continue; } if (isUserGroupInDeletedCache(entry)) { continue; } if (userManager.getPrincipal(entry) != null) { returnList.add(entry); addUserGroupInCache(entry); continue; } else if (userManager.getGroup(entry) != null) { returnList.add(entry); addUserGroupInCache(entry); continue; } else { addUserGroupInDeletedCache(entry); } } return returnList; } private Boolean isUserGroupInCache(String entry) { if (cachedValidatedUserAndGroups == null) { return false; } return cachedValidatedUserAndGroups.contains(entry); } private void addUserGroupInCache(String entry) { if (cachedValidatedUserAndGroups == null) { cachedValidatedUserAndGroups = new ArrayList<String>(); } cachedValidatedUserAndGroups.add(entry); } private Boolean isUserGroupInDeletedCache(String entry) { if (cachedDeletedUserAndGroups == null) { return false; } return cachedDeletedUserAndGroups.contains(entry); } private void addUserGroupInDeletedCache(String entry) { if (cachedDeletedUserAndGroups == null) { cachedDeletedUserAndGroups = new ArrayList<String>(); } cachedDeletedUserAndGroups.add(entry); } /** * Checks if the current user can still read and write access rights. If he can't, then the security data are * rebuilt. */ private boolean checkPermissions() { if (currentUser.isAdministrator()) { return true; } else { List<String> principals = new ArrayList<String>(); principals.add(currentUser.getName()); principals.addAll(currentUser.getAllGroups()); ACP acp = currentDocument.getACP(); new SecurityDataConverter(); List<UserEntry> modifiableEntries = SecurityDataConverter.convertToUserEntries(securityData); if (null == acp) { acp = new ACPImpl(); } acp.setRules(modifiableEntries.toArray(new UserEntry[0])); final boolean access = acp.getAccess(principals.toArray(new String[0]), getPermissionsToCheck()) .toBoolean(); if (!access) { rebuildSecurityData(); } return access; } } protected String[] getPermissionsToCheck() { if (CACHED_PERMISSION_TO_CHECK == null) { PermissionProvider pprovider = Framework.getService(PermissionProvider.class); List<String> aggregatedPerms = new LinkedList<String>(); for (String seedPerm : SEED_PERMISSIONS_TO_CHECK) { aggregatedPerms.add(seedPerm); String[] compoundPerms = pprovider.getPermissionGroups(seedPerm); if (compoundPerms != null) { aggregatedPerms.addAll(Arrays.asList(compoundPerms)); } } CACHED_PERMISSION_TO_CHECK = aggregatedPerms.toArray(new String[aggregatedPerms.size()]); } return CACHED_PERMISSION_TO_CHECK; } @Override public String getSelectedEntry() { return selectedEntry; } @Override public void setSelectedEntry(String selectedEntry) { this.selectedEntry = selectedEntry; } @Override public List<String> getSelectedEntries() { return selectedEntries; } @Override public void setSelectedEntries(List<String> selectedEntries) { this.selectedEntries = selectedEntries; } @Factory(value = "isMSIEorEdge", scope = ScopeType.SESSION) public boolean isMSIEorEdge() { FacesContext context = FacesContext.getCurrentInstance(); if (context != null) { HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); String ua = request.getHeader("User-Agent"); return UserAgentMatcher.isMSIE6or7(ua) || UserAgentMatcher.isMSIE10OrMore(ua) || UserAgentMatcher.isMSEdge(ua); } else { return false; } } public String getLabel(String permission) { return StringUtils.isNotBlank(permission) ? labeler.makeLabel(permission) : permission; } /** * Returns a Map containing all contributed permissions and their associated labels. * * @since 8.1 */ public Map<String, String> getPermissionsToLabels() { PermissionProvider permissionProvider = Framework.getService(PermissionProvider.class); String[] permissions = permissionProvider.getPermissions(); Map<String, String> permissionsToLabels = new HashMap<>(); for (String permission : permissions) { permissionsToLabels.put(permission, getLabel(permission)); } return permissionsToLabels; } }