/* * Copyright 2015 herd contributors * * Licensed 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 org.finra.herd.service.helper; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.finra.herd.model.api.xml.NamespaceAuthorization; import org.finra.herd.model.api.xml.NamespacePermissionEnum; import org.finra.herd.model.dto.ApplicationUser; import org.finra.herd.model.dto.SecurityUserWrapper; /** * Helper for checking permissions based on given parameters. */ @Component public class NamespaceSecurityHelper { private static final Logger LOGGER = LoggerFactory.getLogger(NamespaceSecurityHelper.class); /** * Checks the current user's permissions against the given object which may represent a single or multiple namespaces. Allowed types are String or * Collection of String. * * @param object The string or collection of strings which represents the namespace * @param permissions The set of permissions the current user must have for the given namespace(s) */ public void checkPermission(Object object, NamespacePermissionEnum[] permissions) { List<AccessDeniedException> accessDeniedExceptions = new ArrayList<>(); checkPermission(object, permissions, accessDeniedExceptions); if (!accessDeniedExceptions.isEmpty()) { throw getAccessDeniedException(accessDeniedExceptions); } } /** * Checks the current user's permissions against the given namespace. * * @param namespace The namespace * @param permissions The permissions the current user must have for the given namespace */ public void checkPermission(String namespace, NamespacePermissionEnum[] permissions) { // Skip the permission check if there is no authentication or namespace is not specified. if (!isAuthenticated() || StringUtils.isBlank(namespace)) { return; } // Trim the namespace. String namespaceTrimmed = namespace.trim(); // Check if the current user is authorized to the given namespace and has the given permissions. ApplicationUser applicationUser = getApplicationUser(); if (!isAuthorized(applicationUser, namespaceTrimmed, permissions)) { // The current user is not authorized to access the given namespace, so log a warning and throw an exception. LOGGER.warn(String .format("User does not have permission(s) to the namespace. %s namespace=\"%s\" permissions=\"%s\"", applicationUser, namespaceTrimmed, Arrays.asList(permissions))); if (applicationUser != null) { throw new AccessDeniedException(String .format("User \"%s\" does not have \"%s\" permission(s) to the namespace \"%s\"", applicationUser.getUserId(), Arrays.asList(permissions), namespaceTrimmed)); } else { throw new AccessDeniedException( String.format("Current user does not have \"%s\" permission(s) to the namespace \"%s\"", Arrays.asList(permissions), namespaceTrimmed)); } } } /** * Constructs a new access denied exception by concatenating the given list of exceptions. * * @param accessDeniedExceptions List of exceptions to concatenate * * @return A new AccessDeniedException */ public AccessDeniedException getAccessDeniedException(List<AccessDeniedException> accessDeniedExceptions) { StringBuilder errorMessageBuilder = new StringBuilder(); for (AccessDeniedException accessDeniedException : accessDeniedExceptions) { errorMessageBuilder.append(String.format("%s%n", accessDeniedException.getMessage())); } return new AccessDeniedException(errorMessageBuilder.toString().trim()); } /** * Gets a set of namespace codes which the current user is authorized for the given permissions. * * @param permissions List of permissions to query * * @return Set of namespace codes */ public Set<String> getAuthorizedNamespaces(NamespacePermissionEnum... permissions) { Set<String> namespaces = new HashSet<>(); if (SecurityContextHolder.getContext().getAuthentication() != null) { ApplicationUser applicationUser = getApplicationUser(); if (applicationUser != null) { for (NamespaceAuthorization namespaceAuthorization : applicationUser.getNamespaceAuthorizations()) { if (namespaceAuthorization.getNamespacePermissions().containsAll(Arrays.asList(permissions))) { namespaces.add(namespaceAuthorization.getNamespace()); } } } } return namespaces; } /** * Returns true if the application user is authorized to the given namespace and has the given permissions. * * @param applicationUser the application user * @param namespace the namespace * @param permissions the permissions * * @return true if authorized, false otherwise */ private boolean isAuthorized(ApplicationUser applicationUser, String namespace, NamespacePermissionEnum... permissions) { if (applicationUser != null && applicationUser.getNamespaceAuthorizations() != null) { for (NamespaceAuthorization currentUserAuthorization : applicationUser.getNamespaceAuthorizations()) { List<NamespacePermissionEnum> currentUserNamespacePermissions = currentUserAuthorization.getNamespacePermissions(); if (currentUserNamespacePermissions == null) { currentUserNamespacePermissions = Collections.emptyList(); } if (StringUtils.equalsIgnoreCase(currentUserAuthorization.getNamespace(), namespace) && currentUserNamespacePermissions.containsAll(Arrays.asList(permissions))) { return true; } } } return false; } /** * Checks the current user's permissions against the given object which may represent a single or multiple namespaces. Allowed types are String or * Collection of String. * * @param object The string or collection of strings which represents the namespace * @param permissions The set of permissions the current user must have for the given namespace(s) * @param accessDeniedExceptions The list which any access denied exceptions will be gathered into. This list will be empty if no access denied exceptions * occur. */ private void checkPermission(Object object, NamespacePermissionEnum[] permissions, List<AccessDeniedException> accessDeniedExceptions) { /* * An infinite recursion is theoretically possible by passing in a collection which contains itself, but given our current usage it may be near * impossible to achieve. */ if (object != null) { if (object instanceof Collection) { Collection<?> collection = (Collection<?>) object; for (Object element : collection) { checkPermission(element, permissions, accessDeniedExceptions); } } else if (object instanceof String) { try { checkPermission((String) object, permissions); } catch (AccessDeniedException accessDeniedException) { accessDeniedExceptions.add(accessDeniedException); } } else { throw new IllegalStateException( String.format("Object must be of type %s or %s. Actual object.class = %s", String.class, Collection.class, object.getClass())); } } } /** * Gets the ApplicationUser in the current security context. Assumes the user is already authenticated, and the authenticated user is constructed through * the application's authentication mechanism. * * @return The ApplicationUser or null if not authenticated */ private ApplicationUser getApplicationUser() { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal != null && principal instanceof SecurityUserWrapper) { SecurityUserWrapper securityUserWrapper = (SecurityUserWrapper) principal; return securityUserWrapper.getApplicationUser(); } return null; } /** * Returns true if there is a current authentication. * * @return true if authenticated */ private boolean isAuthenticated() { return SecurityContextHolder.getContext().getAuthentication() != null; } }