001/* 002 * (C) Copyright 2006-2014 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 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 * Bogdan Stefanescu 016 * George Lefter 017 * Stéfane Fermigier 018 * Julien Carsique 019 * Anahide Tchertchian 020 * Alexandre Russel 021 * Thierry Delprat 022 * Stéphane Lacoin 023 * Sun Seng David Tan 024 * Thomas Roger 025 * Thierry Martins 026 * Benoit Delbosc 027 * Florent Guillaume 028 */ 029package org.nuxeo.ecm.platform.usermanager; 030 031import java.security.Principal; 032import java.util.ArrayList; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.LinkedList; 036import java.util.List; 037import java.util.Set; 038 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.nuxeo.ecm.core.api.DataModel; 042import org.nuxeo.ecm.core.api.DocumentModel; 043import org.nuxeo.ecm.core.api.NuxeoGroup; 044import org.nuxeo.ecm.core.api.NuxeoPrincipal; 045import org.nuxeo.ecm.core.api.PropertyException; 046import org.nuxeo.ecm.core.api.impl.DataModelImpl; 047import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; 048import org.nuxeo.ecm.core.api.security.SecurityConstants; 049import org.nuxeo.ecm.directory.DirectoryException; 050import org.nuxeo.runtime.api.Framework; 051 052public class NuxeoPrincipalImpl implements NuxeoPrincipal { 053 054 private static final long serialVersionUID = 1L; 055 056 private static final Log log = LogFactory.getLog(NuxeoPrincipalImpl.class); 057 058 protected UserConfig config = UserConfig.DEFAULT; 059 060 public final List<String> roles = new LinkedList<String>(); 061 062 // group not stored in the backend and added at login time 063 public List<String> virtualGroups = new LinkedList<String>(); 064 065 // transitive closure of the "member of group" relation 066 public List<String> allGroups; 067 068 public final boolean isAnonymous; 069 070 public boolean isAdministrator; 071 072 public String principalId; 073 074 public DocumentModel model; 075 076 public DataModel dataModel; 077 078 public String origUserName; 079 080 /** 081 * Constructor that sets principal to not anonymous, not administrator, and updates all the principal groups. 082 */ 083 public NuxeoPrincipalImpl(String name) { 084 this(name, false, false); 085 } 086 087 /** 088 * Constructor that sets principal to not administrator, and updates all the principal groups. 089 */ 090 public NuxeoPrincipalImpl(String name, boolean isAnonymous) { 091 this(name, isAnonymous, false); 092 } 093 094 /** 095 * Constructor that updates all the principal groups. 096 */ 097 public NuxeoPrincipalImpl(String name, boolean isAnonymous, boolean isAdministrator) { 098 this(name, isAnonymous, isAdministrator, true); 099 } 100 101 public NuxeoPrincipalImpl(String name, boolean isAnonymous, boolean isAdministrator, boolean updateAllGroups) 102 { 103 DocumentModelImpl documentModelImpl = new DocumentModelImpl(config.schemaName); 104 // schema name hardcoded default when setModel is never called 105 // which happens when a principal is created just to encapsulate 106 // a username 107 documentModelImpl.addDataModel(new DataModelImpl(config.schemaName, new HashMap<String, Object>())); 108 setModel(documentModelImpl, updateAllGroups); 109 dataModel.setData(config.nameKey, name); 110 this.isAnonymous = isAnonymous; 111 this.isAdministrator = isAdministrator; 112 } 113 114 public void setConfig(UserConfig config) { 115 this.config = config; 116 } 117 118 public UserConfig getConfig() { 119 return config; 120 } 121 122 @Override 123 public String getCompany() { 124 try { 125 return (String) dataModel.getData(config.companyKey); 126 } catch (PropertyException e) { 127 return null; 128 } 129 } 130 131 @Override 132 public void setCompany(String company) { 133 dataModel.setData(config.companyKey, company); 134 } 135 136 @Override 137 public String getFirstName() { 138 try { 139 return (String) dataModel.getData(config.firstNameKey); 140 } catch (PropertyException e) { 141 return null; 142 } 143 } 144 145 @Override 146 public void setFirstName(String firstName) { 147 dataModel.setData(config.firstNameKey, firstName); 148 } 149 150 @Override 151 public String getLastName() { 152 try { 153 return (String) dataModel.getData(config.lastNameKey); 154 } catch (PropertyException e) { 155 return null; 156 } 157 } 158 159 @Override 160 public void setLastName(String lastName) { 161 dataModel.setData(config.lastNameKey, lastName); 162 } 163 164 // impossible to modify the name - it is PK 165 @Override 166 public void setName(String name) { 167 dataModel.setData(config.nameKey, name); 168 } 169 170 @Override 171 public void setRoles(List<String> roles) { 172 this.roles.clear(); 173 this.roles.addAll(roles); 174 } 175 176 @Override 177 public void setGroups(List<String> groups) { 178 if (virtualGroups != null && !virtualGroups.isEmpty()) { 179 List<String> groupsToWrite = new ArrayList<String>(); 180 for (String group : groups) { 181 if (!virtualGroups.contains(group)) { 182 groupsToWrite.add(group); 183 } 184 } 185 dataModel.setData(config.groupsKey, groupsToWrite); 186 } else { 187 dataModel.setData(config.groupsKey, groups); 188 } 189 } 190 191 @Override 192 public String getName() { 193 try { 194 return (String) dataModel.getData(config.nameKey); 195 } catch (PropertyException e) { 196 return null; 197 } 198 } 199 200 @SuppressWarnings("unchecked") 201 @Override 202 public List<String> getGroups() { 203 List<String> groups = new LinkedList<String>(); 204 List<String> storedGroups; 205 try { 206 storedGroups = (List<String>) dataModel.getData(config.groupsKey); 207 } catch (PropertyException e) { 208 return null; 209 } 210 if (storedGroups != null) { 211 groups.addAll(storedGroups); 212 } 213 groups.addAll(virtualGroups); 214 return groups; 215 } 216 217 @Deprecated 218 @Override 219 public List<String> getRoles() { 220 return new ArrayList<String>(roles); 221 } 222 223 @Override 224 public void setPassword(String password) { 225 dataModel.setData(config.passwordKey, password); 226 } 227 228 @Override 229 public String getPassword() { 230 // password should never be read at the UI level for safety reasons 231 // + backend directories usually only store hashes that are useless 232 // except to check authentication at the directory level 233 return null; 234 } 235 236 @Override 237 public String toString() { 238 return (String) dataModel.getData(config.nameKey); 239 } 240 241 @Override 242 public String getPrincipalId() { 243 return principalId; 244 } 245 246 @Override 247 public void setPrincipalId(String principalId) { 248 this.principalId = principalId; 249 } 250 251 @Override 252 public String getEmail() { 253 try { 254 return (String) dataModel.getData(config.emailKey); 255 } catch (PropertyException e) { 256 return null; 257 } 258 } 259 260 @Override 261 public void setEmail(String email) { 262 dataModel.setData(config.emailKey, email); 263 } 264 265 @Override 266 public DocumentModel getModel() { 267 return model; 268 } 269 270 /** 271 * Sets model and recomputes all groups. 272 */ 273 public void setModel(DocumentModel model, boolean updateAllGroups) { 274 this.model = model; 275 dataModel = model.getDataModels().values().iterator().next(); 276 if (updateAllGroups) { 277 updateAllGroups(); 278 } 279 } 280 281 @Override 282 public void setModel(DocumentModel model) { 283 setModel(model, true); 284 } 285 286 @Override 287 public boolean isMemberOf(String group) { 288 return allGroups.contains(group); 289 } 290 291 @Override 292 public List<String> getAllGroups() { 293 return new ArrayList<String>(allGroups); 294 } 295 296 public void updateAllGroups() { 297 UserManager userManager = Framework.getService(UserManager.class); 298 Set<String> checkedGroups = new HashSet<String>(); 299 List<String> groupsToProcess = new ArrayList<String>(); 300 List<String> resultingGroups = new ArrayList<String>(); 301 groupsToProcess.addAll(getGroups()); 302 303 while (!groupsToProcess.isEmpty()) { 304 String groupName = groupsToProcess.remove(0); 305 if (!checkedGroups.contains(groupName)) { 306 checkedGroups.add(groupName); 307 NuxeoGroup nxGroup = null; 308 if (userManager != null) { 309 try { 310 nxGroup = userManager.getGroup(groupName); 311 } catch (DirectoryException de) { 312 if (virtualGroups.contains(groupName)) { 313 // do not fail while retrieving a virtual group 314 log.warn("Failed to get group '" + groupName + "' due to '" + de.getMessage() 315 + "': permission resolution involving groups may not be correct"); 316 nxGroup = null; 317 } else { 318 throw de; 319 } 320 } 321 } 322 if (nxGroup == null) { 323 if (virtualGroups.contains(groupName)) { 324 // just add the virtual group as is 325 resultingGroups.add(groupName); 326 } else if (userManager != null) { 327 // XXX this should only happens in case of 328 // inconsistency in DB 329 log.error("User " + getName() + " references the " + groupName + " group that does not exists"); 330 } 331 } else { 332 groupsToProcess.addAll(nxGroup.getParentGroups()); 333 // fetch the group name from the returned entry in case 334 // it does not have the same case than the actual entry in 335 // directory (for case insensitive directories) 336 resultingGroups.add(nxGroup.getName()); 337 // XXX: maybe remove group from virtual groups if it 338 // actually exists? otherwise it would be ignored when 339 // setting groups 340 } 341 } 342 } 343 344 allGroups = new ArrayList<String>(resultingGroups); 345 346 // set isAdministrator boolean according to groups declared on user 347 // manager 348 if (!isAdministrator() && userManager != null) { 349 List<String> adminGroups = userManager.getAdministratorsGroups(); 350 for (String adminGroup : adminGroups) { 351 if (allGroups.contains(adminGroup)) { 352 isAdministrator = true; 353 break; 354 } 355 } 356 } 357 } 358 359 public List<String> getVirtualGroups() { 360 return new ArrayList<String>(virtualGroups); 361 } 362 363 public void setVirtualGroups(List<String> virtualGroups, boolean updateAllGroups) { 364 this.virtualGroups = new ArrayList<String>(virtualGroups); 365 if (updateAllGroups) { 366 updateAllGroups(); 367 } 368 } 369 370 /** 371 * Sets virtual groups and recomputes all groups. 372 */ 373 public void setVirtualGroups(List<String> virtualGroups) { 374 setVirtualGroups(virtualGroups, true); 375 } 376 377 @Override 378 public boolean isAdministrator() { 379 return isAdministrator || SecurityConstants.SYSTEM_USERNAME.equals(getName()); 380 } 381 382 @Override 383 public String getTenantId() { 384 return null; 385 } 386 387 @Override 388 public boolean isAnonymous() { 389 return isAnonymous; 390 } 391 392 @Override 393 public boolean equals(Object other) { 394 if (other instanceof Principal) { 395 String name = getName(); 396 String otherName = ((Principal) other).getName(); 397 if (name == null) { 398 return otherName == null; 399 } else { 400 return name.equals(otherName); 401 } 402 } else { 403 return false; 404 } 405 } 406 407 @Override 408 public int hashCode() { 409 String name = getName(); 410 return name == null ? 0 : name.hashCode(); 411 } 412 413 @Override 414 public String getOriginatingUser() { 415 return origUserName; 416 } 417 418 @Override 419 public void setOriginatingUser(String originatingUser) { 420 origUserName = originatingUser; 421 } 422 423 @Override 424 public String getActingUser() { 425 return getOriginatingUser() == null ? getName() : getOriginatingUser(); 426 } 427 428}