com.getperka.flatpack.security.RolePropertySecurity Maven / Gradle / Ivy
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.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.
*/
@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) {
getterRoleNames = security.extractRoleNames(property.getGetter());
getterRoles = security.extractRoles(getterRoleNames);
Set temp = security.extractRoleNames(property.getSetter());
if (noRoleNames.equals(temp)) {
setterRoles = getterRoles;
setterRoleNames = getterRoleNames;
} else {
setterRoleNames = temp;
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) {
// 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;
}
RolesAllowed view = obj.getAnnotation(RolesAllowed.class);
if (view == null) {
return noRoleNames;
}
Set toReturn = FlatPackCollections.setForIteration();
toReturn.addAll(Arrays.asList(view.value()));
if (toReturn.isEmpty()) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(toReturn);
}
/**
* Extract role information from a method or its declaring class.
*/
protected Set extractRoleNames(Method method) {
if (method == null) {
return noRoleNames;
}
Set toReturn = extractRoleNames((AnnotatedElement) method);
if (noRoleNames == toReturn) {
toReturn = extractRoleNames(method.getDeclaringClass());
}
return toReturn;
}
/**
* 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(Set roleNames) {
// Object comparison intentional
if (roleMapper == null || roleNames == null || noRoleNames == roleNames) {
return noRoles;
}
if (allRoleNames == 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) {
toReturn = new PropertyRoles(this, property);
propertyRoles.put(property, toReturn);
}
return toReturn;
}
}