You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2020/11/26 21:20:31 UTC

[archiva-redback-core] 01/05: Refactoring of role API and new Role V2 REST service

This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva-redback-core.git

commit de23f72bf11a46ab0dd3b5f67d82d4101b20b897
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Tue Nov 24 21:28:42 2020 +0100

    Refactoring of role API and new Role V2 REST service
---
 README.adoc                                        |  11 +
 .../redback/common/ldap/role/LdapRoleMapper.java   |   1 -
 .../apache/archiva/redback/rest/api/Constants.java |   2 +-
 .../archiva/redback/rest/api/MessageKeys.java      |  13 +-
 .../redback/rest/api/model/v2/Application.java     |  89 ++++
 .../redback/rest/api/model/v2/BaseGroupInfo.java   |  80 ++++
 .../redback/rest/api/model/v2/BaseRoleInfo.java    | 262 +++++++++++
 .../redback/rest/api/model/v2/BaseUserInfo.java    |  67 +++
 .../archiva/redback/rest/api/model/v2/Role.java    | 104 +++++
 .../redback/rest/api/model/v2/RoleInfo.java        | 182 ++++++++
 .../redback/rest/api/model/v2/RoleTree.java        |  74 +++
 .../redback/rest/api/model/v2/UserInfo.java        |  36 +-
 .../redback/rest/api/services/v2/RoleService.java  | 444 ++++++++++--------
 .../redback/rest/api/services/v2/UserService.java  |  61 ++-
 .../services/DefaultRoleManagementService.java     |   5 +-
 .../rest/services/v2/BaseRedbackService.java       | 145 ++++++
 .../rest/services/v2/DefaultGroupService.java      |   8 +-
 .../rest/services/v2/DefaultRoleService.java       | 434 ++++++++++++++++++
 .../rest/services/v2/DefaultUserService.java       | 185 +++++---
 .../redback/rest/services/v2/QueryHelper.java      | 168 +++++++
 .../src/main/resources/META-INF/spring-context.xml |   2 +-
 .../services/v2/AbstractNativeRestServices.java    |   2 +-
 .../rest/services/v2/NativeRoleServiceTest.java    | 505 +++++++++++++++++++++
 .../rest/services/v2/NativeUserServiceTest.java    | 132 +++++-
 .../archiva/redback/rbac/AbstractRBACManager.java  |  83 +++-
 .../apache/archiva/redback/rbac/AbstractRole.java  |   9 +-
 .../apache/archiva/redback/rbac/RBACManager.java   |  26 +-
 .../java/org/apache/archiva/redback/rbac/Role.java |  26 ++
 .../redback/rbac/cached/CachedRbacManager.java     |  22 +-
 .../archiva/redback/rbac/jpa/JpaRbacManager.java   |  18 +-
 .../archiva/redback/rbac/jpa/model/JpaRole.java    |  44 +-
 .../archiva/redback/rbac/jpa/model/RoleId.java     |  40 +-
 .../archiva/redback/rbac/ldap/LdapRbacManager.java |  45 +-
 .../archiva/redback/rbac/memory/MemoryRole.java    |  22 +
 .../archiva/redback/role/DefaultRoleManager.java   |  23 +-
 .../archiva/redback/role/RoleExistsException.java  |  16 +-
 .../apache/archiva/redback/role/RoleManager.java   |   4 +-
 .../redback/role/RoleNotFoundException.java        |  16 +-
 .../role/processor/DefaultRoleModelProcessor.java  |   3 +-
 .../template/DefaultRoleTemplateProcessor.java     |  40 +-
 .../role/template/RoleTemplateProcessor.java       |  26 +-
 .../AbstractRbacManagerPerformanceTestCase.java    |   2 +
 .../redback/tests/AbstractRbacManagerTestCase.java |   6 +-
 .../archiva/redback/tests/utils/RBACDefaults.java  |   3 +
 44 files changed, 3097 insertions(+), 389 deletions(-)

diff --git a/README.adoc b/README.adoc
index 568868b..646c17a 100644
--- a/README.adoc
+++ b/README.adoc
@@ -2,6 +2,17 @@ Archiva Redback - Documentation
 ===============================
 :toc:
 
+== Update Information for 3.0
+
+=== Database Schema Changes
+
+==== org.apache.archiva.redback.rbac.Role
+
+New fields:
+id, modelId, isTemplateInstance, resource
+
+Primary key changed from name to combined key id,name
+
 
 == How to build and publish the pages for the archiva web content
 
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java
index 1ee2cfe..43bea95 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java
@@ -66,7 +66,6 @@ public interface LdapRoleMapper
     boolean hasRole( DirContext context, String role )
         throws MappingException;
 
-
     /**
      * @return the base dn which contains all ldap groups
      */
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
index 3faaf8c..f70ad58 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
@@ -23,7 +23,7 @@ package org.apache.archiva.redback.rest.api;
  */
 public interface Constants
 {
-    String DEFAULT_PAGE_LIMIT = "1000";
+    String DEFAULT_PAGE_LIMIT = "100";
 
 
 }
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java
index 6f7219a..51f762d 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java
@@ -40,10 +40,19 @@ public interface MessageKeys
     String ERR_USER_ADMIN_EXISTS = "rb.user.admin.exists";
     String ERR_USER_ADMIN_BAD_NAME = "rb.user.admin.badname";
     String ERR_USER_NOT_FOUND = "rb.user.not_found";
+    String ERR_USER_BAD_PASSWORD = "rb.user.bad.password";
     String ERR_PASSWORD_VIOLATION = "rb.user.password_violation";
+
     String ERR_LDAP_GENERIC = "rb.ldap.error";
     String ERR_ROLE_MAPPING = "rb.role.mapping.error";
     String ERR_ROLE_MAPPING_NOT_FOUND = "rb.role.mapping.not_found";
+    String ERR_ROLE_NOT_FOUND = "rb.role.not_found";
+    // A template instance not found. With arguments templateId, resource
+    String ERR_ROLE_INSTANCE_NOT_FOUND = "rb.role.instance.not_found";
+    String ERR_ROLE_EXISTS = "rb.role.exists";
+    // A template instance exists. With arguments templateId, resource
+    String ERR_ROLE_INSTANCE_EXISTS = "rb.role.instance.exists";
+
     String ERR_AUTH_BAD_CODE = "rb.auth.bad_authorization_code";
     String ERR_AUTH_INVALID_CREDENTIALS = "rb.auth.invalid_credentials";
     String ERR_AUTH_FAIL_MSG = "rb.auth.fail";
@@ -52,9 +61,11 @@ public interface MessageKeys
     String ERR_AUTH_UNSUPPORTED_GRANT_TYPE = "rb.auth.unsupported_grant";
     String ERR_AUTH_INVALID_TOKEN = "rb.auth.invalid_token";
     String ERR_AUTH_UNAUTHORIZED_REQUEST = "rb.auth.unauthorized_request";
+
     String ERR_PASSWD_RESET_FAILED = "rb.passwd.reset.fail";
-    String ERR_USER_BAD_PASSWORD = "rb.user.bad.password";
+
     String ERR_REGISTRATION_KEY_INVALID = "rb.registration.key.invalid";
     String ERR_REGISTRATION_USER_VALIDATED = "rb.registration.user.validated";
     String ERR_REGISTRATION_ROLE_ASSIGNMENT_FAILED = "rb.registration.role.assignment.failed";
+
 }
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Application.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Application.java
new file mode 100644
index 0000000..c38392c
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Application.java
@@ -0,0 +1,89 @@
+package org.apache.archiva.redback.rest.api.model.v2;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+
+/**
+ * @author Olivier Lamy
+ */
+@XmlRootElement( name = "application" )
+@Schema(name="Application", description = "A single application that is used for defining roles")
+public class Application
+    implements Serializable
+{
+    private static final long serialVersionUID = -4738856943947960583L;
+
+    private String version;
+    private String id;
+    private String description;
+    private String longDescription;
+
+    public Application()
+    {
+        // no op
+    }
+
+    @Schema(description = "The application version. Used to separate different sets of roles.")
+    public String getVersion()
+    {
+        return version;
+    }
+
+    public void setVersion( String version )
+    {
+        this.version = version;
+    }
+
+    @Schema(description = "The identifier of the application")
+    public String getId()
+    {
+        return id;
+    }
+
+    public void setId( String id )
+    {
+        this.id = id;
+    }
+
+    @Schema(description = "A short description.")
+    public String getDescription()
+    {
+        return description;
+    }
+
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    @Schema(description = "May be a longer explanation, of the application purpose and its defined roles.")
+    public String getLongDescription()
+    {
+        return longDescription;
+    }
+
+    public void setLongDescription( String longDescription )
+    {
+        this.longDescription = longDescription;
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseGroupInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseGroupInfo.java
new file mode 100644
index 0000000..22ff830
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseGroupInfo.java
@@ -0,0 +1,80 @@
+package org.apache.archiva.redback.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+
+/**
+ * Information about a group.
+ *
+ * @since 3.0
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@Schema( name = "Group", description = "Group information" )
+public class BaseGroupInfo implements Serializable
+{
+    private static final long serialVersionUID = 2945927911204165322L;
+    private String id;
+    private String groupName;
+    private String description = "";
+
+    public BaseGroupInfo( )
+    {
+
+    }
+
+    public BaseGroupInfo( String id, String groupName )
+    {
+        this.id = id;
+        this.groupName = groupName;
+    }
+
+    @Schema(description = "The name of the group")
+    public String getGroupName( )
+    {
+        return groupName;
+    }
+
+    public void setGroupName( String groupName )
+    {
+        this.groupName = groupName;
+    }
+
+    @Schema( description = "The unique identifier of the group" )
+    public String getId( )
+    {
+        return id;
+    }
+
+    public void setId( String id )
+    {
+        this.id = id;
+    }
+
+    @Schema( description = "A description of the group" )
+    public String getDescription( )
+    {
+        return description;
+    }
+
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseRoleInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseRoleInfo.java
new file mode 100644
index 0000000..126a5e0
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseRoleInfo.java
@@ -0,0 +1,262 @@
+package org.apache.archiva.redback.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.redback.rbac.Role;
+import org.apache.archiva.redback.role.model.ModelRole;
+
+import javax.xml.bind.annotation.XmlTransient;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Basic role information. This class contains only the standard attributes used for displaying a role.
+ *
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@Schema(name="BaseRoleInfo", description = "Basic role attributes")
+public class BaseRoleInfo implements Serializable
+{
+    private static final long serialVersionUID = -6725489773301720068L;
+
+    protected String id;
+    protected String name;
+    protected String description = "";
+    protected boolean permanent = false;
+    protected String modelId = "";
+    protected String resource = "";
+    protected boolean isTemplateInstance = false;
+    protected boolean assignable = true;
+    private String applicationId = "";
+    private boolean isChild = false;
+
+    protected boolean assigned = false;
+    private List<BaseRoleInfo> children = new ArrayList<>( 0 );
+
+    public BaseRoleInfo() {
+
+    }
+
+    public static BaseRoleInfo ofName(String name) {
+        BaseRoleInfo info = new BaseRoleInfo( );
+        info.setName( name );
+        return info;
+    }
+
+    public static BaseRoleInfo ofId(String id) {
+        BaseRoleInfo info = new BaseRoleInfo( );
+        info.setId( id );
+        return info;
+    }
+
+    public static BaseRoleInfo of(Role rbacRole) {
+        return of( rbacRole, new BaseRoleInfo( ) );
+    }
+
+
+    public static <T extends BaseRoleInfo>  T of( Role rbacRole, T role ) {
+        role.id = rbacRole.getId( );
+        role.name = rbacRole.getName( );
+        role.description = rbacRole.getDescription( ) == null ?"": rbacRole.getDescription();
+        role.permanent = rbacRole.isPermanent( );
+        role.modelId = rbacRole.getModelId( );
+        role.resource = rbacRole.getResource( );
+        role.isTemplateInstance = rbacRole.isTemplateInstance( );
+        role.assignable = rbacRole.isAssignable( );
+        return role;
+    }
+
+
+
+    @Schema(description = "The role name")
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    @Schema( description = "A description of the role" )
+    public String getDescription( )
+    {
+        return description;
+    }
+
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    @Schema( description = "True, if this role cannot be deleted.")
+    public boolean isPermanent()
+    {
+        return permanent;
+    }
+
+    public void setPermanent( boolean permanent )
+    {
+        this.permanent = permanent;
+    }
+
+    @Schema(description = "The identifier of this role")
+    public String getId( )
+    {
+        return id;
+    }
+
+    public void setId( String id )
+    {
+        this.id = id;
+    }
+
+    @Schema(description = "The model this role is derived from")
+    public String getModelId( )
+    {
+        return modelId;
+    }
+
+    public void setModelId( String modelId )
+    {
+        this.modelId = modelId;
+    }
+
+    @Schema(description = "The resource this model is built for, if it is built by a template.")
+    public String getResource( )
+    {
+        return resource;
+    }
+
+    public void setResource( String resource )
+    {
+        this.resource = resource;
+    }
+
+    @Schema(description = "True, if this is a instance of a role template")
+    public boolean isTemplateInstance( )
+    {
+        return isTemplateInstance;
+    }
+
+    public void setTemplateInstance( boolean templateInstance )
+    {
+        isTemplateInstance = templateInstance;
+    }
+
+    @Schema(description = "Roles that are children of this role. This field may not be populated, depending on the REST method call.")
+    public List<BaseRoleInfo> getChildren( )
+    {
+        return children;
+    }
+
+    public void setChildren( List<BaseRoleInfo> children )
+    {
+        this.children = children;
+    }
+
+    public void addChild(BaseRoleInfo child) {
+        if (!this.children.contains( child ))
+        {
+            this.children.add( child );
+        }
+    }
+
+    @Schema(description = "This attribute is only set at specific REST calls, that return roles, that are either assigned or not assigned to a given user.")
+    public boolean isAssigned( )
+    {
+        return assigned;
+    }
+
+    public void setAssigned( boolean assigned )
+    {
+        this.assigned = assigned;
+    }
+
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        BaseRoleInfo that = (BaseRoleInfo) o;
+
+        return id.equals( that.id );
+    }
+
+    @Schema( description = "If true, the role is assignable to users or roles. Otherwise, it can be used only as parent role.")
+    public boolean isAssignable()
+    {
+        return assignable;
+    }
+
+    public void setAssignable( boolean assignable )
+    {
+        this.assignable = assignable;
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        return id.hashCode( );
+    }
+
+    @Override
+    public String toString( )
+    {
+        final StringBuilder sb = new StringBuilder( "BaseRoleInfo{" );
+        sb.append( "id='" ).append( id ).append( '\'' );
+        sb.append( ", name='" ).append( name ).append( '\'' );
+        sb.append( ", description='" ).append( description ).append( '\'' );
+        sb.append( ", permanent=" ).append( permanent );
+        sb.append( ", modelId='" ).append( modelId ).append( '\'' );
+        sb.append( ", resource='" ).append( resource ).append( '\'' );
+        sb.append( ", isTemplateInstance=" ).append( isTemplateInstance );
+        sb.append( '}' );
+        return sb.toString( );
+    }
+
+    @Schema(description = "Application id, where this role belongs to. This is only filled by certain REST methods.")
+    public String getApplicationId( )
+    {
+        return applicationId;
+    }
+
+    public void setApplicationId( String applicationId )
+    {
+        this.applicationId = applicationId;
+    }
+
+    public boolean isChild( )
+    {
+        return isChild;
+    }
+
+    public void setChild( boolean child )
+    {
+        isChild = child;
+    }
+
+    @XmlTransient
+    public boolean isNotChild() {
+        return !isChild;
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseUserInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseUserInfo.java
new file mode 100644
index 0000000..7903a1c
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseUserInfo.java
@@ -0,0 +1,67 @@
+package org.apache.archiva.redback.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.xml.bind.annotation.XmlElement;
+import java.io.Serializable;
+
+/**
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@Schema( name = "BaseUserInfo", description = "Basic user information" )
+public class BaseUserInfo implements Serializable
+{
+    private static final long serialVersionUID = 4643187400578104895L;
+    protected String userId;
+    private String id;
+
+
+    public BaseUserInfo( )
+    {
+    }
+
+    public BaseUserInfo( String id , String userId )
+    {
+        this.userId = userId;
+        this.id = id;
+    }
+
+    @Schema( name = "user_id", description = "The user id" )
+    @XmlElement( name = "user_id" )
+    public String getUserId( )
+    {
+        return userId;
+    }
+
+    public void setUserId( String userId )
+    {
+        this.userId = userId;
+    }
+
+    @Schema( description = "User id that is unique over all user managers" )
+    public String getId( )
+    {
+        return id;
+    }
+
+    public void setId( String id )
+    {
+        this.id = id;
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Role.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Role.java
new file mode 100644
index 0000000..f5909a9
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Role.java
@@ -0,0 +1,104 @@
+package org.apache.archiva.redback.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used for role update. Contains only the role attributes, that can be updated.
+ *
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@Schema(name="Role",description="Role attributes that are used for updating a role")
+public class Role implements Serializable
+{
+    private static final long serialVersionUID = 3238571295658509062L;
+
+    protected String name;
+    protected String id;
+    protected String description;
+    protected boolean permanent = false;
+    /**
+     * The ids of all the assigned users.
+     */
+    protected List<BaseUserInfo> assignedUsers = new ArrayList<>( 0 );
+
+    @Schema(description = "The role name")
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    @Schema( description = "A description of the role" )
+    public String getDescription( )
+    {
+        return description;
+    }
+
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    @Schema( description = "True, if this role cannot be deleted.")
+    public boolean isPermanent()
+    {
+        return permanent;
+    }
+
+    public void setPermanent( boolean permanent )
+    {
+        this.permanent = permanent;
+    }
+
+    @Schema(description = "The identifier of this role")
+    public String getId( )
+    {
+        return id;
+    }
+
+    public void setId( String id )
+    {
+        this.id = id;
+    }
+
+    @Schema( description = "List of user ids that are assigned to this role.")
+    public List<BaseUserInfo> getAssignedUsers( )
+    {
+        return assignedUsers;
+    }
+
+    public void setAssignedUsers( List<BaseUserInfo> assignedUsers )
+    {
+        this.assignedUsers = assignedUsers;
+    }
+
+    public void addAssignedUser( BaseUserInfo id) {
+        this.assignedUsers.add( id );
+    }
+
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleInfo.java
new file mode 100644
index 0000000..aa77836
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleInfo.java
@@ -0,0 +1,182 @@
+package org.apache.archiva.redback.rest.api.model.v2;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.redback.rbac.Role;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Result object for role information.
+ *
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+@XmlRootElement( name = "role" )
+@Schema(name="RoleInfo",description = "Information about role")
+public class RoleInfo extends BaseRoleInfo
+    implements Serializable
+{
+    private static final long serialVersionUID = -3506615158923923845L;
+
+    /**
+     * Field childRoleNames
+     */
+    private List<String> childRoleIds = new ArrayList<>(0);
+
+    /**
+     * Field permissions
+     */
+    private List<Permission> permissions = new ArrayList<>(0);
+
+    /**
+     * The names of all parent roles
+     */
+    private List<String> parentRoleIds = new ArrayList<>(0);
+
+    /**
+     * The ids of all the assigned users.
+     */
+    protected List<BaseUserInfo> assignedUsers = new ArrayList<>( 0 );
+
+    @Schema( description = "List of user ids that are assigned to this role.")
+    public List<BaseUserInfo> getAssignedUsers( )
+    {
+        return assignedUsers;
+    }
+
+    public void setAssignedUsers( List<BaseUserInfo> assignedUsers )
+    {
+        this.assignedUsers = assignedUsers;
+    }
+
+    public void addAssignedUser( BaseUserInfo id) {
+        this.assignedUsers.add( id );
+    }
+
+    public RoleInfo()
+    {
+        // no op
+    }
+
+
+    public static RoleInfo of( Role rbacRole) {
+        RoleInfo role = BaseRoleInfo.of( rbacRole, new RoleInfo( ) );
+        return role;
+    }
+
+    @XmlTransient
+    @Override
+    public List<BaseRoleInfo> getChildren( )
+    {
+        return super.getChildren( );
+    }
+
+    @Schema( description = "List of names of children roles")
+    public List<String> getChildRoleIds()
+    {
+        return childRoleIds;
+    }
+
+    public void setChildRoleIds( List<String> childRoleIds )
+    {
+        this.childRoleIds = childRoleIds;
+    }
+
+    @Schema( description = "List of permissions assigned to this role.")
+    public List<Permission> getPermissions()
+    {
+        return permissions;
+    }
+
+    public void setPermissions( List<Permission> permissions )
+    {
+        this.permissions = permissions;
+    }
+
+    @Schema(description = "List of names of roles that are parents of this role.")
+    public List<String> getParentRoleIds()
+    {
+        return parentRoleIds;
+    }
+
+    public void setParentRoleIds( List<String> parentRoleIds )
+    {
+        this.parentRoleIds = parentRoleIds;
+    }
+
+    @Override
+    public boolean isChild( )
+    {
+        return getParentRoleIds( ).size( ) > 0;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return getName( ) != null ? getName( ).hashCode() : 0;
+    }
+
+    @Override
+    public String toString( )
+    {
+        final StringBuilder sb = new StringBuilder( "RoleInfo{" );
+        sb.append( "name='" ).append( getName( ) ).append( '\'' );
+        sb.append( ", id='" ).append( getId( ) ).append( '\'' );
+        sb.append( ", description='" ).append( getDescription( ) ).append( '\'' );
+        sb.append( ", assignable=" ).append( assignable );
+        sb.append( ", childRoleNames=" ).append( childRoleIds );
+        sb.append( ", permissions=" ).append( permissions );
+        sb.append( ", parentRoleNames=" ).append( parentRoleIds );
+        sb.append( ", assignedUsers=" ).append( assignedUsers );
+        sb.append( ", permanent=" ).append( isPermanent( ) );
+        sb.append( ", modelId='" ).append( modelId ).append( '\'' );
+        sb.append( ", resource='" ).append( resource ).append( '\'' );
+        sb.append( ", isTemplateInstance=" ).append( isTemplateInstance );
+        sb.append( '}' );
+        return sb.toString( );
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        RoleInfo role = (RoleInfo) o;
+
+        return Objects.equals( getName( ), role.getName( ) );
+
+    }
+
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleTree.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleTree.java
new file mode 100644
index 0000000..1af65fd
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleTree.java
@@ -0,0 +1,74 @@
+package org.apache.archiva.redback.rest.api.model.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Information about roles of a application.
+ *
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@Schema(name="RoleTree",description = "Tree of roles defined. The root roles have no parent. Each role may have children recursively.")
+public class RoleTree implements Serializable
+{
+    private static final long serialVersionUID = 6893397477073625729L;
+
+    String userId;
+    Map<String, Application> applications;
+    List<BaseRoleInfo> rootRoles;
+
+    @Schema(description = "The user id for which the assigned flags are set on the roles.")
+    public String getUserId( )
+    {
+        return userId;
+    }
+
+    public void setUserId( String userId )
+    {
+        this.userId = userId;
+    }
+
+    @Schema(description = "Information about the applications that define roles. The keys of the map are the application ids.")
+    public Map<String, Application> getApplications( )
+    {
+        return applications;
+    }
+
+    public void setApplications( Map<String, Application> applications )
+    {
+        this.applications = applications;
+    }
+
+
+    @Schema(description = "The list of roles directly assigned to this application. Roles may contain children roles.")
+    public List<BaseRoleInfo> getRootRoles( )
+    {
+        return rootRoles;
+    }
+
+    public void setRootRoles( List<BaseRoleInfo> rootRoles )
+    {
+        this.rootRoles = rootRoles;
+    }
+
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java
index 274848c..c6043d3 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java
@@ -2,13 +2,11 @@ package org.apache.archiva.redback.rest.api.model.v2;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 
-import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import java.io.Serializable;
 import java.time.Instant;
 import java.time.OffsetDateTime;
 import java.time.ZoneId;
-import java.util.List;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -31,14 +29,11 @@ import java.util.List;
 
 @XmlRootElement( name = "user" )
 @Schema(name="User", description = "User information data")
-public class UserInfo
+public class UserInfo extends BaseUserInfo
     implements Serializable
 {
 
     private static final long serialVersionUID = 822423853981984867L;
-    private String id;
-
-    private String userId;
 
     private String fullName;
 
@@ -125,18 +120,6 @@ public class UserInfo
     }
 
 
-    @Schema( name = "user_id", description = "The user id" )
-    @XmlElement( name = "user_id" )
-    public String getUserId( )
-    {
-        return userId;
-    }
-
-    public void setUserId( String userId )
-    {
-        this.userId = userId;
-    }
-
     @Schema( description = "The full name of the user" )
     public String getFullName( )
     {
@@ -282,22 +265,11 @@ public class UserInfo
         this.validationToken = validationToken;
     }
 
-    @Schema( description = "User id that is unique over all user managers")
-    public String getId( )
-    {
-        return id;
-    }
-
-    public void setId( String id )
-    {
-        this.id = id;
-    }
-
     @Override
     public String toString()
     {
         return "User{" +
-            "username='" + userId + '\'' +
+            "username='" + getUserId( ) + '\'' +
             ", fullName='" + fullName + '\'' +
             ", email='" + email + '\'' +
             ", validated=" + validated +
@@ -328,7 +300,7 @@ public class UserInfo
 
         UserInfo user = (UserInfo) o;
 
-        if ( !userId.equals( user.userId ) )
+        if ( !getUserId( ).equals( user.getUserId( ) ) )
         {
             return false;
         }
@@ -339,6 +311,6 @@ public class UserInfo
     @Override
     public int hashCode()
     {
-        return userId.hashCode();
+        return getUserId( ).hashCode();
     }
 }
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java
index 82143f4..bbb2502 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java
@@ -20,6 +20,7 @@ package org.apache.archiva.redback.rest.api.services.v2;
 
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.headers.Header;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -31,7 +32,6 @@ import org.apache.archiva.redback.rest.api.model.ActionStatus;
 import org.apache.archiva.redback.rest.api.model.Application;
 import org.apache.archiva.redback.rest.api.model.ApplicationRoles;
 import org.apache.archiva.redback.rest.api.model.RedbackRestError;
-import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus;
 import org.apache.archiva.redback.rest.api.model.Role;
 import org.apache.archiva.redback.rest.api.model.User;
 import org.apache.archiva.redback.rest.api.model.VerificationStatus;
@@ -40,14 +40,19 @@ import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
 import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
 
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
+import javax.ws.rs.PATCH;
 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.QueryParam;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 import java.util.List;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -60,15 +65,12 @@ import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT;
 @Tag(name = "v2")
 @Tag(name = "v2/Roles")
 @SecurityRequirement(name = "BearerAuth")
-public interface RoleManagementService
+public interface RoleService
 {
 
-    /**
-     * @since 2.0
-     */
     @Path( "" )
     @GET
-    @Produces( { MediaType.APPLICATION_JSON } )
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
     @Operation( summary = "Returns all roles defined. The result is paged.",
         parameters = {
@@ -99,75 +101,209 @@ public interface RoleManagementService
                                        @QueryParam("order") @DefaultValue( "asc" ) String order)
         throws RedbackServiceException;
 
-    @Path( "createTemplatedRole" )
+    @Path( "{roleId}" )
     @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus createTemplatedRole( @QueryParam( "templateId" ) String templateId,
-                                      @QueryParam( "resource" ) String resource )
+    @Operation( summary = "Returns information about a specific role. Use HTTP HEAD method for checking, if the resource exists.",
+        security = {
+            @SecurityRequirement(
+                name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If role was found in the database",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RoleInfo.class))
+            ),
+            @ApiResponse( responseCode = "404", description = "Role does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class ))
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    RoleInfo getRole( @PathParam( "roleId" ) String roleId )
         throws RedbackServiceException;
 
-    /**
-     * removes a role corresponding to the role Id that was manufactured with the given resource
-     *
-     * it also removes any user assignments for that role
-     *
-     * @param templateId
-     * @param resource
-     */
-    @Path( "removeTemplatedRole" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Path( "{roleId}" )
+    @HEAD
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus removeTemplatedRole( @QueryParam( "templateId" ) String templateId,
-                                 @QueryParam( "resource" ) String resource )
+    @Operation( summary = "Returns information about a specific role. Use HTTP HEAD method for checking, if the resource exists.",
+        security = {
+            @SecurityRequirement(
+                name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If role was found in the database"
+            ),
+            @ApiResponse( responseCode = "404", description = "Role does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class ))
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    Response checkRole( @PathParam( "roleId" ) String roleId )
         throws RedbackServiceException;
 
 
     /**
-     * allows for a role coming from a template to be renamed effectively swapping out the bits of it that
-     * were labeled with the oldResource with the newResource
-     *
-     * it also manages any user assignments for that role
+     * Moves a templated role from one resource to another resource
+     * @TODO: Not sure, if it makes sense to keep the child template at the source. Shouldn't we move the childs too?
      *
-     * @param templateId
-     * @param oldResource
-     * @param newResource
+     * @param templateId the template identifier
+     * @param oldResource the resource of the current role
+     * @param newResource the resource of the new role
      */
-    @Path( "updateRole" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Path( "template/{templateId}/{oldResource}/moveto/{newResource}" )
+    @POST
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus updateRole( @QueryParam( "templateId" ) String templateId, @QueryParam( "oldResource" ) String oldResource,
-                        @QueryParam( "newResource" ) String newResource )
+    @Operation( summary = "Moves a templated role from one resource to another resource. If the template has child templates," +
+        " then child instances will be created on for the destination resource. But the child instances on the source are not deleted.",
+        security = {
+            @SecurityRequirement(
+                name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "201",
+                description = "If user creation was successful",
+                headers = {
+                    @Header( name="Location", description = "The URL of the moved role", schema = @Schema(type="string"))
+                },
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RoleInfo.class))
+            ),
+            @ApiResponse( responseCode = "404", description = "The source role does not exist" ),
+            @ApiResponse( responseCode = "303", description = "The destination role exists already" ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission to move the role.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+
+        }
+    )
+    RoleInfo moveTemplatedRole( @PathParam( "templateId" ) String templateId, @PathParam( "oldResource" ) String oldResource,
+                                @PathParam( "newResource" ) String newResource )
+        throws RedbackServiceException;
+
+    @Path( "template/{templateId}/{resource}" )
+    @HEAD
+    @Produces( { APPLICATION_JSON} )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    @Operation( summary = "Checks, if a instance of the role template exists for the given resource",
+        security = {
+            @SecurityRequirement(
+                name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the role instance exists"
+            ),
+            @ApiResponse( responseCode = "404", description = "Role does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class ))
+            ),
+            @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    Response checkTemplateRole( @PathParam( "templateId" ) String templateId,
+                                @PathParam( "resource" ) String resource )
         throws RedbackServiceException;
 
+    @Path( "template/{templateId}/{resource}" )
+    @PUT
+    @Produces( { APPLICATION_JSON } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    @Operation( summary = "Creates a role instance from a template for the given resource",
+        security = {
+            @SecurityRequirement(
+                name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION
+            )
+        },
+        responses = {
+            @ApiResponse( responseCode = "201",
+                description = "If user creation was successful",
+                headers = {
+                    @Header( name = "Location", description = "The URL of the created role", schema = @Schema( type = "string" ) )
+                },
+                content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = RoleInfo.class ) )
+            ),
+            @ApiResponse( responseCode = "200",
+                description = "If the role instance existed before and was updated",
+                headers = {
+                    @Header( name = "Location", description = "The URL of the updated role", schema = @Schema( type = "string" ) )
+                },
+                content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = RoleInfo.class ) )
+            ),
+            @ApiResponse( responseCode = "404", description = "The template does not exist" ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role creation.",
+                content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = RedbackRestError.class ) ) )
+
+        }
+    )
+    RoleInfo createTemplatedRole( @PathParam( "templateId" ) String templateId,
+                                      @PathParam( "resource" ) String resource )
+        throws RedbackServiceException;
 
     /**
-     * Assigns the role indicated by the roleId to the given principal
+     * Removes a role corresponding to the role Id that was manufactured with the given resource
+     * it also removes any user assignments for that role
      *
-     * @param roleId
-     * @param principal
+     * @param templateId
+     * @param resource
      */
-    @Path( "assignRole" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Path( "template/{templateId}/{resource}" )
+    @DELETE
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus assignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal )
+    @Operation( summary = "Deletes a role template instance",
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If role deletion was successful"
+            ),
+            @ApiResponse( responseCode = "404", description = "Role does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for deletion.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    Response removeTemplatedRole( @PathParam(  "templateId" ) String templateId,
+                                 @PathParam( "resource" ) String resource )
         throws RedbackServiceException;
 
+
     /**
-     * Assigns the role indicated by the roleName to the given principal
+     * Assigns the role indicated by the roleId to the given principal
      *
-     * @param roleName
-     * @param principal
-     * @throws RedbackServiceException
+     * @param roleId
+     * @param userId
      */
-    @Path( "assignRoleByName" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Path( "{roleId}/assign/{userId}" )
+    @PUT
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus assignRoleByName( @QueryParam( "roleName" ) String roleName, @QueryParam( "principal" ) String principal )
+    @Operation( summary = "Assigns a role to a given user",
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the role was assigned"
+            ),
+            @ApiResponse( responseCode = "404", description = "Role does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    RoleInfo assignRole( @PathParam( "roleId" ) String roleId, @PathParam( "userId" ) String userId )
         throws RedbackServiceException;
 
     /**
@@ -179,11 +315,25 @@ public interface RoleManagementService
      * @param resource
      * @param principal
      */
-    @Path( "assignTemplatedRole" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Path( "template/{templateId}/{resource}/assign/{userId}" )
+    @POST
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus assignTemplatedRole( @QueryParam( "templateId" ) String templateId,
+    @Operation( summary = "Assigns a template role instance to a given user",
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the role instance was assigned"
+            ),
+            @ApiResponse( responseCode = "404", description = "Role instance does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    RoleInfo assignTemplatedRole( @QueryParam( "templateId" ) String templateId,
                                  @QueryParam( "resource" ) String resource,
                                  @QueryParam( "principal" ) String principal )
         throws RedbackServiceException;
@@ -195,161 +345,53 @@ public interface RoleManagementService
      * @param principal
      * @throws RedbackServiceException
      */
-    @Path( "unassignRole" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus unassignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal )
-        throws RedbackServiceException;
-
-    /**
-     * Unassigns the role indicated by the role name from the given principal
-     *
-     * @param roleName
-     * @param principal
-     * @throws RedbackServiceException
-     */
-    @Path( "unassignRoleByName" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Path( "{roleId}/{userId}" )
+    @DELETE
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus unassignRoleByName( @QueryParam( "roleName" ) String roleName, @QueryParam( "principal" ) String principal )
-        throws RedbackServiceException;
-
-    /**
-     * true of a role exists with the given roleId
-     *
-     * @param roleId
-     * @return
-     * @throws RedbackServiceException
-     */
-    @Path( "roleExists" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    AvailabilityStatus roleExists( @QueryParam( "roleId" ) String roleId )
-        throws RedbackServiceException;
-
-    /**
-     * true of a role exists with the given roleId
-     *
-     * @param templateId
-     * @param resource
-     * @return
-     * @throws RedbackServiceException
-     */
-    @Path( "templatedRoleExists" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    AvailabilityStatus templatedRoleExists( @QueryParam( "templateId" ) String templateId,
-                                 @QueryParam( "resource" ) String resource )
+    @Operation( summary = "Removes a role assignment for the given role and user",
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the role assignment was removed"
+            ),
+            @ApiResponse( responseCode = "404", description = "Role instance does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    RoleInfo unassignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal )
         throws RedbackServiceException;
 
 
     /**
-     * Check a role template is complete in the RBAC store.
+     * Updates a role. Attributes that are empty or null will be ignored.
      *
-     * @param templateId the templated role
-     * @param resource   the resource to verify
-     * @throws RedbackServiceException
-     */
-    @Path( "verifyTemplatedRole" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    VerificationStatus verifyTemplatedRole( @QueryParam( "templateId" ) String templateId,
-                                            @QueryParam( "resource" ) String resource )
-        throws RedbackServiceException;
-
-    /**
-     * @since 1.4
-     */
-    @Path( "getEffectivelyAssignedRoles/{username}" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    List<Role> getEffectivelyAssignedRoles( @PathParam( "username" ) String username )
-        throws RedbackServiceException;
-
-
-
-
-    /**
-     * @since 2.0
-     */
-    @Path( "detailledAllRoles" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    List<Role> getDetailedAllRoles()
-        throws RedbackServiceException;
-
-
-    /**
-     * @since 2.0
-     */
-    @Path( "getApplications/{username}" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    List<Application> getApplications( @PathParam( "username" ) String username )
-        throws RedbackServiceException;
-
-
-    /**
-     * @since 2.0
+     * @since 3.0
      */
-    @Path( "getRole/{roleName}" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Path( "{roleId}" )
+    @PATCH
+    @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    Role getRole( @PathParam( "roleName" ) String roleName )
-        throws RedbackServiceException;
-
-    /**
-     * @since 2.0
-     */
-    @Path( "updateRoleDescription" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus updateRoleDescription( @QueryParam( "roleName" ) String roleName,
-                                   @QueryParam( "roleDescription" ) String description )
-        throws RedbackServiceException;
-
-    /**
-     * update users assigned to a role
-     * @since 2.0
-     */
-    @Path( "updateRoleUsers" )
-    @POST
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
-    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus updateRoleUsers( Role role )
-        throws RedbackServiceException;
-
-    /**
-     * @since 2.0
-     */
-    @Path( "getApplicationRoles/{username}" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    List<ApplicationRoles> getApplicationRoles( @PathParam( "username" ) String username )
-        throws RedbackServiceException;
+    @Operation( summary = "Creates or updates the given role",
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the update was successful"
+            ),
+            @ApiResponse( responseCode = "404", description = "Role does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    RoleInfo updateRole( @QueryParam("roleId") String roleId, Role role )
+    throws RedbackServiceException;
 
-    /**
-     * update roles assigned to a user
-     * @since 2.0
-     */
-    @Path( "updateUserRoles" )
-    @POST
-    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
-    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
-    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    ActionStatus updateUserRoles( User user )
-        throws RedbackServiceException;
 
 }
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
index f6415bb..243ca6e 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
@@ -31,12 +31,15 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import org.apache.archiva.redback.authorization.RedbackAuthorization;
 import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
 import org.apache.archiva.redback.rest.api.model.ActionStatus;
+import org.apache.archiva.redback.rest.api.model.Application;
 import org.apache.archiva.redback.rest.api.model.RedbackRestError;
-import org.apache.archiva.redback.rest.api.model.v2.Permission;
+import org.apache.archiva.redback.rest.api.model.v2.RoleTree;
 import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus;
 import org.apache.archiva.redback.rest.api.model.v2.PagedResult;
+import org.apache.archiva.redback.rest.api.model.v2.Permission;
 import org.apache.archiva.redback.rest.api.model.v2.PingResult;
 import org.apache.archiva.redback.rest.api.model.v2.RegistrationKey;
+import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
 import org.apache.archiva.redback.rest.api.model.v2.SelfUserData;
 import org.apache.archiva.redback.rest.api.model.v2.User;
 import org.apache.archiva.redback.rest.api.model.v2.UserInfo;
@@ -54,6 +57,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
 import java.util.Collection;
 import java.util.List;
 
@@ -594,4 +598,59 @@ public interface UserService
     )
     VerificationStatus validateUserRegistration( @PathParam( "userId" ) String userId, @PathParam( "key" ) String key )
         throws RedbackServiceException;
+
+
+    /**
+     * Returns all roles for a given user id. Recurses all child roles.
+     *
+     * @since 3.0
+     */
+    @Path( "{userId}/roles" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    @Operation( summary = "Returns a list of all roles effectively assigned to the given user.",
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "The list of roles assigned to the given user",
+                content = @Content(mediaType = APPLICATION_JSON, array = @ArraySchema(schema =
+                @Schema(implementation = org.apache.archiva.redback.rest.api.model.v2.RoleInfo.class )))
+            ),
+            @ApiResponse( responseCode = "404", description = "User does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for retrieving the information.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    List<RoleInfo> getEffectivelyAssignedRoles( @PathParam( "userId" ) String username )
+        throws RedbackServiceException;
+
+
+    /**
+     * @since 3.0
+     */
+    @Path( "{userId}/roletree" )
+    @GET
+    @Produces( { APPLICATION_JSON } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    @Operation( summary = "Returns a list of all roles that are assigned, or can be assigned to the given user. "+
+        "This method sets the 'assigned' flag on all returned role objects.",
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "The list of roles separated by application that are assigned or assignable for the given user",
+                content = @Content(mediaType = APPLICATION_JSON, array = @ArraySchema(schema =
+                @Schema(implementation = Application.class )))
+            ),
+            @ApiResponse( responseCode = "404", description = "User does not exist",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ),
+            @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for retrieving the information.",
+                content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
+        }
+    )
+    RoleTree getRoleTree( @PathParam( "userId" ) String username )
+        throws RedbackServiceException;
+
 }
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java
index a80439b..40ec9a3 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java
@@ -29,7 +29,6 @@ import org.apache.archiva.redback.rbac.UserAssignment;
 import org.apache.archiva.redback.rest.api.model.ActionStatus;
 import org.apache.archiva.redback.rest.api.model.Application;
 import org.apache.archiva.redback.rest.api.model.ApplicationRoles;
-import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus;
 import org.apache.archiva.redback.rest.api.model.ErrorMessage;
 import org.apache.archiva.redback.rest.api.model.Role;
 import org.apache.archiva.redback.rest.api.model.RoleTemplate;
@@ -127,7 +126,7 @@ public class DefaultRoleManagementService
     {
         try
         {
-            roleManager.updateRole( templateId, oldResource, newResource );
+            roleManager.moveTemplatedRole( templateId, oldResource, newResource );
         }
         catch ( RoleManagerException e )
         {
@@ -391,7 +390,7 @@ public class DefaultRoleManagementService
             org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRole( roleName );
             Role role = new Role( rbacRole );
 
-            Map<String, ? extends org.apache.archiva.redback.rbac.Role> parentRoles = rbacManager.getParentRoles( rbacRole );
+            Map<String, ? extends org.apache.archiva.redback.rbac.Role> parentRoles = rbacManager.getParentRoleNames( rbacRole );
             for ( String parentRoleName : parentRoles.keySet() )
             {
                 role.getParentRoleNames().add( parentRoleName );
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/BaseRedbackService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/BaseRedbackService.java
new file mode 100644
index 0000000..63318bb
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/BaseRedbackService.java
@@ -0,0 +1,145 @@
+package org.apache.archiva.redback.rest.services.v2;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.redback.rbac.RBACManager;
+import org.apache.archiva.redback.rbac.RbacManagerException;
+import org.apache.archiva.redback.rbac.Role;
+import org.apache.archiva.redback.rest.api.MessageKeys;
+import org.apache.archiva.redback.rest.api.model.ErrorMessage;
+import org.apache.archiva.redback.rest.api.model.v2.BaseUserInfo;
+import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Named;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+public class BaseRedbackService
+{
+    private static final Logger log = LoggerFactory.getLogger( BaseRedbackService.class );
+
+    protected RBACManager rbacManager;
+    protected UserManager userManager;
+
+    public BaseRedbackService( @Named( value = "rbacManager#default" ) RBACManager rbacManager, @Named( value = "userManager#default" ) UserManager userManager )
+    {
+        this.rbacManager = rbacManager;
+        this.userManager = userManager;
+    }
+
+    protected RoleInfo getRoleInfo( org.apache.archiva.redback.rbac.Role rbacRole ) throws RedbackServiceException
+    {
+        try
+        {
+            RoleInfo role = RoleInfo.of( rbacRole );
+            role.setParentRoleIds( getParentRoles( rbacRole ) );
+            role.setChildRoleIds( getChildRoles( rbacRole ) );
+            role.setAssignedUsers( getAssignedUsersRecursive( rbacRole ) );
+            return role;
+        }
+        catch ( RbacManagerException e )
+        {
+            log.error( "Error while retrieving role information {}", e.getMessage( ), e );
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+    protected List<String> getParentRoles( org.apache.archiva.redback.rbac.Role rbacRole ) throws RbacManagerException
+    {
+        return new ArrayList<>( rbacManager.getParentRoleIds( rbacRole ).keySet( ));
+    }
+
+    protected List<String> getChildRoles( Role rbacRole) throws RbacManagerException
+    {
+        return new ArrayList<>( rbacManager.getChildRoleIds( rbacRole ).keySet( ) );
+    }
+
+    protected List<BaseUserInfo> getAssignedUsersRecursive( org.apache.archiva.redback.rbac.Role rbacRole ) throws RbacManagerException
+    {
+        try
+        {
+            return rbacManager.getUserAssignmentsForRoles( recurseRoles( rbacRole ).map( role -> role.getName( ) ).collect( Collectors.toList( ) ) )
+                .stream( ).map( assignment -> getUserInfo( assignment.getPrincipal( ) ) ).collect( Collectors.toList( ) );
+        }
+        catch ( RuntimeException e )
+        {
+            log.error( "Could not recurse roles for assignments {}", e.getMessage( ) );
+            throw new RbacManagerException( e.getCause( ) );
+        }
+    }
+
+    private Stream<Role> recurseRoles( Role startRole )
+    {
+        return Stream.concat( Stream.of( startRole ), getParentRoleStream( startRole ).flatMap( this::recurseRoles ) ).distinct( );
+    }
+
+    private Stream<? extends Role> getParentRoleStream( Role role )
+    {
+        try
+        {
+            return rbacManager.getParentRoleNames( role ).values( ).stream( );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+    BaseUserInfo getUserInfo( String userId )
+    {
+        try
+        {
+            User user = userManager.findUser( userId );
+            return new BaseUserInfo( user.getId( ), user.getUsername( ) );
+        }
+        catch ( UserManagerException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+    protected Optional<RoleInfo> getRoleInfoOptional( Role rbacRole )
+    {
+        try
+        {
+            RoleInfo role = RoleInfo.of( rbacRole );
+            role.setParentRoleIds( getParentRoles( rbacRole ) );
+            role.setChildRoleIds( getChildRoles( rbacRole ) );
+            role.setAssignedUsers( getAssignedUsersRecursive( rbacRole ) );
+            return Optional.of( role );
+        }
+        catch ( RbacManagerException e )
+        {
+            log.error( "Error while retrieving role information {}", e.getMessage( ), e );
+            return Optional.empty( );
+        }
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java
index c512194..a495d42 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java
@@ -61,6 +61,7 @@ import java.util.stream.Collectors;
  * @author Martin Stockhammer
  * @since 3.0
  */
+@SuppressWarnings( "SpringJavaAutowiredFieldsWarningInspection" )
 @Service("v2.groupService#rest")
 public class DefaultGroupService
     implements GroupService
@@ -85,7 +86,10 @@ public class DefaultGroupService
     @Named(value = "ldapConnectionFactory#configurable")
     private LdapConnectionFactory ldapConnectionFactory;
 
-    private static final Group getGroupFromLdap( LdapGroup ldapGroup ) {
+    public DefaultGroupService( ) {
+    }
+
+    private static Group getGroupFromLdap( LdapGroup ldapGroup ) {
         Group group = new Group( );
         group.setName( ldapGroup.getName() );
         group.setUniqueName( ldapGroup.getDn() );
@@ -128,7 +132,7 @@ public class DefaultGroupService
      * be found, it will set "" for the uniqueName
      *
      * @return the list of mapping
-     * @throws RedbackServiceException
+     * @throws RedbackServiceException if there was an error retrieving the mapping data
      */
     @Override
     public List<GroupMapping> getGroupMappings()
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultRoleService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultRoleService.java
new file mode 100644
index 0000000..a4077b7
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultRoleService.java
@@ -0,0 +1,434 @@
+package org.apache.archiva.redback.rest.services.v2;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
+import org.apache.archiva.redback.integration.util.RoleSorter;
+import org.apache.archiva.redback.rbac.Permission;
+import org.apache.archiva.redback.rbac.RBACManager;
+import org.apache.archiva.redback.rbac.RbacManagerException;
+import org.apache.archiva.redback.rbac.RbacObjectNotFoundException;
+import org.apache.archiva.redback.rbac.Resource;
+import org.apache.archiva.redback.rest.api.MessageKeys;
+import org.apache.archiva.redback.rest.api.model.ErrorMessage;
+import org.apache.archiva.redback.rest.api.model.Role;
+import org.apache.archiva.redback.rest.api.model.RoleTemplate;
+import org.apache.archiva.redback.rest.api.model.v2.PagedResult;
+import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+import org.apache.archiva.redback.rest.api.services.v2.RoleService;
+import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
+import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
+import org.apache.archiva.redback.role.RoleExistsException;
+import org.apache.archiva.redback.role.RoleManager;
+import org.apache.archiva.redback.role.RoleManagerException;
+import org.apache.archiva.redback.role.RoleNotFoundException;
+import org.apache.archiva.redback.role.model.ModelTemplate;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.UserNotFoundException;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.3
+ */
+@Service("v2.roleService#rest")
+public class DefaultRoleService extends BaseRedbackService
+    implements RoleService
+{
+
+    private Logger log = LoggerFactory.getLogger( DefaultRoleService.class );
+
+    private RoleManager roleManager;
+
+    @Context
+    private HttpServletRequest httpServletRequest;
+
+    @Context
+    private HttpServletResponse httpServletResponse;
+
+    @Context
+    private UriInfo uriInfo;
+
+    private static final String[] DEFAULT_SEARCH_FIELDS = {"name", "description"};
+    private static final Map<String, BiPredicate<String, org.apache.archiva.redback.rbac.Role>> FILTER_MAP = new HashMap<>( );
+    private static final Map<String, Comparator<org.apache.archiva.redback.rbac.Role>> ORDER_MAP = new HashMap<>( );
+    private static final QueryHelper<org.apache.archiva.redback.rbac.Role> QUERY_HELPER;
+
+    static
+    {
+
+        QUERY_HELPER = new QueryHelper<>( FILTER_MAP, ORDER_MAP, DEFAULT_SEARCH_FIELDS );
+        QUERY_HELPER.addStringFilter( "name", org.apache.archiva.redback.rbac.Role::getName );
+        QUERY_HELPER.addStringFilter( "description", org.apache.archiva.redback.rbac.Role::getDescription );
+        QUERY_HELPER.addBooleanFilter( "assignable", org.apache.archiva.redback.rbac.Role::isAssignable );
+
+        // The simple Comparator.comparing(attribute) is not null safe
+        // As there are attributes that may have a null value, we have to use a comparator with nullsLast(naturalOrder)
+        // and the wrapping Comparator.nullsLast(Comparator.comparing(attribute)) does not work, because the attribute is not checked by the nullsLast-Comparator
+        QUERY_HELPER.addNullsafeFieldComparator( "name", org.apache.archiva.redback.rbac.Role::getName );
+        QUERY_HELPER.addNullsafeFieldComparator( "id", org.apache.archiva.redback.rbac.Role::getId );
+        QUERY_HELPER.addNullsafeFieldComparator( "resource", org.apache.archiva.redback.rbac.Role::getResource );
+        QUERY_HELPER.addNullsafeFieldComparator( "assignable", org.apache.archiva.redback.rbac.Role::isAssignable );
+        QUERY_HELPER.addNullsafeFieldComparator( "template_instance", org.apache.archiva.redback.rbac.Role::isTemplateInstance );
+    }
+
+    @Inject
+    public DefaultRoleService( RoleManager roleManager,
+                               @Named(value = "rbacManager#default") RBACManager rbacManager,
+                               @Named(value = "userManager#default") UserManager userManager )
+    {
+        super( rbacManager, userManager );
+        this.roleManager = roleManager;
+
+        log.debug( "use rbacManager impl: {}", rbacManager.getClass().getName() );
+        log.debug( "use userManager impl: {}", userManager.getClass().getName() );
+    }
+
+    @Override
+    public PagedResult<RoleInfo> getAllRoles( String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order ) throws RedbackServiceException
+    {
+        boolean ascending = !"desc".equals( order );
+        try
+        {
+            // UserQuery does not work here, because the configurable user manager does only return the query for
+            // the first user manager in the list. So we have to fetch the whole role list
+            List<? extends org.apache.archiva.redback.rbac.Role> rawRoles = rbacManager.getAllRoles( );
+            Predicate<org.apache.archiva.redback.rbac.Role> filter = QUERY_HELPER.getQueryFilter( searchTerm );
+            long size = rawRoles.stream( ).filter( filter ).count( );
+            List<RoleInfo> users = rawRoles.stream( )
+                .filter( filter )
+                .sorted( QUERY_HELPER.getComparator( orderBy, ascending ) ).skip( offset ).limit( limit )
+                .map( role -> {
+                    try
+                    {
+                        return Optional.of( getRoleInfo( role ) );
+                    }
+                    catch ( RedbackServiceException e )
+                    {
+                        return Optional.<RoleInfo>empty();
+                    }
+                } ).filter(Optional::isPresent)
+                .map(Optional::get)
+                .collect( Collectors.toList( ) );
+            return new PagedResult<>( (int) size, offset, limit, users );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL , e.getMessage( )) );
+        }
+
+    }
+
+    @Override
+    public RoleInfo getRole( String roleId ) throws RedbackServiceException
+    {
+        try
+        {
+            org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRoleById( roleId );
+            RoleInfo role = getRoleInfo( rbacRole );
+            return role;
+        }
+        catch ( RbacObjectNotFoundException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, roleId ), 404 );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+    @Override
+    public Response checkRole( String roleId ) throws RedbackServiceException
+    {
+        try
+        {
+            org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRoleById( roleId );
+            if (rbacRole==null) {
+                return Response.status( 404 ).build();
+            } else
+            {
+                return Response.ok( ).build( );
+            }
+        }
+        catch ( RbacObjectNotFoundException e )
+        {
+            return Response.status( 404 ).build();
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+
+
+    @Override
+    public RoleInfo moveTemplatedRole( String templateId, String oldResource, String newResource )
+        throws RedbackServiceException
+    {
+        try
+        {
+            if (StringUtils.isEmpty( templateId ) || StringUtils.isEmpty( oldResource ) || StringUtils.isEmpty( newResource )) {
+                throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND ), 404 );
+            }
+            boolean sourceExists = roleManager.templatedRoleExists( templateId, oldResource );
+            if (!sourceExists) {
+                throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_NOT_FOUND, templateId, oldResource ), 404 );
+            }
+            boolean destExists = roleManager.templatedRoleExists( templateId, newResource );
+            if (destExists) {
+                httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(newResource).build(  ).normalize().toString() );
+                throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_EXISTS, templateId, newResource ), 303 );
+            }
+            String roleId = roleManager.moveTemplatedRole( templateId, oldResource, newResource );
+            httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(newResource).build(  ).normalize().toString() );
+            httpServletResponse.setStatus( 201 );
+            return getRoleInfo( rbacManager.getRoleById( roleId ) );
+        }
+        catch ( RoleExistsException e ) {
+            httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(newResource).build(  ).normalize().toString() );
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_EXISTS, templateId, newResource ), 303 );
+        }
+        catch ( RoleManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+
+    @Override
+    public Response checkTemplateRole( String templateId, String resource )
+        throws RedbackServiceException
+    {
+        try
+        {
+            if (roleManager.templatedRoleExists( templateId, resource )) {
+                return Response.ok( ).build( );
+            } else {
+                return Response.status( 404 ).build();
+            }
+        }
+        catch ( RoleManagerException e )
+        {
+            throw new RedbackServiceException( e.getMessage() );
+        }
+
+    }
+
+    @Override
+    public RoleInfo createTemplatedRole( String templateId, String resource )
+        throws RedbackServiceException
+    {
+        if (StringUtils.isEmpty( templateId )) {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND ), 404 );
+        }
+        if (StringUtils.isEmpty( resource )) {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND ), 404 );
+        }
+        try
+        {
+            boolean exists = roleManager.templatedRoleExists( templateId, resource );
+            String roleId = roleManager.createTemplatedRole( templateId, resource );
+            httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(roleId).build(  ).normalize().toString() );
+            if (exists)
+            {
+                httpServletResponse.setStatus( 200 );
+            } else {
+                httpServletResponse.setStatus( 201 );
+            }
+            return getRoleInfo( rbacManager.getRoleById( roleId ) );
+        } catch (RoleNotFoundException e) {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, templateId, resource ), 404 );
+        } catch (RoleExistsException e) {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_EXISTS, templateId, resource ), 303 );
+        }
+        catch ( RoleManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+    @Override
+    public Response removeTemplatedRole( String templateId, String resource )
+        throws RedbackServiceException
+    {
+
+        try
+        {
+            roleManager.removeTemplatedRole( templateId, resource );
+            return Response.ok( ).build( );
+        }
+        catch ( RoleNotFoundException e ) {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_NOT_FOUND, templateId, resource ), 404 );
+        }
+        catch ( RoleManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+
+
+    @Override
+    public RoleInfo assignRole( String roleId, String userId )
+        throws RedbackServiceException
+    {
+        try
+        {
+            userManager.findUser( userId );
+            roleManager.assignRole( roleId, userId );
+            return getRoleInfo( rbacManager.getRoleById( roleId ) );
+        }
+        catch ( RoleNotFoundException e ) {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, e.getMessage( ) ), 404 );
+        }
+        catch ( RoleManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) );
+        }
+        catch ( UserNotFoundException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USER_NOT_FOUND, e.getMessage( ) ), 404 );
+        }
+        catch ( UserManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USERMANAGER_FAIL, e.getMessage( ) ) );
+        }
+        catch ( RbacObjectNotFoundException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+
+    @Override
+    public RoleInfo assignTemplatedRole( String templateId, String resource, String principal )
+        throws RedbackServiceException
+    {
+        try
+        {
+            roleManager.assignTemplatedRole( templateId, resource, principal );
+        }
+        catch ( RoleManagerException e )
+        {
+            throw new RedbackServiceException( e.getMessage() );
+        }
+        return null;
+    }
+
+    @Override
+    public RoleInfo unassignRole( String roleId, String principal )
+        throws RedbackServiceException
+    {
+        try
+        {
+            roleManager.unassignRole( roleId, principal );
+        }
+        catch ( RoleManagerException e )
+        {
+            throw new RedbackServiceException( e.getMessage() );
+        }
+        return null;
+    }
+
+    @Override
+    public RoleInfo updateRole( String roleId, Role role ) throws RedbackServiceException
+    {
+        return null;
+    }
+
+
+
+//    public List<Role> getEffectivelyAssignedRoles( String username )
+//        throws RedbackServiceException
+//    {
+//        if ( StringUtils.isEmpty( username ) )
+//        {
+//            throw new RedbackServiceException( new ErrorMessage( "user.cannot.be.null" ) );
+//        }
+//        try
+//        {
+//            List<? extends org.apache.archiva.redback.rbac.Role> roles =
+//                filterAssignableRoles( rbacManager.getEffectivelyAssignedRoles( username ) );
+//
+//            List<Role> effectivelyAssignedRoles = new ArrayList<Role>( roles.size() );
+//
+//            for ( org.apache.archiva.redback.rbac.Role r : roles )
+//            {
+//                effectivelyAssignedRoles.add( new Role( r ) );
+//            }
+//
+//            Collections.sort( effectivelyAssignedRoles, RoleComparator.INSTANCE  );
+//
+//            return effectivelyAssignedRoles;
+//        }
+//        catch ( RbacManagerException rme )
+//        {
+//            // ignore, this can happen when the user has no roles assigned
+//        }
+//        return new ArrayList<Role>( 0 );
+//    }
+
+
+    //----------------------------------------------------------------
+    // Internal methods
+    //----------------------------------------------------------------
+
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java
index b465809..5561d5c 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java
@@ -41,17 +41,22 @@ import org.apache.archiva.redback.policy.PasswordRuleViolationException;
 import org.apache.archiva.redback.policy.UserSecurityPolicy;
 import org.apache.archiva.redback.rbac.RBACManager;
 import org.apache.archiva.redback.rbac.RbacManagerException;
+import org.apache.archiva.redback.rbac.Role;
 import org.apache.archiva.redback.rbac.UserAssignment;
 import org.apache.archiva.redback.rest.api.MessageKeys;
 import org.apache.archiva.redback.rest.api.model.ActionStatus;
 import org.apache.archiva.redback.rest.api.model.ErrorMessage;
+import org.apache.archiva.redback.rest.api.model.v2.Application;
 import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus;
+import org.apache.archiva.redback.rest.api.model.v2.BaseRoleInfo;
 import org.apache.archiva.redback.rest.api.model.v2.Operation;
 import org.apache.archiva.redback.rest.api.model.v2.PagedResult;
 import org.apache.archiva.redback.rest.api.model.v2.Permission;
 import org.apache.archiva.redback.rest.api.model.v2.PingResult;
 import org.apache.archiva.redback.rest.api.model.v2.RegistrationKey;
 import org.apache.archiva.redback.rest.api.model.v2.Resource;
+import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
+import org.apache.archiva.redback.rest.api.model.v2.RoleTree;
 import org.apache.archiva.redback.rest.api.model.v2.SelfUserData;
 import org.apache.archiva.redback.rest.api.model.v2.User;
 import org.apache.archiva.redback.rest.api.model.v2.UserInfo;
@@ -65,6 +70,7 @@ import org.apache.archiva.redback.rest.services.interceptors.RedbackPrincipal;
 import org.apache.archiva.redback.rest.services.utils.PasswordValidator;
 import org.apache.archiva.redback.role.RoleManager;
 import org.apache.archiva.redback.role.RoleManagerException;
+import org.apache.archiva.redback.role.model.ModelApplication;
 import org.apache.archiva.redback.system.SecuritySession;
 import org.apache.archiva.redback.system.SecuritySystem;
 import org.apache.archiva.redback.users.UserManager;
@@ -91,17 +97,19 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @Service( "v2.userService#rest" )
-public class DefaultUserService
+public class DefaultUserService extends BaseRedbackService
     implements UserService
 {
 
@@ -113,6 +121,7 @@ public class DefaultUserService
     private static final String[] DEFAULT_SEARCH_FIELDS = {"user_id", "full_name", "email"};
     private static final Map<String, BiPredicate<String, org.apache.archiva.redback.users.User>> FILTER_MAP = new HashMap<>( );
     private static final Map<String, Comparator<org.apache.archiva.redback.users.User>> ORDER_MAP = new HashMap<>( );
+    private static final QueryHelper<org.apache.archiva.redback.users.User> QUERY_HELPER;
 
     static
     {
@@ -133,10 +142,9 @@ public class DefaultUserService
         FILTER_MAP.put( "user_id", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getUsername( ), q ) );
         FILTER_MAP.put( "full_name", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getFullName( ), q ) );
         FILTER_MAP.put( "email", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getEmail( ), q ) );
-    }
-
 
-    private UserManager userManager;
+        QUERY_HELPER = new QueryHelper<>( FILTER_MAP, ORDER_MAP, DEFAULT_SEARCH_FIELDS );
+    }
 
     private SecuritySystem securitySystem;
 
@@ -174,9 +182,10 @@ public class DefaultUserService
     @Inject
     private Mailer mailer;
 
+
     @Inject
-    @Named( value = "rbacManager#default" )
-    private RBACManager rbacManager;
+    @Named( value = "v2.roleService#rest" )
+    private DefaultRoleService roleManagementService;
 
     private HttpAuthenticator httpAuthenticator;
 
@@ -196,10 +205,11 @@ public class DefaultUserService
     private SecurityContext securityContext;
 
     @Inject
-    public DefaultUserService( @Named( value = "userManager#default" ) UserManager userManager,
+    public DefaultUserService(@Named( value = "rbacManager#default" ) RBACManager rbacManager,
+                              @Named( value = "userManager#default" ) UserManager userManager ,
                                SecuritySystem securitySystem )
     {
-        this.userManager = userManager;
+        super( rbacManager, userManager );
         this.securitySystem = securitySystem;
     }
 
@@ -220,7 +230,8 @@ public class DefaultUserService
     public UserInfo createUser( User user )
         throws RedbackServiceException
     {
-        if (user==null) {
+        if ( user == null )
+        {
             throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USER_ID_EMPTY ), 422 );
         }
         UserInfo result;
@@ -311,7 +322,8 @@ public class DefaultUserService
     public void deleteUser( String userId )
         throws RedbackServiceException
     {
-        if (StringUtils.isEmpty( userId )) {
+        if ( StringUtils.isEmpty( userId ) )
+        {
             throw new RedbackServiceException( MessageKeys.ERR_USER_ID_EMPTY, 404 );
         }
 
@@ -355,7 +367,8 @@ public class DefaultUserService
     public UserInfo getUser( String userId )
         throws RedbackServiceException
     {
-        if (StringUtils.isEmpty( userId)) {
+        if ( StringUtils.isEmpty( userId ) )
+        {
             throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USER_ID_EMPTY ), 404 );
         }
         try
@@ -377,55 +390,6 @@ public class DefaultUserService
         }
     }
 
-    Comparator<org.apache.archiva.redback.users.User> getAttributeComparator( String attributeName )
-    {
-        return ORDER_MAP.get( attributeName );
-    }
-
-    Comparator<org.apache.archiva.redback.users.User> getComparator( List<String> orderBy, boolean ascending )
-    {
-        if ( ascending )
-        {
-            return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) ).filter( Objects::nonNull ).reduce( Comparator::thenComparing ).get( );
-        }
-        else
-        {
-            return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) == null ? null : getAttributeComparator( name ).reversed( ) ).filter( Objects::nonNull ).reduce( Comparator::thenComparing ).get( );
-        }
-    }
-
-    static Predicate<org.apache.archiva.redback.users.User> getFilter( final String attribute, final String queryToken )
-    {
-        if ( FILTER_MAP.containsKey( attribute ) )
-        {
-            return ( org.apache.archiva.redback.users.User u ) -> FILTER_MAP.get( attribute ).test( queryToken, u );
-        }
-        else
-        {
-            return Arrays.stream( DEFAULT_SEARCH_FIELDS )
-                .map( att -> getFilter( att, queryToken ) ).reduce( Predicate::or ).get( );
-        }
-    }
-
-    Predicate<org.apache.archiva.redback.users.User> getUserFilter( String queryTerms )
-    {
-        return Arrays.stream( queryTerms.split( "\\s+" ) )
-            .map( s -> {
-                    if ( s.contains( ":" ) )
-                    {
-                        String attr = StringUtils.substringBefore( s, ":" );
-                        String term = StringUtils.substringAfter( s, ":" );
-                        return getFilter( attr, term );
-                    }
-                    else
-                    {
-                        return Arrays.stream( DEFAULT_SEARCH_FIELDS )
-                            .map( att -> getFilter( att, s ) ).reduce( Predicate::or ).get( );
-                    }
-                }
-            ).reduce( Predicate::or ).get( );
-    }
-
     @Override
     public PagedResult<UserInfo> getUsers( String q, Integer offset,
                                            Integer limit, List<String> orderBy, String order )
@@ -437,11 +401,11 @@ public class DefaultUserService
             // UserQuery does not work here, because the configurable user manager does only return the query for
             // the first user manager in the list. So we have to fetch the whole user list
             List<? extends org.apache.archiva.redback.users.User> rawUsers = userManager.getUsers( );
-            Predicate<org.apache.archiva.redback.users.User> filter = getUserFilter( q );
+            Predicate<org.apache.archiva.redback.users.User> filter = QUERY_HELPER.getQueryFilter( q );
             long size = rawUsers.stream( ).filter( filter ).count( );
             List<UserInfo> users = rawUsers.stream( )
                 .filter( filter )
-                .sorted( getComparator( orderBy, ascending ) ).skip( offset ).limit( limit )
+                .sorted( QUERY_HELPER.getComparator( orderBy, ascending ) ).skip( offset ).limit( limit )
                 .map( user -> getRestUser( user ) )
                 .collect( Collectors.toList( ) );
             return new PagedResult<>( (int) size, offset, limit, users );
@@ -975,6 +939,101 @@ public class DefaultUserService
     }
 
     @Override
+    public List<RoleInfo> getEffectivelyAssignedRoles( String username ) throws RedbackServiceException
+    {
+        try
+        {
+            return rbacManager.getEffectivelyAssignedRoles( username ).stream( )
+                .filter( org.apache.archiva.redback.rbac.Role::isAssignable )
+                .map( this::getRoleInfoOptional )
+                .filter( Optional::isPresent )
+                .map(Optional::get).collect( Collectors.toList());
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+    private static final Application toApplication( ModelApplication app )
+    {
+        Application application = new Application( );
+        application.setId( app.getId( ) );
+        application.setVersion( app.getVersion( ) );
+        application.setDescription( app.getDescription( ) == null ? "" :app.getDescription() );
+        application.setLongDescription( app.getLongDescription( ) == null ? "" : app.getLongDescription( ) );
+        return application;
+    }
+
+    private List<Application> getAllApplications( )
+    {
+        return roleManager.getModel( ).getApplications( ).stream( ).map( DefaultUserService::toApplication ).collect( Collectors.toList( ) );
+    }
+
+
+    @Override
+    public RoleTree getRoleTree( final String username ) throws RedbackServiceException
+    {
+        final Map<String, String> roleApplicationMap = roleManager.getModel( ).getApplications( ).stream( )
+            .flatMap( modelApplication -> modelApplication.getRoles( ).stream( ).map( role -> {
+                BaseRoleInfo roleInfo = new BaseRoleInfo( );
+                roleInfo.setId( role.getId( ) );
+                roleInfo.setApplicationId( modelApplication.getId( ) );
+                return roleInfo;
+            } ) ).collect( Collectors.toMap( BaseRoleInfo::getId, BaseRoleInfo::getApplicationId ) );
+
+        try
+        {
+            final Set<String> assignedRoleNames = new HashSet( rbacManager.getUserAssignment( username ).getRoleNames( ) );
+            // We have to reuse the BaseRoleInfo objects, because the roles are not returned starting from the roots
+            final Map<String, BaseRoleInfo> roleNameCache = new HashMap<>( );
+            List<BaseRoleInfo> roleList = rbacManager.getAllRoles( ).stream( ).flatMap( this::flattenRole ).map( role ->
+            {
+                BaseRoleInfo roleInfo = roleNameCache.computeIfAbsent( role.getName( ), s -> new BaseRoleInfo( ) );
+                // Setting the role data, as there may be child role objects that are not completely initialized
+                roleInfo = BaseRoleInfo.of( role, roleInfo );
+                roleInfo.setApplicationId( roleApplicationMap.get( role.getId( ) ) );
+                roleInfo.setAssigned( assignedRoleNames.contains( role.getName( ) ) );
+                roleInfo.setChildren( role.getChildRoleNames( ).stream( )
+                    .map( roleName ->
+                    {
+                        BaseRoleInfo childRoleInfo = roleNameCache.computeIfAbsent( roleName, s -> BaseRoleInfo.ofName( roleName ) );
+                        childRoleInfo.setChild( true );
+                        return childRoleInfo;
+                    } )
+                    .collect( Collectors.toList( ) ) );
+                return roleInfo;
+            } ).collect( Collectors.toList( ) );
+            RoleTree roleTree = new RoleTree( );
+            roleTree.setApplications( getAllApplications( ).stream( ).collect( Collectors.toMap( Application::getId, Function.identity( ) ) ) );
+            roleTree.setRootRoles( roleList.stream( ).filter( BaseRoleInfo::isNotChild ).collect( Collectors.toList( ) ) );
+            roleTree.setUserId( username );
+            return roleTree;
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+    private Stream<Role> flattenRole( Role role )
+    {
+        return Stream.concat( Stream.of( role ), this.getChildren( role ).flatMap( this::flattenRole ) ).distinct( );
+    }
+
+    private Stream<? extends Role> getChildren( Role role )
+    {
+        try
+        {
+            return rbacManager.getChildRoleNames( role ).values( ).stream( );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+    @Override
     public Collection<Operation> getUserOperations( String userName )
         throws RedbackServiceException
     {
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/QueryHelper.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/QueryHelper.java
new file mode 100644
index 0000000..ba417aa
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/QueryHelper.java
@@ -0,0 +1,168 @@
+package org.apache.archiva.redback.rest.services.v2;/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ *
+ * Helper class that returns combined filter and comparison objects for ordering.
+ *
+ * The query term may be consist of simple query terms separated by whitespace or attribute queries
+ * in the form <code>attribute:query</code>, which means only the attribute is searched for the query string.
+ * <br />
+ * Example:
+ * <dl>
+ *     <dt>`user1 test`</dt>
+ *     <dd>
+ * searches for the tokens user1 and test in the default attributes.
+ * </dd>
+ * <dt>`user1 name:test`</dt>
+ * <dd>searches for the token user1 in the default attributes and for the token test in the attribute name.</dd>
+ * </dl>
+ *
+ *
+ * @since 3.0
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+public class QueryHelper<T>
+{
+
+    private final Map<String, BiPredicate<String, T>> FILTER_MAP;
+    private final Map<String, Comparator<T>> ORDER_MAP;
+    private final String[] DEFAULT_SEARCH_FIELDS;
+    private final Predicate<T> DEFAULT_FILTER = ( T att ) -> false;
+
+
+    /**
+     * Creates a new query helper with the given filters and comparators.
+     *
+     * @param filterMap a map of filters, where the key is the attribute name and the value is a predicate that matches
+     *                  the filter value and the object instance.
+     * @param orderMap a map of comparators, where key is the attribute name and the value is a comparator for the given
+     *                 object instance
+     * @param defaultSearchFields A array of attribute names, that are used as default search fields.
+     */
+    public QueryHelper(Map<String, BiPredicate<String, T>> filterMap, Map<String, Comparator<T>> orderMap,
+                       String[] defaultSearchFields)
+    {
+        this.FILTER_MAP = filterMap;
+        this.DEFAULT_SEARCH_FIELDS = defaultSearchFields;
+        this.ORDER_MAP = new HashMap<>( orderMap );
+    }
+
+    public <U extends Comparable<? super U>> void addNullsafeFieldComparator( String fieldName, Function<? super T, U> keyExtractor) {
+        ORDER_MAP.put( fieldName, Comparator.comparing( keyExtractor, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+    }
+
+    public void addStringFilter(String attribute, Function<? super T, String> keyExtractor) {
+        this.FILTER_MAP.put( attribute, ( String q, T r ) -> StringUtils.containsIgnoreCase( keyExtractor.apply( r ), q ) );
+    }
+
+    public void addBooleanFilter(String attribute, Function<? super T, Boolean> keyExtractor) {
+        this.FILTER_MAP.put( attribute, ( String q, T r ) -> Boolean.valueOf( q ) == keyExtractor.apply( r ) );
+    }
+
+    /**
+     * Get the comparator for a specific attribute.
+     * @param attributeName the name of the attribute.
+     * @return
+     */
+    Comparator<T> getAttributeComparator( String attributeName )
+    {
+        return ORDER_MAP.get( attributeName );
+    }
+
+    /**
+     * Get the combined order for the given attributes in the given order.
+     *
+     * @param orderBy the attributes to compare. The first attribute in the list will be used first for comparing.
+     * @param ascending
+     * @return
+     */
+    Comparator<T> getComparator( List<String> orderBy, boolean ascending )
+    {
+        if ( ascending )
+        {
+            return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) ).filter( Objects::nonNull )
+                .reduce( Comparator::thenComparing )
+                .orElseThrow( () -> new IllegalArgumentException( "No attribute ordering found" ) );
+        }
+
+        else
+        {
+            return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) == null ? null : getAttributeComparator( name )
+                .reversed( ) ).filter( Objects::nonNull ).reduce( Comparator::thenComparing )
+                .orElseThrow( () -> new IllegalArgumentException( "No attribute ordering found" ) );
+        }
+    }
+
+    /**
+     * Returns a query filter for a specific attribute and query token.
+     * @param attribute the attribute name to filter for.
+     * @param queryToken the search token.
+     * @return The predicate used to filter the token
+     */
+    Predicate<T> getAttributeQueryFilter( final String attribute, final String queryToken )
+    {
+        if ( FILTER_MAP.containsKey( attribute ) )
+        {
+            return ( T u ) -> FILTER_MAP.get( attribute ).test( queryToken, u );
+        }
+        else
+        {
+            return DEFAULT_FILTER;
+        }
+    }
+
+    /**
+     * Returns the combined query filter for the given query terms.
+     * The query terms may be either simple strings separated by whitespace or use the
+     * <code>attribute:query</code> syntax, that searches only the attribute for the query term.
+     * @param queryTerms the query string
+     * @return the combined query filter
+     */
+    Predicate<T> getQueryFilter( String queryTerms )
+    {
+        return Arrays.stream( queryTerms.split( "\\s+" ) )
+            .map( s -> {
+                    if ( s.contains( ":" ) )
+                    {
+                        String attr = StringUtils.substringBefore( s, ":" );
+                        String term = StringUtils.substringAfter( s, ":" );
+                        return getAttributeQueryFilter( attr, term );
+                    }
+                    else
+                    {
+                        return Arrays.stream( DEFAULT_SEARCH_FIELDS )
+                            .map( att -> getAttributeQueryFilter( att, s ) ).reduce( Predicate::or ).get( );
+                    }
+                }
+            ).reduce( Predicate::or ).get( );
+    }
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
index 5041bc4..ff90b95 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
@@ -93,7 +93,7 @@
     <jaxrs:serviceBeans>
       <ref bean="v2.userService#rest"/>
       <ref bean="v2.authenticationService#rest"/>
-      <ref bean="roleManagementService#rest"/>
+      <ref bean="v2.roleService#rest"/>
       <ref bean="utilServices#rest"/>
       <ref bean="passwordService#rest"/>
       <ref bean="v2.groupService#rest"/>
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
index 2adc1e7..1523387 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
@@ -128,7 +128,7 @@ public abstract class AbstractNativeRestServices
     }
 
 
-    private String getServiceBasePath( )
+    protected String getServiceBasePath( )
     {
         return "/v2/redback";
     }
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeRoleServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeRoleServiceTest.java
new file mode 100644
index 0000000..f3105d3
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeRoleServiceTest.java
@@ -0,0 +1,505 @@
+package org.apache.archiva.redback.rest.services.v2;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.restassured.response.Response;
+import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.http.ContentType.JSON;
+import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@ExtendWith( SpringExtension.class )
+@ContextConfiguration(
+    locations = {"classpath:/ldap-spring-test.xml"} )
+@TestInstance( TestInstance.Lifecycle.PER_CLASS )
+@Tag( "rest-native" )
+@TestMethodOrder( MethodOrderer.Random.class )
+@DisplayName( "Native REST tests for V2 RoleService" )
+public class NativeRoleServiceTest extends AbstractNativeRestServices
+{
+    @Override
+    protected String getServicePath( )
+    {
+        return "/roles";
+    }
+
+    @BeforeAll
+    void setup( ) throws Exception
+    {
+        super.setupNative( );
+    }
+
+    @AfterAll
+    void destroy( ) throws Exception
+    {
+        super.shutdownNative( );
+    }
+
+    private String getUserServicePath()
+    {
+        return new StringBuilder( )
+            .append( getContextRoot( ) )
+            .append( getServiceBasePath( ) )
+            .append( "/users" ).toString( );
+    }
+
+
+
+    @Test
+    void createTemplatedRole( )
+    {
+        String token = getAdminToken( );
+        try
+        {
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 201 ).extract( ).response( );
+            assertNotNull( response );
+            RoleInfo roleInfo = response.getBody( ).jsonPath( ).getObject( "", RoleInfo.class );
+            assertNotNull( response.getHeader( "Location" ) );
+            assertTrue( response.getHeader( "Location" ).endsWith( "/roles/" + roleInfo.getId( ) ) );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .head( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 200 );
+            // Repository observer is child template of repository-manager and will be created too
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .head( "template/archiva-repository-observer/repository01" )
+                .then( ).statusCode( 200 );
+
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-observer/repository01" )
+                .then( ).statusCode( 200 );
+        }
+    }
+
+    @Test
+    void createTemplatedRoleWithNonexistentTemplate( )
+    {
+        String token = getAdminToken( );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .put( "template/abcdefg/repository01" )
+            .then( ).statusCode( 404 );
+    }
+
+    @Test
+    void deleteTemplatedRole( )
+    {
+        String token = getAdminToken( );
+        try
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "template/archiva-repository-manager/repository05" )
+                .then( ).statusCode( 201 ).extract( ).response( );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 404 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-manager/repository05" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-manager/repository05" )
+                .then( ).statusCode( 404 );
+        } finally
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-observer/repository01" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-observer/repository05" )
+                .then( ).statusCode( 200 );
+
+        }
+    }
+
+    @Test
+    void checkTemplatedRole() {
+        String token = getAdminToken( );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .put( "template/archiva-repository-observer/repository01" )
+            .then( ).statusCode( 201 );
+        try {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .head( "template/archiva-repository-observer/repository01" )
+                .then( ).statusCode( 200 );
+
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .head( "archiva-repository-observer.repository01" )
+                .then( ).statusCode( 200 );
+        } finally
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-observer/repository01" )
+                .then( ).statusCode( 200 );
+        }
+
+    }
+
+
+    @Nested
+    @DisplayName( "Test Role queries" )
+    @ContextConfiguration(
+        locations = {"classpath:/ldap-spring-test.xml"} )
+    @TestInstance( TestInstance.Lifecycle.PER_CLASS )
+    class TestRoleRetrieval
+    {
+        int roleInstances = 3;
+        String token;
+
+        @BeforeAll
+        void initRoles( )
+        {
+            this.token = getAdminToken( );
+            for ( int i = 0; i < roleInstances; i++ )
+            {
+                String suffix = String.format( "%03d", i );
+                given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                    .when( )
+                    .put( "template/archiva-repository-manager/repo" + suffix )
+                    .then( ).statusCode( 201 ).extract( ).response( );
+                given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                    .when( )
+                    .put( "template/archiva-repository-observer/repo" + suffix )
+                    .then( ).statusCode( anyOf( equalTo( 200 ), equalTo( 201 ) ) ).extract( ).response( );
+            }
+        }
+
+        @Test
+        void getMultipleRolesWithoutParams( )
+        {
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).get( ).then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull( response );
+            List<RoleInfo> roleData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class );
+            assertNotNull( roleData );
+            assertEquals( roleInstances * 2 + 9, roleData.size( ) );
+            assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
+            assertEquals( Integer.valueOf( DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+            assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
+
+        }
+
+        @Test
+        void getMultipleRolesWithPaging( )
+        {
+            HashMap<String, String> params = new HashMap<>( );
+            params.put( "limit", Integer.toString( 10 ) );
+            params.put( "offset", Integer.toString( 1 ) );
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( );
+            List<RoleInfo> userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class );
+            assertNotNull( userData );
+            response.getBody( ).jsonPath( ).prettyPrint( );
+            assertEquals( 10, userData.size( ) );
+            assertEquals( Integer.valueOf( 1 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
+            assertEquals( Integer.valueOf( 10 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+            assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
+        }
+
+        @Test
+        void getMultipleUsersWithPagingOrderByIdAndResource( )
+        {
+            HashMap<String, Object> params = new HashMap<>( );
+            params.put( "limit", Integer.toString( 8 ) );
+            params.put( "offset", Integer.toString( 5 ) );
+            params.put( "orderBy", Arrays.asList( "id", "resource" ) );
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( );
+            List<RoleInfo> userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class );
+            assertNotNull( userData );
+            // admin user has toto@toto.org as email so is after aragorn
+            assertEquals( "repo002", userData.get( 0 ).getResource( ) );
+            assertEquals( "repo000", userData.get( 1 ).getResource( ) );
+            assertEquals( "archiva-repository-observer.repo000", userData.get( 1 ).getId( ) );
+            assertEquals( "archiva-system-administrator", userData.get( 4 ).getId( ) );
+            assertEquals( 8, userData.size( ) );
+            assertEquals( Integer.valueOf( 5 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
+            assertEquals( Integer.valueOf( 8 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+            assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
+        }
+
+        @Test
+        void getMultipleUsersWithPagingOrderByIdAndResourceReverse( )
+        {
+            HashMap<String, Object> params = new HashMap<>( );
+            params.put( "limit", Integer.toString( 7 ) );
+            params.put( "offset", Integer.toString( 1 ) );
+            params.put( "orderBy", Arrays.asList( "id", "resource" ) );
+            params.put( "order", "desc" );
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( );
+            response.getBody( ).jsonPath( ).prettyPrint( );
+            List<RoleInfo> userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class );
+            assertNotNull( userData );
+            // admin user has toto@toto.org as email so is after aragorn
+            assertEquals( "system-administrator", userData.get( 0 ).getId( ) );
+            assertEquals( "registered-user", userData.get( 1 ).getId( ) );
+            assertEquals( "guest", userData.get( 2 ).getId( ) );
+            assertEquals( "archiva-repository-observer.repo002", userData.get( 5 ).getId( ) );
+            assertEquals( 7, userData.size( ) );
+            assertEquals( Integer.valueOf( 1 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
+            assertEquals( Integer.valueOf( 7 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+            assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
+        }
+
+        @Test
+        void getMultipleRolesWithPagingAndQuery( )
+        {
+            HashMap<String, String> params = new HashMap<>( );
+            params.put( "limit", Integer.toString( 10 ) );
+            params.put( "offset", Integer.toString( 0 ) );
+            params.put( "order", "asc" );
+            params.put( "q", "system" );
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( );
+            List<RoleInfo> userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class );
+            assertNotNull( userData );
+            assertEquals( 2, userData.size( ) );
+            assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
+            assertEquals( Integer.valueOf( 10 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+            assertEquals( Integer.valueOf( 2 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
+
+        }
+
+
+        @AfterAll
+        void cleanupRoles( )
+        {
+            for ( int i = 0; i < roleInstances; i++ )
+            {
+                String suffix = String.format( "%03d", i );
+                given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                    .when( ).delete( "template/archiva-repository-manager/repo" + suffix ).then( ).statusCode( 200 );
+                given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                    .when( ).delete( "template/archiva-repository-observer/repo" + suffix ).then( ).statusCode( 200 );
+            }
+
+        }
+    }
+
+    @Test
+    void getRole( )
+    {
+        String token = getAdminToken( );
+        Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( ).get( "archiva-system-administrator" ).then( ).statusCode( 200 ).extract( ).response( );
+        assertNotNull( response );
+        RoleInfo roleInfo = response.getBody( ).jsonPath( ).getObject( "", RoleInfo.class );
+        assertNotNull( roleInfo );
+        assertEquals( "archiva-system-administrator", roleInfo.getId( ) );
+        assertEquals( "Archiva System Administrator", roleInfo.getName( ) );
+    }
+
+    @Test
+    void getNonExistingRole( )
+    {
+        String token = getAdminToken( );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( ).get( "abcdefg" ).then( ).statusCode( 404 );
+    }
+
+    @Test
+    void checkRole() {
+        String token = getAdminToken( );
+        Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( ).head( "archiva-system-administrator" ).then( ).statusCode( 200 ).extract( ).response( );
+        assertEquals(0,response.getBody( ).asByteArray().length);
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( ).head( "abcdefg" ).then( ).statusCode( 404 );
+
+    }
+
+    @Test
+    void moveRole() {
+        String token = getAdminToken( );
+        try
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 201 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).head( "template/archiva-repository-observer/repository01" ).then( ).statusCode( 200 );
+
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).post( "template/archiva-repository-manager/repository01/moveto/repository02" ).then( ).statusCode( 201 ).extract( ).response( );
+            RoleInfo role = response.getBody( ).jsonPath( ).getObject( "", RoleInfo.class );
+            assertNotNull( role );
+            assertEquals( "archiva-repository-manager.repository02", role.getId( ) );
+            assertEquals( "repository02", role.getResource( ) );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).head( "template/archiva-repository-manager/repository01" ).then( ).statusCode( 404 );
+            // Child templates are copied and not moved
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).head( "template/archiva-repository-observer/repository01" ).then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).head( "template/archiva-repository-observer/repository02" ).then( ).statusCode( 200 );
+
+        } finally
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-manager/repository02" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-observer/repository01" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-observer/repository02" )
+                .then( ).statusCode( 200 );
+
+        }
+
+    }
+
+    @Test
+    void moveRoleToExistingDestination() {
+        String token = getAdminToken( );
+        try
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 201 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "template/archiva-repository-manager/repository02" )
+                .then( ).statusCode( 201 );
+            Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).redirects( ).follow( false )
+                .post( "template/archiva-repository-manager/repository01/moveto/repository02" ).then( ).statusCode( 303 )
+                .extract( ).response( );
+            System.out.println( response.getHeader( "Location" ) );
+            assertTrue( response.getHeader( "Location" ).endsWith( "/roles/template/archiva-repository-manager/repository02" ) );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( ).head( "template/archiva-repository-manager/repository01" ).then( ).statusCode( 200 );
+        } finally
+        {
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-manager/repository01" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-manager/repository02" )
+                .then( ).statusCode( 200 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .delete( "template/archiva-repository-observer/repository02" )
+                .then( ).statusCode( 200 );
+
+        }
+
+    }
+
+
+    @Test
+    void assignRole() {
+        String token = getAdminToken( );
+        Map<String, Object> jsonAsMap = new HashMap<>( );
+        jsonAsMap.put( "user_id", "aragorn");
+        jsonAsMap.put( "email", "aragorn@lordoftherings.org" );
+        jsonAsMap.put( "full_name", "Aragorn King of Gondor " );
+        jsonAsMap.put( "password", "pAssw0rD" );
+
+        try
+        {
+            given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON )
+                .body( jsonAsMap )
+                .when( )
+                .post( )
+                .then( ).statusCode( 201 );
+
+            Response response = given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON )
+                .when( )
+                .get( "aragorn/roles" )
+                .then( ).statusCode( 200 ).extract( ).response( );
+            List<RoleInfo> roles = response.getBody( ).jsonPath( ).getList( "", RoleInfo.class );
+            assertFalse( roles.stream( ).filter( role -> "system-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "system-administrator/assign/aragorn" )
+                .prettyPeek()
+                .then( ).statusCode( 200 );
+            response = given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON )
+                .when( )
+                .get( "aragorn/roles" )
+                .then( ).statusCode( 200 ).extract( ).response( );
+            roles = response.getBody( ).jsonPath( ).getList( "", RoleInfo.class );
+            assertTrue( roles.stream( ).filter( role -> "system-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) );
+        } finally
+        {
+            given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON )
+                .when( )
+                .delete( "aragorn" ).getBody( );
+        }
+    }
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java
index e8f6cef..4181d27 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java
@@ -18,10 +18,13 @@ package org.apache.archiva.redback.rest.services.v2;
  * under the License.
  */
 
+import io.restassured.path.json.JsonPath;
 import io.restassured.response.Response;
+import org.apache.archiva.redback.rest.api.model.v2.BaseRoleInfo;
 import org.apache.archiva.redback.rest.api.model.v2.Operation;
 import org.apache.archiva.redback.rest.api.model.v2.Permission;
 import org.apache.archiva.redback.rest.api.model.v2.RegistrationKey;
+import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
 import org.apache.archiva.redback.rest.api.model.v2.UserInfo;
 import org.apache.archiva.redback.rest.api.model.v2.VerificationStatus;
 import org.apache.archiva.redback.rest.services.mock.EmailMessage;
@@ -44,6 +47,7 @@ import java.util.Map;
 
 import static io.restassured.RestAssured.given;
 import static io.restassured.http.ContentType.JSON;
+import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -87,7 +91,7 @@ public class NativeUserServiceTest extends AbstractNativeRestServices
         assertNotNull( userData );
         assertEquals( 2, userData.size( ) );
         assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
-        assertEquals( Integer.valueOf( 1000 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+        assertEquals( Integer.valueOf( DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
         assertEquals( Integer.valueOf( 2 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
     }
 
@@ -134,7 +138,7 @@ public class NativeUserServiceTest extends AbstractNativeRestServices
             assertEquals( "admin", userData.get( 0 ).getUserId( ) );
             assertEquals( userNum + 2, userData.size( ) );
             assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
-            assertEquals( Integer.valueOf( 1000 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+            assertEquals( Integer.valueOf( DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
             assertEquals( Integer.valueOf( userNum + 2 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
 
         }
@@ -1508,4 +1512,128 @@ public class NativeUserServiceTest extends AbstractNativeRestServices
                 .then( ).statusCode( 200 );
         }
     }
+
+    @Test
+    void getUserRoles() {
+        String adminToken = getAdminToken( );
+
+        Map<String, Object> userMap = new HashMap<>( );
+        userMap.put( "user_id", "bilbo" );
+        userMap.put( "email", "bilbo@lordoftherings.org" );
+        userMap.put( "full_name", "Bilbo Beutlin" );
+        userMap.put( "validated", true );
+        userMap.put( "password", "pAssw0rD" );
+        userMap.put( "confirm_password", "pAssw0rD" );
+        given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+            .body( userMap )
+            .when( )
+            .post( )
+            .then( ).statusCode( 201 );
+
+
+        try
+        {
+            Response response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+                .when( )
+                .get( "bilbo/roles" )
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull( response );
+            JsonPath jsonPath = response.getBody( ).jsonPath( );
+            List<RoleInfo> roleList = jsonPath.getList( "", RoleInfo.class );
+            assertEquals( 1, roleList.size( ) );
+            assertEquals( "registered-user", roleList.get( 0 ).getId( ) );
+
+            response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+                .when( )
+                .get( "admin/roles" )
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull( response );
+            jsonPath = response.getBody( ).jsonPath( );
+            roleList = jsonPath.getList( "", RoleInfo.class );
+            jsonPath.prettyPrint( );
+            assertEquals( 4, roleList.size( ) );
+            assertTrue( roleList.stream( ).filter( role -> "system-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) );
+            assertTrue( roleList.stream( ).filter( role -> "archiva-global-repository-manager".equals( role.getId( ) ) ).findAny( ).isPresent( ) );
+            assertTrue( roleList.stream( ).filter( role -> "archiva-global-repository-observer".equals( role.getId( ) ) ).findAny( ).isPresent( ) );
+            assertTrue( roleList.stream( ).filter( role -> "user-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) );
+
+
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+                .delete( "bilbo" )
+                .then( ).statusCode( 200 );
+        }
+    }
+
+    @Test
+    void getRoleTree() {
+        String adminToken = getAdminToken( );
+
+        Map<String, Object> userMap = new HashMap<>( );
+        userMap.put( "user_id", "bilbo" );
+        userMap.put( "email", "bilbo@lordoftherings.org" );
+        userMap.put( "full_name", "Bilbo Beutlin" );
+        userMap.put( "validated", true );
+        userMap.put( "password", "pAssw0rD" );
+        userMap.put( "confirm_password", "pAssw0rD" );
+        given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+            .body( userMap )
+            .when( )
+            .post( )
+            .then( ).statusCode( 201 );
+
+
+        try
+        {
+            Response response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+                .when( )
+                .get( "bilbo/roletree" )
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull( response );
+            JsonPath jsonPath = response.getBody( ).jsonPath( );
+            assertTrue( jsonPath.getMap( "applications" ).containsKey( "System" ) );
+            assertTrue( jsonPath.getMap( "applications" ).containsKey( "Archiva" ) );
+            List<BaseRoleInfo> roleList = jsonPath.getList( "root_roles", BaseRoleInfo.class );
+            assertEquals( 3, roleList.size( ) );
+            assertTrue( roleList.stream( ).filter( role -> role.getId( ).equals( "guest" ) ).findFirst( ).isPresent( ) );
+            BaseRoleInfo registered = roleList.stream( ).filter( role -> role.getId( ).equals( "registered-user" ) ).findFirst( ).get( );
+            assertTrue( registered.isAssigned( ) );
+            BaseRoleInfo sysadmin = roleList.stream( ).filter( role -> role.getId( ).equals( "system-administrator" ) ).findFirst( ).get( );
+            assertFalse( sysadmin.isAssigned( ) );
+            assertFalse( sysadmin.isChild( ) );
+            assertEquals( 2, sysadmin.getChildren( ).size( ) );
+            assertTrue( sysadmin.getChildren( ).stream( ).filter( role -> role.getId( ).equals( "user-administrator" ) ).findFirst( ).isPresent( ) );
+
+
+            response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+                .when( )
+                .get( "admin/roletree" )
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull( response );
+            System.out.println( response.getBody( ).prettyPrint( ) );
+            jsonPath = response.getBody( ).jsonPath( );
+            assertTrue( jsonPath.getMap( "applications" ).containsKey( "System" ) );
+            assertTrue( jsonPath.getMap( "applications" ).containsKey( "Archiva" ) );
+            roleList = jsonPath.getList( "root_roles", BaseRoleInfo.class );
+            assertEquals( 3, roleList.size( ) );
+            assertTrue( roleList.stream( ).filter( role -> role.getId( ).equals( "guest" ) ).findFirst( ).isPresent( ) );
+            registered = roleList.stream( ).filter( role -> role.getId( ).equals( "registered-user" ) ).findFirst( ).get( );
+            assertFalse( registered.isAssigned( ) );
+            sysadmin = roleList.stream( ).filter( role -> role.getId( ).equals( "system-administrator" ) ).findFirst( ).get( );
+            assertTrue( sysadmin.isAssigned( ) );
+            assertFalse( sysadmin.isChild( ) );
+            assertEquals( 2, sysadmin.getChildren( ).size( ) );
+            assertTrue( sysadmin.getChildren( ).stream( ).filter( role -> role.getId( ).equals( "user-administrator" ) ).findFirst( ).isPresent( ) );
+
+
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON )
+                .delete( "bilbo" )
+                .then( ).statusCode( 200 );
+        }
+    }
 }
diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java
index 8ace7e6..bdecd29 100644
--- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java
+++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java
@@ -33,6 +33,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * AbstractRBACManager
@@ -499,7 +500,7 @@ public abstract class AbstractRBACManager
 
         if ( role.hasChildRoles() )
         {
-            Map<String, ? extends Role> childRoles = getChildRoles( role );
+            Map<String, ? extends Role> childRoles = getChildRoleNames( role );
             Iterator<? extends Role> it = childRoles.values().iterator();
             while ( it.hasNext() )
             {
@@ -736,11 +737,11 @@ public abstract class AbstractRBACManager
         throws RbacObjectInvalidException, RbacManagerException
     {
         saveRole( childRole );
-        role.addChildRoleName( childRole.getName() );
+        role.addChildRole( childRole );
     }
 
     @Override
-    public Map<String, ? extends Role> getChildRoles( Role role )
+    public Map<String, ? extends Role> getChildRoleNames( Role role )
         throws RbacManagerException
     {
         Map<String, Role> childRoles = new HashMap<String, Role>();
@@ -797,7 +798,64 @@ public abstract class AbstractRBACManager
     }
 
     @Override
-    public Map<String, ? extends Role> getParentRoles( Role role )
+    public Map<String, ? extends Role> getChildRoleIds( Role role )
+        throws RbacManagerException
+    {
+        Map<String, Role> childRoles = new HashMap<String, Role>();
+
+        boolean childRoleNamesUpdated = false;
+
+        Iterator<String> it = role.getChildRoleIds().listIterator();
+
+        final List<String> updatedChildRoleList = new ArrayList<String>( role.getChildRoleIds().size() );
+
+        while ( it.hasNext() )
+        {
+            String roleId = it.next();
+            try
+            {
+                Role child = getRoleById(  roleId );
+                // archiva can change role manager but LDAP can be non writable so in such case
+                // some roles doesn't exists !!
+                if ( child != null )
+                {
+                    childRoles.put( child.getId(), child );
+                    updatedChildRoleList.add( roleId );
+                }
+                else
+                {
+                    log.warn(
+                        "error searching role with name '{}' probably some issues when migrating your role manager",
+                        roleId );
+                }
+            }
+            catch ( RbacObjectNotFoundException e )
+            {
+                // Found a bad roleName! - trigger new List save
+                //it.remove();
+                childRoleNamesUpdated = true;
+            }
+            catch ( RbacManagerException e )
+            {
+                if ( !( e.getCause() instanceof RbacObjectNotFoundException ) )
+                {
+                    throw e;
+                }
+                childRoleNamesUpdated = true;
+            }
+        }
+
+        if ( childRoleNamesUpdated )
+        {
+            role.setChildRoleIds( updatedChildRoleList );
+            saveRole( role );
+        }
+
+        return childRoles;
+    }
+
+    @Override
+    public Map<String, ? extends Role> getParentRoleNames( Role role )
         throws RbacManagerException
     {
         Map<String, Role> parentRoles = new HashMap<String, Role>();
@@ -823,6 +881,23 @@ public abstract class AbstractRBACManager
     }
 
     @Override
+    public Map<String, ? extends Role> getParentRoleIds( final Role role ) throws RbacManagerException
+    {
+        return getAllRoles( ).stream( ).filter( r -> !r.getId( ).equals( role.getId( ) ) )
+            .filter( r -> {
+                    try
+                    {
+                        return getEffectiveRoles( r ).stream( ).map( Role::getId ).filter( cRoleId -> cRoleId.equals( role.getId( ) ) ).findAny( ).isPresent( );
+                    }
+                    catch ( RbacManagerException e )
+                    {
+                        return false;
+                    }
+                }
+            ).distinct().collect( Collectors.toMap( Role::getId, Function.identity( ) ) );
+    }
+
+    @Override
     public Set<? extends Role> getEffectiveRoles( Role role )
         throws RbacObjectNotFoundException, RbacManagerException
     {
diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java
index a90f66a..5736f5b 100644
--- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java
+++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java
@@ -29,7 +29,7 @@ public abstract class AbstractRole
     @Override
     public boolean hasChildRoles()
     {
-        return ( getChildRoleNames() != null ) && !getChildRoleNames().isEmpty();
+        return ( getChildRoleIds() != null ) && !getChildRoleIds().isEmpty();
     }
 
     /**
@@ -57,4 +57,11 @@ public abstract class AbstractRole
 
         return result;
     }
+
+    @Override
+    public void addChildRole( Role child )
+    {
+        addChildRoleName( child.getName() );
+        addChildRoleId( child.getId() );
+    }
 }
diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java
index 11b56db..16ac43d 100644
--- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java
+++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java
@@ -155,21 +155,39 @@ public interface RBACManager
         throws RbacObjectInvalidException, RbacManagerException;
 
     /**
-     * Returns all the child roles of a given role.
+     * Returns all the child roles of a given role as (name, role) pairs.
      * @param role the parent role
      * @return the list of child roles
      * @throws RbacManagerException if the access to the backend datastore failed
      */
-    Map<String, ? extends Role> getChildRoles( Role role )
+    Map<String, ? extends Role> getChildRoleNames( Role role )
         throws RbacManagerException;
 
     /**
-     * Returns all the parent roles of a given role.
+     * Returns all the child roles of a given role as (role id, role) pairs.
+     * @param role the parent role
+     * @return the map of child roles as (role id, role) pairs
+     * @throws RbacManagerException if the access to the backend datastore failed
+     */
+    Map<String, ? extends Role> getChildRoleIds( Role role )
+        throws RbacManagerException;
+
+    /**
+     * Returns all the parent roles of a given role as map of (name, role)  elements.
      * @param role the role to check for parent roles
      * @return the list of parent roles that have <code>role</code> als child
      * @throws RbacManagerException if the access to the backend datastore failed
      */
-    Map<String, ? extends Role> getParentRoles( Role role )
+    Map<String, ? extends Role> getParentRoleNames( Role role )
+        throws RbacManagerException;
+
+    /**
+     * Returns all the parent roles of a given role as map of (id, role) elements.
+     * @param role the role to check for parents roles
+     * @return a map of (role id, role) pairs that have <code>role</code> as child
+     * @throws RbacManagerException if the access to the backend datastore failed
+     */
+    Map<String, ? extends Role> getParentRoleIds( Role role )
         throws RbacManagerException;
 
     /**
diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java
index 407be44..f389d82 100644
--- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java
+++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java
@@ -59,6 +59,24 @@ public interface Role
     List<String> getChildRoleNames();
 
     /**
+     * Adds a child role and sets the list of child names and child ids.
+     * @param child the child role
+     */
+    void addChildRole( Role child );
+
+    /**
+     * Adds a child role id
+     * @param id the id
+     */
+    void addChildRoleId( String id );
+
+    /**
+     * Returns the child role ids
+     * @return the list of child role ids
+     */
+    List<String> getChildRoleIds();
+
+    /**
      * Convenience method to see if Role has Child Roles.
      *
      * @return true if child roles exists and has any roles being tracked.
@@ -115,6 +133,12 @@ public interface Role
     void setChildRoleNames( List<String> names );
 
     /**
+     * Sets the list of child role ids
+     * @param ids
+     */
+    void setChildRoleIds( List<String> ids );
+
+    /**
      * Set the Description
      *
      * @param description the role description
@@ -220,4 +244,6 @@ public interface Role
      * @param resource the resource identifier. Must not be null.
      */
     void setResource( String resource );
+
+
 }
\ No newline at end of file
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java
index 8ffc360..7fbbcdd 100644
--- a/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java
@@ -291,19 +291,33 @@ public class CachedRbacManager
     }
 
     @Override
-    public Map<String, ? extends Role> getChildRoles( Role role )
+    public Map<String, ? extends Role> getChildRoleNames( Role role )
         throws RbacManagerException
     {
         log.debug( "NOT CACHED - .getChildRoles(Role)" );
-        return this.rbacImpl.getChildRoles( role );
+        return this.rbacImpl.getChildRoleNames( role );
     }
 
     @Override
-    public Map<String, ? extends Role> getParentRoles( Role role )
+    public Map<String, ? extends Role> getChildRoleIds( Role role ) throws RbacManagerException
+    {
+        log.debug( "NOT CACHED - .getChildRoles(Role)" );
+        return this.rbacImpl.getChildRoleIds( role );
+    }
+
+    @Override
+    public Map<String, ? extends Role> getParentRoleNames( Role role )
         throws RbacManagerException
     {
         log.debug( "NOT CACHED - .getParentRoles(Role)" );
-        return this.rbacImpl.getParentRoles( role );
+        return this.rbacImpl.getParentRoleNames( role );
+    }
+
+    @Override
+    public Map<String, ? extends Role> getParentRoleIds( Role role ) throws RbacManagerException
+    {
+        log.debug( "NOT CACHED - .getParentRoles(Role)" );
+        return this.rbacImpl.getParentRoleIds( role );
     }
 
     @Override
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java
index 0c7a9da..8bd3212 100644
--- a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java
@@ -35,7 +35,7 @@ import org.apache.archiva.redback.rbac.jpa.model.JpaPermission;
 import org.apache.archiva.redback.rbac.jpa.model.JpaResource;
 import org.apache.archiva.redback.rbac.jpa.model.JpaRole;
 import org.apache.archiva.redback.rbac.jpa.model.JpaUserAssignment;
-import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.archiva.redback.rbac.jpa.model.RoleId;
 import org.springframework.stereotype.Service;
 
 import javax.persistence.EntityManager;
@@ -44,9 +44,6 @@ import javax.persistence.PersistenceContext;
 import javax.persistence.Query;
 import javax.persistence.TypedQuery;
 import javax.transaction.Transactional;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -138,8 +135,15 @@ public class JpaRbacManager extends AbstractRBACManager  {
 
     @Transactional
     @Override
-    public Map<String, ? extends Role> getChildRoles(Role role) throws RbacManagerException {
-        return super.getChildRoles(role);
+    public Map<String, ? extends Role> getChildRoleNames( Role role) throws RbacManagerException {
+        return super.getChildRoleNames(role);
+    }
+
+    @Transactional
+    @Override
+    public Map<String, ? extends Role> getChildRoleIds( Role role ) throws RbacManagerException
+    {
+        return super.getChildRoleIds( role );
     }
 
     @Transactional
@@ -219,7 +223,7 @@ public class JpaRbacManager extends AbstractRBACManager  {
             throw new RbacPermanentException( "Unable to delete permanent role [" + role.getName() + "]" );
         }
         final EntityManager em = getEm();
-        JpaRole myRole = em.find(JpaRole.class, role.getName());
+        JpaRole myRole = em.find(JpaRole.class, new RoleId( role.getId(), role.getName()));
         if (myRole == null) {
             throw new RbacObjectNotFoundException("Role not found "+role.getName());
         }
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java
index d21eb6a..7b775ae 100644
--- a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java
@@ -21,6 +21,7 @@ package org.apache.archiva.redback.rbac.jpa.model;
 
 import org.apache.archiva.redback.rbac.AbstractRole;
 import org.apache.archiva.redback.rbac.Permission;
+import org.apache.archiva.redback.rbac.Role;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.annotation.Order;
@@ -37,15 +38,17 @@ import java.util.List;
 @Table(
         name="SECURITY_ROLES"
 )
+@IdClass( RoleId.class )
 public class JpaRole extends AbstractRole implements Serializable {
 
     private static final Logger log = LoggerFactory.getLogger( JpaRole.class );
     private static final long serialVersionUID = 4564608138465995665L;
 
     @Id
-    @Column(name="NAME")
+    @Column(name="NAME", unique = true)
     private String name;
-    @Column(name="ID", unique = true)
+    @Id
+    @Column( name = "ID", unique = true )
     private String id;
     @Column(name="DESCRIPTION")
     private String description;
@@ -75,6 +78,17 @@ public class JpaRole extends AbstractRole implements Serializable {
     )
     List<String> childRoleNames = new ArrayList<String>();
 
+    @ElementCollection(fetch = FetchType.EAGER)
+    @OrderColumn(name="INTEGER_IDX",nullable = false)
+    @Column(name="CHILD_IDS")
+    @CollectionTable(
+        name="SECURITY_ROLE_CHILDROLE_ID_MAP",
+        joinColumns = {
+            @JoinColumn(name="ID_OID",referencedColumnName = "ID", nullable = false)
+        }
+    )
+    List<String> childRoleIds = new ArrayList<String>();
+
     @Column(name="TEMPLATE_INSTANCE",nullable = false)
     private Boolean templateInstance = false;
 
@@ -102,11 +116,23 @@ public class JpaRole extends AbstractRole implements Serializable {
     }
 
     @Override
+    public void addChildRoleId( String id )
+    {
+        this.childRoleIds.add( id );
+    }
+
+    @Override
     public List<String> getChildRoleNames() {
         return childRoleNames;
     }
 
     @Override
+    public List<String> getChildRoleIds( )
+    {
+        return childRoleIds;
+    }
+
+    @Override
     public String getDescription() {
         return description;
     }
@@ -143,6 +169,13 @@ public class JpaRole extends AbstractRole implements Serializable {
     }
 
     @Override
+    public void setChildRoleIds( List<String> childRoleIds )
+    {
+        this.childRoleIds.clear();
+        this.childRoleIds.addAll( childRoleIds );
+    }
+
+    @Override
     public void setDescription(String description) {
         this.description=description;
 
@@ -245,12 +278,15 @@ public class JpaRole extends AbstractRole implements Serializable {
 
         JpaRole jpaRole = (JpaRole) o;
 
-        return name.equals( jpaRole.name );
+        if ( !name.equals( jpaRole.name ) ) return false;
+        return id.equals( jpaRole.id );
     }
 
     @Override
     public int hashCode( )
     {
-        return name.hashCode( );
+        int result = name.hashCode( );
+        result = 31 * result + id.hashCode( );
+        return result;
     }
 }
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/RoleId.java
similarity index 50%
copy from redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
copy to redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/RoleId.java
index 3faaf8c..f63d77f 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/RoleId.java
@@ -1,6 +1,4 @@
-package org.apache.archiva.redback.rest.api;
-
-/*
+package org.apache.archiva.redback.rbac.jpa.model;/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -18,12 +16,44 @@ package org.apache.archiva.redback.rest.api;
  * under the License.
  */
 
+import java.io.Serializable;
+
 /**
  * @author Martin Stockhammer <ma...@apache.org>
  */
-public interface Constants
+public class RoleId implements Serializable
 {
-    String DEFAULT_PAGE_LIMIT = "1000";
+    private static final long serialVersionUID = -3358026083136193536L;
+    private String id;
+    private String name;
+
+    public RoleId( )
+    {
+    }
+
+    public RoleId( String id, String name )
+    {
+        this.id = id;
+        this.name = name;
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        RoleId roleId = (RoleId) o;
 
+        if ( !id.equals( roleId.id ) ) return false;
+        return name.equals( roleId.name );
+    }
 
+    @Override
+    public int hashCode( )
+    {
+        int result = id.hashCode( );
+        result = 31 * result + name.hashCode( );
+        return result;
+    }
 }
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java
index df05dcf..c5d9e8a 100644
--- a/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java
@@ -60,10 +60,8 @@ import javax.naming.directory.DirContext;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -474,17 +472,29 @@ public class LdapRbacManager
     }
 
     @Override
-    public Map<String, ? extends Role> getChildRoles( Role role )
+    public Map<String, ? extends Role> getChildRoleNames( Role role )
         throws RbacManagerException
     {
-        return this.rbacImpl.getChildRoles( role );
+        return this.rbacImpl.getChildRoleNames( role );
     }
 
     @Override
-    public Map<String, ? extends Role> getParentRoles( Role role )
+    public Map<String, ? extends Role> getChildRoleIds( Role role ) throws RbacManagerException
+    {
+        return this.rbacImpl.getChildRoleIds( role );
+    }
+
+    @Override
+    public Map<String, ? extends Role> getParentRoleNames( Role role )
         throws RbacManagerException
     {
-        return this.rbacImpl.getParentRoles( role );
+        return this.rbacImpl.getParentRoleNames( role );
+    }
+
+    @Override
+    public Map<String, ? extends Role> getParentRoleIds( Role role ) throws RbacManagerException
+    {
+        return this.rbacImpl.getParentRoleIds( role );
     }
 
     //
@@ -1241,9 +1251,10 @@ public class LdapRbacManager
         private boolean isTemplateInstance=false;
         private String resource="";
 
-        private List<Permission> permissions = new ArrayList<Permission>();
+        private List<Permission> permissions = new ArrayList<>();
 
-        private List<String> childRoleNames = new ArrayList<String>();
+        private List<String> childRoleNames = new ArrayList<>();
+        private List<String> childRoleIds = new ArrayList<>( );
 
         private RoleImpl( String name )
         {
@@ -1281,6 +1292,18 @@ public class LdapRbacManager
         }
 
         @Override
+        public void addChildRoleId( String id )
+        {
+            this.childRoleIds.add( id );
+        }
+
+        @Override
+        public List<String> getChildRoleIds( )
+        {
+            return this.childRoleIds;
+        }
+
+        @Override
         public String getDescription()
         {
             return this.description;
@@ -1323,6 +1346,12 @@ public class LdapRbacManager
         }
 
         @Override
+        public void setChildRoleIds( List<String> ids )
+        {
+
+        }
+
+        @Override
         public void setDescription( String description )
         {
             this.description = description;
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java b/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java
index c243af8..1eed3a0 100644
--- a/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java
@@ -61,6 +61,8 @@ public class MemoryRole
      */
     private List<String> childRoleNames = new ArrayList<>( 0 );
 
+    private List<String> childRoleIds = new ArrayList<>( 0 );
+
     /**
      * Field permissions
      */
@@ -110,6 +112,20 @@ public class MemoryRole
         return this.childRoleNames;
     }
 
+
+
+    @Override
+    public void addChildRoleId( String id )
+    {
+        this.childRoleIds.add( id );
+    }
+
+    @Override
+    public List<String> getChildRoleIds( )
+    {
+        return this.childRoleIds;
+    }
+
     @Override
     public String getDescription()
     {
@@ -199,6 +215,12 @@ public class MemoryRole
     }
 
     @Override
+    public void setChildRoleIds( List<String> ids )
+    {
+
+    }
+
+    @Override
     public boolean isPermanent()
     {
         return permanent;
diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java
index 9d68db6..9fbf5c9 100644
--- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java
+++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java
@@ -21,6 +21,7 @@ package org.apache.archiva.redback.role;
 
 import org.apache.archiva.redback.rbac.RBACManager;
 import org.apache.archiva.redback.rbac.RbacManagerException;
+import org.apache.archiva.redback.rbac.RbacObjectNotFoundException;
 import org.apache.archiva.redback.rbac.Role;
 import org.apache.archiva.redback.rbac.UserAssignment;
 import org.apache.archiva.redback.role.model.ModelApplication;
@@ -184,10 +185,10 @@ public class DefaultRoleManager
      * resolving the ${resource} expression
      */
     @Override
-    public void createTemplatedRole( String templateId, String resource )
+    public String createTemplatedRole( String templateId, String resource )
         throws RoleManagerException
     {
-        templateProcessor.create( blessedModel, templateId, resource );
+        return templateProcessor.create( blessedModel, templateId, resource );
     }
 
     /**
@@ -198,13 +199,10 @@ public class DefaultRoleManager
     public void removeTemplatedRole( String templateId, String resource )
         throws RoleManagerException
     {
-        ModelTemplate template = RoleModelUtils.getModelTemplate( blessedModel, templateId );
-
-        String roleName = template.getNamePrefix() + template.getDelimiter() + resource;
-
+        String roleId = templateProcessor.getRoleId( templateId, resource );
         try
         {
-            Role role = rbacManager.getRole( roleName );
+            Role role = rbacManager.getRoleById( roleId );
 
             for ( UserAssignment assignment : rbacManager.getUserAssignmentsForRoles(
                 Arrays.asList( role.getName() ) ) )
@@ -213,10 +211,12 @@ public class DefaultRoleManager
                 rbacManager.saveUserAssignment( assignment );
             }
 
+        } catch ( RbacObjectNotFoundException e) {
+            throw new RoleNotFoundException( e.getMessage( ), e );
         }
         catch ( RbacManagerException e )
         {
-            throw new RoleManagerException( "unable to remove role", e );
+            throw new RoleManagerException( "Unable to remove role", e );
         }
 
         templateProcessor.remove( blessedModel, templateId, resource );
@@ -229,11 +229,11 @@ public class DefaultRoleManager
      * because of the use of the name as an identifier
      */
     @Override
-    public void moveTemplatedRole( String templateId, String oldResource, String newResource )
+    public String moveTemplatedRole( String templateId, String oldResource, String newResource )
         throws RoleManagerException
     {
         // make the new role
-        templateProcessor.create( blessedModel, templateId, newResource );
+        String roleId = templateProcessor.create( blessedModel, templateId, newResource );
 
         ModelTemplate template = RoleModelUtils.getModelTemplate( blessedModel, templateId );
 
@@ -259,6 +259,7 @@ public class DefaultRoleManager
         }
 
         templateProcessor.remove( blessedModel, templateId, oldResource );
+        return roleId;
     }
 
     @Override
@@ -269,7 +270,7 @@ public class DefaultRoleManager
 
         if ( modelRole == null )
         {
-            throw new RoleManagerException( "Unable to assign role: " + roleId + " does not exist." );
+            throw new RoleNotFoundException( "Unable to assign role: " + roleId + " does not exist." );
         }
 
         try
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleExistsException.java
similarity index 73%
copy from redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
copy to redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleExistsException.java
index 3faaf8c..0403cb3 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
+++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleExistsException.java
@@ -1,6 +1,4 @@
-package org.apache.archiva.redback.rest.api;
-
-/*
+package org.apache.archiva.redback.role;/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -21,9 +19,15 @@ package org.apache.archiva.redback.rest.api;
 /**
  * @author Martin Stockhammer <ma...@apache.org>
  */
-public interface Constants
+public class RoleExistsException extends RoleManagerException
 {
-    String DEFAULT_PAGE_LIMIT = "1000";
-
+    public RoleExistsException( String string )
+    {
+        super( string );
+    }
 
+    public RoleExistsException( String string, Throwable throwable )
+    {
+        super( string, throwable );
+    }
 }
diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java
index 7c727e8..7448a8f 100644
--- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java
+++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java
@@ -53,7 +53,7 @@ public interface RoleManager
      * @param resource
      * @throws RoleManagerException
      */
-    void createTemplatedRole( String templateId, String resource )
+    String createTemplatedRole( String templateId, String resource )
         throws RoleManagerException;
 
     /**
@@ -78,7 +78,7 @@ public interface RoleManager
      * @param newResource the new resource name
      * @throws RoleManagerException
      */
-    void moveTemplatedRole( String templateId, String oldResource, String newResource )
+    String moveTemplatedRole( String templateId, String oldResource, String newResource )
         throws RoleManagerException;
 
 
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleNotFoundException.java
similarity index 73%
copy from redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
copy to redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleNotFoundException.java
index 3faaf8c..e29c14a 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java
+++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleNotFoundException.java
@@ -1,6 +1,4 @@
-package org.apache.archiva.redback.rest.api;
-
-/*
+package org.apache.archiva.redback.role;/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -21,9 +19,15 @@ package org.apache.archiva.redback.rest.api;
 /**
  * @author Martin Stockhammer <ma...@apache.org>
  */
-public interface Constants
+public class RoleNotFoundException extends RoleManagerException
 {
-    String DEFAULT_PAGE_LIMIT = "1000";
-
+    public RoleNotFoundException( String string )
+    {
+        super( string );
+    }
 
+    public RoleNotFoundException( String string, Throwable throwable )
+    {
+        super( string, throwable );
+    }
 }
diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java
index e0b4b65..b39c0c7 100644
--- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java
+++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java
@@ -216,6 +216,7 @@ public class DefaultRoleModelProcessor
                         {
                             ModelRole childRoleProfile = RoleModelUtils.getModelRole( model, childRoleId );
                             role.addChildRoleName( childRoleProfile.getName() );
+                            role.addChildRoleId( childRoleProfile.getId() );
                         }
                     }
 
@@ -229,7 +230,7 @@ public class DefaultRoleModelProcessor
                         {
                             ModelRole parentModelRole = RoleModelUtils.getModelRole( model, parentRoleId );
                             Role parentRole = rbacManager.getRole( parentModelRole.getName() );
-                            parentRole.addChildRoleName( role.getName() );
+                            parentRole.addChildRole( role );
                             rbacManager.saveRole( parentRole );
                             allRoleNames.add( parentRole.getName() );
                         }
diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java
index 3c43333..f7533c1 100644
--- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java
+++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java
@@ -25,7 +25,9 @@ import org.apache.archiva.redback.rbac.RbacManagerException;
 import org.apache.archiva.redback.rbac.Resource;
 import org.apache.archiva.redback.rbac.Role;
 import org.apache.archiva.redback.rbac.RBACManager;
+import org.apache.archiva.redback.role.RoleExistsException;
 import org.apache.archiva.redback.role.RoleManagerException;
+import org.apache.archiva.redback.role.RoleNotFoundException;
 import org.apache.archiva.redback.role.model.ModelApplication;
 import org.apache.archiva.redback.role.model.ModelOperation;
 import org.apache.archiva.redback.role.model.ModelPermission;
@@ -60,8 +62,9 @@ public class DefaultRoleTemplateProcessor
     @Named(value = "rbacManager#default")
     private RBACManager rbacManager;
 
+    @Override
     @SuppressWarnings("unchecked")
-    public void create( RedbackRoleModel model, String templateId, String resource )
+    public String create( final RedbackRoleModel model, final String templateId, final String resource )
         throws RoleManagerException
     {
         for ( ModelApplication application : model.getApplications() )
@@ -74,16 +77,16 @@ public class DefaultRoleTemplateProcessor
                     processResource( template, resource );
 
                     // templates are roles that have yet to be paired with a resource for creation
-                    processTemplate( model, template, resource );
+                    return processTemplate( model, template, resource );
 
-                    return;
                 }
             }
         }
 
-        throw new RoleManagerException( "unknown template '" + templateId + "'" );
+        throw new RoleNotFoundException( "unknown template '" + templateId + "'" );
     }
 
+    @Override
     @SuppressWarnings("unchecked")
     public void remove( RedbackRoleModel model, String templateId, String resource )
         throws RoleManagerException
@@ -106,11 +109,11 @@ public class DefaultRoleTemplateProcessor
     private void removeTemplatedRole( RedbackRoleModel model, ModelTemplate template, String resource )
         throws RoleManagerException
     {
-        String roleName = template.getNamePrefix() + template.getDelimiter() + resource;
+        String roleId = getRoleId( template.getId( ), resource );
 
         try
         {
-            Role role = rbacManager.getRole( roleName );
+            Role role = rbacManager.getRoleById( roleId );
 
             if ( !role.isPermanent() )
             {
@@ -142,12 +145,12 @@ public class DefaultRoleTemplateProcessor
             }
             else
             {
-                throw new RoleManagerException( "unable to remove role, it is flagged permanent" );
+                throw new RoleManagerException( "Unable to remove role, it is flagged permanent" );
             }
         }
         catch ( RbacManagerException e )
         {
-            throw new RoleManagerException( "unable to remove templated role: " + roleName, e );
+            throw new RoleManagerException( "Unable to remove templated role: " + roleId, e );
         }
         //catch ( RoleTemplateProcessorException e )
         //{
@@ -173,12 +176,17 @@ public class DefaultRoleTemplateProcessor
         }
     }
 
+    @Override
+    public String getRoleId( String templateId, String resource) {
+        return templateId + "." + resource;
+    }
+
     @SuppressWarnings("unchecked")
-    private void processTemplate( RedbackRoleModel model, ModelTemplate template, String resource )
+    private String processTemplate( RedbackRoleModel model, ModelTemplate template, String resource )
         throws RoleManagerException
     {
         final String templateName = template.getNamePrefix() + template.getDelimiter() + resource;
-        final String roleId = template.getId( ) + "." + resource;
+        final String roleId = getRoleId( template.getId( ), resource );
 
         List<Permission> permissions = processPermissions( model, template, resource );
 
@@ -190,7 +198,7 @@ public class DefaultRoleTemplateProcessor
         }
         catch ( RbacManagerException e )
         {
-            throw new RoleManagerException( e.getMessage(), e );
+            throw new RoleExistsException( e.getMessage(), e );
         }
 
         if ( !roleExists )
@@ -221,6 +229,7 @@ public class DefaultRoleTemplateProcessor
                     {
                         ModelRole childRoleProfile = RoleModelUtils.getModelRole( model, childRoleId );
                         role.addChildRoleName( childRoleProfile.getName() );
+                        role.addChildRoleId( childRoleProfile.getId() );
                     }
                 }
 
@@ -246,12 +255,14 @@ public class DefaultRoleTemplateProcessor
                         if ( rbacManager.roleExists( childRoleName ) )
                         {
                             role.addChildRoleName( childRoleName );
+                            role.addChildRoleId( getRoleId( childTemplateId, resource ) );
                         }
                         else
                         {
                             processTemplate( model, childModelTemplate, resource );
 
                             role.addChildRoleName( childRoleName );
+                            role.addChildRoleId( getRoleId( childTemplateId, resource ) );
                         }
                     }
                 }
@@ -270,7 +281,7 @@ public class DefaultRoleTemplateProcessor
                     {
                         ModelRole parentModelRole = RoleModelUtils.getModelRole( model, parentRoleId );
                         Role parentRole = rbacManager.getRole( parentModelRole.getName() );
-                        parentRole.addChildRoleName( role.getName() );
+                        parentRole.addChildRole( role );
                         rbacManager.saveRole( parentRole );
                     }
                 }
@@ -298,7 +309,7 @@ public class DefaultRoleTemplateProcessor
                         {
                             Role parentRole = rbacManager.getRole( parentRoleName );
 
-                            parentRole.addChildRoleName( role.getName() );
+                            parentRole.addChildRole( role );
                             rbacManager.saveRole( parentRole );
                         }
                         else
@@ -307,7 +318,7 @@ public class DefaultRoleTemplateProcessor
 
                             Role parentRole = rbacManager.getRole( parentRoleName );
 
-                            parentRole.addChildRoleName( role.getName() );
+                            parentRole.addChildRole( role );
                             rbacManager.saveRole( parentRole );
                         }
                     }
@@ -358,6 +369,7 @@ public class DefaultRoleTemplateProcessor
                 throw new RoleManagerException( "error updating role '" + templateName + "'", e );
             }
         }
+        return roleId;
     }
 
     @SuppressWarnings("unchecked")
diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java
index 7f8e07d..e237a04 100644
--- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java
+++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java
@@ -30,9 +30,33 @@ import org.apache.archiva.redback.role.model.RedbackRoleModel;
 public interface RoleTemplateProcessor
 {
 
-    void create( RedbackRoleModel model, String templateId, String resource )
+    /**
+     * Creates a role instance from a template for the given resource and returns the id of the new role.
+     * @param model the model
+     * @param templateId the template identifier
+     * @param resource the resource to which the role is applied
+     * @return the id of the role
+     * @throws RoleManagerException if the access to the backend datastore failed
+     */
+    String create( RedbackRoleModel model, String templateId, String resource )
         throws RoleManagerException;
 
+    /**
+     * Removes the role instance that belongs to the template from the datastore
+     * @param model the model
+     * @param templateId the template identifier
+     * @param resource the resource to which the role is applied
+     * @throws RoleManagerException if the access to the backend datastore failed
+     */
     void remove( RedbackRoleModel model, String templateId, String resource )
         throws RoleManagerException;
+
+
+    /**
+     * Returns the role id that identifies the role that is a instance of the given template for the given resource.
+     * @param templateId the template identifier
+     * @param resource the resource
+     * @return the role identifier
+     */
+    String getRoleId( String templateId, String resource );
 }
diff --git a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java
index 11eacdd..7d1512f 100644
--- a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java
+++ b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java
@@ -169,6 +169,7 @@ public class AbstractRbacManagerPerformanceTestCase
         Role devRole = getDeveloperRole();
         Role devPlusRole = getSuperDeveloperRole();
         devPlusRole.setChildRoleNames( Collections.singletonList( devRole.getName() ) );
+        devPlusRole.setChildRoleIds( Collections.singletonList( devRole.getId() ) );
         devRole = manager.saveRole( devRole );
         devPlusRole = manager.saveRole( devPlusRole );
 
@@ -197,6 +198,7 @@ public class AbstractRbacManagerPerformanceTestCase
         username = "janet";
 
         devPlusRole.setChildRoleNames( Collections.singletonList( devRole.getName() ) );
+        devPlusRole.setChildRoleIds( Collections.singletonList( devRole.getId() ) );
         devRole = manager.saveRole( devRole );
         manager.saveRole( devPlusRole );
 
diff --git a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java
index 5ec4cc5..bca655e 100644
--- a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java
+++ b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java
@@ -91,7 +91,7 @@ public abstract class AbstractRbacManagerTestCase
     private Role getAdminRole()
         throws RbacManagerException
     {
-        Role role = rbacManager.createRole( "ADMIN" );
+        Role role = rbacManager.createRole( "admin", "ADMIN" );
         role.setAssignable( false );
 
         Permission perm = rbacManager.createPermission( "EDIT_ANY_USER", "EDIT", "User:*" );
@@ -375,6 +375,7 @@ public abstract class AbstractRbacManagerTestCase
         manager.saveRole( projectRole );
 
         develRole.addChildRoleName( projectRoleName );
+        develRole.addChildRoleId( projectRole.getId() );
 
         manager.saveRole( develRole );
 
@@ -428,7 +429,7 @@ public abstract class AbstractRbacManagerTestCase
 
         Role adminRole = getAdminRole();
 
-        adminRole.addChildRoleName( developerRole.getName() );
+        adminRole.addChildRole( developerRole );
 
         adminRole = manager.saveRole( adminRole );
 
@@ -631,6 +632,7 @@ public abstract class AbstractRbacManagerTestCase
         Role devRole = getDeveloperRole();
         Role devPlusRole = getSuperDeveloperRole();
         devPlusRole.setChildRoleNames( Collections.singletonList( devRole.getName() ) );
+        devPlusRole.setChildRoleIds( Collections.singletonList( devRole.getId() ) );
         manager.saveRole( devRole );
         manager.saveRole( devPlusRole );
 
diff --git a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java
index 16c85af..a451485 100644
--- a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java
+++ b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java
@@ -159,6 +159,7 @@ public class RBACDefaults
         {
             Role admin = manager.createRole( "System Administrator" );
             admin.addChildRoleName( "User Administrator" );
+            admin.addChildRoleId( "user-administrator" );
             admin.addPermission( manager.getPermission( "Edit Configuration" ) );
             admin.addPermission( manager.getPermission( "Run Indexer" ) );
             admin.addPermission( manager.getPermission( "Add Repository" ) );
@@ -171,6 +172,7 @@ public class RBACDefaults
         {
             Role developer = manager.createRole( "Trusted Developer" );
             developer.addChildRoleName( "System Administrator" );
+            developer.addChildRoleId( "system-administrator" );
             developer.addPermission( manager.getPermission( "Run Indexer" ) );
             developer.setAssignable( true );
             manager.saveRole( developer );
@@ -180,6 +182,7 @@ public class RBACDefaults
         {
             Role developer = manager.createRole( "Developer" );
             developer.addChildRoleName( "Trusted Developer" );
+            developer.addChildRoleId( "trusted-developer" );
             developer.addPermission( manager.getPermission( "Run Indexer" ) );
             developer.setAssignable( true );
             manager.saveRole( developer );