:toc: macro toc::[] = Data-permissions In some projects there are demands for permissions and authorization that is dependent on the processed data. E.g. a user may only be allowed to read or write data for a specific region. This is adding some additional complexity to your authorization. If you can avoid this it is always best to keep things simple. However, in various cases this is a requirement. Therefore the following sections give you guidance and patterns how to solve this properly. == Structuring your data For all your business objects (entities) that have to be secured regarding to data permissions we recommend that you create a separate interface that provides access to the relevant data required to decide about the permission. Here is a simple example: [source,java] ---- public interface SecurityDataPermissionCountry { /** * @return the 2-letter ISO code of the country this object is associated with. Users need * a data-permission for this country in order to read and write this object. */ String getCountry(); } ---- Now related business objects (entities) can implement this interface. Often such data-permissions have to be applied to an entire object-hierarchy. For security reasons we recommend that also all child-objects implement this interface. For performance reasons we recommend that the child-objects redundantly store the data-permission properties (such as `country` in the example above) and this gets simply propagated from the parent, when a child object is created. == Permissions for processing data When saving or processing objects with a data-permission, we recommend to provide dedicated methods to verify the permission in an abstract base-class such as `AbstractUc` and simply call this explicitly from your business code. This makes it easy to understand and debug the code. Here is a simple example: [source,java] ---- protected void verifyPermission(SecurityDataPermissionCountry entity) throws AccessDeniedException; ---- === Beware of AOP For simple but cross-cutting data-permissions you may also use link:guide-aop[AOP]. This leads to programming aspects that reflectively scan method arguments and magically decide what to do. Be aware that this quickly gets tricky: * What if multiple of your method arguments have data-permissions (e.g. implement `SecurityDataPermission*`)? * What if the object to authorize is only provided as reference (e.g. `Long` or `IdRef`) and only loaded and processed inside the implementation where the AOP aspect does not apply? * How to express advanced data-permissions in annotations? What we have learned is that annotations like `@PreAuthorize` from `spring-security` easily lead to the "programming in string literals" anti-pattern. We strongly discourage to use this anti-pattern. In such case writing your own `verifyPermission` methods that you manually call in the right places of your business-logic is much better to understand, debug and maintain. == Permissions for reading data When it comes to restrictions on the data to read it becomes even more tricky. In the context of a user only entities shall be loaded from the database he is permitted to read. This is simple for loading a single entity (e.g. by its ID) as you can load it and then if not permitted throw an exception to secure your code. But what if the user is performing a search query to find many entities? For performance reasons we should only find data the user is permitted to read and filter all the rest already via the database query. But what if this is not a requirement for a single query but needs to be applied cross-cutting to tons of queries? Therefore we have the following pattern that solves your problem: For each data-permission attribute (or set of such) we create an abstract base entity: [source,java] ---- @MappedSuperclass @EntityListeners(PermissionCheckListener.class) @FilterDef(name = "country", parameters = {@ParamDef(name = "countries", type = "string")}) @Filter(name = "country", condition = "country in (:countries)") public abstract class SecurityDataPermissionCountryEntity extends ApplicationPersistenceEntity implements SecurityDataPermissionCountry { private String country; @Override public String getCountry() { return this.country; } public void setCountry(String country) { this.country = country; } } ---- There are some special hibernate annotations `@EntityListeners`, `@FilterDef`, and `@Filter` used here allowing to apply a filter on the `country` for any (non-native) query performed by hibernate. The entity listener may look like this: [source,java] ---- public class PermissionCheckListener { @PostLoad public void read(SecurityDataPermissionCountryEntity entity) { PermissionChecker.getInstance().requireReadPermission(entity); } @PrePersist @PreUpdate public void write(SecurityDataPermissionCountryEntity entity) { PermissionChecker.getInstance().requireWritePermission(entity); } } ---- This will ensure that hibernate implicitly will call these checks for every such entity when it is read from or written to the database. Further to avoid reading entities from the database the user is not permitted to (and ending up with exceptions), we create an AOP aspect that automatically activates the above declared hibernate filter: [source,java] ---- @Named public class PermissionCheckerAdvice implements MethodBeforeAdvice { @Inject private PermissionChecker permissionChecker; @PersistenceContext private EntityManager entityManager; @Override public void before(Method method, Object[] args, Object target) { Collection permittedCountries = this.permissionChecker.getPermittedCountriesForReading(); if (permittedCountries != null) { // null is returned for admins that may access all countries if (permittedCountries.isEmpty()) { throw new AccessDeniedException("Not permitted for any country!"); } Session session = this.entityManager.unwrap(Session.class); session.enableFilter("country").setParameterList("countries", permittedCountries.toArray()); } } } ---- Finally to apply this aspect to all Repositories (can easily be changed to DAOs) implement the following advisor: [source,java] ---- @Named public class PermissionCheckerAdvisor implements PointcutAdvisor, Pointcut, ClassFilter, MethodMatcher { @Inject private PermissionCheckerAdvice advice; @Override public Advice getAdvice() { return this.advice; } @Override public boolean isPerInstance() { return false; } @Override public Pointcut getPointcut() { return this; } @Override public ClassFilter getClassFilter() { return this; } @Override public MethodMatcher getMethodMatcher() { return this; } @Override public boolean matches(Method method, Class targetClass) { return true; // apply to all methods } @Override public boolean isRuntime() { return false; } @Override public boolean matches(Method method, Class targetClass, Object... args) { throw new IllegalStateException("isRuntime()==false"); } @Override public boolean matches(Class clazz) { // when using DAOs simply change to some class like ApplicationDao return DefaultRepository.class.isAssignableFrom(clazz); } } ---- == Managing and granting the data-permissions Following our link:guide-access-control#authorization[authorization guide] we can simply create a permission for each country. We might simply reserve a prefix (as virtual `«app-id»`) for each data-permission to allow granting data-permissions to end-users across all applications of the IT landscape. In our example we could create access controls `country.DE`, `country.US`, `country.ES`, etc. and assign those to the users. The method `permissionChecker.getPermittedCountriesForReading()` would then scan for these access controls and only return the 2-letter country code from it. CAUTION: Before you make your decisions how to design your access controls please clarify the following questions: * Do you need to separate data-permissions independent of the functional permissions? E.g. may it be required to express that a user can read data from the countries `ES` and `PL` but is only permitted to modify data from `PL`? In such case a single assignment of "country-permissions" to users is insufficient. * Do you want to grant data-permissions individually for each application (higher flexibility and complexity) or for the entire application landscape (simplicity, better maintenance for administrators)? In case of the first approach you would rather have access controls like `app1.country.GB` and `app2.country.GB`. * Do your data-permissions depend on objects that can be created dynamically inside your application? * If you want to grant data-permissions on other business objects (entities), how do you want to reference them (primary keys, business keys, etc.)? What reference is most stable? Which is most readable?