From 05dbcfdd1de545de0c982cf148a488838f7f6b0c Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Thu, 29 Mar 2018 16:30:47 +0200
Subject: [PATCH] OO-3299: more restriction in user management for user
 managers of organizations

---
 .../user/NewUsersNotificationsController.java | 28 ++++--
 .../user/UsermanagerUserSearchController.java |  7 +-
 .../user/_content/newusersNotifications.html  |  4 +-
 .../IdentityPowerSearchQueries.java           | 19 ++++
 .../IdentityPowerSearchQueriesImpl.java       | 21 +++-
 .../_spring/notificationsContext.xml          | 10 --
 .../manager/NotificationsManagerImpl.java     | 12 +--
 .../restapi/NotificationsWebService.java      |  6 +-
 .../NewUsersNotificationHandler.java          | 24 ++++-
 .../UsersSubscriptionManager.java             | 23 ++---
 .../UsersSubscriptionManagerImpl.java         | 95 +++++++++----------
 .../user/ui/admin/IdentityListDataSource.java | 19 ++++
 .../ui/admin/UserAdminMainController.java     | 52 +++++-----
 .../user/ui/admin/UserSearchDataSource.java   | 19 ++++
 .../ui/admin/UserSearchTableController.java   | 19 ++++
 .../user/ui/admin/UserSearchTableModel.java   | 19 ++++
 .../OrganisationUserManagementController.java | 47 +++++++--
 ...OrganisationsStructureAdminController.java | 10 +-
 .../ui/organisation/RoleListController.java   | 80 ++++++++++++++++
 .../_content/organisation_user_mgmt.html      |  4 +-
 .../_i18n/LocalStrings_de.properties          | 14 ++-
 .../component/RoleFlexiCellRenderer.java      | 60 ++++++++++++
 .../user/ui/organisation/event/RoleEvent.java | 46 +++++++++
 .../org/olat/restapi/NotificationsTest.java   | 27 ++++--
 24 files changed, 512 insertions(+), 153 deletions(-)
 create mode 100644 src/main/java/org/olat/user/ui/organisation/RoleListController.java
 create mode 100644 src/main/java/org/olat/user/ui/organisation/component/RoleFlexiCellRenderer.java
 create mode 100644 src/main/java/org/olat/user/ui/organisation/event/RoleEvent.java

diff --git a/src/main/java/org/olat/admin/user/NewUsersNotificationsController.java b/src/main/java/org/olat/admin/user/NewUsersNotificationsController.java
index 0fc4335fb59..4bd3ee5fc39 100644
--- a/src/main/java/org/olat/admin/user/NewUsersNotificationsController.java
+++ b/src/main/java/org/olat/admin/user/NewUsersNotificationsController.java
@@ -30,6 +30,7 @@ import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionCon
 import org.olat.core.commons.services.notifications.ui.DateChooserController;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
@@ -37,6 +38,7 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.id.Identity;
 import org.olat.user.notification.UsersSubscriptionManager;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
@@ -51,20 +53,25 @@ import org.olat.user.notification.UsersSubscriptionManager;
  */
 public class NewUsersNotificationsController extends BasicController {
 
-	private DateChooserController dateChooserController;
+	private final DateChooserController dateChooserController;
 	private UsermanagerUserSearchController searchController;
-	private ContextualSubscriptionController subscriptionController;
+	private final ContextualSubscriptionController subscriptionController;
 	
-	private VelocityContainer mainVC;
+	private final VelocityContainer mainVC;
+	private TooledStackedPanel stackedPanel;
 	
-	public NewUsersNotificationsController(UserRequest ureq, WindowControl wControl) {
+	@Autowired
+	private UsersSubscriptionManager usersSubscriptionManager;
+	
+	public NewUsersNotificationsController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackedPanel) {
 		super(ureq, wControl);
+		this.stackedPanel = stackedPanel;
 		
 		mainVC = createVelocityContainer("newusersNotifications");
 		
 		// subscribe/unsubscribe
-		SubscriptionContext subContext = UsersSubscriptionManager.getInstance().getNewUsersSubscriptionContext();
-		PublisherData publisherData = UsersSubscriptionManager.getInstance().getNewUsersPublisherData();
+		SubscriptionContext subContext = usersSubscriptionManager.getNewUsersSubscriptionContext();
+		PublisherData publisherData = usersSubscriptionManager.getNewUsersPublisherData();
 		
 		subscriptionController = new ContextualSubscriptionController(ureq, getWindowControl(), subContext, publisherData);
 		listenTo(subscriptionController);
@@ -84,15 +91,16 @@ public class NewUsersNotificationsController extends BasicController {
 		if(searchController != null) {
 			removeAsListenerAndDispose(searchController);
 		}
-		List<Identity> identities = UsersSubscriptionManager.getInstance().getNewIdentityCreated(compareDate);
-		searchController = new UsermanagerUserSearchController(ureq, getWindowControl(), identities, Identity.STATUS_VISIBLE_LIMIT, true, false);
+		
+		List<Identity> identities = usersSubscriptionManager.getNewIdentityCreated(compareDate, getIdentity(), ureq.getUserSession().getRoles());
+		searchController = new UsermanagerUserSearchController(ureq, getWindowControl(), stackedPanel, identities, Identity.STATUS_VISIBLE_LIMIT, true, false);
 		listenTo(searchController);
 		mainVC.put("notificationsList", searchController.getInitialComponent());
 
 		if(identities.isEmpty()) {
-			mainVC.contextPut("hasNews", "false");
+			mainVC.contextPut("hasNews", Boolean.FALSE);
 		} else {
-			mainVC.contextPut("hasNews", "true");
+			mainVC.contextPut("hasNews", Boolean.TRUE);
 		}
 	}
 
diff --git a/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java b/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java
index b8465b74567..0bde3184a30 100644
--- a/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java
+++ b/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java
@@ -45,7 +45,6 @@ import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.components.link.LinkFactory;
-import org.olat.core.gui.components.panel.StackedPanel;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.components.table.Table;
 import org.olat.core.gui.components.table.TableEvent;
@@ -97,7 +96,6 @@ public class UsermanagerUserSearchController extends BasicController implements
 	private static final String CMD_BULKEDIT = "bulkEditUsers";
 
 	private VelocityContainer userListVC, userSearchVC, mailVC;
-	private StackedPanel panel2;
 
 	private TooledStackedPanel stackedPanel;
 
@@ -207,10 +205,11 @@ public class UsermanagerUserSearchController extends BasicController implements
 	 * @param status
 	 * @param showEmailButton
 	 */
-	public UsermanagerUserSearchController(UserRequest ureq, WindowControl wControl, List<Identity> identitiesList,
-			Integer status, boolean showEmailButton, boolean showTitle) {
+	public UsermanagerUserSearchController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackedPanel,
+			List<Identity> identitiesList, Integer status, boolean showEmailButton, boolean showTitle) {
 		super(ureq, wControl);
 		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
+		this.stackedPanel = stackedPanel;
 		
 		isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles());
 
diff --git a/src/main/java/org/olat/admin/user/_content/newusersNotifications.html b/src/main/java/org/olat/admin/user/_content/newusersNotifications.html
index 08302411166..fd1345b61b4 100644
--- a/src/main/java/org/olat/admin/user/_content/newusersNotifications.html
+++ b/src/main/java/org/olat/admin/user/_content/newusersNotifications.html
@@ -3,8 +3,8 @@ $r.render("newUsersSubscription")
 <div class="o_notifications_news_datechooser">
 $r.render("dateChooser")
 </div>
-#if ($hasNews == "false")
-	<p>$r.translate("notification.noNews")</p>
+#if ($r.isFalse($hasNews))
+	<div class="o_info">$r.translate("notification.noNews")</div>
 #else
 	$r.render("notificationsList")
 #end
\ No newline at end of file
diff --git a/src/main/java/org/olat/basesecurity/IdentityPowerSearchQueries.java b/src/main/java/org/olat/basesecurity/IdentityPowerSearchQueries.java
index 6ad469efa22..a4d9889a047 100644
--- a/src/main/java/org/olat/basesecurity/IdentityPowerSearchQueries.java
+++ b/src/main/java/org/olat/basesecurity/IdentityPowerSearchQueries.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.basesecurity;
 
 import java.util.List;
diff --git a/src/main/java/org/olat/basesecurity/manager/IdentityPowerSearchQueriesImpl.java b/src/main/java/org/olat/basesecurity/manager/IdentityPowerSearchQueriesImpl.java
index 4ef611c4d07..817ebaa360a 100644
--- a/src/main/java/org/olat/basesecurity/manager/IdentityPowerSearchQueriesImpl.java
+++ b/src/main/java/org/olat/basesecurity/manager/IdentityPowerSearchQueriesImpl.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.basesecurity.manager;
 
 import java.util.ArrayList;
@@ -437,7 +456,7 @@ public class IdentityPowerSearchQueriesImpl implements IdentityPowerSearchQuerie
 		if(params.hasOrganisationParents()) {
 			for(int i=0; i<params.getOrganisationParents().size(); i++) {
 				String org = params.getOrganisationParents().get(i).getMaterializedPathKeys();
-				dbq.setParameter("organisationParent_" + i, org);
+				dbq.setParameter("organisationParent_" + i, org + "%");
 			}
 		}
 		
diff --git a/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml b/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml
index 0faef9070fa..7fb68ab1e98 100644
--- a/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml
+++ b/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml
@@ -12,7 +12,6 @@
 	<bean id="org.olat.course.nodes.ta.ReturnboxFileUploadNotificationHandler" class="org.olat.course.nodes.ta.ReturnboxFileUploadNotificationHandler"/>
 	<bean id="org.olat.course.nodes.ta.SolutionFileUploadNotificationHandler" class="org.olat.course.nodes.ta.SolutionFileUploadNotificationHandler"/>
 	<bean id="org.olat.modules.wiki.WikiPageChangeOrCreateNotificationHandler" class="org.olat.modules.wiki.WikiPageChangeOrCreateNotificationHandler" />
-	<bean id="org.olat.user.notification.NewUsersNotificationHandler" class="org.olat.user.notification.NewUsersNotificationHandler" />
 	
 	<bean id="notificationsManager" class="org.olat.core.commons.services.notifications.manager.NotificationsManagerImpl" >
 		<property name="dbInstance" ref="database"/>
@@ -43,15 +42,6 @@
 		</property>
 	</bean>
 	
-	<bean
-		id="org.olat.user.notification.UsersSubscriptionManager" class="org.olat.user.notification.UsersSubscriptionManagerImpl"
-		 depends-on="coordinatorManager">
-		<property name="autoSubscribe" value="false" />
-		<property name="coordinator">
-			<ref bean="coordinatorManager" />
-		</property>
-	</bean>
-	
 	<!-- Notification config:
 		If you want to disable notificaition please comment out the corresponding entry in scheduler
 		spring file in serviceconfig/org/olat/core/commons/scheduler/_spring/olatextconfig.xml
diff --git a/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java b/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java
index dd6d855ee45..dc195955ad4 100644
--- a/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java
+++ b/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java
@@ -103,7 +103,7 @@ public class NotificationsManagerImpl extends NotificationsManager implements Us
 	private static final int PUB_STATE_NOT_OK = 1;
 	private static final int BATCH_SIZE = 500;
 	private static final String LATEST_EMAIL_USER_PROP = "noti_latest_email";
-	private final SubscriptionInfo NOSUBSINFO = new NoSubscriptionInfo();
+	private static final SubscriptionInfo NOSUBSINFO = new NoSubscriptionInfo();
 
 	private final OLATResourceable oresMyself = OresHelper.lookupType(NotificationsManagerImpl.class);
 
@@ -810,10 +810,6 @@ public class NotificationsManagerImpl extends NotificationsManager implements Us
 				.getResultList();
 	}
 	
-	/**
-	 * 
-	 * @see org.olat.core.commons.services.notifications.NotificationsManager#getSubscriberIdentities(org.olat.core.commons.services.notifications.Publisher)
-	 */
 	@Override
 	public List<Identity> getSubscriberIdentities(Publisher publisher) {
 		return dbInstance.getCurrentEntityManager()
@@ -825,12 +821,13 @@ public class NotificationsManagerImpl extends NotificationsManager implements Us
 	/**
 	 * @return the handler for the type
 	 */
+	@Override
 	public NotificationsHandler getNotificationsHandler(Publisher publisher) {
 		String type = publisher.getType();
 		if (notificationHandlers == null) {
 			synchronized(lockObject) {
 				if (notificationHandlers == null) { // check again in synchronized-block, only one may create list
-					notificationHandlers = new HashMap<String,NotificationsHandler>();
+					notificationHandlers = new HashMap<>();
 					Map<String, NotificationsHandler> notificationsHandlerMap = CoreSpringFactory.getBeansOfType(NotificationsHandler.class);
 					Collection<NotificationsHandler> notificationsHandlerValues = notificationsHandlerMap.values();
 					for (NotificationsHandler notificationsHandler : notificationsHandlerValues) {
@@ -849,7 +846,8 @@ public class NotificationsManagerImpl extends NotificationsManager implements Us
 	private void deleteSubscriber(Subscriber subscriber) {
 		dbInstance.deleteObject(subscriber);
 	}
-	
+
+	@Override
 	public boolean deleteSubscriber(Long subscriberKey) {
 		String sb = "delete from notisub sub where sub.key=:subscriberKey";
 		int rows = dbInstance.getCurrentEntityManager()
diff --git a/src/main/java/org/olat/core/commons/services/notifications/restapi/NotificationsWebService.java b/src/main/java/org/olat/core/commons/services/notifications/restapi/NotificationsWebService.java
index 7642d2f1f9d..79bd9afbb50 100644
--- a/src/main/java/org/olat/core/commons/services/notifications/restapi/NotificationsWebService.java
+++ b/src/main/java/org/olat/core/commons/services/notifications/restapi/NotificationsWebService.java
@@ -212,13 +212,13 @@ public class NotificationsWebService {
 			compareDate = man.getCompareDateFromInterval(man.getUserIntervalOrDefault(identity));
 		}
 		
-		List<String> types = new ArrayList<String>(1);
+		List<String> types = new ArrayList<>(1);
 		if(StringHelper.containsNonWhitespace(type)) {
 			types.add(type);
 		}
 		
 		Map<Subscriber,SubscriptionInfo> subsInfoMap = NotificationHelper.getSubscriptionMap(identity, locale, true, compareDate, types);
-		List<SubscriptionInfoVO> voes = new ArrayList<SubscriptionInfoVO>();
+		List<SubscriptionInfoVO> voes = new ArrayList<>();
 		for(Map.Entry<Subscriber, SubscriptionInfo> entry: subsInfoMap.entrySet()) {
 			SubscriptionInfo info = entry.getValue();
 			if(info.hasNews()) {
@@ -234,7 +234,7 @@ public class NotificationsWebService {
 	private SubscriptionInfoVO createSubscriptionInfoVO(Publisher publisher, SubscriptionInfo info) {
 		SubscriptionInfoVO infoVO  = new SubscriptionInfoVO(info);
 		if(info.getSubscriptionListItems() != null && !info.getSubscriptionListItems().isEmpty()) {
-			List<SubscriptionListItemVO> itemVOes = new ArrayList<SubscriptionListItemVO>(info.getSubscriptionListItems().size());
+			List<SubscriptionListItemVO> itemVOes = new ArrayList<>(info.getSubscriptionListItems().size());
 			
 			String publisherType = publisher.getType();
 			String resourceType = publisher.getResName();
diff --git a/src/main/java/org/olat/user/notification/NewUsersNotificationHandler.java b/src/main/java/org/olat/user/notification/NewUsersNotificationHandler.java
index 33f1e6456b0..8270f33d242 100644
--- a/src/main/java/org/olat/user/notification/NewUsersNotificationHandler.java
+++ b/src/main/java/org/olat/user/notification/NewUsersNotificationHandler.java
@@ -23,6 +23,7 @@ import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 
+import org.olat.basesecurity.BaseSecurity;
 import org.olat.core.commons.services.notifications.NotificationHelper;
 import org.olat.core.commons.services.notifications.NotificationsHandler;
 import org.olat.core.commons.services.notifications.NotificationsManager;
@@ -34,10 +35,13 @@ import org.olat.core.commons.services.notifications.model.TitleItem;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.gui.util.CSSHelper;
 import org.olat.core.id.Identity;
+import org.olat.core.id.Roles;
 import org.olat.core.id.context.BusinessControlFactory;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.Util;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
 
 /**
  * 
@@ -49,9 +53,17 @@ import org.olat.core.util.Util;
  * 
  * @author srosse, stephane.rosse@frentix.com
  */
+@Service
 public class NewUsersNotificationHandler implements NotificationsHandler {
 	private static final OLog log = Tracing.createLoggerFor(NewUsersNotificationHandler.class);
 
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private NotificationsManager notificationsManager;
+	@Autowired
+	private UsersSubscriptionManager usersSubscriptionManager;
+	
 	@Override
 	public SubscriptionInfo createSubscriptionInfo(Subscriber subscriber, Locale locale, Date compareDate) {
 		Publisher p = subscriber.getPublisher();
@@ -61,10 +73,12 @@ public class NewUsersNotificationHandler implements NotificationsHandler {
 		Translator translator = Util.createPackageTranslator(this.getClass(), locale);
 		// there could be news for me, investigate deeper
 		try {
-			if (NotificationsManager.getInstance().isPublisherValid(p) && compareDate.before(latestNews)) {
-				List<Identity> identities = UsersSubscriptionManager.getInstance().getNewIdentityCreated(compareDate);
+			if (notificationsManager.isPublisherValid(p) && compareDate.before(latestNews)) {
+				Identity identity = subscriber.getIdentity();
+				Roles roles = securityManager.getRoles(identity);
+				List<Identity> identities = usersSubscriptionManager.getNewIdentityCreated(compareDate, subscriber.getIdentity(), roles);
 				if (identities.isEmpty()) {
-					si = NotificationsManager.getInstance().getNoSubscriptionInfo();
+					si = notificationsManager.getNoSubscriptionInfo();
 				} else {
 					translator = Util.createPackageTranslator(this.getClass(), locale);
 					si = new SubscriptionInfo(subscriber.getKey(), p.getType(), new TitleItem(getItemTitle(identities, translator), CSSHelper.CSS_CLASS_GROUP), null);
@@ -79,11 +93,11 @@ public class NewUsersNotificationHandler implements NotificationsHandler {
 					}
 				}
 			} else {
-				si = NotificationsManager.getInstance().getNoSubscriptionInfo();
+				si = notificationsManager.getNoSubscriptionInfo();
 			}
 		} catch (Exception e) {
 			log.error("Error creating new identity's notifications for subscriber: " + subscriber.getKey(), e);
-			si = NotificationsManager.getInstance().getNoSubscriptionInfo();
+			si = notificationsManager.getNoSubscriptionInfo();
 		}
 		return si;
 	}
diff --git a/src/main/java/org/olat/user/notification/UsersSubscriptionManager.java b/src/main/java/org/olat/user/notification/UsersSubscriptionManager.java
index 4f1a0a1d5de..a7a154b1c4f 100644
--- a/src/main/java/org/olat/user/notification/UsersSubscriptionManager.java
+++ b/src/main/java/org/olat/user/notification/UsersSubscriptionManager.java
@@ -22,12 +22,11 @@ package org.olat.user.notification;
 import java.util.Date;
 import java.util.List;
 
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.services.notifications.PublisherData;
 import org.olat.core.commons.services.notifications.Subscriber;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
 import org.olat.core.id.Identity;
-import org.olat.core.manager.BasicManager;
+import org.olat.core.id.Roles;
 
 
 /**
@@ -41,23 +40,19 @@ import org.olat.core.manager.BasicManager;
  *
  * @author srosse
  */
-public abstract class UsersSubscriptionManager extends BasicManager {
+public interface UsersSubscriptionManager {
 	
-	public static final UsersSubscriptionManager getInstance() {
-		return (UsersSubscriptionManager) CoreSpringFactory.getBean(UsersSubscriptionManager.class.getCanonicalName());
-	}
+	public SubscriptionContext getNewUsersSubscriptionContext();
 	
-	public abstract SubscriptionContext getNewUsersSubscriptionContext();
+	public PublisherData getNewUsersPublisherData();
 	
-	public abstract PublisherData getNewUsersPublisherData();
+	public Subscriber getNewUsersSubscriber(Identity identity);
 	
-	public abstract Subscriber getNewUsersSubscriber(Identity identity);
+	public void subscribe(Identity identity);
 	
-	public abstract void subscribe(Identity identity);
+	public void unsubscribe(Identity identity);
 	
-	public abstract void unsubscribe(Identity identity);
+	public void markPublisherNews();
 	
-	public abstract void markPublisherNews();
-	
-	public abstract List<Identity> getNewIdentityCreated(Date From);
+	public List<Identity> getNewIdentityCreated(Date from, Identity actingIdentity, Roles roles);
 }
diff --git a/src/main/java/org/olat/user/notification/UsersSubscriptionManagerImpl.java b/src/main/java/org/olat/user/notification/UsersSubscriptionManagerImpl.java
index 28ce07b7f00..a9cab470fbe 100644
--- a/src/main/java/org/olat/user/notification/UsersSubscriptionManagerImpl.java
+++ b/src/main/java/org/olat/user/notification/UsersSubscriptionManagerImpl.java
@@ -21,13 +21,14 @@ package org.olat.user.notification;
 
 import java.util.Collections;
 import java.util.Date;
-import java.util.Iterator;
 import java.util.List;
 
 import org.olat.basesecurity.BaseSecurity;
-import org.olat.basesecurity.BaseSecurityManager;
+import org.olat.basesecurity.Organisation;
 import org.olat.basesecurity.OrganisationRoles;
+import org.olat.basesecurity.SearchIdentityParams;
 import org.olat.basesecurity.events.NewIdentityCreatedEvent;
+import org.olat.basesecurity.manager.OrganisationDAO;
 import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.Publisher;
 import org.olat.core.commons.services.notifications.PublisherData;
@@ -36,12 +37,15 @@ import org.olat.core.commons.services.notifications.SubscriptionContext;
 import org.olat.core.gui.control.Event;
 import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
+import org.olat.core.id.Roles;
 import org.olat.core.id.User;
 import org.olat.core.id.context.BusinessControlFactory;
 import org.olat.core.id.context.ContextEntry;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
 
 
 /**
@@ -54,47 +58,41 @@ import org.olat.core.util.resource.OresHelper;
  *
  * @author srosse
  */
-public class UsersSubscriptionManagerImpl extends UsersSubscriptionManager implements GenericEventListener {
+@Service
+public class UsersSubscriptionManagerImpl implements UsersSubscriptionManager, GenericEventListener {
 	public static final String NEW = "NewIdentityCreated";
-	public static final Long RES_ID = new Long(0);
+	public static final Long RES_ID = Long.valueOf(0);
 	public static final String RES_NAME = OresHelper.calculateTypeName(User.class);
 	public static final OLATResourceable IDENTITY_EVENT_CHANNEL = OresHelper.lookupType(Identity.class);
 	
-	private boolean autoSubscribe;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private OrganisationDAO organisationDao;
+	@Autowired
+	private NotificationsManager notificationsManager;
 	
-	public UsersSubscriptionManagerImpl() {
-		//
-	}
-	
-	public void setCoordinator(CoordinatorManager coordinatorManager) {
+	@Autowired
+	public UsersSubscriptionManagerImpl(CoordinatorManager coordinatorManager) {
 		coordinatorManager.getCoordinator().getEventBus().registerFor(this, null, IDENTITY_EVENT_CHANNEL);
 	}
 	
-	public void setAutoSubscribe(boolean autoSubscribe) {
-		this.autoSubscribe = autoSubscribe;
-	}
-	
 	/**
 	 * Receive the event after the creation of new identities
 	 */
+	@Override
 	public void event(Event event) {
 		if (event instanceof NewIdentityCreatedEvent) {
 			markPublisherNews();
-			if(autoSubscribe) {
-				NewIdentityCreatedEvent e = (NewIdentityCreatedEvent)event;
-				Identity identity = BaseSecurityManager.getInstance().loadIdentityByKey(e.getIdentityId());
-				subscribe(identity);
-			}
 		}
 	}
 
-	/**
-	 * 
-	 */
+	@Override
 	public SubscriptionContext getNewUsersSubscriptionContext() {
 		return new SubscriptionContext(RES_NAME, RES_ID, NEW);
 	}
-	
+
+	@Override
 	public PublisherData getNewUsersPublisherData() {
 		ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(new OLATResourceable() {
 			@Override
@@ -102,42 +100,45 @@ public class UsersSubscriptionManagerImpl extends UsersSubscriptionManager imple
 			@Override
 			public String getResourceableTypeName() { return "NewIdentityCreated"; }
 		});
-		PublisherData publisherData = new PublisherData(RES_NAME, NEW, ce.toString());
-		return publisherData;
+		return new PublisherData(RES_NAME, NEW, ce.toString());
 	}
-	
+
+	@Override
 	public Subscriber getNewUsersSubscriber(Identity identity) {
 		SubscriptionContext context = getNewUsersSubscriptionContext();
-		Publisher publisher = NotificationsManager.getInstance().getPublisher(context);
+		Publisher publisher = notificationsManager.getPublisher(context);
 		if(publisher == null) {
 			return null;
 		}
-		return NotificationsManager.getInstance().getSubscriber(identity, publisher);
+		return notificationsManager.getSubscriber(identity, publisher);
 	}
 	
 	/**
 	 * Subscribe to notifications of new identity created
 	 */
+	@Override
 	public void subscribe(Identity identity) {
 		PublisherData data = getNewUsersPublisherData();
 		SubscriptionContext context = getNewUsersSubscriptionContext();
-		NotificationsManager.getInstance().subscribe(identity, context, data);
+		notificationsManager.subscribe(identity, context, data);
 	}
 	
 	/**
 	 * Unsubscribe to notifications of new identity created
 	 */
+	@Override
 	public void unsubscribe(Identity identity) {
 		SubscriptionContext context = getNewUsersSubscriptionContext();
-		NotificationsManager.getInstance().unsubscribe(identity, context);	
+		notificationsManager.unsubscribe(identity, context);	
 	}
 	
 	/**
 	 * Call this method if there is new identities
 	 */
+	@Override
 	public void markPublisherNews() {
 		SubscriptionContext context = getNewUsersSubscriptionContext();
-		NotificationsManager.getInstance().markPublisherNews(context, null, true);
+		notificationsManager.markPublisherNews(context, null, true);
 	}
 
 	/**
@@ -145,25 +146,23 @@ public class UsersSubscriptionManagerImpl extends UsersSubscriptionManager imple
 	 * The guest are also removed from the list.
 	 */
 	@Override
-	public List<Identity> getNewIdentityCreated(Date from) {
-		if(from == null)
-			return Collections.emptyList();
-		
-		BaseSecurity manager = BaseSecurityManager.getInstance();
-		OrganisationRoles[] guestRoles = { OrganisationRoles.guest };
-		List<Identity> guests = manager.getIdentitiesByPowerSearch(null, null, true, guestRoles, null, from, null, null, null, Identity.STATUS_VISIBLE_LIMIT);
-		List<Identity> identities = manager.getIdentitiesByPowerSearch(null, null, true, null, null, from, null, null, null, Identity.STATUS_VISIBLE_LIMIT);
-		if(!identities.isEmpty() && !guests.isEmpty()) {
-			identities.removeAll(guests);
-		}
-		
-		for(Iterator<Identity> identityIt=identities.iterator(); identityIt.hasNext(); ) {
-			Identity identity = identityIt.next();
-			if(identity.getCreationDate().before(from)) {
-				identityIt.remove();
+	public List<Identity> getNewIdentityCreated(Date from, Identity actingIdentity, Roles roles) {
+		if(from == null || (!roles.isUserManager() && !roles.isOLATAdmin())) return Collections.emptyList();
+
+		List<Organisation> userManagerOrganisations = null;
+		if(!roles.isOLATAdmin()) {
+			userManagerOrganisations = organisationDao
+					.getOrganisations(actingIdentity, Collections.singletonList(OrganisationRoles.usermanager.name()));
+			if(userManagerOrganisations.isEmpty()) {
+				return Collections.emptyList();
 			}
 		}
 		
-		return identities;
+		SearchIdentityParams params = new SearchIdentityParams();
+		params.setExcludedRoles(new OrganisationRoles[]{ OrganisationRoles.guest });
+		params.setCreatedAfter(from);
+		params.setStatus(Identity.STATUS_VISIBLE_LIMIT);
+		params.setOrganisationParents(userManagerOrganisations);
+		return securityManager.getIdentitiesByPowerSearch(params, 0, -1);
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/admin/IdentityListDataSource.java b/src/main/java/org/olat/user/ui/admin/IdentityListDataSource.java
index fa697a9cf4a..a364be7951e 100644
--- a/src/main/java/org/olat/user/ui/admin/IdentityListDataSource.java
+++ b/src/main/java/org/olat/user/ui/admin/IdentityListDataSource.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.user.ui.admin;
 
 import java.util.ArrayList;
diff --git a/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java b/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java
index 49887592176..23049e2740c 100644
--- a/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java
+++ b/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java
@@ -303,7 +303,7 @@ public class UserAdminMainController extends MainLayoutBasicController implement
 			case "created.lastweek": return createUserSearchControllerAfterDate(ureq, bwControl, Calendar.DAY_OF_MONTH, -7);
 			case "created.lastmonth": return createUserSearchControllerAfterDate(ureq, bwControl, Calendar.MONTH, -1);
 			case "created.sixmonth": return createUserSearchControllerAfterDate(ureq, bwControl, Calendar.MONTH, -6);
-			case "created.newUsersNotification": return new NewUsersNotificationsController(ureq, bwControl);
+			case "created.newUsersNotification": return new NewUsersNotificationsController(ureq, bwControl, content);
 			// repository entry owners - authors
 			case "coauthors": return createUserSearchController(ureq, bwControl,
 					SearchIdentityParams.resources(GroupRoles.owner, null, null, new OrganisationRoles[] { OrganisationRoles.author }, Identity.STATUS_VISIBLE_LIMIT));
@@ -318,28 +318,19 @@ public class UserAdminMainController extends MainLayoutBasicController implement
 					SearchIdentityParams.resources(null, GroupRoles.coach, null, null, Identity.STATUS_VISIBLE_LIMIT));
 			case "noauthentication": return createUserSearchController(ureq, bwControl,
 					SearchIdentityParams.authenticationProviders(new String[]{ null }, Identity.STATUS_VISIBLE_LIMIT));
-			default: return getControllerFromMenuCommand(ureq, bwControl, uobject);		
+			case "userswithoutgroup":
+				List<Identity> usersWithoutGroup = securityManager.findIdentitiesWithoutBusinessGroup(Identity.STATUS_VISIBLE_LIMIT);
+				return new UsermanagerUserSearchController(ureq, bwControl, content, usersWithoutGroup, null, true, true);
+			case "userswithoutemail":
+				List<Identity> usersWithoutEmail = userManager.findVisibleIdentitiesWithoutEmail();
+				return new UsermanagerUserSearchController(ureq, bwControl, content, usersWithoutEmail, null, true, true);
+			case "usersemailduplicates":
+				List<Identity> usersEmailDuplicates = userManager.findVisibleIdentitiesWithEmailDuplicates();
+				return new UsermanagerUserSearchController(ureq, bwControl, content, usersEmailDuplicates, null, true, true);
+			default: return null;		
 		}
 	}
 
-	private Controller getControllerFromMenuCommand(UserRequest ureq, WindowControl bwControl, Object uobject) {
-		UsermanagerUserSearchController ctrl;
-		if (uobject.equals("userswithoutgroup")) {
-			List<Identity> usersWithoutGroup = securityManager.findIdentitiesWithoutBusinessGroup(Identity.STATUS_VISIBLE_LIMIT);
-			ctrl = new UsermanagerUserSearchController(ureq, bwControl, usersWithoutGroup, null, true, true);
-		} else if (uobject.equals("userswithoutemail")) {
-			List<Identity> usersWithoutEmail = userManager.findVisibleIdentitiesWithoutEmail();
-			ctrl = new UsermanagerUserSearchController(ureq, bwControl, usersWithoutEmail, null, true, true);
-		} else if (uobject.equals("usersemailduplicates")) {
-			List<Identity> usersEmailDuplicates = userManager.findVisibleIdentitiesWithEmailDuplicates();
-			ctrl = new UsermanagerUserSearchController(ureq, bwControl, usersEmailDuplicates, null, true, true);
-		} else {
-			ctrl = null;
-		}
-		return ctrl;
-	}
-	
-
 	private Controller getController(UserRequest ureq, Organisation organisation) {
 		SearchIdentityParams predefinedQuery = SearchIdentityParams.organisation(organisation, Identity.STATUS_VISIBLE_LIMIT);
 		return createUserSearchController(ureq, getWindowControl(), predefinedQuery);
@@ -468,7 +459,16 @@ public class UserAdminMainController extends MainLayoutBasicController implement
 	private void buildTreeOrganisationSubMenu(GenericTreeNode accessNode) {
 		List<Organisation> organisations;
 		if(userManagerOrganisations != null) {
-			organisations = new ArrayList<>(userManagerOrganisations);
+			organisations = new ArrayList<>();
+			List<Organisation> allOrganisations = organisationService.getOrganisations();
+			for(Organisation organisation:allOrganisations) {
+				String path = organisation.getMaterializedPathKeys();
+				for(Organisation userManagerOrganisation:userManagerOrganisations) {
+					if(path.startsWith(userManagerOrganisation.getMaterializedPathKeys())) {
+						organisations.add(organisation);
+					}
+				}
+			}
 		} else {
 			organisations = organisationService.getOrganisations();
 		}
@@ -484,8 +484,8 @@ public class UserAdminMainController extends MainLayoutBasicController implement
 			});
 
 			Organisation parentOrganisation = organisation.getParent();
-			if(parentOrganisation == null) {
-				//this is a root
+			if(parentOrganisation == null || !keytoOrganisations.containsKey(parentOrganisation.getKey())) {
+				//this is a root, or the user has not access to parent
 				accessNode.addChild(node);
 			} else {
 				Long parentKey = parentOrganisation.getKey();
@@ -532,8 +532,10 @@ public class UserAdminMainController extends MainLayoutBasicController implement
 
 	private void buildTreeQueriesSubMenu(GenericTreeNode queriesNode) {
 		appendNode("menu.userswithoutgroup", "menu.userswithoutgroup.alt", "userswithoutgroup", "o_sel_useradmin_userswithoutgroup", queriesNode);
-		appendNode("menu.users.without.email", "menu.users.without.email.alt", "userswithoutemail", "o_sel_useradmin_userswithoutemail", queriesNode);
-		appendNode("menu.users.email.duplicate", "menu.users.email.duplicate.alt", "usersemailduplicates", "o_sel_useradmin_usersemailduplicates", queriesNode);
+		if(isOlatAdmin) {
+			appendNode("menu.users.without.email", "menu.users.without.email.alt", "userswithoutemail", "o_sel_useradmin_userswithoutemail", queriesNode);
+			appendNode("menu.users.email.duplicate", "menu.users.email.duplicate.alt", "usersemailduplicates", "o_sel_useradmin_usersemailduplicates", queriesNode);
+		}
 		appendNode("menu.created.lastweek", "menu.created.lastweek.alt", "created.lastweek", "o_sel_useradmin_createdlastweek", queriesNode);
 		appendNode("menu.created.lastmonth", "menu.created.lastmonth.alt", "created.lastmonth", "o_sel_useradmin_createdlastmonth", queriesNode);
 		appendNode("menu.created.sixmonth", "menu.created.sixmonth.alt", "created.sixmonth", "o_sel_useradmin_createdsixmonth", queriesNode);
diff --git a/src/main/java/org/olat/user/ui/admin/UserSearchDataSource.java b/src/main/java/org/olat/user/ui/admin/UserSearchDataSource.java
index bb5057f5f80..7c596a47315 100644
--- a/src/main/java/org/olat/user/ui/admin/UserSearchDataSource.java
+++ b/src/main/java/org/olat/user/ui/admin/UserSearchDataSource.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.user.ui.admin;
 
 import java.util.Collections;
diff --git a/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java b/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java
index c94b67a172e..9917d8f6efe 100644
--- a/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java
+++ b/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.user.ui.admin;
 
 import java.util.Collections;
diff --git a/src/main/java/org/olat/user/ui/admin/UserSearchTableModel.java b/src/main/java/org/olat/user/ui/admin/UserSearchTableModel.java
index bd4a19635e3..5e9c347491f 100644
--- a/src/main/java/org/olat/user/ui/admin/UserSearchTableModel.java
+++ b/src/main/java/org/olat/user/ui/admin/UserSearchTableModel.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.user.ui.admin;
 
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataSourceModel;
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationUserManagementController.java b/src/main/java/org/olat/user/ui/organisation/OrganisationUserManagementController.java
index 571494ada9f..32158f4c4f1 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationUserManagementController.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationUserManagementController.java
@@ -49,6 +49,7 @@ import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
 import org.olat.core.gui.control.generic.modal.DialogBoxController;
 import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
@@ -56,6 +57,8 @@ import org.olat.core.id.Identity;
 import org.olat.user.UserManager;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
 import org.olat.user.ui.organisation.OrganisationUserManagementTableModel.MemberCols;
+import org.olat.user.ui.organisation.component.RoleFlexiCellRenderer;
+import org.olat.user.ui.organisation.event.RoleEvent;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -70,7 +73,7 @@ public class OrganisationUserManagementController extends FormBasicController {
 	public static final String usageIdentifyer = UserTableDataModel.class.getCanonicalName();
 	
 	private FlexiTableElement tableEl;
-	private FormLink addUserManagerButton;
+	private FormLink addMemberButton;
 	private FormLink removeMembershipButton;
 	private OrganisationUserManagementTableModel tableModel;
 	
@@ -78,6 +81,9 @@ public class OrganisationUserManagementController extends FormBasicController {
 	private UserSearchController userSearchCtrl;
 	private DialogBoxController confirmRemoveCtrl;
 	
+	private RoleListController roleListCtrl;
+	private CloseableCalloutWindowController calloutCtrl;
+	
 	private Organisation organisation;
 	private final boolean membersManaged;
 	private final boolean isAdministrativeUser;
@@ -108,8 +114,9 @@ public class OrganisationUserManagementController extends FormBasicController {
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		if(!membersManaged) {
-			addUserManagerButton = uifactory.addFormLink("add.user.manager", formLayout, Link.BUTTON);
-			addUserManagerButton.setIconLeftCSS("o_icon o_icon-fw o_icon_add_member");
+			addMemberButton = uifactory.addFormLink("add.member", formLayout, Link.BUTTON);
+			addMemberButton.setIconLeftCSS("o_icon o_icon-fw o_icon_add_member");
+			addMemberButton.setIconRightCSS("o_icon o_icon_caret");
 		
 			removeMembershipButton = uifactory.addFormLink("remove.memberships", formLayout, Link.BUTTON);
 		}
@@ -127,7 +134,7 @@ public class OrganisationUserManagementController extends FormBasicController {
 			colIndex++;
 		}
 		
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(MemberCols.role));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(MemberCols.role, new RoleFlexiCellRenderer(getTranslator())));
 
 		tableModel = new OrganisationUserManagementTableModel(columnsModel); 
 		tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 20, false, getTranslator(), formLayout);
@@ -175,7 +182,14 @@ public class OrganisationUserManagementController extends FormBasicController {
 			}
 			cmc.deactivate();
 			cleanUp();
-		} else if(cmc == source) {
+		} else if(roleListCtrl == source) {
+			calloutCtrl.deactivate();
+			cleanUp();
+			if(event instanceof RoleEvent) {
+				RoleEvent re = (RoleEvent)event;
+				doSearchMember(ureq, re.getSelectedRole());
+			}
+		} else if(cmc == source || calloutCtrl == source) {
 			cleanUp();
 		}
 		super.event(ureq, source, event);
@@ -184,9 +198,11 @@ public class OrganisationUserManagementController extends FormBasicController {
 	private void cleanUp() {
 		removeAsListenerAndDispose(confirmRemoveCtrl);
 		removeAsListenerAndDispose(userSearchCtrl);
+		removeAsListenerAndDispose(calloutCtrl);
 		removeAsListenerAndDispose(cmc);
 		confirmRemoveCtrl = null;
 		userSearchCtrl = null;
+		calloutCtrl = null;
 		cmc = null;
 	}
 
@@ -197,8 +213,8 @@ public class OrganisationUserManagementController extends FormBasicController {
 
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
-		if(addUserManagerButton == source) {
-			doSearchMember(ureq, OrganisationRoles.usermanager);
+		if(addMemberButton == source) {
+			doRolleCallout(ureq);
 		} else if(removeMembershipButton == source) {
 			doConfirmRemoveAllMemberships(ureq);
 		}
@@ -227,6 +243,19 @@ public class OrganisationUserManagementController extends FormBasicController {
 		loadModel(true);
 	}
 	
+	private void doRolleCallout(UserRequest ureq) {
+		removeAsListenerAndDispose(calloutCtrl);
+		removeAsListenerAndDispose(roleListCtrl);
+		
+		String title = translate("add.member");
+		roleListCtrl = new RoleListController(ureq, getWindowControl(), OrganisationRoles.values());
+		listenTo(roleListCtrl);
+		
+		calloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), roleListCtrl.getInitialComponent(), addMemberButton, title, true, null);
+		listenTo(calloutCtrl);
+		calloutCtrl.activate();	
+	}
+	
 	private void doSearchMember(UserRequest ureq, OrganisationRoles role) {
 		if(userSearchCtrl != null) return;
 
@@ -234,8 +263,8 @@ public class OrganisationUserManagementController extends FormBasicController {
 		userSearchCtrl.setUserObject(role);
 		listenTo(userSearchCtrl);
 		
-		cmc = new CloseableModalController(getWindowControl(), translate("close"), userSearchCtrl.getInitialComponent(),
-				true, translate("add.role." + role));
+		String title = translate("add.member.role", new String[] { translate(role.name()) });
+		cmc = new CloseableModalController(getWindowControl(), translate("close"), userSearchCtrl.getInitialComponent(), true, title);
 		listenTo(cmc);
 		cmc.activate();
 	}
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java b/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java
index 60de54957f6..fd6f1374443 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java
@@ -222,10 +222,12 @@ public class OrganisationsStructureAdminController extends FormBasicController i
 		if(createOrganisationButton == source) {
 			doCreateOrganisation(ureq);
 		} else if(tableEl == source) {
-			SelectionEvent se = (SelectionEvent)event;
-			if("select".equals(se.getCommand())) {
-				OrganisationRow row = model.getObject(se.getIndex());
-				doSelectOrganisation(ureq, row);
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				if("select".equals(event.getCommand())) {
+					OrganisationRow row = model.getObject(se.getIndex());
+					doSelectOrganisation(ureq, row);
+				}
 			}
 		} else if (source instanceof FormLink) {
 			FormLink link = (FormLink)source;
diff --git a/src/main/java/org/olat/user/ui/organisation/RoleListController.java b/src/main/java/org/olat/user/ui/organisation/RoleListController.java
new file mode 100644
index 00000000000..a8005a306c1
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/organisation/RoleListController.java
@@ -0,0 +1,80 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.user.ui.organisation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.olat.basesecurity.OrganisationRoles;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.link.LinkFactory;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.controller.BasicController;
+import org.olat.user.ui.organisation.event.RoleEvent;
+
+/**
+ * 
+ * Initial date: 29 mars 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RoleListController extends BasicController {
+
+	private final VelocityContainer mainVC;
+
+	public RoleListController(UserRequest ureq, WindowControl wControl, OrganisationRoles[] roles) {
+		super(ureq, wControl);
+		
+		mainVC = createVelocityContainer("tools");
+		List<String> links = new ArrayList<>(roles.length);
+		for(OrganisationRoles role:roles) {
+			addLink(role, links);
+		}
+		mainVC.contextPut("links", links);
+		putInitialPanel(mainVC);
+	}
+	
+	private Link addLink(OrganisationRoles role, List<String> links) {
+		Link link = LinkFactory.createLink(role.name(), role.name(), getTranslator(), mainVC, this, Link.LINK);
+		link.setUserObject(role);
+		mainVC.put(role.name(), link);
+		links.add(role.name());
+		return link;
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if(source instanceof Link) {
+			Link link = (Link)source;
+			if(link.getUserObject() instanceof OrganisationRoles) {
+				fireEvent(ureq, new RoleEvent((OrganisationRoles)link.getUserObject()));
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/organisation/_content/organisation_user_mgmt.html b/src/main/java/org/olat/user/ui/organisation/_content/organisation_user_mgmt.html
index f600d9a0ede..84540626779 100644
--- a/src/main/java/org/olat/user/ui/organisation/_content/organisation_user_mgmt.html
+++ b/src/main/java/org/olat/user/ui/organisation/_content/organisation_user_mgmt.html
@@ -1,6 +1,6 @@
-#if($r.available("add.user.manager"))
+#if($r.available("add.member"))
 <div class="o_button_group o_button_group_right">
-	$r.render("add.user.manager")
+	$r.render("add.member")
 </div>
 #end
 $r.render("table")
diff --git a/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties
index 258824d0895..cc4f731e588 100644
--- a/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties
@@ -1,6 +1,16 @@
 #Fri Dec 08 12:29:12 CET 2017
-add.role.usermanager=$\:add.user.manager
-add.user.manager=Benutzerverwalter hinzufügen
+usermanager=Benutzerverwalter
+sysadmin=Systemadministrator
+administrator=Administrator
+learnresourcemanager=Lernressourcenverwalter
+groupmanager=Gruppenverwalter
+poolmanager=Poolverwalter
+curriculummanager=Curriculumverwalter
+author=Author
+user=Benutzer
+guest=Gast
+add.member=Benutzer hinzufügen
+add.member.role=Benutzer hinzufügen als "{0}"
 admin.description=Organisationen verwalten
 admin.menu.title=Organisationen
 admin.menu.title.alt=Organisationen
diff --git a/src/main/java/org/olat/user/ui/organisation/component/RoleFlexiCellRenderer.java b/src/main/java/org/olat/user/ui/organisation/component/RoleFlexiCellRenderer.java
new file mode 100644
index 00000000000..e3077c88c8c
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/organisation/component/RoleFlexiCellRenderer.java
@@ -0,0 +1,60 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.user.ui.organisation.component;
+
+import org.olat.basesecurity.OrganisationRoles;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
+import org.olat.core.gui.render.Renderer;
+import org.olat.core.gui.render.StringOutput;
+import org.olat.core.gui.render.URLBuilder;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 29 mars 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RoleFlexiCellRenderer implements FlexiCellRenderer {
+	
+	private final Translator translator;
+	
+	public RoleFlexiCellRenderer(Translator translator) {
+		this.translator = translator;
+	}
+
+	@Override
+	public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source,
+			URLBuilder ubu, Translator trans) {
+		if(cellValue instanceof OrganisationRoles) {
+			OrganisationRoles role = (OrganisationRoles)cellValue;
+			target.append(translator.translate(role.name()));
+		} else if(cellValue instanceof String) {
+			String val = (String)cellValue;
+			if(OrganisationRoles.valid(val)) {
+				target.append(translator.translate(OrganisationRoles.valueOf(val).name()));
+			} else {
+				target.append(StringHelper.escapeHtml(val));
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/organisation/event/RoleEvent.java b/src/main/java/org/olat/user/ui/organisation/event/RoleEvent.java
new file mode 100644
index 00000000000..078463e2556
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/organisation/event/RoleEvent.java
@@ -0,0 +1,46 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.user.ui.organisation.event;
+
+import org.olat.basesecurity.OrganisationRoles;
+import org.olat.core.gui.control.Event;
+
+/**
+ * 
+ * Initial date: 29 mars 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RoleEvent extends Event {
+
+	private static final long serialVersionUID = -8941881101301854057L;
+	public static final String SELECT_ROLE = "select-role";
+	
+	private final OrganisationRoles selectedRole;
+	
+	public RoleEvent(OrganisationRoles selectedRole) {
+		super(SELECT_ROLE);
+		this.selectedRole = selectedRole;
+	}
+
+	public OrganisationRoles getSelectedRole() {
+		return selectedRole;
+	}
+}
diff --git a/src/test/java/org/olat/restapi/NotificationsTest.java b/src/test/java/org/olat/restapi/NotificationsTest.java
index ecc069e3191..319cad3982a 100644
--- a/src/test/java/org/olat/restapi/NotificationsTest.java
+++ b/src/test/java/org/olat/restapi/NotificationsTest.java
@@ -54,6 +54,8 @@ import org.codehaus.jackson.type.TypeReference;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.olat.basesecurity.OrganisationRoles;
+import org.olat.basesecurity.OrganisationService;
 import org.olat.collaboration.CollaborationTools;
 import org.olat.collaboration.CollaborationToolsFactory;
 import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
@@ -65,6 +67,8 @@ import org.olat.core.commons.services.notifications.restapi.vo.PublisherVO;
 import org.olat.core.commons.services.notifications.restapi.vo.SubscriptionInfoVO;
 import org.olat.core.commons.services.notifications.restapi.vo.SubscriptionListItemVO;
 import org.olat.core.id.Identity;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSLeaf;
@@ -96,6 +100,8 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
 public class NotificationsTest extends OlatJerseyTestCase {
+	
+	private static final OLog log = Tracing.createLoggerFor(NotificationsTest.class);
 
 	private static Identity userSubscriberId;
 	private static Identity userAndForumSubscriberId;
@@ -106,11 +112,15 @@ public class NotificationsTest extends OlatJerseyTestCase {
 	@Autowired
 	private DB dbInstance;
 	@Autowired
+	private OrganisationService organisationService;
+	@Autowired
 	private BusinessGroupService businessGroupService;
 	@Autowired
 	private NotificationsManager notificationManager;
 	@Autowired
 	private RepositoryManager repositoryManager;
+	@Autowired
+	private UsersSubscriptionManager usersSubscriptionManager;
 	
 	@Before
 	public void setUp() throws Exception {
@@ -119,9 +129,11 @@ public class NotificationsTest extends OlatJerseyTestCase {
 			userSubscriberId = JunitTestHelper.createAndPersistIdentityAsUser("rest-notifications-test-1");
 			userAndForumSubscriberId = JunitTestHelper.createAndPersistIdentityAsUser("rest-notifications-test-2");
 			JunitTestHelper.createAndPersistIdentityAsUser("rest-notifications-test-3");
+			//for the news
+			organisationService.addMember(userSubscriberId, OrganisationRoles.usermanager);
 			
-			SubscriptionContext subContext = UsersSubscriptionManager.getInstance().getNewUsersSubscriptionContext();
-			PublisherData publisherData = UsersSubscriptionManager.getInstance().getNewUsersPublisherData();
+			SubscriptionContext subContext = usersSubscriptionManager.getNewUsersSubscriptionContext();
+			PublisherData publisherData = usersSubscriptionManager.getNewUsersPublisherData();
 			if(!notificationManager.isSubscribed(userSubscriberId, subContext)) {
 				notificationManager.subscribe(userSubscriberId, subContext, publisherData);
 			}
@@ -487,11 +499,12 @@ public class NotificationsTest extends OlatJerseyTestCase {
 	private String addFile(VFSContainer folder) throws IOException {
 		String filename = UUID.randomUUID().toString();
 		VFSLeaf file = folder.createChildLeaf(filename + ".jpg");
-		OutputStream out = file.getOutputStream(true);
-		InputStream in = UserMgmtTest.class.getResourceAsStream("portrait.jpg");
-		IOUtils.copy(in, out);
-		IOUtils.closeQuietly(in);
-		IOUtils.closeQuietly(out);
+		try(OutputStream out = file.getOutputStream(true);
+			InputStream in = UserMgmtTest.class.getResourceAsStream("portrait.jpg")) {
+			IOUtils.copy(in, out);
+		} catch(IOException e) {
+			log.error("", e);
+		}
 		return file.getName();
 	}
 	
-- 
GitLab