/** * This file is part of lavagna. * * lavagna 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. * * lavagna 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 lavagna. If not, see <http://www.gnu.org/licenses/>. */ package io.lavagna.service; import io.lavagna.model.*; import io.lavagna.query.PermissionQuery; import org.apache.commons.lang3.Validate; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.Map.Entry; @Service @Transactional(readOnly = true) public class PermissionService { private final NamedParameterJdbcTemplate jdbc; private final PermissionQuery queries; private final UserRepository userRepository; public PermissionService(NamedParameterJdbcTemplate jdbc, PermissionQuery queries, UserRepository userRepository) { this.jdbc = jdbc; this.queries = queries; this.userRepository = userRepository; } /** * A role can be without any permission. */ public Map<String, RoleAndPermissions> findBaseRoleAndPermissionByUserId(int userId) { return toMap(queries.findBaseRoleAndPermissionByUserId(userId)); } public Map<String, RoleAndPermissions> findRoleAndPermissionByUserIdInProjectId(int userId, int projectId) { return toMap(queries.findRoleAndPermissionByUserIdInProjectId(userId, projectId)); } public Set<Permission> findBasePermissionByUserId(int userId) { return toSet(queries.findBaseRoleAndPermissionByUserId(userId)); } public RoleAndMetadata findRoleByName(String name) { return queries.findRoleByName(name); } public RoleAndMetadata findRoleInProjectByName(int projectId, String name) { return queries.findRoleInProjectIdByName(projectId, name); } public ProjectRoleAndPermissionFullHolder findPermissionsGroupedByProjectForUserId(int userId) { List<ProjectRoleAndPermission> found = queries.findPermissionsGroupedByProjectForUserId(userId); Map<String, Set<Permission>> res = new HashMap<>(); Map<Integer, Set<Permission>> resById = new HashMap<>(); for (ProjectRoleAndPermission p : found) { if (p.getPermission() == null) { continue; } if (!res.containsKey(p.getProjectShortName())) { res.put(p.getProjectShortName(), EnumSet.noneOf(Permission.class)); } res.get(p.getProjectShortName()).add(p.getPermission()); if (!resById.containsKey(p.getProjectId())) { resById.put(p.getProjectId(), EnumSet.noneOf(Permission.class)); } resById.get(p.getProjectId()).add(p.getPermission()); } return new ProjectRoleAndPermissionFullHolder(res, resById); } public ProjectRoleFullHolder findUserRolesByProject(int userId) { Set<String> globalRoles = new HashSet<>(); for (RoleAndMetadata role : queries.findUserRoles(userId)) { globalRoles.add(role.getRoleName()); } Map<String, Set<String>> res = new HashMap<>(); for (RoleAndProject rp : queries.findUserRolesByProject(userId)) { if (!res.containsKey(rp.getProjectName())) { res.put(rp.getProjectName(), new HashSet<String>()); } res.get(rp.getProjectName()).add(rp.getRoleName()); } return new ProjectRoleFullHolder(globalRoles, res); } public Set<Permission> findPermissionByUsernameInProjectId(int userId, int projectId) { return toSet(queries.findRoleAndPermissionByUserIdInProjectId(userId, projectId)); } public Map<String, RoleAndPermissions> findAllRolesAndRelatedPermission() { return toMap(queries.findAllRolesAndRelatedPermission()); } public Map<String, RoleAndPermissionsWithUsers> findAllRolesAndRelatedPermissionWithUsers() { Map<String, RoleAndPermissionsWithUsers> res = new TreeMap<>(); for (RoleAndPermission rap : queries.findAllRolesAndRelatedPermission()) { if (!res.containsKey(rap.getRoleName())) { res.put(rap.getRoleName(), new RoleAndPermissionsWithUsers(rap, queries.findUserIdentifierByRole(rap.getRoleName()))); } if (rap.getPermission() != null) { res.get(rap.getRoleName()).getRoleAndPermissions().add(rap); } } return res; } public Map<String, RoleAndPermissionsWithUsers> findAllRolesAndRelatedPermissionWithUsersInProjectId( int projectId) { Map<String, RoleAndPermissionsWithUsers> res = new TreeMap<>(); for (RoleAndPermission rap : queries.findAllRolesAndRelatedPermissionInProjectId(projectId)) { if (!res.containsKey(rap.getRoleName())) { res.put(rap.getRoleName(), new RoleAndPermissionsWithUsers(rap, queries.findUserIdentifierByRoleAndProjectId( rap.getRoleName(), projectId))); } if (rap.getPermission() != null) { res.get(rap.getRoleName()).getRoleAndPermissions().add(rap); } } return res; } public Map<String, RoleAndPermissions> findAllRolesAndRelatedPermissionInProjectId(int projectId) { return toMap(queries.findAllRolesAndRelatedPermissionInProjectId(projectId)); } /** * Return 1 if created * * @param role * @return */ @Transactional(readOnly = false) public int createRole(Role role) { return queries.createRole(Objects.requireNonNull(role).getName()); } @Transactional(readOnly = false) public int createRoleInProjectId(Role role, int projectId) { return queries.createRoleInProjectId(Objects.requireNonNull(role).getName(), projectId); } @Transactional(readOnly = false) public int createFullRoleInProjectId(Role role, int projectId, boolean removable, boolean hidden, boolean readOnly) { return queries.createFullRoleInProjectId(Objects.requireNonNull(role).getName(), projectId, removable, hidden, readOnly); } @Transactional(readOnly = false) public void createMissingRolesWithPermissions(Map<RoleAndPermission, Set<Permission>> rolesWithPermissions) { Set<String> currentRoles = findAllRolesAndRelatedPermission().keySet(); for (Entry<RoleAndPermission, Set<Permission>> kv : rolesWithPermissions.entrySet()) { RoleAndPermission rp = kv.getKey(); if (!currentRoles.contains(rp.getRoleName())) { queries.createFullRole(rp.getRoleName(), rp.getRemovable(), rp.getHidden(), rp.getHidden()); } updatePermissionsToRole(new Role(rp.getRoleName()), kv.getValue()); } } @Transactional(readOnly = false) public void createMissingRolesWithPermissionForProject(int projectId, Map<RoleAndPermission, Set<Permission>> p) { createMissingRolesWithPermissionForProjects(Collections.singletonMap(projectId, p)); } @Transactional(readOnly = false) public void createMissingRolesWithPermissionForProjects(Map<Integer, Map<RoleAndPermission, Set<Permission>>> r) { for (Entry<Integer, Map<RoleAndPermission, Set<Permission>>> projIdToRolesAndPermissions : r.entrySet()) { int projectId = projIdToRolesAndPermissions.getKey(); Set<String> currentRoles = findAllRolesAndRelatedPermissionInProjectId(projectId).keySet(); for (Entry<RoleAndPermission, Set<Permission>> kv : projIdToRolesAndPermissions.getValue().entrySet()) { RoleAndPermission rp = kv.getKey(); if (!currentRoles.contains(rp.getRoleName())) { createFullRoleInProjectId(new Role(rp.getRoleName()), projectId, rp.getRemovable(), rp.getHidden(), rp.getReadOnly()); } updatePermissionsToRoleInProjectId(new Role(rp.getRoleName()), kv.getValue(), projectId); } } } /** * Return 1 if deleted * * @param role * @return */ @Transactional(readOnly = false) public int deleteRole(Role role) { Objects.requireNonNull(role); queries.removeUsersFromRole(role.getName()); queries.deletePermissions(role.getName()); return queries.deleteRole(role.getName()); } @Transactional(readOnly = false) public int deleteRoleInProjectId(Role role, int projectId) { Objects.requireNonNull(role); queries.removeUsersFromRoleInProjectId(role.getName(), projectId); queries.deletePermissionsInProjectId(role.getName(), projectId); return queries.deleteRoleInProjectId(role.getName(), projectId); } @Transactional(readOnly = false) public void updatePermissionsToRole(Role role, Set<Permission> enabledPermissions) { Objects.requireNonNull(role); Objects.requireNonNull(enabledPermissions); // step 1: remove all permissions queries.deletePermissions(role.getName()); // step 2: add the enabled permission jdbc.batchUpdate(queries.addPermission(), from(role, enabledPermissions)); } @Transactional(readOnly = false) public void updatePermissionsToRoleInProjectId(Role role, Set<Permission> permissions, int projectId) { Objects.requireNonNull(role); Objects.requireNonNull(permissions); Permission.Companion.ensurePermissionForProject(permissions); // step 1: remove all permissions queries.deletePermissionsInProjectId(role.getName(), projectId); // step 2: add the enabled permission jdbc.batchUpdate(queries.addPermissionInProjectId(), addProjectId(from(role, permissions), projectId)); } private void checkRoleCondition(String roleName, Set<Integer> usersId) { if ("ANONYMOUS".equals(roleName) && !usersId.isEmpty()) { Validate.isTrue(usersId.size() == 1); Validate.isTrue(userRepository.findById(usersId.iterator().next()).getAnonymous()); } } @Transactional(readOnly = false) public void assignRolesToUsers(Map<Role, Set<Integer>> rolesToUsersId) { for (Entry<Role, Set<Integer>> roleToUsersId : rolesToUsersId.entrySet()) { assignRoleToUsers(roleToUsersId.getKey(), roleToUsersId.getValue()); } } @Transactional(readOnly = false) public void assignRoleToUsers(Role role, Set<Integer> userIds) { Objects.requireNonNull(role); Objects.requireNonNull(userIds); checkRoleCondition(role.getName(), userIds); jdbc.batchUpdate(queries.assignRoleToUser(), fromUserIdAndRoleName(role, userIds)); } @Transactional(readOnly = false) public void assignRoleToUsersInProjectId(Role role, Set<Integer> userIds, int projectId) { Objects.requireNonNull(role); Objects.requireNonNull(userIds); checkRoleCondition(role.getName(), userIds); jdbc.batchUpdate(queries.assignRoleToUsersInProjectId(), addProjectId(fromUserIdAndRoleName(role, userIds), projectId)); } @Transactional(readOnly = false) public void removeRoleToUsers(Role role, Set<Integer> userIds) { Objects.requireNonNull(role); Objects.requireNonNull(userIds); checkRoleCondition(role.getName(), userIds); jdbc.batchUpdate(queries.removeRoleToUsers(), fromUserIdAndRoleName(role, userIds)); } @Transactional(readOnly = false) public void removeRoleToUsersInProjectId(Role role, Set<Integer> userIds, int projectId) { Objects.requireNonNull(role); Objects.requireNonNull(userIds); checkRoleCondition(role.getName(), userIds); jdbc.batchUpdate(queries.removeRoleToUsersInProjectId(), addProjectId(fromUserIdAndRoleName(role, userIds), projectId)); } public List<User> findUserByRole(Role role) { Objects.requireNonNull(role); return queries.findUserByRole(role.getName()); } public List<User> findUserByRoleAndProjectId(Role role, int projectId) { Objects.requireNonNull(role); return queries.findUserByRoleAndProjectId(role.getName(), projectId); } private static Map<String, RoleAndPermissions> toMap(List<RoleAndPermission> l) { Map<String, RoleAndPermissions> res = new TreeMap<>(); for (RoleAndPermission rap : l) { if (!res.containsKey(rap.getRoleName())) { res.put(rap.getRoleName(), new RoleAndPermissions(rap)); } if (rap.getPermission() != null) { res.get(rap.getRoleName()).roleAndPermissions.add(rap); } } return res; } private static Set<Permission> toSet(List<RoleAndPermission> rp) { Set<Permission> permissions = EnumSet.noneOf(Permission.class); for (RoleAndPermission rap : rp) { if (rap.getPermission() != null) { permissions.add(rap.getPermission()); } } return permissions; } private static MapSqlParameterSource[] fromUserIdAndRoleName(Role role, Set<Integer> userIds) { List<MapSqlParameterSource> ret = new ArrayList<>(userIds.size()); for (Integer userId : userIds) { ret.add(new MapSqlParameterSource("userId", userId).addValue("roleName", role.getName())); } return ret.toArray(new MapSqlParameterSource[ret.size()]); } private static MapSqlParameterSource[] addProjectId(MapSqlParameterSource[] s, int projectId) { for (MapSqlParameterSource param : s) { param.addValue("projectId", projectId); } return s; } private static MapSqlParameterSource[] from(Role role, Set<Permission> l) { List<MapSqlParameterSource> ret = new ArrayList<>(l.size()); for (Permission p : l) { ret.add(new MapSqlParameterSource("permission", p.toString()).addValue("roleName", role.getName())); } return ret.toArray(new MapSqlParameterSource[ret.size()]); } public static class RoleAndPermissions { private final String name; private final boolean removable; private final boolean hidden; private final boolean readOnly; private final List<RoleAndPermission> roleAndPermissions = new ArrayList<>(); private RoleAndPermissions(RoleAndPermission base) { this.name = base.getRoleName(); this.removable = base.getRemovable(); this.hidden = base.getHidden(); this.readOnly = base.getReadOnly(); } public String getName() { return this.name; } public boolean isRemovable() { return this.removable; } public boolean isHidden() { return this.hidden; } public boolean isReadOnly() { return this.readOnly; } public List<RoleAndPermission> getRoleAndPermissions() { return this.roleAndPermissions; } } public static class RoleAndPermissionsWithUsers extends RoleAndPermissions { private final List<UserIdentifier> assignedUsers; public RoleAndPermissionsWithUsers(RoleAndPermission base, List<UserIdentifier> assignedUsers) { super(base); this.assignedUsers = assignedUsers; } public List<UserIdentifier> getAssignedUsers() { return this.assignedUsers; } } public static class ProjectRoleAndPermissionFullHolder { private final Map<String, Set<Permission>> permissionsByProject; private final Map<Integer, Set<Permission>> permissionsByProjectId; @java.beans.ConstructorProperties({ "permissionsByProject", "permissionsByProjectId" }) public ProjectRoleAndPermissionFullHolder( Map<String, Set<Permission>> permissionsByProject, Map<Integer, Set<Permission>> permissionsByProjectId) { this.permissionsByProject = permissionsByProject; this.permissionsByProjectId = permissionsByProjectId; } public Map<String, Set<Permission>> getPermissionsByProject() { return this.permissionsByProject; } public Map<Integer, Set<Permission>> getPermissionsByProjectId() { return this.permissionsByProjectId; } } public static class ProjectRoleFullHolder { private final Set<String> globalRoles; private final Map<String, Set<String>> rolesByProject; @java.beans.ConstructorProperties({ "globalRoles", "rolesByProject" }) public ProjectRoleFullHolder( Set<String> globalRoles, Map<String, Set<String>> rolesByProject) { this.globalRoles = globalRoles; this.rolesByProject = rolesByProject; } public Set<String> getGlobalRoles() { return this.globalRoles; } public Map<String, Set<String>> getRolesByProject() { return this.rolesByProject; } } }