mirror of
https://gitee.com/jmix/cuba.git
synced 2024-12-04 20:28:00 +08:00
PL-6868 Do not check security in DataManager on the middleware by default
This commit is contained in:
parent
18f69d29c9
commit
36a43fcf10
@ -122,4 +122,9 @@ public class DataManagerClientImpl implements DataManager {
|
||||
Collections.singleton(entity));
|
||||
commit(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataManager secure() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import com.haulmont.cuba.core.entity.BaseGenericIdEntity;
|
||||
import com.haulmont.cuba.core.entity.CategoryAttributeValue;
|
||||
import com.haulmont.cuba.core.entity.Entity;
|
||||
import com.haulmont.cuba.core.global.*;
|
||||
import com.haulmont.cuba.core.sys.AppContext;
|
||||
import com.haulmont.cuba.security.entity.ConstraintOperationType;
|
||||
import com.haulmont.cuba.security.entity.EntityOp;
|
||||
import com.haulmont.cuba.security.entity.PermissionType;
|
||||
@ -25,6 +26,9 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang.StringUtils.isBlank;
|
||||
@ -45,7 +49,7 @@ public class DataManagerBean implements DataManager {
|
||||
protected ViewRepository viewRepository;
|
||||
|
||||
@Inject
|
||||
protected Configuration configuration;
|
||||
protected ServerConfig serverConfig;
|
||||
|
||||
@Inject
|
||||
protected PersistenceSecurity security;
|
||||
@ -76,7 +80,7 @@ public class DataManagerBean implements DataManager {
|
||||
|
||||
final MetaClass metaClass = metadata.getSession().getClassNN(context.getMetaClass());
|
||||
|
||||
if (!security.isEntityOpPermitted(metaClass, EntityOp.READ)) {
|
||||
if (!isEntityOpPermitted(metaClass, EntityOp.READ)) {
|
||||
log.debug("reading of " + metaClass + " not permitted, returning null");
|
||||
return null;
|
||||
}
|
||||
@ -128,7 +132,7 @@ public class DataManagerBean implements DataManager {
|
||||
|
||||
MetaClass metaClass = metadata.getClassNN(context.getMetaClass());
|
||||
|
||||
if (!security.isEntityOpPermitted(metaClass, EntityOp.READ)) {
|
||||
if (!isEntityOpPermitted(metaClass, EntityOp.READ)) {
|
||||
log.debug("reading of " + metaClass + " not permitted, returning empty list");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@ -143,7 +147,7 @@ public class DataManagerBean implements DataManager {
|
||||
persistence.getEntityManagerContext().setDbHints(context.getDbHints());
|
||||
|
||||
boolean ensureDistinct = false;
|
||||
if (configuration.getConfig(ServerConfig.class).getInMemoryDistinct() && context.getQuery() != null) {
|
||||
if (serverConfig.getInMemoryDistinct() && context.getQuery() != null) {
|
||||
QueryTransformer transformer = QueryTransformerFactory.createTransformer(
|
||||
context.getQuery().getQueryString());
|
||||
ensureDistinct = transformer.removeDistinct();
|
||||
@ -186,7 +190,7 @@ public class DataManagerBean implements DataManager {
|
||||
|
||||
MetaClass metaClass = metadata.getClassNN(context.getMetaClass());
|
||||
|
||||
if (!security.isEntityOpPermitted(metaClass, EntityOp.READ)) {
|
||||
if (!isEntityOpPermitted(metaClass, EntityOp.READ)) {
|
||||
log.debug("reading of " + metaClass + " not permitted, returning 0");
|
||||
return 0;
|
||||
}
|
||||
@ -344,7 +348,7 @@ public class DataManagerBean implements DataManager {
|
||||
tx.end();
|
||||
}
|
||||
|
||||
if (userSessionSource.getUserSession().hasConstraints()) {
|
||||
if (isAuthorizationRequired() && userSessionSource.getUserSession().hasConstraints()) {
|
||||
security.applyConstraints(res);
|
||||
}
|
||||
|
||||
@ -364,7 +368,8 @@ public class DataManagerBean implements DataManager {
|
||||
}
|
||||
|
||||
protected void checkOperationPermitted(Entity entity, ConstraintOperationType operationType) {
|
||||
if (userSessionSource.getUserSession().hasConstraints()
|
||||
if (isAuthorizationRequired()
|
||||
&& userSessionSource.getUserSession().hasConstraints()
|
||||
&& security.hasConstraints(entity.getMetaClass())
|
||||
&& !security.isPermitted(entity, operationType)) {
|
||||
throw new RowLevelSecurityException(
|
||||
@ -463,6 +468,19 @@ public class DataManagerBean implements DataManager {
|
||||
commit(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataManager secure() {
|
||||
if (serverConfig.getDataManagerChecksSecurityOnMiddleware()) {
|
||||
return this;
|
||||
} else {
|
||||
return (DataManager) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(),
|
||||
new Class[]{DataManager.class},
|
||||
new SecureDataManagerInvocationHandler(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected Query createQuery(EntityManager em, LoadContext context) {
|
||||
LoadContext.Query contextQuery = context.getQuery();
|
||||
if ((contextQuery == null || isBlank(contextQuery.getQueryString()))
|
||||
@ -621,10 +639,19 @@ public class DataManagerBean implements DataManager {
|
||||
}
|
||||
|
||||
protected void checkPermission(MetaClass metaClass, EntityOp operation) {
|
||||
if (!security.isEntityOpPermitted(metaClass, operation))
|
||||
if (!isEntityOpPermitted(metaClass, operation))
|
||||
throw new AccessDeniedException(PermissionType.ENTITY_OP, metaClass.getName());
|
||||
}
|
||||
|
||||
private boolean isEntityOpPermitted(MetaClass metaClass, EntityOp operation) {
|
||||
return !isAuthorizationRequired() || security.isEntityOpPermitted(metaClass, operation);
|
||||
}
|
||||
|
||||
protected boolean isAuthorizationRequired() {
|
||||
return serverConfig.getDataManagerChecksSecurityOnMiddleware()
|
||||
|| AppContext.getSecurityContext().isAuthorizationRequired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update references from newly persisted entities to merged detached entities. Otherwise a new entity can
|
||||
* contain a stale instance of merged entity.
|
||||
@ -688,7 +715,7 @@ public class DataManagerBean implements DataManager {
|
||||
}
|
||||
|
||||
protected boolean needToApplyConstraints(LoadContext context) {
|
||||
if (!userSessionSource.getUserSession().hasConstraints()) {
|
||||
if (!isAuthorizationRequired() || !userSessionSource.getUserSession().hasConstraints()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -721,4 +748,24 @@ public class DataManagerBean implements DataManager {
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
private class SecureDataManagerInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final DataManager impl;
|
||||
|
||||
private SecureDataManagerInvocationHandler(DataManager impl) {
|
||||
this.impl = impl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
boolean authorizationRequired = AppContext.getSecurityContext().isAuthorizationRequired();
|
||||
AppContext.getSecurityContext().setAuthorizationRequired(true);
|
||||
try {
|
||||
return method.invoke(impl, args);
|
||||
} finally {
|
||||
AppContext.getSecurityContext().setAuthorizationRequired(authorizationRequired);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,22 +27,22 @@ public class DataServiceBean implements DataService {
|
||||
|
||||
@Override
|
||||
public Set<Entity> commit(CommitContext context) {
|
||||
return dataManager.commit(context);
|
||||
return dataManager.secure().commit(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <E extends Entity> E load(LoadContext<E> context) {
|
||||
return dataManager.load(context);
|
||||
return dataManager.secure().load(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Entity> List<E> loadList(LoadContext<E> context) {
|
||||
return dataManager.loadList(context);
|
||||
return dataManager.secure().loadList(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount(LoadContext<? extends Entity> context) {
|
||||
return dataManager.getCount(context);
|
||||
return dataManager.secure().getCount(context);
|
||||
}
|
||||
}
|
@ -148,4 +148,12 @@ public interface ServerConfig extends Config {
|
||||
@Property("cuba.keyForSecurityTokenEncryption")
|
||||
@DefaultString("CUBA.Platform")
|
||||
String getKeyForSecurityTokenEncryption();
|
||||
|
||||
/**
|
||||
* Indicates that {@code DataManager} should always apply security restrictions on the middleware.
|
||||
*/
|
||||
@Property("cuba.dataManagerChecksSecurityOnMiddleware")
|
||||
@Source(type = SourceType.DATABASE)
|
||||
@DefaultBoolean(false)
|
||||
boolean getDataManagerChecksSecurityOnMiddleware();
|
||||
}
|
||||
|
@ -9,11 +9,12 @@ import com.haulmont.cuba.core.entity.BaseEntity;
|
||||
import com.haulmont.cuba.core.entity.annotation.Listeners;
|
||||
import com.haulmont.cuba.core.global.AppBeans;
|
||||
import com.haulmont.cuba.core.listener.*;
|
||||
import com.haulmont.cuba.core.sys.AppContext;
|
||||
import org.apache.commons.lang.ClassUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -166,42 +167,54 @@ public class EntityListenerManager {
|
||||
return;
|
||||
|
||||
List listeners = getListener(entity.getClass(), type);
|
||||
for (Object listener : listeners) {
|
||||
switch (type) {
|
||||
case BEFORE_DETACH:
|
||||
logExecution(type, entity);
|
||||
((BeforeDetachEntityListener) listener).onBeforeDetach(entity, persistence.getEntityManager());
|
||||
break;
|
||||
case BEFORE_ATTACH:
|
||||
logExecution(type, entity);
|
||||
((BeforeAttachEntityListener) listener).onBeforeAttach(entity);
|
||||
break;
|
||||
case BEFORE_INSERT:
|
||||
logExecution(type, entity);
|
||||
((BeforeInsertEntityListener) listener).onBeforeInsert(entity);
|
||||
break;
|
||||
case AFTER_INSERT:
|
||||
logExecution(type, entity);
|
||||
((AfterInsertEntityListener) listener).onAfterInsert(entity);
|
||||
break;
|
||||
case BEFORE_UPDATE:
|
||||
logExecution(type, entity);
|
||||
((BeforeUpdateEntityListener) listener).onBeforeUpdate(entity);
|
||||
break;
|
||||
case AFTER_UPDATE:
|
||||
logExecution(type, entity);
|
||||
((AfterUpdateEntityListener) listener).onAfterUpdate(entity);
|
||||
break;
|
||||
case BEFORE_DELETE:
|
||||
logExecution(type, entity);
|
||||
((BeforeDeleteEntityListener) listener).onBeforeDelete(entity);
|
||||
break;
|
||||
case AFTER_DELETE:
|
||||
logExecution(type, entity);
|
||||
((AfterDeleteEntityListener) listener).onAfterDelete(entity);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported EntityListenerType: " + type);
|
||||
|
||||
boolean saved = false;
|
||||
if (AppContext.getSecurityContext() != null) { // can be null before login when detaching entities
|
||||
saved = AppContext.getSecurityContext().isAuthorizationRequired();
|
||||
AppContext.getSecurityContext().setAuthorizationRequired(false);
|
||||
}
|
||||
try {
|
||||
for (Object listener : listeners) {
|
||||
switch (type) {
|
||||
case BEFORE_DETACH:
|
||||
logExecution(type, entity);
|
||||
((BeforeDetachEntityListener) listener).onBeforeDetach(entity, persistence.getEntityManager());
|
||||
break;
|
||||
case BEFORE_ATTACH:
|
||||
logExecution(type, entity);
|
||||
((BeforeAttachEntityListener) listener).onBeforeAttach(entity);
|
||||
break;
|
||||
case BEFORE_INSERT:
|
||||
logExecution(type, entity);
|
||||
((BeforeInsertEntityListener) listener).onBeforeInsert(entity);
|
||||
break;
|
||||
case AFTER_INSERT:
|
||||
logExecution(type, entity);
|
||||
((AfterInsertEntityListener) listener).onAfterInsert(entity);
|
||||
break;
|
||||
case BEFORE_UPDATE:
|
||||
logExecution(type, entity);
|
||||
((BeforeUpdateEntityListener) listener).onBeforeUpdate(entity);
|
||||
break;
|
||||
case AFTER_UPDATE:
|
||||
logExecution(type, entity);
|
||||
((AfterUpdateEntityListener) listener).onAfterUpdate(entity);
|
||||
break;
|
||||
case BEFORE_DELETE:
|
||||
logExecution(type, entity);
|
||||
((BeforeDeleteEntityListener) listener).onBeforeDelete(entity);
|
||||
break;
|
||||
case AFTER_DELETE:
|
||||
logExecution(type, entity);
|
||||
((AfterDeleteEntityListener) listener).onAfterDelete(entity);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported EntityListenerType: " + type);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (AppContext.getSecurityContext() != null) {
|
||||
AppContext.getSecurityContext().setAuthorizationRequired(saved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2016 Haulmont. All rights reserved.
|
||||
* Use is subject to license terms, see http://www.cuba-platform.com/license for details.
|
||||
*/
|
||||
|
||||
package com.haulmont.cuba.security;
|
||||
|
||||
import com.haulmont.cuba.core.EntityManager;
|
||||
import com.haulmont.cuba.core.Transaction;
|
||||
import com.haulmont.cuba.core.app.DataService;
|
||||
import com.haulmont.cuba.core.entity.Server;
|
||||
import com.haulmont.cuba.core.global.*;
|
||||
import com.haulmont.cuba.security.app.LoginWorker;
|
||||
import com.haulmont.cuba.security.entity.*;
|
||||
import com.haulmont.cuba.security.global.UserSession;
|
||||
import com.haulmont.cuba.testsupport.TestContainer;
|
||||
import com.haulmont.cuba.testsupport.TestUserSessionSource;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DataManagerSecurityTest {
|
||||
|
||||
@ClassRule
|
||||
public static TestContainer cont = TestContainer.Common.INSTANCE;
|
||||
|
||||
private static final String USER_NAME = "testUser";
|
||||
private static final String USER_PASSW = "testUser";
|
||||
private static final String PERM_TARGET = "sys$Server:" + EntityOp.READ.getId();
|
||||
|
||||
private PasswordEncryption passwordEncryption;
|
||||
private Role role;
|
||||
private Permission permission;
|
||||
private Group group;
|
||||
private User user;
|
||||
private UserRole userRole;
|
||||
private Server server;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
passwordEncryption = AppBeans.get(PasswordEncryption.class);
|
||||
|
||||
try (Transaction tx = cont.persistence().createTransaction()) {
|
||||
EntityManager em = cont.persistence().getEntityManager();
|
||||
|
||||
server = new Server();
|
||||
server.setName("someServer");
|
||||
server.setRunning(false);
|
||||
em.persist(server);
|
||||
|
||||
role = new Role();
|
||||
role.setName("testRole1");
|
||||
em.persist(role);
|
||||
|
||||
permission = new Permission();
|
||||
permission.setRole(role);
|
||||
permission.setType(PermissionType.ENTITY_OP);
|
||||
permission.setTarget(PERM_TARGET);
|
||||
permission.setValue(0);
|
||||
em.persist(permission);
|
||||
|
||||
group = new Group();
|
||||
group.setName("testGroup");
|
||||
em.persist(group);
|
||||
|
||||
user = new User();
|
||||
user.setName(USER_NAME);
|
||||
user.setLogin(USER_NAME);
|
||||
|
||||
String pwd = passwordEncryption.getPasswordHash(user.getId(), USER_PASSW);
|
||||
user.setPassword(pwd);
|
||||
|
||||
user.setGroup(group);
|
||||
em.persist(user);
|
||||
|
||||
userRole = new UserRole();
|
||||
userRole.setUser(user);
|
||||
userRole.setRole(role);
|
||||
em.persist(userRole);
|
||||
|
||||
tx.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
cont.deleteRecord(userRole, user, group, permission, role, server);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
LoginWorker lw = AppBeans.get(LoginWorker.NAME);
|
||||
|
||||
UserSession userSession = lw.login(USER_NAME, passwordEncryption.getPlainHash(USER_PASSW), Locale.getDefault());
|
||||
assertNotNull(userSession);
|
||||
|
||||
UserSessionSource uss = AppBeans.get(UserSessionSource.class);
|
||||
UserSession savedUserSession = uss.getUserSession();
|
||||
((TestUserSessionSource) uss).setUserSession(userSession);
|
||||
try {
|
||||
DataManager dm = AppBeans.get(DataManager.NAME);
|
||||
LoadContext<Server> loadContext = LoadContext.create(Server.class)
|
||||
.setQuery(new LoadContext.Query("select s from sys$Server s"));
|
||||
List<Server> list = dm.loadList(loadContext);
|
||||
assertFalse("Permission took effect when calling DataManager inside middleware", list.isEmpty());
|
||||
|
||||
DataService ds = AppBeans.get(DataService.NAME);
|
||||
loadContext = LoadContext.create(Server.class)
|
||||
.setQuery(new LoadContext.Query("select s from sys$Server s"));
|
||||
list = ds.loadList(loadContext);
|
||||
assertTrue("Permission did not take effect when calling DataService", list.isEmpty());
|
||||
|
||||
} finally {
|
||||
((TestUserSessionSource) uss).setUserSession(savedUserSession);
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,11 @@ import java.util.Set;
|
||||
* <p>Works with non-managed (new or detached) entities, always starts and commits new transactions. Can be used on
|
||||
* both middle and client tiers.</p>
|
||||
*
|
||||
* <p>When used on the client tier - always applies security restrictions.
|
||||
* <p>When used on the middleware - does not apply security restrictions by default. If you want to apply security,
|
||||
* get {@link #secure()} instance or set the {@code cuba.dataManagerChecksSecurityOnMiddleware} application property
|
||||
* to use it by default.
|
||||
*
|
||||
* @author krivopustov
|
||||
* @version $Id$
|
||||
*/
|
||||
@ -127,4 +132,14 @@ public interface DataManager {
|
||||
* @param entity entity instance
|
||||
*/
|
||||
void remove(Entity entity);
|
||||
|
||||
/**
|
||||
* Returns the DataManager implementation that is guaranteed to apply security restrictions.
|
||||
* <p>By default, DataManager does not apply security when used on the middleware. Use this method if you want
|
||||
* to run the same code both on the client and middle tier. For example:
|
||||
* <pre>
|
||||
* AppBeans.get(DataManager.class).secure().load(context);
|
||||
* </pre>
|
||||
*/
|
||||
DataManager secure();
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ public class SecurityContext {
|
||||
private UserSession session;
|
||||
private String user;
|
||||
|
||||
private boolean authorizationRequired;
|
||||
|
||||
public SecurityContext(UUID sessionId) {
|
||||
Objects.requireNonNull(sessionId, "sessionId is null");
|
||||
this.sessionId = sessionId;
|
||||
@ -76,6 +78,31 @@ public class SecurityContext {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the security check is required for standard mechanisms ({@code DataManager} in particular) on
|
||||
* the middleware
|
||||
*/
|
||||
public boolean isAuthorizationRequired() {
|
||||
return authorizationRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the security check is required for standard mechanisms ({@code DataManager} in particular) on
|
||||
* the middleware. Example usage:
|
||||
* <pre>
|
||||
* boolean saved = AppContext.getSecurityContext().isAuthorizationRequired();
|
||||
* AppContext.getSecurityContext().setAuthorizationRequired(true);
|
||||
* try {
|
||||
* // all calls to DataManager will apply security restrictions
|
||||
* } finally {
|
||||
* AppContext.getSecurityContext().setAuthorizationRequired(saved);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public void setAuthorizationRequired(boolean authorizationRequired) {
|
||||
this.authorizationRequired = authorizationRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SecurityContext{" +
|
||||
|
@ -70,6 +70,11 @@ public class GenericDataSupplier implements DataSupplier {
|
||||
dataManager.remove(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataManager secure() {
|
||||
return dataManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entity> commit(CommitContext context) {
|
||||
return dataManager.commit(context);
|
||||
|
Loading…
Reference in New Issue
Block a user