All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.getperka.flatpack.security.RolePropertySecurity Maven / Gradle / Ivy

There is a newer version: 2.21.0
Show newest version
package com.getperka.flatpack.security;

/*
 * #%L
 * FlatPack serialization code
 * %%
 * Copyright (C) 2012 - 2013 Perka Inc.
 * %%
 * 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.
 * #L%
 */

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.inject.Singleton;

import com.getperka.flatpack.HasUuid;
import com.getperka.flatpack.RoleMapper;
import com.getperka.flatpack.ext.PrincipalMapper;
import com.getperka.flatpack.ext.Property;
import com.getperka.flatpack.ext.PropertySecurity;
import com.getperka.flatpack.util.FlatPackCollections;

/**
 * Enforces {@link RolesAllowed} restrictions on entity properties.
 * 

* The guide to access control: *

    *
  • A {@link RolesAllowed}, {@link DenyAll} or {@link PermitAll} decoration on a getter or setter * is definitive *
  • A {@link RoleDefaults} annotation is preferred over a {@link RolesAllowed} on a type *
  • If no class-level annotations are present, public getters are presumed to be allowed *
  • If no class-level role annotations are present, a public setter will inherit from its getter *
*/ @Singleton public class RolePropertySecurity implements PropertySecurity { /** * Used to memoize property information. */ static class PropertyRoles { final Set> getterRoles; final Set getterRoleNames; final Set> setterRoles; final Set setterRoleNames; PropertyRoles(RolePropertySecurity security, Property property) { Set getterFallback = property.getGetter() != null && Modifier.isPublic(property.getGetter().getModifiers()) ? allRoleNames : Collections. emptySet(); getterRoleNames = security.extractRoleNames(property.getGetter(), false, getterFallback); getterRoles = security.extractRoles(getterRoleNames); Set setterFallback = property.getSetter() != null && Modifier.isPublic(property.getSetter().getModifiers()) ? getterRoleNames : Collections. emptySet(); setterRoleNames = security.extractRoleNames(property.getSetter(), true, setterFallback); setterRoles = security.extractRoles(setterRoleNames); } } private interface AllRolesView {} private interface NoRolesView {} final Set> allRoles = Collections.> singleton(AllRolesView.class); final Set> noRoles = Collections.> singleton(NoRolesView.class); private PrincipalMapper principalMapper; private RoleMapper roleMapper; private final ConcurrentMap propertyRoles = new ConcurrentHashMap(); /** * Requires injection. */ protected RolePropertySecurity() {} @Override public Set getGetterRoleNames(Property property) { return getPropertyRoles(property).getterRoleNames; } @Override public Set getSetterRoleNames(Property property) { return getPropertyRoles(property).setterRoleNames; } @Override public boolean mayGet(Property property, Principal principal, HasUuid target) { PropertyRoles roles = getPropertyRoles(property); // Always enforce @DenyAll if (roles.getterRoles.isEmpty()) { return false; } if (!principalMapper.isAccessEnforced(principal, target)) { return true; } return checkRoles(roles.getterRoles, principalMapper.getRoles(principal)); } @Override public boolean maySet(Property property, Principal principal, HasUuid target, Object newValue) { PropertyRoles roles = getPropertyRoles(property); // Always enforce @DenyAll if (roles.setterRoles.isEmpty()) { return false; } if (!principalMapper.isAccessEnforced(principal, target)) { return true; } return checkRoles(roles.setterRoles, principalMapper.getRoles(principal)); } /** * Returns the role names associated with a method or class. This method never returns * {@code null}, however there are several special return values: *
    *
  • {@link #noRoleNames} indicates that no access information was set *
  • {@link #allRoleNames} indicates that access should be allowed for all roles *
  • An empty set indicates access should be denied for all roles *
*/ protected Set extractRoleNames(AnnotatedElement obj, boolean isSetter) { // Map no method to none-allowed if (obj == null) { return noRoleNames; } if (obj.isAnnotationPresent(DenyAll.class)) { return Collections.emptySet(); } if (obj.isAnnotationPresent(PermitAll.class)) { return allRoleNames; } RoleDefaults defaults = obj.getAnnotation(RoleDefaults.class); if (defaults != null) { return extractRoles(isSetter ? defaults.setters() : defaults.getters()); } RolesAllowed view = obj.getAnnotation(RolesAllowed.class); if (view != null) { return extractRoles(view); } return noRoleNames; } /** * Extract role information from a method or, optionally, its declaring class. This method will * also check for {@link RoleDefaults} if no role information exists on the method. */ protected Set extractRoleNames(Method method, boolean isSetter, Set fallback) { if (method == null) { return noRoleNames; } // Look at method Set toReturn = extractRoleNames(method, isSetter); if (!noRoleNames.equals(toReturn)) { return toReturn; } // Look at declaring class toReturn = extractRoleNames(method.getDeclaringClass(), isSetter); if (!noRoleNames.equals(toReturn)) { return toReturn; } return fallback; } /** * Determine if any of the given credential roles map onto a required view. Visible for testing. */ boolean checkRoles(Collection> required, Collection credentials) { // Object comparison intentional if (roleMapper == null || required == allRoles) { return true; } if (required == null || required.isEmpty()) { return false; } for (String cred : credentials) { Class credView = roleMapper.mapRole(cred); if (credView == null) { continue; } if (required.contains(credView)) { return true; } for (Class req : required) { if (req.isAssignableFrom(credView)) { return true; } } } return false; } @Inject void inject(PrincipalMapper principalMapper, RoleMapper roleMapper) { this.principalMapper = principalMapper; this.roleMapper = roleMapper; } private Set extractRoles(RolesAllowed view) { Set toReturn = FlatPackCollections.setForIteration(); toReturn.addAll(Arrays.asList(view.value())); if (toReturn.isEmpty()) { return Collections.emptySet(); } return Collections.unmodifiableSet(toReturn); } private Set> extractRoles(Set roleNames) { if (roleMapper == null || roleNames == null || noRoleNames.equals(roleNames)) { return noRoles; } if (allRoleNames.equals(roleNames)) { return allRoles; } Set> toReturn = FlatPackCollections.setForIteration(); for (String name : roleNames) { Class roleClass = roleMapper.mapRole(name); if (roleClass != null) { toReturn.add(roleClass); } } return Collections.unmodifiableSet(toReturn); } private PropertyRoles getPropertyRoles(Property property) { PropertyRoles toReturn = propertyRoles.get(property); if (toReturn == null) { synchronized (this) { toReturn = new PropertyRoles(this, property); propertyRoles.put(property, toReturn); } } return toReturn; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy