001/* 002 * Copyright 2016 Anyware Services 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 */ 016package org.ametys.plugins.workspaces.members; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Objects; 028import java.util.Optional; 029import java.util.Set; 030import java.util.function.BiPredicate; 031import java.util.function.Predicate; 032import java.util.stream.Collectors; 033import java.util.stream.Stream; 034 035import org.apache.avalon.framework.component.Component; 036import org.apache.avalon.framework.context.Context; 037import org.apache.avalon.framework.context.ContextException; 038import org.apache.avalon.framework.context.Contextualizable; 039import org.apache.avalon.framework.service.ServiceException; 040import org.apache.avalon.framework.service.ServiceManager; 041import org.apache.avalon.framework.service.Serviceable; 042import org.apache.cocoon.components.ContextHelper; 043import org.apache.cocoon.environment.Request; 044import org.apache.commons.collections.CollectionUtils; 045import org.apache.commons.lang3.StringUtils; 046import org.apache.http.annotation.Obsolete; 047 048import org.ametys.cms.repository.Content; 049import org.ametys.core.group.Group; 050import org.ametys.core.group.GroupIdentity; 051import org.ametys.core.group.GroupManager; 052import org.ametys.core.observation.Event; 053import org.ametys.core.observation.ObservationManager; 054import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint; 055import org.ametys.core.right.RightManager; 056import org.ametys.core.right.RightProfilesDAO; 057import org.ametys.core.ui.Callable; 058import org.ametys.core.user.CurrentUserProvider; 059import org.ametys.core.user.User; 060import org.ametys.core.user.UserIdentity; 061import org.ametys.core.user.UserManager; 062import org.ametys.plugins.core.user.UserHelper; 063import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 064import org.ametys.plugins.repository.AmetysObject; 065import org.ametys.plugins.repository.AmetysObjectIterable; 066import org.ametys.plugins.repository.AmetysObjectResolver; 067import org.ametys.plugins.repository.AmetysRepositoryException; 068import org.ametys.plugins.repository.ModifiableAmetysObject; 069import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 070import org.ametys.plugins.repository.RepositoryConstants; 071import org.ametys.plugins.userdirectory.UserDirectoryHelper; 072import org.ametys.plugins.workspaces.ObservationConstants; 073import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType; 074import org.ametys.plugins.workspaces.project.ProjectManager; 075import org.ametys.plugins.workspaces.project.modules.WorkspaceModule; 076import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 077import org.ametys.plugins.workspaces.project.objects.Project; 078import org.ametys.plugins.workspaces.project.objects.Project.InscriptionStatus; 079import org.ametys.plugins.workspaces.project.rights.ProjectRightHelper; 080import org.ametys.runtime.plugin.component.AbstractLogEnabled; 081import org.ametys.web.population.PopulationContextHelper; 082import org.ametys.web.repository.page.ModifiablePage; 083import org.ametys.web.repository.page.Page; 084import org.ametys.web.repository.site.Site; 085 086import com.google.common.collect.ImmutableList; 087 088/** 089 * Helper component for managing project's users 090 */ 091public class ProjectMemberManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 092{ 093 /** Avalon Role */ 094 public static final String ROLE = ProjectMemberManager.class.getName(); 095 096 /** The id of the members service */ 097 public static final String __WORKSPACES_SERVICE_MEMBERS = "org.ametys.plugins.workspaces.module.Members"; 098 099 @Obsolete // For v1 project only 100 private static final String __PROJECT_RIGHT_PROFILE = "PROJECT"; 101 102 /** Constants for users project node */ 103 private static final String __PROJECT_MEMBERS_NODE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":members"; 104 105 /** The type of the project users node type */ 106 private static final String __PROJECT_MEMBERS_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":unstructured"; 107 108 /** The type of a project user node type */ 109 private static final String __PROJECT_MEMBER_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":project-member"; 110 111 /** Avalon context */ 112 protected Context _context; 113 114 /** Project manager */ 115 protected ProjectManager _projectManager; 116 117 /** Project rights helper */ 118 protected ProjectRightHelper _projectRightHelper; 119 120 /** Profiles right manager */ 121 protected RightProfilesDAO _rightProfilesDAO; 122 123 /** Profile assignment storage */ 124 protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageExtensionPoint; 125 126 /** Ametys object resolver */ 127 protected AmetysObjectResolver _resolver; 128 129 /** Rights manager */ 130 protected RightManager _rightManager; 131 132 /** Current user provider */ 133 protected CurrentUserProvider _currentUserProvider; 134 135 /** Users manager */ 136 protected UserManager _userManager; 137 138 /** The observation manager */ 139 protected ObservationManager _observationManager; 140 141 /** Module managers EP */ 142 protected WorkspaceModuleExtensionPoint _moduleManagerEP; 143 144 /** The user helper */ 145 protected UserHelper _userHelper; 146 147 /** The groups manager */ 148 protected GroupManager _groupManager; 149 150 /** The population context helper */ 151 protected PopulationContextHelper _populationContextHelper; 152 153 /** The user directory helper */ 154 protected UserDirectoryHelper _userDirectoryHelper; 155 156 @Override 157 public void contextualize(Context context) throws ContextException 158 { 159 _context = context; 160 } 161 162 @Override 163 public void service(ServiceManager manager) throws ServiceException 164 { 165 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 166 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 167 _projectRightHelper = (ProjectRightHelper) manager.lookup(ProjectRightHelper.ROLE); 168 _rightProfilesDAO = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE); 169 _profileAssignmentStorageExtensionPoint = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE); 170 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 171 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 172 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 173 _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE); 174 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 175 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 176 _moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 177 _populationContextHelper = (PopulationContextHelper) manager.lookup(org.ametys.core.user.population.PopulationContextHelper.ROLE); 178 _userDirectoryHelper = (UserDirectoryHelper) manager.lookup(UserDirectoryHelper.ROLE); 179 } 180 181 /** 182 * Retrieve the data of a member of a project, or the default data if no user is provided 183 * @param projectName The name of the project 184 * @param identity The user or group identity. If null, return the default profiles for a new user 185 * @param type The type of the identity. Can be "user" or "group" 186 * @return The map of profiles per module for the user 187 * @throws IllegalAccessException If the user cannot execute this operation 188 */ 189 @Callable 190 public Map<String, Object> getProjectMemberData(String projectName, String identity, String type) throws IllegalAccessException 191 { 192 Map<String, Object> result = new HashMap<>(); 193 194 boolean isTypeUser = JCRProjectMember.MemberType.USER.toString().equals(type); 195 boolean isTypeGroup = JCRProjectMember.MemberType.GROUP.toString().equals(type); 196 UserIdentity user = Optional.ofNullable(identity) 197 .filter(id -> id != null && isTypeUser) 198 .map(UserIdentity::stringToUserIdentity) 199 .orElse(null); 200 GroupIdentity group = Optional.ofNullable(identity) 201 .filter(id -> id != null && isTypeGroup) 202 .map(GroupIdentity::stringToGroupIdentity) 203 .orElse(null); 204 205 if (identity != null) 206 { 207 if (isTypeGroup && group == null) 208 { 209 result.put("message", "unknow-group"); 210 return result; 211 } 212 else if (isTypeUser && user == null) 213 { 214 result.put("message", "unknow-user"); 215 return result; 216 } 217 } 218 219 Project project = _projectManager.getProject(projectName); 220 if (project == null) 221 { 222 result.put("message", "unknow-project"); 223 return result; 224 } 225 226 if (!_projectRightHelper.canAddMember(project)) 227 { 228 throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to get member's rights without convenient right [" + projectName + ", " + identity + "]"); 229 } 230 231 boolean newMember = true; 232 Map<String, String> userProfiles; 233 234 if (user != null || group != null) 235 { 236 JCRProjectMember projectMember = user != null ? getOrCreateProjectMember(project, user) : getOrCreateProjectMember(project, group); 237 238 newMember = projectMember.needsSave(); 239 240 String role = projectMember.getRole(); 241 if (role != null) 242 { 243 result.put("role", role); 244 } 245 246 userProfiles = _getMemberProfiles(projectMember, project); 247 } 248 else 249 { 250 userProfiles = new HashMap<>(); 251 } 252 253 result.put("profiles", userProfiles); 254 result.put("status", newMember ? "new" : "edit"); 255 256 return result; 257 } 258 259 private Map<String, String> _getMemberProfiles(JCRProjectMember member, Project project) 260 { 261 Map<String, String> userProfiles = new HashMap<>(); 262 263 // Get allowed profile on modules (among the project members's profiles) 264 for (WorkspaceModule module : _projectManager.getModules(project)) 265 { 266 String allowedProfileOnProject = _projectRightHelper.getAllowedProfileOnModule(project, module, member); 267 userProfiles.put(module.getId(), allowedProfileOnProject); 268 } 269 270 return userProfiles; 271 } 272 273 /** 274 * Set the user data in the project 275 * @param projectName The project name 276 * @param identity The user or group identity. 277 * @param type The type of the identity. Can be "user" or "group" 278 * @param newProfiles The profiles to affect, mapped by module 279 * @param role The user role inside the project 280 * @return The result 281 * @throws IllegalAccessException If the user cannot execute this operation 282 */ 283 @Callable 284 public Map<String, Object> setProjectMemberData(String projectName, String identity, String type, Map<String, String> newProfiles, String role) throws IllegalAccessException 285 { 286 Map<String, Object> result = new HashMap<>(); 287 288 boolean isTypeUser = JCRProjectMember.MemberType.USER.toString().equals(type); 289 boolean isTypeGroup = JCRProjectMember.MemberType.GROUP.toString().equals(type); 290 UserIdentity user = Optional.ofNullable(identity) 291 .filter(id -> id != null && isTypeUser) 292 .map(UserIdentity::stringToUserIdentity) 293 .orElse(null); 294 GroupIdentity group = Optional.ofNullable(identity) 295 .filter(id -> id != null && isTypeGroup) 296 .map(GroupIdentity::stringToGroupIdentity) 297 .orElse(null); 298 299 if (group == null && user == null) 300 { 301 result.put("message", isTypeGroup ? "unknow-group" : "unknow-user"); 302 return result; 303 } 304 305 306 Project project = _projectManager.getProject(projectName); 307 if (project == null) 308 { 309 result.put("message", "unknow-project"); 310 return result; 311 } 312 313 if (!_projectRightHelper.canAddMember(project)) 314 { 315 throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to set member rights without convenient right [" + projectName + ", " + identity + "]"); 316 } 317 318 JCRProjectMember projectMember = isTypeUser ? getOrCreateProjectMember(project, user) : getOrCreateProjectMember(project, group); 319 boolean newMember = projectMember.needsSave(); 320 321 if (role != null) 322 { 323 projectMember.setRole(role); 324 } 325 326 setMemberProfiles(newProfiles, projectMember, project, newMember); 327 328 project.saveChanges(); 329 330 if (newMember) 331 { 332 // Notify listeners 333 Map<String, Object> eventParams = new HashMap<>(); 334 eventParams.put(ObservationConstants.ARGS_MEMBER, projectMember); 335 eventParams.put(ObservationConstants.ARGS_MEMBER_ID, projectMember.getId()); 336 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 337 eventParams.put(ObservationConstants.ARGS_PROJECT_ID, project.getId()); 338 _observationManager.notify(new Event(ObservationConstants.EVENT_MEMBER_ADDED, _currentUserProvider.getUser(), eventParams)); 339 } 340 341 return result; 342 } 343 344 /** 345 * Add a user to a project with open inscriptions, using the default values 346 * @param project The project 347 * @param user The user 348 * @return True if the user was successfully added 349 */ 350 public boolean addProjectMember(Project project, UserIdentity user) 351 { 352 if (user == null) 353 { 354 return false; 355 } 356 357 InscriptionStatus inscriptionStatus = project.getInscriptionStatus(); 358 if (!inscriptionStatus.equals(InscriptionStatus.OPEN)) 359 { 360 return false; 361 } 362 363 JCRProjectMember projectMember = getOrCreateProjectMember(project, user); 364 365 // Allow the user to access the project's site 366 _allowReadAccessOnProject(projectMember, project); 367 368 Set<String> allowedProfiles = Stream.of(RightManager.READER_PROFILE_ID, project.getDefaultProfile()) 369 .filter(Objects::nonNull) 370 .collect(Collectors.toSet()); 371 372 for (WorkspaceModule module : _moduleManagerEP.getModules()) 373 { 374 _setProfileOnModule(projectMember, project, module, allowedProfiles); 375 } 376 377 project.saveChanges(); 378 379 // Notify listeners 380 Map<String, Object> eventParams = new HashMap<>(); 381 eventParams.put(ObservationConstants.ARGS_MEMBER, projectMember); 382 eventParams.put(ObservationConstants.ARGS_MEMBER_ID, projectMember.getId()); 383 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 384 eventParams.put(ObservationConstants.ARGS_PROJECT_ID, project.getId()); 385 _observationManager.notify(new Event(ObservationConstants.EVENT_MEMBER_ADDED, _currentUserProvider.getUser(), eventParams)); 386 387 return true; 388 } 389 390 /** 391 * Set the profiles for a member 392 * @param newProfiles The allowed profile by module 393 * @param projectMember The member 394 * @param project The project 395 * @param newMember true if it is a new created member 396 */ 397 public void setMemberProfiles(Map<String, String> newProfiles, JCRProjectMember projectMember, Project project, boolean newMember) 398 { 399 // Allow the user to access the project's site 400 _allowReadAccessOnProject(projectMember, project); 401 402 for (Map.Entry<String, String> entry : newProfiles.entrySet()) 403 { 404 String moduleId = entry.getKey(); 405 String profileId = entry.getValue(); 406 407 if (__PROJECT_RIGHT_PROFILE.equals(moduleId)) 408 { 409 // V1 project only 410 if (profileId != null) 411 { 412 _addProfile(projectMember, profileId, project); 413 } 414 else 415 { 416 _removeProfile(projectMember, profileId, project); 417 } 418 } 419 else 420 { 421 Set<String> allowedProfiles = profileId != null ? Stream.of(RightManager.READER_PROFILE_ID, profileId).collect(Collectors.toSet()) : Collections.EMPTY_SET; 422 423 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 424 _setProfileOnModule(projectMember, project, module, allowedProfiles); 425 } 426 } 427 } 428 429 private void _setProfileOnModule(JCRProjectMember member, Project project, WorkspaceModule module, Set<String> allowedProfiles) 430 { 431 if (module != null && _projectManager.isModuleActivated(project, module.getId())) 432 { 433 AmetysObject moduleObject = module.getModuleRoot(project, false); 434 _setMemberProfiles(member, allowedProfiles, moduleObject); 435 436 // Update read access on project module's pages 437 AmetysObjectIterable<Page> modulePages = module.getModulePages(project, null); 438 for (Page modulePage : modulePages) 439 { 440 _setMemberProfiles(member, allowedProfiles.contains(RightManager.READER_PROFILE_ID) ? Collections.singleton(RightManager.READER_PROFILE_ID) : Collections.EMPTY_SET, modulePage); 441 } 442 } 443 } 444 445 private Set<String> _getAllowedProfile(JCRProjectMember member, AmetysObject object) 446 { 447 return MemberType.GROUP.toString().equals(member.getType()) 448 ? _profileAssignmentStorageExtensionPoint.getAllowedProfilesForGroup(object, member.getGroup()) 449 : _profileAssignmentStorageExtensionPoint.getAllowedProfilesForUser(object, member.getUser()); 450 } 451 452 private void _setMemberProfiles(JCRProjectMember member, Set<String> allowedProfiles, AmetysObject object) 453 { 454 Set<String> currentAllowedProfiles = _getAllowedProfile(member, object); 455 456 Collection<String> profilesToRemove = CollectionUtils.removeAll(currentAllowedProfiles, allowedProfiles); 457 458 Collection<String> profilesToAdd = CollectionUtils.removeAll(allowedProfiles, currentAllowedProfiles); 459 460 for (String profileId : profilesToRemove) 461 { 462 _removeProfile(member, profileId, object); 463 } 464 465 for (String profileId : profilesToAdd) 466 { 467 _addProfile(member, profileId, object); 468 } 469 470 Collection<String> updatedProfiles = CollectionUtils.union(profilesToAdd, profilesToRemove); 471 472 if (updatedProfiles.size() > 0) 473 { 474 Map<String, Object> eventParams = new HashMap<>(); 475 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT, object); 476 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT_IDENTIFIER, object.getId()); 477 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_PROFILES, updatedProfiles); 478 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_SOLR_CACHE_UNINFLUENTIAL, true); 479 480 _observationManager.notify(new Event(org.ametys.core.ObservationConstants.EVENT_ACL_UPDATED, _currentUserProvider.getUser(), eventParams)); 481 } 482 } 483 484 private void _removeProfile(JCRProjectMember member, String profileId, AmetysObject aclObject) 485 { 486 if (MemberType.GROUP.toString().equals(member.getType())) 487 { 488 _profileAssignmentStorageExtensionPoint.removeAllowedProfileFromGroup(member.getGroup(), profileId, aclObject); 489 } 490 else 491 { 492 _profileAssignmentStorageExtensionPoint.removeAllowedProfileFromUser(member.getUser(), profileId, aclObject); 493 } 494 } 495 496 private void _addProfile(JCRProjectMember member, String profileId, AmetysObject aclObject) 497 { 498 if (MemberType.GROUP.toString().equals(member.getType())) 499 { 500 _profileAssignmentStorageExtensionPoint.allowProfileToGroup(member.getGroup(), profileId, aclObject); 501 } 502 else 503 { 504 _profileAssignmentStorageExtensionPoint.allowProfileToUser(member.getUser(), profileId, aclObject); 505 } 506 } 507 508 private void _setReadAccess(JCRProjectMember member, AmetysObject aclObject) 509 { 510 Set<String> currentAllowedProfiles = _getAllowedProfile(member, aclObject); 511 512 if (!currentAllowedProfiles.contains(RightManager.READER_PROFILE_ID)) 513 { 514 _addProfile(member, RightManager.READER_PROFILE_ID, aclObject); 515 516 Map<String, Object> eventParams = new HashMap<>(); 517 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT, aclObject); 518 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT_IDENTIFIER, aclObject.getId()); 519 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_PROFILES, Collections.singleton(RightManager.READER_PROFILE_ID)); 520 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_SOLR_CACHE_UNINFLUENTIAL, true); 521 522 _observationManager.notify(new Event(org.ametys.core.ObservationConstants.EVENT_ACL_UPDATED, MemberType.GROUP.toString().equals(member.getType()) ? null : member.getUser(), eventParams)); 523 } 524 } 525 526 private void _allowReadAccessOnProject(JCRProjectMember member, Project project) 527 { 528 _setReadAccess(member, project); 529 530 // Add READER profile for all dashboards of project 531 for (ModifiablePage dashboardPage : _getProjectDashboardPages(project)) 532 { 533 _setReadAccess(member, dashboardPage); 534 } 535 } 536 537 private List<ModifiablePage> _getProjectDashboardPages(Project project) 538 { 539 List<ModifiablePage> dashboardPages = project.getSites() 540 .stream() 541 .map(Site::getSitemaps) 542 .flatMap(AmetysObjectIterable::stream) 543 .map(sitemap -> _projectManager.getProjectDashboardPage(project, sitemap.getSitemapName())) 544 .flatMap(AmetysObjectIterable::stream) 545 .filter(page -> page instanceof ModifiablePage) 546 .map(ModifiablePage.class::cast) 547 .collect(Collectors.toList()); 548 return dashboardPages; 549 } 550 551 private void _removeMemberProfiles(JCRProjectMember member, AmetysObject object) 552 { 553 Set<String> currentAllowedProfiles = _getAllowedProfile(member, object); 554 555 for (String allowedProfile : currentAllowedProfiles) 556 { 557 _removeProfile(member, allowedProfile, object); 558 } 559 560 if (currentAllowedProfiles.size() > 0) 561 { 562 ((ModifiableAmetysObject) object).saveChanges(); 563 564 Map<String, Object> eventParams = new HashMap<>(); 565 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT, object); 566 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT_IDENTIFIER, object.getId()); 567 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_PROFILES, currentAllowedProfiles); 568 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_SOLR_CACHE_UNINFLUENTIAL, true); 569 570 _observationManager.notify(new Event(org.ametys.core.ObservationConstants.EVENT_ACL_UPDATED, _currentUserProvider.getUser(), eventParams)); 571 } 572 } 573 574 /** 575 * Get the current user information 576 * @return The user 577 */ 578 @Callable 579 public Map<String, Object> getCurrentUser() 580 { 581 Map<String, Object> result = new HashMap<>(); 582 result.put("user", _userHelper.user2json(_currentUserProvider.getUser())); 583 return result; 584 } 585 586 /** 587 * Get the members of current project or all the members of all projects in where is no current project 588 * @return The members 589 */ 590 @Callable 591 public Map<String, Object> getProjectMembers() 592 { 593 Map<String, Object> result = new HashMap<>(); 594 595 Request request = ContextHelper.getRequest(_context); 596 String projectName = (String) request.getAttribute("projectName"); 597 598 Collection<Project> projects = new ArrayList<>(); 599 600 if (StringUtils.isNotEmpty(projectName)) 601 { 602 projects.add(_projectManager.getProject(projectName)); 603 } 604 else 605 { 606 _projectManager.getProjects() 607 .stream() 608 .forEach(project -> projects.add(project)); 609 } 610 611 Set<String> projectsPopulations = projects.stream() 612 .map(project -> project.getSites()) 613 .flatMap(Collection::stream) 614 .map(site -> _populationContextHelper.getUserPopulationsOnContext("/sites/" + site.getName(), false)) 615 .flatMap(Set::stream) 616 .collect(Collectors.toSet()); 617 618 Set<JCRProjectMember> members = projects.stream() 619 .map(project -> getProjectMembers(project)) 620 .flatMap(Set::stream) 621 .collect(Collectors.toSet()); 622 623 Set<UserIdentity> users = members.stream() 624 .filter(member -> MemberType.USER.toString().equals(member.getType())) 625 .map(JCRProjectMember::getUser) 626 .collect(Collectors.toSet()); 627 628 users.addAll(members.stream() 629 .filter(member -> MemberType.GROUP.toString().equals(member.getType())) 630 .map(member -> _groupManager.getGroup(member.getGroup())) 631 .filter(Objects::nonNull) 632 .map(group -> group.getUsers()) 633 .flatMap(Set::stream) 634 .filter(user -> projectsPopulations.contains(user.getPopulationId())) 635 .collect(Collectors.toSet())); 636 637 result.put("users", users.stream() 638 .map(identity -> _userHelper.getUser(identity)) 639 .filter(Objects::nonNull) 640 .map(user -> _userHelper.user2json(user, true)) 641 .collect(Collectors.toList())); 642 643 return result; 644 } 645 646 /** 647 * Get the members of a project, sorted by managers, non empty role and name 648 * @param projectName the project's name 649 * @param lang the language to get user content 650 * @param expandGroup true to expand the user of a group 651 * @param withUserContent true to get the user content 652 * @return the members of project 653 * @throws IllegalAccessException if an error occurred 654 * @throws AmetysRepositoryException if an error occurred 655 */ 656 @Callable 657 public List<Map<String, Object>> getProjectMembers(String projectName, String lang, boolean expandGroup, boolean withUserContent) throws IllegalAccessException, AmetysRepositoryException 658 { 659 Map<String, Object> result = new HashMap<>(); 660 661 Project project = _projectManager.getProject(projectName); 662 if (!_projectRightHelper.hasReadAccess(project)) 663 { 664 throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to access a privilege feature without reader right in the project " + project.getPath()); 665 } 666 667 List<Map<String, Object>> membersData = new ArrayList<>(); 668 669 List<ProjectMember> projectMembers = getProjectMembers(project, expandGroup); 670 671 for (ProjectMember projectMember : projectMembers) 672 { 673 Map<String, Object> memberData = new HashMap<>(); 674 675 memberData.put("type", projectMember.getType().toString()); 676 memberData.put("title", projectMember.getTitle()); 677 memberData.put("sortabletitle", projectMember.getSortableTitle()); 678 memberData.put("manager", projectMember.isManager()); 679 680 String role = projectMember.getRole(); 681 if (StringUtils.isNotEmpty(role)) 682 { 683 memberData.put("role", role); 684 } 685 686 User user = projectMember.getUser(); 687 if (user != null) 688 { 689 memberData.put("id", UserIdentity.userIdentityToString(user.getIdentity())); 690 memberData.putAll(_userHelper.user2json(user)); 691 692 if (withUserContent) 693 { 694 Content userContent = _userDirectoryHelper.getUserContent(user.getIdentity(), lang); 695 if (userContent != null) 696 { 697 memberData.put("userContentId", userContent.getId()); 698 } 699 } 700 } 701 702 Group group = projectMember.getGroup(); 703 if (group != null) 704 { 705 memberData.put("id", GroupIdentity.groupIdentityToString(group.getIdentity())); 706 memberData.putAll(group2Json(group)); 707 } 708 709 membersData.add(memberData); 710 } 711 712 result.put("members", membersData); 713 714 return membersData; 715 } 716 717 /** 718 * Get the members of a project, sorted by managers, non empty role and name 719 * @param project the project 720 * @param expandGroup true to expand the user of a group 721 * @return the members of project 722 * @throws IllegalAccessException if an error occurred 723 * @throws AmetysRepositoryException if an error occurred 724 */ 725 public List<ProjectMember> getProjectMembers(Project project, boolean expandGroup) throws IllegalAccessException, AmetysRepositoryException 726 { 727 Set<ProjectMember> members = new HashSet<>(); // avoid duplication 728 729 Set<JCRProjectMember> jcrMembers = getProjectMembers(project); 730 731 List<UserIdentity> managers = Arrays.asList(project.getManagers()); 732 733 for (JCRProjectMember jcrMember : jcrMembers) 734 { 735 if (MemberType.USER.toString().equals(jcrMember.getType())) 736 { 737 UserIdentity userIdentity = jcrMember.getUser(); 738 User user = _userManager.getUser(userIdentity); 739 if (user != null) 740 { 741 boolean isManager = managers.contains(userIdentity); 742 743 ProjectMember projectMember = new ProjectMember(user.getFullName(), user.getSortableName(), user, jcrMember.getRole(), isManager); 744 if (!members.add(projectMember)) 745 { 746 //if set already contains the user, override it (users always take over users' group) 747 members.remove(projectMember); // remove the one in the set 748 members.add(projectMember); // add the new one 749 } 750 } 751 } 752 else if (MemberType.GROUP.toString().equals(jcrMember.getType())) 753 { 754 GroupIdentity groupIdentity = jcrMember.getGroup(); 755 Group group = _groupManager.getGroup(groupIdentity); 756 if (group != null) 757 { 758 if (expandGroup) 759 { 760 for (UserIdentity userIdentity : group.getUsers()) 761 { 762 User user = _userManager.getUser(userIdentity); 763 if (user != null) 764 { 765 ProjectMember projectMember = new ProjectMember(user.getFullName(), user.getSortableName(), user, null, false); 766 members.add(projectMember); // add if does not exist yet 767 } 768 } 769 } 770 else 771 { 772 // Add the member as group 773 members.add(new ProjectMember(group.getLabel(), group.getLabel(), group)); 774 } 775 } 776 } 777 } 778 779 // Sort by managers, not empty role then alpha order 780 List<ProjectMember> membersAsList = new ArrayList<>(members); 781 782 Comparator<ProjectMember> managerComparator = Comparator.comparing(m -> m.isManager() ? 0 : 1); 783 Comparator<ProjectMember> roleComparator = Comparator.comparing(m -> StringUtils.isNotBlank(m.getRole()) ? 0 : 1); 784 Comparator<ProjectMember> nameComparator = (m1, m2) -> m1.getSortableTitle().compareToIgnoreCase(m2.getSortableTitle()); 785 Collections.sort(membersAsList, managerComparator.thenComparing(roleComparator).thenComparing(nameComparator)); 786 787 return membersAsList; 788 } 789 790 /** 791 * Retrieves the rights for the current user in the project 792 * @param projectName The project Name 793 * @return The project 794 */ 795 @Callable 796 public Map<String, Object> getMemberModuleRights(String projectName) 797 { 798 Map<String, Object> rights = new HashMap<>(); 799 800 Project project = _projectManager.getProject(projectName); 801 if (project == null) 802 { 803 rights.put("message", "unknow-project"); 804 return rights; 805 } 806 rights.put("view", _projectRightHelper.canViewMembers(project)); 807 rights.put("add", _projectRightHelper.canAddMember(project)); 808 rights.put("edit", _projectRightHelper.canAddMember(project)); 809 rights.put("delete", _projectRightHelper.canRemoveMember(project)); 810 811 return rights; 812 } 813 814 /** 815 * Get the list of users of the project 816 * @param project The project 817 * @return The list of users 818 */ 819 public Set<JCRProjectMember> getProjectMembers(Project project) 820 { 821 Set<JCRProjectMember> projectUsers = new HashSet<>(); 822 823 if (project != null) 824 { 825 ModifiableTraversableAmetysObject usersNode = _getProjectMembersNode(project); 826 827 for (AmetysObject userNode : usersNode.getChildren()) 828 { 829 if (userNode instanceof JCRProjectMember) 830 { 831 projectUsers.add((JCRProjectMember) userNode); 832 } 833 } 834 } 835 836 return projectUsers; 837 } 838 839 /** 840 * Test if an user is a member of a project 841 * @param project The project 842 * @param userIdentity The user identity 843 * @return True if this user is a member of this project 844 */ 845 public boolean isProjectMember(Project project, UserIdentity userIdentity) 846 { 847 return getProjectMember(project, userIdentity) != null; 848 } 849 850 /** 851 * Retrieve the member of a project corresponding to the user identity 852 * @param project The project 853 * @param userIdentity The user identity 854 * @return The member of this project, which can be of type "user" or "group", or null if the user is not in the project 855 */ 856 public JCRProjectMember getProjectMember(Project project, UserIdentity userIdentity) 857 { 858 if (userIdentity == null) 859 { 860 return null; 861 } 862 863 Set<JCRProjectMember> members = getProjectMembers(project); 864 865 JCRProjectMember projectMember = members.stream() 866 .filter(member -> MemberType.USER.toString().equals(member.getType())) 867 .filter(member -> userIdentity.equals(member.getUser())) 868 .findFirst() 869 .orElse(null); 870 871 if (projectMember == null) 872 { 873 Set<String> projectPopulations = project.getSites() 874 .stream() 875 .map(site -> _populationContextHelper.getUserPopulationsOnContexts(ImmutableList.of("/sites/" + site.getName(), "/sites-fo/" + site.getName()), false, false)) 876 .flatMap(Set::stream) 877 .collect(Collectors.toSet()); 878 879 Predicate<UserIdentity> isUserInProjectPopulation = user -> projectPopulations.contains(user.getPopulationId()) && userIdentity.equals(user); 880 881 Predicate<JCRProjectMember> isGroupInProjectPopulations = member -> _getUsersOfGroupMember(member).stream() 882 .filter(isUserInProjectPopulation) 883 .findAny() 884 .isPresent(); 885 886 projectMember = members.stream() 887 .filter(isGroupInProjectPopulations) 888 .findFirst() 889 .orElse(null); 890 } 891 return projectMember; 892 } 893 894 private Set<UserIdentity> _getUsersOfGroupMember(JCRProjectMember projectMember) 895 { 896 Predicate<JCRProjectMember> isTypeGroup = member -> MemberType.GROUP.toString().equals(member.getType()); 897 898 return Optional.ofNullable(projectMember) 899 .filter(isTypeGroup) 900 .map(JCRProjectMember::getGroup) 901 .map(_groupManager::getGroup) 902 .map(Group::getUsers) 903 .orElse(Collections.emptySet()); 904 } 905 906 /** 907 * Set the manager of a project 908 * @param projectName The project name 909 * @param profileId The profile id to affect 910 * @param managers The managers' user identity 911 */ 912 public void setProjectManager(String projectName, String profileId, List<UserIdentity> managers) 913 { 914 Project project = _projectManager.getProject(projectName); 915 if (project != null) 916 { 917 // Remove from old managers the rights to edit and delete the project 918 Arrays.stream(project.getManagers()) 919 .filter(((Predicate<UserIdentity>) managers::contains).negate()) 920 .forEach(userIdentity -> _removeManagerRights(project, userIdentity)); 921 922 project.setManagers(managers.toArray(new UserIdentity[managers.size()])); 923 924 for (UserIdentity userIdentity : managers) 925 { 926 _profileAssignmentStorageExtensionPoint.getAllowedProfilesForUser(project, userIdentity).stream() 927 .filter(previousProfile -> !profileId.equals(previousProfile) && !RightManager.READER_PROFILE_ID.equals(previousProfile)) 928 .forEach(obsoleteProfile -> _profileAssignmentStorageExtensionPoint.removeAllowedProfileFromUser(userIdentity, obsoleteProfile, project)); 929 930 _profileAssignmentStorageExtensionPoint.allowProfileToUser(userIdentity, RightManager.READER_PROFILE_ID, project); 931 _profileAssignmentStorageExtensionPoint.allowProfileToUser(userIdentity, profileId, project); 932 _notifyAclUpdated(userIdentity, project, Arrays.asList(RightManager.READER_PROFILE_ID, profileId)); 933 934 JCRProjectMember member = getOrCreateProjectMember(project, userIdentity); 935 936 // Allow the user to access the project's site 937 _allowReadAccessOnProject(member, project); 938 _addProfile(member, profileId, project); 939 940 for (WorkspaceModule module : _projectManager.getModules(project)) 941 { 942 Set<String> allowedProfiles = Stream.of(RightManager.READER_PROFILE_ID, profileId).collect(Collectors.toSet()); 943 _setProfileOnModule(member, project, module, allowedProfiles); 944 } 945 } 946 947 project.saveChanges(); 948 } 949 } 950 951 private void _removeManagerRights(Project project, UserIdentity userIdentity) 952 { 953 _profileAssignmentStorageExtensionPoint.getAllowedProfilesForUser(project, userIdentity).stream() 954 .filter(((Predicate<String>) RightManager.READER_PROFILE_ID::equals).negate()) // keep reader profile for project access, only remove manager specific rights 955 .forEach(profile -> _profileAssignmentStorageExtensionPoint.removeAllowedProfileFromUser(userIdentity, profile, project)); 956 } 957 958 private void _notifyAclUpdated(UserIdentity userIdentity, AmetysObject aclContext, Collection<String> aclProfiles) 959 { 960 Map<String, Object> eventParams = new HashMap<>(); 961 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT, aclContext); 962 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT_IDENTIFIER, aclContext.getId()); 963 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_PROFILES, aclProfiles); 964 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_SOLR_CACHE_UNINFLUENTIAL, true); 965 966 _observationManager.notify(new Event(org.ametys.core.ObservationConstants.EVENT_ACL_UPDATED, userIdentity, eventParams)); 967 } 968 969 /** 970 * Retrieve or create a user in a project 971 * @param project The project 972 * @param userIdentity the user 973 * @return The user 974 */ 975 public JCRProjectMember getOrCreateProjectMember(Project project, UserIdentity userIdentity) 976 { 977 Predicate<? super AmetysObject> findMemberPredicate = memberNode -> MemberType.USER.toString().equals(((JCRProjectMember) memberNode).getType()) 978 && userIdentity.equals(((JCRProjectMember) memberNode).getUser()); 979 JCRProjectMember projectMember = _getOrCreateProjectMember(project, findMemberPredicate); 980 981 if (projectMember.needsSave()) 982 { 983 projectMember.setUser(userIdentity); 984 projectMember.setType(MemberType.USER); 985 } 986 987 return projectMember; 988 } 989 990 /** 991 * Retrieve or create a group as a member in a project 992 * @param project The project 993 * @param groupIdentity the group 994 * @return The user 995 */ 996 public JCRProjectMember getOrCreateProjectMember(Project project, GroupIdentity groupIdentity) 997 { 998 Predicate<? super AmetysObject> findMemberPredicate = memberNode -> MemberType.GROUP.toString().equals(((JCRProjectMember) memberNode).getType()) 999 && groupIdentity.equals(((JCRProjectMember) memberNode).getGroup()); 1000 JCRProjectMember projectMember = _getOrCreateProjectMember(project, findMemberPredicate); 1001 1002 if (projectMember.needsSave()) 1003 { 1004 projectMember.setGroup(groupIdentity); 1005 projectMember.setType(MemberType.GROUP); 1006 } 1007 1008 return projectMember; 1009 } 1010 1011 /** 1012 * Retrieve or create a member in a project 1013 * @param project The project 1014 * @param findMemberPredicate The predicate to find the member node 1015 * @return The member node. A new node is created if the member node was not found 1016 */ 1017 protected JCRProjectMember _getOrCreateProjectMember(Project project, Predicate<? super AmetysObject> findMemberPredicate) 1018 { 1019 ModifiableTraversableAmetysObject membersNode = _getProjectMembersNode(project); 1020 1021 Optional<AmetysObject> member = _getProjectMembersNode(project).getChildren() 1022 .stream() 1023 .filter(memberNode -> memberNode instanceof JCRProjectMember) 1024 .filter(findMemberPredicate) 1025 .findFirst(); 1026 if (member.isPresent()) 1027 { 1028 return (JCRProjectMember) member.get(); 1029 } 1030 1031 String baseName = "member"; 1032 String name = baseName; 1033 int index = 1; 1034 while (membersNode.hasChild(name)) 1035 { 1036 index++; 1037 name = baseName + "-" + index; 1038 } 1039 1040 return membersNode.createChild(name, __PROJECT_MEMBER_NODE_TYPE); 1041 } 1042 1043 1044 /** 1045 * Remove a user from a project 1046 * @param projectName The project name 1047 * @param identity The identity of the user or group, who must be a member of the project 1048 * @param type The type of the member, user or group 1049 * @return The error code, if an error occurred 1050 * @throws IllegalAccessException If the user cannot execute this operation 1051 */ 1052 @Callable 1053 public Map<String, Object> removeMember(String projectName, String identity, String type) throws IllegalAccessException 1054 { 1055 Map<String, Object> result = new HashMap<>(); 1056 1057 boolean isTypeUser = JCRProjectMember.MemberType.USER.toString().equals(type); 1058 boolean isTypeGroup = JCRProjectMember.MemberType.GROUP.toString().equals(type); 1059 UserIdentity user = Optional.ofNullable(identity) 1060 .filter(id -> id != null && isTypeUser) 1061 .map(UserIdentity::stringToUserIdentity) 1062 .orElse(null); 1063 GroupIdentity group = Optional.ofNullable(identity) 1064 .filter(id -> id != null && isTypeGroup) 1065 .map(GroupIdentity::stringToGroupIdentity) 1066 .orElse(null); 1067 1068 if ((isTypeGroup && group == null) || (isTypeUser && user == null)) 1069 { 1070 result.put("message", isTypeGroup ? "unknow-group" : "unknow-user"); 1071 return result; 1072 } 1073 1074 Project project = _projectManager.getProject(projectName); 1075 if (project == null) 1076 { 1077 result.put("message", "unknow-project"); 1078 return result; 1079 } 1080 1081 if (_isCurrentUser(isTypeUser, user)) 1082 { 1083 result.put("message", "current-user"); 1084 return result; 1085 } 1086 1087 // If there is only one manager, do not remove him from the project's members 1088 if (_isOnlyManager(project, isTypeUser, user)) 1089 { 1090 result.put("message", "only-manager"); 1091 return result; 1092 } 1093 1094 if (!_projectRightHelper.canRemoveMember(project)) 1095 { 1096 throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to remove member without convenient right [" + projectName + ", " + identity + "]"); 1097 } 1098 1099 JCRProjectMember projectMember = null; 1100 if (isTypeUser) 1101 { 1102 projectMember = _getProjectMember(project, user); 1103 } 1104 else if (isTypeGroup) 1105 { 1106 projectMember = _getProjectMember(project, group); 1107 } 1108 1109 if (projectMember == null) 1110 { 1111 result.put("message", "unknow-member"); 1112 return result; 1113 } 1114 1115 _removeManager(project, isTypeUser, user); 1116 _removeMemberProfiles(project, projectMember); 1117 1118 projectMember.remove(); 1119 project.saveChanges(); 1120 1121 Map<String, Object> eventParams = new HashMap<>(); 1122 eventParams.put(ObservationConstants.ARGS_MEMBER_IDENTITY, identity); 1123 eventParams.put(ObservationConstants.ARGS_MEMBER_IDENTITY_TYPE, type); 1124 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 1125 _observationManager.notify(new Event(ObservationConstants.EVENT_MEMBER_DELETED, _currentUserProvider.getUser(), eventParams)); 1126 1127 return result; 1128 } 1129 1130 private boolean _isCurrentUser(boolean isTypeUser, UserIdentity user) 1131 { 1132 return isTypeUser && _currentUserProvider.getUser().equals(user); 1133 } 1134 1135 private boolean _isOnlyManager(Project project, boolean isTypeUser, UserIdentity user) 1136 { 1137 UserIdentity[] managers = project.getManagers(); 1138 return isTypeUser && managers.length == 1 && managers[0].equals(user); 1139 } 1140 1141 private JCRProjectMember _getProjectMember(Project project, GroupIdentity group) 1142 { 1143 JCRProjectMember projectMember = null; 1144 ModifiableTraversableAmetysObject membersNode = _getProjectMembersNode(project); 1145 1146 for (AmetysObject memberNode : membersNode.getChildren()) 1147 { 1148 if (memberNode instanceof JCRProjectMember) 1149 { 1150 JCRProjectMember member = (JCRProjectMember) memberNode; 1151 if (MemberType.GROUP.toString().equals(member.getType()) && group.equals(member.getGroup())) 1152 { 1153 projectMember = (JCRProjectMember) memberNode; 1154 } 1155 1156 } 1157 } 1158 return projectMember; 1159 } 1160 1161 private JCRProjectMember _getProjectMember(Project project, UserIdentity user) 1162 { 1163 JCRProjectMember projectMember = null; 1164 ModifiableTraversableAmetysObject membersNode = _getProjectMembersNode(project); 1165 1166 for (AmetysObject memberNode : membersNode.getChildren()) 1167 { 1168 if (memberNode instanceof JCRProjectMember) 1169 { 1170 JCRProjectMember member = (JCRProjectMember) memberNode; 1171 if (MemberType.USER.toString().equals(member.getType()) && user.equals(member.getUser())) 1172 { 1173 projectMember = (JCRProjectMember) memberNode; 1174 } 1175 } 1176 } 1177 return projectMember; 1178 } 1179 1180 private void _removeManager(Project project, boolean isTypeUser, UserIdentity user) 1181 { 1182 if (isTypeUser) 1183 { 1184 UserIdentity[] oldManagers = project.getManagers(); 1185 1186 // Remove the rights to edit and delete the project from the deleted manager 1187 Arrays.stream(oldManagers) 1188 .filter(user::equals) 1189 .forEach(userIdentity -> _removeManagerRights(project, userIdentity)); 1190 1191 // Remove the user from the project's managers 1192 UserIdentity[] managers = Arrays.stream(oldManagers) 1193 .filter(manager -> !manager.equals(user)) 1194 .toArray(UserIdentity[]::new); 1195 1196 project.setManagers(managers); 1197 } 1198 } 1199 1200 private void _removeMemberProfiles(Project project, JCRProjectMember projectMember) 1201 { 1202 // Remove rights on project and project's site 1203 _removeMemberProfiles(projectMember, project); 1204 1205 for (ModifiablePage dashboardPage : _getProjectDashboardPages(project)) 1206 { 1207 _removeMemberProfiles(projectMember, dashboardPage); 1208 } 1209 1210 for (WorkspaceModule module : _projectManager.getModules(project)) 1211 { 1212 ModifiableResourceCollection moduleRootNode = module.getModuleRoot(project, false); 1213 // Remove profiles on module 1214 _removeMemberProfiles(projectMember, moduleRootNode); 1215 1216 // Remove profiles on module's pages 1217 AmetysObjectIterable<Page> modulePages = module.getModulePages(project, null); 1218 for (Page modulePage : modulePages) 1219 { 1220 _removeMemberProfiles(projectMember, modulePage); 1221 } 1222 } 1223 } 1224 1225 /** 1226 * Retrieves the users node of the project 1227 * The users node will be created if necessary 1228 * @param project The project 1229 * @return The users node of the project 1230 */ 1231 protected ModifiableTraversableAmetysObject _getProjectMembersNode(Project project) 1232 { 1233 if (project == null) 1234 { 1235 throw new AmetysRepositoryException("Error getting the project users node, project is null"); 1236 } 1237 1238 try 1239 { 1240 ModifiableTraversableAmetysObject usersNode; 1241 if (project.hasChild(__PROJECT_MEMBERS_NODE)) 1242 { 1243 usersNode = project.getChild(__PROJECT_MEMBERS_NODE); 1244 } 1245 else 1246 { 1247 usersNode = project.createChild(__PROJECT_MEMBERS_NODE, __PROJECT_MEMBERS_NODE_TYPE); 1248 } 1249 1250 return usersNode; 1251 } 1252 catch (AmetysRepositoryException e) 1253 { 1254 throw new AmetysRepositoryException("Error getting the project users node", e); 1255 } 1256 } 1257 1258 /** 1259 * Get the JSON representation of a group 1260 * @param group The group 1261 * @return The group 1262 */ 1263 protected Map<String, Object> group2Json(Group group) 1264 { 1265 Map<String, Object> infos = new HashMap<>(); 1266 infos.put("id", GroupIdentity.groupIdentityToString(group.getIdentity())); 1267 infos.put("groupId", group.getIdentity().getId()); 1268 infos.put("label", group.getLabel()); 1269 infos.put("sortablename", group.getLabel()); 1270 infos.put("groupDirectory", group.getIdentity().getDirectoryId()); 1271 return infos; 1272 } 1273 1274 /** 1275 * Count the total of unique users in the project and in the project's group 1276 * @param project The project 1277 * @return The total of members 1278 */ 1279 public Long getMembersCount(Project project) 1280 { 1281 Set<JCRProjectMember> projectMembers = getProjectMembers(project); 1282 1283 Stream<UserIdentity> projectUsers = projectMembers.stream() 1284 .filter(isUserOfType(MemberType.USER)) 1285 .map(JCRProjectMember::getUser) 1286 .filter(Objects::nonNull); 1287 1288 Stream<UserIdentity> projectGroupUsers = projectMembers.stream() 1289 .filter(isUserOfType(MemberType.GROUP)) 1290 .map(member -> getGroupUsers(member)) 1291 .filter(Objects::nonNull) 1292 .flatMap(Set::stream); 1293 1294 return Stream.concat(projectUsers, projectGroupUsers) 1295 .distinct() 1296 .count(); 1297 } 1298 1299 private Set<UserIdentity> getGroupUsers(JCRProjectMember member) 1300 { 1301 return Optional.ofNullable(member.getGroup()) 1302 .map(groupdId -> _groupManager.getGroup(groupdId)) 1303 .map(group -> group.getUsers()) 1304 .orElse(null); 1305 } 1306 1307 private Predicate<JCRProjectMember> isUserOfType(MemberType memberType) 1308 { 1309 return member -> member.getType().equals(memberType.toString()); 1310 } 1311 1312 /** 1313 * Get the users from a group that are part of the project. They can be filtered with a predicate 1314 * @param group The group 1315 * @param project The project 1316 * @param filteringPredicate The predicate to filter 1317 * @return The list of users 1318 */ 1319 public List<User> getGroupUsersFromProject(Group group, Project project, BiPredicate<Project, UserIdentity> filteringPredicate) 1320 { 1321 Set<String> projectPopulations = project.getSites() 1322 .stream() 1323 .map(Site::getName) 1324 .map(siteName -> _populationContextHelper.getUserPopulationsOnContexts(ImmutableList.of("/sites/" + siteName), false, false)) 1325 .flatMap(Set::stream) 1326 .collect(Collectors.toSet()); 1327 1328 return group.getUsers().stream() 1329 .filter(user -> projectPopulations.contains(user.getPopulationId())) 1330 .filter(user -> filteringPredicate.test(project, user)) 1331 .map(_userManager::getUser) 1332 .filter(Objects::nonNull) 1333 .collect(Collectors.toList()); 1334 } 1335 1336 /** 1337 * This class represents a member of a project. Could be a user or a group 1338 * 1339 */ 1340 static class ProjectMember 1341 { 1342 private String _title; 1343 private String _sortableTitle; 1344 private MemberType _type; 1345 private String _role; 1346 private User _user; 1347 private Group _group; 1348 private boolean _isManager; 1349 1350 /** 1351 * Create a project member as a group 1352 * @param title the member's title (user's full name or group's label) 1353 * @param sortableTitle the sortable title 1354 * @param group the group attached to this member. Cannot be null. 1355 */ 1356 public ProjectMember(String title, String sortableTitle, Group group) 1357 { 1358 _title = title; 1359 _sortableTitle = sortableTitle; 1360 _type = MemberType.GROUP; 1361 _role = null; 1362 _isManager = false; 1363 _user = null; 1364 _group = group; 1365 } 1366 1367 /** 1368 * Create a project member as a group 1369 * @param title the member's title (user's full name or group's label) 1370 * @param sortableTitle the sortable title 1371 * @param role the role 1372 * @param isManager true if the member is a manager of the project 1373 * @param user the user attached to this member. Cannot be null. 1374 */ 1375 public ProjectMember(String title, String sortableTitle, User user, String role, boolean isManager) 1376 { 1377 _title = title; 1378 _sortableTitle = sortableTitle; 1379 _type = MemberType.USER; 1380 _role = role; 1381 _isManager = isManager; 1382 _user = user; 1383 _group = null; 1384 } 1385 1386 String getTitle() 1387 { 1388 return _title; 1389 } 1390 1391 String getSortableTitle() 1392 { 1393 return _sortableTitle; 1394 } 1395 1396 MemberType getType() 1397 { 1398 return _type; 1399 } 1400 1401 String getRole() 1402 { 1403 return _role; 1404 } 1405 1406 boolean isManager() 1407 { 1408 return _isManager; 1409 } 1410 1411 User getUser() 1412 { 1413 return _user; 1414 } 1415 1416 Group getGroup() 1417 { 1418 return _group; 1419 } 1420 1421 @Override 1422 public boolean equals(Object obj) 1423 { 1424 if (obj == null || !(obj instanceof ProjectMember)) 1425 { 1426 return false; 1427 } 1428 1429 ProjectMember otherMember = (ProjectMember) obj; 1430 1431 if (getType() != otherMember.getType()) 1432 { 1433 return false; 1434 } 1435 1436 if (getType() == MemberType.USER) 1437 { 1438 return getUser().equals(otherMember.getUser()); 1439 } 1440 else 1441 { 1442 return getGroup().equals(otherMember.getGroup()); 1443 } 1444 } 1445 1446 @Override 1447 public int hashCode() 1448 { 1449 return getType() == MemberType.USER ? getUser().getIdentity().hashCode() : getGroup().getIdentity().hashCode(); 1450 } 1451 } 1452}