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