Class AclQueryUtils

java.lang.Object
ubic.gemma.persistence.util.AclQueryUtils

public class AclQueryUtils extends Object
Utilities for integrating ACL into Query.

To build a query, sequentially proceed as follows:

  1. form your select clause and your jointures
  2. concatenate formAclRestrictionClause(String) in the jointure section
  3. form where clause and add your constraints
  4. concatenate formNativeAclRestrictionClause(SessionFactoryImplementor, String) in the clause section (only for native queries)
  5. bind all your parameters
  6. 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 Details

  • Constructor Details

    • AclQueryUtils

      public AclQueryUtils()
  • Method Details

    • formIsPubliclyReadableExpression

      public static String formIsPubliclyReadableExpression(String idColumn)
      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 on aoi.type, which is a formula resolving to a correlated subquery on acl_class. With the formula the planner re-ran that lookup per outer row and /datasets/count never 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

      public static String formAclRestrictionClause(String aoiIdColumn)
      Create an HQL restriction clause with the BasePermission.READ permission.
      See Also:
    • 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 EXISTS sub-query against AclObjectIdentity that 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 by compensations; the historical requiresCountDistinct() / requiresGroupBy() helpers have been removed (Session 3 cleanup).
      • The aoi / sid HQL 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 via loadAclInfoFor(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 a where clause 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. The formAclRestrictionClause(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 via addAclParameters(Query, Class) afterward
      permission - requested permission(s)
      Returns:
      clause to add to the query after any jointure
    • 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 of formAclRestrictionClause(String, Permission), for DAOs that build their queries with CriteriaBuilder rather 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. ExpressionAnalysisResultSetDaoImpl is Criteria-built (its filters go through FilterJpaUtils), so it could reach neither, and GET /resultSets therefore 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_HQL and ANONYMOUS_SID_HQL are 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. A SecuredChild inherits rather than carrying entries, so restricting on one matches nothing — which is why this rejects them exactly as addAclParameters(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 SIDs
      query - the enclosing query or subquery, needed to create the correlated subquery
      aoiIdExpression - path to the id of the securable being restricted
      aoiType - the securable's class, resolved to acl_class.id
      permission - 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 given Securable type. Replaces the select ee, aoi, sid projection that the old JOIN-form ACL clause supported — the EXISTS-form clause can no longer propagate aoi/sid into the outer projection.
      Parameters:
      session - Hibernate session to run the query on
      ids - the entity ids whose ACL info to fetch; empty input returns empty map
      aoiType - the AOI type (the entity class) — used to scope the lookup
      Returns:
      a map keyed by entity id, value is a Pair of (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 a BasePermission.READ permission.
      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 supplied aoiIdColumn.

      Parameters:
      sessionFactoryImplementor - session factory implementor used to dialect-render the bitwise-AND fragment
      aoiIdColumn - outer-query column to correlate against acl_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
      Bind Query parameters to a join clause generated with formAclRestrictionClause(String) and add ACL restriction parameters defined in formAclRestrictionClause(String).

      This method also work for native queries formed with formNativeAclRestrictionClause(SessionFactoryImplementor, String).

      Parameters:
      query - a Query object that contains the join and restriction clauses
      aoiType - the AOI type to be bound in the query
      Throws:
      QueryParameterException - if any defined parameters are missing, which is typically due to a missing prior formAclRestrictionClause(String).