PL-10619 REST should have an ability to work without security token for entities with constraints

This commit is contained in:
Andrey Subbotin 2018-02-22 14:40:18 +04:00
parent d4a739d2ea
commit a499ca3dbb
8 changed files with 383 additions and 264 deletions

View File

@ -102,9 +102,17 @@ public interface PersistenceSecurity extends Security {
* Validate that security token exists for specific cases.
* For example, security constraints exists
* @param entity - entity to check security token
*/
void assertToken(Entity entity);
/**
* Validate that security token for REST exists for specific cases.
* For example, security constraints exists
* @param entity - entity to check security token
* @param view - view for entity
*/
void checkSecurityToken(Entity entity, View view);
void assertTokenForREST(Entity entity, View view);
/**
* Calculate filtered data

View File

@ -348,7 +348,7 @@ public class RdbmsStore implements DataStore {
for (Entity entity : context.getCommitInstances()) {
if (!PersistenceHelper.isNew(entity)) {
if (isAuthorizationRequired()) {
security.checkSecurityToken(entity, null);
security.assertToken(entity);
}
security.restoreSecurityStateAndFilteredData(entity);
attributeSecurity.beforeMerge(entity);
@ -377,7 +377,7 @@ public class RdbmsStore implements DataStore {
// remove
for (Entity entity : context.getRemoveInstances()) {
if (isAuthorizationRequired()) {
security.checkSecurityToken(entity, null);
security.assertToken(entity);
}
security.restoreSecurityStateAndFilteredData(entity);

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.core.app.importexport;
import com.haulmont.cuba.core.entity.Entity;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class CollectionCompare {
protected Consumer<Entity> createConsumer;
protected Consumer<Entity> deleteConsumer;
protected BiConsumer<Entity, Entity> updateConsumer;
private CollectionCompare() {
}
public static CollectionCompare with() {
return new CollectionCompare();
}
public CollectionCompare onCreate(Consumer<Entity> createConsumer) {
this.createConsumer = createConsumer;
return this;
}
public CollectionCompare onDelete(Consumer<Entity> deleteConsumer) {
this.deleteConsumer = deleteConsumer;
return this;
}
public CollectionCompare onUpdate(BiConsumer<Entity, Entity> updateConsumer) {
this.updateConsumer = updateConsumer;
return this;
}
public void compare(Collection<Entity> src, Collection<Entity> dst) {
final Collection<Entity> srcNN = Optional.ofNullable(src)
.orElse(Collections.emptyList());
final Collection<Entity> dstNN = Optional.ofNullable(dst)
.orElse(Collections.emptyList());
for (Entity srcEntity : srcNN) {
Optional<Entity> existingOptional = dstNN.stream()
.filter(e -> Objects.equals(e, srcEntity))
.findFirst();
if (existingOptional.isPresent()) {
updateConsumer.accept(srcEntity, existingOptional.get());
} else {
createConsumer.accept(srcEntity);
}
}
dstNN.stream().filter(item -> !srcNN.contains(item)).forEach(deleteConsumer);
}
}

View File

@ -17,14 +17,11 @@
package com.haulmont.cuba.core.app.importexport;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.Range;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.PersistenceSecurity;
import com.haulmont.cuba.core.Transaction;
import com.haulmont.cuba.core.app.DataStore;
import com.haulmont.cuba.core.app.RdbmsStore;
import com.haulmont.cuba.core.app.StoreFactory;
@ -54,6 +51,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.CRC32;
import static java.lang.String.format;
@ -91,6 +89,9 @@ public class EntityImportExport implements EntityImportExportAPI {
@Inject
protected ReferenceToEntitySupport referenceToEntitySupport;
@Inject
protected GlobalConfig globalConfig;
@Override
public byte[] exportEntitiesToZIP(Collection<? extends Entity> entities, View view) {
return exportEntitiesToZIP(reloadEntities(entities, view));
@ -286,25 +287,29 @@ public class EntityImportExport implements EntityImportExportAPI {
//we must specify a view here because otherwise we may get UnfetchedAttributeException during merge
commitContext.addInstanceToCommit(dstEntity, regularView);
SecurityState securityState = null;
if (srcEntity instanceof BaseGenericIdEntity && !createOp) {
String storeName = metadata.getTools().getStoreName(srcEntity.getMetaClass());
SecurityState dstSecurityState = null;
SecurityState srcSecurityState = null;
if (dstEntity instanceof BaseGenericIdEntity && !createOp) {
String storeName = metadata.getTools().getStoreName(dstEntity.getMetaClass());
DataStore dataStore = storeFactory.get(storeName);
//row-level security works only for entities from RdbmsStore
if (dataStore instanceof RdbmsStore) {
persistenceSecurity.checkSecurityToken(srcEntity, regularView);
persistenceSecurity.restoreSecurityState(srcEntity);
securityState = BaseEntityInternalAccess.getSecurityState(srcEntity);
if (useSecurityToken()) {
persistenceSecurity.assertTokenForREST(srcEntity, regularView);
persistenceSecurity.restoreSecurityState(srcEntity);
srcSecurityState = BaseEntityInternalAccess.getSecurityState(srcEntity);
}
persistenceSecurity.restoreSecurityState(dstEntity);
dstSecurityState = BaseEntityInternalAccess.getSecurityState(dstEntity);
}
}
for (EntityImportViewProperty importViewProperty : importView.getProperties()) {
String propertyName = importViewProperty.getName();
MetaProperty metaProperty = metaClass.getPropertyNN(propertyName);
if (BaseEntityInternalAccess.isHiddenOrReadOnly(securityState, propertyName)) {
if (BaseEntityInternalAccess.isHiddenOrReadOnly(dstSecurityState, propertyName)) {
continue;
}
if (BaseEntityInternalAccess.isRequired(securityState, propertyName) && srcEntity.getValue(propertyName) == null) {
if (BaseEntityInternalAccess.isRequired(dstSecurityState, propertyName) && srcEntity.getValue(propertyName) == null) {
throw new CustomValidationException(format("Attribute [%s] is required for entity %s", propertyName, srcEntity));
}
if ((metaProperty.getRange().isDatatype() && !"version".equals(metaProperty.getName())) || metaProperty.getRange().isEnum()) {
@ -319,13 +324,15 @@ public class EntityImportExport implements EntityImportExportAPI {
} else {
switch (metaProperty.getRange().getCardinality()) {
case MANY_TO_MANY:
importManyToManyCollectionAttribute(srcEntity, dstEntity, createOp, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
importManyToManyCollectionAttribute(srcEntity, dstEntity, srcSecurityState,
importViewProperty, regularPropertyView, commitContext, referenceInfoList);
break;
case ONE_TO_MANY:
importOneToManyCollectionAttribute(srcEntity, dstEntity, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
importOneToManyCollectionAttribute(srcEntity, dstEntity, srcSecurityState,
importViewProperty, regularPropertyView, commitContext, referenceInfoList);
break;
default:
importReference(srcEntity, dstEntity, createOp, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
importReference(srcEntity, dstEntity, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
}
}
}
@ -344,15 +351,14 @@ public class EntityImportExport implements EntityImportExportAPI {
protected void importReference(Entity srcEntity,
Entity dstEntity,
boolean createOp,
EntityImportViewProperty importViewProperty,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
Entity srcPropertyValue = srcEntity.<Entity>getValue(importViewProperty.getName());
Entity dstPropertyValue = dstEntity.<Entity>getValue(importViewProperty.getName());
Entity srcPropertyValue = srcEntity.getValue(importViewProperty.getName());
Entity dstPropertyValue = dstEntity.getValue(importViewProperty.getName());
if (importViewProperty.getView() == null) {
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, createOp, importViewProperty, srcPropertyValue, dstPropertyValue);
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, null, importViewProperty, srcPropertyValue, dstPropertyValue);
referenceInfoList.add(referenceInfo);
} else {
dstPropertyValue = importEntity(srcPropertyValue, dstPropertyValue, importViewProperty.getView(), regularView, commitContext, referenceInfoList);
@ -362,118 +368,98 @@ public class EntityImportExport implements EntityImportExportAPI {
protected void importOneToManyCollectionAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty importViewProperty,
SecurityState srcSecurityState,
EntityImportViewProperty viewProperty,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
String propertyName = importViewProperty.getName();
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(propertyName);
Collection<Entity> collectionValue = srcEntity.getValue(viewProperty.getName());
Collection<Entity> prevCollectionValue = dstEntity.getValue(viewProperty.getName());
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(viewProperty.getName());
MetaProperty inverseMetaProperty = metaProperty.getInverse();
//filteredItems collection will contain entities filtered by the row-level security
Multimap<String, Object> filteredItems = ArrayListMultimap.create();
if (srcEntity instanceof BaseGenericIdEntity) {
String storeName = metadata.getTools().getStoreName(srcEntity.getMetaClass());
DataStore dataStore = storeFactory.get(storeName);
//row-level security works only for entities from RdbmsStore
if (dataStore instanceof RdbmsStore) {
filteredItems = BaseEntityInternalAccess.getFilteredData(srcEntity);
}
}
Collection<Entity> srcPropertyValue = srcEntity.getValue(propertyName);
Collection<Entity> dstPropertyValue = dstEntity.getValue(propertyName);
if (dstPropertyValue == null) dstPropertyValue = new ArrayList<>();
Collection<Entity> collection;
try {
collection = srcPropertyValue.getClass().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error on import entities", e);
}
if (srcPropertyValue != null) {
for (Entity srcChildEntity : srcPropertyValue) {
if (importViewProperty.getView() != null) {
//create new referenced entity
Entity dstChildEntity = null;
for (Entity _entity : dstPropertyValue) {
if (_entity.equals(srcChildEntity)) {
dstChildEntity = _entity;
break;
Collection dstFilteredIds = getFilteredIds(dstEntity, metaProperty.getName());
Collection srcFilteredIds = getFilteredIds(srcSecurityState, metaProperty.getName());
Collection<Entity> newCollectionValue = createNewCollection(metaProperty);
CollectionCompare.with()
.onCreate(e -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(e))) {
Entity result = importEntity(e, null, viewProperty.getView(), regularView,
commitContext, referenceInfoList);
if (inverseMetaProperty != null) {
result.setValue(inverseMetaProperty.getName(), dstEntity);
}
newCollectionValue.add(result);
}
})
.onUpdate((src, dst) -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(src))) {
Entity result = importEntity(src, dst, viewProperty.getView(), regularView,
commitContext, referenceInfoList);
if (inverseMetaProperty != null) {
result.setValue(inverseMetaProperty.getName(), dstEntity);
}
newCollectionValue.add(result);
}
})
.onDelete(e -> {
Object refId = referenceToEntitySupport.getReferenceId(e);
if (viewProperty.getCollectionImportPolicy() == CollectionImportPolicy.REMOVE_ABSENT_ITEMS) {
if (!dstFilteredIds.contains(refId) && !srcFilteredIds.contains(refId)) {
commitContext.addInstanceToRemove(e);
}
}
dstChildEntity = importEntity(srcChildEntity, dstChildEntity, importViewProperty.getView(), regularView, commitContext, referenceInfoList);
if (inverseMetaProperty != null) {
dstChildEntity.setValue(inverseMetaProperty.getName(), dstEntity);
if (srcFilteredIds.contains(refId)) {
newCollectionValue.add(e);
}
collection.add(dstChildEntity);
}
}
}
if (importViewProperty.getCollectionImportPolicy() == CollectionImportPolicy.REMOVE_ABSENT_ITEMS) {
Collection<? extends Entity> dstValue = dstEntity.getValue(propertyName);
if (dstValue != null) {
Multimap<String, Object> finalFilteredItems = filteredItems;
List<? extends Entity> collectionItemsToRemove = dstValue.stream()
.filter(entity -> !collection.contains(entity) &&
(finalFilteredItems == null || !finalFilteredItems.containsValue(referenceToEntitySupport.getReferenceId(entity))))
.collect(Collectors.toList());
for (Entity _entity : collectionItemsToRemove) {
commitContext.addInstanceToRemove(_entity);
}
}
}
dstEntity.setValue(propertyName, collection);
})
.compare(collectionValue, prevCollectionValue);
dstEntity.setValue(metaProperty.getName(), newCollectionValue);
}
protected void importManyToManyCollectionAttribute(Entity srcEntity,
Entity dstEntity,
boolean createOp,
EntityImportViewProperty importViewProperty,
SecurityState srcSecurityState,
EntityImportViewProperty viewProperty,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
Collection<Entity> srcPropertyValue = srcEntity.getValue(importViewProperty.getName());
Collection<Entity> dstPropertyValue = dstEntity.getValue(importViewProperty.getName());
if (dstPropertyValue == null) dstPropertyValue = new ArrayList<>();
if (importViewProperty.getView() != null) {
//create/update passed entities
Collection<Entity> collection;
try {
collection = srcPropertyValue.getClass().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error on import entities", e);
}
Collection<Entity> collectionValue = srcEntity.getValue(viewProperty.getName());
Collection<Entity> prevCollectionValue = dstEntity.getValue(viewProperty.getName());
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(viewProperty.getName());
Collection dstFilteredIds = getFilteredIds(dstEntity, metaProperty.getName());
Collection srcFilteredIds = getFilteredIds(dstEntity, metaProperty.getName());
for (Entity srcChildEntity : srcPropertyValue) {
//create new referenced entity
Entity dstChildEntity = null;
for (Entity _entity : dstPropertyValue) {
if (_entity.equals(srcChildEntity)) {
dstChildEntity = _entity;
break;
}
}
dstChildEntity = importEntity(srcChildEntity, dstChildEntity, importViewProperty.getView(), regularView, commitContext, referenceInfoList);
collection.add(dstChildEntity);
}
if (importViewProperty.getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
Collection<Entity> existingCollectionValue = dstEntity.getValue(importViewProperty.getName());
if (existingCollectionValue != null) {
for (Entity existingCollectionItem : existingCollectionValue) {
if (!collection.contains(existingCollectionItem)) collection.add(existingCollectionItem);
}
}
}
dstEntity.setValue(importViewProperty.getName(), collection);
if (viewProperty.getView() != null) {
Collection<Entity> newCollectionValue = createNewCollection(metaProperty);
CollectionCompare.with()
.onCreate(e -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(e))) {
Entity result = importEntity(e, null, viewProperty.getView(), regularView,
commitContext, referenceInfoList);
newCollectionValue.add(result);
}
})
.onUpdate((src, dst) -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(src))) {
Entity result = importEntity(src, dst, viewProperty.getView(), regularView,
commitContext, referenceInfoList);
newCollectionValue.add(result);
}
})
.onDelete(e -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(e))) {
if (srcFilteredIds.contains(referenceToEntitySupport.getReferenceId(e))) {
newCollectionValue.add(e);
} else if (viewProperty.getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
newCollectionValue.add(e);
}
}
})
.compare(collectionValue, prevCollectionValue);
dstEntity.setValue(metaProperty.getName(), newCollectionValue);
} else {
//create ReferenceInfo objects - they will be parsed later
Collection<Entity> existingCollectionValue = dstEntity.getValue(importViewProperty.getName());
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, createOp, importViewProperty, srcPropertyValue, existingCollectionValue);
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, srcSecurityState, viewProperty, collectionValue, prevCollectionValue);
referenceInfoList.add(referenceInfo);
}
}
@ -497,24 +483,29 @@ public class EntityImportExport implements EntityImportExportAPI {
dstEmbeddedEntity = metadata.create(embeddedAttrMetaClass);
}
SecurityState securityState = null;
if (srcEntity instanceof BaseGenericIdEntity && !createOp) {
String storeName = metadata.getTools().getStoreName(srcEntity.getMetaClass());
SecurityState dstSecurityState = null;
SecurityState srcSecurityState = null;
if (dstEntity instanceof BaseGenericIdEntity && !createOp) {
String storeName = metadata.getTools().getStoreName(dstEntity.getMetaClass());
DataStore dataStore = storeFactory.get(storeName);
//row-level security works only for entities from RdbmsStore
if (dataStore instanceof RdbmsStore) {
persistenceSecurity.checkSecurityToken(srcEmbeddedEntity, null);
persistenceSecurity.restoreSecurityState(srcEmbeddedEntity);
securityState = BaseEntityInternalAccess.getSecurityState(srcEmbeddedEntity);
if (useSecurityToken()) {
persistenceSecurity.assertTokenForREST(srcEmbeddedEntity, regularView);
persistenceSecurity.restoreSecurityState(srcEmbeddedEntity);
srcSecurityState = BaseEntityInternalAccess.getSecurityState(srcEmbeddedEntity);
}
persistenceSecurity.restoreSecurityState(dstEmbeddedEntity);
dstSecurityState = BaseEntityInternalAccess.getSecurityState(dstEmbeddedEntity);
}
}
for (EntityImportViewProperty vp : importViewProperty.getView().getProperties()) {
MetaProperty mp = embeddedAttrMetaClass.getPropertyNN(vp.getName());
if (BaseEntityInternalAccess.isHiddenOrReadOnly(securityState, mp.getName())) {
if (BaseEntityInternalAccess.isHiddenOrReadOnly(dstSecurityState, mp.getName())) {
continue;
}
if (BaseEntityInternalAccess.isRequired(securityState, mp.getName()) && srcEmbeddedEntity.getValue(mp.getName()) == null) {
if (BaseEntityInternalAccess.isRequired(dstSecurityState, mp.getName()) && srcEmbeddedEntity.getValue(mp.getName()) == null) {
throw new CustomValidationException(format("Attribute [%s] is required for entity %s", mp.getName(), srcEmbeddedEntity));
}
if ((mp.getRange().isDatatype() && !"version".equals(mp.getName())) || mp.getRange().isEnum()) {
@ -522,11 +513,13 @@ public class EntityImportExport implements EntityImportExportAPI {
} else if (mp.getRange().isClass()) {
View propertyRegularView = regularView.getProperty(propertyName) != null ? regularView.getProperty(propertyName).getView() : null;
if (metaProperty.getRange().getCardinality() == Range.Cardinality.ONE_TO_MANY) {
importOneToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, vp, propertyRegularView, commitContext, referenceInfoList);
importOneToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, srcSecurityState,
vp, propertyRegularView, commitContext, referenceInfoList);
} else if (metaProperty.getRange().getCardinality() == Range.Cardinality.MANY_TO_MANY) {
importManyToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, false, vp, propertyRegularView, commitContext, referenceInfoList);
importManyToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, srcSecurityState,
vp, propertyRegularView, commitContext, referenceInfoList);
} else {
importReference(srcEmbeddedEntity, dstEmbeddedEntity, false, vp, propertyRegularView, commitContext, referenceInfoList);
importReference(srcEmbeddedEntity, dstEmbeddedEntity, vp, propertyRegularView, commitContext, referenceInfoList);
}
}
}
@ -540,109 +533,69 @@ public class EntityImportExport implements EntityImportExportAPI {
*/
protected void processReferenceInfo(ReferenceInfo referenceInfo, CommitContext commitContext, Set<Entity> loadedEntities) {
Entity entity = referenceInfo.getEntity();
String propertyName = referenceInfo.getViewProperty().getName();
MetaProperty metaProperty = entity.getMetaClass().getPropertyNN(propertyName);
EntityImportViewProperty viewProperty = referenceInfo.getViewProperty();
MetaProperty metaProperty = entity.getMetaClass().getPropertyNN(viewProperty.getName());
Collection dstFilteredIds = getFilteredIds(entity, metaProperty.getName());
Collection srcFilteredIds = getFilteredIds(referenceInfo.getPrevSecurityState(), metaProperty.getName());
if (metaProperty.getRange().getCardinality() == Range.Cardinality.MANY_TO_MANY) {
Collection<Entity> propertyValue = (Collection<Entity>) referenceInfo.getPropertyValue();
if (propertyValue == null) {
entity.setValue(propertyName, null);
@SuppressWarnings("unchecked")
Collection<Entity> collectionValue = (Collection<Entity>) referenceInfo.getPropertyValue();
@SuppressWarnings("unchecked")
Collection<Entity> prevCollectionValue = (Collection<Entity>) referenceInfo.getPrevPropertyValue();
if (collectionValue == null && srcFilteredIds.isEmpty()) {
entity.setValue(metaProperty.getName(), createNewCollection(metaProperty));
return;
}
Collection<Entity> collection;
try {
collection = propertyValue.getClass().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error on import entities", e);
}
for (Entity childEntity : propertyValue) {
Entity entityFromLoadedEntities = findEntityInCollection(loadedEntities, childEntity);
if (entityFromLoadedEntities != null) {
collection.add(entityFromLoadedEntities);
} else {
Entity entityFromCommitContext = findEntityInCollection(commitContext.getCommitInstances(), childEntity);
if (entityFromCommitContext != null) {
collection.add(entityFromCommitContext);
} else {
LoadContext<? extends Entity> ctx = LoadContext.create(childEntity.getClass())
.setSoftDeletion(false)
.setView(View.MINIMAL)
.setId(childEntity.getId());
Entity loadedReference = dataManager.load(ctx);
if (loadedReference == null) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + entity.getId() + " is missing");
Collection<Entity> newCollectionValue = createNewCollection(metaProperty);
CollectionCompare.with()
.onCreate(e -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(e))) {
Entity result = findReferenceEntity(e, viewProperty, commitContext, loadedEntities);
if (result != null) {
newCollectionValue.add(result);
}
} else {
collection.add(loadedReference);
loadedEntities.add(loadedReference);
}
}
}
}
//keep absent collection members if we need it
if (referenceInfo.getViewProperty().getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
Collection<Entity> prevCollectionValue = (Collection<Entity>) referenceInfo.getPrevPropertyValue();
if (prevCollectionValue != null) {
for (Entity prevCollectionItem : prevCollectionValue) {
if (!collection.contains(prevCollectionItem)) {
collection.add(prevCollectionItem);
})
.onUpdate((src, dst) -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(dst))) {
Entity result = findReferenceEntity(src, viewProperty, commitContext, loadedEntities);
if (result != null) {
newCollectionValue.add(result);
}
}
}
}
}
entity.setValue(propertyName, collection);
//row-level security works only for entities from RdbmsStore
String storeName = metadata.getTools().getStoreName(entity.getMetaClass());
DataStore dataStore = storeFactory.get(storeName);
if (dataStore instanceof RdbmsStore && !referenceInfo.isCreateOp()) {
//restore filtered data, otherwise they will be lost
try (Transaction tx = persistence.getTransaction()) {
persistenceSecurity.checkSecurityToken((BaseGenericIdEntity<?>) entity, null);
persistenceSecurity.restoreSecurityStateAndFilteredData((BaseGenericIdEntity<?>) entity);
tx.commit();
}
}
})
.onDelete(e -> {
if (!dstFilteredIds.contains(referenceToEntitySupport.getReferenceId(e))) {
if (srcFilteredIds.contains(referenceToEntitySupport.getReferenceId(e))) {
newCollectionValue.add(e);
} else if (viewProperty.getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
newCollectionValue.add(e);
}
}
})
.compare(collectionValue, prevCollectionValue);
entity.setValue(metaProperty.getName(), newCollectionValue);
//end of many-to-many processing block
} else {
//all other reference types (except many-to-many)
Entity propertyValue = (Entity) referenceInfo.getPropertyValue();
if (propertyValue == null) {
entity.setValue(propertyName, null);
//in case of NULL value we must delete COMPOSITION entities
if (metaProperty.getType() == MetaProperty.Type.COMPOSITION) {
Object prevPropertyValue = referenceInfo.getPrevPropertyValue();
if (prevPropertyValue != null) {
commitContext.addInstanceToRemove((Entity) prevPropertyValue);
Entity entityValue = (Entity) referenceInfo.getPropertyValue();
if (entityValue == null) {
if (dstFilteredIds.isEmpty()) {
entity.setValue(metaProperty.getName(), null);
//in case of NULL value we must delete COMPOSITION entities
if (metaProperty.getType() == MetaProperty.Type.COMPOSITION) {
Entity prevEntityValue = (Entity) referenceInfo.getPrevPropertyValue();
if (prevEntityValue != null) {
commitContext.addInstanceToRemove(prevEntityValue);
}
}
}
} else {
Entity entityFromLoadedEntities = findEntityInCollection(loadedEntities, propertyValue);
if (entityFromLoadedEntities != null) {
entity.setValue(propertyName, entityFromLoadedEntities);
} else {
Entity entityFromCommitContext = findEntityInCollection(commitContext.getCommitInstances(), propertyValue);
if (entityFromCommitContext != null) {
entity.setValue(propertyName, entityFromCommitContext);
} else {
LoadContext<? extends Entity> ctx = LoadContext.create(propertyValue.getClass())
.setSoftDeletion(false)
.setId(propertyValue.getId());
dataManager.load(ctx);
Entity loadedReference = dataManager.load(ctx);
if (loadedReference == null) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + propertyValue.getId() + " is missing");
}
} else {
entity.setValue(propertyName, loadedReference);
loadedEntities.add(loadedReference);
}
if (dstFilteredIds.isEmpty()) {
Entity result = findReferenceEntity(entityValue, viewProperty, commitContext, loadedEntities);
if (result != null) {
entity.setValue(metaProperty.getName(), result);
}
}
}
@ -673,27 +626,82 @@ public class EntityImportExport implements EntityImportExportAPI {
return regularView;
}
@Nullable
protected Entity findEntityInCollection(Collection<Entity> collection, Entity entity) {
for (Entity entityFromCollection : collection) {
if (entityFromCollection.equals(entity)) return entityFromCollection;
protected Collection getFilteredIds(Entity entity, String propertyName) {
if (entity instanceof BaseGenericIdEntity) {
String storeName = metadata.getTools().getStoreName(entity.getMetaClass());
DataStore dataStore = storeFactory.get(storeName);
if (dataStore instanceof RdbmsStore) {
persistenceSecurity.restoreSecurityState(entity);
return Optional.ofNullable(BaseEntityInternalAccess.getFilteredData(entity))
.map(v -> v.get(propertyName))
.orElse(Collections.emptyList());
}
}
return null;
return Collections.emptyList();
}
protected Collection getFilteredIds(SecurityState securityState, String propertyName) {
if (securityState != null) {
return Optional.ofNullable(BaseEntityInternalAccess.getFilteredData(securityState))
.map(v -> v.get(propertyName))
.orElse(Collections.emptyList());
}
return Collections.emptyList();
}
protected Collection<Entity> createNewCollection(MetaProperty metaProperty) {
Collection<Entity> entities;
Class<?> propertyType = metaProperty.getJavaType();
if (List.class.isAssignableFrom(propertyType)) {
entities = new ArrayList<>();
} else if (Set.class.isAssignableFrom(propertyType)) {
entities = new LinkedHashSet<>();
} else {
throw new RuntimeException(String.format("Could not instantiate collection with class [%s].", propertyType));
}
return entities;
}
protected boolean useSecurityToken() {
return globalConfig.getRestUseSecurityTokenForClient();
}
protected Entity findReferenceEntity(Entity entity, EntityImportViewProperty viewProperty, CommitContext commitContext,
Set<Entity> loadedEntities) {
Entity result = Stream.concat(loadedEntities.stream(), commitContext.getCommitInstances().stream())
.filter(item -> item.equals(entity))
.findFirst().orElse(null);
if (result == null) {
LoadContext<? extends Entity> ctx = LoadContext.create(entity.getClass())
.setSoftDeletion(false)
.setView(View.MINIMAL)
.setId(entity.getId());
result = dataManager.load(ctx);
if (result == null) {
if (viewProperty.getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException(String.format("Referenced entity for property '%s' with id = %s is missing",
viewProperty.getName(), entity.getId()));
}
} else {
loadedEntities.add(result);
}
}
return result;
}
protected class ReferenceInfo {
protected Entity entity;
protected boolean createOp;
protected SecurityState prevSecurityState;
protected EntityImportViewProperty viewProperty;
protected Object propertyValue;
protected Object prevPropertyValue;
public ReferenceInfo(Entity entity, boolean createOp, EntityImportViewProperty viewProperty, Object propertyValue, Object prevPropertyValue) {
public ReferenceInfo(Entity entity, SecurityState prevSecurityState, EntityImportViewProperty viewProperty, Object propertyValue, Object prevPropertyValue) {
this.entity = entity;
this.prevSecurityState = prevSecurityState;
this.viewProperty = viewProperty;
this.propertyValue = propertyValue;
this.prevPropertyValue = prevPropertyValue;
this.createOp = createOp;
}
public EntityImportViewProperty getViewProperty() {
@ -708,12 +716,12 @@ public class EntityImportExport implements EntityImportExportAPI {
return entity;
}
public SecurityState getPrevSecurityState() {
return prevSecurityState;
}
public Object getPropertyValue() {
return propertyValue;
}
public boolean isCreateOp() {
return createOp;
}
}
}

View File

@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.Serializable;
import java.util.*;
import java.util.function.BiPredicate;
import static java.lang.String.format;
@ -66,6 +67,9 @@ public class PersistenceSecurityImpl extends SecurityImpl implements Persistence
@Inject
protected EntityStates entityStates;
@Inject
protected GlobalConfig globalConfig;
@Override
public boolean applyConstraints(Query query) {
QueryParser parser = QueryTransformerFactory.createParser(query.getQueryString());
@ -213,30 +217,47 @@ public class PersistenceSecurityImpl extends SecurityImpl implements Persistence
}
@Override
public void checkSecurityToken(Entity entity, View view) {
public void assertToken(Entity entity) {
if (BaseEntityInternalAccess.getSecurityToken(entity) == null) {
MetaClass metaClass = metadata.getClassNN(entity.getClass());
for (MetaProperty metaProperty : metaClass.getProperties()) {
if (metaProperty.getRange().isClass() && metadataTools.isPersistent(metaProperty)) {
if (entityStates.isDetached(entity) && !entityStates.isLoaded(entity, metaProperty.getName())) {
continue;
} else if (view != null && !view.containsProperty(metaProperty.getName())) {
continue;
}
List<ConstraintData> existingConstraints = getConstraints(metaProperty.getRange().asClass(),
constraint -> constraint.getCheckType().memory());
if (CollectionUtils.isNotEmpty(existingConstraints)) {
throw new RowLevelSecurityException(format("Could not read security token from entity %s, " +
"even though there are active constraints for the related entities.", entity),
entity.getMetaClass().getName());
}
assertSecurityConstraints(entity, (e, metaProperty) -> entityStates.isDetached(entity)
&& !entityStates.isLoaded(entity, metaProperty.getName()));
assertTokenForAttributeAccess(entity);
}
}
@Override
public void assertTokenForREST(Entity entity, View view) {
if (BaseEntityInternalAccess.getSecurityToken(entity) == null) {
assertSecurityConstraints(entity,
(e, metaProperty) -> view != null && !view.containsProperty(metaProperty.getName()));
assertTokenForAttributeAccess(entity);
}
}
protected void assertSecurityConstraints(Entity entity, BiPredicate<Entity, MetaProperty> predicate) {
MetaClass metaClass = metadata.getClassNN(entity.getClass());
for (MetaProperty metaProperty : metaClass.getProperties()) {
if (metaProperty.getRange().isClass() && metadataTools.isPersistent(metaProperty)) {
if (predicate.test(entity, metaProperty)) {
continue;
}
if (hasInMemoryConstraints(metaProperty.getRange().asClass(), ConstraintOperationType.READ,
ConstraintOperationType.ALL)) {
throw new RowLevelSecurityException(format("Could not read security token from entity %s, " +
"even though there are active READ/ALL constraints for the property: %s", entity,
metaProperty.getName()),
entity.getMetaClass().getName());
}
}
if (attributeSecuritySupport.isAttributeAccessEnabled(metaClass)) {
throw new RowLevelSecurityException(format("Could not read security token from entity %s, " +
"even though there are active attribute access for the entity.", entity),
entity.getMetaClass().getName());
}
}
}
protected void assertTokenForAttributeAccess(Entity entity) {
MetaClass metaClass = metadata.getClassNN(entity.getClass());
if (attributeSecuritySupport.isAttributeAccessEnabled(metaClass)) {
throw new RowLevelSecurityException(format("Could not read security token from entity %s, " +
"even though there are active attribute access for the entity.", entity),
entity.getMetaClass().getName());
}
}

View File

@ -66,10 +66,7 @@ public class SecurityTokenManager {
String[] filteredAttributes = new String[entries.size()];
int i = 0;
for (Map.Entry<String, Collection<Object>> entry : entries) {
MetaProperty metaProperty = entity.getMetaClass().getPropertyNN(entry.getKey());
if (metadata.getTools().isOwningSide(metaProperty)) {
jsonObject.put(entry.getKey(), entry.getValue());
}
jsonObject.put(entry.getKey(), entry.getValue());
filteredAttributes[i++] = entry.getKey();
}
setFilteredAttributes(securityState, filteredAttributes);

View File

@ -65,6 +65,9 @@ public class EntitySerialization implements EntitySerializationAPI {
@Inject
protected DynamicAttributes dynamicAttributes;
@Inject
protected GlobalConfig globalConfig;
protected ThreadLocal<EntitySerializationContext> context =
ThreadLocal.withInitial(EntitySerializationContext::new);
@ -228,12 +231,14 @@ public class EntitySerialization implements EntitySerializationAPI {
writeFields(entity, jsonObject, view, cyclicReferences);
}
if (entity instanceof BaseGenericIdEntity || entity instanceof EmbeddableEntity) {
SecurityState securityState = getSecurityState(entity);
if (securityState != null) {
byte[] securityToken = getSecurityToken(securityState);
if (securityToken != null) {
jsonObject.addProperty("__securityToken", Base64.getEncoder().encodeToString(securityToken));
if (globalConfig.getRestUseSecurityTokenForClient()) {
if (entity instanceof BaseGenericIdEntity || entity instanceof EmbeddableEntity) {
SecurityState securityState = getSecurityState(entity);
if (securityState != null) {
byte[] securityToken = getSecurityToken(securityState);
if (securityToken != null) {
jsonObject.addProperty("__securityToken", Base64.getEncoder().encodeToString(securityToken));
}
}
}
}
@ -456,7 +461,7 @@ public class EntitySerialization implements EntitySerializationAPI {
}
}
if (entity instanceof BaseGenericIdEntity) {
if (globalConfig.getRestUseSecurityTokenForClient() && entity instanceof BaseGenericIdEntity) {
JsonPrimitive securityTokenJonPrimitive = jsonObject.getAsJsonPrimitive("__securityToken");
if (securityTokenJonPrimitive != null) {
byte[] securityToken = Base64.getDecoder().decode(securityTokenJonPrimitive.getAsString());
@ -567,7 +572,7 @@ public class EntitySerialization implements EntitySerializationAPI {
Entity entity = metadata.create(metaClass);
clearFields(entity);
readFields(jsonObject, entity);
if (entity instanceof EmbeddableEntity) {
if (globalConfig.getRestUseSecurityTokenForClient() && entity instanceof EmbeddableEntity) {
JsonPrimitive securityTokenJonPrimitive = jsonObject.getAsJsonPrimitive("__securityToken");
if (securityTokenJonPrimitive != null) {
byte[] securityToken = Base64.getDecoder().decode(securityTokenJonPrimitive.getAsString());

View File

@ -234,4 +234,12 @@ public interface GlobalConfig extends Config {
@Property("cuba.enableIdGenerationForEntitiesInAdditionalDataStores")
@DefaultBoolean(true)
boolean getEnableIdGenerationForEntitiesInAdditionalDataStores();
/**
* @return true if REST doesn't check security token for entities with security constraints
*/
@Property("cuba.rest.useSecurityTokenForClient")
@Source(type = SourceType.DATABASE)
@DefaultBoolean(false)
boolean getRestUseSecurityTokenForClient();
}