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