diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java index 92f5aa841aed5f759d433076700a8b1ce13e26dc..bfb1497d71a5ede778255f6640038d647a001547 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java @@ -26,6 +26,7 @@ package org.caosdb.server.database.backend.transaction; import java.util.LinkedList; import java.util.List; +import org.apache.shiro.subject.Subject; import org.caosdb.server.database.BackendTransaction; import org.caosdb.server.database.exceptions.EntityDoesNotExistException; import org.caosdb.server.datatype.CollectionValue; @@ -38,6 +39,7 @@ import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.Role; import org.caosdb.server.entity.container.Container; import org.caosdb.server.entity.wrapper.Property; +import org.caosdb.server.permissions.EntityPermission; import org.caosdb.server.query.Query; import org.caosdb.server.query.Query.Selection; import org.caosdb.server.utils.EntityStatus; @@ -57,19 +59,22 @@ import org.caosdb.server.utils.EntityStatus; public class RetrieveFullEntityTransaction extends BackendTransaction { private final Container<? extends EntityInterface> container; + private final Subject subject; - public RetrieveFullEntityTransaction(final EntityInterface entity) { - final Container<EntityInterface> c = new Container<>(); - c.add(entity); - this.container = c; + @SuppressWarnings("unchecked") + public RetrieveFullEntityTransaction(final EntityInterface entity, Subject subject) { + this(new Container<>(), subject); + ((Container<EntityInterface>) this.container).add(entity); } - public RetrieveFullEntityTransaction(final Container<? extends EntityInterface> container) { + public RetrieveFullEntityTransaction( + final Container<? extends EntityInterface> container, final Subject subject) { this.container = container; + this.subject = subject; } - public RetrieveFullEntityTransaction(final EntityID id) { - this(new RetrieveEntity(id)); + public RetrieveFullEntityTransaction(final EntityID id, Subject subject) { + this(new RetrieveEntity(id), subject); } @Override @@ -220,9 +225,17 @@ public class RetrieveFullEntityTransaction extends BackendTransaction { private void resolveReferenceValue( final ReferenceValue value, final List<Selection> selections, final String propertyName) { final RetrieveEntity ref = new RetrieveEntity(value.getId()); - // recursion! (Only for the matching selections) - retrieveFullEntity(ref, getSubSelects(selections, propertyName)); - value.setEntity(ref, true); + + if (this.subject != null) { + // recursion! (Only for the matching selections) + retrieveFullEntity(ref, getSubSelects(selections, propertyName)); + + // check whether the referenced entity is readable + if (!ref.getEntityACL().isPermitted(this.subject, EntityPermission.RETRIEVE_ENTITY)) { + return; + } + value.setEntity(ref, true); + } } /** diff --git a/src/main/java/org/caosdb/server/entity/RetrieveEntity.java b/src/main/java/org/caosdb/server/entity/RetrieveEntity.java index 00a120fbf023024eae8acc5788a9260d020a956d..e1b9e8c06fbc0d14a8719bd50635464a4c117326 100644 --- a/src/main/java/org/caosdb/server/entity/RetrieveEntity.java +++ b/src/main/java/org/caosdb/server/entity/RetrieveEntity.java @@ -24,6 +24,14 @@ */ package org.caosdb.server.entity; +/** + * Entity which is to be retrieved (i.e. read-only). + * + * <p>This class only exposes those constructors which are necessary for Entity which are to be + * retrieved (e.g. Entity(id)) and hide the others (e.g. Entity(XML-Represenation)). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class RetrieveEntity extends Entity { public RetrieveEntity() { diff --git a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java index 35d94aa79fd62a04f8b0dae1a17829a1106c2d97..bbe1749c91e2b102a7c41e78e00cd5f02904a5ff 100644 --- a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java @@ -169,14 +169,16 @@ public class EntityToElementStrategy implements ToElementStrategy { // processing SELECT Queries. final EntityInterface ref = ((ReferenceValue) entity.getValue()).getEntity(); if (ref != null) { - if (entity.hasDatatype()) { - setDatatype(entity, element); - } ref.addToElement(element, serializeFieldStrategy); - // the referenced entity has been appended. Return here to suppress - // adding the reference id as well. - return; + } else { + entity.getValue().addToElement(element); + } + if (entity.hasDatatype()) { + setDatatype(entity, element); } + // the referenced entity has been appended. Return here to suppress + // adding the reference id as well. + return; } else if (entity.isReferenceList() && serializeFieldStrategy.isToBeSet("_referenced")) { // Append the all referenced entities. This needs to be done when we are // processing SELECT Queries. diff --git a/src/main/java/org/caosdb/server/query/POV.java b/src/main/java/org/caosdb/server/query/POV.java index 6ee5f9d0122aa417c357e71ef6d6ea0516007215..597495f2d16d6e27229660d2341962d1dce20f91 100644 --- a/src/main/java/org/caosdb/server/query/POV.java +++ b/src/main/java/org/caosdb/server/query/POV.java @@ -405,6 +405,12 @@ public class POV implements EntityFilterInterface { } final long t2 = System.currentTimeMillis(); query.addBenchmark(measurement(".initPOVRefidsTable()"), t2 - t1); + + if (this.refIdsTable != null) { + query.getQuery().filterIntermediateResult(this.refIdsTable); + } + final long t3 = System.currentTimeMillis(); + query.addBenchmark(measurement(".filterRefidsWithoutRetrievePermission"), t3 - t2); try (PreparedStatement stmt = query.getConnection().prepareCall("call initPOVPropertiesTable(?,?,?)")) { // initPOVPropertiesTable(in pid INT UNSIGNED, in pname @@ -455,12 +461,12 @@ public class POV implements EntityFilterInterface { } } } - final long t3 = System.currentTimeMillis(); - query.addBenchmark(measurement(""), t3 - t2); + final long t4 = System.currentTimeMillis(); + query.addBenchmark(measurement(""), t4 - t3); if (this.refIdsTable != null) { query.getQuery().applyQueryTemplates(query, this.refIdsTable); - query.addBenchmark(measurement(".applyQueryTemplates()"), System.currentTimeMillis() - t3); + query.addBenchmark(measurement(".applyQueryTemplates()"), System.currentTimeMillis() - t4); } if (hasSubProperty() && this.targetSet != null) { diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index 117176ec5c6a1fcb10e588b860926b02eb76a2a9..168b7ac8b987ecfd75355580fd6af6409a54c4ac 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -72,11 +72,21 @@ import org.caosdb.server.permissions.EntityPermission; import org.caosdb.server.query.CQLParser.CqContext; import org.caosdb.server.query.CQLParsingErrorListener.ParsingError; import org.caosdb.server.transaction.EntityTransactionInterface; +import org.caosdb.server.transaction.Retrieve; import org.caosdb.server.transaction.Transaction; import org.caosdb.server.transaction.WriteTransaction; import org.jdom2.Element; import org.slf4j.Logger; +/** + * This class represents a single, complete Query execution from the parsing of the query string to + * the resulting list of entity ids. + * + * <p>This class handles caching of queries and checking retrieve permissions as well. It does not, + * however, retrieve the resulting entities; this is handled by the {@link Retrieve} class. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class Query implements QueryInterface, ToElementable, EntityTransactionInterface { /** Class which represents the selection of (sub)properties. */ diff --git a/src/main/java/org/caosdb/server/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java index 643c1201b530af9821c9a5c1a7b62f7c5d04cf52..54500c5932d7d8e4af41b5d88f79a2f5f914492f 100644 --- a/src/main/java/org/caosdb/server/transaction/Retrieve.java +++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java @@ -114,7 +114,7 @@ public class Retrieve extends Transaction<RetrieveContainer> { private void retrieveFullEntities(final RetrieveContainer container, final Access access) throws Exception { - execute(new RetrieveFullEntityTransaction(container), access); + execute(new RetrieveFullEntityTransaction(container, getTransactor()), access); } @Override diff --git a/src/main/java/org/caosdb/server/transaction/UpdateACL.java b/src/main/java/org/caosdb/server/transaction/UpdateACL.java index 289b5d53a14c642b4ac85bd9c02dd5c69484fa91..4bb27c2399d97bca0035ea27b5f7e4797428b18e 100644 --- a/src/main/java/org/caosdb/server/transaction/UpdateACL.java +++ b/src/main/java/org/caosdb/server/transaction/UpdateACL.java @@ -67,7 +67,8 @@ public class UpdateACL extends Transaction<TransactionContainer> oldContainer.add(new UpdateEntity(e.getId(), null)); } - RetrieveFullEntityTransaction t = new RetrieveFullEntityTransaction(oldContainer); + RetrieveFullEntityTransaction t = + new RetrieveFullEntityTransaction(oldContainer, getTransactor()); execute(t, getAccess()); // the entities in this container only have an id and an ACL. -> Replace diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java index 939d0bf6473611cb1a0bb1ee28bfd91a568babd3..08f80915f362274cbe2a20ee844afb7075864252 100644 --- a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java @@ -165,10 +165,10 @@ public class WriteTransaction extends Transaction<WritableContainer> // Retrieve a container which contains all IDs of those entities // which are to be updated. - execute(new RetrieveFullEntityTransaction(oldContainer), getAccess()); + execute(new RetrieveFullEntityTransaction(oldContainer, getTransactor()), getAccess()); // Retrieve all entities which are to be deleted. - execute(new RetrieveFullEntityTransaction(deleteContainer), getAccess()); + execute(new RetrieveFullEntityTransaction(deleteContainer, getTransactor()), getAccess()); // Check if any updates are to be processed. for (final EntityInterface entity : getContainer()) { diff --git a/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java b/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java index cfd8fcc9ba39cdf68dbf43be77de30f81c4fbec9..bb209f0eb5ea2a7438b7488955cb1a1d22424ca5 100644 --- a/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java +++ b/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java @@ -23,8 +23,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.Callable; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.Permission; +import org.apache.shiro.session.Session; +import org.apache.shiro.subject.ExecutionException; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; +import org.caosdb.server.CaosDBServer; +import org.caosdb.server.accessControl.Principal; import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.entity.Entity; import org.caosdb.server.entity.EntityID; @@ -32,15 +45,168 @@ import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.PropertyToElementStrategyTest; +import org.caosdb.server.permissions.EntityACL; import org.caosdb.server.query.Query.Selection; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class RetrieveFullEntityTest { + @BeforeAll + public static void setup() throws IOException { + CaosDBServer.initServerProperties(); + } + @Test public void testRetrieveSubEntities() { final RetrieveFullEntityTransaction r = - new RetrieveFullEntityTransaction(new EntityID("0")) { + new RetrieveFullEntityTransaction( + new EntityID("0"), + new Subject() { + + @Override + public Object getPrincipal() { + return new Principal("Bla", "Blub"); + } + + @Override + public PrincipalCollection getPrincipals() { + return null; + } + + @Override + public boolean isPermitted(String permission) { + assertEquals( + permission, org.caosdb.server.permissions.EntityPermission.RETRIEVE_ENTITY); + return true; + } + + @Override + public boolean isPermitted(Permission permission) { + return false; + } + + @Override + public boolean[] isPermitted(String... permissions) { + return null; + } + + @Override + public boolean[] isPermitted(List<Permission> permissions) { + return null; + } + + @Override + public boolean isPermittedAll(String... permissions) { + return false; + } + + @Override + public boolean isPermittedAll(Collection<Permission> permissions) { + return false; + } + + @Override + public void checkPermission(String permission) throws AuthorizationException {} + + @Override + public void checkPermission(Permission permission) throws AuthorizationException {} + + @Override + public void checkPermissions(String... permissions) throws AuthorizationException {} + + @Override + public void checkPermissions(Collection<Permission> permissions) + throws AuthorizationException {} + + @Override + public boolean hasRole(String roleIdentifier) { + return false; + } + + @Override + public boolean[] hasRoles(List<String> roleIdentifiers) { + return null; + } + + @Override + public boolean hasAllRoles(Collection<String> roleIdentifiers) { + return false; + } + + @Override + public void checkRole(String roleIdentifier) throws AuthorizationException {} + + @Override + public void checkRoles(Collection<String> roleIdentifiers) + throws AuthorizationException {} + + @Override + public void checkRoles(String... roleIdentifiers) throws AuthorizationException {} + + @Override + public void login(AuthenticationToken token) throws AuthenticationException {} + + @Override + public boolean isAuthenticated() { + return false; + } + + @Override + public boolean isRemembered() { + return false; + } + + @Override + public Session getSession() { + return null; + } + + @Override + public Session getSession(boolean create) { + return null; + } + + @Override + public void logout() {} + + @Override + public <V> V execute(Callable<V> callable) throws ExecutionException { + return null; + } + + @Override + public void execute(Runnable runnable) {} + + @Override + public <V> Callable<V> associateWith(Callable<V> callable) { + return null; + } + + @Override + public Runnable associateWith(Runnable runnable) { + return null; + } + + @Override + public void runAs(PrincipalCollection principals) + throws NullPointerException, IllegalStateException {} + + @Override + public boolean isRunAs() { + return false; + } + + @Override + public PrincipalCollection getPreviousPrincipals() { + return null; + } + + @Override + public PrincipalCollection releaseRunAs() { + return null; + } + }) { /** Mock-up */ @Override @@ -53,6 +219,7 @@ public class RetrieveFullEntityTest { assertEquals("description", selections.get(0).getSelector()); e.setDescription("A heart-shaped window."); + e.setEntityACL(new EntityACL()); } ; };