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