001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Nuxeo - initial API and implementation 011 * 012 * $Id$ 013 */ 014 015package org.nuxeo.ecm.core.security; 016 017import java.util.HashMap; 018import java.util.HashSet; 019import java.util.LinkedList; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023import java.util.TreeSet; 024import java.util.Map.Entry; 025 026import org.apache.commons.lang.StringUtils; 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.nuxeo.ecm.core.api.NuxeoException; 030import org.nuxeo.ecm.core.api.security.UserVisiblePermission; 031 032/** 033 * @author Bogdan Stefanescu 034 * @author Olivier Grisel 035 */ 036public class DefaultPermissionProvider implements PermissionProviderLocal { 037 038 @SuppressWarnings("unused") 039 private static final Log log = LogFactory.getLog(DefaultPermissionProvider.class); 040 041 private final List<PermissionDescriptor> registeredPermissions = new LinkedList<PermissionDescriptor>(); 042 043 // to be recomputed each time a new PermissionDescriptor is registered - 044 // null means invalidated 045 private Map<String, MergedPermissionDescriptor> mergedPermissions; 046 047 private Map<String, Set<String>> mergedGroups; 048 049 private final List<PermissionVisibilityDescriptor> registeredPermissionsVisibility = new LinkedList<PermissionVisibilityDescriptor>(); 050 051 private Map<String, PermissionVisibilityDescriptor> mergedPermissionsVisibility; 052 053 public DefaultPermissionProvider() { 054 mergedPermissionsVisibility = null; 055 } 056 057 @Override 058 public synchronized List<UserVisiblePermission> getUserVisiblePermissionDescriptors(String typeName) 059 { 060 if (mergedPermissionsVisibility == null) { 061 computeMergedPermissionsVisibility(); 062 } 063 // grab the default items (type is "") 064 PermissionVisibilityDescriptor defaultVisibility = mergedPermissionsVisibility.get(typeName); 065 if (defaultVisibility == null) { 066 // fallback to default 067 defaultVisibility = mergedPermissionsVisibility.get(""); 068 } 069 if (defaultVisibility == null) { 070 throw new NuxeoException("no permission visibility configuration registered"); 071 } 072 return defaultVisibility.getSortedUIPermissionDescriptor(); 073 } 074 075 @Override 076 public List<UserVisiblePermission> getUserVisiblePermissionDescriptors() { 077 return getUserVisiblePermissionDescriptors(""); 078 } 079 080 // called synchronized 081 protected void computeMergedPermissionsVisibility() { 082 mergedPermissionsVisibility = new HashMap<String, PermissionVisibilityDescriptor>(); 083 for (PermissionVisibilityDescriptor pvd : registeredPermissionsVisibility) { 084 PermissionVisibilityDescriptor mergedPvd = mergedPermissionsVisibility.get(pvd.getTypeName()); 085 if (mergedPvd == null) { 086 mergedPvd = new PermissionVisibilityDescriptor(pvd); 087 if (!StringUtils.isEmpty(pvd.getTypeName())) { 088 PermissionVisibilityDescriptor defaultPerms = new PermissionVisibilityDescriptor( 089 mergedPermissionsVisibility.get("")); 090 defaultPerms.merge(mergedPvd); 091 mergedPvd.setPermissionUIItems(defaultPerms.getPermissionUIItems().toArray( 092 new PermissionUIItemDescriptor[] {})); 093 } 094 mergedPermissionsVisibility.put(mergedPvd.getTypeName(), mergedPvd); 095 } else { 096 mergedPvd.merge(pvd); 097 } 098 } 099 } 100 101 @Override 102 public synchronized String[] getSubPermissions(String perm) { 103 List<String> permissions = getPermission(perm).getSubPermissions(); 104 return permissions.toArray(new String[permissions.size()]); 105 } 106 107 @Override 108 public synchronized String[] getAliasPermissions(String perm) { 109 List<String> permissions = getPermission(perm).getSubPermissions(); 110 return permissions.toArray(new String[permissions.size()]); 111 } 112 113 // called synchronized 114 protected MergedPermissionDescriptor getPermission(String perm) { 115 if (mergedPermissions == null) { 116 computeMergedPermissions(); 117 } 118 MergedPermissionDescriptor mpd = mergedPermissions.get(perm); 119 if (mpd == null) { 120 throw new NuxeoException(perm + " is not a registered permission"); 121 } 122 return mpd; 123 } 124 125 // OG: this is an awkward method prototype left unchanged for BBB 126 @Override 127 public synchronized String[] getPermissionGroups(String perm) { 128 if (mergedGroups == null) { 129 computeMergedGroups(); 130 } 131 Set<String> groups = mergedGroups.get(perm); 132 if (groups != null && !groups.isEmpty()) { 133 // OG: why return null instead of an empty array 134 return groups.toArray(new String[groups.size()]); 135 } 136 return null; 137 } 138 139 // called synchronized 140 protected void computeMergedGroups() { 141 if (mergedPermissions == null) { 142 computeMergedPermissions(); 143 } 144 mergedGroups = new HashMap<String, Set<String>>(); 145 146 // scanning sub permissions to collect direct group membership 147 for (MergedPermissionDescriptor mpd : mergedPermissions.values()) { 148 for (String subPermission : mpd.getSubPermissions()) { 149 Set<String> groups = mergedGroups.get(subPermission); 150 if (groups == null) { 151 groups = new TreeSet<String>(); 152 groups.add(mpd.getName()); 153 mergedGroups.put(subPermission, groups); 154 } else { 155 if (!groups.contains(mpd.getName())) { 156 groups.add(mpd.getName()); 157 } 158 } 159 } 160 } 161 162 // building the transitive closure on groups membership with a recursive 163 // method 164 Set<String> alreadyProcessed = new HashSet<String>(); 165 for (Entry<String, Set<String>> groupEntry : mergedGroups.entrySet()) { 166 String permissionName = groupEntry.getKey(); 167 Set<String> groups = groupEntry.getValue(); 168 Set<String> allGroups = computeAllGroups(permissionName, alreadyProcessed); 169 groups.addAll(allGroups); 170 } 171 } 172 173 // called synchronized 174 protected Set<String> computeAllGroups(String permissionName, Set<String> alreadyProcessed) { 175 Set<String> allGroups = mergedGroups.get(permissionName); 176 if (allGroups == null) { 177 allGroups = new TreeSet<String>(); 178 } 179 if (alreadyProcessed.contains(permissionName)) { 180 return allGroups; 181 } else { 182 // marking it processed early to avoid infinite loops in case of 183 // recursive inclusion 184 alreadyProcessed.add(permissionName); 185 for (String directGroupName : new TreeSet<String>(allGroups)) { 186 allGroups.addAll(computeAllGroups(directGroupName, alreadyProcessed)); 187 } 188 return allGroups; 189 } 190 } 191 192 // OG: this is an awkward method prototype left unchanged for BBB 193 @Override 194 public synchronized String[] getPermissions() { 195 if (mergedPermissions == null) { 196 computeMergedPermissions(); 197 } 198 // TODO OG: should we add aliased permissions here as well? 199 return mergedPermissions.keySet().toArray(new String[mergedPermissions.size()]); 200 } 201 202 // called synchronized 203 protected void computeMergedPermissions() { 204 mergedPermissions = new HashMap<String, MergedPermissionDescriptor>(); 205 for (PermissionDescriptor pd : registeredPermissions) { 206 MergedPermissionDescriptor mpd = mergedPermissions.get(pd.getName()); 207 if (mpd == null) { 208 mpd = new MergedPermissionDescriptor(pd); 209 mergedPermissions.put(mpd.getName(), mpd); 210 } else { 211 mpd.mergeDescriptor(pd); 212 } 213 } 214 } 215 216 @Override 217 public synchronized void registerDescriptor(PermissionDescriptor descriptor) { 218 // check that all included permission have previously been registered 219 Set<String> alreadyRegistered = new HashSet<String>(); 220 for (PermissionDescriptor registeredPerm : registeredPermissions) { 221 alreadyRegistered.add(registeredPerm.getName()); 222 } 223 for (String includePerm : descriptor.getIncludePermissions()) { 224 if (!alreadyRegistered.contains(includePerm)) { 225 throw new NuxeoException(String.format( 226 "Permission '%s' included by '%s' is not a registered permission", includePerm, 227 descriptor.getName())); 228 } 229 } 230 // invalidate merged permission 231 mergedPermissions = null; 232 mergedGroups = null; 233 // append the new descriptor 234 registeredPermissions.add(descriptor); 235 } 236 237 @Override 238 public synchronized void unregisterDescriptor(PermissionDescriptor descriptor) { 239 int lastOccurence = registeredPermissions.lastIndexOf(descriptor); 240 if (lastOccurence != -1) { 241 // invalidate merged permission 242 mergedPermissions = null; 243 mergedGroups = null; 244 // remove the last occurrence of the descriptor 245 registeredPermissions.remove(lastOccurence); 246 } 247 } 248 249 @Override 250 public synchronized void registerDescriptor(PermissionVisibilityDescriptor descriptor) { 251 // invalidate cached merged descriptors 252 mergedPermissionsVisibility = null; 253 registeredPermissionsVisibility.add(descriptor); 254 } 255 256 @Override 257 public synchronized void unregisterDescriptor(PermissionVisibilityDescriptor descriptor) { 258 int lastOccurence = registeredPermissionsVisibility.lastIndexOf(descriptor); 259 if (lastOccurence != -1) { 260 // invalidate merged descriptors 261 mergedPermissionsVisibility = null; 262 // remove the last occurrence of the descriptor 263 registeredPermissionsVisibility.remove(lastOccurence); 264 } 265 } 266 267}