| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package com.epam.dlab.backendapi.roles; |
| |
| import com.epam.dlab.auth.UserInfo; |
| import com.epam.dlab.backendapi.dao.SecurityDAO; |
| import com.epam.dlab.exceptions.DlabException; |
| import com.google.common.base.MoreObjects; |
| import com.mongodb.client.FindIterable; |
| import org.bson.Document; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Provides user roles access to features. |
| */ |
| public class UserRoles { |
| private static final Logger LOGGER = LoggerFactory.getLogger(UserRoles.class); |
| |
| private static final String ANY_USER = "$anyuser"; |
| /** |
| * Node name of groups. |
| */ |
| private static final String GROUPS = "groups"; |
| /** |
| * Node name of user. |
| */ |
| private static final String USERS = "users"; |
| private static final String PROJECT_ADMIN_ROLE_NAME = "projectAdmin"; |
| private static final String ADMIN_ROLE_NAME = "admin"; |
| /** |
| * Single instance of the user roles. |
| */ |
| private static UserRoles userRoles = null; |
| /** |
| * List of roles. |
| */ |
| private List<UserRole> roles = null; |
| private Map<String, Set<String>> userGroups; |
| |
| /** |
| * Default access to features if the role is not defined. |
| */ |
| private boolean defaultAccess = false; |
| |
| /** |
| * Initialize user roles for all users. |
| * |
| * @param dao security DAO. |
| */ |
| public static void initialize(SecurityDAO dao, boolean defaultAccess) { |
| LOGGER.trace("Loading roles from database..."); |
| if (userRoles == null) { |
| userRoles = new UserRoles(); |
| } |
| userRoles.load(dao, defaultAccess); |
| LOGGER.trace("New roles are : {}", getRoles()); |
| } |
| |
| /** |
| * Return the list of roles for all users. |
| */ |
| public static List<UserRole> getRoles() { |
| return (userRoles == null ? null : userRoles.roles()); |
| } |
| |
| /** |
| * Check access for user to the role. |
| * |
| * @param userInfo user info. |
| * @param type the type of role. |
| * @param name the name of role. |
| * @param roles |
| * @return boolean value |
| */ |
| public static boolean checkAccess(UserInfo userInfo, RoleType type, String name, Collection<String> roles) { |
| return checkAccess(userInfo, type, name, true, roles); |
| } |
| |
| public static boolean isProjectAdmin(UserInfo userInfo) { |
| final List<UserRole> roles = UserRoles.getRoles(); |
| return roles == null || roles.stream().anyMatch(r -> PROJECT_ADMIN_ROLE_NAME.equalsIgnoreCase(r.getId()) && |
| (userRoles.hasAccessByGroup(userInfo, userInfo.getRoles(), r.getGroups()) || userRoles.hasAccessByUserName(userInfo, r))); |
| } |
| |
| public static boolean isProjectAdmin(UserInfo userInfo, Set<String> groups) { |
| final List<UserRole> roles = UserRoles.getRoles(); |
| return roles == null || roles.stream().anyMatch(r -> PROJECT_ADMIN_ROLE_NAME.equalsIgnoreCase(r.getId()) && |
| (userRoles.hasAccessByGroup(userInfo, userInfo.getRoles(), retainGroups(r.getGroups(), groups)) || userRoles.hasAccessByUserName(userInfo, r))); |
| } |
| |
| public static boolean isAdmin(UserInfo userInfo) { |
| final List<UserRole> roles = UserRoles.getRoles(); |
| return roles == null || roles.stream().anyMatch(r -> ADMIN_ROLE_NAME.equalsIgnoreCase(r.getId()) && |
| (userRoles.hasAccessByGroup(userInfo, userInfo.getRoles(), r.getGroups()) || userRoles.hasAccessByUserName(userInfo, r))); |
| } |
| |
| /** |
| * Check access for user to the role. |
| * |
| * @param roles |
| * @param userInfo user info. |
| * @param type the type of role. |
| * @param name the name of role. |
| * @return boolean value |
| */ |
| public static boolean checkAccess(UserInfo userInfo, RoleType type, String name, boolean useDefault, |
| Collection<String> roles) { |
| return (userRoles == null || userRoles.hasAccess(userInfo, type, name, useDefault, roles)); |
| } |
| |
| /** |
| * Loading the user roles for all users from Mongo database. |
| * |
| * @param dao security DAO. |
| */ |
| private synchronized void load(SecurityDAO dao, boolean defaultAccess) { |
| this.defaultAccess = defaultAccess; |
| try { |
| FindIterable<Document> docs = dao.getRoles(); |
| roles = new ArrayList<>(); |
| for (Document d : docs) { |
| Set<String> groups = getAndRemoveSet(d, GROUPS); |
| Set<String> users = getAndRemoveSet(d, USERS); |
| String id = d.getString("_id"); |
| for (RoleType type : RoleType.values()) { |
| @SuppressWarnings("unchecked") |
| List<String> names = d.get(type.getNodeName(), ArrayList.class); |
| if (names != null) { |
| for (String name : names) { |
| append(type, name, groups, users, id); |
| } |
| } |
| } |
| } |
| userGroups = dao.getGroups(); |
| } catch (Exception e) { |
| throw new DlabException("Cannot load roles from database. " + e.getLocalizedMessage(), e); |
| } |
| } |
| |
| private synchronized List<UserRole> roles() { |
| return roles; |
| } |
| |
| /** |
| * Append new role to the list if role not exists in list an return it, otherwise return |
| * existence role. |
| * |
| * @param type type of role. |
| * @param name the name of role. |
| * @param groups the names of external groups. |
| * @param users the name of DLab's users. |
| * @param id |
| * @return role. |
| */ |
| private UserRole append(RoleType type, String name, Set<String> groups, Set<String> users, String id) { |
| UserRole item = new UserRole(id, type, name, groups, users); |
| synchronized (roles) { |
| int index = Collections.binarySearch(roles, item); |
| if (index < 0) { |
| index = -index; |
| if (index > roles.size()) { |
| roles.add(item); |
| } else { |
| roles.add(index - 1, item); |
| } |
| } |
| } |
| return item; |
| } |
| |
| /** |
| * Find and return role by type and name. |
| * |
| * @param type type of role. |
| * @param name the name of role. |
| * @return list of UserRole |
| */ |
| private Set<String> getGroups(RoleType type, String name) { |
| synchronized (roles) { |
| return roles |
| .stream() |
| .filter(r -> type == r.getType() && name.equalsIgnoreCase(r.getName())) |
| .map(UserRole::getGroups) |
| .flatMap(Collection::stream) |
| .collect(Collectors.toSet()); |
| } |
| } |
| |
| /** |
| * Find and return a list by key from JSON document, otherwise return <b>null</b>. |
| * |
| * @param document the document. |
| * @param key the name of node. |
| */ |
| private Set<String> getAndRemoveSet(Document document, String key) { |
| Object o = document.get(key); |
| if (!(o instanceof ArrayList)) { |
| return Collections.emptySet(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| List<String> list = (List<String>) o; |
| if (list.isEmpty()) { |
| return Collections.emptySet(); |
| } |
| |
| Set<String> set = new HashSet<>(); |
| for (String value : list) { |
| set.add(value.toLowerCase()); |
| } |
| document.remove(key); |
| return set; |
| } |
| |
| /** |
| * Check access for user to the role. |
| * |
| * @param userInfo user info. |
| * @param type the type of role. |
| * @param name the name of role. |
| * @param useDefault true/false |
| * @param roles |
| * @return boolean value |
| */ |
| private boolean hasAccess(UserInfo userInfo, RoleType type, String name, boolean useDefault, |
| Collection<String> roles) { |
| if (userRoles == null) { |
| return true; |
| } |
| LOGGER.trace("Check access for user {} with groups {} to {}/{}", userInfo.getName(), userInfo.getRoles(), |
| type, name); |
| Set<String> groups = getGroups(type, name); |
| if (groups == null || groups.isEmpty()) { |
| return checkDefault(useDefault); |
| } |
| if (hasAccessByGroup(userInfo, roles, groups)) { |
| return true; |
| } |
| LOGGER.trace("Access denied for user {} to {}/{}", userInfo.getName(), type, name); |
| return false; |
| } |
| |
| private boolean hasAccessByGroup(UserInfo userInfo, Collection<String> userRoles, Collection<String> groups) { |
| if (groups != null) { |
| if (groups.contains(ANY_USER)) { |
| return true; |
| } |
| for (String group : userRoles) { |
| if (group != null && groups.contains(group.toLowerCase())) { |
| LOGGER.trace("Got access by group {}", group); |
| return true; |
| } |
| } |
| |
| final Optional<String> group = groups |
| .stream() |
| .filter(g -> userGroups.getOrDefault(g, Collections.emptySet()).contains(userInfo.getName().toLowerCase())) |
| .findAny(); |
| if (group.isPresent()) { |
| LOGGER.trace("Got access by local group {}", group.get()); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean hasAccessByUserName(UserInfo userInfo, UserRole role) { |
| if (role.getUsers() != null && |
| userInfo.getName() != null && |
| (role.getUsers().contains(ANY_USER) || |
| role.getUsers().contains(userInfo.getName().toLowerCase()))) { |
| LOGGER.trace("Got access by name"); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean checkDefault(boolean useDefault) { |
| if (useDefault) { |
| LOGGER.trace("Got default access {}", defaultAccess); |
| return defaultAccess; |
| } else { |
| return false; |
| } |
| } |
| |
| private static Set<String> retainGroups(Set<String> groups1, Set<String> groups2) { |
| Set<String> result = groups2 |
| .stream() |
| .map(String::toLowerCase) |
| .collect(Collectors.toSet()); |
| result.retainAll(groups1); |
| |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(roles) |
| .addValue(roles) |
| .toString(); |
| } |
| } |