diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/BibliographicReferenceDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/BibliographicReferenceDaoImpl.java index 92b23f067e..e72954d538 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/BibliographicReferenceDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/BibliographicReferenceDaoImpl.java @@ -75,7 +75,7 @@ public long countDistinctWithRelatedExperiments() { .createQuery( "select count(" + ( AclQueryUtils.requiresCountDistinct() ? "distinct " : "" ) + " b)" + " " + "from ExpressionExperiment e join e.primaryPublication b " + AclQueryUtils.formAclRestrictionClause( "e.id" ) ); - AclQueryUtils.addAclParameters( q, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( q, ExpressionExperiment.class ); return ( Long ) q.uniqueResult(); } @@ -87,7 +87,7 @@ public long countWithRelatedExperiments() { .createQuery( "select count(" + ( AclQueryUtils.requiresCountDistinct() ? "distinct " : "" ) + " e)" + " " + "from ExpressionExperiment e join e.primaryPublication b" + AclQueryUtils.formAclRestrictionClause( "e.id" ) ); - AclQueryUtils.addAclParameters( q, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( q, ExpressionExperiment.class ); return ( Long ) q.uniqueResult(); } @@ -99,7 +99,7 @@ public LinkedHashMap os = q .setFirstResult( offset ) @@ -126,7 +126,7 @@ public LinkedHashMap> g + "and b in (:recs) " + ( AclQueryUtils.requiresGroupBy() ? "group by b, e " : "" ) + "order by b.authorList nulls last, b.title nulls last" ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); List os = QueryUtils.listByIdentifiableBatch( query, "recs", records, eeBatchSize ); LinkedHashMap> result = new LinkedHashMap<>(); for ( Object[] o : os ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java index 45415335b8..fdbfc4727c 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java @@ -29,6 +29,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.StandardBasicTypes; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.acls.domain.BasePermission; import org.springframework.stereotype.Repository; import org.springframework.util.Assert; import ubic.gemma.core.ontology.OntologyUtils; @@ -45,7 +46,7 @@ import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.GeneSet; import ubic.gemma.persistence.service.AbstractNoopFilteringVoEnabledDao; -import ubic.gemma.persistence.util.EE2CAclQueryUtils; +import ubic.gemma.persistence.util.AclDenormalizedTableQueryUtils; import ubic.gemma.persistence.util.IdentifiableUtils; import ubic.gemma.persistence.util.QueryUtils; @@ -56,6 +57,7 @@ import static org.hibernate.criterion.Restrictions.like; import static org.hibernate.criterion.Restrictions.not; +import static ubic.gemma.persistence.service.maintenance.TableMaintenanceUtil.EE2C_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN; import static ubic.gemma.persistence.service.maintenance.TableMaintenanceUtil.EE2C_QUERY_SPACE; import static ubic.gemma.persistence.util.IdentifiableUtils.toIdentifiableSet; import static ubic.gemma.persistence.util.QueryUtils.*; @@ -242,11 +244,13 @@ public Map, Map> private Map, Map>> findExperimentsByUrisInternal( Collection uris, boolean includeSubjects, boolean includePredicates, boolean includeObjects, @Nullable Taxon taxon, boolean rankByLevel, int limit ) { String qs = "select T.`LEVEL`, T.VALUE_URI, T.PREDICATE_URI, T.OBJECT_URI, T.SECOND_PREDICATE_URI, T.SECOND_OBJECT_URI, T.EXPRESSION_EXPERIMENT_FK from EXPRESSION_EXPERIMENT2CHARACTERISTIC T" + ( taxon != null ? " join INVESTIGATION I on T.EXPRESSION_EXPERIMENT_FK = I.ID " : "" ) - + EE2CAclQueryUtils.formNativeAclJoinClause( "T.EXPRESSION_EXPERIMENT_FK" ) + " " + + AclDenormalizedTableQueryUtils.formNativeAclJoinClause( "T.EXPRESSION_EXPERIMENT_FK", "IS_AUTHENTICATED_ANONYMOUSLY" ) + " " + "where " + createPredicates( "T", "uris", includeSubjects, includePredicates, includeObjects ) + ( taxon != null ? " and I.TAXON_FK = :taxonId" : "" ) - + EE2CAclQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), "T.ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK" ) + + AclDenormalizedTableQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), + "IS_AUTHENTICATED_ANONYMOUSLY", "T." + EE2C_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN, + BasePermission.READ ) + ( rankByLevel ? " order by FIELD(T.LEVEL, :eeClass, :edClass, :bmClass)" : "" ); Query query = getSessionFactory().getCurrentSession().createSQLQuery( qs ) @@ -274,7 +278,7 @@ private Map, Map>> findExperimen query.setParameter( "taxonId", taxon.getId() ); } - EE2CAclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclDenormalizedTableQueryUtils.setAclParameters( query, "IS_AUTHENTICATED_ANONYMOUSLY", ExpressionExperiment.class ); query.setCacheable( true ); diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java index 185a481902..a20b3f42fe 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java @@ -25,6 +25,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.StandardBasicTypes; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.acls.domain.BasePermission; import org.springframework.stereotype.Repository; import org.springframework.util.Assert; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; @@ -43,6 +44,7 @@ import ubic.gemma.model.genome.sequenceAnalysis.BlatAssociation; import ubic.gemma.model.genome.sequenceAnalysis.BlatResult; import ubic.gemma.persistence.service.common.auditAndSecurity.curation.AbstractCuratableDao; +import ubic.gemma.persistence.service.maintenance.TableMaintenanceUtil; import ubic.gemma.persistence.util.*; import ubic.gemma.persistence.util.Filter; @@ -492,7 +494,7 @@ public long countExpressionExperiments( ArrayDesign arrayDesign ) { + AclQueryUtils.formAclRestrictionClause( "ee.id" ) + " " + "and ad = :ad" ) .setParameter( "ad", arrayDesign ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); return ( Long ) query.uniqueResult(); } @@ -503,7 +505,7 @@ public Map getPerTaxonCount() { + "join ad.primaryTaxon t " + AclQueryUtils.formAclRestrictionClause( "ad.id" ) + " " + "group by t" ); - AclQueryUtils.addAclParameters( query, ArrayDesign.class ); + AclQueryUtils.setAclParameters( query, ArrayDesign.class ); //noinspection unchecked List csList = query.setCacheable( true ).list(); return csList.stream().collect( Collectors.toMap( row -> ( Taxon ) row[0], row -> ( Long ) row[1] ) ); @@ -536,7 +538,7 @@ public long countSwitchedExpressionExperiments( ArrayDesign arrayDesign ) { + AclQueryUtils.formAclRestrictionClause( "e.id" ) + " " + "and b.originalPlatform = :arrayDesign" ) .setParameter( "arrayDesign", arrayDesign ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); return ( Long ) query .setCacheable( true ) .uniqueResult(); @@ -1059,7 +1061,7 @@ private Query finishFilteringQuery( String queryString, @Nullable Filters filter Query query = this.getSessionFactory().getCurrentSession().createQuery( queryString ); - AclQueryUtils.addAclParameters( query, ArrayDesign.class ); + AclQueryUtils.setAclParameters( query, ArrayDesign.class ); FilterQueryUtils.addRestrictionParameters( query, filters ); return query; @@ -1136,10 +1138,12 @@ private void populateBlacklisted( Collection vos ) { private void populateExpressionExperimentCount( Collection entities ) { Query query = this.getSessionFactory().getCurrentSession() .createSQLQuery( "select ee2ad.ARRAY_DESIGN_FK as ID, count(distinct ee2ad.EXPRESSION_EXPERIMENT_FK) as EE_COUNT from EXPRESSION_EXPERIMENT2ARRAY_DESIGN ee2ad " - + EE2CAclQueryUtils.formNativeAclJoinClause( "ee2ad.EXPRESSION_EXPERIMENT_FK" ) + " " + + AclDenormalizedTableQueryUtils.formNativeAclJoinClause( "ee2ad.EXPRESSION_EXPERIMENT_FK", "IS_AUTHENTICATED_ANONYMOUSLY" ) + " " + "where ee2ad.ARRAY_DESIGN_FK in :ids " + "and not ee2ad.IS_ORIGINAL_PLATFORM" - + EE2CAclQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), "ee2ad.ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK" ) + + AclDenormalizedTableQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), + "IS_AUTHENTICATED_ANONYMOUSLY", "ee2ad." + TableMaintenanceUtil.EE2AD_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN, + BasePermission.READ ) + formNativeNonTroubledClause( "ee2ad.EXPRESSION_EXPERIMENT_FK", ExpressionExperiment.class ) + " group by ee2ad.ARRAY_DESIGN_FK" ) .addScalar( "ID", StandardBasicTypes.LONG ) @@ -1150,7 +1154,7 @@ private void populateExpressionExperimentCount( Collection countById = QueryUtils.streamByBatch( query, "ids", IdentifiableUtils.getIds( entities ), 2048 ) .collect( Collectors.toMap( o -> ( Long ) o[0], o -> ( Long ) o[1] ) ); for ( ArrayDesignValueObject vo : entities ) { @@ -1162,12 +1166,14 @@ private void populateExpressionExperimentCount( Collection entities ) { Query query = this.getSessionFactory().getCurrentSession() .createSQLQuery( "select ee2ad.ARRAY_DESIGN_FK as ID, count(distinct ee2ad.EXPRESSION_EXPERIMENT_FK) as EE_COUNT from EXPRESSION_EXPERIMENT2ARRAY_DESIGN ee2ad " - + EE2CAclQueryUtils.formNativeAclJoinClause( "ee2ad.EXPRESSION_EXPERIMENT_FK" ) + " " + + AclDenormalizedTableQueryUtils.formNativeAclJoinClause( "ee2ad.EXPRESSION_EXPERIMENT_FK", "IS_AUTHENTICATED_ANONYMOUSLY" ) + " " + "where ee2ad.ARRAY_DESIGN_FK in :ids " + "and ee2ad.IS_ORIGINAL_PLATFORM " // ignore noop switches + "and ee2ad.ARRAY_DESIGN_FK not in (select ARRAY_DESIGN_FK from EXPRESSION_EXPERIMENT2ARRAY_DESIGN where EXPRESSION_EXPERIMENT_FK = ee2ad.EXPRESSION_EXPERIMENT_FK and ARRAY_DESIGN_FK = ee2ad.ARRAY_DESIGN_FK and not IS_ORIGINAL_PLATFORM)" - + EE2CAclQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), "ee2ad.ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK" ) + + AclDenormalizedTableQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), + "IS_AUTHENTICATED_ANONYMOUSLY", "ee2ad." + TableMaintenanceUtil.EE2AD_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN, + BasePermission.READ ) + formNativeNonTroubledClause( "ee2ad.EXPRESSION_EXPERIMENT_FK", ExpressionExperiment.class ) + " group by ee2ad.ARRAY_DESIGN_FK" ) .addScalar( "ID", StandardBasicTypes.LONG ) @@ -1178,7 +1184,7 @@ private void populateSwitchedExpressionExperimentCount( Collection switchedCountById = QueryUtils.streamByBatch( query, "ids", IdentifiableUtils.getIds( entities ), 2048 ) .collect( Collectors.toMap( row -> ( Long ) row[0], row -> ( Long ) row[1] ) ); for ( ArrayDesignValueObject vo : entities ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java index f3476362be..1071c4ba7a 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java @@ -99,7 +99,7 @@ protected Query getFilteringQuery( @Nullable Filters filters, @Nullable Sort sor + ( !SecurityUtil.isUserAdmin() ? " and ad.curationDetails.troubled = false" : "" ) + FilterQueryUtils.formRestrictionClause( filters ) + FilterQueryUtils.formOrderByClause( sort ) ); - AclQueryUtils.addAclParameters( query, ArrayDesign.class ); + AclQueryUtils.setAclParameters( query, ArrayDesign.class ); FilterQueryUtils.addRestrictionParameters( query, filters ); return query; } @@ -119,7 +119,7 @@ protected Query getFilteringIdQuery( @Nullable Filters filters, @Nullable Sort s + ( !SecurityUtil.isUserAdmin() ? " and ad.curationDetails.troubled = false" : "" ) + FilterQueryUtils.formRestrictionClause( filters ) + FilterQueryUtils.formOrderByClause( sort ) ); - AclQueryUtils.addAclParameters( query, ArrayDesign.class ); + AclQueryUtils.setAclParameters( query, ArrayDesign.class ); FilterQueryUtils.addRestrictionParameters( query, filters ); return query; } @@ -132,7 +132,7 @@ protected Query getFilteringCountQuery( @Nullable Filters filters ) { + AclQueryUtils.formAclRestrictionClause( "ad.id" ) + ( !SecurityUtil.isUserAdmin() ? " and ad.curationDetails.troubled = false" : "" ) + FilterQueryUtils.formRestrictionClause( filters ) ); - AclQueryUtils.addAclParameters( query, ArrayDesign.class ); + AclQueryUtils.setAclParameters( query, ArrayDesign.class ); FilterQueryUtils.addRestrictionParameters( query, filters ); return query; } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java index 5a72437049..ece0492db7 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java @@ -35,6 +35,7 @@ import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.acls.domain.BasePermission; import org.springframework.stereotype.Repository; import org.springframework.util.Assert; import ubic.gemma.core.analysis.singleCell.SingleCellMaskUtils; @@ -174,7 +175,7 @@ public List loadAllIdentifiers() { .createQuery( "select ee.id as id, ee.shortName as shortName, ee.name as name, accession.accession as accession from ExpressionExperiment ee " + "left join ee.accession accession " + AclQueryUtils.formAclRestrictionClause( "ee.id" ) ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); //noinspection unchecked return query.setResultTransformer( aliasToBean( Identifiers.class ) ).list(); } @@ -828,7 +829,7 @@ public Map getCategoriesUsageFrequency( @Nullable Collecti } String query = "select T.CATEGORY as CATEGORY, T.CATEGORY_URI as CATEGORY_URI, count(distinct T.EXPRESSION_EXPERIMENT_FK) as EE_COUNT from EXPRESSION_EXPERIMENT2CHARACTERISTIC T "; if ( doAclFiltering ) { - query += EE2CAclQueryUtils.formNativeAclJoinClause( "T.EXPRESSION_EXPERIMENT_FK" ) + " "; + query += AclDenormalizedTableQueryUtils.formNativeAclJoinClause( "T.EXPRESSION_EXPERIMENT_FK", "IS_AUTHENTICATED_ANONYMOUSLY" ) + " "; } if ( eeIds != null ) { query += "where T.EXPRESSION_EXPERIMENT_FK in :eeIds"; @@ -846,7 +847,9 @@ public Map getCategoriesUsageFrequency( @Nullable Collecti query += ")"; } if ( doAclFiltering ) { - query += EE2CAclQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), "T.ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK" ); + query += AclDenormalizedTableQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), + "IS_AUTHENTICATED_ANONYMOUSLY", "T." + EE2C_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN, + BasePermission.READ ); // troubled filtering query += formNativeNonTroubledClause( "T.EXPRESSION_EXPERIMENT_FK", ExpressionExperiment.class ); } @@ -871,7 +874,7 @@ public Map getCategoriesUsageFrequency( @Nullable Collecti q.setParameterList( "retainedTermUris", optimizeParameterList( retainedTermUris ) ); } if ( doAclFiltering ) { - EE2CAclQueryUtils.addAclParameters( q, ExpressionExperiment.class ); + AclDenormalizedTableQueryUtils.setAclParameters( q, "IS_AUTHENTICATED_ANONYMOUSLY", ExpressionExperiment.class ); } q.setCacheable( true ); List result; @@ -990,7 +993,7 @@ public Map getAnnotationsUsageFrequencyInternal( } String query = "select T." + valueColumn + " as `VALUE`, T." + valueUriColumn + " as VALUE_URI, " + ( isCategorized ? "T.CATEGORY" : "NULL" ) + " as CATEGORY, " + ( isCategorized ? "T.CATEGORY_URI" : "NULL" ) + " as CATEGORY_URI, T.EVIDENCE_CODE as EVIDENCE_CODE, count(distinct T.EXPRESSION_EXPERIMENT_FK) as EE_COUNT from EXPRESSION_EXPERIMENT2CHARACTERISTIC T "; if ( doAclFiltering ) { - query += EE2CAclQueryUtils.formNativeAclJoinClause( "T.EXPRESSION_EXPERIMENT_FK" ) + " "; + query += AclDenormalizedTableQueryUtils.formNativeAclJoinClause( "T.EXPRESSION_EXPERIMENT_FK", "IS_AUTHENTICATED_ANONYMOUSLY" ) + " "; } if ( eeIds != null ) { query += "where T.EXPRESSION_EXPERIMENT_FK in :eeIds"; @@ -1029,7 +1032,8 @@ else if ( category.startsWith( "http://" ) ) { query += ")"; } if ( doAclFiltering ) { - query += EE2CAclQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), "T.ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK" ); + query += AclDenormalizedTableQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), + "IS_AUTHENTICATED_ANONYMOUSLY", "T." + EE2C_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN, BasePermission.READ ); query += formNativeNonTroubledClause( "T.EXPRESSION_EXPERIMENT_FK", ExpressionExperiment.class ); } //language=HQL @@ -1079,7 +1083,7 @@ else if ( category.startsWith( "http://" ) ) { q.setParameter( "minFrequency", minFrequency ); } if ( doAclFiltering ) { - EE2CAclQueryUtils.addAclParameters( q, ExpressionExperiment.class ); + AclDenormalizedTableQueryUtils.setAclParameters( q, "IS_AUTHENTICATED_ANONYMOUSLY", ExpressionExperiment.class ); } q.setCacheable( true ); List result; @@ -1244,9 +1248,10 @@ public Map getTechnologyTypeUsageFrequency() { Query q = getSessionFactory().getCurrentSession() .createSQLQuery( "select AD.TECHNOLOGY_TYPE as TT, count(distinct EE2AD.EXPRESSION_EXPERIMENT_FK) as EE_COUNT from EXPRESSION_EXPERIMENT2ARRAY_DESIGN EE2AD " + "join ARRAY_DESIGN AD on EE2AD.ARRAY_DESIGN_FK = AD.ID " - + EE2CAclQueryUtils.formNativeAclJoinClause( "EE2AD.EXPRESSION_EXPERIMENT_FK" ) + " " + + AclDenormalizedTableQueryUtils.formNativeAclJoinClause( "EE2AD.EXPRESSION_EXPERIMENT_FK", "IS_AUTHENTICATED_ANONYMOUSLY" ) + " " + "where EE2AD.EXPRESSION_EXPERIMENT_FK is not NULL" - + EE2CAclQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), "EE2AD.ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK" ) + + AclDenormalizedTableQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), + "IS_AUTHENTICATED_ANONYMOUSLY", "EE2AD." + EE2AD_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN, BasePermission.READ ) + formNativeNonTroubledClause( "EE2AD.ARRAY_DESIGN_FK", ArrayDesign.class ) + formNativeNonTroubledClause( "EE2AD.EXPRESSION_EXPERIMENT_FK", ExpressionExperiment.class ) + " group by AD.TECHNOLOGY_TYPE" ) @@ -1256,7 +1261,7 @@ public Map getTechnologyTypeUsageFrequency() { .addSynchronizedEntityClass( ExpressionExperiment.class ) .addSynchronizedEntityClass( ArrayDesign.class ) .setCacheable( true ); - EE2CAclQueryUtils.addAclParameters( q, ExpressionExperiment.class ); + AclDenormalizedTableQueryUtils.setAclParameters( q, "IS_AUTHENTICATED_ANONYMOUSLY", ExpressionExperiment.class ); //noinspection unchecked List results = q.list(); return results.stream().collect( Collectors.groupingBy( row -> TechnologyType.valueOf( ( String ) row[0] ), Collectors.summingLong( row -> ( Long ) row[1] ) ) ); @@ -1306,11 +1311,12 @@ private Map getPlatformsUsageFrequency( boolean original, int Query query = getSessionFactory().getCurrentSession() .createSQLQuery( "select ad.*, count(distinct ee2ad.EXPRESSION_EXPERIMENT_FK) EE_COUNT from EXPRESSION_EXPERIMENT2ARRAY_DESIGN ee2ad " + "join ARRAY_DESIGN ad on ee2ad.ARRAY_DESIGN_FK = ad.ID " - + EE2CAclQueryUtils.formNativeAclJoinClause( "ee2ad.EXPRESSION_EXPERIMENT_FK" ) + " " + + AclDenormalizedTableQueryUtils.formNativeAclJoinClause( "ee2ad.EXPRESSION_EXPERIMENT_FK", "IS_AUTHENTICATED_ANONYMOUSLY" ) + " " + "where ee2ad.IS_ORIGINAL_PLATFORM = :original" // exclude noop switch + ( original ? " and ee2ad.ARRAY_DESIGN_FK not in (select ARRAY_DESIGN_FK from EXPRESSION_EXPERIMENT2ARRAY_DESIGN where EXPRESSION_EXPERIMENT_FK = ee2ad.EXPRESSION_EXPERIMENT_FK and ARRAY_DESIGN_FK = ee2ad.ARRAY_DESIGN_FK and not IS_ORIGINAL_PLATFORM)" : "" ) - + EE2CAclQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), "ee2ad.ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK" ) + " " + + AclDenormalizedTableQueryUtils.formNativeAclRestrictionClause( ( SessionFactoryImplementor ) getSessionFactory(), + "IS_AUTHENTICATED_ANONYMOUSLY", "ee2ad." + EE2AD_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN, BasePermission.READ ) + " " // exclude troubled platforms or experiments for non-admins + formNativeNonTroubledClause( "ee2ad.ARRAY_DESIGN_FK", ArrayDesign.class ) + formNativeNonTroubledClause( "ee2ad.EXPRESSION_EXPERIMENT_FK", ExpressionExperiment.class ) @@ -1325,8 +1331,8 @@ private Map getPlatformsUsageFrequency( boolean original, int .addSynchronizedEntityClass( ExpressionExperiment.class ) .addSynchronizedEntityClass( ArrayDesign.class ); query.setParameter( "original", original ); - EE2CAclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); - EE2CAclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclDenormalizedTableQueryUtils.setAclParameters( query, "IS_AUTHENTICATED_ANONYMOUSLY", ExpressionExperiment.class ); + AclDenormalizedTableQueryUtils.setAclParameters( query, "IS_AUTHENTICATED_ANONYMOUSLY", ExpressionExperiment.class ); query.setCacheable( true ); List result; //noinspection unchecked @@ -1596,7 +1602,7 @@ public Map getPerTaxonCount() { Query query = this.getSessionFactory().getCurrentSession().createQuery( queryString ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); // it is important to cache this, as it gets called on the home page. Though it's actually fast. //noinspection unchecked @@ -3868,7 +3874,7 @@ private Query finishFilteringQuery( String queryString, @Nullable Filters filter Query query = this.getSessionFactory().getCurrentSession().createQuery( queryString ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); FilterQueryUtils.addRestrictionParameters( query, filters ); return query; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java index a52cc48909..e9c2393272 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java @@ -82,7 +82,7 @@ public Slice loadAll( int offset, int limit ) { + AclQueryUtils.formAclRestrictionClause( "ee.id" ) + " " + "and ee.experimentalDesign = ed " + ( AclQueryUtils.requiresGroupBy() ? " group by fv" : "" ) ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); //noinspection unchecked return new Slice<>( ( List ) query .setFirstResult( offset ) @@ -97,7 +97,7 @@ public Collection loadAllIds() { + AclQueryUtils.formAclRestrictionClause( "ee.id" ) + " " + "and ee.experimentalDesign = ed " + ( AclQueryUtils.requiresGroupBy() ? " group by fv" : "" ) ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); //noinspection unchecked return query.list(); } @@ -109,7 +109,7 @@ public Slice loadAllIds( int offset, int limit ) { + AclQueryUtils.formAclRestrictionClause( "ee.id" ) + " " + "and ee.experimentalDesign = ed" + ( AclQueryUtils.requiresGroupBy() ? " group by fv" : "" ) ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); //noinspection unchecked return new Slice<>( ( List ) query .setFirstResult( offset ) @@ -123,7 +123,7 @@ private long countAllWithAcls() { + "join fv.experimentalFactor ef join ef.experimentalDesign ed, ExpressionExperiment ee " + AclQueryUtils.formAclRestrictionClause( "ee.id" ) + " " + "and ee.experimentalDesign = ed" ); - AclQueryUtils.addAclParameters( countQuery, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( countQuery, ExpressionExperiment.class ); return ( Long ) countQuery.uniqueResult(); } @@ -135,7 +135,7 @@ public Collection findByValueStartingWith( String valuePrefix, int + "and ee.experimentalDesign = ed " + "and fv.value like :q" + ( AclQueryUtils.requiresGroupBy() ? " group by fv" : "" ) ); - AclQueryUtils.addAclParameters( query, ExpressionExperiment.class ); + AclQueryUtils.setAclParameters( query, ExpressionExperiment.class ); //noinspection unchecked return query .setParameter( "q", escapeLike( valuePrefix ) + "%" ) diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java index 5457024011..ac9c449876 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java @@ -228,7 +228,7 @@ public long getCompositeSequenceCount( Gene gene, boolean includeDummyProducts ) + "and gp.gene = :gene" + ( includeDummyProducts ? "" : " and gp.dummy = false" ) ) .setParameter( "gene", gene ); - AclQueryUtils.addAclParameters( query, ArrayDesign.class ); + AclQueryUtils.setAclParameters( query, ArrayDesign.class ); return ( Long ) query.uniqueResult(); } @@ -247,7 +247,7 @@ public long getCompositeSequenceCountById( long id, boolean includeDummyProducts + "and gp.gene.id = :id" + ( includeDummyProducts ? "" : " and gp.dummy = false" ) ) .setParameter( "id", id ); - AclQueryUtils.addAclParameters( query, ArrayDesign.class ); + AclQueryUtils.setAclParameters( query, ArrayDesign.class ); return ( Long ) query.uniqueResult(); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtil.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtil.java index 175bb78eca..5d1c300de4 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtil.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtil.java @@ -53,6 +53,11 @@ public interface TableMaintenanceUtil { */ String EE2C_QUERY_SPACE = "EXPRESSION_EXPERIMENT2CHARACTERISTIC"; + /** + * Column in the EE2C table that contains a permission mask for anonymous users. + */ + String EE2C_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN = "ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK"; + /** * Query space used by the {@code EXPRESSION_EXPERIMENT2ARRAY_DESIGN} table. *

@@ -62,6 +67,11 @@ public interface TableMaintenanceUtil { */ String EE2AD_QUERY_SPACE = "EXPRESSION_EXPERIMENT2_ARRAY_DESIGN"; + /** + * Column in the EE2AD table that contains a permission mask for anonymous users. + */ + String EE2AD_IS_AUTHENTICATED_ANONYMOUSLY_MASK_COLUMN = "ACL_IS_AUTHENTICATED_ANONYMOUSLY_MASK"; + /** * If necessary, update the GENE2CS table. */ diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/AclDenormalizedTableQueryUtils.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/AclDenormalizedTableQueryUtils.java new file mode 100644 index 0000000000..e25768c5fe --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/AclDenormalizedTableQueryUtils.java @@ -0,0 +1,102 @@ +package ubic.gemma.persistence.util; + +import gemma.gsec.util.SecurityUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.Query; +import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.type.IntegerType; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.Assert; +import ubic.gemma.model.common.auditAndSecurity.Securable; +import ubic.gemma.model.common.auditAndSecurity.SecuredChild; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class provides a fast-path to {@link AclQueryUtils} that uses denormalized permission masks for a specified + * granted authority. + * @author poirigui + */ +public class AclDenormalizedTableQueryUtils { + + /** + * Form a native ACL join clause for the given AOI ID column and granted authorities. + *

+ * The fast path is only taken if the user is an administrator or if it has at least one of the granted authorities + * that are relevant to the query. + * + * @see AclQueryUtils#formNativeAclJoinClause(String) + */ + public static String formNativeAclJoinClause( String aoiIdColumn, String grantedAuthority ) { + Assert.isTrue( StringUtils.isNotBlank( aoiIdColumn ), "The ACL object identity column cannot be empty." ); + Assert.isTrue( StringUtils.isNotBlank( grantedAuthority ), "The granted authority cannot be empty." ); + if ( SecurityUtil.isUserAdmin() || CollectionUtils.containsAny( getGrantedAuthorities(), grantedAuthority ) ) { + // nothing to do, we're in the fast path + return ""; + } else { + return AclQueryUtils.formNativeAclJoinClause( aoiIdColumn ); + } + } + + /** + * Form a native ACL restriction clause. + * @param sessionFactoryImplementor a session factory, used for generating queries + * @param grantedAuthority a granted authority relevant to the query + * @param grantedAuthorityPermissionMaskAlias the corresponding permission mask alias in the query + * @param permission a requested permission + */ + public static String formNativeAclRestrictionClause( SessionFactoryImplementor sessionFactoryImplementor, String grantedAuthority, String grantedAuthorityPermissionMaskAlias, Permission permission ) { + Assert.isTrue( StringUtils.isNotBlank( grantedAuthority ), "The granted authority cannot be empty." ); + Assert.isTrue( StringUtils.isNotBlank( grantedAuthorityPermissionMaskAlias ), "The granted authority permission mask alias must not be empty." ); + Assert.isTrue( permission.getMask() > 0, "At least one bit must be set in the permission mask." ); + if ( SecurityUtil.isUserAdmin() ) { + return ""; + } else { + if ( !getGrantedAuthorities().contains( grantedAuthority ) ) { + SQLFunction bitwiseAnd = sessionFactoryImplementor.getSqlFunctionRegistry().findSQLFunction( "bitwise_and" ); + // user has at least one relevant authority, use it to check permissions + return " and " + "(" + bitwiseAnd.render( new IntegerType(), Arrays.asList( grantedAuthorityPermissionMaskAlias, permission.getMask() ), sessionFactoryImplementor ) + " <> 0)"; + } else { + // user has no relevant authorities, default to the inefficient ACL check + return AclQueryUtils.formNativeAclRestrictionClause( sessionFactoryImplementor, permission ); + } + } + } + + /** + * Add ACL parameters to the query with an ACL clause for the given granted authorities by {@link #formNativeAclJoinClause(String, String)}. + * + * @param query a query on which ACL parameters should be set + * @param aoiType the type of secured object being queried + * @see AclQueryUtils#setAclParameters(Query, Class) + */ + public static void setAclParameters( Query query, String grantedAuthority, Class aoiType ) { + Assert.isTrue( StringUtils.isNotBlank( grantedAuthority ), "The granted authority cannot be empty." ); + Assert.isTrue( !SecuredChild.class.isAssignableFrom( aoiType ), + "ACL filtering cannot be done on a SecuredChild; instead identify the owner and apply ACLs on it." ); + //noinspection StatementWithEmptyBody + if ( SecurityUtil.isUserAdmin() || getGrantedAuthorities().contains( grantedAuthority ) ) { + // nothing to do, we're in the fast path + } else { + AclQueryUtils.setAclParameters( query, aoiType ); + } + } + + /** + * Obtain a set of granted authorities for the current user. + */ + private static Set getGrantedAuthorities() { + return SecurityContextHolder.getContext() + .getAuthentication() + .getAuthorities() + .stream() + .map( GrantedAuthority::getAuthority ) + .collect( Collectors.toSet() ); + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java index 87dd1ff86a..687e39e57d 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java @@ -25,7 +25,7 @@ *

  • form where clause and add your constraints
  • *
  • concatenate {@link #formNativeAclRestrictionClause(SessionFactoryImplementor)} in the clause section (only for native queries)
  • *
  • bind all your parameters
  • - *
  • bind ACL-specific parameters with {@link #addAclParameters(Query, Class)} to the query object
  • + *
  • bind ACL-specific parameters with {@link #setAclParameters(Query, Class)} to the query object
  • * * * @author poirigui @@ -83,7 +83,8 @@ public class AclQueryUtils { /** * Indicate if the ACL query requires a {@code count(distinct ...)} clause. *

    - * FIXME: remove the need for a distinct altogether by using a sub-query to apply ACLs (see #784) + * This is only applicable to {@link #formAclRestrictionClause(String, Permission)} and {@link #formNativeAclRestrictionClause(SessionFactoryImplementor, Permission)}, + * you do not need to use distinct clause when using a sub-query. */ public static boolean requiresCountDistinct() { return !SecurityUtil.isUserAdmin(); @@ -92,7 +93,8 @@ public static boolean requiresCountDistinct() { /** * Indicate if the ACL query requires a {@code group by} clause. *

    - * FIXME: remove the need for a count distinct altogether by using a sub-query to apply ACLs (see #784) + * This is only applicable to {@link #formAclRestrictionClause(String, Permission)} and {@link #formNativeAclRestrictionClause(SessionFactoryImplementor, Permission)}, + * you do not need to use a group by clause when using a sub-query. */ public static boolean requiresGroupBy() { return !SecurityUtil.isUserAdmin(); @@ -110,7 +112,7 @@ public static String formAclRestrictionClause( String aoiIdColumn ) { * Create an HQL join clause for {@link gemma.gsec.acl.domain.AclObjectIdentity}, {@link gemma.gsec.acl.domain.AclGrantedAuthoritySid} * and a restriction clause to limit the result only to objects the current user can access. *

    - * Ensure that you use {@link #addAclParameters(Query, Class)} afterward to bind the query parameters. + * Ensure that you use {@link #setAclParameters(Query, Class)} afterward to bind the query parameters. *

    * Important note: when using this, ensure that you have a {@code group by} clause in your query, otherwise * entities with multiple ACL entries will be duplicated in the results. You can use {@link #requiresGroupBy()} to @@ -120,15 +122,13 @@ public static String formAclRestrictionClause( String aoiIdColumn ) { * would be preferable? * * @param aoiIdColumn column name to match against the ACL object identity, the object class is passed via - * {@link #addAclParameters(Query, Class)} afterward + * {@link #setAclParameters(Query, Class)} afterward * @param permission requested permission(s) * @return clause to add to the query after any jointure */ public static String formAclRestrictionClause( String aoiIdColumn, Permission permission ) { - if ( StringUtils.isBlank( aoiIdColumn ) ) { - throw new IllegalArgumentException( "Object identity column cannot be empty." ); - } - Assert.isTrue( permission.getMask() > 0, "The mask must have at least one bit set." ); + Assert.isTrue( permission.getMask() > 0, "At least one bit must be set in the permission mask." ); + Assert.isTrue( StringUtils.isNotBlank( aoiIdColumn ), "Object identity column cannot be empty." ); //language=HQL String q = ", AclObjectIdentity as " + AOI_ALIAS + " join " + AOI_ALIAS + ".ownerSid " + SID_ALIAS; // for non-admin, we have to include aoi.entries @@ -138,22 +138,33 @@ public static String formAclRestrictionClause( String aoiIdColumn, Permission pe } q += " where (" + AOI_ALIAS + ".identifier = " + aoiIdColumn + " and " + AOI_ALIAS + ".type = :" + AOI_TYPE_PARAM + ")"; // add ACL restrictions - if ( !SecurityUtil.isUserAdmin() ) { - if ( SecurityUtil.isUserAnonymous() ) { - //language=HQL - q += " and (bitwise_and(" + ACE_ALIAS + ".mask, " + permission.getMask() + ") <> 0 and " + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + "))"; - } else { - q += " and (" - // user own the object - + SID_ALIAS + ".principal = :" + USER_NAME_PARAM + " " - // specific rights to the object - + "or (" + ACE_ALIAS + ".sid in (" + CURRENT_USER_SIDS_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + permission.getMask() + ") <> 0) " - // publicly available - + "or (" + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + permission.getMask() + ") <> 0)" - + ")"; - } + if ( SecurityUtil.isUserAnonymous() ) { + //language=HQL + return q + " and (bitwise_and(" + ACE_ALIAS + ".mask, " + permission.getMask() + ") <> 0 and " + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + "))"; + } else if ( !SecurityUtil.isUserAdmin() ) { + return q + " and (" + // user own the object + + SID_ALIAS + ".principal = :" + USER_NAME_PARAM + " " + // specific rights to the object + + "or (" + ACE_ALIAS + ".sid in (" + CURRENT_USER_SIDS_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + permission.getMask() + ") <> 0) " + // publicly available + + "or (" + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + permission.getMask() + ") <> 0)" + + ")"; + } else { + return q; + } + } + + public static String formAclRestrictionSubqueryClause( String aoiIdColumn, Permission permission ) { + Assert.isTrue( StringUtils.isNotBlank( aoiIdColumn ), "ACL object identity column cannot be empty." ); + Assert.isTrue( permission.getMask() > 0, "At least one bit must be set in the permission mask." ); + if ( SecurityUtil.isUserAnonymous() ) { + return " and "; + } else if ( !SecurityUtil.isUserAdmin() ) { + return " and "; + } else { + return ""; } - return q; } /** @@ -165,18 +176,15 @@ public static String formAclRestrictionClause( String aoiIdColumn, Permission pe * Important note: when using this, ensure that you have a {@code group by} clause in your query, otherwise * entities with multiple ACL entries will be duplicated in the results. * @param aoiIdColumn column name to match against the ACL object identity, the object class is passed via - * {@link #addAclParameters(Query, Class)} afterward + * {@link #setAclParameters(Query, Class)} afterward * * @see #formAclRestrictionClause(String) */ public static String formNativeAclJoinClause( String aoiIdColumn ) { - if ( StringUtils.isBlank( aoiIdColumn ) ) { - throw new IllegalArgumentException( "Object identity column cannot be empty." ); - } + Assert.isTrue( StringUtils.isNotBlank( aoiIdColumn ), "ACL object identity ID column cannot be empty." ); //language=SQL String q = " join ACLOBJECTIDENTITY " + AOI_ALIAS + " on (" + AOI_ALIAS + ".OBJECT_CLASS = :" + AOI_TYPE_PARAM + " and " + AOI_ALIAS + ".OBJECT_ID = " + aoiIdColumn + ") " + "join ACLSID " + SID_ALIAS + " on (" + SID_ALIAS + ".ID = " + AOI_ALIAS + ".OWNER_SID_FK)"; - // for non-admin, we have to include aoi.entries // if aoi.entries is empty, the user might still be the owner, so we use a left join if ( !SecurityUtil.isUserAdmin() ) { @@ -201,6 +209,7 @@ public static String formNativeAclRestrictionClause( SessionFactoryImplementor s * @see #formAclRestrictionClause(String, Permission) */ public static String formNativeAclRestrictionClause( SessionFactoryImplementor sessionFactoryImplementor, Permission permission ) { + Assert.isTrue( permission.getMask() > 0, "At least one bit must be set in the permission mask." ); SQLFunction bitwiseAnd = sessionFactoryImplementor.getSqlFunctionRegistry().findSQLFunction( "bitwise_and" ); String renderedMask = bitwiseAnd.render( new IntegerType(), Arrays.asList( ACE_ALIAS + ".MASK", permission.getMask() ), sessionFactoryImplementor ); //language=SQL @@ -221,6 +230,31 @@ public static String formNativeAclRestrictionClause( SessionFactoryImplementor s } } + /** + * Native flavour of the ACL restriction subquery clause. + * + * @see #formAclRestrictionSubqueryClause(String, Permission) + */ + public static String formNativeAclRestrictionSubqueryClause( SessionFactoryImplementor sessionFactoryImplementor, String aoiIdColumn, Permission permission ) { + Assert.isTrue( StringUtils.isNotBlank( aoiIdColumn ), "ACL object identity ID column cannot be empty." ); + Assert.isTrue( permission.getMask() > 0, "At least one bit must be set in the permission mask." ); + SQLFunction bitwiseAnd = sessionFactoryImplementor.getSqlFunctionRegistry().findSQLFunction( "bitwise_and" ); + String renderedMask = bitwiseAnd.render( new IntegerType(), Arrays.asList( ACE_ALIAS + ".MASK", permission.getMask() ), sessionFactoryImplementor ); + //language=SQL + if ( SecurityUtil.isUserAnonymous() ) { + return " and " + aoiIdColumn + " in (select ACLOBJECTIDENTITY.OBJECT_ID from ACLOBJECTIDENTITY aoi " + + "left join ACLENTRY " + ACE_ALIAS + " on aoi.ID = " + ACE_ALIAS + ".OBJECTIDENTITY_FK " + + "where aoi.OBJECT_CLASS = :" + AOI_TYPE_PARAM + " and " + renderedMask + " <> 0))"; + } else if ( !SecurityUtil.isUserAdmin() ) { + return " and " + aoiIdColumn + " in (select ACLOBJECTIDENTITY.OBJECT_ID from ACLOBJECTIDENTITY aoi " + + "left join ACLENTRY " + ACE_ALIAS + " on aoi.ID = " + ACE_ALIAS + ".OBJECTIDENTITY_FK " + + "where aoi.OBJECT_CLASS = :" + AOI_TYPE_PARAM + " and " + renderedMask + " <> 0))"; + } else { + // For administrators, no filtering is needed, so the ACE is completely skipped from the where clause. + return ""; + } + } + /** * Bind {@link Query} parameters to a join clause generated with {@link #formAclRestrictionClause(String)} and add ACL * restriction parameters defined in {@link #formAclRestrictionClause(String)}. @@ -234,10 +268,9 @@ public static String formNativeAclRestrictionClause( SessionFactoryImplementor s * {@link #formAclRestrictionClause(String)}. */ @SuppressWarnings("StatementWithEmptyBody") - public static void addAclParameters( Query query, Class aoiType ) throws QueryParameterException { - if ( SecuredChild.class.isAssignableFrom( aoiType ) ) { - throw new IllegalArgumentException( "ACL filtering cannot be done on a SecuredChild; instead identify the owner and apply ACLs on it." ); - } + public static Query setAclParameters( Query query, Class aoiType ) throws QueryParameterException { + Assert.isTrue( !SecuredChild.class.isAssignableFrom( aoiType ), + "ACL filtering cannot be done on a SecuredChild; instead identify the owner and apply ACLs on it." ); query.setParameter( AOI_TYPE_PARAM, aoiType.getCanonicalName() ); if ( SecurityUtil.isUserAnonymous() ) { // a constant is used directly in ANONYMOUS_SID_SQL, so no binding is necessary @@ -246,5 +279,6 @@ public static void addAclParameters( Query query, Class aoi } else { // For administrators, no filtering is needed, so the ACE is completely skipped from the where clause. } + return query; } } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/EE2CAclQueryUtils.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/EE2CAclQueryUtils.java deleted file mode 100644 index b03dba9552..0000000000 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/EE2CAclQueryUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -package ubic.gemma.persistence.util; - -import gemma.gsec.util.SecurityUtil; -import org.hibernate.Query; -import org.hibernate.dialect.function.SQLFunction; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.type.IntegerType; -import org.springframework.security.acls.domain.BasePermission; -import org.springframework.security.acls.model.Permission; -import ubic.gemma.model.common.auditAndSecurity.Securable; - -import java.util.Arrays; - -/** - * This class provides a fast-path to {@link AclQueryUtils} that uses the denormalized mask for anonymous users. - * @author poirigui - */ -public class EE2CAclQueryUtils { - - public static String formNativeAclJoinClause( String aoiIdColumn ) { - // ACLs are only necessary for regular, non-admin users - if ( SecurityUtil.isUserAnonymous() || SecurityUtil.isUserAdmin() ) { - return ""; - } else { - return AclQueryUtils.formNativeAclJoinClause( aoiIdColumn ); - } - } - - public static String formNativeAclRestrictionClause( SessionFactoryImplementor sessionFactoryImplementor, String anonymousMaskColumn ) { - return formNativeAclRestrictionClause( sessionFactoryImplementor, anonymousMaskColumn, BasePermission.READ ); - } - - public static String formNativeAclRestrictionClause( SessionFactoryImplementor sessionFactoryImplementor, String anonymousMaskColumn, Permission permission ) { - if ( SecurityUtil.isUserAnonymous() ) { - SQLFunction bitwiseAnd = sessionFactoryImplementor.getSqlFunctionRegistry().findSQLFunction( "bitwise_and" ); - String renderedMask = bitwiseAnd.render( new IntegerType(), Arrays.asList( anonymousMaskColumn, permission.getMask() ), sessionFactoryImplementor ); - return " and " + renderedMask + " <> 0"; - } else if ( SecurityUtil.isUserAdmin() ) { - return ""; - } else { - return AclQueryUtils.formNativeAclRestrictionClause( sessionFactoryImplementor, permission ); - } - } - - public static void addAclParameters( Query query, Class aoiType ) { - if ( !SecurityUtil.isUserAdmin() && !SecurityUtil.isUserAnonymous() ) { - AclQueryUtils.addAclParameters( query, aoiType ); - } - } -} diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/util/AclQueryUtilsTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/util/AclQueryUtilsTest.java index 36c83916bb..c715676c97 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/util/AclQueryUtilsTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/util/AclQueryUtilsTest.java @@ -71,7 +71,7 @@ public void testFormAclJoinClauseAsNonAdminIncludesAoiEntriesInnerJointure() { @Test public void testAddAclJoinParameters() { Query query = mock( Query.class ); - addAclParameters( query, ExpressionExperiment.class ); + setAclParameters( query, ExpressionExperiment.class ); verify( query ).setParameter( "aclQueryUtils_aoiType", "ubic.gemma.model.expression.experiment.ExpressionExperiment" ); } @@ -116,7 +116,7 @@ public void testAsAdmin() { Query q = session.createQuery( "select ee from ExpressionExperiment ee" + formAclRestrictionClause( "ee.id" ) ); - addAclParameters( q, ExpressionExperiment.class ); + setAclParameters( q, ExpressionExperiment.class ); q.setMaxResults( 1 ); q.list(); } @@ -127,7 +127,7 @@ public void testAsUser() { Query q = session.createQuery( "select ee from ExpressionExperiment ee" + formAclRestrictionClause( "ee.id" ) ); - addAclParameters( q, ExpressionExperiment.class ); + setAclParameters( q, ExpressionExperiment.class ); q.setMaxResults( 1 ); q.list(); } @@ -138,7 +138,7 @@ public void testAsAnonymous() { Query q = session.createQuery( "select ee from ExpressionExperiment ee" + formAclRestrictionClause( "ee.id" ) ); - addAclParameters( q, ExpressionExperiment.class ); + setAclParameters( q, ExpressionExperiment.class ); q.setMaxResults( 1 ); q.list(); } @@ -151,7 +151,7 @@ public void testNative() { + "where {I}.class = 'ExpressionExperiment'" + formNativeAclRestrictionClause( ( SessionFactoryImplementor ) sessionFactory ) ) .addEntity( "I", ExpressionExperiment.class ); - addAclParameters( q, ExpressionExperiment.class ); + setAclParameters( q, ExpressionExperiment.class ); q.setMaxResults( 1 ); q.list(); } @@ -165,7 +165,7 @@ public void testNativeAsUser() { + "where {I}.class = 'ExpressionExperiment'" + formNativeAclRestrictionClause( ( SessionFactoryImplementor ) sessionFactory ) ) .addEntity( "I", ExpressionExperiment.class ); - addAclParameters( q, ExpressionExperiment.class ); + setAclParameters( q, ExpressionExperiment.class ); q.setMaxResults( 1 ); q.list(); } @@ -179,7 +179,7 @@ public void testNativeAsAnonymous() { + "where {I}.class = 'ExpressionExperiment'" + formNativeAclRestrictionClause( ( SessionFactoryImplementor ) sessionFactory ) ) .addEntity( "I", ExpressionExperiment.class ); - addAclParameters( q, ExpressionExperiment.class ); + setAclParameters( q, ExpressionExperiment.class ); q.setMaxResults( 1 ); q.list(); }