001/* 002 * (C) Copyright 2006-2014 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 * Anahide Tchertchian 018 * 019 */ 020 021package org.nuxeo.ecm.directory; 022 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.common.collections.ScopeType; 035import org.nuxeo.common.collections.ScopedMap; 036import org.nuxeo.ecm.core.api.DataModel; 037import org.nuxeo.ecm.core.api.DocumentModel; 038import org.nuxeo.ecm.core.api.DocumentModelList; 039import org.nuxeo.ecm.core.api.NuxeoPrincipal; 040import org.nuxeo.ecm.core.api.PropertyException; 041import org.nuxeo.ecm.core.api.impl.DataModelImpl; 042import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; 043import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 044import org.nuxeo.ecm.core.api.local.ClientLoginModule; 045import org.nuxeo.ecm.core.api.security.SecurityConstants; 046import org.nuxeo.runtime.api.login.LoginComponent; 047 048/** 049 * Base session class with helper methods common to all kinds of directory sessions. 050 * 051 * @author Anahide Tchertchian 052 * @since 5.2M4 053 */ 054public abstract class BaseSession implements Session { 055 056 protected static final String POWER_USERS_GROUP = "powerusers"; 057 058 protected static final String READONLY_ENTRY_FLAG = "READONLY_ENTRY"; 059 060 protected static final String MULTI_TENANT_ID_FORMAT = "tenant_%s_%s"; 061 062 private final static Log log = LogFactory.getLog(BaseSession.class); 063 064 protected final Directory directory; 065 066 protected PermissionDescriptor[] permissions = null; 067 068 protected BaseSession(Directory directory) { 069 this.directory = directory; 070 } 071 072 /** To be implemented with a more specific return type. */ 073 public abstract Directory getDirectory(); 074 075 @Override 076 public String getIdField() { 077 return directory.getIdField(); 078 } 079 080 @Override 081 public String getPasswordField() { 082 return directory.getPasswordField(); 083 } 084 085 @Override 086 public boolean isAuthenticating() { 087 return directory.getPasswordField() != null; 088 } 089 090 @Override 091 public boolean isReadOnly() { 092 return directory.isReadOnly(); 093 } 094 095 /** 096 * Check the current user rights for the given permission against the permission descriptor 097 * 098 * @return true if the user 099 * @since 6.0 100 */ 101 public boolean isCurrentUserAllowed(String permissionTocheck) { 102 PermissionDescriptor[] permDescriptors = permissions; 103 NuxeoPrincipal currentUser = ClientLoginModule.getCurrentPrincipal(); 104 105 if (currentUser == null) { 106 if (log.isDebugEnabled()) { 107 log.debug("Can't get current user to check directory permission. EVERYTHING is allowed by default"); 108 } 109 return true; 110 } 111 String username = currentUser.getName(); 112 List<String> userGroups = currentUser.getAllGroups(); 113 114 if (username.equalsIgnoreCase(LoginComponent.SYSTEM_USERNAME)) { 115 return true; 116 } 117 118 if (permDescriptors == null || permDescriptors.length == 0) { 119 if (currentUser.isAdministrator()) { 120 // By default if nothing is specified, admin is allowed 121 return true; 122 } 123 if (currentUser.isMemberOf(POWER_USERS_GROUP)) { 124 return true; 125 } 126 127 // Return true for read access to anyone when nothing defined 128 if (permissionTocheck.equalsIgnoreCase(SecurityConstants.READ)) { 129 return true; 130 } 131 132 if (log.isDebugEnabled()) { 133 log.debug(String.format("User '%s', is not allowed for permission '%s' on the directory", currentUser, 134 permissionTocheck)); 135 } 136 // Deny in all other case 137 return false; 138 } 139 boolean allowed = checkPermission(permDescriptors, permissionTocheck, username, userGroups); 140 if (allowed != true) { 141 // If the permission has not been found and if the permission to 142 // check is read 143 // Then try to check if the current user is allowed, because having 144 // write access include read 145 if (permissionTocheck.equalsIgnoreCase(SecurityConstants.READ)) { 146 allowed = checkPermission(permDescriptors, SecurityConstants.WRITE, username, userGroups); 147 } 148 } 149 if (log.isDebugEnabled() && !allowed) { 150 log.debug(String.format("User '%s', is not allowed for permission '%s' on the directory", currentUser, 151 permissionTocheck)); 152 } 153 return allowed; 154 155 } 156 157 private boolean checkPermission(PermissionDescriptor permDescriptors[], String permToChek, String username, 158 List<String> userGroups) { 159 List<String> groups = new ArrayList<String>(userGroups); 160 groups.add(SecurityConstants.EVERYONE); 161 for (int i = 0; i < permDescriptors.length; i++) { 162 PermissionDescriptor currentDesc = permDescriptors[i]; 163 if (currentDesc.name.equalsIgnoreCase(permToChek)) { 164 if (currentDesc.groups != null) { 165 for (int j = 0; j < currentDesc.groups.length; j++) { 166 String groupName = currentDesc.groups[j]; 167 if (groups.contains(groupName)) { 168 return true; 169 } 170 } 171 } 172 173 if (currentDesc.users != null) { 174 for (int j = 0; j < currentDesc.users.length; j++) { 175 String currentUsername = currentDesc.users[j]; 176 if (currentUsername.equals(username)) { 177 return true; 178 } 179 } 180 } 181 } 182 } 183 return false; 184 } 185 186 /** 187 * Returns a bare document model suitable for directory implementations. 188 * <p> 189 * Can be used for creation screen. 190 * 191 * @since 5.2M4 192 */ 193 public static DocumentModel createEntryModel(String sessionId, String schema, String id, Map<String, Object> values) 194 throws PropertyException { 195 DocumentModelImpl entry = new DocumentModelImpl(sessionId, schema, id, null, null, null, null, 196 new String[] { schema }, new HashSet<String>(), null, null); 197 DataModel dataModel; 198 if (values == null) { 199 values = Collections.emptyMap(); 200 } 201 dataModel = new DataModelImpl(schema, values); 202 entry.addDataModel(dataModel); 203 return entry; 204 } 205 206 /** 207 * Returns a bare document model suitable for directory implementations. 208 * <p> 209 * Allow setting the readonly entry flag to {@code Boolean.TRUE}. See {@code Session#isReadOnlyEntry(DocumentModel)} 210 * 211 * @since 5.3.1 212 */ 213 public static DocumentModel createEntryModel(String sessionId, String schema, String id, 214 Map<String, Object> values, boolean readOnly) throws PropertyException { 215 DocumentModel entry = createEntryModel(sessionId, schema, id, values); 216 if (readOnly) { 217 setReadOnlyEntry(entry); 218 } 219 return entry; 220 } 221 222 protected static Map<String, Serializable> mkSerializableMap(Map<String, Object> map) { 223 Map<String, Serializable> serializableMap = null; 224 if (map != null) { 225 serializableMap = new HashMap<String, Serializable>(); 226 for (String key : map.keySet()) { 227 serializableMap.put(key, (Serializable) map.get(key)); 228 } 229 } 230 return serializableMap; 231 } 232 233 protected static Map<String, Object> mkObjectMap(Map<String, Serializable> map) { 234 Map<String, Object> objectMap = null; 235 if (map != null) { 236 objectMap = new HashMap<String, Object>(); 237 for (String key : map.keySet()) { 238 objectMap.put(key, map.get(key)); 239 } 240 } 241 return objectMap; 242 } 243 244 /** 245 * Test whether entry comes from a read-only back-end directory. 246 * 247 * @since 5.3.1 248 */ 249 public static boolean isReadOnlyEntry(DocumentModel entry) { 250 ScopedMap contextData = entry.getContextData(); 251 return contextData.getScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG) == Boolean.TRUE; 252 } 253 254 /** 255 * Set the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance. 256 * 257 * @since 5.3.2 258 */ 259 public static void setReadOnlyEntry(DocumentModel entry) { 260 ScopedMap contextData = entry.getContextData(); 261 contextData.putScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG, Boolean.TRUE); 262 } 263 264 /** 265 * Unset the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance. 266 * 267 * @since 5.3.2 268 */ 269 public static void setReadWriteEntry(DocumentModel entry) { 270 ScopedMap contextData = entry.getContextData(); 271 contextData.putScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG, Boolean.FALSE); 272 } 273 274 /** 275 * Compute a multi tenant directory id based on the given {@code tenantId}. 276 * 277 * @return the computed directory id 278 * @since 5.6 279 */ 280 public static String computeMultiTenantDirectoryId(String tenantId, String id) { 281 return String.format(MULTI_TENANT_ID_FORMAT, tenantId, id); 282 } 283 284 @Override 285 public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, 286 boolean fetchReferences, int limit, int offset) throws DirectoryException { 287 log.info("Call an unoverrided query with offset and limit."); 288 DocumentModelList entries = query(filter, fulltext, orderBy, fetchReferences); 289 int toIndex = offset + limit; 290 if (toIndex > entries.size()) { 291 toIndex = entries.size(); 292 } 293 294 return new DocumentModelListImpl(entries.subList(offset, toIndex)); 295 } 296 297}