diff --git a/src/main/java/org/olat/admin/restapi/RestapiAdminController.java b/src/main/java/org/olat/admin/restapi/RestapiAdminController.java
index 839ad08884b10f47807ba1c2a9e3c18ed188699b..0f643ed1cbdd5f9d5e7dfd64c70c0c2e19b03bae 100644
--- a/src/main/java/org/olat/admin/restapi/RestapiAdminController.java
+++ b/src/main/java/org/olat/admin/restapi/RestapiAdminController.java
@@ -19,6 +19,7 @@
  */
 package org.olat.admin.restapi;
 
+import org.olat.basesecurity.BaseSecurityModule;
 import org.olat.commons.calendar.CalendarModule;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
@@ -49,7 +50,11 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class RestapiAdminController extends FormBasicController {
 	
-	private MultipleSelectionElement enabled, managedGroupsEl, managedRepoEl, managedCalendarEl;
+	private MultipleSelectionElement enabled;
+	private MultipleSelectionElement managedRepoEl;
+	private MultipleSelectionElement managedGroupsEl;
+	private MultipleSelectionElement managedCalendarEl;
+	private MultipleSelectionElement managedRelationRole;
 	private FormLayoutContainer docLinkFlc;
 	
 	private static final String[] keys = {"on"};
@@ -62,6 +67,8 @@ public class RestapiAdminController extends FormBasicController {
 	private BusinessGroupModule groupModule;
 	@Autowired
 	private RepositoryModule repositoryModule;
+	@Autowired
+	private BaseSecurityModule securityModule;
 
 	public RestapiAdminController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl, "rest");
@@ -87,8 +94,8 @@ public class RestapiAdminController extends FormBasicController {
 			FormLayoutContainer accessDataFlc = FormLayoutContainer.createDefaultFormLayout("flc_access_data", getTranslator());
 			layoutContainer.add(accessDataFlc);
 
-			String[] values = new String[] { getTranslator().translate("rest.on") };
-			enabled = uifactory.addCheckboxesHorizontal("rest.enabled", accessDataFlc, keys, values);
+			String[] valueOn = new String[] { getTranslator().translate("rest.on") };
+			enabled = uifactory.addCheckboxesHorizontal("rest.enabled", accessDataFlc, keys, valueOn);
 			enabled.select(keys[0], restEnabled);
 			enabled.addActionListener(FormEvent.ONCHANGE);
 			
@@ -98,20 +105,21 @@ public class RestapiAdminController extends FormBasicController {
 			FormLayoutContainer managedFlc = FormLayoutContainer.createDefaultFormLayout("flc_managed", getTranslator());
 			layoutContainer.add(managedFlc);
 			
-			String[] valueGrps = new String[] { getTranslator().translate("rest.on") };
-			managedGroupsEl = uifactory.addCheckboxesHorizontal("managed.group", managedFlc, keys, valueGrps);
+			managedGroupsEl = uifactory.addCheckboxesHorizontal("managed.group", managedFlc, keys, valueOn);
 			managedGroupsEl.addActionListener(FormEvent.ONCHANGE);
 			managedGroupsEl.select(keys[0], groupModule.isManagedBusinessGroups());
 			
-			String[] valueRes = new String[] { getTranslator().translate("rest.on") };
-			managedRepoEl = uifactory.addCheckboxesHorizontal("managed.repo", managedFlc, keys, valueRes);
+			managedRepoEl = uifactory.addCheckboxesHorizontal("managed.repo", managedFlc, keys, valueOn);
 			managedRepoEl.addActionListener(FormEvent.ONCHANGE);
 			managedRepoEl.select(keys[0], repositoryModule.isManagedRepositoryEntries());
 			
-			String[] valueCal = new String[] { getTranslator().translate("rest.on") };
-			managedCalendarEl = uifactory.addCheckboxesHorizontal("managed.cal", managedFlc, keys, valueCal);
+			managedCalendarEl = uifactory.addCheckboxesHorizontal("managed.cal", managedFlc, keys, valueOn);
 			managedCalendarEl.addActionListener(FormEvent.ONCHANGE);
 			managedCalendarEl.select(keys[0], calendarModule.isManagedCalendars());
+			
+			managedRelationRole = uifactory.addCheckboxesHorizontal("managed.relation.role", managedFlc, keys, valueOn);
+			managedRelationRole.addActionListener(FormEvent.ONCHANGE);
+			managedRelationRole.select(keys[0], securityModule.isRelationRoleManaged());
 		}
 	}
 
@@ -122,7 +130,7 @@ public class RestapiAdminController extends FormBasicController {
 
 	@Override
 	protected void formOK(UserRequest ureq) {
-		
+		//
 	}
 
 	@Override
@@ -141,6 +149,9 @@ public class RestapiAdminController extends FormBasicController {
 		} else if (source == managedCalendarEl) {
 			boolean enable = managedCalendarEl.isAtLeastSelected(1);
 			calendarModule.setManagedCalendars(enable);
+		} else if (source == managedRelationRole) {
+			boolean enable = managedRelationRole.isAtLeastSelected(1);
+			securityModule.setRelationRoleManaged(enable);
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
diff --git a/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties
index 0e1eb1dee19723bd7cac57567fcd4a4e62bc6150..81909e1e84f5c0ae31a89723b0b67b9ce46be735 100644
--- a/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_de.properties
@@ -5,19 +5,12 @@ rest.intro=Die REST API macht viele OLAT-Funktionalit\u00e4ten f\u00fcr andere S
 rest.enabled=REST API Zugang
 rest.on=ein
 warn.beta.feature=Achtung! Diese Funktion befindet sich in einer Versuchsphase. Bitte beachten Sie, dass Fehler auftreten k\u00f6nnen, wenn diese Funktion verwendet wird.
-
-
-
-
-
-
-
-
 managed.cal=Managed Kalender
 managed.objects=Extern verwaltete Kurse und Gruppen
 managed.intro=Kurse und Gruppen k\u00f6nnen \u00fcber das REST API erstellt werden. Solch extern erstellt Kurse und Gruppen werden als "managed" bezeichnet da ein externes System das datenf\u00fchrende System ist. Die Verwendung dieser Funktion k\u00f6nnen Sie hier ein- und ausschalten.
 managed.group=Managed Gruppen
 managed.repo=Managed Lernressourcen
+managed.relation.role=Managed Benutzer zu Benutzer Beziehungen
 managed.flags.course.all=Vollst\u00e4ndige externe Verwaltung 
 managed.flags.course.editcontent=Kurseditor
 managed.flags.course.details=Titel und Beschreibung
diff --git a/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_en.properties
index 8257e3dcac4f7793821d11bff7232a273f5df952..ae4223d7767d5f3ee2cc9647239667f9bcb1f1dc 100644
--- a/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/admin/restapi/_i18n/LocalStrings_en.properties
@@ -1,12 +1,4 @@
 #Wed Aug 28 22:03:17 CEST 2013
-
-
-
-
-
-
-
-
 managed.cal=Managed calendars
 managed.flags.course.access=Access configuration
 managed.flags.course.all=Fully externally managed
@@ -46,6 +38,7 @@ managed.flags.group.tools=Tools configuration
 managed.group=Managed groups
 managed.intro=Courses and groups can be created via REST API. Such externally managed courses and groups are called "managed" as an external system is responsible for their lifecycle. This functionality can be enabled/disabled in the checkboxes below.  
 managed.objects=Externally managed courses and groups
+managed.relation.role=Managed user to user relations
 managed.repo=Managed learning resources
 rest.doc=Documentation
 rest.enabled=Access REST API
diff --git a/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_en.properties
index eb34af22e7cfa4429ccc95c8fd9b33001b17f517..4e711259283f276151796225c84be4b9f4c00252 100644
--- a/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_en.properties
@@ -1,5 +1,5 @@
 #Sat Jan 22 17:35:30 CET 2011
-form.addusers=User names
+form.addusers=User names, $org.olat.user.propertyhandlers\:table.name.email or $org.olat.user.propertyhandlers\:table.name.institutionalUserIdentifier
 form.names.example=One user name per line\: <br>administrator<br>author<br>user5<br>etc.
 import.intro=Indicate OLAT user names using the field below to add these to your group. One user name per line.
 import.success=Users were added to that group.
diff --git a/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_fr.properties
index b4d1ebb08ef38cdd02290501363e64adb89bd089..b8a0ff4ebdd4769a730cf395366df0d4cb6985eb 100644
--- a/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/admin/securitygroup/gui/multi/_i18n/LocalStrings_fr.properties
@@ -1,5 +1,5 @@
 #Sat Jan 08 14:56:26 CET 2011
-form.addusers=Nom d'utilisateur
+form.addusers=Nom d'utilisateurs, $org.olat.user.propertyhandlers\:table.name.email ou $org.olat.user.propertyhandlers\:table.name.institutionalUserIdentifier
 form.names.example=Ins\u00E9rer un nom par ligne\:<br>administrator<br>author<br>user5<br>etc.
 import.intro=Ins\u00E9rez des noms d'utilisateurs OLAT dans le champ ci-dessous pour les enregistrer dans le groupe. Un nom d'utilisateur par ligne.
 import.success=Les utilisateurs ont \u00E9t\u00E9 ajout\u00E9s au groupe.
diff --git a/src/main/java/org/olat/admin/user/UserAdminController.java b/src/main/java/org/olat/admin/user/UserAdminController.java
index ddd406ce77c850d973a60287862ee377e78c42fd..160cc6224a1ae24b879aca1970b40decb54e25d1 100644
--- a/src/main/java/org/olat/admin/user/UserAdminController.java
+++ b/src/main/java/org/olat/admin/user/UserAdminController.java
@@ -77,6 +77,7 @@ import org.olat.user.PropFoundEvent;
 import org.olat.user.UserManager;
 import org.olat.user.UserPropertiesController;
 import org.olat.user.ui.data.UserDataExportController;
+import org.olat.user.ui.identity.UserRelationsOverviewController;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -102,9 +103,10 @@ public class UserAdminController extends BasicController implements Activateable
 	private static final String NLS_EDIT_UAUTH 			= "edit.uauth";
 	private static final String NLS_EDIT_UPROP			= "edit.uprop";
 	private static final String NLS_EDIT_UROLES			= "edit.uroles";
+	private static final String NLS_EDIT_RELATIONS		= "edit.urelations";
 	private static final String NLS_EDIT_UQUOTA			= "edit.uquota";
 	private static final String NLS_VIEW_GROUPS			= "view.groups";
-	private static final String NLS_VIEW_COURSES			= "view.courses";
+	private static final String NLS_VIEW_COURSES		= "view.courses";
 	private static final String NLS_VIEW_ACCESS			= "view.access";
 	private static final String NLS_VIEW_EFF_STATEMENTS	= "view.effStatements";
 	private static final String NLS_VIEW_SUBSCRIPTIONS 	= "view.subscriptions";
@@ -123,6 +125,7 @@ public class UserAdminController extends BasicController implements Activateable
 	// controllers used in tabbed pane
 	private TabbedPane userTabP;
 	private Controller prefsCtr, propertiesCtr, pwdCtr, quotaCtr, rolesCtr, userShortDescrCtr;
+	private UserRelationsOverviewController relationsCtrl;
 	private DisplayPortraitController portraitCtr;
 	private UserAuthenticationsEditorController authenticationsCtr;
 	private Link backLink;
@@ -442,6 +445,15 @@ public class UserAdminController extends BasicController implements Activateable
 			listenTo(rolesCtr);
 			return rolesCtr.getInitialComponent();
 		});
+		
+		if (isUserManagerOf || isRolesManagerOf || isAdminOf || isPrincipalOf) {
+			userTabP.addTab(translate(NLS_EDIT_RELATIONS),  uureq -> {
+				boolean canModify = isUserManagerOf || isRolesManagerOf || isAdminOf;
+				relationsCtrl = new UserRelationsOverviewController(uureq, getWindowControl(), identity, canModify);
+				listenTo(relationsCtrl);
+				return relationsCtrl.getInitialComponent();
+			});
+		}
 
 		if (isUserManagerOf || isRolesManagerOf || isAdminOf) {
 			userTabP.addTab(translate(NLS_EDIT_UQUOTA),  uureq -> {
diff --git a/src/main/java/org/olat/admin/user/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/user/_i18n/LocalStrings_de.properties
index d1868a1815f7d7a722464f6c236c99751fb7a624..9ab66c57edb028bf69a135759e8e73efa4914aa3 100644
--- a/src/main/java/org/olat/admin/user/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/admin/user/_i18n/LocalStrings_de.properties
@@ -35,6 +35,7 @@ edit.uprofile=Benutzerprofil
 edit.uprop=Properties
 edit.upwd=Passwort \u00E4ndern
 edit.uquota=Quota
+edit.urelations=Beziehungen
 edit.uroles=Rollen
 email.notsent=$org.olat.user\:email.notsent
 email.sent=$org.olat.user\:email.sent
diff --git a/src/main/java/org/olat/admin/user/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/user/_i18n/LocalStrings_en.properties
index ca5e7b1706ba0a0e96d75128d140c3ddb710b72f..ab17ef46acac6e05aa913afc03b7f8fe3c1ad771 100644
--- a/src/main/java/org/olat/admin/user/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/admin/user/_i18n/LocalStrings_en.properties
@@ -35,6 +35,7 @@ edit.uprofile=User profile
 edit.uprop=Properties
 edit.upwd=Change password
 edit.uquota=Quota
+edit.urelations=Relations
 edit.uroles=Roles
 email.notsent=$org.olat.user\:email.notsent
 email.sent=$org.olat.user\:email.sent
diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityModule.java b/src/main/java/org/olat/basesecurity/BaseSecurityModule.java
index 2e753c72c82cf7ad03885a0b809f8221c156ad49..bcd4aa1799c61a176de431ac0155c063fac598d2 100644
--- a/src/main/java/org/olat/basesecurity/BaseSecurityModule.java
+++ b/src/main/java/org/olat/basesecurity/BaseSecurityModule.java
@@ -105,6 +105,7 @@ public class BaseSecurityModule extends AbstractSpringModule {
 	private static final String USERSEARCHAUTOCOMPLETE_SYSTEMADMINS = "userSearchAdminPropsForSystemAdmins";
 	private static final String USERSEARCH_MAXRESULTS = "userSearchMaxResults";
 	
+	private static final String RELATION_ROLE_MANAGED = "relationRoleManaged";
 
 	private static final String USERINFOS_TUNNEL_CBB = "userInfosTunnelCourseBuildingBlock";
 	/** The feature is enabled, always */
@@ -212,8 +213,8 @@ public class BaseSecurityModule extends AbstractSpringModule {
 	@Value("${usersearch.autocomplete.systemadmins:enabled}")
 	private String userSearchAutocompleteForSystemAdmins;
 	
-	
-	
+	@Value("${managed.relation.role:enabled}")
+	private String relationRoleManaged;
 	
 	@Value("${userinfos.tunnelcoursebuildingblock}")
 	private String userInfosTunnelCourseBuildingBlock;
@@ -297,6 +298,7 @@ public class BaseSecurityModule extends AbstractSpringModule {
 		userSearchAutocompleteForSystemAdmins = getStringPropertyValue(USERSEARCHAUTOCOMPLETE_SYSTEMADMINS, userSearchAutocompleteForSystemAdmins);
 
 		// other stuff
+		relationRoleManaged = getStringPropertyValue(USERSEARCHAUTOCOMPLETE_USERS, relationRoleManaged);
 		userSearchMaxResults = getStringPropertyValue(USERSEARCH_MAXRESULTS, userSearchMaxResults);
 		userInfosTunnelCourseBuildingBlock = getStringPropertyValue(USERINFOS_TUNNEL_CBB, userInfosTunnelCourseBuildingBlock);
 		wikiEnabled = getStringPropertyValue(WIKI_ENABLED, wikiEnabled);
@@ -529,6 +531,15 @@ public class BaseSecurityModule extends AbstractSpringModule {
 	public void setUserInfosTunnelCourseBuildingBlock(String enable) {
 		setStringProperty(USERINFOS_TUNNEL_CBB, enable, true);
 	}
+	
+	public boolean isRelationRoleManaged() {
+		return "enabled".equals(relationRoleManaged);
+	}
+
+	public void setRelationRoleManaged(boolean managed) {
+		relationRoleManaged = managed ? "enabled" : "disabled";
+		setStringProperty(RELATION_ROLE_MANAGED, relationRoleManaged, true);
+	}
 
 	public boolean isWikiEnabled() {
 		return "enabled".equals(wikiEnabled);
diff --git a/src/main/java/org/olat/basesecurity/IdentityRelationshipService.java b/src/main/java/org/olat/basesecurity/IdentityRelationshipService.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d6a660f994a775c1981f2fff0dbd90713c0b6ce
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/IdentityRelationshipService.java
@@ -0,0 +1,75 @@
+/**
+ * <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;
+
+import org.olat.core.id.Identity;
+
+/**
+ * 
+ * The service which manage identity to identity relationships. 
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface IdentityRelationshipService {
+	
+	public RelationRole createRole(String role, List<RelationRight> rights);
+	
+	public RelationRole createRole(String role, String externalId, String externalRef,
+			RelationRoleManagedFlag[] managedFlags, List<RelationRight> rights);
+	
+	public RelationRole updateRole(RelationRole relationRole, List<RelationRight> rights);
+	
+	public RelationRole getRole(Long key);
+	
+	public List<RelationRole> getAvailableRoles();
+	
+	public List<RelationRight> getAvailableRights();
+	
+	public boolean isInUse(RelationRole relationRole);
+	
+	public void deleteRole(RelationRole role);
+
+	public IdentityToIdentityRelation addRelation(Identity source, Identity target, RelationRole relationRole,
+			String externalId, IdentityToIdentityRelationManagedFlag[] managedFlags);
+	
+	public void addRelations(Identity source, Identity target, List<RelationRole> relationRoles);
+	
+	public void removeRelation(IdentityRef source, IdentityRef target, RelationRole relationRole);
+	
+	public IdentityToIdentityRelation getRelation(Long relationKey);
+	
+	public void deleteRelation(IdentityToIdentityRelation relation);
+	
+	/**
+	 * 
+	 * @param asSource
+	 * @return
+	 */
+	public List<IdentityToIdentityRelation> getRelationsAsSource(IdentityRef asSource);
+	
+	public List<IdentityToIdentityRelation> getRelationsAsTarget(IdentityRef asTarget);
+	
+	
+
+}
diff --git a/src/main/java/org/olat/basesecurity/IdentityToIdentityRelation.java b/src/main/java/org/olat/basesecurity/IdentityToIdentityRelation.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c7139e78b7a944f1df4ea0f1bc09e277c6fbb3b
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/IdentityToIdentityRelation.java
@@ -0,0 +1,45 @@
+/**
+ * <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 org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface IdentityToIdentityRelation extends CreateInfo {
+	
+	public Long getKey();
+	
+	public String getExternalId();
+	
+	public IdentityToIdentityRelationManagedFlag[] getManagedFlags();
+	
+	public Identity getSource();
+	
+	public Identity getTarget();
+	
+	public RelationRole getRole();
+
+}
diff --git a/src/main/java/org/olat/basesecurity/IdentityToIdentityRelationManagedFlag.java b/src/main/java/org/olat/basesecurity/IdentityToIdentityRelationManagedFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fb297ccd01b48f0a268437fbd21de6c7b051673
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/IdentityToIdentityRelationManagedFlag.java
@@ -0,0 +1,132 @@
+/**
+ * <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.Arrays;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum IdentityToIdentityRelationManagedFlag {
+
+	all,
+		delete(all);
+	
+	private static BaseSecurityModule securityModule;
+	private IdentityToIdentityRelationManagedFlag[] parents;
+	private static final OLog log = Tracing.createLoggerFor(IdentityToIdentityRelationManagedFlag.class);
+	public static final IdentityToIdentityRelationManagedFlag[] EMPTY_ARRAY = new IdentityToIdentityRelationManagedFlag[0];
+	
+	private IdentityToIdentityRelationManagedFlag() {
+		//
+	}
+	
+	private IdentityToIdentityRelationManagedFlag(IdentityToIdentityRelationManagedFlag... parents) {
+		if(parents == null) {
+			this.parents = new IdentityToIdentityRelationManagedFlag[0];
+		} else {
+			this.parents = parents;
+		}
+	}
+	
+	public static String toString(IdentityToIdentityRelationManagedFlag[] flags) {
+		if(flags == null || flags.length == 0) return "";
+		
+		StringBuilder sb = new StringBuilder();
+		for(IdentityToIdentityRelationManagedFlag flag:flags) {
+			if(sb.length() > 0) sb.append(",");
+			sb.append(flag.name());
+		}
+		return sb.toString();
+	}
+	
+	public static IdentityToIdentityRelationManagedFlag[] toEnum(String flags) {
+		if(StringHelper.containsNonWhitespace(flags)) {
+			String[] flagArr = flags.split(",");
+			IdentityToIdentityRelationManagedFlag[] flagEnums = new IdentityToIdentityRelationManagedFlag[flagArr.length];
+	
+			int count = 0;
+			for(String flag:flagArr) {
+				if(StringHelper.containsNonWhitespace(flag)) {
+					try {
+						IdentityToIdentityRelationManagedFlag flagEnum = valueOf(flag);
+						flagEnums[count++] = flagEnum;
+					} catch (Exception e) {
+						log.warn("Cannot parse this managed flag: " + flag, e);
+					}
+				}
+			}
+			
+			if(count != flagEnums.length) {
+				flagEnums = Arrays.copyOf(flagEnums, count);
+			}
+			return flagEnums;
+		} else {
+			return EMPTY_ARRAY;
+		}
+	}
+	
+	public static boolean isManaged(IdentityToIdentityRelation re, IdentityToIdentityRelationManagedFlag marker) {
+		if(securityModule == null) {
+			securityModule = CoreSpringFactory.getImpl(BaseSecurityModule.class);
+		}
+		if(!securityModule.isRelationRoleManaged()) {
+			return false;
+		}
+		return re != null && (contains(re, marker) || contains(re, marker.parents));
+	}
+	
+	public static boolean isManaged(IdentityToIdentityRelationManagedFlag[] flags, IdentityToIdentityRelationManagedFlag marker) {
+		if(securityModule == null) {
+			securityModule = CoreSpringFactory.getImpl(BaseSecurityModule.class);
+		}
+		if(!securityModule.isRelationRoleManaged()) {
+			return false;
+		}
+		return flags != null && (contains(flags, marker) || contains(flags, marker.parents));
+	}
+	
+	private static boolean contains(IdentityToIdentityRelation re, IdentityToIdentityRelationManagedFlag... markers) {
+		if(re == null) return false;
+		IdentityToIdentityRelationManagedFlag[] flags = re.getManagedFlags();
+		return contains(flags, markers);
+	}
+
+	private static boolean contains(IdentityToIdentityRelationManagedFlag[] flags, IdentityToIdentityRelationManagedFlag... markers) {
+		if(flags == null || flags.length == 0) return false;
+
+		for(IdentityToIdentityRelationManagedFlag flag:flags) {
+			for(IdentityToIdentityRelationManagedFlag marker:markers) {
+				if(flag.equals(marker)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/RelationRight.java b/src/main/java/org/olat/basesecurity/RelationRight.java
new file mode 100644
index 0000000000000000000000000000000000000000..af49991d30e7bd2162ee7def96fa97ab432dbf32
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/RelationRight.java
@@ -0,0 +1,36 @@
+/**
+ * <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 org.olat.core.id.CreateInfo;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface RelationRight extends CreateInfo {
+	
+	public Long getKey();
+	
+	public String getRight();
+
+}
diff --git a/src/main/java/org/olat/basesecurity/RelationRole.java b/src/main/java/org/olat/basesecurity/RelationRole.java
new file mode 100644
index 0000000000000000000000000000000000000000..613585911574c613395494a49783da3e7d0d9cf0
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/RelationRole.java
@@ -0,0 +1,55 @@
+/**
+ * <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.Set;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.ModifiedInfo;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface RelationRole extends CreateInfo, ModifiedInfo {
+	
+	public Long getKey();
+	
+	public String getRole();
+	
+	public void setRole(String role);
+	
+	public String getExternalId();
+	
+	public void setExternalId(String externalId);
+	
+	public String getExternalRef();
+	
+	public void setExternalRef(String externalRef);
+	
+	public RelationRoleManagedFlag[] getManagedFlags();
+	
+	public void setManagedFlags(RelationRoleManagedFlag[] flags);
+	
+	public Set<RelationRoleToRight> getRights();
+
+}
diff --git a/src/main/java/org/olat/basesecurity/RelationRoleManagedFlag.java b/src/main/java/org/olat/basesecurity/RelationRoleManagedFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..fde76aed45ded153ecd1101c5c13f4137fd6bf25
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/RelationRoleManagedFlag.java
@@ -0,0 +1,134 @@
+/**
+ * <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.Arrays;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum RelationRoleManagedFlag {
+
+	all,
+	 name(all),
+	 rights(all),
+     delete(all);
+	
+	private static BaseSecurityModule securityModule;
+	private RelationRoleManagedFlag[] parents;
+	private static final OLog log = Tracing.createLoggerFor(RelationRoleManagedFlag.class);
+	public static final RelationRoleManagedFlag[] EMPTY_ARRAY = new RelationRoleManagedFlag[0];
+	
+	private RelationRoleManagedFlag() {
+		//
+	}
+	
+	private RelationRoleManagedFlag(RelationRoleManagedFlag... parents) {
+		if(parents == null) {
+			this.parents = new RelationRoleManagedFlag[0];
+		} else {
+			this.parents = parents;
+		}
+	}
+	
+	public static RelationRoleManagedFlag[] toEnum(String flags) {
+		if(StringHelper.containsNonWhitespace(flags)) {
+			String[] flagArr = flags.split(",");
+			RelationRoleManagedFlag[] flagEnums = new RelationRoleManagedFlag[flagArr.length];
+	
+			int count = 0;
+			for(String flag:flagArr) {
+				if(StringHelper.containsNonWhitespace(flag)) {
+					try {
+						RelationRoleManagedFlag flagEnum = valueOf(flag);
+						flagEnums[count++] = flagEnum;
+					} catch (Exception e) {
+						log.warn("Cannot parse this managed flag: " + flag, e);
+					}
+				}
+			}
+			
+			if(count != flagEnums.length) {
+				flagEnums = Arrays.copyOf(flagEnums, count);
+			}
+			return flagEnums;
+		} else {
+			return EMPTY_ARRAY;
+		}
+	}
+	
+	public static String toString(RelationRoleManagedFlag[] flags) {
+		if(flags == null || flags.length == 0) return "";
+		
+		StringBuilder sb = new StringBuilder();
+		for(RelationRoleManagedFlag flag:flags) {
+			if(sb.length() > 0) sb.append(",");
+			sb.append(flag.name());
+		}
+		return sb.toString();
+	}
+	
+	public static boolean isManaged(RelationRole re, RelationRoleManagedFlag marker) {
+		if(securityModule == null) {
+			securityModule = CoreSpringFactory.getImpl(BaseSecurityModule.class);
+		}
+		if(!securityModule.isRelationRoleManaged()) {
+			return false;
+		}
+		return re != null && (contains(re, marker) || contains(re, marker.parents));
+	}
+	
+	public static boolean isManaged(RelationRoleManagedFlag[] flags, RelationRoleManagedFlag marker) {
+		if(securityModule == null) {
+			securityModule = CoreSpringFactory.getImpl(BaseSecurityModule.class);
+		}
+		if(!securityModule.isRelationRoleManaged()) {
+			return false;
+		}
+		return flags != null && (contains(flags, marker) || contains(flags, marker.parents));
+	}
+	
+	private static boolean contains(RelationRole re, RelationRoleManagedFlag... markers) {
+		if(re == null) return false;
+		RelationRoleManagedFlag[] flags = re.getManagedFlags();
+		return contains(flags, markers);
+	}
+
+	private static boolean contains(RelationRoleManagedFlag[] flags, RelationRoleManagedFlag... markers) {
+		if(flags == null || flags.length == 0) return false;
+
+		for(RelationRoleManagedFlag flag:flags) {
+			for(RelationRoleManagedFlag marker:markers) {
+				if(flag.equals(marker)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/RelationRoleToRight.java b/src/main/java/org/olat/basesecurity/RelationRoleToRight.java
new file mode 100644
index 0000000000000000000000000000000000000000..22ea02a80b6d9f6b45cf9a33f87c2db33a2a47e7
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/RelationRoleToRight.java
@@ -0,0 +1,38 @@
+/**
+ * <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 org.olat.core.id.CreateInfo;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface RelationRoleToRight extends CreateInfo {
+	
+	public Long getKey();
+	
+	public RelationRole getRole();
+
+	public RelationRight getRight();
+
+}
diff --git a/src/main/java/org/olat/basesecurity/manager/IdentityRelationshipServiceImpl.java b/src/main/java/org/olat/basesecurity/manager/IdentityRelationshipServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..53de2bb028c3e47569e61bbd62b67759661d5668
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/manager/IdentityRelationshipServiceImpl.java
@@ -0,0 +1,152 @@
+/**
+ * <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;
+import java.util.List;
+
+import org.olat.basesecurity.IdentityRef;
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.IdentityToIdentityRelationManagedFlag;
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.core.id.Identity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class IdentityRelationshipServiceImpl implements IdentityRelationshipService {
+	
+	@Autowired
+	private RelationRoleDAO relationRoleDao;
+	@Autowired
+	private RelationRightDAO relationRightDao;
+	@Autowired
+	private IdentityToIdentityRelationDAO identityRelationshipDao;
+	
+	@Override
+	public RelationRole createRole(String role, List<RelationRight> rights) {
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		if(rights != null && !rights.isEmpty()) {
+			relationRoleDao.setRights(relationRole, rights);
+		}
+		return relationRole;
+	}
+
+	@Override
+	public RelationRole createRole(String role, String externalId, String externalRef,
+			RelationRoleManagedFlag[] managedFlags, List<RelationRight> rights) {
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, externalId, externalRef, managedFlags);
+		if(rights != null && !rights.isEmpty()) {
+			relationRoleDao.setRights(relationRole, rights);
+		}
+		return relationRole;
+	}
+
+
+
+	@Override
+	public RelationRole updateRole(RelationRole relationRole, List<RelationRight> rights) {
+		if(rights == null) {
+			rights = new ArrayList<>();
+		}
+		return relationRoleDao.setRights(relationRole, rights);
+	}
+	
+	@Override
+	public RelationRole getRole(Long key) {
+		return relationRoleDao.loadRelationRoleByKey(key);
+	}
+
+	@Override
+	public List<RelationRole> getAvailableRoles() {
+		return relationRoleDao.loadRelationRoles();
+	}
+
+	@Override
+	public List<RelationRight> getAvailableRights() {
+		return relationRightDao.loadRelationRights();
+	}
+
+	@Override
+	public boolean isInUse(RelationRole relationRole) {
+		return identityRelationshipDao.isUsed(relationRole);
+	}
+
+	@Override
+	public void deleteRole(RelationRole role) {
+		RelationRole reloadedRole = relationRoleDao.loadRelationRoleByKey(role.getKey());
+		if(reloadedRole != null) {
+			relationRoleDao.delete(reloadedRole);
+		}
+	}
+	
+	@Override
+	public IdentityToIdentityRelation addRelation(Identity source, Identity target, RelationRole relationRole, String externalId,
+			IdentityToIdentityRelationManagedFlag[] managedFlags) {
+		return identityRelationshipDao.createRelation(source, target, relationRole,
+				externalId, IdentityToIdentityRelationManagedFlag.toString(managedFlags));
+	}
+
+	@Override
+	public void addRelations(Identity source, Identity target, List<RelationRole> relationRoles) {
+		for(RelationRole relationRole:relationRoles) {
+			if(!identityRelationshipDao.hasRelation(source, target, relationRole)) {
+				identityRelationshipDao.createRelation(source, target, relationRole, null, null);
+			}
+		}
+	}
+
+	@Override
+	public List<IdentityToIdentityRelation> getRelationsAsSource(IdentityRef asSource) {
+		return identityRelationshipDao.getRelationsAsSource(asSource);
+	}
+
+	@Override
+	public List<IdentityToIdentityRelation> getRelationsAsTarget(IdentityRef asTarget) {
+		return identityRelationshipDao.getRelationsAsTarget(asTarget);
+	}
+
+	@Override
+	public IdentityToIdentityRelation getRelation(Long relationKey) {
+		return identityRelationshipDao.getRelation(relationKey);
+	}
+
+	@Override
+	public void deleteRelation(IdentityToIdentityRelation relation) {
+		identityRelationshipDao.removeRelation(relation);
+	}
+
+	@Override
+	public void removeRelation(IdentityRef source, IdentityRef target, RelationRole relationRole) {
+		IdentityToIdentityRelation relation = identityRelationshipDao.getRelation(source, target, relationRole);
+		if(relation != null) {
+			identityRelationshipDao.removeRelation(relation);
+		}
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/manager/IdentityToIdentityRelationDAO.java b/src/main/java/org/olat/basesecurity/manager/IdentityToIdentityRelationDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8c9cf8ad83b0867d1ab4e4027722adb109d2a0d
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/manager/IdentityToIdentityRelationDAO.java
@@ -0,0 +1,154 @@
+/**
+ * <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.Date;
+import java.util.List;
+
+import org.olat.basesecurity.IdentityRef;
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.model.IdentityToIdentityRelationImpl;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class IdentityToIdentityRelationDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public IdentityToIdentityRelation createRelation(Identity source, Identity target, RelationRole role,
+			String externalId, String managedFlagsString) {
+		IdentityToIdentityRelationImpl relation = new IdentityToIdentityRelationImpl();
+		relation.setCreationDate(new Date());
+		relation.setExternalId(externalId);
+		relation.setManagedFlagsString(managedFlagsString);
+		relation.setSource(source);
+		relation.setTarget(target);
+		relation.setRole(role);
+		dbInstance.getCurrentEntityManager().persist(relation);
+		return relation;
+	}
+	
+	public boolean isUsed(RelationRole relationRole) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select identRel.key from identitytoidentity as identRel")
+		  .append(" where identRel.role.key=:roleKey");
+		
+		List<Long> usages = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), Long.class)
+			.setParameter("roleKey", relationRole.getKey())
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return usages != null && !usages.isEmpty() && usages.get(0) != null;
+	}
+	
+	public boolean hasRelation(IdentityRef source, IdentityRef target, RelationRole relationRole) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select identRel.key from identitytoidentity as identRel")
+		  .append(" where identRel.role.key=:roleKey and identRel.source.key=:sourceKey and identRel.target.key=:targetKey");
+		
+		List<Long> usages = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), Long.class)
+			.setParameter("roleKey", relationRole.getKey())
+			.setParameter("sourceKey", source.getKey())
+			.setParameter("targetKey", target.getKey())
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return usages != null && !usages.isEmpty() && usages.get(0) != null;
+	}
+	
+	public IdentityToIdentityRelation getRelation(IdentityRef source, IdentityRef target, RelationRole relationRole) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select identRel from identitytoidentity as identRel")
+		  .append(" where identRel.role.key=:roleKey and identRel.source.key=:sourceKey and identRel.target.key=:targetKey");
+		
+		List<IdentityToIdentityRelation> usages = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), IdentityToIdentityRelation.class)
+			.setParameter("roleKey", relationRole.getKey())
+			.setParameter("sourceKey", source.getKey())
+			.setParameter("targetKey", target.getKey())
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return usages != null && !usages.isEmpty() ? usages.get(0) : null;
+	}
+	
+	public IdentityToIdentityRelation getRelation(Long relationKey) {
+		StringBuilder sb = new StringBuilder(512);
+		sb.append("select identRel from identitytoidentity as identRel")
+		  .append(" inner join fetch identRel.role as relRol")
+		  .append(" inner join fetch identRel.target as identTarget")
+		  .append(" inner join fetch identTarget.user as userTarget")
+		  .append(" inner join fetch identRel.source as identSource")
+		  .append(" inner join fetch identSource.user as userSource")
+		  .append(" where identRel.key=:relationKey");
+		
+		List<IdentityToIdentityRelation> usages = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), IdentityToIdentityRelation.class)
+			.setParameter("relationKey", relationKey)
+			.getResultList();
+		return usages != null && !usages.isEmpty() ? usages.get(0) : null;
+	}
+	
+	public List<IdentityToIdentityRelation> getRelationsAsSource(IdentityRef source) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select identRel from identitytoidentity as identRel")
+		  .append(" inner join fetch identRel.role as relRol")
+		  .append(" inner join fetch identRel.target as identTarget")
+		  .append(" inner join fetch identTarget.user as userTarget")
+		  .append(" where identRel.source.key=:sourceKey");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), IdentityToIdentityRelation.class)
+				.setParameter("sourceKey", source.getKey())
+				.getResultList();
+	}
+	
+	public List<IdentityToIdentityRelation> getRelationsAsTarget(IdentityRef target) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select identRel from identitytoidentity as identRel")
+		  .append(" inner join fetch identRel.role as relRol")
+		  .append(" inner join fetch identRel.source as identSource")
+		  .append(" inner join fetch identSource.user as userSource")
+		  .append(" where identRel.target.key=:targetKey");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), IdentityToIdentityRelation.class)
+				.setParameter("targetKey", target.getKey())
+				.getResultList();
+	}
+	
+	public void removeRelation(IdentityToIdentityRelation relation) {
+		dbInstance.getCurrentEntityManager().remove(relation);
+	}
+
+}
diff --git a/src/main/java/org/olat/basesecurity/manager/RelationRightDAO.java b/src/main/java/org/olat/basesecurity/manager/RelationRightDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..23c1f5483f693ee04d4ebb817ea2a4d9fca7a336
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/manager/RelationRightDAO.java
@@ -0,0 +1,119 @@
+/**
+ * <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.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.model.RelationRightImpl;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.util.StringHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class RelationRightDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public RelationRight createRelationRight(String right) {
+		RelationRightImpl relationRight = new RelationRightImpl();
+		relationRight.setCreationDate(new Date());
+		relationRight.setRight(right);
+		dbInstance.getCurrentEntityManager().persist(relationRight);
+		return relationRight;
+	}
+	
+	public RelationRight loadRelationRightByKey(Long rightKey) {
+		List<RelationRight> rights = dbInstance.getCurrentEntityManager()
+			.createNamedQuery("loadRelationRightByKey", RelationRight.class)
+			.setParameter("rightKey", rightKey)
+			.getResultList();
+		return rights.isEmpty() ? null : rights.get(0);
+	}
+	
+	public RelationRight loadRelationRightByRight(String right) {
+		List<RelationRight> rights = dbInstance.getCurrentEntityManager()
+			.createNamedQuery("loadRelationRightByRight", RelationRight.class)
+			.setParameter("right", right)
+			.getResultList();
+		return rights.isEmpty() ? null : rights.get(0);
+	}
+	
+	public List<RelationRight> loadRelationRights() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select relRight from relationright as relRight");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), RelationRight.class)
+				.getResultList();
+	}
+	
+	/**
+	 * This method commits and closes the hibernate session.
+	 * 
+	 * @param rights The rights to create if they not exists
+	 */
+	public void ensureRightsExists(Class<? extends Enum<?>> rightsEnum) {
+		Enum<?>[] rights = rightsEnum.getEnumConstants();
+		
+		List<RelationRight> relationRights = loadRelationRights();
+		Set<String> rightNames = relationRights.stream()
+				.map(RelationRight::getRight).collect(Collectors.toSet());
+		
+		for(Enum<?> right:rights) {
+			if(!rightNames.contains(right.name())) {
+				createRelationRight(right.name());
+			}
+		}
+		dbInstance.commitAndCloseSession();
+	}
+	
+	/**
+	 * This method commits and closes the hibernate session.
+	 * 
+	 * @param rights The rights to create if they not exists
+	 */
+	public void ensureRightsExists(String...  rights) {
+		if(rights == null || rights.length == 0 || rights[0] == null) return;
+		
+		List<RelationRight> relationRights = loadRelationRights();
+		Set<String> rightNames = relationRights.stream()
+				.map(RelationRight::getRight).collect(Collectors.toSet());
+		
+		for(String right:rights) {
+			if(StringHelper.containsNonWhitespace(right) && !rightNames.contains(right)) {
+				createRelationRight(right);
+			}
+		}
+		dbInstance.commitAndCloseSession();
+	}
+
+}
diff --git a/src/main/java/org/olat/basesecurity/manager/RelationRoleDAO.java b/src/main/java/org/olat/basesecurity/manager/RelationRoleDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..1055af2dde442169039d371e2225c21656768af7
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/manager/RelationRoleDAO.java
@@ -0,0 +1,147 @@
+/**
+ * <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;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.basesecurity.RelationRoleToRight;
+import org.olat.basesecurity.model.RelationRoleImpl;
+import org.olat.basesecurity.model.RelationRoleToRightImpl;
+import org.olat.core.commons.persistence.DB;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class RelationRoleDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public RelationRole createRelationRole(String role, String externalId, String externalRef, RelationRoleManagedFlag[] flags) {
+		RelationRoleImpl relationRole = new RelationRoleImpl();
+		relationRole.setCreationDate(new Date());
+		relationRole.setLastModified(relationRole.getCreationDate());
+		relationRole.setRole(role);
+		relationRole.setExternalRef(externalRef);
+		relationRole.setExternalId(externalId);
+		relationRole.setManagedFlags(flags);
+		dbInstance.getCurrentEntityManager().persist(relationRole);
+		return relationRole;
+	}
+	
+	public RelationRole update(RelationRole relationRole) {
+		return dbInstance.getCurrentEntityManager().merge(relationRole);
+	}
+	
+	public RelationRole loadRelationRoleByKey(Long key) {
+		List<RelationRole> roles = dbInstance.getCurrentEntityManager()
+			.createNamedQuery("loadRelationRoleByKey", RelationRole.class)
+			.setParameter("roleKey", key)
+			.getResultList();
+		return roles.isEmpty() ? null : roles.get(0);
+	}
+	
+	public RelationRole loadRelationRoleByRole(String role) {
+		List<RelationRole> roles = dbInstance.getCurrentEntityManager()
+			.createNamedQuery("loadRelationRoleByRole", RelationRole.class)
+			.setParameter("role", role)
+			.getResultList();
+		return roles.isEmpty() ? null : roles.get(0);
+	}
+	
+	public List<RelationRole> loadRelationRoles() {
+		String q = "select relRole from relationrole as relRole";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(q, RelationRole.class)
+				.getResultList();
+	}
+	
+	public RelationRole setRights(RelationRole role, List<RelationRight> rights) {
+		RelationRoleImpl relationRole = (RelationRoleImpl)role;
+		
+		List<RelationRoleToRight> currentRoleToRights = new ArrayList<>(relationRole.getRights());
+		List<RelationRight> currentRights = currentRoleToRights
+				.stream().map(RelationRoleToRight::getRight).collect(Collectors.toList());
+		List<RelationRight> rightsToAdd = rights.stream()
+				.filter(r -> !currentRights.contains(r)).collect(Collectors.toList());
+
+		for(RelationRight rightToAdd:rightsToAdd) {
+			RelationRoleToRightImpl roleToRight = new RelationRoleToRightImpl();
+			roleToRight.setCreationDate(new Date());
+			roleToRight.setRole(relationRole);
+			roleToRight.setRight(rightToAdd);
+			dbInstance.getCurrentEntityManager().persist(roleToRight);
+			relationRole.getRights().add(roleToRight);
+		}
+		
+		for(RelationRoleToRight currentRoleToRight:currentRoleToRights) {
+			if(!rights.contains(currentRoleToRight.getRight())) {
+				relationRole.getRights().remove(currentRoleToRight);
+				//dbInstance.getCurrentEntityManager().remove(currentRoleToRight);
+			}
+		}
+		
+		relationRole = dbInstance.getCurrentEntityManager().merge(relationRole);
+		dbInstance.commit();
+		
+		//relationRole.setL
+		return relationRole;
+	}
+	
+	public RelationRole addRight(RelationRole role, RelationRight right) {
+		RelationRoleImpl relationRole = (RelationRoleImpl)role;
+		for(RelationRoleToRight roleToRight:relationRole.getRights()) {
+			if(roleToRight.getRight().equals(right)) {
+				return relationRole; // already added
+			}
+		}
+		
+		RelationRoleToRightImpl roleToRight = new RelationRoleToRightImpl();
+		roleToRight.setCreationDate(new Date());
+		roleToRight.setRole(relationRole);
+		roleToRight.setRight(right);
+		dbInstance.getCurrentEntityManager().persist(roleToRight);
+		relationRole.getRights().add(roleToRight);
+		//relationRole.setL
+		dbInstance.getCurrentEntityManager().merge(relationRole);
+		return relationRole;
+	}
+	
+	public void delete(RelationRole role) {
+		String del = "delete from relationroletoright where role.key=:roleKey";
+		dbInstance.getCurrentEntityManager().createQuery(del)
+			.setParameter("roleKey", role.getKey())
+			.executeUpdate();
+		dbInstance.getCurrentEntityManager().remove(role);
+		dbInstance.commit();
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/model/IdentityToIdentityRelationImpl.java b/src/main/java/org/olat/basesecurity/model/IdentityToIdentityRelationImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b6ba010b456e3f2bd3734bf6a518409f0862284
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/model/IdentityToIdentityRelationImpl.java
@@ -0,0 +1,169 @@
+/**
+ * <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.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.IdentityToIdentityRelationManagedFlag;
+import org.olat.basesecurity.RelationRole;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="identitytoidentity")
+@Table(name="o_bs_identity_to_identity")
+public class IdentityToIdentityRelationImpl implements IdentityToIdentityRelation, Persistable {
+
+	private static final long serialVersionUID = -8417977304577506811L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	
+	@Column(name="g_external_id", nullable=true, insertable=true, updatable=false)
+	private String externalId;
+	@Column(name="g_managed_flags", nullable=true, insertable=true, updatable=true)
+	private String managedFlagsString;
+	
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_source_id", nullable=false, insertable=true, updatable=false)
+	private Identity source;
+	
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_target_id", nullable=false, insertable=true, updatable=false)
+	private Identity target;
+	
+	@ManyToOne(targetEntity=RelationRoleImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_role_id", nullable=false, insertable=true, updatable=false)
+	private RelationRole role;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+	
+	@Override
+	public String getExternalId() {
+		return externalId;
+	}
+
+	public void setExternalId(String externalId) {
+		this.externalId = externalId;
+	}
+
+	public String getManagedFlagsString() {
+		return managedFlagsString;
+	}
+
+	public void setManagedFlagsString(String managedFlagsString) {
+		this.managedFlagsString = managedFlagsString;
+	}
+	
+	@Override
+	public IdentityToIdentityRelationManagedFlag[] getManagedFlags() {
+		return IdentityToIdentityRelationManagedFlag.toEnum(managedFlagsString);
+	}
+
+	@Override
+	public Identity getSource() {
+		return source;
+	}
+
+	public void setSource(Identity source) {
+		this.source = source;
+	}
+
+	@Override
+	public Identity getTarget() {
+		return target;
+	}
+
+	public void setTarget(Identity target) {
+		this.target = target;
+	}
+
+	@Override
+	public RelationRole getRole() {
+		return role;
+	}
+
+	public void setRole(RelationRole role) {
+		this.role = role;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 69615671 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof IdentityToIdentityRelationImpl) {
+			IdentityToIdentityRelationImpl rel = (IdentityToIdentityRelationImpl)obj;
+			return getKey() != null && getKey().equals(rel.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/model/RelationRightImpl.java b/src/main/java/org/olat/basesecurity/model/RelationRightImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..b61efec2bd5c75c2401928fef7f686a604ed9b1f
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/model/RelationRightImpl.java
@@ -0,0 +1,111 @@
+/**
+ * <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.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.RelationRight;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="relationright")
+@Table(name="o_bs_relation_right")
+@NamedQuery(name="loadRelationRightByKey", query="select relRight from relationright relRight where relRight.key=:rightKey")
+@NamedQuery(name="loadRelationRightByRight", query="select relRight from relationright relRight where relRight.right=:right")
+public class RelationRightImpl implements RelationRight, Persistable {
+
+	private static final long serialVersionUID = 2403839554972052354L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+
+	@Column(name="g_right", nullable=false, insertable=true, updatable=false)
+	private String right;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+	
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public String getRight() {
+		return right;
+	}
+	
+	public void setRight(String right) {
+		this.right = right;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? -74283 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof RelationRightImpl) {
+			RelationRightImpl relation = (RelationRightImpl)obj;
+			return getKey() != null && getKey().equals(relation.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/model/RelationRoleImpl.java b/src/main/java/org/olat/basesecurity/model/RelationRoleImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..5306205b811d7b9d8ec045a8ccf368f83a9828a8
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/model/RelationRoleImpl.java
@@ -0,0 +1,191 @@
+/**
+ * <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.model;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.basesecurity.RelationRoleToRight;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="relationrole")
+@Table(name="o_bs_relation_role")
+@NamedQuery(name="loadRelationRoleByKey", query="select relRole from relationrole relRole where relRole.key=:roleKey")
+@NamedQuery(name="loadRelationRoleByRole", query="select relRole from relationrole relRole where relRole.role=:role")
+public class RelationRoleImpl implements RelationRole, Persistable {
+
+	private static final long serialVersionUID = 5176649780644160612L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=false)
+	private Date lastModified;
+
+	@Column(name="g_role", nullable=false, insertable=true, updatable=true)
+	private String role;
+	@Column(name="g_external_id", nullable=true, insertable=true, updatable=true)
+	private String externalId;
+	@Column(name="g_external_ref", nullable=true, insertable=true, updatable=true)
+	private String externalRef;
+	@Column(name="g_managed_flags", nullable=true, insertable=true, updatable=true)
+	private String managedFlagsString;
+	
+	@OneToMany(targetEntity=RelationRoleToRightImpl.class, fetch=FetchType.LAZY,
+			orphanRemoval=true, cascade={CascadeType.PERSIST, CascadeType.REMOVE})
+	@JoinColumn(name="fk_role_id")
+	private Set<RelationRoleToRight> rights;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date date) {
+		lastModified = date;
+	}
+
+	@Override
+	public String getRole() {
+		return role;
+	}
+
+	public void setRole(String role) {
+		this.role = role;
+	}
+	
+	@Override
+	public String getExternalId() {
+		return externalId;
+	}
+
+	public void setExternalId(String externalId) {
+		this.externalId = externalId;
+	}
+
+	@Override
+	public String getExternalRef() {
+		return externalRef;
+	}
+
+	public void setExternalRef(String externalRef) {
+		this.externalRef = externalRef;
+	}
+
+	public String getManagedFlagsString() {
+		return managedFlagsString;
+	}
+
+	public void setManagedFlagsString(String managedFlagsString) {
+		this.managedFlagsString = managedFlagsString;
+	}
+
+	@Override
+	public RelationRoleManagedFlag[] getManagedFlags() {
+		return RelationRoleManagedFlag.toEnum(managedFlagsString);
+	}
+
+	@Override
+	public void setManagedFlags(RelationRoleManagedFlag[] flags) {
+		managedFlagsString = RelationRoleManagedFlag.toString(flags);
+	}
+
+	@Override
+	public Set<RelationRoleToRight> getRights() {
+		if(rights == null) {
+			rights = new HashSet<>();
+		}
+		return rights;
+	}
+
+	public void setRights(Set<RelationRoleToRight> rights) {
+		this.rights = rights;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 1456092 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof RelationRoleImpl) {
+			RelationRoleImpl relation = (RelationRoleImpl)obj;
+			return getKey() != null && getKey().equals(relation.getKey());
+		}
+		return super.equals(obj);
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/model/RelationRoleToRightImpl.java b/src/main/java/org/olat/basesecurity/model/RelationRoleToRightImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..3407b5288f0fc4cc5f801340a37ac822367ce646
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/model/RelationRoleToRightImpl.java
@@ -0,0 +1,125 @@
+/**
+ * <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.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleToRight;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="relationroletoright")
+@Table(name="o_bs_relation_role_to_right")
+public class RelationRoleToRightImpl implements RelationRoleToRight, Persistable {
+
+	private static final long serialVersionUID = 6149090168353809087L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+
+	@ManyToOne(targetEntity=RelationRoleImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_role_id", nullable=false, insertable=true, updatable=false)
+	private RelationRole role;
+	
+	@ManyToOne(targetEntity=RelationRightImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_right_id", nullable=false, insertable=true, updatable=false)
+	private RelationRight right;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+	
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+	
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	public RelationRole getRole() {
+		return role;
+	}
+
+	public void setRole(RelationRole role) {
+		this.role = role;
+	}
+
+	public RelationRight getRight() {
+		return right;
+	}
+
+	public void setRight(RelationRight right) {
+		this.right = right;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? -381746 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof RelationRoleToRightImpl) {
+			RelationRoleToRightImpl relation = (RelationRoleToRightImpl)obj;
+			return getKey() != null && getKey().equals(relation.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/course/CourseModule.java b/src/main/java/org/olat/course/CourseModule.java
index 848bd4308ed54ad71dc6bb6e61679536a555b3d5..7d6e36768266f650a65f5417045d4d8dcf1464b4 100644
--- a/src/main/java/org/olat/course/CourseModule.java
+++ b/src/main/java/org/olat/course/CourseModule.java
@@ -27,6 +27,7 @@ package org.olat.course;
 
 import java.util.HashMap;
 
+import org.olat.basesecurity.manager.RelationRightDAO;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
 import org.olat.core.configuration.AbstractSpringModule;
 import org.olat.core.id.Identity;
@@ -36,6 +37,7 @@ import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.course.assessment.AssessmentManager;
+import org.olat.course.groupsandrights.CourseRightsEnum;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -69,10 +71,13 @@ public class CourseModule extends AbstractSpringModule {
 	private boolean displayChangeLog;
 	
 	// Repository types
-	public static String ORES_TYPE_COURSE = OresHelper.calculateTypeName(CourseModule.class);
-	public static OLATResourceable ORESOURCEABLE_TYPE_COURSE = OresHelper.lookupType(CourseModule.class);
+	public static final String ORES_TYPE_COURSE = OresHelper.calculateTypeName(CourseModule.class);
+	public static final OLATResourceable ORESOURCEABLE_TYPE_COURSE = OresHelper.lookupType(CourseModule.class);
 	public static final String ORES_COURSE_ASSESSMENT = OresHelper.calculateTypeName(AssessmentManager.class);
 	
+	@Autowired
+	private RelationRightDAO relationRightDao;
+	
 	private static CoordinatorManager coordinatorManager;
 
 	@Autowired
@@ -97,6 +102,14 @@ public class CourseModule extends AbstractSpringModule {
 	@Override
 	public void init() {
 		initFromChangedProperties();
+		initCourseRights();
+	}
+	
+	/**
+	 * Initialize the course rights for user to user relations.
+	 */
+	private void initCourseRights() {
+		relationRightDao.ensureRightsExists(CourseRightsEnum.class);
 	}
 	
 	/**
@@ -142,8 +155,7 @@ public class CourseModule extends AbstractSpringModule {
 	 * @return the generated SubscriptionContext
 	 */
 	public static SubscriptionContext createSubscriptionContext(CourseEnvironment ce, CourseNode cn) {
-		SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent());
-		return sc;
+		return new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent());
 	}
 
 	/**
@@ -153,8 +165,7 @@ public class CourseModule extends AbstractSpringModule {
 	 *         to be able to cleanup/obtain
 	 */
 	public static SubscriptionContext createTechnicalSubscriptionContext(CourseEnvironment ce, CourseNode cn) {
-		SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent());
-		return sc;
+		return new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent());
 	}
 
 	/**
@@ -168,8 +179,7 @@ public class CourseModule extends AbstractSpringModule {
 	 * @return
 	 */
 	public static SubscriptionContext createSubscriptionContext(CourseEnvironment ce, CourseNode cn, String subsubId) {
-		SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent() + ":" + subsubId);
-		return sc;
+		return new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent() + ":" + subsubId);
 	}
 	
 	public static void registerForCourseType(GenericEventListener gel, Identity identity) {
diff --git a/src/main/java/org/olat/course/groupsandrights/CourseRightsEnum.java b/src/main/java/org/olat/course/groupsandrights/CourseRightsEnum.java
new file mode 100644
index 0000000000000000000000000000000000000000..53344841fdf4f7d2ef862c83f7b7016ec1871e84
--- /dev/null
+++ b/src/main/java/org/olat/course/groupsandrights/CourseRightsEnum.java
@@ -0,0 +1,33 @@
+/**
+ * <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.course.groupsandrights;
+
+/**
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum CourseRightsEnum {
+	
+	viewEfficiencyStatement,
+	viewCourseCalendar
+
+}
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java
index db4344714fbbe90be3ddf49ca68ced952a488d45..dbfd954edd7bf696c616b7715405f7dd309a3616 100644
--- a/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java
+++ b/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java
@@ -29,7 +29,6 @@ import org.olat.basesecurity.BaseSecurity;
 import org.olat.basesecurity.BaseSecurityModule;
 import org.olat.basesecurity.OrganisationRoles;
 import org.olat.basesecurity.OrganisationService;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
@@ -73,9 +72,6 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr
 
 	public ImportMemberOverviewIdentitiesController(UserRequest ureq, WindowControl wControl, Form rootForm, StepsRunContext runContext) {
 		super(ureq, wControl, rootForm, runContext, LAYOUT_VERTICAL, null);
-		userManager = UserManager.getInstance();
-		securityManager = CoreSpringFactory.getImpl(BaseSecurity.class);
-		securityModule = CoreSpringFactory.getImpl(BaseSecurityModule.class);
 
 		oks = null;
 		if(containsRunContextKey("logins")) {
diff --git a/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties
index e390a00bc6afb3404243cfba949af0e5e5fa401d..1bad84460c987bc07c9f29bb8b9b1b7b434c16fb 100644
--- a/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties
@@ -103,6 +103,8 @@ pwdav.password.set=******* (Passwort verschl\u00FCsselt)
 pwdav.password.successful=Ihr neues WebDAV Passwort wurde erfolgreich gespeichert. Es ist ab sofort aktiv.
 pwdav.title=WebDAV Zugang
 pwdav.username=WebDAV Benutzername
+relation.right.viewEfficiencyStatement=Leistungnachweise ansehen
+relation.right.viewCourseCalendar=Kurskalender ansehen
 replayurl.active=Load Performance URL eingeschaltet
 reset.desc=Hier k\u00F6nnen Sie die personalisierten Systemkonfiguration wieder auf die Standardeinstellung zur\u00FCcksetzen. W\u00E4hlen Sie unten die Einstellungen aus, die zur\u00FCckgesetzt werden sollen. Um die Einstellungen zu aktivieren werden Sie automatisch aus dem System ausgeloggt\!
 reset.elements=Einstellungen
diff --git a/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties
index 0160f5d573a56bc8833315589bc4fd39a7a0ed3e..10dc88f14f5afdd0de8de495542babc1961ea822 100644
--- a/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties
@@ -104,6 +104,8 @@ pwdav.password.successful=Your new WebDAV password has been saved successfully.
 pwdav.title=WebDAV access
 pwdav.username=WebDav user name
 replayurl.active=Load performance URL active
+relation.role.viewEfficiencyStatement=View efficiency statements
+relation.role.viewCourseCalendar=View course calenders
 reset.desc=You can reset your personalized system configuration to the default values using this form. Choose below the settings you want reset. To activate the changes the system will automatically trigger a logout. 
 reset.elements=Configurations
 reset.elements.guiprefs=Personalized interface components (menu, tool boxes, tables, portal, calendar etc.)
diff --git a/src/main/java/org/olat/user/_spring/userContext.xml b/src/main/java/org/olat/user/_spring/userContext.xml
index 46c8306fbed3af7443ddf0e5ebd9a5da8be0aa05..f7a3b5bcf73fbd9bf03a5d9304ae2f9f5a25b9f8 100644
--- a/src/main/java/org/olat/user/_spring/userContext.xml
+++ b/src/main/java/org/olat/user/_spring/userContext.xml
@@ -291,6 +291,26 @@
 		</property>
 	</bean>
 	
+	<!-- Roles and rights for identities relationships -->
+	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
+		<property name="order" value="9013" />
+		<property name="actionController">	
+			<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
+				<property name="className" value="org.olat.user.ui.role.RelationRolesAdminController"/>
+			</bean>
+		</property>
+		<property name="navigationKey" value="identityroles" />
+		<property name="parentTreeNodeIdentifier" value="modulesParent" /> 
+		<property name="i18nActionKey" value="admin.menu.title"/>
+		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
+		<property name="translationPackage" value="org.olat.user.ui.role"/>
+		<property name="extensionPoints">
+			<list>	
+				<value>org.olat.admin.SystemAdminMainController</value>		
+			</list>
+		</property>
+	</bean>
+	
 	<!--  user properties admin-gui -->
 	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
 		<property name="actionController">
diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
index 39b9819066694b0e10cc6e8868c7c052b87561de..50c3d7f1078772f081dfce5b3c3925536a43130a 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
+++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
@@ -1061,6 +1061,22 @@
 						</property>
 					</bean>
 				</entry>
+				
+				<entry key="org.olat.user.ui.identity.UserRelationsController">
+					<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
+					<property name="description" value="Relations user to user" />
+						<property name="propertyHandlers">
+							<list>
+								<ref bean="userPropertyFirstName" />
+								<ref bean="userPropertyLastName" />
+								<ref bean="userPropertyEmail" />
+								<ref bean="userPropertyInstitutionalName" />
+								<ref bean="userPropertyInstitutionalUserIdentifier" />
+								<ref bean="userPropertyInstitutionalEmail" />
+							</list>
+						</property>
+					</bean>
+				</entry>
 
 				<entry key="org.olat.group.BusinessGroupArchiver">
 					<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
diff --git a/src/main/java/org/olat/user/restapi/Examples.java b/src/main/java/org/olat/user/restapi/Examples.java
index 0ac048600f396ca47d9d226aaa647b61de6b0d96..79bae53c184e0023028cab18b3f3cf9d063ae0dd 100644
--- a/src/main/java/org/olat/user/restapi/Examples.java
+++ b/src/main/java/org/olat/user/restapi/Examples.java
@@ -20,6 +20,8 @@
 
 package org.olat.user.restapi;
 
+import java.util.Collections;
+
 /**
  * 
  * Description:<br>
@@ -41,6 +43,8 @@ public class Examples {
 	
 	public static final OrganisationTypeVO SAMPLE_ORGANISATIONTYPEVO = new OrganisationTypeVO();
 	
+	public static final RelationRoleVO SAMPLE_RELATIONROLEVO = new RelationRoleVO();
+	public static final IdentityToIdentityRelationVO SAMPLE_IDENTITYTOIDENTITYRELATIONVO = new IdentityToIdentityRelationVO();
   
 	static {
 		SAMPLE_USERVO.setKey(345l);
@@ -77,5 +81,20 @@ public class Examples {
 		SAMPLE_ORGANISATIONTYPEVO.setDisplayName("Organization type");
 		SAMPLE_ORGANISATIONTYPEVO.setExternalId("OWL-1-ext");
 		SAMPLE_ORGANISATIONTYPEVO.setManagedFlagsString("externalId");
+		
+		SAMPLE_RELATIONROLEVO.setKey(56l);
+		SAMPLE_RELATIONROLEVO.setExternalId("RO-1");
+		SAMPLE_RELATIONROLEVO.setExternalRef("ROR-2");
+		SAMPLE_RELATIONROLEVO.setManagedFlags("delete");
+		SAMPLE_RELATIONROLEVO.setRights(Collections.singletonList("myRight"));
+		
+		SAMPLE_IDENTITYTOIDENTITYRELATIONVO.setKey(234l);
+		SAMPLE_IDENTITYTOIDENTITYRELATIONVO.setExternalId("ID-2-ID-256");
+		SAMPLE_IDENTITYTOIDENTITYRELATIONVO.setIdentitySourceKey(34019l);
+		SAMPLE_IDENTITYTOIDENTITYRELATIONVO.setIdentityTargetKey(23100l);
+		SAMPLE_IDENTITYTOIDENTITYRELATIONVO.setRelationRole("Supervisor");
+		SAMPLE_IDENTITYTOIDENTITYRELATIONVO.setRelationRoleKey(23l);
+		SAMPLE_IDENTITYTOIDENTITYRELATIONVO.setManagedFlagsString("all");
+		
 	}
 }
diff --git a/src/main/java/org/olat/user/restapi/IdentityToIdentityRelationVO.java b/src/main/java/org/olat/user/restapi/IdentityToIdentityRelationVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec3b422b408e7e3e5032b981706e6abf5986ff73
--- /dev/null
+++ b/src/main/java/org/olat/user/restapi/IdentityToIdentityRelationVO.java
@@ -0,0 +1,120 @@
+/**
+ * <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.restapi;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.IdentityToIdentityRelationManagedFlag;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "identityToIdentityRelationVO")
+public class IdentityToIdentityRelationVO {
+	
+	private Long key;
+	private Long identitySourceKey;
+	private Long identityTargetKey;
+
+	private String externalId;
+	private String managedFlagsString;
+	
+	private Long relationRoleKey;
+	private String relationRole;
+	
+	public IdentityToIdentityRelationVO() {
+		//
+	}
+	
+	public static IdentityToIdentityRelationVO valueOf(IdentityToIdentityRelation relation) {
+		IdentityToIdentityRelationVO relationVo = new IdentityToIdentityRelationVO();
+		relationVo.setKey(relation.getKey());
+		relationVo.setIdentitySourceKey(relation.getSource().getKey());
+		relationVo.setIdentityTargetKey(relation.getTarget().getKey());
+		relationVo.setExternalId(relation.getExternalId());
+		relationVo.setManagedFlagsString(IdentityToIdentityRelationManagedFlag.toString(relation.getManagedFlags()));
+		relationVo.setRelationRoleKey(relation.getRole().getKey());
+		relationVo.setRelationRole(relation.getRole().getRole());
+		return relationVo;
+	}
+
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	public Long getIdentitySourceKey() {
+		return identitySourceKey;
+	}
+
+	public void setIdentitySourceKey(Long identitySourceKey) {
+		this.identitySourceKey = identitySourceKey;
+	}
+
+	public Long getIdentityTargetKey() {
+		return identityTargetKey;
+	}
+
+	public void setIdentityTargetKey(Long identityTargetKey) {
+		this.identityTargetKey = identityTargetKey;
+	}
+
+	public String getExternalId() {
+		return externalId;
+	}
+
+	public void setExternalId(String externalId) {
+		this.externalId = externalId;
+	}
+
+	public String getManagedFlagsString() {
+		return managedFlagsString;
+	}
+
+	public void setManagedFlagsString(String managedFlagsString) {
+		this.managedFlagsString = managedFlagsString;
+	}
+
+	public Long getRelationRoleKey() {
+		return relationRoleKey;
+	}
+
+	public void setRelationRoleKey(Long relationRoleKey) {
+		this.relationRoleKey = relationRoleKey;
+	}
+
+	public String getRelationRole() {
+		return relationRole;
+	}
+
+	public void setRelationRole(String relationRole) {
+		this.relationRole = relationRole;
+	}
+}
diff --git a/src/main/java/org/olat/user/restapi/IdentityToIdentityRelationsWebService.java b/src/main/java/org/olat/user/restapi/IdentityToIdentityRelationsWebService.java
new file mode 100644
index 0000000000000000000000000000000000000000..2171cecf1f447ca797821643b80f7fa8a13e0c12
--- /dev/null
+++ b/src/main/java/org/olat/user/restapi/IdentityToIdentityRelationsWebService.java
@@ -0,0 +1,201 @@
+/**
+ * <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.restapi;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.IdentityToIdentityRelationManagedFlag;
+import org.olat.basesecurity.RelationRole;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.id.Identity;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * The access permission is done by UserWebService.
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class IdentityToIdentityRelationsWebService {
+	
+	private final Identity identity;
+	
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private IdentityRelationshipService identityRelationshipService;
+	
+	public IdentityToIdentityRelationsWebService(Identity identity) {
+		CoreSpringFactory.autowireObject(this);
+		this.identity = identity;
+	}
+	
+	
+	/**
+	 * List of relations from the specified user to others.
+	 * 
+	 * @response.representation.200.qname {http://www.example.com}identityToIdentityRelationVO
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The list of relation from the specified user
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_IDENTITYTOIDENTITYRELATIONVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @param httpRequest The HTTP request
+	 * @return An array of relations
+	 */
+	@GET
+	@Path("source")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getRelationsAsSource() {
+		List<IdentityToIdentityRelation> relations = identityRelationshipService.getRelationsAsSource(identity);
+		IdentityToIdentityRelationVO[] relationVOes = toArrayOfVOes(relations);
+		return Response.ok(relationVOes).build();
+	}
+	
+	/**
+	 * List of relations to the specified user from others.
+	 * 
+	 * @response.representation.200.qname {http://www.example.com}identityToIdentityRelationVO
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The list of relation to the specified user
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_IDENTITYTOIDENTITYRELATIONVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @param httpRequest The HTTP request
+	 * @return An array of relations
+	 */
+	@GET
+	@Path("target")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getRelationsAsTarget() {
+		List<IdentityToIdentityRelation> relations = identityRelationshipService.getRelationsAsTarget(identity);
+		IdentityToIdentityRelationVO[] relationVOes = toArrayOfVOes(relations);
+		return Response.ok(relationVOes).build();
+	}
+	
+	/**
+	 * Creates and persists a new relation entity.
+	 * 
+	 * @response.representation.qname {http://www.example.com}identityToIdentityRelationVO
+	 * @response.representation.mediaType application/xml, application/json
+	 * @response.representation.doc The relation to persist
+	 * @response.representation.example {@link org.olat.user.restapi.Examples#SAMPLE_IDENTITYTOIDENTITYRELATIONVO}
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The persisted relation
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_IDENTITYTOIDENTITYRELATIONVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @param relationRoleVo The relation to persist
+	 * @param request The HTTP request
+	 * @return The new persisted <code>relation</code>
+	 */
+	@PUT
+	@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response putRelation(IdentityToIdentityRelationVO relationRoleVo) {
+		return postRelation(relationRoleVo);
+	}
+	
+	/**
+	 * Creates and persists a new relation entity.
+	 * 
+	 * @response.representation.qname {http://www.example.com}identityToIdentityRelationVO
+	 * @response.representation.mediaType application/xml, application/json
+	 * @response.representation.doc The relation to persist
+	 * @response.representation.example {@link org.olat.user.restapi.Examples#SAMPLE_IDENTITYTOIDENTITYRELATIONVO}
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The persisted relation
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_IDENTITYTOIDENTITYRELATIONVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @param relationRoleVo The relation to persist
+	 * @param request The HTTP request
+	 * @return The new persisted <code>relation</code>
+	 */
+	@POST
+	@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response postRelation(IdentityToIdentityRelationVO relationRoleVo) {
+		IdentityToIdentityRelation savedRelationRole = saveRelation(relationRoleVo);
+		if(savedRelationRole == null) {
+			return Response.serverError().status(Status.CONFLICT).build();
+		}
+		return Response.ok(IdentityToIdentityRelationVO.valueOf(savedRelationRole)).build();
+	}
+	
+	/**
+	 * Deletes a relation entity.
+	 * 
+	 * @response.representation.200.doc The relation
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @param relationKey The relation to delete
+	 * @return Ok if the relation was deleted
+	 */
+	@DELETE
+	@Path("{relationKey}")
+	public Response deleteRelation(@PathParam("relationKey") Long relationKey) {
+		IdentityToIdentityRelation relation = identityRelationshipService.getRelation(relationKey);
+		if(relation == null) {
+			return Response.serverError().status(Status.CONFLICT).build();
+		}
+		identityRelationshipService.removeRelation(relation.getSource(), relation.getTarget(), relation.getRole());
+		return Response.ok().build();
+	}
+	
+	private IdentityToIdentityRelation saveRelation(IdentityToIdentityRelationVO relationVo) {
+		IdentityToIdentityRelation relation;
+		if(relationVo.getKey() == null) {
+			Identity source = securityManager.loadIdentityByKey(relationVo.getIdentitySourceKey());
+			Identity target = securityManager.loadIdentityByKey(relationVo.getIdentityTargetKey());
+			RelationRole relationRole = identityRelationshipService.getRole(relationVo.getRelationRoleKey());
+			if(source == null || target == null || relationRole == null) {
+				throw new WebApplicationException(Status.NOT_FOUND);
+			}
+			relation = identityRelationshipService.addRelation(source, target, relationRole,
+					relationVo.getExternalId(), IdentityToIdentityRelationManagedFlag.toEnum(relationVo.getManagedFlagsString()));
+		} else {
+			relation = null;
+		}
+		return relation;
+	}
+	
+	private IdentityToIdentityRelationVO[] toArrayOfVOes(List<IdentityToIdentityRelation> relationRoles) {
+		int i=0;
+		IdentityToIdentityRelationVO[] roleVOes = new IdentityToIdentityRelationVO[relationRoles.size()];
+		for (IdentityToIdentityRelation relationRole:relationRoles) {
+			roleVOes[i++] = IdentityToIdentityRelationVO.valueOf(relationRole);
+		}
+		return roleVOes;
+	}
+
+}
diff --git a/src/main/java/org/olat/user/restapi/ManagedUserVO.java b/src/main/java/org/olat/user/restapi/ManagedUserVO.java
index 6ec69fd95f9c4e871792623135af5716f8356f55..8a4785159b11a99c1b7cbdac48b6babb20d78200 100644
--- a/src/main/java/org/olat/user/restapi/ManagedUserVO.java
+++ b/src/main/java/org/olat/user/restapi/ManagedUserVO.java
@@ -70,6 +70,11 @@ public class ManagedUserVO {
 		return "ManagedUserVO[key=" + key + ":name=" + login + ":externalId=" + externalId + "]";
 	}
 	
+	@Override
+	public int hashCode() {
+		return key == null ? 296578 : key.hashCode();
+	}
+
 	@Override
 	public boolean equals(Object obj) {
 		if(this == obj) {
diff --git a/src/main/java/org/olat/user/restapi/RelationRoleVO.java b/src/main/java/org/olat/user/restapi/RelationRoleVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..19dafa7c4644fdfe69e6388151ddf84b2b72a305
--- /dev/null
+++ b/src/main/java/org/olat/user/restapi/RelationRoleVO.java
@@ -0,0 +1,119 @@
+/**
+ * <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.restapi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.basesecurity.RelationRoleToRight;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "relationRoleVO")
+public class RelationRoleVO {
+	
+	private Long key;
+	private String role;
+	private String externalId;
+	private String externalRef;
+	private String managedFlags;
+	private List<String> rights;
+	
+	public RelationRoleVO() {
+		//
+	}
+	
+	public static RelationRoleVO valueOf(RelationRole role) {
+		RelationRoleVO roleVo = new RelationRoleVO();
+		roleVo.setKey(role.getKey());
+		roleVo.setRole(role.getRole());
+		roleVo.setExternalId(role.getExternalId());
+		roleVo.setExternalRef(role.getExternalRef());
+		roleVo.setManagedFlags(RelationRoleManagedFlag.toString(role.getManagedFlags()));
+
+		List<String> rightNames = new ArrayList<>();
+		Set<RelationRoleToRight> roleToRights = role.getRights();
+		for( RelationRoleToRight roleToRight:roleToRights) {
+			rightNames.add(roleToRight.getRight().getRight());
+		}
+		roleVo.setRights(rightNames);
+		return roleVo;
+	}
+
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	public String getRole() {
+		return role;
+	}
+
+	public void setRole(String role) {
+		this.role = role;
+	}
+
+	public String getExternalId() {
+		return externalId;
+	}
+
+	public void setExternalId(String externalId) {
+		this.externalId = externalId;
+	}
+
+	public String getExternalRef() {
+		return externalRef;
+	}
+
+	public void setExternalRef(String externalRef) {
+		this.externalRef = externalRef;
+	}
+
+	public String getManagedFlags() {
+		return managedFlags;
+	}
+
+	public void setManagedFlags(String managedFlags) {
+		this.managedFlags = managedFlags;
+	}
+
+	public List<String> getRights() {
+		return rights;
+	}
+
+	public void setRights(List<String> rights) {
+		this.rights = rights;
+	}
+}
diff --git a/src/main/java/org/olat/user/restapi/RelationRolesWebService.java b/src/main/java/org/olat/user/restapi/RelationRolesWebService.java
new file mode 100644
index 0000000000000000000000000000000000000000..873adc72e1a0734f7060966de3ce74ba3207efa4
--- /dev/null
+++ b/src/main/java/org/olat/user/restapi/RelationRolesWebService.java
@@ -0,0 +1,246 @@
+/**
+ * <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.restapi;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.core.id.Roles;
+import org.olat.restapi.security.RestSecurityHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Component
+@Path("users/relations")
+public class RelationRolesWebService {
+	
+	private static final String VERSION = "1.0";
+	
+	@Autowired
+	private IdentityRelationshipService identityRelationshipService;
+	
+	/**
+	 * The version of the Web Service
+	 * @response.representation.200.mediaType text/plain
+ 	 * @response.representation.200.doc The version of this specific Web Service
+ 	 * @response.representation.200.example 1.0
+	 * @return The version number
+	 */
+	@GET
+	@Path("version")
+	@Produces(MediaType.TEXT_PLAIN)
+	public Response getVersion() {
+		return Response.ok(VERSION).build();
+	}
+	
+	/**
+	 * List of relation roles.
+	 * 
+	 * @response.representation.200.qname {http://www.example.com}relationRoleVO
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The list of all relation roles in the OpenOLAT system
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_RELATIONROLEVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @param httpRequest The HTTP request
+	 * @return An array of organizations
+	 */
+	@GET
+	@Path("roles")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getRoles(@Context HttpServletRequest httpRequest) {
+		if(!isAdministrator(httpRequest)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		List<RelationRole> relationRoles = identityRelationshipService.getAvailableRoles();
+		RelationRoleVO[] relationRoleVOes = toArrayOfVOes(relationRoles);
+		return Response.ok(relationRoleVOes).build();
+	}
+	
+	/**
+	 * Creates and persists a new relation role entity.
+	 * 
+	 * @response.representation.qname {http://www.example.com}relationRoleVO
+	 * @response.representation.mediaType application/xml, application/json
+	 * @response.representation.doc The relation role to persist
+	 * @response.representation.example {@link org.olat.user.restapi.Examples#SAMPLE_RELATIONROLEVO}
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The persisted relation role
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_RELATIONROLEVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @response.representation.406.mediaType application/xml, application/json
+	 * @param relationRoleVo The relation role to persist
+	 * @param request The HTTP request
+	 * @return The new persisted <code>relation role</code>
+	 */
+	@PUT
+	@Path("roles")
+	@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response putRelationRole(RelationRoleVO relationRoleVo, @Context HttpServletRequest httpRequest) {
+		if(!isAdministrator(httpRequest)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		RelationRole savedRelationRole = saveRelationRole(relationRoleVo);
+		return Response.ok(RelationRoleVO.valueOf(savedRelationRole)).build();
+	}
+	
+	/**
+	 * Updates a relation role entity.
+	 * 
+	 * @response.representation.qname {http://www.example.com}relationRoleVO
+	 * @response.representation.mediaType application/xml, application/json
+	 * @response.representation.doc The relation role to update
+	 * @response.representation.example {@link org.olat.user.restapi.Examples#SAMPLE_RELATIONROLEVO}
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The merged relation role
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_RELATIONROLEVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @response.representation.406.mediaType application/xml, application/json
+	 * @param relationRoleVo The relation role to merge
+	 * @param request The HTTP request
+	 * @return The merged <code>relation role</code>
+	 */
+	@POST
+	@Path("roles")
+	@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response postRelationRole(RelationRoleVO relationRoleVo, @Context HttpServletRequest httpRequest) {
+		if(!isAdministrator(httpRequest)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		RelationRole savedRelationRole = saveRelationRole(relationRoleVo);
+		return Response.ok(RelationRoleVO.valueOf(savedRelationRole)).build();
+	}
+	
+	/**
+	 * Updates a relation role entity.
+	 * 
+	 * @response.representation.qname {http://www.example.com}relationRoleVO
+	 * @response.representation.mediaType application/xml, application/json
+	 * @response.representation.doc The relation role to update
+	 * @response.representation.example {@link org.olat.user.restapi.Examples#SAMPLE_RELATIONROLEVO}
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The merged relation role
+	 * @response.representation.200.example {@link org.olat.user.restapi.Examples#SAMPLE_RELATIONROLEVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @response.representation.406.mediaType application/xml, application/json
+	 * @param relationRoleVo The relation role to merge
+	 * @param request The HTTP request
+	 * @return The merged <code>relation role</code>
+	 */
+	@POST
+	@Path("roles/{relationRoleKey}")
+	@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response postRelationRole(RelationRoleVO relationRoleVo, @PathParam("relationRoleKey") Long relationRoleKey,
+			@Context HttpServletRequest httpRequest) {
+		if(relationRoleVo.getKey() == null) {
+			relationRoleVo.setKey(relationRoleKey);
+		} else if(!relationRoleKey.equals(relationRoleVo.getKey())) {
+			return Response.serverError().status(Status.CONFLICT).build();
+		}
+		return postRelationRole(relationRoleVo, httpRequest);
+	}
+	
+	private RelationRole saveRelationRole(RelationRoleVO relationRoleVo) {
+		List<RelationRight> rights;
+		if(relationRoleVo.getRights() != null && !relationRoleVo.getRights().isEmpty()) {
+			List<RelationRight> availableRights = identityRelationshipService.getAvailableRights();
+			rights = availableRights.stream()
+					.filter(r -> relationRoleVo.getRights().contains(r.getRight()))
+					.collect(Collectors.toList());
+		} else {
+			rights = Collections.emptyList();
+		}
+		
+		RelationRole relationRole;
+		if(relationRoleVo.getKey() == null) {
+			relationRole = identityRelationshipService.createRole(relationRoleVo.getRole(), relationRoleVo.getExternalId(), relationRoleVo.getExternalRef(),
+					RelationRoleManagedFlag.toEnum(relationRoleVo.getManagedFlags()), rights);
+		} else {
+			relationRole = identityRelationshipService.getRole(relationRoleVo.getKey());
+			relationRole.setRole(relationRoleVo.getRole());
+			relationRole.setExternalId(relationRoleVo.getExternalId());
+			relationRole.setExternalRef(relationRoleVo.getExternalRef());
+			relationRole.setManagedFlags(RelationRoleManagedFlag.toEnum(relationRoleVo.getManagedFlags()));
+			relationRole = identityRelationshipService.updateRole(relationRole, rights);
+		}
+		return relationRole;
+	}
+	
+	@DELETE
+	@Path("roles/{relationRoleKey}")
+	public Response deleteRelationRole(@PathParam("relationRoleKey") Long relationRoleKey, @Context HttpServletRequest httpRequest) {
+		if(!isAdministrator(httpRequest)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		RelationRole role = identityRelationshipService.getRole(relationRoleKey);
+		if(role == null) {
+			return Response.serverError().status(Status.NOT_FOUND).build();
+		}
+		identityRelationshipService.deleteRole(role);
+		return Response.serverError().status(Status.OK).build();
+	}
+	
+	private RelationRoleVO[] toArrayOfVOes(List<RelationRole> relationRoles) {
+		int i=0;
+		RelationRoleVO[] roleVOes = new RelationRoleVO[relationRoles.size()];
+		for (RelationRole relationRole:relationRoles) {
+			roleVOes[i++] = RelationRoleVO.valueOf(relationRole);
+		}
+		return roleVOes;
+	}
+	
+	private boolean isAdministrator(HttpServletRequest request) {
+		try {
+			Roles roles = RestSecurityHelper.getRoles(request);
+			return roles.isAdministrator() || roles.isSystemAdmin();
+		} catch (Exception e) {
+			return false;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/user/restapi/UserVO.java b/src/main/java/org/olat/user/restapi/UserVO.java
index 22dfcbb44d53804968ed405b2bba19e73ed4a73d..65c5aa6eb129c54e42ae4ec22f20ba3d8c5dd15c 100644
--- a/src/main/java/org/olat/user/restapi/UserVO.java
+++ b/src/main/java/org/olat/user/restapi/UserVO.java
@@ -146,6 +146,11 @@ public class UserVO {
 		return "UserVO[key=" + key + ":name=" + login + "]";
 	}
 	
+	@Override
+	public int hashCode() {
+		return key == null ? 7287 : key.hashCode();
+	}
+
 	@Override
 	public boolean equals(Object obj) {
 		if(this == obj) {
diff --git a/src/main/java/org/olat/user/restapi/UserVOFactory.java b/src/main/java/org/olat/user/restapi/UserVOFactory.java
index e24f54240d5549f2311307151a5738960d77fc51..cde19ec1701803364f70fc40587035978b8ca051 100644
--- a/src/main/java/org/olat/user/restapi/UserVOFactory.java
+++ b/src/main/java/org/olat/user/restapi/UserVOFactory.java
@@ -40,7 +40,6 @@ import org.olat.core.id.User;
 import org.olat.core.id.UserConstants;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
-import org.olat.core.util.FileUtils;
 import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
@@ -98,10 +97,8 @@ public class UserVOFactory {
 		if(withPortrait) {
 			File portrait = CoreSpringFactory.getImpl(DisplayPortraitManager.class).getSmallPortrait(identity.getName());
 			if(portrait != null && portrait.exists()) {
-				try {
-					InputStream input = new FileInputStream(portrait);
+				try(InputStream input = new FileInputStream(portrait)) {
 					byte[] datas = IOUtils.toByteArray(input);
-					FileUtils.closeSafely(input);
 					byte[] data64 = Base64.encodeBase64(datas);
 					userVO.setPortrait(new String(data64, "UTF8"));
 				} catch (IOException e) {
@@ -110,7 +107,6 @@ public class UserVOFactory {
 			}
 		}
 		
-		
 		if(allProperties) {
 			UserManager um = UserManager.getInstance();
 			HomePageConfig hpc = isAdmin ? null : CoreSpringFactory.getImpl(HomePageConfigManager.class).loadConfigFor(identity.getName());
diff --git a/src/main/java/org/olat/user/restapi/UserWebService.java b/src/main/java/org/olat/user/restapi/UserWebService.java
index 82f1406cacee38d2999e54a0017150c409e1658d..1ceebfc9738727b44b73f515bef106c5bc60b84f 100644
--- a/src/main/java/org/olat/user/restapi/UserWebService.java
+++ b/src/main/java/org/olat/user/restapi/UserWebService.java
@@ -543,7 +543,19 @@ public class UserWebService {
 		userManager.updateUserFromIdentity(identity);
 		return Response.ok(new PreferencesVO(prefs)).build();
 	}
-	
+
+	@Path("{identityKey}/relations")
+	public IdentityToIdentityRelationsWebService getRelations(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {
+		boolean isUserManager = isUserManagerOf(identityKey, request);
+		if(!isUserManager) {
+			throw new WebApplicationException(Status.FORBIDDEN);
+		}
+		Identity identity = securityManager.loadIdentityByKey(identityKey, false);
+		if(identity == null) {
+			throw new WebApplicationException(Status.NOT_FOUND);
+		}
+		return new IdentityToIdentityRelationsWebService(identity);
+	}
 
 	/**
 	 * Retrieves an user given its unique key identifier
diff --git a/src/main/java/org/olat/user/ui/identity/ConfirmRemoveRelationController.java b/src/main/java/org/olat/user/ui/identity/ConfirmRemoveRelationController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0867e9feafae142443dbce53841673548f8946d3
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/ConfirmRemoveRelationController.java
@@ -0,0 +1,106 @@
+/**
+ * <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.identity;
+
+import java.util.List;
+
+import org.olat.basesecurity.IdentityRef;
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.model.IdentityRefImpl;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+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.id.Identity;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ConfirmRemoveRelationController extends FormBasicController {
+	
+	private final Identity editedIdentity;
+	private List<IdentityRelationRow> relationsToRemove;
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private IdentityRelationshipService identityRelationsService;
+	
+	public ConfirmRemoveRelationController(UserRequest ureq, WindowControl wControl,
+			Identity editedIdentity, List<IdentityRelationRow> relationsToRemove) {
+		super(ureq, wControl, "confirm_remove");
+		this.editedIdentity = editedIdentity;
+		this.relationsToRemove = relationsToRemove;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			String messageI18n = relationsToRemove.size() <= 1
+					? "confirm.remove.relation.text.singular" : "confirm.remove.relation.text.plural";
+			String message = translate(messageI18n, new String[] { Integer.toString(relationsToRemove.size())} );
+			layoutCont.contextPut("msg", message);
+		}
+
+		uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("remove", formLayout);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		int count = 0;
+		for(IdentityRelationRow relationToRemove:relationsToRemove) {
+			RelationRole relationRole = relationToRemove.getRelationRole();
+			IdentityRef relation = new IdentityRefImpl(relationToRemove.getIdentityKey());
+			if(relationToRemove.isAsSource()) {
+				identityRelationsService.removeRelation(relation, editedIdentity, relationRole);
+			} else {
+				identityRelationsService.removeRelation(editedIdentity, relation, relationRole);
+			}
+			if(++count % 20 == 0) {
+				dbInstance.commit();
+			}
+		}
+		dbInstance.commit();
+		
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessedIdentityCourseRow.java b/src/main/java/org/olat/user/ui/identity/IdentityRelationRow.java
similarity index 50%
rename from src/main/java/org/olat/course/assessment/ui/tool/AssessedIdentityCourseRow.java
rename to src/main/java/org/olat/user/ui/identity/IdentityRelationRow.java
index 0bf6b7892325ccc51b87d68918bd6c449ea7b071..cc5526f09f5ed20a598729af28a7a374c20d4c75 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/AssessedIdentityCourseRow.java
+++ b/src/main/java/org/olat/user/ui/identity/IdentityRelationRow.java
@@ -17,52 +17,57 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.course.assessment.ui.tool;
+package org.olat.user.ui.identity;
 
-import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 
+import org.olat.basesecurity.IdentityToIdentityRelationManagedFlag;
+import org.olat.basesecurity.RelationRole;
 import org.olat.core.id.Identity;
-import org.olat.modules.coach.model.EfficiencyStatementEntry;
 import org.olat.user.UserPropertiesRow;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
 
 /**
  * 
- * Initial date: 07.10.2015<br>
+ * Initial date: 30 janv. 2019<br>
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class AssessedIdentityCourseRow extends UserPropertiesRow {
-	
-	private final Float score;
-	private final Boolean passed;
-	private final Date lastModified;
+public class IdentityRelationRow extends UserPropertiesRow {
+
+	private final RelationRole relationRole;
+	private final Long relationKey;
+	private final boolean asSource;
+	private final IdentityToIdentityRelationManagedFlag[] managedFlags;
 	
-	public AssessedIdentityCourseRow(Identity identity, EfficiencyStatementEntry entry, List<UserPropertyHandler> userPropertyHandlers, Locale locale) {
+	public IdentityRelationRow(boolean asSource, Long relationKey, Identity identity, RelationRole relationRole,
+			IdentityToIdentityRelationManagedFlag[] managedFlags, List<UserPropertyHandler> userPropertyHandlers,
+			Locale locale) {
 		super(identity, userPropertyHandlers, locale);
-		
-		if(entry != null && entry.getUserEfficencyStatement() != null) {
-			score = entry.getUserEfficencyStatement().getScore();
-			passed = entry.getUserEfficencyStatement().getPassed();
-			lastModified = entry.getUserEfficencyStatement().getLastModified();
-		} else {
-			score = null;
-			passed = null;
-			lastModified = null;
-		}
+		this.relationRole = relationRole;
+		this.relationKey = relationKey;
+		this.managedFlags = managedFlags;
+		this.asSource = asSource;
 	}
-
-	public Float getScore() {
-		return score;
+	
+	public boolean isAsSource() {
+		return asSource;
 	}
-
-	public Boolean getPassed() {
-		return passed;
+	
+	public  Long getRelationKey() {
+		return relationKey;
 	}
-
-	public Date getLastModified() {
-		return lastModified;
+	
+	public String getRelationRoleName() {
+		return relationRole.getRole();
+	}
+	
+	public RelationRole getRelationRole() {
+		return relationRole;
+	}
+	
+	public IdentityToIdentityRelationManagedFlag[] getManagedFlags() {
+		return managedFlags;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/olat/user/ui/identity/ImportRelation_1a_Step.java b/src/main/java/org/olat/user/ui/identity/ImportRelation_1a_Step.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d5f643671e4bc33550362bd5848d5e2ea5a8ec2
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/ImportRelation_1a_Step.java
@@ -0,0 +1,54 @@
+/**
+ * <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.identity;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.BasicStep;
+import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig;
+import org.olat.core.gui.control.generic.wizard.StepFormController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.course.member.wizard.ImportMemberByUsernamesController;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ImportRelation_1a_Step extends BasicStep {
+	
+	public ImportRelation_1a_Step(UserRequest ureq) {
+		super(ureq);
+		setNextStep(new ImportRelation_2_Step(ureq));
+		setI18nTitleAndDescr("add.import.title", "add.import.title");
+	}
+
+	@Override
+	public PrevNextFinishConfig getInitialPrevNextFinishConfig() {
+		return new PrevNextFinishConfig(false, true, false);
+	}
+
+	@Override
+	public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form form) {
+		return new ImportMemberByUsernamesController(ureq, wControl, form, runContext);
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/identity/ImportRelation_1b_Step.java b/src/main/java/org/olat/user/ui/identity/ImportRelation_1b_Step.java
new file mode 100644
index 0000000000000000000000000000000000000000..fac79f3c4b6d2fc064bc4971d974f2fdd8409da9
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/ImportRelation_1b_Step.java
@@ -0,0 +1,54 @@
+/**
+ * <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.identity;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.BasicStep;
+import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig;
+import org.olat.core.gui.control.generic.wizard.StepFormController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.course.member.wizard.ImportMemberBySearchController;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ImportRelation_1b_Step extends BasicStep {
+	
+	public ImportRelation_1b_Step(UserRequest ureq) {
+		super(ureq);
+		setNextStep(new ImportRelation_2_Step(ureq));
+		setI18nTitleAndDescr("add.import.title", "add.import.title");
+	}
+
+	@Override
+	public PrevNextFinishConfig getInitialPrevNextFinishConfig() {
+		return new PrevNextFinishConfig(false, true, false);
+	}
+
+	@Override
+	public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form form) {
+		return new ImportMemberBySearchController(ureq, wControl, form, runContext);
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/identity/ImportRelation_2_Step.java b/src/main/java/org/olat/user/ui/identity/ImportRelation_2_Step.java
new file mode 100644
index 0000000000000000000000000000000000000000..7accddf146d755afe6b5fc1fc3d4ce3bab8f7d7b
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/ImportRelation_2_Step.java
@@ -0,0 +1,55 @@
+/**
+ * <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.identity;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.BasicStep;
+import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig;
+import org.olat.core.gui.control.generic.wizard.StepFormController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.course.member.wizard.ImportMemberOverviewIdentitiesController;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ImportRelation_2_Step extends BasicStep {
+	
+	public ImportRelation_2_Step(UserRequest ureq) {
+		super(ureq);
+
+		setNextStep(new ImportRelation_3_Step(ureq));
+		setI18nTitleAndDescr("add.confirm.title", "add.confirm.title");
+	}
+
+	@Override
+	public PrevNextFinishConfig getInitialPrevNextFinishConfig() {
+		return new PrevNextFinishConfig(true, true, false);
+	}
+
+	@Override
+	public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form form) {
+		return new ImportMemberOverviewIdentitiesController(ureq, wControl, form, runContext);
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/identity/ImportRelation_3_Step.java b/src/main/java/org/olat/user/ui/identity/ImportRelation_3_Step.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c38b1fb2ddf7b8ae1a35daf2bf24fcb2dfb0dc2
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/ImportRelation_3_Step.java
@@ -0,0 +1,53 @@
+/**
+ * <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.identity;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.BasicStep;
+import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig;
+import org.olat.core.gui.control.generic.wizard.StepFormController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ImportRelation_3_Step extends BasicStep {
+	
+	public ImportRelation_3_Step(UserRequest ureq) {
+		super(ureq);
+		setNextStep(NOSTEP);
+		setI18nTitleAndDescr("add.roles.title", "add.roles.title");
+	}
+
+	@Override
+	public PrevNextFinishConfig getInitialPrevNextFinishConfig() {
+		return new PrevNextFinishConfig(false, true, true);
+	}
+
+	@Override
+	public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form form) {
+		return new RelationRolesController(ureq, wControl, form, runContext);
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/identity/RelationRolesController.java b/src/main/java/org/olat/user/ui/identity/RelationRolesController.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fc3fd3f634e8cf5c3938724075e77a0df8e10a9
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/RelationRolesController.java
@@ -0,0 +1,111 @@
+/**
+ * <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.identity;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.RelationRole;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
+import org.olat.core.gui.control.generic.wizard.StepsEvent;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.core.util.Util;
+import org.olat.user.UserModule;
+import org.olat.user.ui.role.RelationRolesAndRightsUIFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRolesController extends StepFormBasicController {
+	
+	private MultipleSelectionElement relationRoleEl;
+	
+	private List<RelationRole> availableRoles;
+	
+	@Autowired
+	private IdentityRelationshipService identityRelationsService;
+	
+	public RelationRolesController(UserRequest ureq, WindowControl wControl, Form rootForm,
+			StepsRunContext runContext) {
+		super(ureq, wControl, rootForm, runContext, LAYOUT_DEFAULT, null);
+		setTranslator(Util.createPackageTranslator(UserModule.class, getLocale(), getTranslator()));
+		availableRoles = identityRelationsService.getAvailableRoles();
+		initForm (ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String[] roleKeys = new String[availableRoles.size()];
+		String[] roleValues = new String[availableRoles.size()];
+		
+		for(int i=availableRoles.size(); i-->0; ) {
+			roleKeys[i] = availableRoles.get(i).getKey().toString();
+			String translatedRole = translate(RelationRolesAndRightsUIFactory.TRANS_ROLE_PREFIX + availableRoles.get(i).getKey());
+			if(translatedRole.length() > 256 || translatedRole.startsWith(RelationRolesAndRightsUIFactory.TRANS_ROLE_PREFIX)) {
+				translatedRole = availableRoles.get(i).getRole();
+			}
+			roleValues[i] = translatedRole;
+		}
+		relationRoleEl = uifactory.addCheckboxesVertical("relation.roles", formLayout, roleKeys, roleValues, 2);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		relationRoleEl.clearError();
+		if(relationRoleEl.getSelectedKeys().isEmpty()) {
+			relationRoleEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
+		return allOk;
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		Set<Long> selectedKeys = relationRoleEl.getSelectedKeys().stream()
+				.map(Long::valueOf)
+				.collect(Collectors.toSet());
+		List<RelationRole> selectedRoles = availableRoles.stream()
+				.filter(role -> selectedKeys.contains(role.getKey()))
+				.collect(Collectors.toList());
+
+		addToRunContext("relationRoles", selectedRoles);
+		fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/identity/UserRelationsController.java b/src/main/java/org/olat/user/ui/identity/UserRelationsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..98a38593faa4acb7e21a3f435556c0afd2486463
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/UserRelationsController.java
@@ -0,0 +1,345 @@
+/**
+ * <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.identity;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.olat.basesecurity.BaseSecurityModule;
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.IdentityToIdentityRelationManagedFlag;
+import org.olat.basesecurity.RelationRole;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.BooleanCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
+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.CloseableModalController;
+import org.olat.core.gui.control.generic.wizard.Step;
+import org.olat.core.gui.control.generic.wizard.StepRunnerCallback;
+import org.olat.core.gui.control.generic.wizard.StepsMainRunController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.core.id.Identity;
+import org.olat.course.assessment.ui.tool.AssessmentToolConstants;
+import org.olat.user.UserManager;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+import org.olat.user.ui.identity.UserRelationsTableModel.RelationCols;
+import org.olat.user.ui.role.ManagedCellRenderer;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class UserRelationsController extends FormBasicController {
+	
+	public static final int USER_PROPS_OFFSET = 500;
+	public static final String usageIdentifyer = UserRelationsController.class.getCanonicalName();
+
+	private FormLink addAsSourceButton;
+	private FormLink importAsSourceButton;
+	private FormLink addAsTargetButton;
+	private FormLink removeButton;
+	
+	private FlexiTableElement tableEl;
+	private UserRelationsTableModel tableModel;
+	
+	private CloseableModalController cmc;
+	private StepsMainRunController importRelationsWizard;
+	private ConfirmRemoveRelationController confirmRemoveRelationsCtrl;
+	
+	private final boolean asSource;
+	private final boolean canModify;
+	private final Identity editedIdentity;
+	private final boolean isAdministrativeUser;
+	private final List<UserPropertyHandler> userPropertyHandlers;
+
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private BaseSecurityModule securityModule;
+	@Autowired
+	private IdentityRelationshipService identityRelationsService;
+	
+	public UserRelationsController(UserRequest ureq, WindowControl wControl, Identity editedIdentity,
+			boolean source, boolean canModify) {
+		super(ureq, wControl, "relations");
+		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
+		this.asSource = source;
+		this.canModify = canModify;
+		this.editedIdentity = editedIdentity;
+		
+		isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles());
+		userPropertyHandlers = userManager.getUserPropertyHandlersFor(usageIdentifyer, isAdministrativeUser);
+		
+		initForm(ureq);
+		loadModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(canModify) {
+			if(asSource) {
+				importAsSourceButton = uifactory.addFormLink("import.as.source", formLayout, Link.BUTTON);
+				importAsSourceButton.setIconLeftCSS("o_icon o_icon-fw o_icon_import");
+				
+				addAsSourceButton = uifactory.addFormLink("add.as.source", formLayout, Link.BUTTON);
+				addAsSourceButton.setIconLeftCSS("o_icon o_icon-fw o_icon_add_member");
+			} else {
+				addAsTargetButton = uifactory.addFormLink("add.as.target", formLayout, Link.BUTTON);
+				addAsTargetButton.setIconLeftCSS("o_icon o_icon-fw o_icon_add_member");
+			}
+			
+			removeButton = uifactory.addFormLink("remove", formLayout, Link.BUTTON);
+		}
+		
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, RelationCols.key));
+		if(securityModule.isRelationRoleManaged()) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RelationCols.managed, new ManagedCellRenderer()));
+		}
+		//add the table
+		if(isAdministrativeUser) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RelationCols.username));
+		}
+		
+		int colIndex = USER_PROPS_OFFSET;
+		for (int i = 0; i < userPropertyHandlers.size(); i++) {
+			UserPropertyHandler userPropertyHandler	= userPropertyHandlers.get(i);
+			boolean visible = UserManager.getInstance().isMandatoryUserProperty(AssessmentToolConstants.usageIdentifyer , userPropertyHandler);
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex, null, true, "userProp-" + colIndex));
+			colIndex++;
+		}
+
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RelationCols.role));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("remove", RelationCols.remove.ordinal(), "remove",
+				new BooleanCellRenderer(
+						new StaticFlexiCellRenderer(translate("remove"), "remove"), null)));
+		
+		tableModel = new UserRelationsTableModel(columnsModel, getLocale());
+		tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 24, false, getTranslator(), formLayout);
+		tableEl.setExportEnabled(true);
+		tableEl.setMultiSelect(true);
+		tableEl.setAndLoadPersistedPreferences(ureq, "relations-u-to-u");
+	}
+	
+	protected void loadModel() {
+		List<IdentityToIdentityRelation> relations = asSource ? identityRelationsService.getRelationsAsSource(editedIdentity)
+				: identityRelationsService.getRelationsAsTarget(editedIdentity);
+		List<IdentityRelationRow> rows = relations.stream()
+				.map(rel -> new IdentityRelationRow(!asSource, rel.getKey(), asSource ? rel.getTarget() : rel.getSource(),
+						rel.getRole(), rel.getManagedFlags(), userPropertyHandlers, getLocale()))
+				.collect(Collectors.toList());
+		tableModel.setObjects(rows);
+		tableEl.reset(true, true, true);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(source == importRelationsWizard) {
+			if(event == Event.CANCELLED_EVENT || event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				getWindowControl().pop();
+				cleanUp();
+				if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+					loadModel();
+				}
+			}
+		} else if(confirmRemoveRelationsCtrl == source) {
+			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				loadModel();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		}
+		
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(confirmRemoveRelationsCtrl);
+		removeControllerListener(importRelationsWizard);
+		removeAsListenerAndDispose(cmc);
+		confirmRemoveRelationsCtrl = null;
+		importRelationsWizard = null;
+		cmc = null;
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(importAsSourceButton == source) {
+			doImportRelationsAsSource(ureq);
+		} else if(addAsSourceButton == source) {
+			doAddRelationsAsSource(ureq);
+		} else if(addAsTargetButton == source) {
+			doAddRelationsAsTarget(ureq);
+		} else if(removeButton == source) {
+			doConfirmRemove(ureq);
+		} else if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				if("remove".equals(se.getCommand())) {
+					doConfirmRemove(ureq, tableModel.getObject(se.getIndex()));
+				}
+			}
+		}
+		
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	private void doConfirmRemove(UserRequest ureq, IdentityRelationRow row) {
+		if(confirmRemoveRelationsCtrl != null) return;
+		
+		List<IdentityRelationRow> relationsToRemove = new ArrayList<>(1);
+		if(canModify && !IdentityToIdentityRelationManagedFlag.isManaged(row.getManagedFlags(), IdentityToIdentityRelationManagedFlag.delete)) {
+			relationsToRemove.add(row);
+		}
+		doConfirmRemove(ureq, relationsToRemove);
+	}
+	
+	private void doConfirmRemove(UserRequest ureq) {
+		if(confirmRemoveRelationsCtrl != null) return;
+		
+		Set<Integer> selectedIndex = tableEl.getMultiSelectedIndex();
+		List<IdentityRelationRow> relationsToRemove = new ArrayList<>(selectedIndex.size());
+		for(Integer index:selectedIndex) {
+			IdentityRelationRow row = tableModel.getObject(index.intValue());
+			if(canModify && !IdentityToIdentityRelationManagedFlag.isManaged(row.getManagedFlags(), IdentityToIdentityRelationManagedFlag.delete)) {
+				relationsToRemove.add(row);
+			}
+		}
+		doConfirmRemove(ureq, relationsToRemove);
+	}
+	
+	private void doConfirmRemove(UserRequest ureq, List<IdentityRelationRow> relationsToRemove) {	
+		if(relationsToRemove.isEmpty()) {
+			showWarning("warning.at.least.one.relation");
+		} else {
+			confirmRemoveRelationsCtrl = new ConfirmRemoveRelationController(ureq, getWindowControl(), editedIdentity, relationsToRemove);
+			listenTo(confirmRemoveRelationsCtrl);
+			String title = translate("confirm.remove.relation.title");
+			cmc = new CloseableModalController(getWindowControl(), "close", confirmRemoveRelationsCtrl.getInitialComponent(), true, title);
+			listenTo(cmc);
+			cmc.activate();
+		}
+	}
+	
+	private void doAddRelationsAsTarget(UserRequest ureq) {
+		removeAsListenerAndDispose(importRelationsWizard);
+
+		Step start = new ImportRelation_1b_Step(ureq);
+		StepRunnerCallback finish = (uureq, wControl, runContext) -> {
+			doAddRelationsAsTarget(runContext);
+			if(runContext.containsKey("notFounds")) {
+				showWarning("user.notfound", runContext.get("notFounds").toString());
+			}
+			return StepsMainRunController.DONE_MODIFIED;
+		};
+		
+		importRelationsWizard = new StepsMainRunController(ureq, getWindowControl(), start, finish, null,
+				translate("import.relations"), "o_sel_import_realtions_wizard");
+		listenTo(importRelationsWizard);
+		getWindowControl().pushAsModalDialog(importRelationsWizard.getInitialComponent());
+	}
+	
+	private void doAddRelationsAsTarget(StepsRunContext runContext) {
+		@SuppressWarnings("unchecked")
+		List<Identity> relations = (List<Identity>)runContext.get("members");
+		@SuppressWarnings("unchecked")
+		List<RelationRole> relationRoles = (List<RelationRole>)runContext.get("relationRoles");
+		for(Identity source:relations) {
+			identityRelationsService.addRelations(source, editedIdentity, relationRoles);
+		}
+	}
+	
+	private void doAddRelationsAsSource(UserRequest ureq) {
+		removeAsListenerAndDispose(importRelationsWizard);
+
+		Step start = new ImportRelation_1b_Step(ureq);
+		StepRunnerCallback finish = (uureq, wControl, runContext) -> {
+			doAddRelationsAsSource(runContext);
+			if(runContext.containsKey("notFounds")) {
+				showWarning("user.notfound", runContext.get("notFounds").toString());
+			}
+			return StepsMainRunController.DONE_MODIFIED;
+		};
+		
+		importRelationsWizard = new StepsMainRunController(ureq, getWindowControl(), start, finish, null,
+				translate("import.relations"), "o_sel_import_realtions_wizard");
+		listenTo(importRelationsWizard);
+		getWindowControl().pushAsModalDialog(importRelationsWizard.getInitialComponent());
+	}
+	
+	private void doImportRelationsAsSource(UserRequest ureq) {
+		removeAsListenerAndDispose(importRelationsWizard);
+
+		Step start = new ImportRelation_1a_Step(ureq);
+		StepRunnerCallback finish = (uureq, wControl, runContext) -> {
+			doAddRelationsAsSource(runContext);
+			if(runContext.containsKey("notFounds")) {
+				showWarning("user.notfound", runContext.get("notFounds").toString());
+			}
+			return StepsMainRunController.DONE_MODIFIED;
+		};
+		
+		importRelationsWizard = new StepsMainRunController(ureq, getWindowControl(), start, finish, null,
+				translate("import.relations"), "o_sel_import_realtions_wizard");
+		listenTo(importRelationsWizard);
+		getWindowControl().pushAsModalDialog(importRelationsWizard.getInitialComponent());
+	}
+	
+	private void doAddRelationsAsSource(StepsRunContext runContext) {
+		@SuppressWarnings("unchecked")
+		List<Identity> relations = (List<Identity>)runContext.get("members");
+		@SuppressWarnings("unchecked")
+		List<RelationRole> relationRoles = (List<RelationRole>)runContext.get("relationRoles");
+		for(Identity target:relations) {
+			identityRelationsService.addRelations(editedIdentity, target, relationRoles);
+		}
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/identity/UserRelationsOverviewController.java b/src/main/java/org/olat/user/ui/identity/UserRelationsOverviewController.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5c8df792585f22b73bb4a30d8ec9956ff2cafc5
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/UserRelationsOverviewController.java
@@ -0,0 +1,111 @@
+/**
+ * <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.identity;
+
+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.segmentedview.SegmentViewComponent;
+import org.olat.core.gui.components.segmentedview.SegmentViewEvent;
+import org.olat.core.gui.components.segmentedview.SegmentViewFactory;
+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.core.id.Identity;
+import org.olat.core.util.Util;
+import org.olat.user.UserInfoMainController;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class UserRelationsOverviewController extends BasicController {
+	
+	private final Link asSourceLink;
+	private final Link asTargetLink;
+	private final VelocityContainer mainVC;
+	private final SegmentViewComponent segmentView;
+	
+	private UserRelationsController relationsAsSourceCtrl;
+	private UserRelationsController relationsAsTargetCtrl;
+	
+	private final boolean canModify;
+	private final Identity editedIdentity;
+	
+	public UserRelationsOverviewController(UserRequest ureq, WindowControl wControl, Identity editedIdentity, boolean canModify) {
+		super(ureq, wControl, Util.createPackageTranslator(UserInfoMainController.class, ureq.getLocale()));
+		this.canModify = canModify;
+		this.editedIdentity = editedIdentity;
+		
+		mainVC = createVelocityContainer("relations_overview");
+
+		segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
+		
+		asSourceLink = LinkFactory.createLink("relation.overview.as.source", mainVC, this);
+		segmentView.addSegment(asSourceLink, true);
+		doOpenRelationsAsSource(ureq);
+		asTargetLink = LinkFactory.createLink("relation.overview.as.target", mainVC, this);
+		segmentView.addSegment(asTargetLink, false);
+
+		putInitialPanel(mainVC);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if(source == segmentView) {
+			if(event instanceof SegmentViewEvent) {
+				SegmentViewEvent sve = (SegmentViewEvent)event;
+				String segmentCName = sve.getComponentName();
+				Component clickedLink = mainVC.getComponent(segmentCName);
+				if(clickedLink == asSourceLink) {
+					doOpenRelationsAsSource(ureq);
+				} else if(clickedLink == asTargetLink) {
+					doOpenRelationsAsTarget(ureq);
+				}
+			}
+		}
+	}
+	
+	private void doOpenRelationsAsSource(UserRequest ureq) {
+		if(relationsAsSourceCtrl == null) {
+			relationsAsSourceCtrl = new UserRelationsController(ureq, getWindowControl(), editedIdentity, true, canModify);
+			listenTo(relationsAsSourceCtrl);
+		}
+		mainVC.put("segmentCmp", relationsAsSourceCtrl.getInitialComponent());
+	}
+	
+	private void doOpenRelationsAsTarget(UserRequest ureq) {
+		if(relationsAsTargetCtrl == null) {
+			relationsAsTargetCtrl = new UserRelationsController(ureq, getWindowControl(), editedIdentity, false, canModify);
+			listenTo(relationsAsTargetCtrl);
+		}
+		mainVC.put("segmentCmp", relationsAsTargetCtrl.getInitialComponent());
+	}
+
+}
diff --git a/src/main/java/org/olat/user/ui/identity/UserRelationsTableModel.java b/src/main/java/org/olat/user/ui/identity/UserRelationsTableModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..91359156e1b098db21a15fa3cdaa29a18ac1ac9c
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/UserRelationsTableModel.java
@@ -0,0 +1,115 @@
+/**
+ * <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.identity;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.olat.basesecurity.IdentityToIdentityRelationManagedFlag;
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
+
+/**
+ * 
+ * Initial date: 30 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class UserRelationsTableModel extends DefaultFlexiTableDataModel<IdentityRelationRow>
+implements SortableFlexiTableDataModel<IdentityRelationRow> {
+	
+	private final Locale locale;
+	
+	public UserRelationsTableModel(FlexiTableColumnModel columnModel, Locale locale) {
+		super(columnModel);
+		this.locale = locale;
+	}
+
+	@Override
+	public void sort(SortKey orderBy) {
+		if(orderBy != null) {
+			List<IdentityRelationRow> rows = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort();
+			super.setObjects(rows);
+		}
+	}
+	
+	@Override
+	public Object getValueAt(int row, int col) {
+		IdentityRelationRow relation = getObject(row);
+		return getValueAt(relation, col);
+	}
+
+	@Override
+	public Object getValueAt(IdentityRelationRow row, int col) {
+		if(col >= 0 && col < RelationCols.values().length) {
+			switch(RelationCols.values()[col]) {
+				case key: return row.getRelationKey();
+				case managed: return row.getManagedFlags().length > 0;
+				case username: return row.getIdentityName();
+				case role: return row.getRelationRoleName();
+				case remove: return !IdentityToIdentityRelationManagedFlag
+						.isManaged(row.getManagedFlags(), IdentityToIdentityRelationManagedFlag.delete);
+				default: return "ERROR";
+			}
+		}
+		
+		int pos = col - UserRelationsController.USER_PROPS_OFFSET;
+		return row.getIdentityProp(pos);
+	}
+
+	@Override
+	public UserRelationsTableModel createCopyWithEmptyList() {
+		return new UserRelationsTableModel(getTableColumnModel(), locale);
+	}
+	
+	public enum RelationCols implements FlexiSortableColumnDef {
+		
+		key("table.header.key"),
+		managed("table.header.managed"),
+		username("table.header.username"),
+		role("table.header.role"),
+		remove("remove");
+		
+		private final String i18nHeaderKey;
+		
+		private RelationCols(String i18nHeaderKey) {
+			this.i18nHeaderKey = i18nHeaderKey;
+		}
+
+		@Override
+		public String i18nHeaderKey() {
+			return i18nHeaderKey;
+		}
+
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/identity/_content/confirm_remove.html b/src/main/java/org/olat/user/ui/identity/_content/confirm_remove.html
new file mode 100644
index 0000000000000000000000000000000000000000..ea1b8bb21aa54aa769bc64d62c9ae696f706d8e8
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/_content/confirm_remove.html
@@ -0,0 +1,5 @@
+<div class="o_warning">$msg</div>
+<div class="o_button_group">
+	$r.render("cancel")
+	$r.render("remove")
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/identity/_content/relations.html b/src/main/java/org/olat/user/ui/identity/_content/relations.html
new file mode 100644
index 0000000000000000000000000000000000000000..5812ce1beb644d12ce2e5ab02dfa15d9db93b432
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/_content/relations.html
@@ -0,0 +1,23 @@
+#if($r.available("add.as.source") || $r.available("import.as.source") || $r.available("add.as.target"))
+<div class="o_button_group o_button_group_right">
+	#if($r.available("add.as.source") || $r.available("import.as.source"))
+	<div class="btn-group">
+	#if($r.available("add.as.source"))
+		$r.render("add.as.source")
+	#end
+	#if($r.available("import.as.source"))
+		$r.render("import.as.source")
+	#end
+	</div>
+	#end
+	#if($r.available("add.as.target"))
+		$r.render("add.as.target")
+	#end
+</div>
+#end
+$r.render("table")
+#if($r.available("remove"))
+<div class="o_button_group">
+	$r.render("remove")
+</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/identity/_content/relations_overview.html b/src/main/java/org/olat/user/ui/identity/_content/relations_overview.html
new file mode 100644
index 0000000000000000000000000000000000000000..e2f8e68cdf1672fb5aa04f7221e11d63aff9a967
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/_content/relations_overview.html
@@ -0,0 +1,6 @@
+<div class="clearfix">
+	$r.render("segments")<br>
+	#if($r.available("segmentCmp"))
+		$r.render("segmentCmp")
+	#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/identity/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/ui/identity/_i18n/LocalStrings_de.properties
new file mode 100644
index 0000000000000000000000000000000000000000..ef7f4ff74c678c622a9d0690001538dd92ee04ed
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/_i18n/LocalStrings_de.properties
@@ -0,0 +1,23 @@
+#Wed Jul 18 09:11:07 CEST 2018
+add.as.source=Neue Beziehung hinzuf\u00FCgen
+add.as.target=Neue Beziehung hinzuf\u00FCgen
+add.import.title=Benutzer suchen
+add.confirm.title=\u00DCberpr\u00FCfen
+add.roles.title=Rollen
+confirm.remove.relation.title=Beziehungen entfernen
+confirm.remove.relation.text.singular=Wollen Sie wirklich <strong>{0} Beziehung</strong> entfernen?
+confirm.remove.relation.text.plural=Wollen Sie wirklich <strong>{0} Beziehungen</strong> entfernen?
+form.addrelations=Benutzername, $org.olat.user.propertyhandlers\:table.name.email oder $org.olat.user.propertyhandlers\:table.name.institutionalUserIdentifier
+form.names.example=test01<br/>author02<br/>$org.olat.user.propertyhandlers\:import.example.institutionalUserIdentifier
+import.as.source=Importieren
+import.relations=Beziehungen importieren
+relation.overview.as.source=Als Quelle
+relation.overview.as.target=Als Ziel
+relation.roles=Rollen
+remove=Entfernen
+table.header.key=ID
+table.header.name=Name
+table.header.managed=<i class='o_icon o_icon_managed'> </i>
+table.header.role=Rolle
+table.header.username=Benutzername
+warning.at.least.one.relation=Sie m\u00FCssen mindestens eine Beziehung w\u00E4hlen.
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/identity/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/ui/identity/_i18n/LocalStrings_en.properties
new file mode 100644
index 0000000000000000000000000000000000000000..2024da3c0cb8298401a87ccec5e47368090cd99a
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/identity/_i18n/LocalStrings_en.properties
@@ -0,0 +1,21 @@
+#Wed Jul 18 09:11:07 CEST 2018
+add.as.source=Add new relation
+add.as.target=Add new relation
+add.import.title=Search for users
+add.confirm.title=Review
+add.roles.title=Roles
+confirm.remove.relation.title=Remove relations
+confirm.remove.relation.text.singular=Do you really want to remove <strong>{0} relation</strong>?
+confirm.remove.relation.text.plural=Do you really want to remove <strong>{0} relations</strong>?
+form.addrelations=User names, $org.olat.user.propertyhandlers\:table.name.email oder $org.olat.user.propertyhandlers\:table.name.institutionalUserIdentifier
+form.names.example=test01<br/>author02<br/>$org.olat.user.propertyhandlers\:import.example.institutionalUserIdentifier
+import.as.source=Import
+import.relations=Import relations
+relation.overview.as.source=As source
+relation.overview.as.target=As target
+relation.roles=Roles
+remove=Remove
+table.header.managed=<i class='o_icon o_icon_managed'> </i>
+table.header.role=Role
+table.header.username=Username
+warning.at.least.one.relation=You must select at least one relation.
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/role/EditRelationRoleController.java b/src/main/java/org/olat/user/ui/role/EditRelationRoleController.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbf92d6c8761b3ee3fe0e129dccc9c89e926b2c2
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/EditRelationRoleController.java
@@ -0,0 +1,142 @@
+/**
+ * <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.role;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.basesecurity.RelationRoleToRight;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+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.util.StringHelper;
+import org.olat.core.util.Util;
+import org.olat.user.UserModule;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EditRelationRoleController extends FormBasicController {
+	
+	private TextElement roleEl;
+	private MultipleSelectionElement rightsEl;
+	
+	private RelationRole relationRole;
+	private List<RelationRight> rights;
+	
+	@Autowired
+	private IdentityRelationshipService identityRelationsService;
+
+	public EditRelationRoleController(UserRequest ureq, WindowControl wControl) {
+		this(ureq, wControl, null);
+	}
+	
+	public EditRelationRoleController(UserRequest ureq, WindowControl wControl, RelationRole relationRole) {
+		super(ureq, wControl, Util.createPackageTranslator(UserModule.class, ureq.getLocale()));
+		rights = identityRelationsService.getAvailableRights();
+		this.relationRole = relationRole;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String name = relationRole == null ? null : relationRole.getRole();
+		roleEl = uifactory.addTextElement("role.name", 128, name, formLayout);
+		roleEl.setEnabled(!RelationRoleManagedFlag.isManaged(relationRole, RelationRoleManagedFlag.name));
+		
+		String[] rightKeys = new String[rights.size()];
+		String[] rightValues = new String[rights.size()];
+		for(int i=rights.size(); i-->0; ) {
+			rightKeys[i] = rights.get(i).getRight();
+			rightValues[i] = translate(RelationRolesAndRightsUIFactory.TRANS_RIGHT_PREFIX.concat(rights.get(i).getRight()));
+		}
+		rightsEl = uifactory.addCheckboxesVertical("role.rights", formLayout, rightKeys, rightValues, 2);
+		rightsEl.setEnabled(!RelationRoleManagedFlag.isManaged(relationRole, RelationRoleManagedFlag.rights));
+		if(relationRole != null) {
+			Set<RelationRoleToRight> roleToRights = relationRole.getRights();
+			for(RelationRoleToRight roleToRight:roleToRights) {
+				String right = roleToRight.getRight().getRight();
+				for(String rightKey:rightKeys) {
+					if(rightKey.equals(right)) {
+						rightsEl.select(rightKey, true);
+					}
+				}
+			}	
+		}
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("save", buttonsCont);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		roleEl.clearError();
+		if(!StringHelper.containsNonWhitespace(roleEl.getValue())) {
+			roleEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
+		return allOk;
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		Collection<String> selectedRightKeys = rightsEl.getSelectedKeys();
+		List<RelationRight> selectedRights = rights.stream()
+				.filter(r -> selectedRightKeys.contains(r.getRight())).collect(Collectors.toList());
+		if(relationRole == null) {
+			identityRelationsService.createRole(roleEl.getValue(), selectedRights);
+		} else {
+			//update
+		}
+		
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/role/ManagedCellRenderer.java b/src/main/java/org/olat/user/ui/role/ManagedCellRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c0faaba3616ebcb921e89703c948e97dcc2b3f9
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/ManagedCellRenderer.java
@@ -0,0 +1,44 @@
+/**
+ * <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.role;
+
+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;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ManagedCellRenderer implements FlexiCellRenderer {
+
+	@Override
+	public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source,
+			URLBuilder ubu, Translator translator) {
+		if(Boolean.TRUE.equals(cellValue)) {
+			target.append("<i class='o_icon o_icon_managed'> </i>");
+		}
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/role/RelationRoleRow.java b/src/main/java/org/olat/user/ui/role/RelationRoleRow.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b6a1e3230660bc0a23dd2ec9e2a6a735f4533ee
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/RelationRoleRow.java
@@ -0,0 +1,50 @@
+/**
+ * <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.role;
+
+import org.olat.basesecurity.RelationRole;
+
+/**
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRoleRow {
+	
+	private RelationRole relationRole;
+	
+	public RelationRoleRow(RelationRole relationRole) {
+		this.relationRole = relationRole;
+	}
+	
+	public Long getKey() {
+		return relationRole.getKey();
+	}
+	
+	public String getRole() {
+		return relationRole.getRole();
+	}
+	
+	public RelationRole getRelationRole() {
+		return relationRole;
+	}
+
+}
diff --git a/src/main/java/org/olat/user/ui/role/RelationRolesAdminController.java b/src/main/java/org/olat/user/ui/role/RelationRolesAdminController.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc03730d8aedeb61dfd9860091dc5944fe310dbb
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/RelationRolesAdminController.java
@@ -0,0 +1,230 @@
+/**
+ * <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.role;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.RelationRole;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.BooleanCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
+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.CloseableModalController;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+import org.olat.core.util.i18n.ui.SingleKeyTranslatorController;
+import org.olat.user.UserModule;
+import org.olat.user.ui.role.RelationRolesTableModel.RelationRoleCols;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRolesAdminController extends FormBasicController {
+	
+	private FlexiTableElement tableEl;
+	private RelationRolesTableModel tableModel;
+	
+	private FormLink addRoleButton;
+	
+	private CloseableModalController cmc;
+	private DialogBoxController confirmDeleteCtrl;
+	private EditRelationRoleController editRoleCtrl;
+	private SingleKeyTranslatorController translatorCtrl;
+	
+	@Autowired
+	private IdentityRelationshipService identityRelationsService;
+	
+	public RelationRolesAdminController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, "relation_roles");
+		
+		initForm(ureq);
+		loadModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		addRoleButton = uifactory.addFormLink("add.role", formLayout, Link.BUTTON);
+		addRoleButton.setIconLeftCSS("o_icon o_icon_add");
+		
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, RelationRoleCols.key));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RelationRoleCols.managed, new ManagedCellRenderer()));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RelationRoleCols.role));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("translate", translate("translate"), "translate"));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("edit", translate("edit"), "edit"));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("delete", RelationRoleCols.delete.ordinal(), "delete",
+				new BooleanCellRenderer(
+						new StaticFlexiCellRenderer(translate("delete"), "delete"), null)));
+		
+		tableModel = new RelationRolesTableModel(columnsModel, getLocale());
+		tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 24, false, getTranslator(), formLayout);
+	}
+
+	@Override
+	protected void doDispose() {
+		// 
+	}
+	
+	private void loadModel() {
+		List<RelationRole> relationRoles = identityRelationsService.getAvailableRoles();
+		List<RelationRoleRow> rows = new ArrayList<>(relationRoles.size());
+		for(RelationRole relationRole:relationRoles) {
+			rows.add(new RelationRoleRow(relationRole));
+		}
+		
+		tableModel.setObjects(rows);
+		tableEl.reset(true, true, true);
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(editRoleCtrl == source || translatorCtrl == source) {
+			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				loadModel();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(confirmDeleteCtrl == source) {
+			if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) {
+				doDelete((RelationRole)confirmDeleteCtrl.getUserObject());
+				loadModel();
+			}
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(confirmDeleteCtrl);
+		removeAsListenerAndDispose(translatorCtrl);
+		removeAsListenerAndDispose(editRoleCtrl);
+		removeAsListenerAndDispose(cmc);
+		confirmDeleteCtrl = null;
+		translatorCtrl = null;
+		editRoleCtrl = null;
+		cmc = null;
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(addRoleButton == source) {
+			doAddRole(ureq);
+		} else if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				RelationRoleRow row = tableModel.getObject(se.getIndex());
+				if("edit".equals(se.getCommand())) {
+					doEditRole(ureq, row);
+				} else if("translate".equals(se.getCommand())) {
+					doTranslate(ureq, row);
+				} else if("delete".equals(se.getCommand())) {
+					doConfirmDelete(ureq, row);
+				}
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	private void doAddRole(UserRequest ureq) {
+		if(editRoleCtrl != null) return;
+		
+		editRoleCtrl = new EditRelationRoleController(ureq, getWindowControl());
+		listenTo(editRoleCtrl);
+		
+		cmc = new CloseableModalController(getWindowControl(), "close", editRoleCtrl.getInitialComponent(), true, translate("add.role"));
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private void doEditRole(UserRequest ureq, RelationRoleRow row) {
+		if(editRoleCtrl != null) return;
+		
+		RelationRole role = identityRelationsService.getRole(row.getKey());
+		if(role == null) {
+			showWarning("error.relation.role.deleted");
+			loadModel();
+		} else {
+			editRoleCtrl = new EditRelationRoleController(ureq, getWindowControl(), role);
+			listenTo(editRoleCtrl);
+			String title = translate("edit.role", new String[] { row.getRole() });
+			cmc = new CloseableModalController(getWindowControl(), "close", editRoleCtrl.getInitialComponent(), true, title);
+			listenTo(cmc);
+			cmc.activate();
+		}
+	}
+	
+	private void doTranslate(UserRequest ureq, RelationRoleRow relationRole) {
+		String i18nKey = RelationRolesAndRightsUIFactory.TRANS_ROLE_PREFIX + relationRole.getKey();
+
+		String[] keys2Translate = { i18nKey };
+		translatorCtrl = new SingleKeyTranslatorController(ureq, getWindowControl(), keys2Translate, UserModule.class);
+		listenTo(translatorCtrl);
+		String title = translate("translate.title", new String[] { relationRole.getRole() });
+		cmc = new CloseableModalController(getWindowControl(), "close", translatorCtrl.getInitialComponent(), true, title);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private void doConfirmDelete(UserRequest ureq, RelationRoleRow row) {
+		RelationRole role = identityRelationsService.getRole(row.getKey());
+		if(role == null) {
+			showWarning("error.relation.role.deleted");
+			loadModel();
+		} else if(identityRelationsService.isInUse(role)) {
+			showWarning("error.relation.role.in.use");
+		} else {
+			String title = translate("confirm.delete.role.title", new String[] { role.getRole() });
+			String text = translate("confirm.delete.role.text", new String[] { role.getRole() });
+			confirmDeleteCtrl = activateOkCancelDialog(ureq, title, text, confirmDeleteCtrl);
+			confirmDeleteCtrl.setUserObject(role);
+		}
+	}
+	
+	private void doDelete(RelationRole role) {
+		identityRelationsService.deleteRole(role);
+		showInfo("info.role.delete", new String[] { role.getRole() });
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/role/RelationRolesAndRightsUIFactory.java b/src/main/java/org/olat/user/ui/role/RelationRolesAndRightsUIFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..07bbd0245bc3ddf8570d99e148f61008807a0785
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/RelationRolesAndRightsUIFactory.java
@@ -0,0 +1,33 @@
+/**
+ * <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.role;
+
+/**
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRolesAndRightsUIFactory {
+	
+	public static final String TRANS_ROLE_PREFIX = "relation.role.";
+	public static final String TRANS_RIGHT_PREFIX = "relation.right.";
+
+}
diff --git a/src/main/java/org/olat/user/ui/role/RelationRolesTableModel.java b/src/main/java/org/olat/user/ui/role/RelationRolesTableModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f7b98f78e4b874ff99e4dba7835fce9d12d668c
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/RelationRolesTableModel.java
@@ -0,0 +1,106 @@
+/**
+ * <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.role;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
+
+/**
+ * 
+ * Initial date: 29 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRolesTableModel extends DefaultFlexiTableDataModel<RelationRoleRow>
+implements SortableFlexiTableDataModel<RelationRoleRow> {
+	
+	private final Locale locale;
+	
+	public RelationRolesTableModel(FlexiTableColumnModel columnModel, Locale locale) {
+		super(columnModel);
+		this.locale = locale;
+	}
+
+	@Override
+	public void sort(SortKey orderBy) {
+		if(orderBy != null) {
+			List<RelationRoleRow> rows = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort();
+			super.setObjects(rows);
+		}
+	}
+
+	@Override
+	public Object getValueAt(int row, int col) {
+		RelationRoleRow relationRole = getObject(row);
+		return getValueAt(relationRole, col);
+	}
+
+	@Override
+	public Object getValueAt(RelationRoleRow row, int col) {
+		switch(RelationRoleCols.values()[col]) {
+			case key: return row.getKey();
+			case managed: return row.getRelationRole().getManagedFlags().length > 0;
+			case role: return row.getRole();
+			case delete: return !RelationRoleManagedFlag.isManaged(row.getRelationRole(), RelationRoleManagedFlag.delete);
+			default: return "ERROR";
+		}
+	}
+
+	@Override
+	public RelationRolesTableModel createCopyWithEmptyList() {
+		return new RelationRolesTableModel(getTableColumnModel(), locale);
+	}
+	
+	public enum RelationRoleCols implements FlexiSortableColumnDef {
+		key("table.header.id"),
+		managed("table.header.managed"),
+		role("table.header.role"),
+		delete("delete");
+		
+		private final String i18nKey;
+		
+		private RelationRoleCols(String i18nKey) {
+			this.i18nKey = i18nKey;
+		}
+
+		@Override
+		public String i18nHeaderKey() {
+			return i18nKey;
+		}
+
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/role/_content/relation_roles.html b/src/main/java/org/olat/user/ui/role/_content/relation_roles.html
new file mode 100644
index 0000000000000000000000000000000000000000..8f1b7d69c07a458a0cb0b0620d9bf19dfd8e1758
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/_content/relation_roles.html
@@ -0,0 +1,7 @@
+<fieldset>
+	<legend>$r.translate("admin.title")</legend>
+	<div class="o_button_group o_button_group_right">
+		$r.render("add.role")
+	</div>
+	$r.render("table")
+</fieldset>
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/role/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/ui/role/_i18n/LocalStrings_de.properties
new file mode 100644
index 0000000000000000000000000000000000000000..e34d8c1ca923c74732267d2e62ac4faae86fd59e
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/_i18n/LocalStrings_de.properties
@@ -0,0 +1,18 @@
+#Wed Jul 18 09:11:07 CEST 2018
+add.role=Neue Rolle hinzuf\u00FCgen
+admin.menu.title=Rolle Benutzer zu Benutzer
+admin.menu.title.alt=Rolle Benutzer zu Benutzer
+admin.title=Rolle Benutzer zu Benutzer
+confirm.delete.role.title=Rolle l\u00F6schen
+confirm.delete.role.text=Wollen Sie wirklich der Rolle "{0}"?
+edit.role=Rolle editieren "{0}"
+error.relation.role.deleted=Der Rolle wurde von jemand anders gel\u00F6scht.
+error.relation.role.in.use=Der Rolle "{0}" ist noch in Einsatz und kann nicht gel\u00F6scht werden.
+info.role.delete=Der Rolle "{0}" wurde erfolgreich gel\u00F6scht.
+role.name=Rolle
+role.rights=Rechte
+table.header.id=ID
+table.header.managed=<i class='o_icon o_icon_managed'> </i>
+table.header.role=Rolle
+translate=\u00DCbersetzen
+translate.title=\u00DCbersetzen Rolle "{0}"
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/ui/role/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/ui/role/_i18n/LocalStrings_en.properties
new file mode 100644
index 0000000000000000000000000000000000000000..2f0dd9ea8348b5a48a95572f0acf8c6e78c946b6
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/role/_i18n/LocalStrings_en.properties
@@ -0,0 +1,18 @@
+#Wed Jul 18 09:11:07 CEST 2018
+add.role=Add new role
+admin.menu.title=Role user to user
+admin.menu.title.alt=Role user to user
+admin.title=Rolle Benutzer zu Benutzer
+confirm.delete.role.title=Delete role
+confirm.delete.role.text=Do you really want to delete the role "{0}"?
+edit.role=Edit role "{0}"
+error.relation.role.deleted=The role was deleted by someone else in the meantime.
+error.relation.role.in.use=The role "{0}" is still used and cannot be deleted.
+info.role.delete=The role "{0}" was successfully deleted.
+role.name=Role
+role.rights=Rights
+table.header.id=ID
+table.header.managed=<i class='o_icon o_icon_managed'> </i>
+table.header.role=Role
+translate=Translate
+translate.title=Translate role "{0}"
\ No newline at end of file
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index 6c40961fb5df20d075bc2c22388c70806d04994c..d1713575036c244e774f244fb3a9ddea4b00b3c7 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -69,6 +69,10 @@
 		<class>org.olat.basesecurity.model.OrganisationTypeImpl</class>
 		<class>org.olat.basesecurity.model.OrganisationTypeToTypeImpl</class>
 		<class>org.olat.basesecurity.model.OrganisationImpl</class>
+		<class>org.olat.basesecurity.model.RelationRoleImpl</class>
+		<class>org.olat.basesecurity.model.RelationRightImpl</class>
+		<class>org.olat.basesecurity.model.RelationRoleToRightImpl</class>
+		<class>org.olat.basesecurity.model.IdentityToIdentityRelationImpl</class>
 		<class>org.olat.core.dispatcher.mapper.model.PersistedMapper</class>
 		<class>org.olat.core.logging.activity.LoggingObject</class>
 		<class>org.olat.commons.calendar.model.ImportedCalendar</class>
diff --git a/src/main/resources/database/mysql/alter_13_1_x_to_13_2_0.sql b/src/main/resources/database/mysql/alter_13_1_x_to_13_2_0.sql
new file mode 100644
index 0000000000000000000000000000000000000000..9e69ada19fae5871fa40ee8131be54c43dd653cd
--- /dev/null
+++ b/src/main/resources/database/mysql/alter_13_1_x_to_13_2_0.sql
@@ -0,0 +1,53 @@
+-- user to user relations
+create table o_bs_relation_role (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   g_role varchar(128) not null,
+   g_external_id varchar(128),
+   g_external_ref varchar(128),
+   g_managed_flags varchar(256),
+   primary key (id)
+);
+
+alter table o_bs_relation_role ENGINE = InnoDB;
+
+create table o_bs_relation_right (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   g_right varchar(128) not null,
+   primary key (id)
+);
+
+alter table o_bs_relation_right ENGINE = InnoDB;
+
+create index idx_right_idx on o_bs_relation_right (g_right);
+
+create table o_bs_relation_role_to_right (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   fk_role_id bigint,
+   fk_right_id bigint not null,
+   primary key (id)
+);
+
+alter table o_bs_relation_role_to_right ENGINE = InnoDB;
+
+alter table o_bs_relation_role_to_right add constraint role_to_right_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+alter table o_bs_relation_role_to_right add constraint role_to_right_right_idx foreign key (fk_right_id) references o_bs_relation_right (id);
+
+create table o_bs_identity_to_identity (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   g_external_id varchar(128),
+   g_managed_flags varchar(256),
+   fk_source_id bigint not null,
+   fk_target_id bigint not null,
+   fk_role_id bigint not null,
+   primary key (id)
+);
+
+alter table o_bs_identity_to_identity add constraint id_to_id_source_idx foreign key (fk_source_id) references o_bs_identity (id);
+alter table o_bs_identity_to_identity add constraint id_to_id_target_idx foreign key (fk_target_id) references o_bs_identity (id);
+alter table o_bs_identity_to_identity add constraint id_to_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 560841b7edcb6c815b6120fc4d4d4a9aaff4f436..beedcf0153ad020e75f571165366c81de1cf0176 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -175,6 +175,39 @@ create table if not exists o_bs_identity (
    deletedby varchar(128),
    primary key (id)
 );
+create table o_bs_relation_role (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   g_role varchar(128) not null,
+   g_external_id varchar(128),
+   g_external_ref varchar(128),
+   g_managed_flags varchar(256),
+   primary key (id)
+);
+create table o_bs_relation_right (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   g_right varchar(128) not null,
+   primary key (id)
+);
+create table o_bs_relation_role_to_right (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   fk_role_id bigint,
+   fk_right_id bigint not null,
+   primary key (id)
+);
+create table o_bs_identity_to_identity (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   g_external_id varchar(128),
+   g_managed_flags varchar(256),
+   fk_source_id bigint not null,
+   fk_target_id bigint not null,
+   fk_role_id bigint not null,
+   primary key (id)
+);
 create table o_csp_log (
    id bigint not null auto_increment,
    creationdate datetime,
@@ -2857,6 +2890,9 @@ alter table o_property ENGINE = InnoDB;
 alter table o_bs_secgroup ENGINE = InnoDB;
 alter table o_bs_group ENGINE = InnoDB;
 alter table o_bs_group_member ENGINE = InnoDB;
+alter table o_bs_relation_role ENGINE = InnoDB;
+alter table o_bs_relation_right ENGINE = InnoDB;
+alter table o_bs_relation_role_to_right ENGINE = InnoDB;
 alter table o_re_to_group ENGINE = InnoDB;
 alter table o_re_to_tax_level ENGINE = InnoDB;
 alter table o_bs_grant ENGINE = InnoDB;
@@ -3109,6 +3145,18 @@ alter table o_bs_membership add constraint FK7B6288B4B85B522C foreign key (secgr
 alter table o_bs_invitation add constraint inv_to_group_group_ctx foreign key (fk_group_id) references o_bs_group (id);
 alter table o_bs_invitation add constraint invit_to_id_idx foreign key (fk_identity_id) references o_bs_identity (id);
 
+-- user to user relations
+alter table o_bs_relation_role ENGINE = InnoDB;
+
+create index idx_right_idx on o_bs_relation_right (g_right);
+
+alter table o_bs_relation_role_to_right add constraint role_to_right_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+alter table o_bs_relation_role_to_right add constraint role_to_right_right_idx foreign key (fk_right_id) references o_bs_relation_right (id);
+
+alter table o_bs_identity_to_identity add constraint id_to_id_source_idx foreign key (fk_source_id) references o_bs_identity (id);
+alter table o_bs_identity_to_identity add constraint id_to_id_target_idx foreign key (fk_target_id) references o_bs_identity (id);
+alter table o_bs_identity_to_identity add constraint id_to_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+
 -- user
 create index usr_notification_interval_idx on o_user (notification_interval);
 create index idx_user_firstname_idx on o_user (u_firstname);
diff --git a/src/main/resources/database/oracle/alter_13_1_x_to_13_2_0.sql b/src/main/resources/database/oracle/alter_13_1_x_to_13_2_0.sql
new file mode 100644
index 0000000000000000000000000000000000000000..cb4309e31a8ba2437774412672efad7a482664d8
--- /dev/null
+++ b/src/main/resources/database/oracle/alter_13_1_x_to_13_2_0.sql
@@ -0,0 +1,50 @@
+-- user to user relations
+create table o_bs_relation_role (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   g_role varchar(128) not null,
+   g_external_id varchar(128),
+   g_external_ref varchar(128),
+   g_managed_flags varchar(256),
+   primary key (id)
+);
+
+create table o_bs_relation_right (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   g_right varchar(128) not null,
+   primary key (id)
+);
+create index idx_right_idx on o_bs_relation_right (g_right);
+
+create table o_bs_relation_role_to_right (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   fk_role_id number(20),
+   fk_right_id number(20) not null,
+   primary key (id)
+);
+
+alter table o_bs_relation_role_to_right add constraint role_to_right_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_role_to_right_role_idx on o_bs_relation_role_to_right (fk_role_id);
+alter table o_bs_relation_role_to_right add constraint role_to_right_right_idx foreign key (fk_right_id) references o_bs_relation_right (id);
+create index idx_role_to_right_right_idx on o_bs_relation_role_to_right (fk_right_id);
+
+create table o_bs_identity_to_identity (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   g_external_id varchar(128),
+   g_managed_flags varchar(256),
+   fk_source_id number(20) not null,
+   fk_target_id number(20) not null,
+   fk_role_id number(20) not null,
+   primary key (id)
+);
+
+alter table o_bs_identity_to_identity add constraint id_to_id_source_idx foreign key (fk_source_id) references o_bs_identity (id);
+create index idx_id_to_id_source_idx on o_bs_identity_to_identity (fk_source_id);
+alter table o_bs_identity_to_identity add constraint id_to_id_target_idx foreign key (fk_target_id) references o_bs_identity (id);
+create index idx_id_to_id_target_idx on o_bs_identity_to_identity (fk_target_id);
+alter table o_bs_identity_to_identity add constraint id_to_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_id_to_id_role_idx on o_bs_identity_to_identity (fk_role_id);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index c5c12dd492608092bc570fd7693baaf911c8919f..92ab0020d9c6b262e5906aca8198b0c5310f2929 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -195,6 +195,43 @@ CREATE TABLE o_bs_identity (
   PRIMARY KEY (id)
 );
 
+CREATE TABLE o_bs_relation_role (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   g_role varchar(128) not null,
+   g_external_id varchar(128),
+   g_external_ref varchar(128),
+   g_managed_flags varchar(256),
+   PRIMARY KEY (id)
+);
+
+CREATE TABLE o_bs_relation_right (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   g_right varchar(128) not null,
+   PRIMARY KEY (id)
+);
+
+CREATE TABLE o_bs_relation_role_to_right (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   fk_role_id number(20),
+   fk_right_id number(20) not null,
+   PRIMARY KEY (id)
+);
+
+CREATE TABLE o_bs_identity_to_identity (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   g_external_id varchar(128),
+   g_managed_flags varchar(256),
+   fk_source_id number(20) not null,
+   fk_target_id number(20) not null,
+   fk_role_id number(20) not null,
+   PRIMARY KEY (id)
+);
+
 CREATE TABLE o_csp_log (
   id number(20) generated always as identity,
   creationdate date,
@@ -3088,6 +3125,21 @@ create index idx_inv_to_group_group_ctx on o_bs_invitation (fk_group_id);
 alter table o_bs_invitation add constraint invit_to_id_idx foreign key (fk_identity_id) references o_bs_identity (id);
 create index idx_invit_to_id_idx on o_bs_invitation (fk_identity_id);
 
+-- user to user relations
+create index idx_right_idx on o_bs_relation_right (g_right);
+
+alter table o_bs_relation_role_to_right add constraint role_to_right_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_role_to_right_role_idx on o_bs_relation_role_to_right (fk_role_id);
+alter table o_bs_relation_role_to_right add constraint role_to_right_right_idx foreign key (fk_right_id) references o_bs_relation_right (id);
+create index idx_role_to_right_right_idx on o_bs_relation_role_to_right (fk_right_id);
+
+alter table o_bs_identity_to_identity add constraint id_to_id_source_idx foreign key (fk_source_id) references o_bs_identity (id);
+create index idx_id_to_id_source_idx on o_bs_identity_to_identity (fk_source_id);
+alter table o_bs_identity_to_identity add constraint id_to_id_target_idx foreign key (fk_target_id) references o_bs_identity (id);
+create index idx_id_to_id_target_idx on o_bs_identity_to_identity (fk_target_id);
+alter table o_bs_identity_to_identity add constraint id_to_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_id_to_id_role_idx on o_bs_identity_to_identity (fk_role_id);
+
 -- user
 create index usr_notification_interval_idx on o_user (notification_interval);
 create index idx_user_firstname_idx on o_user (u_firstname);
diff --git a/src/main/resources/database/postgresql/alter_13_1_x_to_13_2_0.sql b/src/main/resources/database/postgresql/alter_13_1_x_to_13_2_0.sql
new file mode 100644
index 0000000000000000000000000000000000000000..57a379aca99861c5d7243e6ee0de7474d329a2ea
--- /dev/null
+++ b/src/main/resources/database/postgresql/alter_13_1_x_to_13_2_0.sql
@@ -0,0 +1,50 @@
+-- user to user relations
+create table o_bs_relation_role (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   g_role varchar(128) not null,
+   g_external_id varchar(128),
+   g_external_ref varchar(128),
+   g_managed_flags varchar(256),
+   primary key (id)
+);
+
+create table o_bs_relation_right (
+   id bigserial,
+   creationdate timestamp not null,
+   g_right varchar(128) not null,
+   primary key (id)
+);
+create index idx_right_idx on o_bs_relation_right (g_right);
+
+create table o_bs_relation_role_to_right (
+   id bigserial,
+   creationdate timestamp not null,
+   fk_role_id bigint,
+   fk_right_id bigint not null,
+   primary key (id)
+);
+
+alter table o_bs_relation_role_to_right add constraint role_to_right_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_role_to_right_role_idx on o_bs_relation_role_to_right (fk_role_id);
+alter table o_bs_relation_role_to_right add constraint role_to_right_right_idx foreign key (fk_right_id) references o_bs_relation_right (id);
+create index idx_role_to_right_right_idx on o_bs_relation_role_to_right (fk_right_id);
+
+create table o_bs_identity_to_identity (
+   id bigserial,
+   creationdate timestamp not null,
+   g_external_id varchar(128),
+   g_managed_flags varchar(256),
+   fk_source_id bigint not null,
+   fk_target_id bigint not null,
+   fk_role_id bigint not null,
+   primary key (id)
+);
+
+alter table o_bs_identity_to_identity add constraint id_to_id_source_idx foreign key (fk_source_id) references o_bs_identity (id);
+create index idx_id_to_id_source_idx on o_bs_identity_to_identity (fk_source_id);
+alter table o_bs_identity_to_identity add constraint id_to_id_target_idx foreign key (fk_target_id) references o_bs_identity (id);
+create index idx_id_to_id_target_idx on o_bs_identity_to_identity (fk_target_id);
+alter table o_bs_identity_to_identity add constraint id_to_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_id_to_id_role_idx on o_bs_identity_to_identity (fk_role_id);
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index c7f53cc6ad348851fdcb9d81b8d423a18f756ba5..ec27d2f809281dc19510120875e5adf4afdf53b5 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -173,6 +173,39 @@ create table o_bs_identity (
    deletedby varchar(128),
    primary key (id)
 );
+create table o_bs_relation_role (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   g_role varchar(128) not null,
+   g_external_id varchar(128),
+   g_external_ref varchar(128),
+   g_managed_flags varchar(256),
+   primary key (id)
+);
+create table o_bs_relation_right (
+   id bigserial,
+   creationdate timestamp not null,
+   g_right varchar(128) not null,
+   primary key (id)
+);
+create table o_bs_relation_role_to_right (
+   id bigserial,
+   creationdate timestamp not null,
+   fk_role_id bigint,
+   fk_right_id bigint not null,
+   primary key (id)
+);
+create table o_bs_identity_to_identity (
+   id bigserial,
+   creationdate timestamp not null,
+   g_external_id varchar(128),
+   g_managed_flags varchar(256),
+   fk_source_id bigint not null,
+   fk_target_id bigint not null,
+   fk_role_id bigint not null,
+   primary key (id)
+);
 create table o_csp_log (
    id bigserial not null,
    creationdate timestamp,
@@ -2975,6 +3008,21 @@ create index idx_invit_to_id_idx on o_bs_invitation (fk_identity_id);
 
 create index idx_secgroup_creationdate_idx on o_bs_secgroup (creationdate);
 
+-- user to user relations
+create index idx_right_idx on o_bs_relation_right (g_right);
+
+alter table o_bs_relation_role_to_right add constraint role_to_right_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_role_to_right_role_idx on o_bs_relation_role_to_right (fk_role_id);
+alter table o_bs_relation_role_to_right add constraint role_to_right_right_idx foreign key (fk_right_id) references o_bs_relation_right (id);
+create index idx_role_to_right_right_idx on o_bs_relation_role_to_right (fk_right_id);
+
+alter table o_bs_identity_to_identity add constraint id_to_id_source_idx foreign key (fk_source_id) references o_bs_identity (id);
+create index idx_id_to_id_source_idx on o_bs_identity_to_identity (fk_source_id);
+alter table o_bs_identity_to_identity add constraint id_to_id_target_idx foreign key (fk_target_id) references o_bs_identity (id);
+create index idx_id_to_id_target_idx on o_bs_identity_to_identity (fk_target_id);
+alter table o_bs_identity_to_identity add constraint id_to_role_idx foreign key (fk_role_id) references o_bs_relation_role (id);
+create index idx_id_to_id_role_idx on o_bs_identity_to_identity (fk_role_id);
+
 -- user
 create index usr_notification_interval_idx on o_user (notification_interval);
 create index idx_user_firstname_idx on o_user (u_firstname);
diff --git a/src/test/java/org/olat/basesecurity/manager/IdentityToIdentityRelationDAOTest.java b/src/test/java/org/olat/basesecurity/manager/IdentityToIdentityRelationDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f20c8ed2a18be873e70ef0ebeccbe2832a549b08
--- /dev/null
+++ b/src/test/java/org/olat/basesecurity/manager/IdentityToIdentityRelationDAOTest.java
@@ -0,0 +1,177 @@
+/**
+ * <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.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.model.IdentityToIdentityRelationImpl;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class IdentityToIdentityRelationDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private RelationRoleDAO relationRoleDao;
+	@Autowired
+	private IdentityToIdentityRelationDAO identityToIdentityRelationDao;
+	
+	@Test
+	public void createIdentityToIdentityRelation() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		dbInstance.commitAndCloseSession();
+		
+		IdentityToIdentityRelation relation = identityToIdentityRelationDao.createRelation(idSource, idTarget, relationRole, "External-id", "all");
+		dbInstance.commit();
+		
+		Assert.assertNotNull(relation);
+		Assert.assertNotNull(relation.getKey());
+		Assert.assertNotNull(relation.getCreationDate());
+		Assert.assertEquals(idSource, relation.getSource());
+		Assert.assertEquals(idTarget, relation.getTarget());
+		Assert.assertEquals(relationRole, relation.getRole());
+	}
+	
+	@Test
+	public void isUsed_yes() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityToIdentityRelationDao.createRelation(idSource, idTarget, relationRole, null, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(relation);
+		
+		boolean used = identityToIdentityRelationDao.isUsed(relationRole);
+		Assert.assertTrue(used);
+	}
+	
+	@Test
+	public void isUsed_no() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		dbInstance.commitAndCloseSession();
+		
+		boolean used = identityToIdentityRelationDao.isUsed(relationRole);
+		Assert.assertFalse(used);
+	}
+	
+	@Test
+	public void hasRelation() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityToIdentityRelationDao.createRelation(idSource, idTarget, relationRole, null, null);
+		dbInstance.commitAndCloseSession();
+		
+		// check
+		boolean hasRelation = identityToIdentityRelationDao.hasRelation(idSource, idTarget, relationRole);
+		Assert.assertTrue(hasRelation);
+		// double check
+		boolean hasRelationAlt = identityToIdentityRelationDao.hasRelation(relation.getSource(), relation.getTarget(), relation.getRole());
+		Assert.assertTrue(hasRelationAlt);
+		// direction is important
+		boolean hasReverseRelation = identityToIdentityRelationDao.hasRelation(idTarget, idSource, relationRole);
+		Assert.assertFalse(hasReverseRelation);
+	}
+	
+	@Test
+	public void getRelation() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityToIdentityRelationDao.createRelation(idSource, idTarget, relationRole, "External-id", "all");
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(relation);
+		
+		IdentityToIdentityRelation loadedRelation = identityToIdentityRelationDao.getRelation(idSource, idTarget, relationRole);
+		Assert.assertNotNull(loadedRelation);
+		Assert.assertEquals(relation, loadedRelation);
+		Assert.assertEquals("External-id", loadedRelation.getExternalId());
+		Assert.assertEquals("all", ((IdentityToIdentityRelationImpl)loadedRelation).getManagedFlagsString());
+		
+		// direction is important
+		IdentityToIdentityRelation reversedRelation = identityToIdentityRelationDao.getRelation(idTarget, idSource, relationRole);
+		Assert.assertNull(reversedRelation);
+	}
+	
+	@Test
+	public void getRelationsAsSource() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityToIdentityRelationDao.createRelation(idSource, idTarget, relationRole, null, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(relation);
+		
+		List<IdentityToIdentityRelation> relations = identityToIdentityRelationDao.getRelationsAsSource(idSource);
+		Assert.assertNotNull(relations);
+		Assert.assertEquals(1, relations.size());
+		Assert.assertEquals(relation, relations.get(0));
+		
+		// direction is important
+		List<IdentityToIdentityRelation> reversedRelations = identityToIdentityRelationDao.getRelationsAsSource(idTarget);
+		Assert.assertNotNull(reversedRelations);
+		Assert.assertTrue(reversedRelations.isEmpty());
+	}
+	
+	@Test
+	public void getRelationsAsTarget() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityToIdentityRelationDao.createRelation(idSource, idTarget, relationRole, null, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(relation);
+		
+		List<IdentityToIdentityRelation> relations = identityToIdentityRelationDao.getRelationsAsTarget(idTarget);
+		Assert.assertNotNull(relations);
+		Assert.assertEquals(1, relations.size());
+		Assert.assertEquals(relation, relations.get(0));
+		
+		// direction is important
+		List<IdentityToIdentityRelation> reversedRelations = identityToIdentityRelationDao.getRelationsAsTarget(idSource);
+		Assert.assertNotNull(reversedRelations);
+		Assert.assertTrue(reversedRelations.isEmpty());
+	}
+
+}
diff --git a/src/test/java/org/olat/basesecurity/manager/RelationRightDAOTest.java b/src/test/java/org/olat/basesecurity/manager/RelationRightDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..966e62ab4af03f5713a5207d6d97c3f84726967d
--- /dev/null
+++ b/src/test/java/org/olat/basesecurity/manager/RelationRightDAOTest.java
@@ -0,0 +1,109 @@
+/**
+ * <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.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.RelationRight;
+import org.olat.core.commons.persistence.DB;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRightDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private RelationRightDAO relationRightDao;
+	
+	@Test
+	public void createRelationRight() {
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight = relationRightDao.createRelationRight(right);
+		dbInstance.commit();
+		
+		Assert.assertNotNull(relationRight);
+		Assert.assertNotNull(relationRight.getKey());
+		Assert.assertNotNull(relationRight.getCreationDate());
+		Assert.assertEquals(right, relationRight.getRight());
+	}
+	
+	@Test
+	public void loadRelationRight_byKey() {
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight = relationRightDao.createRelationRight(right);
+		dbInstance.commitAndCloseSession();
+		
+		RelationRight loadedRelationRight = relationRightDao.loadRelationRightByKey(relationRight.getKey());
+		Assert.assertNotNull(loadedRelationRight);
+		Assert.assertEquals(relationRight, loadedRelationRight);
+		Assert.assertEquals(right, loadedRelationRight.getRight());
+		Assert.assertEquals(relationRight.getKey(), loadedRelationRight.getKey());
+	}
+	
+	@Test
+	public void loadRelationRight_byRight() {
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight = relationRightDao.createRelationRight(right);
+		dbInstance.commitAndCloseSession();
+		
+		RelationRight loadedRelationRight = relationRightDao.loadRelationRightByRight(right);
+		Assert.assertNotNull(loadedRelationRight);
+		Assert.assertEquals(relationRight, loadedRelationRight);
+		Assert.assertEquals(right, loadedRelationRight.getRight());
+		Assert.assertEquals(relationRight.getKey(), loadedRelationRight.getKey());
+	}
+	
+	@Test
+	public void loadRelationRights() {
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight = relationRightDao.createRelationRight(right);
+		dbInstance.commitAndCloseSession();
+		
+		List<RelationRight> allRights = relationRightDao.loadRelationRights();
+		Assert.assertNotNull(allRights);
+		Assert.assertTrue(allRights.contains(relationRight));
+	}
+	
+	@Test
+	public void ensureRightsExists_enum() {
+		relationRightDao.ensureRightsExists(UnitTestRights.class);
+		dbInstance.commitAndCloseSession();
+		
+		RelationRight firstRight = relationRightDao.loadRelationRightByRight(UnitTestRights.iWantToTest.name());
+		Assert.assertNotNull(firstRight);
+		RelationRight secondRight = relationRightDao.loadRelationRightByRight(UnitTestRights.moreUnitTest.name());
+		Assert.assertNotNull(secondRight);
+	}
+	
+	public enum UnitTestRights {
+		iWantToTest,
+		moreUnitTest	
+	}
+}
diff --git a/src/test/java/org/olat/basesecurity/manager/RelationRoleDAOTest.java b/src/test/java/org/olat/basesecurity/manager/RelationRoleDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e613fc76b90257d1fa6a0afe91228dcf8f6ca822
--- /dev/null
+++ b/src/test/java/org/olat/basesecurity/manager/RelationRoleDAOTest.java
@@ -0,0 +1,218 @@
+/**
+ * <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;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleToRight;
+import org.olat.core.commons.persistence.DB;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 28 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRoleDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private RelationRoleDAO relationRoleDao;
+	@Autowired
+	private RelationRightDAO relationRightDao;
+	
+	@Test
+	public void createRelationRole() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, "External-id", "External-ref", null);
+		dbInstance.commit();
+		
+		Assert.assertNotNull(relationRole);
+		Assert.assertNotNull(relationRole.getKey());
+		Assert.assertNotNull(relationRole.getCreationDate());
+		Assert.assertEquals(role, relationRole.getRole());
+		Assert.assertEquals("External-ref", relationRole.getExternalRef());
+		Assert.assertEquals("External-id", relationRole.getExternalId());
+	}
+	
+	@Test
+	public void loadRelationRole_byKey() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		dbInstance.commitAndCloseSession();
+		
+		RelationRole loadedRelationRole = relationRoleDao.loadRelationRoleByKey(relationRole.getKey());
+		
+		Assert.assertNotNull(loadedRelationRole);
+		Assert.assertEquals(relationRole, loadedRelationRole);
+		Assert.assertEquals(role, loadedRelationRole.getRole());
+	}
+	
+	
+	@Test
+	public void loadRelationRole_byRole() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		dbInstance.commitAndCloseSession();
+		
+		RelationRole loadedRelationRole = relationRoleDao.loadRelationRoleByRole(role);
+		
+		Assert.assertNotNull(loadedRelationRole);
+		Assert.assertEquals(relationRole, loadedRelationRole);
+		Assert.assertEquals(role, loadedRelationRole.getRole());
+	}
+
+	@Test
+	public void addRight() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight = relationRightDao.createRelationRight(right);
+		relationRoleDao.addRight(relationRole, relationRight);
+		dbInstance.commitAndCloseSession();
+		
+		RelationRole loadedRelationRole = relationRoleDao.loadRelationRoleByRole(role);
+		
+		Assert.assertNotNull(loadedRelationRole);
+		Assert.assertEquals(relationRole, loadedRelationRole);
+		Assert.assertEquals(role, loadedRelationRole.getRole());
+		Assert.assertEquals(1, loadedRelationRole.getRights().size());
+		
+		RelationRoleToRight roleToRight = loadedRelationRole.getRights().iterator().next();
+		Assert.assertEquals(relationRole, roleToRight.getRole());
+		Assert.assertEquals(relationRight, roleToRight.getRight());	
+	}
+	
+	@Test
+	public void setRights_once() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight1 = relationRightDao.createRelationRight(right + "-1");
+		RelationRight relationRight2 = relationRightDao.createRelationRight(right + "-2");
+		dbInstance.commit();
+		List<RelationRight> relationRights = new ArrayList<>();
+		relationRights.add(relationRight1);
+		relationRights.add(relationRight2);
+		relationRoleDao.setRights(relationRole, relationRights);
+		dbInstance.commitAndCloseSession();
+		
+		RelationRole loadedRelationRole = relationRoleDao.loadRelationRoleByRole(role);
+		
+		Assert.assertNotNull(loadedRelationRole);
+		Assert.assertEquals(relationRole, loadedRelationRole);
+		Assert.assertEquals(role, loadedRelationRole.getRole());
+		Assert.assertEquals(2, loadedRelationRole.getRights().size());
+		
+		Set<RelationRoleToRight> roleToRights = loadedRelationRole.getRights();
+		List<RelationRight> savedRights = roleToRights.stream()
+				.map(RelationRoleToRight::getRight).collect(Collectors.toList());
+		Assert.assertTrue(savedRights.contains(relationRight1));
+		Assert.assertTrue(savedRights.contains(relationRight2));
+	}
+	
+	
+	@Test
+	public void setRights_twice() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight1 = relationRightDao.createRelationRight(right + "-1");
+		RelationRight relationRight2 = relationRightDao.createRelationRight(right + "-2");
+		RelationRight relationRight3 = relationRightDao.createRelationRight(right + "-2");
+		dbInstance.commit();
+		List<RelationRight> relationRights = new ArrayList<>();
+		relationRights.add(relationRight1);
+		relationRights.add(relationRight2);
+		relationRoleDao.setRights(relationRole, relationRights);
+		dbInstance.commitAndCloseSession();
+		
+		// load with the first 2 rights
+		RelationRole loadedRelationRole = relationRoleDao.loadRelationRoleByRole(role);
+		
+		Assert.assertNotNull(loadedRelationRole);
+		Assert.assertEquals(relationRole, loadedRelationRole);
+		Assert.assertEquals(role, loadedRelationRole.getRole());
+		Assert.assertEquals(2, loadedRelationRole.getRights().size());
+		
+		Set<RelationRoleToRight> roleToRights = loadedRelationRole.getRights();
+		List<RelationRight> savedRights = roleToRights.stream()
+				.map(RelationRoleToRight::getRight).collect(Collectors.toList());
+		Assert.assertTrue(savedRights.contains(relationRight1));
+		Assert.assertTrue(savedRights.contains(relationRight2));
+		
+		// set the 2 last rights
+		List<RelationRight> updatedRelationRights = new ArrayList<>();
+		updatedRelationRights.add(relationRight2);
+		updatedRelationRights.add(relationRight3);
+		relationRoleDao.setRights(loadedRelationRole, updatedRelationRights);
+		dbInstance.commitAndCloseSession();
+		
+		// check the rights
+		RelationRole reloadedRelationRole = relationRoleDao.loadRelationRoleByRole(role);
+		Assert.assertNotNull(reloadedRelationRole);
+		Assert.assertEquals(relationRole, reloadedRelationRole);
+		Assert.assertEquals(role, reloadedRelationRole.getRole());
+		Assert.assertEquals(2, reloadedRelationRole.getRights().size());
+		
+		Set<RelationRoleToRight> updatedRoleToRights = reloadedRelationRole.getRights();
+		List<RelationRight> updatededRights = updatedRoleToRights.stream()
+				.map(RelationRoleToRight::getRight).collect(Collectors.toList());
+		Assert.assertTrue(updatededRights.contains(relationRight2));
+		Assert.assertTrue(updatededRights.contains(relationRight3));
+	}
+	
+	@Test
+	public void deleteRelationRole() {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = relationRoleDao.createRelationRole(role, null, null, null);
+		String right = UUID.randomUUID().toString();
+		RelationRight relationRight1 = relationRightDao.createRelationRight(right + "-1");
+		RelationRight relationRight2 = relationRightDao.createRelationRight(right + "-2");
+		RelationRight relationRight3 = relationRightDao.createRelationRight(right + "-2");
+		dbInstance.commit();
+		List<RelationRight> relationRights = new ArrayList<>();
+		relationRights.add(relationRight1);
+		relationRights.add(relationRight2);
+		relationRights.add(relationRight3);
+		relationRoleDao.setRights(relationRole, relationRights);
+		dbInstance.commitAndCloseSession();
+		
+		// delete
+		RelationRole loadedRole = relationRoleDao.loadRelationRoleByKey(relationRole.getKey());
+		relationRoleDao.delete(loadedRole);
+		dbInstance.commit();
+		
+		// check
+		RelationRole deletedRole = relationRoleDao.loadRelationRoleByKey(relationRole.getKey());
+		Assert.assertNull(deletedRole);
+	}
+}
diff --git a/src/test/java/org/olat/restapi/IdentityToIdentityRelationsWebServiceTest.java b/src/test/java/org/olat/restapi/IdentityToIdentityRelationsWebServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbb0028633e86966edb35c8bd81c64945cbad8cb
--- /dev/null
+++ b/src/test/java/org/olat/restapi/IdentityToIdentityRelationsWebServiceTest.java
@@ -0,0 +1,237 @@
+/**
+ * <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.restapi;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.IdentityToIdentityRelation;
+import org.olat.basesecurity.RelationRole;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatJerseyTestCase;
+import org.olat.user.restapi.IdentityToIdentityRelationVO;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class IdentityToIdentityRelationsWebServiceTest extends OlatJerseyTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private IdentityRelationshipService identityRelationshipService;
+	
+	@Test
+	public void getRelationsAsSource()
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = identityRelationshipService.createRole(role, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityRelationshipService.addRelation(idSource, idTarget, relationRole, null, null);
+		dbInstance.commitAndCloseSession();
+		
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path(idSource.getKey().toString()).path("relations").path("source").build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		List<IdentityToIdentityRelationVO> relationVoes = parseRelationArray(response.getEntity());
+		Assert.assertNotNull(relationVoes);
+		Assert.assertEquals(1, relationVoes.size());
+		IdentityToIdentityRelationVO relationVo = relationVoes.get(0);
+		Assert.assertEquals(relation.getKey(), relationVo.getKey());
+		Assert.assertEquals(idSource.getKey(), relationVo.getIdentitySourceKey());
+		Assert.assertEquals(idTarget.getKey(), relationVo.getIdentityTargetKey());
+		Assert.assertEquals(relationRole.getKey(), relationVo.getRelationRoleKey());
+		Assert.assertEquals(relationRole.getRole(), relationVo.getRelationRole());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void getRelationsAsTarget()
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = identityRelationshipService.createRole(role, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityRelationshipService.addRelation(idSource, idTarget, relationRole, null, null);
+		dbInstance.commitAndCloseSession();
+		
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path(idTarget.getKey().toString()).path("relations").path("target").build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		List<IdentityToIdentityRelationVO> relationVoes = parseRelationArray(response.getEntity());
+		Assert.assertNotNull(relationVoes);
+		Assert.assertEquals(1, relationVoes.size());
+		IdentityToIdentityRelationVO relationVo = relationVoes.get(0);
+		Assert.assertEquals(relation.getKey(), relationVo.getKey());
+		Assert.assertEquals(idSource.getKey(), relationVo.getIdentitySourceKey());
+		Assert.assertEquals(idTarget.getKey(), relationVo.getIdentityTargetKey());
+		Assert.assertEquals(relationRole.getKey(), relationVo.getRelationRoleKey());
+		Assert.assertEquals(relationRole.getRole(), relationVo.getRelationRole());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void createRelation_put()
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = identityRelationshipService.createRole(role, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		dbInstance.commitAndCloseSession();
+		
+		IdentityToIdentityRelationVO relationVo = new IdentityToIdentityRelationVO();
+		relationVo.setExternalId("PUT-1");
+		relationVo.setIdentitySourceKey(idSource.getKey());
+		relationVo.setIdentityTargetKey(idTarget.getKey());
+		relationVo.setManagedFlagsString("all");
+		relationVo.setRelationRoleKey(relationRole.getKey());
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path(idTarget.getKey().toString()).path("relations").build();
+		HttpPut method = conn.createPut(request, MediaType.APPLICATION_JSON, true);
+		conn.addJsonEntity(method, relationVo);
+		
+		HttpResponse response = conn.execute(method);
+		Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201)));
+		
+		// checked VO
+		IdentityToIdentityRelationVO savedRelationVo = conn.parse(response, IdentityToIdentityRelationVO.class);
+		Assert.assertNotNull(savedRelationVo);
+		Assert.assertNotNull(savedRelationVo.getKey());
+		Assert.assertEquals(idSource.getKey(), savedRelationVo.getIdentitySourceKey());
+		Assert.assertEquals(idTarget.getKey(), savedRelationVo.getIdentityTargetKey());
+		Assert.assertEquals(relationRole.getKey(), savedRelationVo.getRelationRoleKey());
+		Assert.assertEquals(relationRole.getRole(), savedRelationVo.getRelationRole());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void createRelation_post()
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = identityRelationshipService.createRole(role, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		dbInstance.commitAndCloseSession();
+		
+		IdentityToIdentityRelationVO relationVo = new IdentityToIdentityRelationVO();
+		relationVo.setExternalId("PUT-1");
+		relationVo.setIdentitySourceKey(idSource.getKey());
+		relationVo.setIdentityTargetKey(idTarget.getKey());
+		relationVo.setManagedFlagsString("all");
+		relationVo.setRelationRoleKey(relationRole.getKey());
+		
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path(idTarget.getKey().toString()).path("relations").build();
+		HttpPost method = conn.createPost(request, MediaType.APPLICATION_JSON);
+		conn.addJsonEntity(method, relationVo);
+		
+		HttpResponse response = conn.execute(method);
+		Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201)));
+		
+		// checked VO
+		IdentityToIdentityRelationVO savedRelationVo = conn.parse(response, IdentityToIdentityRelationVO.class);
+		Assert.assertNotNull(savedRelationVo);
+		Assert.assertNotNull(savedRelationVo.getKey());
+		Assert.assertEquals(idSource.getKey(), savedRelationVo.getIdentitySourceKey());
+		Assert.assertEquals(idTarget.getKey(), savedRelationVo.getIdentityTargetKey());
+		Assert.assertEquals(relationRole.getKey(), savedRelationVo.getRelationRoleKey());
+		Assert.assertEquals(relationRole.getRole(), savedRelationVo.getRelationRole());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void deleteRelation()
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		RelationRole relationRole = identityRelationshipService.createRole(role, null);
+		Identity idSource = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-1");
+		Identity idTarget = JunitTestHelper.createAndPersistIdentityAsRndUser("id-2-id-2");
+		IdentityToIdentityRelation relation = identityRelationshipService.addRelation(idSource, idTarget, relationRole, null, null);
+		dbInstance.commitAndCloseSession();
+		
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path(idTarget.getKey().toString())
+				.path("relations").path(relation.getKey().toString()).build();
+		HttpDelete method = conn.createDelete(request, MediaType.APPLICATION_JSON);
+
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		
+		conn.shutdown();
+	}
+	
+	
+	protected List<IdentityToIdentityRelationVO> parseRelationArray(HttpEntity body) {
+		try(InputStream in = body.getContent()) {
+			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
+			return mapper.readValue(in, new TypeReference<List<IdentityToIdentityRelationVO>>(){/* */});
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+}
diff --git a/src/test/java/org/olat/restapi/RelationRolesWebServiceTest.java b/src/test/java/org/olat/restapi/RelationRolesWebServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9d39571a6e5329f5b4a18f9df55ef50ae69f268
--- /dev/null
+++ b/src/test/java/org/olat/restapi/RelationRolesWebServiceTest.java
@@ -0,0 +1,309 @@
+/**
+ * <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.restapi;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.util.EntityUtils;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.IdentityRelationshipService;
+import org.olat.basesecurity.RelationRight;
+import org.olat.basesecurity.RelationRole;
+import org.olat.basesecurity.RelationRoleManagedFlag;
+import org.olat.basesecurity.RelationRoleToRight;
+import org.olat.core.commons.persistence.DB;
+import org.olat.course.groupsandrights.CourseRightsEnum;
+import org.olat.test.OlatJerseyTestCase;
+import org.olat.user.restapi.RelationRoleVO;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * 
+ * Initial date: 31 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class RelationRolesWebServiceTest extends OlatJerseyTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private IdentityRelationshipService identityRelationshipService;
+	
+	@Test
+	public void getRelationRoles()
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		List<RelationRight> rights = identityRelationshipService.getAvailableRights();
+		RelationRole relationRole = identityRelationshipService.createRole(role, rights);
+		dbInstance.commit();
+		Assert.assertNotNull(relationRole);
+
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path("relations").path("roles").build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		List<RelationRoleVO> relationRoleVoes = parseRelationRoleArray(response.getEntity());
+		Assert.assertNotNull(relationRoleVoes);
+		Assert.assertFalse(relationRoleVoes.isEmpty());
+		
+		boolean found = false;
+		for(RelationRoleVO relationRoleVo:relationRoleVoes) {
+			if(role.equals(relationRoleVo.getRole())) {
+				found = true;
+			}
+		}
+
+		Assert.assertTrue(found);
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void createRelationRole()
+	throws IOException, URISyntaxException {
+		RelationRoleVO vo = new RelationRoleVO();
+		vo.setRole("REST role");
+		vo.setExternalId("REST-RO-1");
+		vo.setExternalRef("REST-ID-CEL-1");
+		vo.setManagedFlags("delete");
+		vo.setRights(Collections.singletonList(CourseRightsEnum.viewCourseCalendar.name()));
+
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path("relations").path("roles").build();
+		HttpPut method = conn.createPut(request, MediaType.APPLICATION_JSON, true);
+		conn.addJsonEntity(method, vo);
+		
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		
+		// checked VO
+		RelationRoleVO savedVo = conn.parse(response, RelationRoleVO.class);
+		Assert.assertNotNull(savedVo);
+		Assert.assertEquals("REST role", savedVo.getRole());
+		Assert.assertEquals("REST-RO-1", savedVo.getExternalId());
+		Assert.assertEquals("REST-ID-CEL-1", savedVo.getExternalRef());
+		Assert.assertEquals("delete", savedVo.getManagedFlags());
+		
+		List<String> rights = savedVo.getRights();
+		Assert.assertNotNull(rights);
+		Assert.assertEquals(1, rights.size());
+		Assert.assertEquals(CourseRightsEnum.viewCourseCalendar.name(), rights.get(0));
+		
+		// checked database
+		RelationRole relationRole = identityRelationshipService.getRole(savedVo.getKey());
+		Assert.assertNotNull(relationRole);
+		Assert.assertEquals("REST role", relationRole.getRole());
+		Assert.assertEquals("REST-RO-1", relationRole.getExternalId());
+		Assert.assertEquals("REST-ID-CEL-1", relationRole.getExternalRef());
+		Assert.assertNotNull(relationRole.getManagedFlags());
+		Assert.assertEquals(1, relationRole.getManagedFlags().length);
+		Assert.assertEquals(RelationRoleManagedFlag.delete, relationRole.getManagedFlags()[0]);
+		
+		Set<RelationRoleToRight> roleToRights = relationRole.getRights();
+		Assert.assertNotNull(roleToRights);
+		Assert.assertEquals(1, roleToRights.size());
+		Assert.assertEquals(CourseRightsEnum.viewCourseCalendar.name(), roleToRights.iterator().next().getRight().getRight());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void updateRelationRole() 
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		List<RelationRight> rights = identityRelationshipService.getAvailableRights();
+		RelationRole relationRole = identityRelationshipService.createRole(role, rights);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(relationRole);
+		
+		RelationRoleVO vo = new RelationRoleVO();
+		vo.setKey(relationRole.getKey());
+		vo.setRole("REST update role");
+		vo.setExternalId("REST-UPRO-1");
+		vo.setExternalRef("REST-ID-UPCEL-1");
+		vo.setManagedFlags("all");
+		vo.setRights(Collections.singletonList(CourseRightsEnum.viewEfficiencyStatement.name()));
+
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path("relations")
+				.path("roles").build();
+		HttpPost method = conn.createPost(request, MediaType.APPLICATION_JSON);
+		conn.addJsonEntity(method, vo);
+		
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		
+		// checked VO
+		RelationRoleVO savedVo = conn.parse(response, RelationRoleVO.class);
+		Assert.assertNotNull(savedVo);
+		Assert.assertEquals("REST update role", savedVo.getRole());
+		Assert.assertEquals("REST-UPRO-1", savedVo.getExternalId());
+		Assert.assertEquals("REST-ID-UPCEL-1", savedVo.getExternalRef());
+		Assert.assertEquals("all", savedVo.getManagedFlags());
+		
+		List<String> savedRights = savedVo.getRights();
+		Assert.assertNotNull(savedRights);
+		Assert.assertEquals(1, savedRights.size());
+		Assert.assertEquals(CourseRightsEnum.viewEfficiencyStatement.name(), savedRights.get(0));
+		
+		// checked database
+		RelationRole persistedRelationRole = identityRelationshipService.getRole(savedVo.getKey());
+		Assert.assertNotNull(persistedRelationRole);
+		Assert.assertEquals("REST update role", persistedRelationRole.getRole());
+		Assert.assertEquals("REST-UPRO-1", persistedRelationRole.getExternalId());
+		Assert.assertEquals("REST-ID-UPCEL-1", persistedRelationRole.getExternalRef());
+		Assert.assertNotNull(persistedRelationRole.getManagedFlags());
+		Assert.assertEquals(1, persistedRelationRole.getManagedFlags().length);
+		Assert.assertEquals(RelationRoleManagedFlag.all, persistedRelationRole.getManagedFlags()[0]);
+		
+		Set<RelationRoleToRight> roleToRights = persistedRelationRole.getRights();
+		Assert.assertNotNull(roleToRights);
+		Assert.assertEquals(1, roleToRights.size());
+		Assert.assertEquals(CourseRightsEnum.viewEfficiencyStatement.name(), roleToRights.iterator().next().getRight().getRight());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void updateRelationRole_withKey() 
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		List<RelationRight> rights = identityRelationshipService.getAvailableRights();
+		RelationRole relationRole = identityRelationshipService.createRole(role, rights);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(relationRole);
+		
+		RelationRoleVO vo = new RelationRoleVO();
+		vo.setRole("REST update role");
+		vo.setExternalId("REST-UPRO-1");
+		vo.setExternalRef("REST-ID-UPCEL-1");
+		vo.setManagedFlags("all");
+		vo.setRights(Collections.singletonList(CourseRightsEnum.viewEfficiencyStatement.name()));
+
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path("relations")
+				.path("roles").path(relationRole.getKey().toString()).build();
+		HttpPost method = conn.createPost(request, MediaType.APPLICATION_JSON);
+		conn.addJsonEntity(method, vo);
+		
+		HttpResponse response = conn.execute(method);
+		Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201)));
+		
+		// checked VO
+		RelationRoleVO savedVo = conn.parse(response, RelationRoleVO.class);
+		Assert.assertNotNull(savedVo);
+		Assert.assertEquals("REST update role", savedVo.getRole());
+		Assert.assertEquals("REST-UPRO-1", savedVo.getExternalId());
+		Assert.assertEquals("REST-ID-UPCEL-1", savedVo.getExternalRef());
+		Assert.assertEquals("all", savedVo.getManagedFlags());
+		
+		List<String> savedRights = savedVo.getRights();
+		Assert.assertNotNull(savedRights);
+		Assert.assertEquals(1, savedRights.size());
+		Assert.assertEquals(CourseRightsEnum.viewEfficiencyStatement.name(), savedRights.get(0));
+		
+		// checked database
+		RelationRole persistedRelationRole = identityRelationshipService.getRole(savedVo.getKey());
+		Assert.assertNotNull(persistedRelationRole);
+		Assert.assertEquals("REST update role", persistedRelationRole.getRole());
+		Assert.assertEquals("REST-UPRO-1", persistedRelationRole.getExternalId());
+		Assert.assertEquals("REST-ID-UPCEL-1", persistedRelationRole.getExternalRef());
+		Assert.assertNotNull(persistedRelationRole.getManagedFlags());
+		Assert.assertEquals(1, persistedRelationRole.getManagedFlags().length);
+		Assert.assertEquals(RelationRoleManagedFlag.all, persistedRelationRole.getManagedFlags()[0]);
+		
+		Set<RelationRoleToRight> roleToRights = persistedRelationRole.getRights();
+		Assert.assertNotNull(roleToRights);
+		Assert.assertEquals(1, roleToRights.size());
+		Assert.assertEquals(CourseRightsEnum.viewEfficiencyStatement.name(), roleToRights.iterator().next().getRight().getRight());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void deleteRelationRole()
+	throws IOException, URISyntaxException {
+		String role = UUID.randomUUID().toString();
+		List<RelationRight> rights = identityRelationshipService.getAvailableRights();
+		RelationRole relationRole = identityRelationshipService.createRole(role, rights);
+		dbInstance.commit();
+		Assert.assertNotNull(relationRole);
+		
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		URI request = UriBuilder.fromUri(getContextURI()).path("users").path("relations")
+				.path("roles").path(relationRole.getKey().toString()).build();
+		HttpDelete method = conn.createDelete(request, MediaType.APPLICATION_JSON);
+		
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		EntityUtils.consume(response.getEntity());
+		
+		RelationRole deletedRole = identityRelationshipService.getRole(relationRole.getKey());
+		Assert.assertNull(deletedRole);
+		
+		conn.shutdown();
+	}
+	
+	protected List<RelationRoleVO> parseRelationRoleArray(HttpEntity body) {
+		try(InputStream in = body.getContent()) {
+			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
+			return mapper.readValue(in, new TypeReference<List<RelationRoleVO>>(){/* */});
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index c504da1876a434da85e90fa2b04f8b39b0632b80..1a31c1a58c441b1cef7f8522034932842b51688c 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -130,6 +130,9 @@ import org.junit.runners.Suite;
 	org.olat.basesecurity.manager.AuthenticationDAOTest.class,
 	org.olat.basesecurity.manager.AuthenticationHistoryDAOTest.class,
 	org.olat.basesecurity.manager.GroupDAOTest.class,
+	org.olat.basesecurity.manager.RelationRightDAOTest.class,
+	org.olat.basesecurity.manager.RelationRoleDAOTest.class,
+	org.olat.basesecurity.manager.IdentityToIdentityRelationDAOTest.class,
 	org.olat.basesecurity.SecurityManagerTest.class,
 	org.olat.basesecurity.GetIdentitiesByPowerSearchTest.class,
 	org.olat.basesecurity.BaseSecurityManagerTest.class,
@@ -379,6 +382,8 @@ import org.junit.runners.Suite;
 	org.olat.restapi.LecturesBlockRollCallTest.class,
 	org.olat.restapi.NotificationsTest.class,
 	org.olat.restapi.NotificationsSubscribersTest.class,
+	org.olat.restapi.RelationRolesWebServiceTest.class,
+	org.olat.restapi.IdentityToIdentityRelationsWebServiceTest.class,
 	org.olat.restapi.RepositoryEntryLifecycleTest.class,
 	org.olat.restapi.RepositoryEntriesTest.class,
 	org.olat.restapi.RepositoryEntryWebServiceTest.class,