/* * (C) Copyright 2006-2011 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.core.security; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.Map.Entry; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.security.UserVisiblePermission; /** * @author Bogdan Stefanescu * @author Olivier Grisel */ public class DefaultPermissionProvider implements PermissionProviderLocal { @SuppressWarnings("unused") private static final Log log = LogFactory.getLog(DefaultPermissionProvider.class); private final List<PermissionDescriptor> registeredPermissions = new LinkedList<PermissionDescriptor>(); // to be recomputed each time a new PermissionDescriptor is registered - // null means invalidated private Map<String, MergedPermissionDescriptor> mergedPermissions; private Map<String, Set<String>> mergedGroups; private final List<PermissionVisibilityDescriptor> registeredPermissionsVisibility = new LinkedList<PermissionVisibilityDescriptor>(); private Map<String, PermissionVisibilityDescriptor> mergedPermissionsVisibility; public DefaultPermissionProvider() { mergedPermissionsVisibility = null; } @Override public synchronized List<UserVisiblePermission> getUserVisiblePermissionDescriptors(String typeName) { if (mergedPermissionsVisibility == null) { computeMergedPermissionsVisibility(); } // grab the default items (type is "") PermissionVisibilityDescriptor defaultVisibility = mergedPermissionsVisibility.get(typeName); if (defaultVisibility == null) { // fallback to default defaultVisibility = mergedPermissionsVisibility.get(""); } if (defaultVisibility == null) { throw new NuxeoException("no permission visibility configuration registered"); } return defaultVisibility.getSortedUIPermissionDescriptor(); } @Override public List<UserVisiblePermission> getUserVisiblePermissionDescriptors() { return getUserVisiblePermissionDescriptors(""); } // called synchronized protected void computeMergedPermissionsVisibility() { mergedPermissionsVisibility = new HashMap<String, PermissionVisibilityDescriptor>(); for (PermissionVisibilityDescriptor pvd : registeredPermissionsVisibility) { PermissionVisibilityDescriptor mergedPvd = mergedPermissionsVisibility.get(pvd.getTypeName()); if (mergedPvd == null) { mergedPvd = new PermissionVisibilityDescriptor(pvd); if (!StringUtils.isEmpty(pvd.getTypeName())) { PermissionVisibilityDescriptor defaultPerms = new PermissionVisibilityDescriptor( mergedPermissionsVisibility.get("")); defaultPerms.merge(mergedPvd); mergedPvd.setPermissionUIItems(defaultPerms.getPermissionUIItems().toArray( new PermissionUIItemDescriptor[] {})); } mergedPermissionsVisibility.put(mergedPvd.getTypeName(), mergedPvd); } else { mergedPvd.merge(pvd); } } } @Override public synchronized String[] getSubPermissions(String perm) { List<String> permissions = getPermission(perm).getSubPermissions(); return permissions.toArray(new String[permissions.size()]); } @Override public synchronized String[] getAliasPermissions(String perm) { List<String> permissions = getPermission(perm).getSubPermissions(); return permissions.toArray(new String[permissions.size()]); } // called synchronized protected MergedPermissionDescriptor getPermission(String perm) { if (mergedPermissions == null) { computeMergedPermissions(); } MergedPermissionDescriptor mpd = mergedPermissions.get(perm); if (mpd == null) { throw new NuxeoException(perm + " is not a registered permission"); } return mpd; } // OG: this is an awkward method prototype left unchanged for BBB @Override public synchronized String[] getPermissionGroups(String perm) { if (mergedGroups == null) { computeMergedGroups(); } Set<String> groups = mergedGroups.get(perm); if (groups != null && !groups.isEmpty()) { // OG: why return null instead of an empty array return groups.toArray(new String[groups.size()]); } return null; } // called synchronized protected void computeMergedGroups() { if (mergedPermissions == null) { computeMergedPermissions(); } mergedGroups = new HashMap<String, Set<String>>(); // scanning sub permissions to collect direct group membership for (MergedPermissionDescriptor mpd : mergedPermissions.values()) { for (String subPermission : mpd.getSubPermissions()) { Set<String> groups = mergedGroups.get(subPermission); if (groups == null) { groups = new TreeSet<String>(); groups.add(mpd.getName()); mergedGroups.put(subPermission, groups); } else { if (!groups.contains(mpd.getName())) { groups.add(mpd.getName()); } } } } // building the transitive closure on groups membership with a recursive // method Set<String> alreadyProcessed = new HashSet<String>(); for (Entry<String, Set<String>> groupEntry : mergedGroups.entrySet()) { String permissionName = groupEntry.getKey(); Set<String> groups = groupEntry.getValue(); Set<String> allGroups = computeAllGroups(permissionName, alreadyProcessed); groups.addAll(allGroups); } } // called synchronized protected Set<String> computeAllGroups(String permissionName, Set<String> alreadyProcessed) { Set<String> allGroups = mergedGroups.get(permissionName); if (allGroups == null) { allGroups = new TreeSet<String>(); } if (alreadyProcessed.contains(permissionName)) { return allGroups; } else { // marking it processed early to avoid infinite loops in case of // recursive inclusion alreadyProcessed.add(permissionName); for (String directGroupName : new TreeSet<String>(allGroups)) { allGroups.addAll(computeAllGroups(directGroupName, alreadyProcessed)); } return allGroups; } } // OG: this is an awkward method prototype left unchanged for BBB @Override public synchronized String[] getPermissions() { if (mergedPermissions == null) { computeMergedPermissions(); } // TODO OG: should we add aliased permissions here as well? return mergedPermissions.keySet().toArray(new String[mergedPermissions.size()]); } // called synchronized protected void computeMergedPermissions() { mergedPermissions = new HashMap<String, MergedPermissionDescriptor>(); for (PermissionDescriptor pd : registeredPermissions) { MergedPermissionDescriptor mpd = mergedPermissions.get(pd.getName()); if (mpd == null) { mpd = new MergedPermissionDescriptor(pd); mergedPermissions.put(mpd.getName(), mpd); } else { mpd.mergeDescriptor(pd); } } } @Override public synchronized void registerDescriptor(PermissionDescriptor descriptor) { // check that all included permission have previously been registered Set<String> alreadyRegistered = new HashSet<String>(); for (PermissionDescriptor registeredPerm : registeredPermissions) { alreadyRegistered.add(registeredPerm.getName()); } for (String includePerm : descriptor.getIncludePermissions()) { if (!alreadyRegistered.contains(includePerm)) { throw new NuxeoException(String.format( "Permission '%s' included by '%s' is not a registered permission", includePerm, descriptor.getName())); } } // invalidate merged permission mergedPermissions = null; mergedGroups = null; // append the new descriptor registeredPermissions.add(descriptor); } @Override public synchronized void unregisterDescriptor(PermissionDescriptor descriptor) { int lastOccurence = registeredPermissions.lastIndexOf(descriptor); if (lastOccurence != -1) { // invalidate merged permission mergedPermissions = null; mergedGroups = null; // remove the last occurrence of the descriptor registeredPermissions.remove(lastOccurence); } } @Override public synchronized void registerDescriptor(PermissionVisibilityDescriptor descriptor) { // invalidate cached merged descriptors mergedPermissionsVisibility = null; registeredPermissionsVisibility.add(descriptor); } @Override public synchronized void unregisterDescriptor(PermissionVisibilityDescriptor descriptor) { int lastOccurence = registeredPermissionsVisibility.lastIndexOf(descriptor); if (lastOccurence != -1) { // invalidate merged descriptors mergedPermissionsVisibility = null; // remove the last occurrence of the descriptor registeredPermissionsVisibility.remove(lastOccurence); } } }