JPA Security intercepts your action with the EntityManager. Whenever you retrieve an entity from your EntityManager, it is subsidized by a proxy from JPA Security. Likewise whenever you perform a JPQL-query, it is modified with additional clauses and parameters to match your security rules.
JPA Security modifies the where-clause of your JPQL queries by adding restrictions according to your access rules. This behavior enforces security-rule-evaluation within the database. Only database rows resulting in entities that the user is allowed to read will be loaded. When using Hibernate as persistence provider, the Hibernate-WITH-clause is supported by JPA Security.
The proxies that are created around your entities are called SecureEntity (actually they implement an interface of the same name). This is how they behave:
Collection relationships (i.e. one-to-many- and many-to-many-relations) are handled via SecureCollections. Secure collections are filtered in memory and the backing collection will contain every entity of the original relationship. The main difference is that when you access any method of a secure collection it will behave, as if it only contained those entities you are allowed to read. In addition write-access will only be possible if write-access is allowed to the owning entity. Furthermore, for performance reasons every modification to a secure collection is queued and will not be executed until a commit operation.
Every entity that was loaded over a secured EntityManager can be casted to SecureEntity. This interface provides methods to programmatically check accessibility, force read- and write-check (via refresh() and flush()) and check the state of the entity.
A secure EntityManager can be casted to AccessManager, which allows programmatic security-checks, too.
On every operation that does not result into a query to the database JPA Security tries to check the configured access rules in memory. That means, for normal create-, update- and delete-operations, no database interaction is needed for the access check.
In-memory evaluation works perfectly, when no subselect is contained in the access rules. When the access rules contain subselects, some constraints are placed on the definition of the access rules. The first restriction is, that access rules that contain subselects may only contain subselects within EXISTS-clauses and not within IN-clauses. This restriction is likely to change in the future.
For example the following works:
GRANT ACCESS TO TestEntity entity WHERE EXISTS (SELECT e FROM TestEntity e WHERE e = entity AND ...)
Whereas the following will not work for the current release:
GRANT ACCESS TO TestEntity entity WHERE entity IN (SELECT e FROM TestEntity e WHERE ...)
Every subselect that contains only references to pathes to properties of the checked entity will work.
For example the following works, since acl is a direct reference to a property of the checked entity (indicated by acl = entity.acl):
GRANT ACCESS TO TestEntity entity WHERE EXISTS (SELECT acl FROM AccessControlList acl WHERE acl = entity.acl AND ...)
Whereas the following will not work since there is no direct path from a property of the checked entity to e (Reverse navigation would take place from e.acl to e, which is currently not supported).
GRANT ACCESS TO TestEntity entity WHERE EXISTS (SELECT e FROM AclEntry e WHERE e.acl = entity.acl)
The access rule could be rewritten to work with in-memory evaluation:
GRANT ACCESS TO TestEntity entity WHERE EXISTS (SELECT e FROM AccessControlList acl JOIN acl.entries e WHERE acl = entity.acl)
When in-memory evaluation cannot take place within the previously defined constraints, there is another chance to evaluate a query in memory: When the entities that are needed for the specified evaluation are already loaded within the specific EntityManager, evaluation will take place based on that entities. That means, if every AclEntry of the specific entity was loaded into memory by previous operations, the following access rule can be evaluated:
GRANT ACCESS TO TestEntity entity WHERE EXISTS (SELECT e FROM AclEntry e WHERE e.acl = entity.acl)
When an access rule cannot be evaluated the access-check will return false. This behavior will change in the future since it is not deterministic since the evaluation depends on previously loaded entities.