package org.sigmah.server.handler.util; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.util.*; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.map.HashedMap; import org.sigmah.server.dispatch.CommandHandler; import org.sigmah.server.domain.Contact; import org.sigmah.server.domain.OrgUnit; import org.sigmah.server.domain.Organization; import org.sigmah.server.domain.Project; import org.sigmah.server.domain.User; import org.sigmah.server.domain.profile.GlobalPermission; import org.sigmah.server.domain.profile.OrgUnitProfile; import org.sigmah.server.domain.profile.PrivacyGroupPermission; import org.sigmah.server.domain.profile.Profile; import org.sigmah.server.mapper.Mapper; import org.sigmah.server.util.Languages; import org.sigmah.shared.Language; import org.sigmah.shared.command.result.Authentication; import org.sigmah.shared.dto.profile.PrivacyGroupDTO; import org.sigmah.shared.dto.profile.ProfileDTO; import org.sigmah.shared.dto.referential.GlobalPermissionEnum; import org.sigmah.shared.dto.referential.PrivacyGroupPermissionEnum; import org.sigmah.shared.security.UnauthorizedAccessException; import org.sigmah.shared.util.Month; import org.sigmah.shared.util.OrgUnitUtils; /** * Convenient methods for {@link CommandHandler} implementations. * * @author Maxime Lombard (mlombard@ideia.fr) * @author Denis Colliot (dcolliot@ideia.fr) */ public final class Handlers { private Handlers() { // Utility class. } public static Month monthFromRange(Date date1, Date date2) { final Calendar c1 = Calendar.getInstance(); c1.setTime(date1); if (c1.get(Calendar.DAY_OF_MONTH) != 5) { return null; } final Calendar c2 = Calendar.getInstance(); c2.setTime(date2); if (c2.get(Calendar.DAY_OF_MONTH) != c2.getActualMaximum(Calendar.DAY_OF_MONTH) - 5) { return null; } if (c2.get(Calendar.MONTH) != c1.get(Calendar.MONTH) || c2.get(Calendar.YEAR) != c2.get(Calendar.YEAR)) { return null; } return new Month(c1.get(Calendar.YEAR), c1.get(Calendar.MONTH) + 1); } /** * Creates a new {@link Authentication} with the given arguments. * * @param user * The {@link User} instance, may be {@code null}. * @param language * The {@link Language} value, may be {@code null}. * @param mapper * The {@link Mapper} service. * @return The created {@link Authentication}. Its language property is never {@code null}. */ public static Authentication createAuthentication(final User user, final Language language, Set<Integer> memberOfProjectIds, Set<Integer> secondaryOrgUnitIds, final Mapper mapper) { final Organization organization = user.getOrganization(); final OrgUnitProfile orgUnitWithProfiles = user.getMainOrgUnitWithProfiles(); final Integer organizationId = organization != null ? organization.getId() : null; final String organizationName = organization != null ? organization.getName() : null; final String organizationLogo = organization != null ? organization.getLogo() : null; final Integer orgUnitId = orgUnitWithProfiles != null && orgUnitWithProfiles.getOrgUnit() != null ? orgUnitWithProfiles.getOrgUnit().getId() : null; return new Authentication(user.getId(), user.getEmail(), user.getName(), user.getFirstName(), Languages.notNull(language), organizationId, organizationName, organizationLogo, orgUnitId, secondaryOrgUnitIds, Handlers.aggregateProfiles(user, mapper), memberOfProjectIds); } /** * <p> * Aggregates the list of profiles of a {@code user}. * </p> * <p> * The {@link User} may have several profiles which link it to its {@link OrgUnit}. * This handler merges also all the profiles in one <em>aggregated profile</em>. * </p> * <p> * It returns a map relating the id of a OrgUnit with an aggregated ProfileDTO. * Each OrgUnit is crawled and put into the map with the best matching (i.e. the nearest) ProfileDTO. * </p> * * @param user * The user. * @param mapper * The mapper service. * @return The aggregated profile DTO. */ public static Map<Integer, ProfileDTO> aggregateProfiles(final User user, final Mapper mapper) { Map<Integer, ProfileDTO> aggregatedProfiles = new HashMap<>(); if (user == null) { return aggregatedProfiles; } Map<Integer, Integer> orgUnitDistance = new HashedMap<>(); for (OrgUnitProfile orgUnitProfile : user.getOrgUnitsWithProfiles()) { ProfileDTO aggretatedProfileDTO = new ProfileDTO(); aggretatedProfileDTO.setName("AGGREGATED_PROFILE"); aggretatedProfileDTO.setGlobalPermissions(new HashSet<GlobalPermissionEnum>()); aggretatedProfileDTO.setPrivacyGroups(new HashMap<PrivacyGroupDTO, PrivacyGroupPermissionEnum>()); if (CollectionUtils.isEmpty(orgUnitProfile.getProfiles())) { aggregatedProfiles.put(orgUnitProfile.getOrgUnit().getId(), aggretatedProfileDTO); continue; } for (Profile profile : orgUnitProfile.getProfiles()) { if (profile.getGlobalPermissions() != null) { for (GlobalPermission p : profile.getGlobalPermissions()) { // Aggregates global permissions among profiles. aggretatedProfileDTO.getGlobalPermissions().add(p.getPermission()); } } if (profile.getPrivacyGroupPermissions() != null) { for (PrivacyGroupPermission p : profile.getPrivacyGroupPermissions()) { PrivacyGroupDTO groupDTO = mapper.map(p.getPrivacyGroup(), new PrivacyGroupDTO()); // Aggregates privacy groups among profiles. if (aggretatedProfileDTO.getPrivacyGroups().get(groupDTO) != PrivacyGroupPermissionEnum.WRITE) { aggretatedProfileDTO.getPrivacyGroups().put(groupDTO, p.getPermission()); } } } } List<OrgUnit> orgUnits = new ArrayList<>(); crawlUnits(orgUnitProfile.getOrgUnit(), orgUnits, true); for (int i = 0; i < orgUnits.size(); i++) { Integer orgUnitId = orgUnits.get(i).getId(); if (aggregatedProfiles.containsKey(orgUnitId) && orgUnitDistance.get(orgUnitId) <= i) { // The OrgUnit is already inside the Map // and the current profile is farther than the one inside the map so let's try another OrgUnitProfile // or else break; } aggregatedProfiles.put(orgUnitId, aggretatedProfileDTO); orgUnitDistance.put(orgUnitId, i); } } return aggregatedProfiles; } /** * Adds recursively all the OrgUnits children of a {@code user} in a collection. * * @param user * The {@link User} from which the hierarchy is traversed. * @param units * The current collection in which the units are added. * @param addRoot * If the root must be added too. */ public static void crawlUnits(final User user, final Collection<OrgUnit> units, final boolean addRoot) { if (user == null || user.getOrgUnitsWithProfiles() == null) { // No units available. return; } for (OrgUnitProfile orgUnitProfile : user.getOrgUnitsWithProfiles()) { crawlUnits(orgUnitProfile.getOrgUnit(), units, addRoot); } } /** * Adds recursively all the children of an unit in a collection. * * @param root * The root unit from which the hierarchy is traversed. * @param units * The current collection in which the units are added. * @param addRoot * If the root must be added too. */ public static void crawlUnits(final OrgUnit root, final Collection<OrgUnit> units, final boolean addRoot) { if (addRoot) { units.add(root); } final Set<OrgUnit> children = root.getChildrenOrgUnits(); if (children != null) { for (final OrgUnit child : children) { crawlUnits(child, units, true); } } } /** * Returns if the project is visible for the given user. * * @param project * The project. * @param user * The user. * @return If the project is visible for the user. */ public static boolean isProjectVisible(final Project project, final User user) { return isProjectAccessible(project, user, false); } public static boolean isProjectEditable(final Project project, final User user) { return isProjectAccessible(project, user, true); } public static boolean isProjectAccessible(Project project, User user, boolean edition) { // Checks that the project is not deleted if (project.isDeleted()) { return false; } // Owner. final User owner = project.getOwner(); if (owner != null && owner.getId().equals(user.getId())) { return true; } // Manager. final User manager = project.getManager(); if (manager != null && manager.getId().equals(user.getId())) { return true; } OrgUnitProfile targetedOrgUnitProfile = getTargetedOrgUnitProfile(project.getOrgUnit().getId(), user); if (targetedOrgUnitProfile == null) { return false; } boolean canSeeHisProjects = false; boolean canEditHisProjects = false; for (Profile profile : targetedOrgUnitProfile.getProfiles()) { for (GlobalPermission globalPermission : profile.getGlobalPermissions()) { if (globalPermission.getPermission() == GlobalPermissionEnum.EDIT_ALL_PROJECTS) { // If the profile has EDIT_ALL_PROJECTS permission, it has VIEW_ALL_PROJECTS too return true; } if (globalPermission.getPermission() == GlobalPermissionEnum.VIEW_ALL_PROJECTS && !edition) { return true; } if (globalPermission.getPermission() == GlobalPermissionEnum.VIEW_MY_PROJECTS) { canSeeHisProjects = true; } else if (globalPermission.getPermission() == GlobalPermissionEnum.EDIT_PROJECT) { canEditHisProjects = true; // If the profile has EDIT_PROJECT permission, it has VIEW_MY_PROJECTS too canSeeHisProjects = true; } } } if ((!edition && !canSeeHisProjects) || (edition && !canEditHisProjects)) { return false; } // Let's see if the user belongs to the project team for (User teamMember : project.getTeamMembers()) { if (teamMember.equals(user)) { return true; } } for (Profile profile : targetedOrgUnitProfile.getProfiles()) { if (project.getTeamMemberProfiles().contains(profile)) { return true; } } return false; } /** * * @param contact The contact to check * @param user The user attempting to access contact data * @param contactProjects The projects related to the contact * @param edition Does the user need right access? * @return true if the contact is accessible for the user attempting to access contact data */ public static boolean isContactAccessible(Contact contact, User user, List<Project> contactProjects, boolean edition) { if (contact.isDeleted()) { return false; } if (Objects.equals(contact.getUser().getId(), user.getId())) { return true; } List<OrgUnit> orgUnitsRelatedToContact = new ArrayList<>(); orgUnitsRelatedToContact.addAll(contact.getOrgUnits()); for (Project contactProject : contactProjects) { orgUnitsRelatedToContact.add(contactProject.getOrgUnit()); } // Get the list of profiles compatible with the contact List<OrgUnitProfile> targetedOrgUnitProfiles = new ArrayList<>(); for (OrgUnit orgUnit : orgUnitsRelatedToContact) { OrgUnitProfile targetedOrgUnitProfile = getTargetedOrgUnitProfile(orgUnit.getId(), user); if (targetedOrgUnitProfile != null) { targetedOrgUnitProfiles.add(targetedOrgUnitProfile); } } if (targetedOrgUnitProfiles.isEmpty()) { return false; } boolean canSeeContact = false; boolean canEditContact = false; for (OrgUnitProfile targetedOrgUnitProfile : targetedOrgUnitProfiles) { for (Profile profile : targetedOrgUnitProfile.getProfiles()) { for (GlobalPermission globalPermission : profile.getGlobalPermissions()) { if (globalPermission.getPermission() == GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS) { canSeeContact = true; } else if (globalPermission.getPermission() == GlobalPermissionEnum.EDIT_VISIBLE_CONTACTS) { canEditContact = true; // If the profile has EDIT_VISIBLE_CONTACT permission, it has VIEW_MY_CONTACTS too canSeeContact = true; } } } } return (!edition && canSeeContact) || (edition && canEditContact); } /** * Returns if the given {@code orgUnit} is visible to the given {@code user}. * * @param orgUnit * The org unit. * @param user * The user (the user's linked OrgUnit must be loaded). * @return {@code true} if the given {@code orgUnit} is visible to the given {@code user}, {@code false} otherwise. * @throws NullPointerException * If one of the arguments is {@code null}. */ public static boolean isOrgUnitVisible(final OrgUnit orgUnit, final User user) { if (orgUnit.getDeleted() != null) { return false; } // Checks that the user can see this org unit. final HashSet<OrgUnit> units = new HashSet<OrgUnit>(); for (OrgUnitProfile orgUnitProfile : user.getOrgUnitsWithProfiles()) { Handlers.crawlUnits(orgUnitProfile.getOrgUnit(), units, true); } for (final OrgUnit unit : units) { if (orgUnit.getId().equals(unit.getId())) { return true; } } return false; } /** * Utiliy to check the user's grant for a given permission. * * @param userOrgUnit * Link between user and orgunit to check. * @param permission * Permission to search. * @return <code>true</code> if the given user is granted the given permission, * <code>false</code> otherwise. */ public static boolean isGranted(final OrgUnitProfile userOrgUnit, final GlobalPermissionEnum permission) { List<Profile> profiles = userOrgUnit.getProfiles(); for (final Profile profile : profiles) { if (profile.getGlobalPermissions() != null) { for (final GlobalPermission p : profile.getGlobalPermissions()) { if (p.getPermission().equals(permission)) { return true; } } } } return false; } public static boolean isGranted(List<OrgUnitProfile> userUnits, GlobalPermissionEnum permission) { for (OrgUnitProfile userUnit : userUnits) { if (isGranted(userUnit, permission)) { return true; } } return false; } public static boolean isGranted(List<OrgUnitProfile> userUnits, OrgUnit targetOrgUnit, GlobalPermissionEnum permission) { for (OrgUnitProfile userUnit : userUnits) { if (!OrgUnitUtils.areOrgUnitsEqualOrParent(targetOrgUnit, userUnit.getOrgUnit().getId())) { continue; } if (isGranted(userUnit, permission)) { return true; } } return false; } /** * Asserts that the user has permission to modify the structure of the given database. * * NOTE: Design privilege from Activity Info have been removed. * To satisfy this check a user must now be able to view the given database * and must have the {@link GlobalPermissionEnum#EDIT_INDICATOR} permission. * * @param user * The user for whom to check permissions. * @param project * The project the user is trying to modify. * @throws UnauthorizedAccessException * If the user does not have design permission. */ public static void assertDesignPrivileges(final User user, final Project project) throws UnauthorizedAccessException { if (!isGranted(user.getOrgUnitsWithProfiles(), GlobalPermissionEnum.EDIT_INDICATOR)) { throw new UnauthorizedAccessException("Access denied to project '" + project.getId() + "'."); } if (!isProjectVisible(project, user)) { throw new UnauthorizedAccessException("Project '" + project.getId() + "' is not visible by user '" + user.getEmail() + "'."); } } private static OrgUnitProfile getTargetedOrgUnitProfile(Integer orgUnitIdRelatedToContainer, User user) { // let's get the nearest OrgUnitProfile from the target OrgUnit int minDistance = Integer.MAX_VALUE; OrgUnitProfile targetedOrgUnitProfile = null; for (OrgUnitProfile orgUnitProfile : user.getOrgUnitsWithProfiles()) { if (Objects.equals(orgUnitProfile.getOrgUnit().getId(), orgUnitIdRelatedToContainer)) { // This OrgUnitProfile is directly related to the targeted OrgUnit, so he is obviously the nearest OrgUnitProfile targetedOrgUnitProfile = orgUnitProfile; break; } if (minDistance == 1) { // The nearest OrgUnitProfile is either the current one or a OrgUnitProfile directly related to the targeted OrgUnit continue; } List<OrgUnit> orgUnits = new ArrayList<>(); crawlUnits(orgUnitProfile.getOrgUnit(), orgUnits, false); int currentDistance = 1; for (OrgUnit orgUnit : orgUnits) { // This loop is over the distance of the currently selected OrgUnitProfile if (currentDistance >= minDistance) { break; } if (Objects.equals(orgUnit.getId(), orgUnitIdRelatedToContainer)) { targetedOrgUnitProfile = orgUnitProfile; minDistance = currentDistance; break; } currentDistance++; } } return targetedOrgUnitProfile; } }