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 * Nuxeo - initial API and implementation 018 * 019 * $Id$ 020 */ 021package org.nuxeo.ecm.platform.userworkspace.core.service; 022 023import java.io.Serializable; 024import java.security.MessageDigest; 025import java.security.NoSuchAlgorithmException; 026import java.security.Principal; 027import java.util.HashMap; 028import java.util.Map; 029 030import org.apache.commons.codec.binary.Hex; 031import org.apache.commons.lang.StringUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.common.utils.IdUtils; 035import org.nuxeo.common.utils.Path; 036import org.nuxeo.ecm.core.api.CoreSession; 037import org.nuxeo.ecm.core.api.DocumentModel; 038import org.nuxeo.ecm.core.api.DocumentModelList; 039import org.nuxeo.ecm.core.api.DocumentNotFoundException; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.api.NuxeoPrincipal; 042import org.nuxeo.ecm.core.api.PathRef; 043import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 044import org.nuxeo.ecm.core.api.event.CoreEventConstants; 045import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 046import org.nuxeo.ecm.core.api.security.SecurityConstants; 047import org.nuxeo.ecm.core.event.Event; 048import org.nuxeo.ecm.core.event.EventContext; 049import org.nuxeo.ecm.core.event.EventProducer; 050import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 051import org.nuxeo.ecm.core.event.impl.EventContextImpl; 052import org.nuxeo.ecm.platform.usermanager.UserAdapter; 053import org.nuxeo.ecm.platform.usermanager.UserManager; 054import org.nuxeo.ecm.platform.userworkspace.api.UserWorkspaceService; 055import org.nuxeo.ecm.platform.userworkspace.constants.UserWorkspaceConstants; 056import org.nuxeo.runtime.api.Framework; 057 058/** 059 * Abstract class holding most of the logic for using {@link UnrestrictedSessionRunner} while creating UserWorkspaces 060 * and associated resources 061 * 062 * @author tiry 063 * @since 5.9.5 064 */ 065public abstract class AbstractUserWorkspaceImpl implements UserWorkspaceService { 066 067 private static final Log log = LogFactory.getLog(DefaultUserWorkspaceServiceImpl.class); 068 069 private static final long serialVersionUID = 1L; 070 071 protected String targetDomainName; 072 073 protected final int maxsize; 074 075 public AbstractUserWorkspaceImpl() { 076 super(); 077 maxsize = Framework.getService(PathSegmentService.class) 078 .getMaxSize(); 079 } 080 081 protected String getDomainName(CoreSession userCoreSession, DocumentModel currentDocument) { 082 if (targetDomainName == null) { 083 RootDomainFinder finder = new RootDomainFinder(userCoreSession); 084 finder.runUnrestricted(); 085 targetDomainName = finder.domaineName; 086 } 087 return targetDomainName; 088 } 089 090 protected String getUserWorkspaceNameForUser(String username) { 091 return IdUtils.generateId(username, "-", false, maxsize); 092 } 093 094 protected String digest(String username, int maxsize) { 095 try { 096 MessageDigest crypt = MessageDigest.getInstance("SHA-1"); 097 crypt.update(username.getBytes()); 098 return new String(Hex.encodeHex(crypt.digest())).substring(0, maxsize); 099 } catch (NoSuchAlgorithmException cause) { 100 throw new NuxeoException("Cannot digest " + username, cause); 101 } 102 } 103 104 protected String computePathUserWorkspaceRoot(CoreSession userCoreSession, String usedUsername, 105 DocumentModel currentDocument) { 106 String domainName = getDomainName(userCoreSession, currentDocument); 107 if (domainName == null) { 108 throw new NuxeoException("Unable to find root domain for UserWorkspace"); 109 } 110 return new Path("/" + domainName) 111 .append(UserWorkspaceConstants.USERS_PERSONAL_WORKSPACES_ROOT) 112 .toString(); 113 } 114 115 @Override 116 public DocumentModel getCurrentUserPersonalWorkspace(String userName, DocumentModel currentDocument) { 117 if (currentDocument == null) { 118 return null; 119 } 120 return getCurrentUserPersonalWorkspace(null, userName, currentDocument.getCoreSession(), currentDocument); 121 } 122 123 @Override 124 public DocumentModel getCurrentUserPersonalWorkspace(CoreSession userCoreSession, DocumentModel context) { 125 return getCurrentUserPersonalWorkspace(userCoreSession.getPrincipal(), null, userCoreSession, context); 126 } 127 128 /** 129 * This method handles the UserWorkspace creation with a Principal or a username. At least one should be passed. If 130 * a principal is passed, the username is not taken into account. 131 * 132 * @since 5.7 "userWorkspaceCreated" is triggered 133 */ 134 protected DocumentModel getCurrentUserPersonalWorkspace(Principal principal, String userName, 135 CoreSession userCoreSession, DocumentModel context) { 136 if (principal == null && StringUtils.isEmpty(userName)) { 137 return null; 138 } 139 140 String usedUsername; 141 if (principal instanceof NuxeoPrincipal) { 142 usedUsername = ((NuxeoPrincipal) principal).getActingUser(); 143 } else { 144 usedUsername = userName; 145 } 146 if (NuxeoPrincipal.isTransientUsername(usedUsername)) { 147 // no personal workspace for transient users 148 return null; 149 } 150 151 PathRef rootref = getExistingUserWorkspaceRoot(userCoreSession, usedUsername, context); 152 PathRef uwref = getExistingUserWorkspace(userCoreSession, rootref, principal, usedUsername); 153 DocumentModel uw = userCoreSession.getDocument(uwref); 154 155 return uw; 156 } 157 158 protected PathRef getExistingUserWorkspaceRoot(CoreSession session, String username, DocumentModel context) { 159 PathRef rootref = new PathRef(computePathUserWorkspaceRoot(session, username, context)); 160 if (session.exists(rootref)) { 161 return rootref; 162 } 163 return new PathRef(new UnrestrictedRootCreator(rootref, username, session).create().getPathAsString()); 164 } 165 166 protected PathRef getExistingUserWorkspace(CoreSession session, PathRef rootref, Principal principal, String username) { 167 String workspacename = getUserWorkspaceNameForUser(username); 168 PathRef uwref = resolveUserWorkspace(session, rootref, username, workspacename, maxsize); 169 if (session.exists(uwref)) { 170 return uwref; 171 } 172 PathRef uwcompatref = resolveUserWorkspace(session, rootref, username, IdUtils.generateId(username, "-", false, 30), 30); 173 if (uwcompatref != null && session.exists(uwcompatref)) { 174 return uwcompatref; 175 } 176 new UnrestrictedUWSCreator(uwref, session, principal, username).create(); 177 return uwref; 178 } 179 180 protected PathRef resolveUserWorkspace(CoreSession session, PathRef rootref, String username, String workspacename, int maxsize) { 181 PathRef uwref = new PathRef(rootref, workspacename); 182 if (workspacename.length() == maxsize && !new UnrestrictedPermissionChecker(session, uwref).hasPermission()) { 183 String substring = workspacename.substring(0, workspacename.length()-8); 184 return new PathRef(rootref, substring.concat(digest(username, 8))); 185 } 186 return uwref; 187 } 188 189 @Override 190 public DocumentModel getUserPersonalWorkspace(NuxeoPrincipal principal, DocumentModel context) { 191 return getCurrentUserPersonalWorkspace(principal, null, context.getCoreSession(), context); 192 } 193 194 @Override 195 public DocumentModel getUserPersonalWorkspace(String userName, DocumentModel context) { 196 UnrestrictedUserWorkspaceFinder finder = new UnrestrictedUserWorkspaceFinder(userName, context); 197 finder.runUnrestricted(); 198 return finder.getDetachedUserWorkspace(); 199 } 200 201 protected String buildUserWorkspaceTitle(Principal principal, String userName) { 202 if (userName == null) {// avoid looking for UserManager for nothing 203 return null; 204 } 205 // get the user service 206 UserManager userManager = Framework.getService(UserManager.class); 207 if (userManager == null) { 208 // for tests 209 return userName; 210 } 211 212 // Adapter userModel to get its fields (firstname, lastname) 213 DocumentModel userModel = userManager.getUserModel(userName); 214 if (userModel == null) { 215 return userName; 216 } 217 218 UserAdapter userAdapter = null; 219 userAdapter = userModel.getAdapter(UserAdapter.class); 220 221 if (userAdapter == null) { 222 return userName; 223 } 224 225 // compute the title 226 StringBuilder title = new StringBuilder(); 227 String firstName = userAdapter.getFirstName(); 228 if (firstName != null && firstName.trim() 229 .length() > 0) { 230 title.append(firstName); 231 } 232 233 String lastName = userAdapter.getLastName(); 234 if (lastName != null && lastName.trim() 235 .length() > 0) { 236 if (title.length() > 0) { 237 title.append(" "); 238 } 239 title.append(lastName); 240 } 241 242 if (title.length() > 0) { 243 return title.toString(); 244 } 245 246 return userName; 247 248 } 249 250 protected void notifyEvent(CoreSession coreSession, DocumentModel document, NuxeoPrincipal principal, 251 String eventId, Map<String, Serializable> properties) { 252 if (properties == null) { 253 properties = new HashMap<String, Serializable>(); 254 } 255 EventContext eventContext = null; 256 if (document != null) { 257 properties.put(CoreEventConstants.REPOSITORY_NAME, document.getRepositoryName()); 258 properties.put(CoreEventConstants.SESSION_ID, coreSession.getSessionId()); 259 properties.put(CoreEventConstants.DOC_LIFE_CYCLE, document.getCurrentLifeCycleState()); 260 eventContext = new DocumentEventContext(coreSession, principal, document); 261 } else { 262 eventContext = new EventContextImpl(coreSession, principal); 263 } 264 eventContext.setProperties(properties); 265 Event event = eventContext.newEvent(eventId); 266 Framework.getLocalService(EventProducer.class) 267 .fireEvent(event); 268 } 269 270 protected class UnrestrictedRootCreator extends UnrestrictedSessionRunner { 271 public UnrestrictedRootCreator(PathRef ref, String username, CoreSession session) { 272 super(session); 273 this.ref = ref; 274 this.username = username; 275 } 276 PathRef ref; 277 final String username; 278 DocumentModel doc; 279 280 @Override 281 public void run() { 282 if (session.exists(ref)) { 283 doc = session.getDocument(ref); 284 } else { 285 try { 286 doc = doCreateUserWorkspacesRoot(session, ref); 287 } catch (DocumentNotFoundException e) { 288 // domain may have been removed ! 289 targetDomainName = null; 290 ref = new PathRef(computePathUserWorkspaceRoot(session, username, null)); 291 doc = doCreateUserWorkspacesRoot(session, ref); 292 } 293 } 294 doc.detach(true); 295 assert (doc.getPathAsString() 296 .equals(ref.toString())); 297 } 298 299 DocumentModel create() { 300 synchronized (UnrestrictedRootCreator.class) { 301 runUnrestricted(); 302 return doc; 303 } 304 } 305 } 306 307 protected class UnrestrictedUWSCreator extends UnrestrictedSessionRunner { 308 309 PathRef userWSRef; 310 311 String userName; 312 313 Principal principal; 314 315 DocumentModel uw; 316 317 public UnrestrictedUWSCreator(PathRef userWSRef, CoreSession userCoreSession, 318 Principal principal, String userName) { 319 super(userCoreSession); 320 this.userWSRef = userWSRef; 321 this.userName = userName; 322 this.principal = principal; 323 } 324 325 @Override 326 public void run() { 327 if (session.exists(userWSRef)) { 328 uw = session.getDocument(userWSRef); 329 } else { 330 uw = doCreateUserWorkspace(session, userWSRef, principal, userName); 331 } 332 uw.detach(true); 333 assert (uw.getPathAsString() 334 .equals(userWSRef.toString())); 335 } 336 337 DocumentModel create() { 338 synchronized (UnrestrictedSessionRunner.class) { 339 runUnrestricted(); 340 return uw; 341 } 342 } 343 344 } 345 346 protected class UnrestrictedPermissionChecker extends UnrestrictedSessionRunner { 347 protected UnrestrictedPermissionChecker(CoreSession session, PathRef ref) { 348 super(session); 349 this.ref = ref; 350 principal = session.getPrincipal(); 351 } 352 353 final Principal principal; 354 355 final PathRef ref; 356 357 boolean hasPermission; 358 359 @Override 360 public void run() { 361 hasPermission = !session.exists(ref) || session.hasPermission(principal, ref, SecurityConstants.EVERYTHING); 362 } 363 364 boolean hasPermission() { 365 runUnrestricted(); 366 return hasPermission; 367 } 368 } 369 370 protected class UnrestrictedUserWorkspaceFinder extends UnrestrictedSessionRunner { 371 372 protected DocumentModel userWorkspace; 373 374 protected String userName; 375 376 protected DocumentModel context; 377 378 protected UnrestrictedUserWorkspaceFinder(String userName, DocumentModel context) { 379 super(context.getCoreSession() 380 .getRepositoryName(), userName); 381 this.userName = userName; 382 this.context = context; 383 } 384 385 @Override 386 public void run() { 387 userWorkspace = getCurrentUserPersonalWorkspace(null, userName, session, context); 388 if (userWorkspace != null) { 389 userWorkspace.detach(true); 390 } 391 } 392 393 public DocumentModel getDetachedUserWorkspace() { 394 return userWorkspace; 395 } 396 } 397 398 protected class RootDomainFinder extends UnrestrictedSessionRunner { 399 400 public RootDomainFinder(CoreSession userCoreSession) { 401 super(userCoreSession); 402 } 403 404 protected String domaineName; 405 406 @Override 407 public void run() { 408 409 String targetName = getComponent().getTargetDomainName(); 410 PathRef ref = new PathRef("/" + targetName); 411 if (session.exists(ref)) { 412 domaineName = targetName; 413 return; 414 } 415 // configured domain does not exist !!! 416 DocumentModelList domains = session.query("select * from Domain order by dc:created"); 417 418 if (!domains.isEmpty()) { 419 domaineName = domains.get(0) 420 .getName(); 421 } 422 } 423 } 424 425 protected UserWorkspaceServiceImplComponent getComponent() { 426 return (UserWorkspaceServiceImplComponent) Framework.getRuntime() 427 .getComponent( 428 UserWorkspaceServiceImplComponent.NAME); 429 } 430 431 protected abstract DocumentModel doCreateUserWorkspacesRoot(CoreSession unrestrictedSession, PathRef rootRef); 432 433 protected abstract DocumentModel doCreateUserWorkspace(CoreSession unrestrictedSession, PathRef wsRef, 434 Principal principal, String userName); 435 436}