diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java
index 4430974abe287c390b7114a7e36b7d874bae285f..fc45681e74582b38a004824bab7eb035debb14d9 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientService.java
@@ -285,6 +285,18 @@ public interface ICommonClientService extends IClientService
             final TableExportCriteria<TableModelRowWithObject<RoleAssignment>> criteria)
             throws UserFailureException;
 
+    /**
+     * Registers a new role from given role set code, project identifier and grantee.
+     */
+    public void registerProjectRole(RoleWithHierarchy role, String projectIdentifier, Grantee grantee)
+            throws UserFailureException;
+
+    /**
+     * Deletes the role described by given role set code, project identifier and grantee.
+     */
+    public void deleteProjectRole(RoleWithHierarchy role, String projectIdentifier, Grantee grantee)
+            throws UserFailureException;
+
     /**
      * Registers a new role from given role set code, space code and grantee.
      */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java
index a0a6f311a0ec59a61fbb2ab05819b35afc7263cd..57366d4b3a1691866799539f73a92ea81896eac6 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/ICommonClientServiceAsync.java
@@ -234,6 +234,14 @@ public interface ICommonClientServiceAsync extends IClientServiceAsync
             TableExportCriteria<TableModelRowWithObject<RoleAssignment>> exportCriteria,
             AsyncCallback<String> callback);
 
+    /** @see ICommonClientService#registerProjectRole(RoleWithHierarchy, String, Grantee) */
+    public void registerProjectRole(RoleWithHierarchy role, String projectIdentifier, Grantee grantee,
+            AsyncCallback<Void> asyncCallback);
+
+    /** @see ICommonClientService#deleteProjectRole(RoleWithHierarchy, String, Grantee) */
+    public void deleteProjectRole(RoleWithHierarchy role, String projectIdentifier, Grantee grantee,
+            AsyncCallback<Void> asyncCallback);
+    
     /** @see ICommonClientService#registerSpaceRole(RoleWithHierarchy, String, Grantee) */
     public void registerSpaceRole(RoleWithHierarchy role, String spaceCode, Grantee grantee,
             AsyncCallback<Void> asyncCallback);
@@ -1005,7 +1013,7 @@ public interface ICommonClientServiceAsync extends IClientServiceAsync
     public void listPersons(AsyncCallback<List<Person>> callback);
 
     /**
-     * @see ICommonClientService#listPersons()
+     * @see ICommonClientService#listActivePersons()
      */
     public void listActivePersons(AsyncCallback<List<Person>> callback);
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RoleAssignmentGrid.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RoleAssignmentGrid.java
index 1c9b771542be56bf6f4ac817c172da7308b03c13..c569893bf5ffd0ebb60b3a5f3710ba8db99765ca 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RoleAssignmentGrid.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RoleAssignmentGrid.java
@@ -142,17 +142,22 @@ public class RoleAssignmentGrid extends TypedTableGrid<RoleAssignment>
             Grantee grantee =
                     (person != null && StringUtils.isBlank(person.getUserId()) == false) ? Grantee
                             .createPerson(person.getUserId()) : Grantee
-                            .createAuthorizationGroup(selectedRoleAssignment
-                                    .getAuthorizationGroup().getCode());
-            if (selectedRoleAssignment.getSpace() == null)
+                                    .createAuthorizationGroup(selectedRoleAssignment
+                                            .getAuthorizationGroup().getCode());
+            if (selectedRoleAssignment.getRoleSetCode().isInstanceLevel())
             {
                 viewContext.getService().deleteInstanceRole(
                         selectedRoleAssignment.getRoleSetCode(), grantee, roleListRefreshCallback);
-            } else
+            } else if (selectedRoleAssignment.getRoleSetCode().isSpaceLevel())
             {
                 viewContext.getService().deleteSpaceRole(selectedRoleAssignment.getRoleSetCode(),
                         selectedRoleAssignment.getSpace().getCode(), grantee,
                         roleListRefreshCallback);
+            } else if (selectedRoleAssignment.getRoleSetCode().isProjectLevel())
+            {
+                viewContext.getService().deleteProjectRole(selectedRoleAssignment.getRoleSetCode(),
+                        selectedRoleAssignment.getProject().getIdentifier(), grantee,
+                        roleListRefreshCallback);
             }
         }
 
@@ -190,14 +195,13 @@ public class RoleAssignmentGrid extends TypedTableGrid<RoleAssignment>
     {
         return Arrays.asList(RoleAssignmentGridColumnIDs.AUTHORIZATION_GROUP,
                 RoleAssignmentGridColumnIDs.DATABASE_INSTANCE, RoleAssignmentGridColumnIDs.PERSON,
-                RoleAssignmentGridColumnIDs.ROLE, RoleAssignmentGridColumnIDs.SPACE);
+                RoleAssignmentGridColumnIDs.ROLE, RoleAssignmentGridColumnIDs.SPACE, RoleAssignmentGridColumnIDs.PROJECT);
     }
 
     @Override
     public DatabaseModificationKind[] getRelevantModifications()
     {
-        return new DatabaseModificationKind[]
-        { DatabaseModificationKind.createOrDelete(ObjectKind.ROLE_ASSIGNMENT),
+        return new DatabaseModificationKind[] { DatabaseModificationKind.createOrDelete(ObjectKind.ROLE_ASSIGNMENT),
                 DatabaseModificationKind.edit(ObjectKind.ROLE_ASSIGNMENT) };
     }
 }
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 9505434ae2643f554e7068e329529879ec733a45..935654254b0fbf913de94455e83378071bddf0a1 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
@@ -30,14 +30,17 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericCon
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.help.HelpPageIdentifier;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.AuthorizationGroupSelectionWidget;
-import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.SpaceSelectionWidget;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.PersonSelectionWidget;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.SpaceSelectionWidget;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.experiment.ProjectSelectionWidget;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.AbstractRegistrationDialog;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.FieldUtil;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.DialogWithOnlineHelpUtils;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.GWTUtils;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IDelegatedAction;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
 
 /**
@@ -57,6 +60,8 @@ public class AddRoleAssignmentDialog extends AbstractRegistrationDialog
 
     private final SpaceSelectionWidget group;
 
+    private final ProjectSelectionWidget project;
+
     private final PersonSelectionWidget person;
 
     private final AdapterField roleBox;
@@ -77,12 +82,16 @@ public class AddRoleAssignmentDialog extends AbstractRegistrationDialog
         group = new SpaceSelectionWidget(viewContext, PREFIX, false, false);
         group.setWidth(100);
 
-        roleBox = new AdapterField(new RoleListBox(viewContext, group));
+        project = new ProjectSelectionWidget(viewContext, PREFIX);
+        project.setWidth(100);
+
+        roleBox = new AdapterField(new RoleListBox(viewContext, group, project));
         roleBox.setFieldLabel("Role");
         roleBox.setWidth(100);
         roleBox.setId(ROLE_FIELD_ID);
         addField(roleBox);
         addField(group);
+        addField(project);
 
         RadioGroup radioGroup = new RadioGroup();
         radioGroup.setFieldLabel("Grantee Type");
@@ -131,15 +140,21 @@ public class AddRoleAssignmentDialog extends AbstractRegistrationDialog
                         : Grantee.createAuthorizationGroup(authGroup
                                 .tryGetSelectedAuthorizationGroupCode());
 
-        if (((RoleListBox) roleBox.getWidget()).getValue().isSpaceLevel() == false)
+        RoleListBox roleWidget = (RoleListBox) roleBox.getWidget();
+        RoleWithHierarchy role = roleWidget.getValue();
+
+        if (role.isInstanceLevel())
+        {
+            viewContext.getService().registerInstanceRole(roleWidget.getValue(), grantee, registrationCallback);
+        } else if (role.isSpaceLevel())
         {
-            viewContext.getService().registerInstanceRole(
-                    ((RoleListBox) roleBox.getWidget()).getValue(), grantee, registrationCallback);
-        } else
+            Space selectedSpace = group.tryGetSelectedSpace();
+            viewContext.getService().registerSpaceRole(roleWidget.getValue(), selectedSpace.getCode(), grantee,
+                    registrationCallback);
+        } else if (role.isProjectLevel())
         {
-            Space spaceOrNull = group.tryGetSelectedSpace();
-            viewContext.getService().registerSpaceRole(
-                    ((RoleListBox) roleBox.getWidget()).getValue(), spaceOrNull.getCode(), grantee,
+            Project selectedProject = project.tryGetSelectedProject();
+            viewContext.getService().registerProjectRole(roleWidget.getValue(), selectedProject.getIdentifier(), grantee,
                     registrationCallback);
         }
     }
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 39cd50d7a60d22619f0b6d7cfdee58a5bd8cb0f2..a9774963f9037fdfc611dbd0669a3c37cc6e9f3f 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
@@ -26,6 +26,7 @@ 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.experiment.ProjectSelectionWidget;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.FieldUtil;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy;
 
@@ -41,7 +42,8 @@ public class RoleListBox extends ListBox
 
     private List<RoleWithHierarchy> roles;
 
-    public RoleListBox(IViewContext<ICommonClientServiceAsync> viewContext, final SpaceSelectionWidget groupWidget)
+    public RoleListBox(IViewContext<ICommonClientServiceAsync> viewContext, final SpaceSelectionWidget groupWidget,
+            final ProjectSelectionWidget projectWidget)
     {
         this.viewContext = viewContext;
 
@@ -50,7 +52,7 @@ public class RoleListBox extends ListBox
             addItem(visibleRoleCode.toString());
         }
         setVisibleItemCount(1);
-        updateWidgetsVisibility(groupWidget);
+        updateWidgetsVisibility(groupWidget, projectWidget);
 
         addChangeHandler(new ChangeHandler()
             {
@@ -58,10 +60,9 @@ public class RoleListBox extends ListBox
                 @Override
                 public final void onChange(final ChangeEvent sender)
                 {
-                    updateWidgetsVisibility(groupWidget);
+                    updateWidgetsVisibility(groupWidget, projectWidget);
                 }
             });
-
     }
 
     public final RoleWithHierarchy getValue()
@@ -69,21 +70,31 @@ public class RoleListBox extends ListBox
         return getRoles().get(getSelectedIndex());
     }
 
-    private void updateWidgetsVisibility(final SpaceSelectionWidget group)
+    private void updateWidgetsVisibility(final SpaceSelectionWidget group, final ProjectSelectionWidget project)
     {
         int index = getSelectedIndex();
+
         if (index < 0 || index >= getRoles().size())
+        {
             return;
-        boolean groupLevel = getRoles().get(index).isSpaceLevel();
-        FieldUtil.setMandatoryFlag(group, groupLevel);
-        group.setVisible(groupLevel);
+        }
+
+        RoleWithHierarchy role = getRoles().get(index);
+        boolean spaceLevel = role.isSpaceLevel();
+        boolean projectLevel = role.isProjectLevel();
+
+        FieldUtil.setMandatoryFlag(group, spaceLevel);
+        group.setVisible(spaceLevel);
+
+        FieldUtil.setMandatoryFlag(project, projectLevel);
+        project.setVisible(projectLevel);
     }
 
     private List<RoleWithHierarchy> getRoles()
     {
         if (roles == null)
         {
-            boolean projectLevelAuthorizationEnabled = viewContext.getModel().getApplicationInfo().isProjectAuthorizationEnabled();
+            boolean projectLevelAuthorizationEnabled = viewContext.getModel().getApplicationInfo().isProjectLevelAuthorizationEnabled();
 
             roles = new ArrayList<RoleWithHierarchy>();
             for (RoleWithHierarchy role : RoleWithHierarchy.values())
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 f6159f0450c968fc1a350f08adce53c31a2e2d83..a58a18262a0629aaa4c736931f92efe625d9e64a 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
@@ -49,6 +49,8 @@ public final class ApplicationInfo implements IsSerializable
 
     private boolean projectLevelAuthorizationEnabled;
 
+    private boolean projectLevelAuthorizationUser;
+
     private WebClientConfiguration webClientConfiguration;
 
     private Set<String> enabledTechnologies;
@@ -181,14 +183,24 @@ public final class ApplicationInfo implements IsSerializable
         this.projectSamplesEnabled = projectSamplesEnabled;
     }
 
-    public boolean isProjectAuthorizationEnabled()
+    public boolean isProjectLevelAuthorizationEnabled()
     {
         return projectLevelAuthorizationEnabled;
     }
 
-    public void setProjectLevelAuthorizationEnabled(boolean projectAuthorizationEnabled)
+    public void setProjectLevelAuthorizationEnabled(boolean projectLevelAuthorizationEnabled)
+    {
+        this.projectLevelAuthorizationEnabled = projectLevelAuthorizationEnabled;
+    }
+
+    public boolean isProjectLevelAuthorizationUser()
+    {
+        return projectLevelAuthorizationUser;
+    }
+
+    public void setProjectLevelAuthorizationUser(boolean projectLevelAuthorizationUser)
     {
-        this.projectLevelAuthorizationEnabled = projectAuthorizationEnabled;
+        this.projectLevelAuthorizationUser = projectLevelAuthorizationUser;
     }
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/RoleAssignmentGridColumnIDs.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/RoleAssignmentGridColumnIDs.java
index 2d1af972ac1bd57fc8a53b66cda858f589727d96..c11dab62fe9ed9607f6af33b6d0bb7d7d76dd5c0 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/RoleAssignmentGridColumnIDs.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/RoleAssignmentGridColumnIDs.java
@@ -30,6 +30,8 @@ public class RoleAssignmentGridColumnIDs
     public static final String AUTHORIZATION_GROUP = "AUTHORIZATION_GROUP";
 
     public static final String SPACE = "SPACE";
+    
+    public static final String PROJECT = "PROJECT";
 
     public static final String ROLE = "ROLE";
 
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 9b260bbbc6c0167db99538e90c9490fdbbb76ab6..c1d3ce70d524a95cd0c556d0eeaccf5a62273470 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
@@ -476,6 +476,7 @@ public abstract class AbstractClientService implements IClientService,
         applicationInfo.setArchivingConfigured(isArchivingConfigured());
         applicationInfo.setProjectSamplesEnabled(isProjectSamplesEnabled());
         applicationInfo.setProjectLevelAuthorizationEnabled(isProjectLevelAuthorizationEnabled());
+        applicationInfo.setProjectLevelAuthorizationUser(isProjectLevelAuthorizationUser());
         applicationInfo.setVersion(getVersion());
         return applicationInfo;
     }
@@ -570,7 +571,19 @@ public abstract class AbstractClientService implements IClientService,
     {
         try
         {
-            return getServer().isProjectAuthorizationEnabled(getSessionToken());
+            return getServer().isProjectLevelAuthorizationEnabled(getSessionToken());
+        } catch (InvalidSessionException e)
+        {
+            // ignored
+        }
+        return false;
+    }
+
+    private boolean isProjectLevelAuthorizationUser()
+    {
+        try
+        {
+            return getServer().isProjectLevelAuthorizationUser(getSessionToken());
         } catch (InvalidSessionException e)
         {
             // ignored
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java
index ebdd1ccdd6aa3c46f13105427655f642047a7485..2616f00df8bfc246220b71e9081815eb76ed75ec 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/CommonClientService.java
@@ -127,6 +127,7 @@ import ch.systemsx.cisd.openbis.generic.shared.ICommonServer;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomain;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomainSearchOption;
+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.IEntityInformationHolderWithPermId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IIdHolder;
@@ -250,6 +251,9 @@ public final class CommonClientService extends AbstractClientService implements
     @Autowired
     private IApplicationServerApi applicationServerApi;
 
+    @Autowired
+    private IAuthorizationConfig authorizationConfig;
+
     public CommonClientService(final ICommonServer commonServer,
             final IRequestContextProvider requestContextProvider)
     {
@@ -393,6 +397,15 @@ public final class CommonClientService extends AbstractClientService implements
         commonServer.registerPerson(sessionToken, code);
     }
 
+    @Override
+    public final void registerProjectRole(final RoleWithHierarchy role, final String projectIdentifier,
+            final Grantee grantee)
+    {
+        final String sessionToken = getSessionToken();
+        final ProjectIdentifier projectIdentifierObject = ProjectIdentifierFactory.parse(projectIdentifier);
+        commonServer.registerProjectRole(sessionToken, role.getRoleCode(), projectIdentifierObject, grantee);
+    }
+
     @Override
     public final void registerSpaceRole(final RoleWithHierarchy role, final String group,
             final Grantee grantee)
@@ -410,6 +423,15 @@ public final class CommonClientService extends AbstractClientService implements
         commonServer.registerInstanceRole(sessionToken, role.getRoleCode(), grantee);
     }
 
+    @Override
+    public final void deleteProjectRole(final RoleWithHierarchy role, final String projectIdentifier,
+            final Grantee grantee)
+    {
+        final String sessionToken = getSessionToken();
+        final ProjectIdentifier projectIdentifierObject = ProjectIdentifierFactory.parse(projectIdentifier);
+        commonServer.deleteProjectRole(sessionToken, role.getRoleCode(), projectIdentifierObject, grantee);
+    }
+
     @Override
     public final void deleteSpaceRole(final RoleWithHierarchy role, final String group,
             final Grantee grantee)
@@ -862,7 +884,7 @@ public final class CommonClientService extends AbstractClientService implements
             DefaultResultSetConfig<String, TableModelRowWithObject<RoleAssignment>> criteria)
             throws ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException
     {
-        return listEntities(new RoleAssignmentProvider(commonServer, getSessionToken()), criteria);
+        return listEntities(new RoleAssignmentProvider(commonServer, authorizationConfig, getSessionToken()), criteria);
     }
 
     public final List<RoleAssignment> listRoleAssignments()
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProvider.java
index b5dac6e3f935d840370e29e42be455c6d8e0e61f..6672c6178f6955b701f00a4643efa70d51f2db73 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProvider.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProvider.java
@@ -18,14 +18,17 @@ package ch.systemsx.cisd.openbis.generic.client.web.server.resultset;
 
 import static ch.systemsx.cisd.openbis.generic.client.web.client.dto.RoleAssignmentGridColumnIDs.AUTHORIZATION_GROUP;
 import static ch.systemsx.cisd.openbis.generic.client.web.client.dto.RoleAssignmentGridColumnIDs.PERSON;
+import static ch.systemsx.cisd.openbis.generic.client.web.client.dto.RoleAssignmentGridColumnIDs.PROJECT;
 import static ch.systemsx.cisd.openbis.generic.client.web.client.dto.RoleAssignmentGridColumnIDs.ROLE;
 import static ch.systemsx.cisd.openbis.generic.client.web.client.dto.RoleAssignmentGridColumnIDs.SPACE;
 
 import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.ICommonServer;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AuthorizationGroup;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleAssignment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TypedTableModel;
@@ -39,9 +42,12 @@ import ch.systemsx.cisd.openbis.generic.shared.util.TypedTableModelBuilder;
 public class RoleAssignmentProvider extends AbstractCommonTableModelProvider<RoleAssignment>
 {
 
-    public RoleAssignmentProvider(ICommonServer commonServer, String sessionToken)
+    private IAuthorizationConfig authorizationConfig;
+
+    public RoleAssignmentProvider(ICommonServer commonServer, IAuthorizationConfig authorizationConfig, String sessionToken)
     {
         super(commonServer, sessionToken);
+        this.authorizationConfig = authorizationConfig;
     }
 
     @Override
@@ -52,18 +58,37 @@ public class RoleAssignmentProvider extends AbstractCommonTableModelProvider<Rol
         builder.addColumn(PERSON);
         builder.addColumn(AUTHORIZATION_GROUP);
         builder.addColumn(SPACE);
+
+        if (authorizationConfig.isProjectLevelEnabled())
+        {
+            builder.addColumn(PROJECT);
+        }
+
         builder.addColumn(ROLE);
         for (RoleAssignment roleAssignment : roles)
         {
+            if (roleAssignment.getProject() != null && false == authorizationConfig.isProjectLevelEnabled())
+            {
+                continue;
+            }
+
             builder.addRow(roleAssignment);
             Person person = roleAssignment.getPerson();
             AuthorizationGroup group = roleAssignment.getAuthorizationGroup();
             Space space = roleAssignment.getSpace();
+            Project project = roleAssignment.getProject();
             builder.column(PERSON).addString(person == null ? "" : person.getUserId());
             builder.column(AUTHORIZATION_GROUP).addString(group == null ? "" : group.getCode());
             builder.column(SPACE).addString(space == null ? "" : space.getCode());
+
+            if (authorizationConfig.isProjectLevelEnabled())
+            {
+                builder.column(PROJECT).addString(project == null ? "" : project.getIdentifier());
+            }
+
             builder.column(ROLE).addString(roleAssignment.getCode());
         }
+
         return builder.getModel();
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
index ba6b1ba197327c67ea2ec8b6febbc6a37dcafe18..d8333c424c1456a7470f55f07a1a1bc3867db996 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
@@ -66,7 +66,6 @@ import ch.systemsx.cisd.openbis.generic.shared.IOpenBisSessionManager;
 import ch.systemsx.cisd.openbis.generic.shared.IRemoteHostValidator;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.ResourceNames;
-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.EntityVisitComparatorByTimeStamp;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
@@ -775,10 +774,17 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
     }
 
     @Override
-    public boolean isProjectAuthorizationEnabled(String sessionToken)
+    public boolean isProjectLevelAuthorizationEnabled(String sessionToken)
+    {
+        checkSession(sessionToken);
+        return authorizationConfig.isProjectLevelEnabled();
+    }
+
+    @Override
+    public boolean isProjectLevelAuthorizationUser(String sessionToken)
     {
         Session session = getSession(sessionToken);
-        return new AuthorizationConfigFacade(authorizationConfig).isProjectLevelEnabled(session.getUserName());
+        return authorizationConfig.isProjectLevelUser(session.getUserName());
     }
 
     @SuppressWarnings("deprecation")
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
index 96744e047f5daf0c0433e57046938b59c11fb8db..b3bbb5e222a37fc108d5ff524695a3425b167fcc 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
@@ -434,6 +434,19 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
         return RoleAssignmentTranslator.translate(roles);
     }
 
+    @Override
+    @RolesAllowed(RoleWithHierarchy.PROJECT_ADMIN)
+    public void registerProjectRole(String sessionToken, RoleCode roleCode,
+            @AuthorizationGuard(guardClass = ProjectIdentifierPredicate.class) ProjectIdentifier projectIdentifier, Grantee grantee)
+    {
+        final Session session = getSession(sessionToken);
+
+        final NewRoleAssignment newRoleAssignment = new NewRoleAssignment();
+        newRoleAssignment.setProjectIdentifier(projectIdentifier);
+
+        registerRole(roleCode, grantee, session, newRoleAssignment);
+    }
+
     @Override
     @RolesAllowed(RoleWithHierarchy.SPACE_ADMIN)
     public void registerSpaceRole(String sessionToken, RoleCode roleCode,
@@ -469,6 +482,52 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
 
     }
 
+    @Override
+    @RolesAllowed(RoleWithHierarchy.PROJECT_ADMIN)
+    public void deleteProjectRole(String sessionToken, RoleCode roleCode,
+            @AuthorizationGuard(guardClass = ProjectIdentifierPredicate.class) ProjectIdentifier projectIdentifier, Grantee grantee)
+    {
+        final Session session = getSession(sessionToken);
+
+        final RoleAssignmentPE roleAssignment =
+                getDAOFactory().getRoleAssignmentDAO().tryFindProjectRoleAssignment(roleCode,
+                        projectIdentifier, grantee);
+        if (roleAssignment == null)
+        {
+            throw new UserFailureException("Given project role does not exist.");
+        }
+
+        final PersonPE personPE = session.tryGetPerson();
+
+        if (roleAssignment.getPerson() != null && roleAssignment.getPerson().equals(personPE)
+                && roleAssignment.getRole().equals(RoleCode.ADMIN))
+        {
+            boolean isInstanceAdmin = false;
+            boolean isSpaceAdmin = false;
+
+            for (final RoleAssignmentPE userAssignment : personPE.getRoleAssignments())
+            {
+                if (userAssignment.getRoleWithHierarchy().isInstanceLevel() && userAssignment.getRole().equals(RoleCode.ADMIN))
+                {
+                    isInstanceAdmin = true;
+                } else if (userAssignment.getRoleWithHierarchy().isSpaceLevel()
+                        && userAssignment.getSpace().getCode().equals(roleAssignment.getProject().getSpace().getCode())
+                        && userAssignment.getRole().equals(RoleCode.ADMIN))
+                {
+                    isSpaceAdmin = true;
+                }
+            }
+
+            if (isInstanceAdmin == false && isSpaceAdmin == false)
+            {
+                throw new UserFailureException(
+                        "For safety reason you cannot give away your own project admin power. "
+                                + "Ask space or instance admin to do that for you.");
+            }
+        }
+        getDAOFactory().getRoleAssignmentDAO().deleteRoleAssignment(roleAssignment);
+    }
+
     @Override
     @RolesAllowed(RoleWithHierarchy.SPACE_ADMIN)
     public void deleteSpaceRole(String sessionToken, RoleCode roleCode,
@@ -483,18 +542,22 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
         {
             throw new UserFailureException("Given space role does not exist.");
         }
+
         final PersonPE personPE = session.tryGetPerson();
+
         if (roleAssignment.getPerson() != null && roleAssignment.getPerson().equals(personPE)
                 && roleAssignment.getRole().equals(RoleCode.ADMIN))
         {
             boolean isInstanceAdmin = false;
-            for (final RoleAssignmentPE roleAssigment : personPE.getRoleAssignments())
+
+            for (final RoleAssignmentPE userAssignment : personPE.getRoleAssignments())
             {
-                if (roleAssignment.getRoleWithHierarchy().isInstanceLevel() && roleAssigment.getRole().equals(RoleCode.ADMIN))
+                if (userAssignment.getRoleWithHierarchy().isInstanceLevel() && userAssignment.getRole().equals(RoleCode.ADMIN))
                 {
                     isInstanceAdmin = true;
                 }
             }
+
             if (isInstanceAdmin == false)
             {
                 throw new UserFailureException(
@@ -540,7 +603,7 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
     }
 
     @Override
-    @RolesAllowed(RoleWithHierarchy.INSTANCE_ADMIN)
+    @RolesAllowed(RoleWithHierarchy.PROJECT_OBSERVER)
     public List<Person> listActivePersons(String sessionToken)
     {
         checkSession(sessionToken);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
index 1e3dd25cb98c5d208db8e73aa62054b307bff592..3dd15b719ba57b9ed7d79ce21161b72eb05b3d34 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServerLogger.java
@@ -222,6 +222,14 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
         return null;
     }
 
+    @Override
+    public void registerProjectRole(final String sessionToken, final RoleCode roleCode,
+            final ProjectIdentifier projectIdentifier, final Grantee grantee)
+    {
+        logTracking(sessionToken, "register_role", "ROLE(%s) PROJECT(%s) GRANTEE(%s)", roleCode,
+                projectIdentifier, grantee);
+    }
+
     @Override
     public void registerSpaceRole(final String sessionToken, final RoleCode roleCode,
             final SpaceIdentifier spaceIdentifier, final Grantee grantee)
@@ -239,6 +247,14 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
 
     }
 
+    @Override
+    public void deleteProjectRole(final String sessionToken, final RoleCode roleCode,
+            final ProjectIdentifier projectIdentifier, final Grantee grantee)
+    {
+        logTracking(sessionToken, "delete_role", "ROLE(%s) PROJECT(%s) GRANTEE(%s)", roleCode,
+                projectIdentifier, grantee);
+    }
+
     @Override
     public void deleteSpaceRole(final String sessionToken, final RoleCode roleCode,
             final SpaceIdentifier spaceIdentifier, final Grantee grantee)
@@ -795,7 +811,8 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
     {
         logAccess(sessionToken, "list_materials", "TYPE(%s) IDS(%s) withProperties(%s)",
                 criteria.tryGetMaterialType(), criteria.tryGetMaterialIds() == null ? "-"
-                        : abbreviate(criteria.tryGetMaterialIds()), withProperties);
+                        : abbreviate(criteria.tryGetMaterialIds()),
+                withProperties);
         return null;
     }
 
@@ -1029,7 +1046,7 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
     {
         logTracking(sessionToken, "edit_sample",
                 "SAMPLE(%s) CHANGE_TO_PROJECT(%s) CHANGE_TO_EXPERIMENT(%s) ATTACHMENTS(%s)",
-                updates.getSampleIdOrNull(), updates.getProjectIdentifier(), 
+                updates.getSampleIdOrNull(), updates.getProjectIdentifier(),
                 updates.getExperimentIdentifierOrNull(), updates.getAttachments().size());
         return null;
     }
@@ -1166,7 +1183,8 @@ final class CommonServerLogger extends AbstractServerLogger implements ICommonSe
     {
         logAccess(sessionToken, "get_template_columns",
                 "ENTITY_KIND(%s) ENTITY_TYPE(%s) AUTO_GENERATE(%s) WITH_EXP(%s) "
-                        + "WITH_SPACE(%s) OPERATION(%s)", entityKind, type, autoGenerate,
+                        + "WITH_SPACE(%s) OPERATION(%s)",
+                entityKind, type, autoGenerate,
                 withExperiments, withSpace, operationKind.getDescription());
         return null;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/RoleAssignmentTable.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/RoleAssignmentTable.java
index 9cd8d2e1e4586322bc4d3ab5e0738140b73b73ec..471adf3cda84894f729834e9c790dff95a2d474e 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/RoleAssignmentTable.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/RoleAssignmentTable.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.server.business.bo;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.springframework.dao.DataIntegrityViolationException;
@@ -32,9 +33,11 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AuthorizationGroupPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewRoleAssignment;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.RoleAssignmentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.managed_property.IManagedPropertyEvaluatorFactory;
 
@@ -107,6 +110,13 @@ public final class RoleAssignmentTable extends AbstractBusinessObject implements
         }
         final RoleAssignmentPE roleAssignment = new RoleAssignmentPE();
 
+        if (newRoleAssignment.getSpaceIdentifier() != null && newRoleAssignment.getProjectIdentifier() != null)
+        {
+            throw UserFailureException.fromTemplate(
+                    "Role assignment can have either space or project specified but not both. Space was '%s'. Project was '%s'.",
+                    newRoleAssignment.getSpaceIdentifier(), newRoleAssignment.getProjectIdentifier());
+        }
+
         final SpaceIdentifier groupIdentifier = newRoleAssignment.getSpaceIdentifier();
         if (groupIdentifier != null)
         {
@@ -121,6 +131,19 @@ public final class RoleAssignmentTable extends AbstractBusinessObject implements
             roleAssignment.setSpace(group);
         }
 
+        final ProjectIdentifier projectIdentifier = newRoleAssignment.getProjectIdentifier();
+        if (projectIdentifier != null)
+        {
+            final List<ProjectPE> projects = getProjectDAO().tryFindProjects(Arrays.asList(projectIdentifier));
+
+            if (projects == null || projects.isEmpty())
+            {
+                throw UserFailureException.fromTemplate("Specified project '%s' could not be found",
+                        projectIdentifier);
+            }
+            roleAssignment.setProject(projects.get(0));
+        }
+
         roleAssignment.setRegistrator(findPerson());
         roleAssignment.setRole(newRoleAssignment.getRole());
         if (Grantee.GranteeType.PERSON.equals(newRoleAssignment.getGrantee().getType()))
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IRoleAssignmentDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IRoleAssignmentDAO.java
index 39c8ca6f80c953fcef92675deb6121b43fcc4556..323c01c3a92dfd75c7abcf9062cb699b927d6417 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IRoleAssignmentDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IRoleAssignmentDAO.java
@@ -26,6 +26,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleC
 import ch.systemsx.cisd.openbis.generic.shared.dto.AuthorizationGroupPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.RoleAssignmentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 
 /**
  * <i>Data Access Object</i> for {@link RoleAssignmentPE}.
@@ -59,6 +60,11 @@ public interface IRoleAssignmentDAO extends IGenericDAO<RoleAssignmentPE>
      */
     public List<RoleAssignmentPE> listRoleAssignmentsByPerson(final PersonPE person);
 
+    /**
+     * Returns a {@link RoleAssignmentPE} described by given role, project identifier and grantee.
+     */
+    public RoleAssignmentPE tryFindProjectRoleAssignment(RoleCode role, ProjectIdentifier projectIdentifier, Grantee grantee);
+
     /**
      * Returns a {@link RoleAssignmentPE} described by given role, space code and grantee.
      */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/RoleAssignmentDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/RoleAssignmentDAO.java
index 9da5ecf1b03018db2cbd852e0fa613cd292180aa..c030d140afb2434c68bacfca4b58356e2429e51b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/RoleAssignmentDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/RoleAssignmentDAO.java
@@ -37,6 +37,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleC
 import ch.systemsx.cisd.openbis.generic.shared.dto.AuthorizationGroupPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.RoleAssignmentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 
 /**
  * <i>Data Access Object</i> implementation for {@link RoleAssignmentPE}.
@@ -153,6 +154,28 @@ public final class RoleAssignmentDAO extends AbstractGenericEntityDAO<RoleAssign
         }
     }
 
+    @Override
+    public final RoleAssignmentPE tryFindProjectRoleAssignment(final RoleCode role,
+            final ProjectIdentifier projectIdentifier, final Grantee grantee)
+    {
+        assert role != null : "Unspecified role.";
+        assert grantee != null : "Unspecified grantee.";
+
+        final List<RoleAssignmentPE> roles =
+                cast(getHibernateTemplate().find(
+                        String.format("from %s r where r."
+                                + getGranteeHqlParameter(grantee.getType())
+                                + " = ? and project.code = ? and project.space.code = ? and r.role = ?", TABLE_NAME),
+                        toArray(grantee.getCode(), projectIdentifier.getProjectCode(), projectIdentifier.getSpaceCode(), role)));
+        final RoleAssignmentPE roleAssignment =
+                tryFindEntity(roles, "role_assignments", role, projectIdentifier, grantee);
+        if (operationLog.isInfoEnabled())
+        {
+            operationLog.info(String.format("FIND: project role assignment '%s'.", roleAssignment));
+        }
+        return roleAssignment;
+    }
+    
     @Override
     public final RoleAssignmentPE tryFindSpaceRoleAssignment(final RoleCode role,
             final String space, final Grantee grantee)
@@ -160,6 +183,8 @@ public final class RoleAssignmentDAO extends AbstractGenericEntityDAO<RoleAssign
         assert role != null : "Unspecified role.";
         assert grantee != null : "Unspecified grantee.";
 
+        
+        
         final List<RoleAssignmentPE> roles =
                 cast(getHibernateTemplate().find(
                         String.format("from %s r where r."
@@ -187,7 +212,7 @@ public final class RoleAssignmentDAO extends AbstractGenericEntityDAO<RoleAssign
                 cast(getHibernateTemplate().find(
                         String.format("from %s r where r."
                                 + getGranteeHqlParameter(grantee.getType()) + " = ? "
-                                + "and r.role = ?", TABLE_NAME),
+                                + "and r.role = ? and space is null and project is null", TABLE_NAME),
                         toArray(grantee.getCode(), role)));
         final RoleAssignmentPE roleAssignment =
                 tryFindEntity(roles, "role_assignments", role, grantee);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java
index 73281a2bfc9d1c8bf7f79c8a3c5460efca8b0561..730a8a3dabc4588e83f5042e921793b0509caec5 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java
@@ -365,7 +365,14 @@ public abstract class AbstractServerLogger implements IServer
     }
 
     @Override
-    public boolean isProjectAuthorizationEnabled(String sessionToken)
+    public boolean isProjectLevelAuthorizationEnabled(String sessionToken)
+    {
+        // Do not log that
+        return false;
+    }
+
+    @Override
+    public boolean isProjectLevelAuthorizationUser(String sessionToken)
     {
         // Do not log that
         return false;
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
index ed4a20cf23d5d2dc71cf1582d472680ae1530112..d584d3e2f939e4c36b21d7fd905cd5225f9d0a73 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/ICommonServer.java
@@ -237,6 +237,14 @@ public interface ICommonServer extends IServer
     @Transactional(readOnly = true)
     public List<RoleAssignment> listRoleAssignments(String sessionToken);
 
+    /**
+     * Registers a new project role.
+     */
+    @Transactional
+    @DatabaseCreateOrDeleteModification(value = ObjectKind.ROLE_ASSIGNMENT)
+    public void registerProjectRole(String sessionToken, RoleCode roleCode,
+            ProjectIdentifier identifier, Grantee grantee);
+
     /**
      * Registers a new space role.
      */
@@ -252,6 +260,14 @@ public interface ICommonServer extends IServer
     @DatabaseCreateOrDeleteModification(value = ObjectKind.ROLE_ASSIGNMENT)
     public void registerInstanceRole(String sessionToken, RoleCode roleCode, Grantee grantee);
 
+    /**
+     * Deletes role described by given role code, project identifier and grantee.
+     */
+    @Transactional
+    @DatabaseCreateOrDeleteModification(value = ObjectKind.ROLE_ASSIGNMENT)
+    public void deleteProjectRole(String sessionToken, RoleCode roleCode, ProjectIdentifier identifier,
+            Grantee grantee);
+
     /**
      * Deletes role described by given role code, space identifier and grantee.
      */
@@ -827,8 +843,7 @@ public interface ICommonServer extends IServer
      * {@link #deleteDataSetsForced(String, List, String, DeletionType, boolean)}).
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.DATA_SET, ObjectKind.DELETION })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.DATA_SET, ObjectKind.DELETION })
     public void deleteDataSets(String sessionToken, List<String> dataSetCodes, String reason,
             DeletionType type, boolean isTrashEnabled);
 
@@ -837,8 +852,7 @@ public interface ICommonServer extends IServer
      * {@link #deleteDataSets(String, List, String, DeletionType, boolean)}).
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.DATA_SET, ObjectKind.DELETION })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.DATA_SET, ObjectKind.DELETION })
     public void deleteDataSetsForced(String sessionToken, List<String> dataSetCodes, String reason,
             DeletionType type, boolean isTrashEnabled);
 
@@ -846,8 +860,7 @@ public interface ICommonServer extends IServer
      * Deletes/Trashes specified samples.
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.SAMPLE, ObjectKind.DELETION })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.SAMPLE, ObjectKind.DELETION })
     public void deleteSamples(String sessionToken, List<TechId> sampleIds, String reason,
             DeletionType type);
 
@@ -855,8 +868,7 @@ public interface ICommonServer extends IServer
      * Deletes/Trashes specified experiments.
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.EXPERIMENT, ObjectKind.DELETION })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.EXPERIMENT, ObjectKind.DELETION })
     public void deleteExperiments(String sessionToken, List<TechId> experimentIds, String reason,
             DeletionType deletionType);
 
@@ -971,8 +983,7 @@ public interface ICommonServer extends IServer
      * Saves changed experiment.
      */
     @Transactional
-    @DatabaseUpdateModification(value =
-    { ObjectKind.EXPERIMENT, ObjectKind.SAMPLE })
+    @DatabaseUpdateModification(value = { ObjectKind.EXPERIMENT, ObjectKind.SAMPLE })
     public ExperimentUpdateResult updateExperiment(String sessionToken, ExperimentUpdatesDTO updates);
 
     /**
@@ -1010,40 +1021,35 @@ public interface ICommonServer extends IServer
      * Deletes specified data set types.
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.DATASET_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.DATASET_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
     public void deleteDataSetTypes(String sessionToken, List<String> entityTypesCodes);
 
     /**
      * Deletes specified sample types.
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.SAMPLE_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.SAMPLE_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
     public void deleteSampleTypes(String sessionToken, List<String> entityTypesCodes);
 
     /**
      * Deletes specified experiment types.
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.EXPERIMENT_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.EXPERIMENT_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
     public void deleteExperimentTypes(String sessionToken, List<String> entityTypesCodes);
 
     /**
      * Deletes specified file format types.
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.FILE_FORMAT_TYPE })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.FILE_FORMAT_TYPE })
     public void deleteFileFormatTypes(String sessionToken, List<String> codes);
 
     /**
      * Deletes specified material types.
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.MATERIAL_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.MATERIAL_TYPE, ObjectKind.PROPERTY_TYPE_ASSIGNMENT })
     public void deleteMaterialTypes(String sessionToken, List<String> entityTypesCodes);
 
     /**
@@ -1429,8 +1435,7 @@ public interface ICommonServer extends IServer
      */
     @Transactional
     @DatabaseCreateOrDeleteModification(value = ObjectKind.DELETION)
-    @DatabaseUpdateModification(value =
-    { ObjectKind.EXPERIMENT, ObjectKind.SAMPLE, ObjectKind.DATA_SET })
+    @DatabaseUpdateModification(value = { ObjectKind.EXPERIMENT, ObjectKind.SAMPLE, ObjectKind.DATA_SET })
     public void revertDeletions(final String sessionToken, final List<TechId> deletionIds);
 
     /**
@@ -1438,8 +1443,7 @@ public interface ICommonServer extends IServer
      * in their type (compare with {@link #deletePermanentlyForced(String, List)})
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.DELETION, ObjectKind.EXPERIMENT, ObjectKind.SAMPLE, ObjectKind.DATA_SET })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.DELETION, ObjectKind.EXPERIMENT, ObjectKind.SAMPLE, ObjectKind.DATA_SET })
     public void deletePermanently(final String sessionToken, final List<TechId> deletionIds);
 
     /**
@@ -1447,8 +1451,7 @@ public interface ICommonServer extends IServer
      * type (compare with {@link #deletePermanently(String, List)}).
      */
     @Transactional
-    @DatabaseCreateOrDeleteModification(value =
-    { ObjectKind.DELETION, ObjectKind.EXPERIMENT, ObjectKind.SAMPLE, ObjectKind.DATA_SET })
+    @DatabaseCreateOrDeleteModification(value = { ObjectKind.DELETION, ObjectKind.EXPERIMENT, ObjectKind.SAMPLE, ObjectKind.DATA_SET })
     public void deletePermanentlyForced(final String sessionToken, final List<TechId> deletionIds);
 
     /**
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java
index b84d640776cc83479969709b1ed529fd71744389..2e7a67d090186d86702e9a173676134f4b56f7a6 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java
@@ -64,10 +64,16 @@ public interface IServer extends ISessionProvider
     public boolean isProjectSamplesEnabled(final String sessionToken);
 
     /**
-     * @return 'true' if project authorization is enabled.
+     * @return 'true' if project level authorization is enabled.
      */
     @Transactional(readOnly = true)
-    public boolean isProjectAuthorizationEnabled(final String sessionToken);
+    public boolean isProjectLevelAuthorizationEnabled(final String sessionToken);
+    
+    /**
+     * @return 'true' if user represented by the session token is configured to use the project level authorization.
+     */
+    @Transactional(readOnly = true)
+    public boolean isProjectLevelAuthorizationUser(final String sessionToken);
 
     /**
      * Tries to authenticate the specified user with given password.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/NewRoleAssignment.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/NewRoleAssignment.java
index fc694d68c6d411fd99e07ad67da053fe8d22fdbc..4c6b843dc1896619896669660547bba131fe67f6 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/NewRoleAssignment.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/NewRoleAssignment.java
@@ -23,6 +23,7 @@ import ch.systemsx.cisd.common.reflection.AbstractHashable;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Grantee;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy.RoleCode;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 
 /**
@@ -41,6 +42,8 @@ public final class NewRoleAssignment extends AbstractHashable implements Seriali
 
     private SpaceIdentifier spaceIdentifier;
 
+    private ProjectIdentifier projectIdentifier;
+
     private Grantee grantee;
 
     private RoleCode role;
@@ -71,6 +74,17 @@ public final class NewRoleAssignment extends AbstractHashable implements Seriali
         this.spaceIdentifier = spaceIdentifier;
     }
 
+    public final ProjectIdentifier getProjectIdentifier()
+    {
+        return projectIdentifier;
+    }
+
+    @BeanProperty(label = "project")
+    public final void setProjectIdentifier(final ProjectIdentifier projectIdentifier)
+    {
+        this.projectIdentifier = projectIdentifier;
+    }
+
     public final Grantee getGrantee()
     {
         return grantee;
@@ -95,6 +109,10 @@ public final class NewRoleAssignment extends AbstractHashable implements Seriali
         {
             builder.append(getSpaceIdentifier());
         }
+        if (getProjectIdentifier() != null)
+        {
+            builder.append(getProjectIdentifier());
+        }
         return builder.toString();
     }
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProviderTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProviderTest.java
index 1d57d5fecf93a270599c93842c651793605c1635..425ec5640d99eb4e61d9c6937e2dc0d90d9ebfbf 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProviderTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/server/resultset/RoleAssignmentProviderTest.java
@@ -22,9 +22,12 @@ import java.util.List;
 import org.jmock.Expectations;
 import org.testng.annotations.Test;
 
+import ch.systemsx.cisd.openbis.generic.server.authorization.TestAuthorizationConfig;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.IAuthorizationConfig;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AuthorizationGroup;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseInstance;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleAssignment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
@@ -35,14 +38,27 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TypedTableModel;
  */
 public class RoleAssignmentProviderTest extends AbstractProviderTest
 {
+
+    @Test
+    public void testWithProjectAuthorizationDisabled()
+    {
+        test(new TestAuthorizationConfig(false, false));
+    }
+
     @Test
-    public void test()
+    public void testWithProjectAuthorizationEnabled()
+    {
+        test(new TestAuthorizationConfig(true, true));
+    }
+
+    private void test(IAuthorizationConfig authorizationConfig)
     {
         final RoleAssignment r1 = new RoleAssignment();
         r1.setCode("r1");
         DatabaseInstance instance = new DatabaseInstance();
         instance.setCode("db");
         r1.setInstance(instance);
+
         final RoleAssignment r2 = new RoleAssignment();
         r2.setCode("r2");
         AuthorizationGroup group = new AuthorizationGroup();
@@ -54,25 +70,51 @@ public class RoleAssignmentProviderTest extends AbstractProviderTest
         Space space = new Space();
         space.setCode("s1");
         r2.setSpace(space);
+
+        final RoleAssignment r3 = new RoleAssignment();
+        r3.setCode("r3");
+        r3.setPerson(person);
+        Project project = new Project();
+        project.setIdentifier("/SPACE/PROJECT");
+        r3.setProject(project);
+
         context.checking(new Expectations()
             {
                 {
                     one(server).listRoleAssignments(SESSION_TOKEN);
-                    will(returnValue(Arrays.asList(r1, r2)));
+                    will(returnValue(Arrays.asList(r1, r2, r3)));
                 }
             });
 
-        RoleAssignmentProvider provider = new RoleAssignmentProvider(server, SESSION_TOKEN);
+        RoleAssignmentProvider provider = new RoleAssignmentProvider(server, authorizationConfig, SESSION_TOKEN);
         TypedTableModel<RoleAssignment> model = provider.createTableModel();
 
-        assertEquals("[PERSON, AUTHORIZATION_GROUP, SPACE, ROLE]",
-                getHeaderIDs(model).toString());
-        List<TableModelRowWithObject<RoleAssignment>> rows = model.getRows();
-        assertSame(r1, rows.get(0).getObjectOrNull());
-        assertEquals("[, , , r1]", rows.get(0).getValues().toString());
-        assertSame(r2, rows.get(1).getObjectOrNull());
-        assertEquals("[user, UG, s1, r2]", rows.get(1).getValues().toString());
-        assertEquals(2, rows.size());
+        if (authorizationConfig.isProjectLevelEnabled())
+        {
+            assertEquals("[PERSON, AUTHORIZATION_GROUP, SPACE, PROJECT, ROLE]", getHeaderIDs(model).toString());
+
+            List<TableModelRowWithObject<RoleAssignment>> rows = model.getRows();
+            assertEquals(3, rows.size());
+
+            assertSame(r1, rows.get(0).getObjectOrNull());
+            assertEquals("[, , , , r1]", rows.get(0).getValues().toString());
+            assertSame(r2, rows.get(1).getObjectOrNull());
+            assertEquals("[user, UG, s1, , r2]", rows.get(1).getValues().toString());
+            assertSame(r3, rows.get(2).getObjectOrNull());
+            assertEquals("[user, , , /SPACE/PROJECT, r3]", rows.get(2).getValues().toString());
+        } else
+        {
+            assertEquals("[PERSON, AUTHORIZATION_GROUP, SPACE, ROLE]", getHeaderIDs(model).toString());
+
+            List<TableModelRowWithObject<RoleAssignment>> rows = model.getRows();
+            assertEquals(2, rows.size());
+
+            assertSame(r1, rows.get(0).getObjectOrNull());
+            assertEquals("[, , , r1]", rows.get(0).getValues().toString());
+            assertSame(r2, rows.get(1).getObjectOrNull());
+            assertEquals("[user, UG, s1, r2]", rows.get(1).getValues().toString());
+        }
+
         context.assertIsSatisfied();
     }
 }