Java tutorial
/* * Copyright 2013 Blazebit. * * 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 com.blazebit.security.impl.interceptor; import java.io.Serializable; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import org.apache.commons.lang3.StringUtils; import org.apache.deltaspike.core.api.provider.BeanProvider; import org.hibernate.CallbackException; import org.hibernate.EmptyInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.type.Type; import com.blazebit.annotation.AnnotationUtils; import com.blazebit.reflection.ReflectionUtils; import com.blazebit.security.entity.EntityFeatures; import com.blazebit.security.entity.EntityResourceFactory; import com.blazebit.security.entity.Parent; import com.blazebit.security.entity.EntityResourceType; import com.blazebit.security.entity.UserContext; import com.blazebit.security.exception.PermissionActionException; import com.blazebit.security.model.Action; import com.blazebit.security.model.Permission; import com.blazebit.security.service.PermissionService; import com.blazebit.security.spi.ActionFactory; /** * * @author cuszk */ public class ChangeInterceptor extends EmptyInterceptor { private static final long serialVersionUID = 1L; /** * * * @param entity * @param id * @param currentState * @param previousState * @param propertyNames * @param types * @return true if given entity is permitted to be flushed */ @Override public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { if (!EntityFeatures.isInterceptorActive()) { return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types); } if (AnnotationUtils.findAnnotation(entity.getClass(), EntityResourceType.class) == null) { return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types); } List<String> changedPropertyNames = new ArrayList<String>(); if (previousState != null) { for (int i = 0; i < currentState.length; i++) { // we dont check collections here, there is a separate method // for it See: {@link #onCollectionUpdate(collection, // key) onCollectionUpdate} if (!types[i].isCollectionType()) { if ((currentState[i] != null && !currentState[i].equals(previousState[i])) || (currentState[i] == null && previousState[i] != null)) { changedPropertyNames.add(propertyNames[i]); } } } } UserContext userContext = BeanProvider.getContextualReference(UserContext.class); ActionFactory actionFactory = BeanProvider.getContextualReference(ActionFactory.class); EntityResourceFactory resourceFactory = BeanProvider.getContextualReference(EntityResourceFactory.class); PermissionService permissionService = BeanProvider.getContextualReference(PermissionService.class); boolean isGranted = changedPropertyNames.isEmpty(); for (String propertyName : changedPropertyNames) { isGranted = permissionService.isGranted(actionFactory.createAction(Action.UPDATE), resourceFactory.createResource(entity, propertyName)); if (!isGranted) { break; } } if (isGranted) { return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types); } else { throw new PermissionActionException( "Entity " + entity + " is not permitted to be flushed by " + userContext.getUser()); } } /** * */ @Override public void onCollectionUpdate(Object collection, Serializable key) throws CallbackException { if (!EntityFeatures.isInterceptorActive()) { super.onCollectionUpdate(collection, key); return; } if (collection instanceof PersistentCollection) { PersistentCollection newValuesCollection = (PersistentCollection) collection; Object entity = newValuesCollection.getOwner(); if (AnnotationUtils.findAnnotation(entity.getClass(), EntityResourceType.class) == null) { super.onCollectionUpdate(collection, key); return; } // copy new values and old values @SuppressWarnings({ "unchecked", "rawtypes" }) Collection<?> newValues = new HashSet((Collection<?>) newValuesCollection.getValue()); @SuppressWarnings({ "unchecked", "rawtypes" }) Set<?> oldValues = new HashSet(((Map<?, ?>) newValuesCollection.getStoredSnapshot()).keySet()); String fieldName = StringUtils.replace(newValuesCollection.getRole(), entity.getClass().getName() + ".", ""); UserContext userContext = BeanProvider.getContextualReference(UserContext.class); ActionFactory actionFactory = BeanProvider.getContextualReference(ActionFactory.class); EntityResourceFactory resourceFactory = BeanProvider .getContextualReference(EntityResourceFactory.class); PermissionService permissionService = BeanProvider.getContextualReference(PermissionService.class); // find all objects that were added boolean isGrantedToAdd = true; boolean isGrantedToRemove = true; @SuppressWarnings({ "unchecked", "rawtypes" }) Set<?> retained = new HashSet(oldValues); retained.retainAll(newValues); oldValues.removeAll(retained); // if there is a difference between oldValues and newValues if (!oldValues.isEmpty()) { // if something remained isGrantedToRemove = permissionService.isGranted(actionFactory.createAction(Action.REMOVE), resourceFactory.createResource(entity, fieldName)); } newValues.removeAll(retained); if (!newValues.isEmpty()) { isGrantedToAdd = permissionService.isGranted(actionFactory.createAction(Action.ADD), resourceFactory.createResource(entity, fieldName)); } if (!isGrantedToAdd) { throw new PermissionActionException("Element cannot be added to entity " + entity + "'s collection " + fieldName + " by " + userContext.getUser()); } else { if (!isGrantedToRemove) { throw new PermissionActionException("Element cannot be removed from entity " + entity + "'s collection " + fieldName + " by " + userContext.getUser()); } else { super.onCollectionUpdate(collection, key); return; } } } else { // not a persistent collection? } } /** * */ @Override public void onCollectionRecreate(Object collection, Serializable key) throws CallbackException { // TODO newly created entities with collections should be checked here // for permission but collection cannot give back // its // role in the parent entity. BUG? Workaround: it can be checked in the // #onSave(...) method // if (!ChangeInterceptor.active) { // super.onCollectionRecreate(collection, key); // } // if (collection instanceof PersistentCollection) { // PersistentCollection newValuesCollection = (PersistentCollection) // collection; // Object entity = newValuesCollection.getOwner(); // if (AnnotationUtils.findAnnotation(entity.getClass(), // ResourceName.class) == null) { // super.onCollectionRecreate(collection, key); // } // if (ReflectionUtils.isSubtype(entity.getClass(), Permission.class)) { // throw new // IllegalArgumentException("Permission cannot be persisted by this persistence unit!"); // } // @SuppressWarnings({ "unchecked", "rawtypes" }) // Collection<?> newValues = new HashSet((Collection<?>) // newValuesCollection.getValue()); // String fieldName = StringUtils.replace(newValuesCollection.getRole(), // entity.getClass().getName() + ".", ""); // if (!newValues.isEmpty()) { // // element has been added to the collection - check Add permission // UserContext userContext = // BeanProvider.getContextualReference(UserContext.class); // ActionFactory actionFactory = // BeanProvider.getContextualReference(ActionFactory.class); // EntityResourceFactory entityFieldFactory = // BeanProvider.getContextualReference(EntityResourceFactory.class); // PermissionService permissionService = // BeanProvider.getContextualReference(PermissionService.class); // boolean isGranted = // permissionService.isGranted(userContext.getUser(), // actionFactory.createAction(Action.ADD), // entityFieldFactory.createResource(entity, fieldName)); // if (!isGranted) { // // throw new PermissionException("Element cannot be added to Entity " // + entity + "'s collection " + // // fieldName + " by " + userContext.getUser()); // } // } // } super.onCollectionRecreate(collection, key); } // it is invoked after deleting an entity with oneToMany children. method is // invoked after onDelete the respective child entity. kinda useless @Override public void onCollectionRemove(Object collection, Serializable key) throws CallbackException { // if (!ChangeInterceptor.active) { // super.onCollectionRemove(collection, key); // } // if (collection instanceof PersistentCollection) { // PersistentCollection newValuesCollection = (PersistentCollection) // collection; // Object entity = newValuesCollection.getOwner(); // if (AnnotationUtils.findAnnotation(entity.getClass(), // ResourceName.class) == null) { // super.onCollectionRecreate(collection, key); // } // if (ReflectionUtils.isSubtype(entity.getClass(), Permission.class)) { // throw new IllegalArgumentException( // "Permission cannot be persisted by this persistence unit!"); // } // @SuppressWarnings({ "unchecked", "rawtypes" }) // Collection<?> newValues = new HashSet( // (Collection<?>) newValuesCollection.getValue()); // String fieldName = StringUtils.replace( // newValuesCollection.getRole(), entity.getClass().getName() // + ".", ""); // if (!newValues.isEmpty()) { // // element has been added to the collection - check Add // // permission // UserContext userContext = BeanProvider // .getContextualReference(UserContext.class); // ActionFactory actionFactory = BeanProvider // .getContextualReference(ActionFactory.class); // EntityResourceFactory entityFieldFactory = BeanProvider // .getContextualReference(EntityResourceFactory.class); // PermissionService permissionService = BeanProvider // .getContextualReference(PermissionService.class); // boolean isGranted = permissionService.isGranted(userContext // .getUser(), actionFactory // .createAction(Action.REMOVE), // entityFieldFactory.createResource(entity, // fieldName)); // if (!isGranted) { // throw new PermissionException( // "Element cannot be added to Entity " + entity // + "'s collection " + fieldName + " by " // + userContext.getUser()); // } // } // } super.onCollectionRemove(collection, key); } // add @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { if (!EntityFeatures.isInterceptorActive()) { return super.onSave(entity, id, state, propertyNames, types); } if (AnnotationUtils.findAnnotation(entity.getClass(), EntityResourceType.class) == null) { return super.onSave(entity, id, state, propertyNames, types); } if (ReflectionUtils.isSubtype(entity.getClass(), Permission.class)) { throw new IllegalArgumentException("Permission cannot be persisted by this persistence unit!"); } UserContext userContext = BeanProvider.getContextualReference(UserContext.class); ActionFactory actionFactory = BeanProvider.getContextualReference(ActionFactory.class); EntityResourceFactory resourceFactory = BeanProvider.getContextualReference(EntityResourceFactory.class); PermissionService permissionService = BeanProvider.getContextualReference(PermissionService.class); // check if collection relations have permission boolean isGrantedAddEntity = true; boolean isGrantedCreateRelatedEntity = true; boolean foundNotEmptyField = false; // check every field for create permission, if no field is filled out, // check for entity create permission for (int i = 0; i < state.length; i++) { String fieldName = propertyNames[i]; if (types[i].isCollectionType()) { Collection<?> collection = (Collection<?>) state[i]; if (!collection.isEmpty()) { // elements have been added isGrantedAddEntity = permissionService.isGranted(actionFactory.createAction(Action.CREATE), resourceFactory.createResource(entity, fieldName, id != null)); if (!isGrantedAddEntity) { throw new PermissionActionException("Element to Entity " + entity + "'s collection " + fieldName + " cannot be added by " + userContext.getUser()); } } } else { if (state[i] != null) { foundNotEmptyField = true; boolean isGranted = permissionService.isGranted(userContext.getUser(), actionFactory.createAction(Action.CREATE), resourceFactory.createResource(entity, fieldName, id != null)); if (!isGranted) { throw new PermissionActionException("Entity " + entity + "'s field " + fieldName + " is not permitted to be persisted by " + userContext.getUser()); } // check relations if (types[i].isAssociationType()) { isGrantedCreateRelatedEntity = permissionService.isGranted(userContext.getUser(), actionFactory.createAction(Action.CREATE), resourceFactory.createResource(entity, fieldName, id != null)); if (!isGrantedCreateRelatedEntity) { throw new PermissionActionException("Entity " + entity + "'s field " + fieldName + " cannot be updated by " + userContext.getUser()); } checkAssociation(entity, fieldName, types[i], Action.ADD, userContext, permissionService, resourceFactory, actionFactory); } } } } if (!foundNotEmptyField) { boolean isGranted = permissionService.isGranted(userContext.getUser(), actionFactory.createAction(Action.CREATE), resourceFactory.createResource(entity, id != null)); if (!isGranted) { throw new PermissionActionException( "Entity " + entity + " is not permitted to be persisted by " + userContext.getUser()); } } return super.onSave(entity, id, state, propertyNames, types); } private void checkAssociation(Object entity, String propertyName, Type type, String action, UserContext userContext, PermissionService permissionService, EntityResourceFactory resourceFactory, ActionFactory actionFactory) { Map<Class<?>, Tuple> toBeChecked = new HashMap<Class<?>, Tuple>(); // it can only be on collection types ManyToOne manyToOne; // look for annotation on getter level manyToOne = ReflectionUtils.getGetter(entity.getClass(), propertyName).getAnnotation(ManyToOne.class); if (manyToOne == null) { // on field level manyToOne = ReflectionUtils.getField(entity.getClass(), propertyName).getAnnotation(ManyToOne.class); } if (manyToOne != null) { Object val = null; try { val = ReflectionUtils.getGetter(entity.getClass(), propertyName).invoke(entity); } catch (Exception ex) { throw new RuntimeException(ex); } if (val != null) { toBeChecked.put(type.getReturnedClass(), new Tuple(val, propertyName)); } } for (Map.Entry<Class<?>, Tuple> entry : toBeChecked.entrySet()) { Class<?> c = entry.getKey(); // TODO: We do this complicated stuff to retrieve the inverse mapping, this can be simplified for (Field f : ReflectionUtils.getInstanceFields(c)) { OneToMany oneToMany; if ((oneToMany = ReflectionUtils.getGetter(c, f.getName()) .getAnnotation(OneToMany.class)) != null) { if (entity.getClass().isAssignableFrom(ReflectionUtils.getResolvedFieldTypeArguments(c, f)[0]) && (oneToMany.mappedBy().isEmpty() ? true : oneToMany.mappedBy().equals(entry.getValue().fieldName))) { if (!permissionService.isGranted(userContext.getUser(), actionFactory.createAction(action), resourceFactory.createResource(entry.getValue().o, f.getName()))) { throw new PermissionActionException("Entity " + c + "'s field " + f.getName() + " cannot be " + action + "(e)d by " + userContext.getUser()); } } } } } } private Object getParent(Object entity, String propertyName) { Parent parent; // look for annotation on getter level parent = ReflectionUtils.getGetter(entity.getClass(), propertyName).getAnnotation(Parent.class); if (parent == null) { // on field level parent = ReflectionUtils.getField(entity.getClass(), propertyName).getAnnotation(Parent.class); } if (parent != null) { try { return ReflectionUtils.getGetter(entity.getClass(), propertyName).invoke(entity); } catch (Exception ex) { throw new RuntimeException(ex); } } return null; } // delete private static class Tuple { Object o; String fieldName; public Tuple(Object o, String fieldName) { super(); this.o = o; this.fieldName = fieldName; } } @Override public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { if (!EntityFeatures.isInterceptorActive()) { super.onDelete(entity, id, state, propertyNames, types); } if (AnnotationUtils.findAnnotation(entity.getClass(), EntityResourceType.class) == null) { super.onDelete(entity, id, state, propertyNames, types); } if (ReflectionUtils.isSubtype(entity.getClass(), Permission.class)) { throw new IllegalArgumentException("Permission cannot be deleted by this persistence unit!"); } UserContext userContext = BeanProvider.getContextualReference(UserContext.class); ActionFactory actionFactory = BeanProvider.getContextualReference(ActionFactory.class); EntityResourceFactory resourceFactory = BeanProvider.getContextualReference(EntityResourceFactory.class); PermissionService permissionService = BeanProvider.getContextualReference(PermissionService.class); boolean isGranted = permissionService.isGranted(userContext.getUser(), actionFactory.createAction(Action.DELETE), resourceFactory.createResource(entity)); if (!isGranted) { // try parent permission for (int i = 0; i < propertyNames.length; i++) { String propertyName = propertyNames[i]; // it can only be on collection types if (types[i].isAssociationType()) { Object parent = getParent(entity, propertyName); if (parent != null) { isGranted = permissionService.isGranted(userContext.getUser(), actionFactory.createAction(Action.DELETE), resourceFactory.createResource(parent)); break; } } } } if (!isGranted) throw new PermissionActionException( "Entity " + entity + " is not permitted to be deleted by " + userContext.getUser()); for (int i = 0; i < propertyNames.length; i++) { String propertyName = propertyNames[i]; // it can only be on collection types if (types[i].isAssociationType()) { Object parent = getParent(entity, propertyName); boolean isParentGranted = false; // We skip associations that are annotated as our parents and provide us inherited permissions if (parent != null) { isParentGranted = permissionService.isGranted(userContext.getUser(), actionFactory.createAction(Action.DELETE), resourceFactory.createResource(parent)); } if (!isParentGranted) { checkAssociation(entity, propertyName, types[i], Action.REMOVE, userContext, permissionService, resourceFactory, actionFactory); } } } super.onDelete(entity, id, state, propertyNames, types); } }