From c2be2917c671bb77b761bf48b55d48ebdfa62731 Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Mon, 3 Apr 2017 19:36:02 +0000
Subject: [PATCH] SSDM-4999 : Project Authorization - Step 0: - extend
 RoleWithHierarchy with new project roles - add configuration switch to
 enable/disable project level authorization (disabled by default) - hide the
 new roles in all the places where they would automatically appear (show them
 only if the new feature is enabled) - extend/fix automated tests to cover new
 roles

SVN: 38009
---
 .../ui/amc/AddRoleAssignmentDialog.java       |  2 +-
 .../application/ui/amc/RoleListBox.java       | 18 ++++-
 .../web/client/dto/ApplicationInfo.java       | 12 +++
 .../web/server/AbstractClientService.java     | 11 +++
 .../api/v1/GeneralInformationService.java     | 34 +++++---
 .../server/authorization/CapabilityMap.java   | 31 +++++--
 .../DefaultAccessController.java              |  3 +-
 .../business/bo/AbstractBusinessObject.java   | 15 +++-
 .../dataaccess/IAuthorizationDAOFactory.java  |  5 ++
 .../db/AuthorizationDAOFactory.java           | 14 +++-
 .../server/dataaccess/db/DAOFactory.java      |  5 +-
 .../authorization/AuthorizationConfig.java    | 80 +++++++++++++++++++
 .../AuthorizationConfigFacade.java            | 50 ++++++++++++
 .../authorization/IAuthorizationConfig.java   | 27 +++++++
 .../shared/basic/dto/RoleWithHierarchy.java   | 15 +++-
 .../QueryDatabaseDefinitionProvider.java      | 21 ++++-
 .../source/java/genericApplicationContext.xml |  1 +
 .../api/v1/GeneralInformationServiceTest.java | 18 ++++-
 .../authorization/CapabilityMapTest.java      | 34 +++++---
 .../DefaultAccessControllerTest.java          | 33 +++++---
 .../basic/dto/RoleWithHierarchyTest.java      | 43 +++++++++-
 21 files changed, 412 insertions(+), 60 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfig.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfigFacade.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationConfig.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/AddRoleAssignmentDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/AddRoleAssignmentDialog.java
index 4333021acae..9505434ae26 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/AddRoleAssignmentDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/AddRoleAssignmentDialog.java
@@ -77,7 +77,7 @@ public class AddRoleAssignmentDialog extends AbstractRegistrationDialog
         group = new SpaceSelectionWidget(viewContext, PREFIX, false, false);
         group.setWidth(100);
 
-        roleBox = new AdapterField(new RoleListBox(group));
+        roleBox = new AdapterField(new RoleListBox(viewContext, group));
         roleBox.setFieldLabel("Role");
         roleBox.setWidth(100);
         roleBox.setId(ROLE_FIELD_ID);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/RoleListBox.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/RoleListBox.java
index 4f804cf8e55..39cd50d7a60 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/RoleListBox.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/amc/RoleListBox.java
@@ -23,6 +23,8 @@ import com.google.gwt.event.dom.client.ChangeEvent;
 import com.google.gwt.event.dom.client.ChangeHandler;
 import com.google.gwt.user.client.ui.ListBox;
 
+import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientServiceAsync;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.SpaceSelectionWidget;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.FieldUtil;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
@@ -35,10 +37,14 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 public class RoleListBox extends ListBox
 {
 
+    private IViewContext<ICommonClientServiceAsync> viewContext;
+
     private List<RoleWithHierarchy> roles;
 
-    public RoleListBox(final SpaceSelectionWidget groupWidget)
+    public RoleListBox(IViewContext<ICommonClientServiceAsync> viewContext, final SpaceSelectionWidget groupWidget)
     {
+        this.viewContext = viewContext;
+
         for (RoleWithHierarchy visibleRoleCode : getRoles())
         {
             addItem(visibleRoleCode.toString());
@@ -77,10 +83,18 @@ public class RoleListBox extends ListBox
     {
         if (roles == null)
         {
+            boolean projectLevelAuthorizationEnabled = viewContext.getModel().getApplicationInfo().isProjectAuthorizationEnabled();
+
             roles = new ArrayList<RoleWithHierarchy>();
             for (RoleWithHierarchy role : RoleWithHierarchy.values())
             {
-                if (!RoleWithHierarchy.INSTANCE_DISABLED.equals(role))
+                if (RoleWithHierarchy.INSTANCE_DISABLED.equals(role))
+                {
+                    continue;
+                } else if (role.isProjectLevel() && false == projectLevelAuthorizationEnabled)
+                {
+                    continue;
+                } else
                 {
                     roles.add(role);
                 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java
index c8a023707ee..f6159f0450c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java
@@ -47,6 +47,8 @@ public final class ApplicationInfo implements IsSerializable
 
     private boolean projectSamplesEnabled;
 
+    private boolean projectLevelAuthorizationEnabled;
+
     private WebClientConfiguration webClientConfiguration;
 
     private Set<String> enabledTechnologies;
@@ -179,4 +181,14 @@ public final class ApplicationInfo implements IsSerializable
         this.projectSamplesEnabled = projectSamplesEnabled;
     }
 
+    public boolean isProjectAuthorizationEnabled()
+    {
+        return projectLevelAuthorizationEnabled;
+    }
+
+    public void setProjectLevelAuthorizationEnabled(boolean projectAuthorizationEnabled)
+    {
+        this.projectLevelAuthorizationEnabled = projectAuthorizationEnabled;
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
index abc4dfb9a06..fbcc6fa4de9 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
@@ -77,6 +77,8 @@ import ch.systemsx.cisd.openbis.generic.server.SessionConstants;
 import ch.systemsx.cisd.openbis.generic.shared.Constants;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.WebClientConfigurationProvider;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.AuthorizationConfigFacade;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.WebAppsProperties;
@@ -119,6 +121,9 @@ public abstract class AbstractClientService implements IClientService,
     @Autowired
     private TableDataCache<String, Object> tableDataCache;
 
+    @Autowired
+    private IAuthorizationConfig authorizationConfig;
+
     private String cifexURL;
 
     private String cifexRecipient;
@@ -475,6 +480,7 @@ public abstract class AbstractClientService implements IClientService,
         applicationInfo.setWebapps(extractWebAppsProperties());
         applicationInfo.setArchivingConfigured(isArchivingConfigured());
         applicationInfo.setProjectSamplesEnabled(isProjectSamplesEnabled());
+        applicationInfo.setProjectLevelAuthorizationEnabled(isProjectLevelAuthorizationEnabled());
         applicationInfo.setVersion(getVersion());
         return applicationInfo;
     }
@@ -565,6 +571,11 @@ public abstract class AbstractClientService implements IClientService,
         return false;
     }
 
+    private boolean isProjectLevelAuthorizationEnabled()
+    {
+        return new AuthorizationConfigFacade(authorizationConfig).isProjectLevelEnabled();
+    }
+
     @Override
     public final SessionContext tryToGetCurrentSessionContext(boolean anonymous, String sessionIdOrNull)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
index b9a4da48b86..43ff9eed1d9 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
@@ -33,6 +33,7 @@ import javax.annotation.Resource;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.Transformer;
 import org.hibernate.SQLQuery;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -106,6 +107,8 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.experiment.IExperim
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.metaproject.IMetaprojectId;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.project.IProjectId;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.id.sample.ISampleId;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.AuthorizationConfigFacade;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ContainerDataSet;
@@ -159,6 +162,9 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
     @Resource(name = ComponentNames.MANAGED_PROPERTY_EVALUATOR_FACTORY)
     private IManagedPropertyEvaluatorFactory managedPropertyEvaluatorFactory;
 
+    @Autowired
+    private IAuthorizationConfig authorizationConfig;
+
     // Default constructor needed by Spring
     public GeneralInformationService()
     {
@@ -166,11 +172,12 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
 
     GeneralInformationService(IOpenBisSessionManager sessionManager, IDAOFactory daoFactory,
             ICommonBusinessObjectFactory boFactory, IPropertiesBatchManager propertiesBatchManager,
-            ICommonServer commonServer)
+            ICommonServer commonServer, IAuthorizationConfig authorizationConfig)
     {
         super(sessionManager, daoFactory, propertiesBatchManager);
         this.boFactory = boFactory;
         this.commonServer = commonServer;
+        this.authorizationConfig = authorizationConfig;
     }
 
     @Override
@@ -202,17 +209,26 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
     {
         checkSession(sessionToken);
 
+        AuthorizationConfigFacade configFacade = new AuthorizationConfigFacade(authorizationConfig);
+
         Map<String, Set<Role>> namedRoleSets = new LinkedHashMap<String, Set<Role>>();
         RoleWithHierarchy[] values = RoleWithHierarchy.values();
+
         for (RoleWithHierarchy roleSet : values)
         {
-            Set<RoleWithHierarchy> roles = roleSet.getRoles();
-            Set<Role> translatedRoles = new HashSet<Role>();
-            for (RoleWithHierarchy role : roles)
+            if (configFacade.isRoleEnabled(roleSet))
             {
-                translatedRoles.add(Translator.translate(role));
+                Set<RoleWithHierarchy> roles = roleSet.getRoles();
+                Set<Role> translatedRoles = new HashSet<Role>();
+                for (RoleWithHierarchy role : roles)
+                {
+                    if (configFacade.isRoleEnabled(role))
+                    {
+                        translatedRoles.add(Translator.translate(role));
+                    }
+                }
+                namedRoleSets.put(roleSet.name(), translatedRoles);
             }
-            namedRoleSets.put(roleSet.name(), translatedRoles);
         }
         return namedRoleSets;
     }
@@ -623,8 +639,7 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
 
     @Override
     @Transactional(readOnly = true)
-    @RolesAllowed(value =
-    { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    @RolesAllowed(value = { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public String tryGetDataStoreBaseURL(String sessionToken, String dataSetCode)
     {
         Session session = getSession(sessionToken);
@@ -641,8 +656,7 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
 
     @Override
     @Transactional(readOnly = true)
-    @RolesAllowed(value =
-    { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    @RolesAllowed(value = { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<DataStoreURLForDataSets> getDataStoreBaseURLs(String sessionToken,
             List<String> dataSetCodes)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMap.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMap.java
index 952c2cfa85e..6ebbf47b702 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMap.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMap.java
@@ -34,6 +34,8 @@ import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.openbis.generic.server.authorization.annotation.Capability;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.AuthorizationConfigFacade;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 
 /**
@@ -46,6 +48,8 @@ class CapabilityMap
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
             CapabilityMap.class);
 
+    private final IAuthorizationConfig authorizationConfig;
+
     private final Map<String, Collection<RoleWithHierarchy>> capMap =
             new HashMap<String, Collection<RoleWithHierarchy>>();
 
@@ -73,13 +77,15 @@ class CapabilityMap
         }
     }
 
-    CapabilityMap(File file)
+    CapabilityMap(File file, IAuthorizationConfig authorizationConfig)
     {
-        this(readLines(file), file.getPath());
+        this(readLines(file), file.getPath(), authorizationConfig);
     }
 
-    CapabilityMap(List<String> lines, String filePath)
+    CapabilityMap(List<String> lines, String filePath, IAuthorizationConfig authorizationConfig)
     {
+        this.authorizationConfig = authorizationConfig;
+
         for (String line : lines)
         {
             final String trimmed = line.trim();
@@ -133,23 +139,34 @@ class CapabilityMap
     private void addRoles(String capabilityName, String roleNames, String line, String filePath)
     {
         Collection<RoleWithHierarchy> roles = capMap.get(capabilityName);
+
         if (roles == null)
         {
             roles = new HashSet<RoleWithHierarchy>();
             capMap.put(capabilityName, roles);
         }
+
+        AuthorizationConfigFacade configFacade = new AuthorizationConfigFacade(authorizationConfig);
+
         for (String roleName : StringUtils.split(roleNames, ","))
         {
             roleName = roleName.trim().toUpperCase();
             try
             {
                 final RoleWithHierarchy role = RoleWithHierarchy.valueOf(roleName);
-                roles.add(role);
 
-                if (operationLog.isDebugEnabled())
+                if (configFacade.isRoleEnabled(role))
+                {
+                    roles.add(role);
+
+                    if (operationLog.isDebugEnabled())
+                    {
+                        operationLog.debug(String
+                                .format("Add to map: '%s' -> %s", capabilityName, role));
+                    }
+                } else
                 {
-                    operationLog.debug(String
-                            .format("Add to map: '%s' -> %s", capabilityName, role));
+                    logWarning(line, filePath, "role '" + roleName + "' doesn't exist");
                 }
             } catch (IllegalArgumentException ex)
             {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessController.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessController.java
index 18224192b48..fdc603fc8ff 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessController.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessController.java
@@ -77,12 +77,13 @@ public final class DefaultAccessController implements IAccessController
 
     private final Map<Method, Map<String, Set<RoleWithHierarchy>>> argumentRolesCache = new HashMap<>();
 
-    private final CapabilityMap capabilities = new CapabilityMap(new File("etc/capabilities"));
+    private final CapabilityMap capabilities;
 
     private PredicateExecutor predicateExecutor;
 
     public DefaultAccessController(final IAuthorizationDAOFactory daoFactory)
     {
+        capabilities = new CapabilityMap(new File("etc/capabilities"), daoFactory.getAuthorizationConfig());
         predicateExecutor = new PredicateExecutor();
         predicateExecutor.setPredicateFactory(new PredicateFactory());
         predicateExecutor.setDAOFactory(daoFactory);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
index cbe3dd1bbb2..15124cdd817 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
@@ -87,6 +87,7 @@ import ch.systemsx.cisd.openbis.generic.server.util.SpaceIdentifierHelper;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
 import ch.systemsx.cisd.openbis.generic.shared.ResourceNames;
 import ch.systemsx.cisd.openbis.generic.shared.WebClientConfigurationProvider;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IIdHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Identifier;
@@ -604,6 +605,12 @@ abstract class AbstractBusinessObject implements IDAOFactory
         return daoFactory.getOperationExecutionDAO();
     }
 
+    @Override
+    public IAuthorizationConfig getAuthorizationConfig()
+    {
+        return daoFactory.getAuthorizationConfig();
+    }
+
     protected RelationshipTypePE getParentChildRelationshipType()
     {
         return RelationshipUtils.getParentChildRelationshipType(getRelationshipTypeDAO());
@@ -836,7 +843,7 @@ abstract class AbstractBusinessObject implements IDAOFactory
         }
         assignmentManager.performAssignment(relationshipService, session);
     }
-    
+
     protected void assignSampleToProject(SamplePE sample, ProjectPE project)
     {
         if (project != null)
@@ -867,7 +874,7 @@ abstract class AbstractBusinessObject implements IDAOFactory
         assignmentManager.assignDataSetAndRelatedComponents(data, newSample, experiment);
         assignmentManager.performAssignment(relationshipService, session);
     }
-    
+
     protected <T extends IEntityInformationWithPropertiesHolder> void reindex(Class<T> objectClass, Collection<T> objects)
     {
         IDynamicPropertyEvaluationScheduler indexUpdater = daoFactory.getPersistencyResources().getDynamicPropertyEvaluationScheduler();
@@ -891,7 +898,7 @@ abstract class AbstractBusinessObject implements IDAOFactory
     {
         return WebClientConfigUtils.getExperimentText(getWebClientConfigProvider());
     }
-    
+
     protected String getSampleText()
     {
         return WebClientConfigUtils.getSampleText(getWebClientConfigProvider());
@@ -902,5 +909,5 @@ abstract class AbstractBusinessObject implements IDAOFactory
         return (WebClientConfigurationProvider) CommonServiceProvider.tryToGetBean(
                 ResourceNames.WEB_CLIENT_CONFIGURATION_PROVIDER);
     }
-    
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationDAOFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationDAOFactory.java
index fd00edc2014..682db814727 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationDAOFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationDAOFactory.java
@@ -18,6 +18,8 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess;
 
 import org.hibernate.SessionFactory;
 
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
+
 /**
  * Factory definition for all Data Access Objects which are needed for managing authorization.
  * 
@@ -25,6 +27,9 @@ import org.hibernate.SessionFactory;
  */
 public interface IAuthorizationDAOFactory
 {
+
+    public IAuthorizationConfig getAuthorizationConfig();
+
     /** Returns the persistency resources used to create DAO's. */
     public PersistencyResources getPersistencyResources();
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationDAOFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationDAOFactory.java
index ba5176b0a10..af1317179cc 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationDAOFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationDAOFactory.java
@@ -37,6 +37,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISampleDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISpaceDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.PersistencyResources;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.deletion.EntityHistoryCreator;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
 
 /**
@@ -47,6 +48,8 @@ import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
 public class AuthorizationDAOFactory implements IAuthorizationDAOFactory
 {
 
+    private final IAuthorizationConfig authorizationConfig;
+
     private final IRoleAssignmentDAO roleAssignmentDAO;
 
     private final ISpaceDAO groupDAO;
@@ -78,10 +81,10 @@ public class AuthorizationDAOFactory implements IAuthorizationDAOFactory
     public AuthorizationDAOFactory(final DatabaseConfigurationContext context,
             final SessionFactory sessionFactory,
             final IDynamicPropertyEvaluationScheduler dynamicPropertyEvaluationScheduler,
-            final EntityHistoryCreator historyCreator)
+            final EntityHistoryCreator historyCreator, final IAuthorizationConfig authorizationConfig)
     {
         persistencyResources =
-                new PersistencyResources(context, sessionFactory, 
+                new PersistencyResources(context, sessionFactory,
                         dynamicPropertyEvaluationScheduler);
         personDAO = new PersonDAO(sessionFactory, historyCreator);
         groupDAO = new SpaceDAO(sessionFactory, historyCreator);
@@ -96,6 +99,7 @@ public class AuthorizationDAOFactory implements IAuthorizationDAOFactory
         queryDAO = new QueryDAO(sessionFactory, historyCreator);
         deletionDAO = new DeletionDAO(sessionFactory, persistencyResources, historyCreator);
         metaprojectDAO = new MetaprojectDAO(sessionFactory, historyCreator);
+        this.authorizationConfig = authorizationConfig;
     }
 
     @Override
@@ -188,6 +192,12 @@ public class AuthorizationDAOFactory implements IAuthorizationDAOFactory
         return metaprojectDAO;
     }
 
+    @Override
+    public IAuthorizationConfig getAuthorizationConfig()
+    {
+        return authorizationConfig;
+    }
+
     /**
      * Configures current session settings for batch update mode.
      * 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
index ca7eed5425c..e0001389dc2 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DAOFactory.java
@@ -65,6 +65,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.deletion.EntityHist
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.search.HibernateSearchContext;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.util.UpdateUtils;
 import ch.systemsx.cisd.openbis.generic.shared.Constants;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
 
@@ -142,9 +143,9 @@ public final class DAOFactory extends AuthorizationDAOFactory implements IDAOFac
     public DAOFactory(final DatabaseConfigurationContext context,
             final SessionFactory sessionFactory, HibernateSearchContext hibernateSearchContext,
             final IDynamicPropertyEvaluationScheduler dynamicPropertyEvaluationScheduler,
-            final EntityHistoryCreator historyCreator)
+            final EntityHistoryCreator historyCreator, final IAuthorizationConfig authorizationConfig)
     {
-        super(context, sessionFactory, dynamicPropertyEvaluationScheduler, historyCreator);
+        super(context, sessionFactory, dynamicPropertyEvaluationScheduler, historyCreator, authorizationConfig);
         this.context = context;
         this.dynamicPropertyEvaluationScheduler = dynamicPropertyEvaluationScheduler;
         historyCreator.setDaoFactory(this);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfig.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfig.java
new file mode 100644
index 00000000000..c0f8de5dcc9
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfig.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.shared.authorization;
+
+import java.util.Properties;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
+
+/**
+ * @author pkupczyk
+ */
+@Component("authorization-config")
+public class AuthorizationConfig implements IAuthorizationConfig
+{
+
+    private static final String PROJECT_LEVEL_AUTHORIZATION_ENABLED_PROPERTY_NAME = "authorization.project-level-enabled";
+
+    private static final boolean PROJECT_LEVEL_AUTHORIZATION_ENABLED_DEFAULT = false;
+
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, AuthorizationConfig.class);
+
+    private boolean projectLevelEnabled;
+
+    @Autowired
+    private ExposablePropertyPlaceholderConfigurer configurer;
+
+    private AuthorizationConfig()
+    {
+    }
+
+    @PostConstruct
+    private void init()
+    {
+        Properties properties = configurer.getResolvedProps();
+
+        String projectLevelEnabledString = properties.getProperty(PROJECT_LEVEL_AUTHORIZATION_ENABLED_PROPERTY_NAME);
+
+        if (projectLevelEnabledString == null || projectLevelEnabledString.trim().isEmpty())
+        {
+            projectLevelEnabled = PROJECT_LEVEL_AUTHORIZATION_ENABLED_DEFAULT;
+        } else
+        {
+            projectLevelEnabled = Boolean.parseBoolean(projectLevelEnabledString);
+        }
+
+        if (projectLevelEnabled)
+        {
+            operationLog.info("Project level authorization is enabled");
+        }
+    }
+
+    @Override
+    public boolean isProjectLevelEnabled()
+    {
+        return projectLevelEnabled;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfigFacade.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfigFacade.java
new file mode 100644
index 00000000000..b7614ad862a
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/AuthorizationConfigFacade.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.shared.authorization;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
+
+/**
+ * @author pkupczyk
+ */
+public class AuthorizationConfigFacade
+{
+
+    private IAuthorizationConfig authorizationConfig;
+
+    public AuthorizationConfigFacade(IAuthorizationConfig authorizationConfig)
+    {
+        this.authorizationConfig = authorizationConfig;
+    }
+
+    public boolean isProjectLevelEnabled()
+    {
+        return authorizationConfig.isProjectLevelEnabled();
+    }
+
+    public boolean isRoleEnabled(RoleWithHierarchy role)
+    {
+        if (role.isProjectLevel())
+        {
+            return authorizationConfig.isProjectLevelEnabled();
+        } else
+        {
+            return true;
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationConfig.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationConfig.java
new file mode 100644
index 00000000000..930b74dc101
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/authorization/IAuthorizationConfig.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.shared.authorization;
+
+/**
+ * @author pkupczyk
+ */
+public interface IAuthorizationConfig
+{
+
+    public boolean isProjectLevelEnabled();
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchy.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchy.java
index dc2db87f2ae..b2e0f93d1a7 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchy.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchy.java
@@ -70,11 +70,19 @@ public enum RoleWithHierarchy implements Serializable
 
     SPACE_ETL_SERVER(INSTANCE_ETL_SERVER),
 
+    PROJECT_ADMIN(SPACE_ADMIN),
+
+    PROJECT_POWER_USER(PROJECT_ADMIN, SPACE_POWER_USER),
+
+    PROJECT_USER(PROJECT_POWER_USER, SPACE_USER),
+
+    PROJECT_OBSERVER(PROJECT_USER, SPACE_OBSERVER)
+
     ;
 
     public static enum RoleLevel implements IsSerializable
     {
-        INSTANCE, SPACE;
+        INSTANCE, SPACE, PROJECT;
     }
 
     /**
@@ -181,6 +189,11 @@ public enum RoleWithHierarchy implements Serializable
         return roleLevel.equals(RoleLevel.SPACE);
     }
 
+    public boolean isProjectLevel()
+    {
+        return roleLevel.equals(RoleLevel.PROJECT);
+    }
+
     public RoleLevel getRoleLevel()
     {
         return roleLevel;
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/QueryDatabaseDefinitionProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/QueryDatabaseDefinitionProvider.java
index 8538f6abf0c..31b4f193bb7 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/QueryDatabaseDefinitionProvider.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/QueryDatabaseDefinitionProvider.java
@@ -25,6 +25,7 @@ import java.util.Set;
 
 import javax.annotation.Resource;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
@@ -35,6 +36,8 @@ import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
 import ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext;
 import ch.systemsx.cisd.openbis.generic.server.ComponentNames;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.AuthorizationConfigFacade;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
 import ch.systemsx.cisd.openbis.plugin.query.server.authorization.QueryAccessController;
@@ -72,6 +75,9 @@ public class QueryDatabaseDefinitionProvider implements IQueryDatabaseDefinition
     @Resource(name = ComponentNames.DAO_FACTORY)
     private IDAOFactory daoFactory;
 
+    @Autowired
+    private IAuthorizationConfig authorizationConfig;
+
     /**
      * map from dbKey to DatabaseDefinition
      */
@@ -130,8 +136,19 @@ public class QueryDatabaseDefinitionProvider implements IQueryDatabaseDefinition
             {
                 final RoleWithHierarchy creatorMinimalRole =
                         RoleWithHierarchy.valueOf(creatorMinimalRoleString);
-                definitions.put(databaseKey, new DatabaseDefinition(configurationContext,
-                        databaseKey, label, creatorMinimalRole, dataSpaceOrNull));
+
+                AuthorizationConfigFacade configFacade = new AuthorizationConfigFacade(authorizationConfig);
+
+                if (configFacade.isRoleEnabled(creatorMinimalRole))
+                {
+                    definitions.put(databaseKey, new DatabaseDefinition(configurationContext,
+                            databaseKey, label, creatorMinimalRole, dataSpaceOrNull));
+                } else
+                {
+                    throw new UnsupportedOperationException("Query database '" + databaseKey
+                            + "' is not defined properly. '" + creatorMinimalRoleString
+                            + "' is not a valid role.");
+                }
             } catch (IllegalArgumentException ex)
             {
                 throw new UnsupportedOperationException("Query database '" + databaseKey
diff --git a/openbis/source/java/genericApplicationContext.xml b/openbis/source/java/genericApplicationContext.xml
index dd3fb142158..362ea3aeb07 100644
--- a/openbis/source/java/genericApplicationContext.xml
+++ b/openbis/source/java/genericApplicationContext.xml
@@ -113,6 +113,7 @@
 		<constructor-arg ref="hibernate-search-context" />
 		<constructor-arg ref="dynamic-property-scheduler" />
 		<constructor-arg ref="entity-history-creator" />
+		<constructor-arg ref="authorization-config" />
 	</bean>
 
 	<bean id="dss-factory"
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceTest.java
index 5390d1a6267..41bdb63e342 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceTest.java
@@ -53,6 +53,7 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SampleFetchOption;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClause;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClauseAttribute;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchSubCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SpaceWithProjectsAndRoleAssignments;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
@@ -96,6 +97,8 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
 
     private ICommonBusinessObjectFactory boFactory;
 
+    private IAuthorizationConfig authorizationConfig;
+
     @Override
     @BeforeMethod
     public final void setUp()
@@ -104,10 +107,11 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
         commonServer = context.mock(ICommonServer.class);
         boFactory = context.mock(ICommonBusinessObjectFactory.class);
         sampleLister2 = context.mock(ISampleLister.class, "sampleListerForAPI");
+        authorizationConfig = context.mock(IAuthorizationConfig.class);
 
         service =
                 new GeneralInformationService(sessionManager, daoFactory, boFactory,
-                        propertiesBatchManager, commonServer)
+                        propertiesBatchManager, commonServer, authorizationConfig)
                     {
                         @Override
                         protected ISampleLister createSampleLister(PersonPE person)
@@ -123,6 +127,14 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
     {
         prepareGetSession();
 
+        context.checking(new Expectations()
+            {
+                {
+                    allowing(authorizationConfig).isProjectLevelEnabled();
+                    will(returnValue(false));
+                }
+            });
+
         Map<String, Set<Role>> namedRoleSets = service.listNamedRoleSets(SESSION_TOKEN);
 
         List<Entry<String, Set<Role>>> entries =
@@ -319,7 +331,7 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
                 + "PROPERTY PARENT_PROPERTY: parent property value] (with wildcards), "
                 + "ATTRIBUTE CODE: parent code AND PROPERTY PARENT_PROPERTY: "
                 + "parent property value (with wildcards)]", criteriaMatcher.getRecordedObjects()
-                .toString());
+                        .toString());
         context.assertIsSatisfied();
     }
 
@@ -351,7 +363,7 @@ public class GeneralInformationServiceTest extends AbstractServerTestCase
                 + "PROPERTY CHILD_PROPERTY: child property value] (with wildcards), "
                 + "ATTRIBUTE CODE: child code AND PROPERTY CHILD_PROPERTY: "
                 + "child property value (with wildcards)]", criteriaMatcher.getRecordedObjects()
-                .toString());
+                        .toString());
         context.assertIsSatisfied();
     }
 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMapTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMapTest.java
index 74b08314ac1..103459eefe5 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMapTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/CapabilityMapTest.java
@@ -32,6 +32,7 @@ import org.testng.annotations.Test;
 import ch.systemsx.cisd.common.logging.BufferedAppender;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.openbis.generic.server.authorization.annotation.Capability;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 import ch.systemsx.cisd.openbis.util.LogRecordingUtils;
 
@@ -80,7 +81,7 @@ public class CapabilityMapTest
     {
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList("A: SPACE_POWER_USER\t", "# Some comment", "",
-                        " B  INSTANCE_ETL_SERVER"), "<memory>");
+                        " B  INSTANCE_ETL_SERVER"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertEquals(
@@ -101,7 +102,7 @@ public class CapabilityMapTest
     {
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList("A:SPACE_POWER_USER\t", "# Some comment", "",
-                        " B  INSTANCE_ETL_SERVER"), "<memory>");
+                        " B  INSTANCE_ETL_SERVER"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertEquals(
@@ -122,7 +123,7 @@ public class CapabilityMapTest
     {
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList("A :SPACE_POWER_USER\t", "# Some comment", "",
-                        " B  INSTANCE_ETL_SERVER"), "<memory>");
+                        " B  INSTANCE_ETL_SERVER"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertEquals(
@@ -143,7 +144,7 @@ public class CapabilityMapTest
     {
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList("A : SPACE_POWER_USER\t", "# Some comment", "",
-                        " b  instance_etl_server"), "<memory>");
+                        " b  instance_etl_server"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertEquals(
@@ -164,7 +165,7 @@ public class CapabilityMapTest
     {
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList("A  SPACE_POWER_USER\t",
-                        " A  INSTANCE_ETL_SERVER"), "<memory>");
+                        " A  INSTANCE_ETL_SERVER"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertEquals(2, capMap.tryGetRoles(CapabilityMapTest.class.getDeclaredMethod("dummyA1"), null)
@@ -180,7 +181,7 @@ public class CapabilityMapTest
     {
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList("A : SPACE_POWER_USER,INSTANCE_ETL_SERVER\t"),
-                        "<memory>");
+                        "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertEquals(2, capMap.tryGetRoles(CapabilityMapTest.class.getDeclaredMethod("dummyA1"), null)
@@ -196,7 +197,7 @@ public class CapabilityMapTest
     {
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList("A\tSPACE_POWER_USER,INSTANCE_ETL_SERVER; "
-                        + "sample = SPACE_USER, SPACE_ETL_SERVER"), "<memory>");
+                        + "sample = SPACE_USER, SPACE_ETL_SERVER"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertRoles("[INSTANCE_ETL_SERVER, SPACE_POWER_USER]", capMap, "dummyA1", null);
@@ -207,7 +208,7 @@ public class CapabilityMapTest
     public void testOnlyParameterRoles() throws Exception
     {
         CapabilityMap capMap =
-                new CapabilityMap(Arrays.asList("a : sample = SPACE_USER, SPACE_ETL_SERVER"), "<memory>");
+                new CapabilityMap(Arrays.asList("a : sample = SPACE_USER, SPACE_ETL_SERVER"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertNoRoles(capMap, "dummyA1", null);
@@ -220,7 +221,7 @@ public class CapabilityMapTest
         CapabilityMap capMap =
                 new CapabilityMap(Arrays.asList(
                         "CapabilityMapTest.dummyA: SPACE_POWER_USER #wrong",
-                        "CapabilityMapTest.dummyB  NO_ROLE"), "<memory>");
+                        "CapabilityMapTest.dummyB  NO_ROLE"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("WARN  OPERATION.CapabilityMap - Ignoring mal-formed line "
                 + "'CapabilityMapTest.dummyA: SPACE_POWER_USER #wrong' in <memory> "
@@ -238,7 +239,7 @@ public class CapabilityMapTest
     public void testUserRoleDisabled() throws SecurityException, NoSuchMethodException
     {
         CapabilityMap capMap =
-                new CapabilityMap(Arrays.asList("A: INSTANCE_DISABLED\t"), "<memory>");
+                new CapabilityMap(Arrays.asList("A: INSTANCE_DISABLED\t"), "<memory>", new TestAuthorizationConfig());
 
         assertEquals("", logRecorder.getLogContent());
         assertEquals(
@@ -246,7 +247,7 @@ public class CapabilityMapTest
                 capMap.tryGetRoles(CapabilityMapTest.class.getDeclaredMethod("dummyA1"), null).toArray()[0]);
         assertTrue(capMap.tryGetRoles(CapabilityMapTest.class.getDeclaredMethod("dummyA1"), null)
                 .toArray(new RoleWithHierarchy[0])[0]
-                .getRoles().isEmpty());
+                        .getRoles().isEmpty());
     }
 
     private void assertRoles(String expectedRoles, CapabilityMap capMap, String methodName,
@@ -267,4 +268,15 @@ public class CapabilityMapTest
         assertEquals(null, roles);
     }
 
+    private static class TestAuthorizationConfig implements IAuthorizationConfig
+    {
+
+        @Override
+        public boolean isProjectLevelEnabled()
+        {
+            return false;
+        }
+
+    }
+
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessControllerTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessControllerTest.java
index e4d24abc7a8..6ba41054fd1 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessControllerTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/authorization/DefaultAccessControllerTest.java
@@ -52,6 +52,7 @@ import ch.systemsx.cisd.openbis.generic.server.authorization.annotation.RolesAll
 import ch.systemsx.cisd.openbis.generic.server.authorization.predicate.IPredicate;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IProjectDAO;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode;
 import ch.systemsx.cisd.openbis.generic.shared.dto.IAuthSession;
@@ -78,6 +79,8 @@ public final class DefaultAccessControllerTest
 
     private IProjectDAO projectDAO;
 
+    private IAuthorizationConfig authorizationConfig;
+
     private ProjectPE project;
 
     @BeforeTest
@@ -105,13 +108,17 @@ public final class DefaultAccessControllerTest
         context = new Mockery();
         daoFactory = context.mock(IDAOFactory.class);
         projectDAO = context.mock(IProjectDAO.class);
+        authorizationConfig = context.mock(IAuthorizationConfig.class);
         project = new ProjectPE();
         project.setCode("TEST");
         project.setSpace(new SpacePEBuilder().code("TEST").getSpace());
-        accessController = new DefaultAccessController(daoFactory);
+
         context.checking(new Expectations()
             {
                 {
+                    allowing(daoFactory).getAuthorizationConfig();
+                    will(returnValue(authorizationConfig));
+
                     allowing(daoFactory).getProjectDAO();
                     will(returnValue(projectDAO));
 
@@ -119,6 +126,8 @@ public final class DefaultAccessControllerTest
                     will(returnValue(project));
                 }
             });
+
+        accessController = new DefaultAccessController(daoFactory);
     }
 
     @AfterMethod
@@ -406,8 +415,7 @@ public final class DefaultAccessControllerTest
         @Capability("MY_CAP")
         public void myMethodWithOtherRolesOverridden();
 
-        @RolesAllowed(
-        { RoleWithHierarchy.SPACE_ETL_SERVER, RoleWithHierarchy.SPACE_OBSERVER })
+        @RolesAllowed({ RoleWithHierarchy.SPACE_ETL_SERVER, RoleWithHierarchy.SPACE_OBSERVER })
         public void myMethodWithTwoRoles();
 
         @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
@@ -420,27 +428,26 @@ public final class DefaultAccessControllerTest
 
         @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
         public void myMethodWithGardedArgumentWithDifferentRoles(String sessionToken,
-                @AuthorizationGuard(guardClass = MockPredicate.class,
-                        rolesAllowed = { RoleWithHierarchy.SPACE_ETL_SERVER }) String argument1);
+                @AuthorizationGuard(guardClass = MockPredicate.class, rolesAllowed = { RoleWithHierarchy.SPACE_ETL_SERVER }) String argument1);
 
         @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
         @Capability("MY_CAP")
         public void myMethodWithGardedArgumentWithRolesOverridden(String sessionToken,
-                @AuthorizationGuard(name = "ARG1", guardClass = MockPredicate.class,
-                        rolesAllowed = { RoleWithHierarchy.SPACE_ETL_SERVER }) String argument1);
+                @AuthorizationGuard(name = "ARG1", guardClass = MockPredicate.class, rolesAllowed = {
+                        RoleWithHierarchy.SPACE_ETL_SERVER }) String argument1);
 
         @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
         @Capability("MY_CAP2")
         public void myMethodWithGardedArgumentWithRolesOverridden2(String sessionToken,
-                @AuthorizationGuard(name = "ARG1", guardClass = MockPredicate.class,
-                        rolesAllowed = { RoleWithHierarchy.SPACE_ETL_SERVER }) String argument1);
+                @AuthorizationGuard(name = "ARG1", guardClass = MockPredicate.class, rolesAllowed = {
+                        RoleWithHierarchy.SPACE_ETL_SERVER }) String argument1);
 
         @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
         public void myMethodWithGardedArgumentWithRolesOverridden3(String sessionToken,
-                @AuthorizationGuard(name = "ARG1", guardClass = MockPredicate.class,
-                        rolesAllowed = { RoleWithHierarchy.SPACE_POWER_USER }) String argument1,
-                @AuthorizationGuard(name = "ARG2", guardClass = MockPredicate.class,
-                        rolesAllowed = { RoleWithHierarchy.SPACE_USER }) String argument2);
+                @AuthorizationGuard(name = "ARG1", guardClass = MockPredicate.class, rolesAllowed = {
+                        RoleWithHierarchy.SPACE_POWER_USER }) String argument1,
+                @AuthorizationGuard(name = "ARG2", guardClass = MockPredicate.class, rolesAllowed = {
+                        RoleWithHierarchy.SPACE_USER }) String argument2);
     }
 
     /**
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchyTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchyTest.java
index 4704b1b6f28..19d96a3e948 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchyTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/RoleWithHierarchyTest.java
@@ -16,9 +16,15 @@
 
 package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
 
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
 import org.testng.AssertJUnit;
 import org.testng.annotations.Test;
 
+import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.*;
+
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleLevel;
 
@@ -57,14 +63,42 @@ public class RoleWithHierarchyTest extends AssertJUnit
                 RoleWithHierarchy.valueOf(RoleLevel.INSTANCE, RoleCode.OBSERVER));
         assertEquals(RoleWithHierarchy.SPACE_ADMIN,
                 RoleWithHierarchy.valueOf(RoleLevel.SPACE, RoleCode.ADMIN));
+        assertEquals(RoleWithHierarchy.PROJECT_ADMIN,
+                RoleWithHierarchy.valueOf(RoleLevel.PROJECT, RoleCode.ADMIN));
+    }
+
+    @Test
+    public void testGetRoles() throws Exception
+    {
+        assertRoles(RoleWithHierarchy.INSTANCE_ADMIN.getRoles(), INSTANCE_ADMIN);
+        assertRoles(RoleWithHierarchy.INSTANCE_OBSERVER.getRoles(), INSTANCE_ADMIN, INSTANCE_OBSERVER);
+
+        assertRoles(RoleWithHierarchy.SPACE_ADMIN.getRoles(), INSTANCE_ADMIN, SPACE_ADMIN);
+        assertRoles(RoleWithHierarchy.SPACE_POWER_USER.getRoles(), INSTANCE_ADMIN, SPACE_ADMIN, SPACE_POWER_USER);
+        assertRoles(RoleWithHierarchy.SPACE_USER.getRoles(), INSTANCE_ADMIN, SPACE_ADMIN, SPACE_POWER_USER, SPACE_USER);
+        assertRoles(RoleWithHierarchy.SPACE_OBSERVER.getRoles(), INSTANCE_ADMIN, INSTANCE_OBSERVER, SPACE_ADMIN, SPACE_POWER_USER, SPACE_USER,
+                SPACE_OBSERVER);
+
+        assertRoles(RoleWithHierarchy.PROJECT_ADMIN.getRoles(), INSTANCE_ADMIN, SPACE_ADMIN, PROJECT_ADMIN);
+        assertRoles(RoleWithHierarchy.PROJECT_POWER_USER.getRoles(), INSTANCE_ADMIN, SPACE_ADMIN, SPACE_POWER_USER, PROJECT_ADMIN,
+                PROJECT_POWER_USER);
+        assertRoles(RoleWithHierarchy.PROJECT_USER.getRoles(), INSTANCE_ADMIN, SPACE_ADMIN, SPACE_POWER_USER, SPACE_USER, PROJECT_ADMIN,
+                PROJECT_POWER_USER, PROJECT_USER);
+        assertRoles(RoleWithHierarchy.PROJECT_OBSERVER.getRoles(), INSTANCE_ADMIN, INSTANCE_OBSERVER, SPACE_ADMIN, SPACE_POWER_USER, SPACE_USER,
+                SPACE_OBSERVER, PROJECT_ADMIN, PROJECT_POWER_USER, PROJECT_USER, PROJECT_OBSERVER);
     }
 
     @Test
     public void testFigureRoleCode() throws Exception
     {
+        assertEquals(RoleCode.USER, RoleWithHierarchy.figureRoleCode("PROJECT_USER", RoleLevel.PROJECT));
+        assertEquals(RoleCode.POWER_USER,
+                RoleWithHierarchy.figureRoleCode("PROJECT_POWER_USER", RoleLevel.PROJECT));
+
         assertEquals(RoleCode.USER, RoleWithHierarchy.figureRoleCode("SPACE_USER", RoleLevel.SPACE));
         assertEquals(RoleCode.POWER_USER,
                 RoleWithHierarchy.figureRoleCode("SPACE_POWER_USER", RoleLevel.SPACE));
+
         assertEquals(RoleCode.ADMIN,
                 RoleWithHierarchy.figureRoleCode("INSTANCE_ADMIN", RoleLevel.INSTANCE));
     }
@@ -78,6 +112,7 @@ public class RoleWithHierarchyTest extends AssertJUnit
     @Test
     public void testFigureRoleLevel() throws Exception
     {
+        assertEquals(RoleLevel.PROJECT, RoleWithHierarchy.figureRoleLevel("PROJECT_USER"));
         assertEquals(RoleLevel.SPACE, RoleWithHierarchy.figureRoleLevel("SPACE_USER"));
         assertEquals(RoleLevel.INSTANCE, RoleWithHierarchy.figureRoleLevel("INSTANCE_USER"));
     }
@@ -106,7 +141,13 @@ public class RoleWithHierarchyTest extends AssertJUnit
     @Test(expectedExceptions = IllegalArgumentException.class)
     public void testFigureRoleLevelNotMatchingConventionNonexistentLevel() throws Exception
     {
-        RoleWithHierarchy.figureRoleLevel("PROJECT_USER");
+        RoleWithHierarchy.figureRoleLevel("EXPERIMENT_USER");
+    }
+
+    private void assertRoles(Set<RoleWithHierarchy> actualRoles, RoleWithHierarchy... expectedRoles)
+    {
+        Set<RoleWithHierarchy> expectedRolesSet = new HashSet<RoleWithHierarchy>(Arrays.asList(expectedRoles));
+        assertEquals(expectedRolesSet, actualRoles);
     }
 
 }
-- 
GitLab