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