Class AclQueryUtils
Query.
To build a query, sequentially proceed as follows:
- form your select clause and your jointures
- concatenate
formAclRestrictionClause(String)in the jointure section - form where clause and add your constraints
- concatenate
formNativeAclRestrictionClause(SessionFactoryImplementor, String)in the clause section (only for native queries) - bind all your parameters
- bind ACL-specific parameters with
addAclParameters(Query, Class)to the query object
EXISTS rewrite (Session 2 of the ACL EXISTS refactor)
formAclRestrictionClause(String, Permission) historically emitted a Cartesian
, AclObjectIdentity as aoi join aoi.ownerSid sid [left join aoi.entries ace] where (...)
fragment that multiplied result rows whenever an AOI had more than one matching ACE. Callers
worked around the row multiplication by sprinkling distinct and group by.
The emitted clause is now a correlated EXISTS sub-query against
AclObjectIdentity. Semantics are identical
(anonymous role check, group-grant check, owner-principal check, admin bypass) but no
row multiplication can occur, so the distinct / group by compensations have
been removed (Session 3 cleanup). The aoi / sid HQL aliases that the old
clause exposed in scope are no longer visible to the surrounding query —
callers that need the ACL info (only ExpressionExperimentDaoImpl.getFilteringQuery)
must post-fetch via loadAclInfoFor(org.hibernate.Session, java.util.Collection, Class).
Native callers: explicit id column (HQL_SQL_AUDIT C5)
Native callers previously had to invoke a formNativeAclJoinClause(String) shim before
formNativeAclRestrictionClause(SessionFactoryImplementor, String) so the id column
could be threaded across via a ThreadLocal. That coupling is gone: the restriction
clause now takes the aoiIdColumn as an explicit parameter, and the join shim has
been removed.
- Author:
- poirigui
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringAlias used for the object identityAclObjectIdentityand the owner identityAclSidinside the EXISTS body.static final StringAlias used for the object identityAclObjectIdentityand the owner identityAclSidinside the EXISTS body. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic voidaddAclParameters(org.hibernate.query.Query query, Class<? extends Securable> aoiType) BindQueryparameters to a join clause generated withformAclRestrictionClause(String)and add ACL restriction parameters defined informAclRestrictionClause(String).static StringformAclRestrictionClause(String aoiIdColumn) Create an HQL restriction clause with theBasePermission.READpermission.static StringformAclRestrictionClause(String aoiIdColumn, org.springframework.security.acls.model.Permission permission) Create an HQL restriction clause that limits the result only to objects the current user can access.static jakarta.persistence.criteria.PredicateformAclRestrictionPredicate(Session session, jakarta.persistence.criteria.CriteriaBuilder cb, jakarta.persistence.criteria.CommonAbstractCriteria query, jakarta.persistence.criteria.Expression<Long> aoiIdExpression, Class<? extends Securable> aoiType, org.springframework.security.acls.model.Permission permission) JPA Criteria counterpart offormAclRestrictionClause(String, Permission), for DAOs that build their queries withCriteriaBuilderrather than HQL strings.static StringformIsPubliclyReadableExpression(String idColumn) An HQL boolean expression that is true when the object is readable by an anonymous caller — that is, when it is PUBLIC.static StringformNativeAclRestrictionClause(SessionFactoryImplementor sessionFactoryImplementor, String aoiIdColumn) Native flavour of the ACL restriction clause with aBasePermission.READpermission.static StringformNativeAclRestrictionClause(SessionFactoryImplementor sessionFactoryImplementor, String aoiIdColumn, org.springframework.security.acls.model.Permission permission) Native flavour of the ACL restriction clause.static Map<Long, org.apache.commons.lang3.tuple.Pair<AclObjectIdentity, AclSid>> loadAclInfoFor(Session session, Collection<Long> ids, Class<? extends Securable> aoiType) Batched post-fetch of ACL info (object identity + owner SID) for a set of entity ids of a givenSecurabletype.
-
Field Details
-
AOI_ALIAS
Alias used for the object identityAclObjectIdentityand the owner identityAclSidinside the EXISTS body.Note: after the EXISTS rewrite,
formAclRestrictionClause(String, Permission)no longer leaves these aliases in the outer query's scope (the sub-query has its own scope). Native callers still rely on the aliases for theon-clause shape.- See Also:
-
SID_ALIAS
Alias used for the object identityAclObjectIdentityand the owner identityAclSidinside the EXISTS body.Note: after the EXISTS rewrite,
formAclRestrictionClause(String, Permission)no longer leaves these aliases in the outer query's scope (the sub-query has its own scope). Native callers still rely on the aliases for theon-clause shape.- See Also:
-
-
Constructor Details
-
AclQueryUtils
public AclQueryUtils()
-
-
Method Details
-
formIsPubliclyReadableExpression
An HQL boolean expression that is true when the object is readable by an anonymous caller — that is, when it is PUBLIC.Public is not a stored flag anywhere: it is an ACE granting READ to the anonymous SID, which is exactly what the anonymous branch of
formAclRestrictionClause(String, Permission)tests. This is that same test, shaped as an expression so a filter can compare it like an ordinary boolean property.🛑 It matches on
aoi.objectIdClass, the mapped BIGINT, and never onaoi.type, which is a formula resolving to a correlated subquery onacl_class. With the formula the planner re-ran that lookup per outer row and/datasets/countnever finished against production cardinalities.The parameter it declares is this class's own, so a caller that already runs
addAclParameters(Query, Class)binds it for free; that method binds only parameters the query actually declares.- Parameters:
idColumn- the outer query's id column, e.g.ee.id
-
formAclRestrictionClause
-
formAclRestrictionClause
public static String formAclRestrictionClause(String aoiIdColumn, org.springframework.security.acls.model.Permission permission) Create an HQL restriction clause that limits the result only to objects the current user can access.The clause is a correlated
EXISTSsub-query againstAclObjectIdentitythat preserves the pre-refactor security semantics (anonymous role check, group-grant check, owner-principal check, admin bypass) but does not multiply rows the way the old Cartesian join did. As a consequence:- Callers no longer need
distinct/group bycompensations; the historicalrequiresCountDistinct()/requiresGroupBy()helpers have been removed (Session 3 cleanup). - The
aoi/sidHQL aliases are no longer in scope in the surrounding query (the sub-query has its own scope). Callers that need to project ACL info must post-fetch vialoadAclInfoFor(org.hibernate.Session, java.util.Collection, Class).
The emission shape is
" where (exists (...))"(or empty for admin), so callers can continue to concatenate" and ..."after it the same way they did with the old JOIN clause. Empty for admin because no filtering is needed; callers that need awhereclause for their own predicates must therefore introduce one themselves — but in practice the existing pattern is"from X x " + formAclRestrictionClause(...) + " and ..."which short-circuits in the admin case to"from X x and ..."which is invalid. TheformAclRestrictionClause(String, Permission)contract therefore now always emits at least a" where (1=1)"placeholder when the caller has no other WHERE, preserving the" and ..."concatenation idiom for all three principal classes.- Parameters:
aoiIdColumn- column name to match against the ACL object identity, the object class is passed viaaddAclParameters(Query, Class)afterwardpermission- requested permission(s)- Returns:
- clause to add to the query after any jointure
- Callers no longer need
-
formAclRestrictionPredicate
public static jakarta.persistence.criteria.Predicate formAclRestrictionPredicate(Session session, jakarta.persistence.criteria.CriteriaBuilder cb, jakarta.persistence.criteria.CommonAbstractCriteria query, jakarta.persistence.criteria.Expression<Long> aoiIdExpression, Class<? extends Securable> aoiType, org.springframework.security.acls.model.Permission permission) JPA Criteria counterpart offormAclRestrictionClause(String, Permission), for DAOs that build their queries withCriteriaBuilderrather than HQL strings.Why a third emitter
This class already carries two renderings of one set of semantics — HQL (
formAclRestrictionClause(String, Permission)) and native SQL (formNativeAclRestrictionClause(SessionFactoryImplementor, String, Permission)) — because a caller cannot concatenate an HQL fragment into a query built by another dialect.ExpressionAnalysisResultSetDaoImplis Criteria-built (its filters go throughFilterJpaUtils), so it could reach neither, andGET /resultSetstherefore ran with no ACL restriction at all. Converting that DAO to HQL would mean replacing its whole filter machinery to fix a missing WHERE clause.🛑 Only the EXISTS shape is transcribed here. The part that is subtle — which SIDs the current principal owns, via group membership — is NOT reimplemented:
CURRENT_USER_SIDS_HQLandANONYMOUS_SID_HQLare executed as-is and their results bound as an id set. So a change to how a user's SIDs are derived reaches this emitter for free, and the thing that could silently diverge is limited to a four-line boolean.🛑 Pass the id of the object that OWNS the ACEs, not of a SecuredChild
The EXISTS body tests the object identity's own entries and does not walk
parentAcl. ASecuredChildinherits rather than carrying entries, so restricting on one matches nothing — which is why this rejects them exactly asaddAclParameters(Query, Class)does. For a result set, whose chain is result set → analysis → experiment, the id to pass is the experiment's.- Parameters:
session- session used to resolve the current principal's SIDsquery- the enclosing query or subquery, needed to create the correlated subqueryaoiIdExpression- path to the id of the securable being restrictedaoiType- the securable's class, resolved toacl_class.idpermission- requested permission(s)- Returns:
- a predicate to AND into the caller's restrictions; an always-true conjunction for admins
-
loadAclInfoFor
public static Map<Long, org.apache.commons.lang3.tuple.Pair<AclObjectIdentity, AclSid>> loadAclInfoFor(Session session, Collection<Long> ids, Class<? extends Securable> aoiType) Batched post-fetch of ACL info (object identity + owner SID) for a set of entity ids of a givenSecurabletype. Replaces theselect ee, aoi, sidprojection that the old JOIN-form ACL clause supported — the EXISTS-form clause can no longer propagateaoi/sidinto the outer projection.- Parameters:
session- Hibernate session to run the query onids- the entity ids whose ACL info to fetch; empty input returns empty mapaoiType- the AOI type (the entity class) — used to scope the lookup- Returns:
- a map keyed by entity id, value is a
Pairof (AclObjectIdentity, AclSid). Ids without ACL rows are absent.
-
formNativeAclRestrictionClause
public static String formNativeAclRestrictionClause(SessionFactoryImplementor sessionFactoryImplementor, String aoiIdColumn) Native flavour of the ACL restriction clause with aBasePermission.READpermission.- See Also:
-
formNativeAclRestrictionClause
public static String formNativeAclRestrictionClause(SessionFactoryImplementor sessionFactoryImplementor, String aoiIdColumn, org.springframework.security.acls.model.Permission permission) Native flavour of the ACL restriction clause.Emits a self-contained
" and exists (...)"clause that correlates back to the outer query via the suppliedaoiIdColumn.- Parameters:
sessionFactoryImplementor- session factory implementor used to dialect-render the bitwise-AND fragmentaoiIdColumn- outer-query column to correlate againstacl_object_identity.object_id_identity; must be non-blank (must be a SQL column reference, never user input)permission- requested permission(s)- See Also:
-
addAclParameters
public static void addAclParameters(org.hibernate.query.Query query, Class<? extends Securable> aoiType) throws QueryParameterException BindQueryparameters to a join clause generated withformAclRestrictionClause(String)and add ACL restriction parameters defined informAclRestrictionClause(String).This method also work for native queries formed with
formNativeAclRestrictionClause(SessionFactoryImplementor, String).- Parameters:
query- aQueryobject that contains the join and restriction clausesaoiType- the AOI type to be bound in the query- Throws:
QueryParameterException- if any defined parameters are missing, which is typically due to a missing priorformAclRestrictionClause(String).
-