package org.molgenis.security.owned; import org.molgenis.data.*; import org.molgenis.data.aggregation.AggregateQuery; import org.molgenis.data.aggregation.AggregateResult; import org.molgenis.data.support.QueryImpl; import org.molgenis.security.core.runas.SystemSecurityToken; import org.molgenis.security.core.utils.SecurityUtils; import org.molgenis.util.EntityUtils; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Stream; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import static org.molgenis.security.owned.OwnedEntityType.OWNED; /** * RepositoryDecorator that works on EntityType that extends OwnedEntityType. * <p> * Ensures that when an Entity is created the owner is set to the current user, users can only view, update, delete * their own entities. * <p> * Admins are not effected. */ public class OwnedEntityRepositoryDecorator extends AbstractRepositoryDecorator<Entity> { private final Repository<Entity> decoratedRepo; public OwnedEntityRepositoryDecorator(Repository<Entity> decoratedRepo) { this.decoratedRepo = requireNonNull(decoratedRepo); } @Override protected Repository<Entity> delegate() { return decoratedRepo; } @Override public Query<Entity> query() { return new QueryImpl<>(this); } @Override public Iterator<Entity> iterator() { if (mustAddRowLevelSecurity()) return findAll(new QueryImpl<>()).iterator(); return decoratedRepo.iterator(); } @Override public void forEachBatched(Fetch fetch, Consumer<List<Entity>> consumer, int batchSize) { if (fetch != null) { fetch.field(OwnedEntityType.OWNER_USERNAME); } decoratedRepo.forEachBatched(fetch, entities -> { if (mustAddRowLevelSecurity()) { //TODO: This results in smaller batches! Should do a findAll instead! consumer.accept( entities.stream().filter(OwnedEntityRepositoryDecorator::currentUserIsOwner).collect(toList())); } else { consumer.accept(entities); } }, batchSize); } @Override public long count() { if (mustAddRowLevelSecurity()) return count(new QueryImpl<>()); return decoratedRepo.count(); } @Override public long count(Query<Entity> q) { if (mustAddRowLevelSecurity()) addRowLevelSecurity(q); return decoratedRepo.count(q); } @Override public Stream<Entity> findAll(Query<Entity> q) { if (mustAddRowLevelSecurity()) { addRowLevelSecurity(q); } return decoratedRepo.findAll(q); } @Override public Entity findOne(Query<Entity> q) { if (mustAddRowLevelSecurity()) addRowLevelSecurity(q); return decoratedRepo.findOne(q); } @Override public Entity findOneById(Object id) { Entity e = decoratedRepo.findOneById(id); if (mustAddRowLevelSecurity()) { if (!currentUserIsOwner(e)) return null; } return e; } @Override public Entity findOneById(Object id, Fetch fetch) { if (fetch != null) { fetch.field(OwnedEntityType.OWNER_USERNAME); } Entity e = decoratedRepo.findOneById(id, fetch); if (mustAddRowLevelSecurity()) { if (!currentUserIsOwner(e)) return null; } return e; } @Override public Stream<Entity> findAll(Stream<Object> ids) { Stream<Entity> entities = decoratedRepo.findAll(ids); if (mustAddRowLevelSecurity()) { entities = entities.filter(OwnedEntityRepositoryDecorator::currentUserIsOwner); } return entities; } @Override public Stream<Entity> findAll(Stream<Object> ids, Fetch fetch) { if (fetch != null) { fetch.field(OwnedEntityType.OWNER_USERNAME); } Stream<Entity> entities = decoratedRepo.findAll(ids, fetch); if (mustAddRowLevelSecurity()) { entities = entities.filter(OwnedEntityRepositoryDecorator::currentUserIsOwner); } return entities; } @Override public AggregateResult aggregate(AggregateQuery aggregateQuery) { if (mustAddRowLevelSecurity()) addRowLevelSecurity(aggregateQuery.getQuery()); return decoratedRepo.aggregate(aggregateQuery); } @Override public void update(Entity entity) { if (isOwnedEntityType() && (mustAddRowLevelSecurity() || entity.get(OwnedEntityType.OWNER_USERNAME) == null)) entity.set(OwnedEntityType.OWNER_USERNAME, SecurityUtils.getCurrentUsername()); decoratedRepo.update(entity); } @Override public void update(Stream<Entity> entities) { if (isOwnedEntityType()) { boolean mustAddRowLevelSecurity = mustAddRowLevelSecurity(); String currentUsername = SecurityUtils.getCurrentUsername(); entities = entities.map(entity -> { if (mustAddRowLevelSecurity || entity.get(OwnedEntityType.OWNER_USERNAME) == null) { entity.set(OwnedEntityType.OWNER_USERNAME, currentUsername); } return entity; }); } decoratedRepo.update(entities); } @Override public void delete(Entity entity) { if (mustAddRowLevelSecurity() && !currentUserIsOwner(entity)) return; decoratedRepo.delete(entity); } @Override public void delete(Stream<Entity> entities) { if (mustAddRowLevelSecurity()) { entities = entities.filter(OwnedEntityRepositoryDecorator::currentUserIsOwner); } decoratedRepo.delete(entities); } @Override public void deleteById(Object id) { if (mustAddRowLevelSecurity()) { Entity entity = findOneById(id); if ((entity != null) && !currentUserIsOwner(entity)) return; } decoratedRepo.deleteById(id); } @Override public void deleteAll(Stream<Object> ids) { if (mustAddRowLevelSecurity()) { delete(decoratedRepo.findAll(ids)); } else { decoratedRepo.deleteAll(ids); } } @Override public void deleteAll() { if (mustAddRowLevelSecurity()) { decoratedRepo.forEachBatched(entities -> delete(entities.stream()), 1000); } else { decoratedRepo.deleteAll(); } } @Override public void add(Entity entity) { if (isOwnedEntityType() && (mustAddRowLevelSecurity() || entity.get(OwnedEntityType.OWNER_USERNAME) == null)) { entity.set(OwnedEntityType.OWNER_USERNAME, SecurityUtils.getCurrentUsername()); } decoratedRepo.add(entity); } @Override public Integer add(Stream<Entity> entities) { if (isOwnedEntityType()) { boolean mustAddRowLevelSecurity = mustAddRowLevelSecurity(); String currentUsername = SecurityUtils.getCurrentUsername(); entities = entities.map(entity -> { if (mustAddRowLevelSecurity || entity.get(OwnedEntityType.OWNER_USERNAME) == null) { entity.set(OwnedEntityType.OWNER_USERNAME, currentUsername); } return entity; }); } return decoratedRepo.add(entities); } private boolean mustAddRowLevelSecurity() { return !(SecurityUtils.currentUserIsSu() || SecurityUtils.currentUserHasRole(SystemSecurityToken.ROLE_SYSTEM)) && isOwnedEntityType(); } private boolean isOwnedEntityType() { return EntityUtils.doesExtend(getEntityType(), OWNED); } private static void addRowLevelSecurity(Query<Entity> q) { String user = SecurityUtils.getCurrentUsername(); if (user != null) { if (!q.getRules().isEmpty()) q.and(); q.eq(OwnedEntityType.OWNER_USERNAME, user); } } private static String getOwnerUserName(Entity questionnaire) { return questionnaire.getString(OwnedEntityType.OWNER_USERNAME); } private static boolean currentUserIsOwner(Entity entity) { return null != entity && Objects.equals(SecurityUtils.getCurrentUsername(), getOwnerUserName(entity)); } }