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