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/12/22 23:07:02 UTC

[archiva-redback-core] branch master updated (cba11d9 -> 7de377d)

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

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


    from cba11d9  Adding template method for REST v2
     new 23e2edc  Updating Role REST v2 service
     new 74581c7  Improving role user assignment search
     new 7de377d  Adding role method

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 idea.run.configuration/All Rest Services.run.xml   |   2 +-
 .../V2 AuthenticationServiceTest.run.xml           |   2 +-
 ...st.services.v2 in redback-rest-services.run.xml |   2 +-
 .../org/apache/archiva/redback/rest/api/Util.java  |  59 ++++
 .../redback/rest/api/model/v2/Permission.java      |   4 +
 .../redback/rest/api/model/v2/RoleInfo.java        |  30 +-
 .../redback/rest/api/services/v2/RoleService.java  | 149 +++++++--
 .../interceptors/RequestValidationInterceptor.java |   2 +-
 .../rest/services/v2/BaseRedbackService.java       | 110 ++++++-
 .../rest/services/v2/DefaultRoleService.java       |  65 +++-
 .../rest/services/v2/DefaultUserService.java       |  49 +--
 .../rest/services/v2/NativeRoleServiceTest.java    | 335 ++++++++++++++++++---
 12 files changed, 658 insertions(+), 151 deletions(-)
 create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Util.java


[archiva-redback-core] 01/03: Updating Role REST v2 service

Posted by ma...@apache.org.
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 23e2edc80ef62231fb6df6c9f4f64674b14a619f
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Sun Dec 20 16:46:54 2020 +0100

    Updating Role REST v2 service
---
 .../redback/rest/api/model/v2/Permission.java      |   4 +
 .../redback/rest/api/model/v2/RoleInfo.java        |  30 +---
 .../redback/rest/api/services/v2/RoleService.java  |  83 +++++++----
 .../interceptors/RequestValidationInterceptor.java |   2 +-
 .../rest/services/v2/BaseRedbackService.java       |  90 +++++++++++-
 .../rest/services/v2/DefaultRoleService.java       |  32 +++-
 .../rest/services/v2/DefaultUserService.java       |  49 +------
 .../rest/services/v2/NativeRoleServiceTest.java    | 163 +++++++++++++++------
 8 files changed, 305 insertions(+), 148 deletions(-)

diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Permission.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Permission.java
index daf5f65..3900bc5 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Permission.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Permission.java
@@ -60,6 +60,10 @@ public class Permission
         this.permanent = permission.isPermanent();
     }
 
+    public static Permission of( org.apache.archiva.redback.rbac.Permission perm )  {
+        return new Permission( perm );
+    }
+
     @Schema(name="name", description = "The identifier of the permission")
     public String getName()
     {
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
index 01a2698..11ead1a 100644
--- 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
@@ -28,6 +28,7 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * Result object for role information.
@@ -36,7 +37,7 @@ import java.util.Objects;
  * @since 3.0
  */
 @XmlRootElement( name = "role" )
-@Schema(name="RoleInfo",description = "Information about role")
+@Schema(name="RoleInfo",description = "Information about role.")
 public class RoleInfo extends BaseRoleInfo
     implements Serializable
 {
@@ -57,26 +58,6 @@ public class RoleInfo extends BaseRoleInfo
      */
     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
@@ -85,6 +66,12 @@ public class RoleInfo extends BaseRoleInfo
 
     public static RoleInfo of( Role rbacRole) {
         RoleInfo role = BaseRoleInfo.of( rbacRole, new RoleInfo( ) );
+        if(rbacRole.getPermissions()!=null)
+        {
+            role.permissions = rbacRole.getPermissions( ).stream( ).map( rbacPerm ->
+                Permission.of( rbacPerm )
+            ).collect( Collectors.toList( ) );
+        }
         return role;
     }
 
@@ -151,7 +138,6 @@ public class RoleInfo extends BaseRoleInfo
         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( '\'' );
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 c88bc61..3aec8ba 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
@@ -33,6 +33,7 @@ import org.apache.archiva.redback.rest.api.model.v2.PagedResult;
 import org.apache.archiva.redback.rest.api.model.v2.Role;
 import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
 import org.apache.archiva.redback.rest.api.model.v2.RoleTemplate;
+import org.apache.archiva.redback.rest.api.model.v2.UserInfo;
 import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
 
 import javax.ws.rs.DELETE;
@@ -99,7 +100,7 @@ public interface RoleService
     @GET
     @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    @Operation( summary = "Returns information about a specific role. Use HTTP HEAD method for checking, if the resource exists.",
+    @Operation( summary = "Returns the definition about a specific role.",
         security = {
             @SecurityRequirement(
                 name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION
@@ -124,7 +125,7 @@ public interface RoleService
     @HEAD
     @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    @Operation( summary = "Returns information about a specific role. Use HTTP HEAD method for checking, if the resource exists.",
+    @Operation( summary = "Checks, if the role with the given id exists.",
         security = {
             @SecurityRequirement(
                 name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION
@@ -273,70 +274,71 @@ public interface RoleService
         throws RedbackServiceException;
 
 
+
     /**
-     * Assigns the role indicated by the roleId to the given principal
+     * Assigns the templated role indicated by the templateId
      *
-     * @param roleId
+     * fails if the templated role has not been created
+     *
+     * @param templateId
+     * @param resource
      * @param userId
      */
-    @Path( "{roleId}/user/{userId}" )
+    @Path( "templates/{templateId}/{resource}/user/{userId}" )
     @PUT
     @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    @Operation( summary = "Assigns a role to a given user",
+    @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 was assigned"
+                description = "If the role instance was assigned"
             ),
-            @ApiResponse( responseCode = "404", description = "Role does not exist",
+            @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 assignRole( @PathParam( "roleId" ) String roleId, @PathParam( "userId" ) String userId )
+    RoleInfo assignTemplatedRole( @PathParam( "templateId" ) String templateId,
+                                 @PathParam( "resource" ) String resource,
+                                 @PathParam( "userId" ) String userId )
         throws RedbackServiceException;
 
     /**
-     * Assigns the templated role indicated by the templateId
-     *
-     * fails if the templated role has not been created
+     * Assigns the role indicated by the roleId to the given principal
      *
-     * @param templateId
-     * @param resource
+     * @param roleId
      * @param userId
      */
-    @Path( "templates/{templateId}/{resource}/user/{userId}" )
+    @Path( "{roleId}/user/{userId}" )
     @PUT
     @Produces( { APPLICATION_JSON } )
     @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
-    @Operation( summary = "Assigns a template role instance to a given user",
+    @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 instance was assigned"
+                description = "If the role was assigned"
             ),
-            @ApiResponse( responseCode = "404", description = "Role instance does not exist",
+            @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 assignTemplatedRole( @PathParam( "templateId" ) String templateId,
-                                 @PathParam( "resource" ) String resource,
-                                 @PathParam( "userId" ) String userId )
+    RoleInfo assignRole( @PathParam( "roleId" ) String roleId, @PathParam( "userId" ) String userId )
         throws RedbackServiceException;
 
     /**
-     * Unassigns the role indicated by the role id from the given principal
+     * Deletes the assignment of a role to a user.
      *
-     * @param roleId
-     * @param userId
+     * @param roleId the role id
+     * @param userId the user id
      * @throws RedbackServiceException
      */
     @Path( "{roleId}/user/{userId}" )
@@ -357,9 +359,40 @@ public interface RoleService
                 content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
         }
     )
-    RoleInfo unassignRole( @PathParam( "roleId" ) String roleId, @PathParam( "userId" ) String userId )
+    RoleInfo deleteRoleAssignment( @PathParam( "roleId" ) String roleId, @PathParam( "userId" ) String userId )
         throws RedbackServiceException;
 
+    @Path("{roleId}/user")
+    @GET
+    @Produces({APPLICATION_JSON})
+    @RedbackAuthorization(permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION)
+    @Operation( summary = "Returns the users assigned to the given role",
+        parameters = {
+            @Parameter(name = "q", description = "Search term"),
+            @Parameter(name = "offset", description = "The offset of the first element returned"),
+            @Parameter(name = "limit", description = "Maximum number of items to return in the response"),
+            @Parameter(name = "orderBy", description = "List of attribute used for sorting (user_id, fullName, email, created"),
+            @Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)")
+        },
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the users could be retrieved"
+            ),
+            @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 )) )
+        }
+    )
+    PagedResult<UserInfo> getRoleUsers(@PathParam( "roleId" ) String roleId,
+                                       @QueryParam("q") @DefaultValue( "" ) String searchTerm,
+                                       @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
+                                       @QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
+                                       @QueryParam( "orderBy") @DefaultValue( "id" ) List<String> orderBy,
+                                       @QueryParam("order") @DefaultValue( "asc" ) String order) throws RedbackServiceException;
 
     /**
      * Updates a role. Attributes that are empty or null will be ignored.
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
index 7553cf0..fb3e72e 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
@@ -127,7 +127,7 @@ public class RequestValidationInterceptor
             "origin, content-type, accept, authorization");
         responseContext.getHeaders().add(
             "Access-Control-Allow-Methods",
-            "GET, POST, PUT, DELETE, OPTIONS, HEAD");
+            "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH");
     }
 
     private class HeaderValidationInfo
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
index 4dc9ab3..48de1bb 100644
--- 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
@@ -24,18 +24,26 @@ 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.PagedResult;
 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.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.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.inject.Named;
 import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -44,8 +52,36 @@ import java.util.stream.Stream;
  */
 public class BaseRedbackService
 {
+    protected static final String[] DEFAULT_SEARCH_FIELDS = {"user_id", "full_name", "email"};
+    protected static final Map<String, BiPredicate<String, User>> USER_FILTER_MAP = new HashMap<>( );
+    protected static final Map<String, Comparator<User>> USER_ORDER_MAP = new HashMap<>( );
+    protected static final QueryHelper<User> USER_QUERY_HELPER;
     private static final Logger log = LoggerFactory.getLogger( BaseRedbackService.class );
 
+
+    static
+    {
+        // 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
+        USER_ORDER_MAP.put( "id", Comparator.comparing( org.apache.archiva.redback.users.User::getId, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "user_id", Comparator.comparing( org.apache.archiva.redback.users.User::getUsername, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "full_name", Comparator.comparing( org.apache.archiva.redback.users.User::getFullName, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "email", Comparator.comparing( org.apache.archiva.redback.users.User::getEmail, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "created", Comparator.comparing( org.apache.archiva.redback.users.User::getAccountCreationDate, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "last_login", Comparator.comparing( org.apache.archiva.redback.users.User::getLastLoginDate, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "validated", Comparator.comparing( org.apache.archiva.redback.users.User::isValidated, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "locked", Comparator.comparing( org.apache.archiva.redback.users.User::isLocked, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "password_change_required", Comparator.comparing( org.apache.archiva.redback.users.User::isPasswordChangeRequired, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+        USER_ORDER_MAP.put( "last_password_change", Comparator.comparing( org.apache.archiva.redback.users.User::getLastPasswordChange, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
+
+        USER_FILTER_MAP.put( "user_id", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getUsername( ), q ) );
+        USER_FILTER_MAP.put( "full_name", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getFullName( ), q ) );
+        USER_FILTER_MAP.put( "email", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getEmail( ), q ) );
+
+        USER_QUERY_HELPER = new QueryHelper<>( USER_FILTER_MAP, USER_ORDER_MAP, DEFAULT_SEARCH_FIELDS );
+    }
+
     protected RBACManager rbacManager;
     protected UserManager userManager;
 
@@ -62,7 +98,6 @@ public class BaseRedbackService
             RoleInfo role = RoleInfo.of( rbacRole );
             role.setParentRoleIds( getParentRoles( rbacRole ) );
             role.setChildRoleIds( getChildRoles( rbacRole ) );
-            role.setAssignedUsers( getAssignedUsersRecursive( rbacRole ) );
             return role;
         }
         catch ( RbacManagerException e )
@@ -72,6 +107,10 @@ public class BaseRedbackService
         }
     }
 
+    protected boolean isAscending(String order) {
+        return !"desc".equals( order );
+    }
+
     protected List<String> getParentRoles( org.apache.archiva.redback.rbac.Role rbacRole ) throws RbacManagerException
     {
         return new ArrayList<>( rbacManager.getParentRoleIds( rbacRole ).keySet( ));
@@ -96,6 +135,33 @@ public class BaseRedbackService
         }
     }
 
+    protected List<User> getAssignedRedbackUsersRecursive( org.apache.archiva.redback.rbac.Role rbacRole ) throws RbacManagerException
+    {
+        try
+        {
+            return rbacManager.getUserAssignmentsForRoles( recurseRoles( rbacRole ).map( role -> role.getId( ) ).collect( Collectors.toList( ) ) )
+                .stream( ).map( assignment -> getRedbackUser( assignment.getPrincipal( ) ) ).collect( Collectors.toList( ) );
+        }
+        catch ( RuntimeException e )
+        {
+            log.error( "Could not recurse roles for assignments {}", e.getMessage( ) );
+            throw new RbacManagerException( e.getCause( ) );
+        }
+    }
+
+    protected User getRedbackUser(String userId) throws RuntimeException {
+        try
+        {
+            return userManager.findUser( userId, true );
+        }
+        catch ( UserManagerException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+
+
     private Stream<Role> recurseRoles( Role startRole )
     {
         return Stream.concat( Stream.of( startRole ), getParentRoleStream( startRole ).flatMap( this::recurseRoles ) ).distinct( );
@@ -133,7 +199,6 @@ public class BaseRedbackService
             RoleInfo role = RoleInfo.of( rbacRole );
             role.setParentRoleIds( getParentRoles( rbacRole ) );
             role.setChildRoleIds( getChildRoles( rbacRole ) );
-            role.setAssignedUsers( getAssignedUsersRecursive( rbacRole ) );
             return Optional.of( role );
         }
         catch ( RbacManagerException e )
@@ -142,4 +207,25 @@ public class BaseRedbackService
             return Optional.empty( );
         }
     }
+
+    protected UserInfo getRestUser( User user )
+    {
+        if ( user == null )
+        {
+            return null;
+        }
+        return new UserInfo( user );
+    }
+
+    protected PagedResult<UserInfo> getUserInfoPagedResult( List<? extends User> rawUsers, String q, Integer offset, Integer limit, List<String> orderBy, boolean ascending)
+    {
+        Predicate<User> filter = USER_QUERY_HELPER.getQueryFilter( q );
+        long size = rawUsers.stream( ).filter( filter ).count( );
+        List<UserInfo> users = rawUsers.stream( )
+            .filter( filter )
+            .sorted( USER_QUERY_HELPER.getComparator( orderBy, ascending ) ).skip( offset ).limit( limit )
+            .map( user -> getRestUser( user ) )
+            .collect( Collectors.toList( ) );
+        return new PagedResult<>( (int) size, offset, limit, users );
+    }
 }
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
index 43adc13..ea0c34c 100644
--- 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
@@ -27,6 +27,7 @@ import org.apache.archiva.redback.rest.api.model.v2.PagedResult;
 import org.apache.archiva.redback.rest.api.model.v2.Role;
 import org.apache.archiva.redback.rest.api.model.v2.RoleInfo;
 import org.apache.archiva.redback.rest.api.model.v2.RoleTemplate;
+import org.apache.archiva.redback.rest.api.model.v2.UserInfo;
 import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
 import org.apache.archiva.redback.rest.api.services.v2.RoleService;
 import org.apache.archiva.redback.role.PermanentRoleDeletionInvalid;
@@ -34,9 +35,8 @@ 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.ModelApplication;
-import org.apache.archiva.redback.role.model.ModelTemplate;
 import org.apache.archiva.redback.role.util.RoleModelUtils;
+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;
@@ -83,7 +83,7 @@ public class DefaultRoleService extends BaseRedbackService
     @Context
     private UriInfo uriInfo;
 
-    private static final String[] DEFAULT_SEARCH_FIELDS = {"name", "description"};
+    private static final String[] DEFAULT_SEARCH_FIELDS = {"id", "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;
@@ -92,6 +92,7 @@ public class DefaultRoleService extends BaseRedbackService
     {
 
         QUERY_HELPER = new QueryHelper<>( FILTER_MAP, ORDER_MAP, DEFAULT_SEARCH_FIELDS );
+        QUERY_HELPER.addStringFilter( "id", org.apache.archiva.redback.rbac.Role::getId );
         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 );
@@ -103,6 +104,7 @@ public class DefaultRoleService extends BaseRedbackService
         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( "description", org.apache.archiva.redback.rbac.Role::getDescription );
         QUERY_HELPER.addNullsafeFieldComparator( "template_instance", org.apache.archiva.redback.rbac.Role::isTemplateInstance );
     }
 
@@ -121,7 +123,7 @@ public class DefaultRoleService extends BaseRedbackService
     @Override
     public PagedResult<RoleInfo> getAllRoles( String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order ) throws RedbackServiceException
     {
-        boolean ascending = !"desc".equals( order );
+        boolean ascending = isAscending( order );
         try
         {
             // UserQuery does not work here, because the configurable user manager does only return the query for
@@ -389,7 +391,7 @@ public class DefaultRoleService extends BaseRedbackService
     }
 
     @Override
-    public RoleInfo unassignRole( String roleId, String userId )
+    public RoleInfo deleteRoleAssignment( String roleId, String userId )
         throws RedbackServiceException
     {
         try
@@ -415,8 +417,28 @@ public class DefaultRoleService extends BaseRedbackService
         }
         catch ( RbacObjectNotFoundException e )
         {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, e.getMessage( ) ), 404 );
+        }
+        catch ( RbacManagerException e )
+        {
             throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
         }
+    }
+
+    @Override
+    public PagedResult<UserInfo> getRoleUsers( String roleId, String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order )  throws RedbackServiceException
+    {
+        boolean ascending = isAscending( order );
+        try
+        {
+            org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRoleById( roleId );
+            List<User> rawUsers = getAssignedRedbackUsersRecursive( rbacRole );
+            return getUserInfoPagedResult( rawUsers, searchTerm, offset, limit, orderBy, ascending );
+        }
+        catch ( RbacObjectNotFoundException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, e.getMessage( ) ), 404 );
+        }
         catch ( RbacManagerException e )
         {
             throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
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 f32d595..b89a550 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
@@ -102,7 +102,6 @@ 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.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -118,33 +117,6 @@ public class DefaultUserService extends BaseRedbackService
     private static final String VALID_USERNAME_CHARS = "[a-zA-Z_0-9\\-.@]*";
     private static final String[] INVALID_CREATE_USER_NAMES = {"admin", "guest", "me"};
 
-    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
-    {
-        // 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
-        ORDER_MAP.put( "id", Comparator.comparing( org.apache.archiva.redback.users.User::getId, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "user_id", Comparator.comparing( org.apache.archiva.redback.users.User::getUsername, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "full_name", Comparator.comparing( org.apache.archiva.redback.users.User::getFullName, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "email", Comparator.comparing( org.apache.archiva.redback.users.User::getEmail, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "created", Comparator.comparing( org.apache.archiva.redback.users.User::getAccountCreationDate, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "last_login", Comparator.comparing( org.apache.archiva.redback.users.User::getLastLoginDate, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "validated", Comparator.comparing( org.apache.archiva.redback.users.User::isValidated, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "locked", Comparator.comparing( org.apache.archiva.redback.users.User::isLocked, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "password_change_required", Comparator.comparing( org.apache.archiva.redback.users.User::isPasswordChangeRequired, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-        ORDER_MAP.put( "last_password_change", Comparator.comparing( org.apache.archiva.redback.users.User::getLastPasswordChange, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
-
-        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 ) );
-
-        QUERY_HELPER = new QueryHelper<>( FILTER_MAP, ORDER_MAP, DEFAULT_SEARCH_FIELDS );
-    }
 
     private SecuritySystem securitySystem;
 
@@ -395,20 +367,13 @@ public class DefaultUserService extends BaseRedbackService
                                            Integer limit, List<String> orderBy, String order )
         throws RedbackServiceException
     {
-        boolean ascending = !"desc".equals( order );
+        boolean ascending = isAscending( 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 user list
             List<? extends org.apache.archiva.redback.users.User> rawUsers = userManager.getUsers( );
-            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( QUERY_HELPER.getComparator( orderBy, ascending ) ).skip( offset ).limit( limit )
-                .map( user -> getRestUser( user ) )
-                .collect( Collectors.toList( ) );
-            return new PagedResult<>( (int) size, offset, limit, users );
+            return getUserInfoPagedResult( rawUsers, q, offset, limit, orderBy, ascending );
         }
         catch ( UserManagerException e )
         {
@@ -416,6 +381,7 @@ public class DefaultUserService extends BaseRedbackService
         }
     }
 
+
     @Override
     public UserInfo updateMe( SelfUserData user )
         throws RedbackServiceException
@@ -573,15 +539,6 @@ public class DefaultUserService extends BaseRedbackService
         return new PingResult( true );
     }
 
-    private UserInfo getRestUser( org.apache.archiva.redback.users.User user )
-    {
-        if ( user == null )
-        {
-            return null;
-        }
-        return new UserInfo( user );
-    }
-
     @Override
     public UserInfo createAdminUser( User adminUser )
         throws RedbackServiceException
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
index 936a160..125b8f0 100644
--- 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
@@ -19,8 +19,14 @@ package org.apache.archiva.redback.rest.services.v2;
  */
 
 import io.restassured.response.Response;
+import io.restassured.response.ResponseBodyExtractionOptions;
+import org.apache.archiva.redback.rest.api.model.User;
+import org.apache.archiva.redback.rest.api.model.v2.BaseUserInfo;
+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.RoleInfo;
 import org.apache.archiva.redback.rest.api.model.v2.RoleTemplate;
+import org.apache.archiva.redback.rest.api.model.v2.UserInfo;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.DisplayName;
@@ -38,8 +44,8 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
-import static io.restassured.RestAssured.get;
 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;
@@ -142,26 +148,26 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
     void deleteTemplatedRole( )
     {
         String token = getAdminToken( );
-            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
-                .when( )
-                .put( "templates/archiva-repository-manager/repository05" )
-                .then( ).statusCode( 201 ).extract( ).response( );
-            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
-                .when( )
-                .delete( "templates/archiva-repository-manager/repository01" )
-                .then( ).statusCode( 404 );
-            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
-                .when( )
-                .delete( "templates/archiva-repository-manager/repository05" )
-                .then( ).statusCode( 200 );
-            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
-                .when( )
-                .delete( "templates/archiva-repository-manager/repository05" )
-                .then( ).statusCode( 404 );
-            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
-                .when( )
-                .delete( "templates/archiva-repository-observer/repository05" )
-                .then( ).statusCode( 200 );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .put( "templates/archiva-repository-manager/repository05" )
+            .then( ).statusCode( 201 ).extract( ).response( );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .delete( "templates/archiva-repository-manager/repository01" )
+            .then( ).statusCode( 404 );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .delete( "templates/archiva-repository-manager/repository05" )
+            .then( ).statusCode( 200 );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .delete( "templates/archiva-repository-manager/repository05" )
+            .then( ).statusCode( 404 );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .delete( "templates/archiva-repository-observer/repository05" )
+            .then( ).statusCode( 200 );
 
     }
 
@@ -343,12 +349,20 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
     {
         String token = getAdminToken( );
         Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
-            .when( ).get( "archiva-system-administrator" ).then( ).statusCode( 200 ).extract( ).response( );
+            .when( ).get( "archiva-system-administrator" ).prettyPeek( ).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( ) );
+        List<Permission> perms = roleInfo.getPermissions( );
+        assertNotNull( perms );
+        assertTrue( perms.size( ) > 0 );
+        assertTrue( perms.stream( ).filter( perm -> "archiva-manage-configuration".equals( perm.getName( ) ) ).findAny( ).isPresent( ) );
+        List<String> childs = roleInfo.getChildRoleIds( );
+        assertNotNull( childs );
+        assertTrue( childs.size( ) > 0 );
+        assertTrue( childs.stream( ).filter( id -> "archiva-global-repository-manager".equals( id ) ).findAny( ).isPresent( ) );
     }
 
     @Test
@@ -463,6 +477,47 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
 
     }
 
+    @Test
+    void getAssignedUsers( )
+    {
+        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 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "system-administrator/user/aragorn" )
+                .then( ).statusCode( 200 );
+            Response result = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .get( "system-administrator/user" )
+                .prettyPeek()
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull(result);
+            PagedResult<UserInfo> userResult = result.getBody( ).jsonPath( ).getObject( "", PagedResult.class );
+            assertNotNull( userResult );
+            assertEquals( 2, userResult.getPagination( ).getTotalCount( ) );
+            List<UserInfo> users = result.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
+            assertArrayEquals( new String[] {"admin","aragorn"}, users.stream( ).map( BaseUserInfo::getUserId ).sorted().toArray(String[]::new) );
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
+                .when( )
+                .delete( "aragorn" ).then( ).statusCode( 200 );
+        }
+
+    }
 
     @Test
     void assignRole( )
@@ -503,7 +558,7 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
                 .when( )
-                .delete( "aragorn" ).then().statusCode( 200 );
+                .delete( "aragorn" ).then( ).statusCode( 200 );
         }
     }
 
@@ -546,7 +601,7 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
                 .when( )
-                .delete( "aragorn" ).then().statusCode( 200 );
+                .delete( "aragorn" ).then( ).statusCode( 200 );
         }
     }
 
@@ -605,13 +660,13 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
                 .when( )
-                .delete( "aragorn" ).then().statusCode( 200 );
+                .delete( "aragorn" ).then( ).statusCode( 200 );
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .delete( "templates/archiva-repository-manager/repository11" ).then().statusCode( 200 );
+                .delete( "templates/archiva-repository-manager/repository11" ).then( ).statusCode( 200 );
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .delete( "templates/archiva-repository-observer/repository11" ).then().statusCode( 200 );
+                .delete( "templates/archiva-repository-observer/repository11" ).then( ).statusCode( 200 );
 
         }
     }
@@ -659,7 +714,7 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
                 .when( )
-                .delete( "aragorn" ).then().statusCode( 200 );
+                .delete( "aragorn" ).then( ).statusCode( 200 );
         }
     }
 
@@ -709,13 +764,13 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
                 .when( )
-                .delete( "aragorn" ).then().statusCode( 200 );
+                .delete( "aragorn" ).then( ).statusCode( 200 );
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .delete( "templates/archiva-repository-manager/repository12" ).then().statusCode( 200 );
+                .delete( "templates/archiva-repository-manager/repository12" ).then( ).statusCode( 200 );
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .delete( "templates/archiva-repository-observer/repository12" ).then().statusCode( 200 );
+                .delete( "templates/archiva-repository-observer/repository12" ).then( ).statusCode( 200 );
 
         }
     }
@@ -748,7 +803,13 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
             assertEquals( "This description was updated.", updatedRole.getDescription( ) );
             assertEquals( true, updatedRole.isAssignable( ) );
             assertEquals( false, updatedRole.isPermanent( ) );
-            assertArrayEquals( roleInfo.getAssignedUsers( ).toArray( ), updatedRole.getAssignedUsers( ).toArray( ) );
+            response  = given().spec(getRequestSpec(token)).contentType( JSON )
+                .when()
+                .get("archiva-repository-manager.repository13/user")
+                .then()
+                .extract( ).response( );
+            List<UserInfo> userList = response.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
+            assertEquals( 1, userList.size( ) );
         }
         finally
         {
@@ -812,8 +873,15 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
             assertEquals( "New description", updatedRole.getDescription( ) );
             assertEquals( false, updatedRole.isAssignable( ) );
             assertEquals( true, updatedRole.isPermanent( ) );
-            assertEquals( 2, updatedRole.getAssignedUsers( ).size() );
-            assertTrue( updatedRole.getAssignedUsers( ).stream( ).filter( user -> "aragorn".equals( user.getUserId( ) ) ).findAny().isPresent() );
+
+            response  = given().spec(getRequestSpec(token)).contentType( JSON )
+                .when()
+                .get("archiva-repository-manager.repository14/user")
+                .then()
+                .extract( ).response( );
+            List<UserInfo> userList = response.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
+            assertEquals( 2, userList.size( ) );
+            assertTrue( userList.stream( ).filter( user -> "aragorn".equals( user.getUserId( ) ) ).findAny( ).isPresent( ) );
         }
         finally
         {
@@ -829,7 +897,7 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
 
             given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
                 .when( )
-                .delete( "aragorn" ).then().statusCode( 200 );
+                .delete( "aragorn" ).then( ).statusCode( 200 );
 
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
@@ -937,20 +1005,21 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
     void updateRoleNotExist( )
     {
         String token = getAdminToken( );
-            Map<String, Object> jsonAsMap = new HashMap<>( );
-            jsonAsMap.put( "id", "abcdefg" );
-            jsonAsMap.put( "name", "abcdefg" );
-            jsonAsMap.put( "description", "This description was updated." );
-            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
-                .when( )
-                .body( jsonAsMap )
-                .patch( "abcdefg" )
-                .then( ).statusCode( 404 );
+        Map<String, Object> jsonAsMap = new HashMap<>( );
+        jsonAsMap.put( "id", "abcdefg" );
+        jsonAsMap.put( "name", "abcdefg" );
+        jsonAsMap.put( "description", "This description was updated." );
+        given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+            .when( )
+            .body( jsonAsMap )
+            .patch( "abcdefg" )
+            .then( ).statusCode( 404 );
     }
 
 
     @Test
-    void getTemplates() {
+    void getTemplates( )
+    {
         String token = getAdminToken( );
         Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
             .when( )
@@ -959,8 +1028,8 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
         assertNotNull( response );
         List<RoleTemplate> templates = response.getBody( ).jsonPath( ).getList( "", RoleTemplate.class );
         assertEquals( 2, templates.size( ) );
-        assertTrue( templates.stream( ).filter( tmpl -> "archiva-repository-manager".equals( tmpl.getId( ) ) ).findAny().isPresent() );
-        assertTrue( templates.stream( ).filter( tmpl -> "archiva-repository-observer".equals( tmpl.getId( ) ) ).findAny().isPresent() );
+        assertTrue( templates.stream( ).filter( tmpl -> "archiva-repository-manager".equals( tmpl.getId( ) ) ).findAny( ).isPresent( ) );
+        assertTrue( templates.stream( ).filter( tmpl -> "archiva-repository-observer".equals( tmpl.getId( ) ) ).findAny( ).isPresent( ) );
     }
 
 }


[archiva-redback-core] 02/03: Improving role user assignment search

Posted by ma...@apache.org.
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 74581c7aa5de0ee1d95f47310a6e3639af3e7446
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Mon Dec 21 21:38:01 2020 +0100

    Improving role user assignment search
---
 idea.run.configuration/All Rest Services.run.xml   |  2 +-
 .../V2 AuthenticationServiceTest.run.xml           |  2 +-
 ...st.services.v2 in redback-rest-services.run.xml |  2 +-
 .../org/apache/archiva/redback/rest/api/Util.java  | 59 +++++++++++++
 .../redback/rest/api/services/v2/RoleService.java  | 30 ++++++-
 .../rest/services/v2/BaseRedbackService.java       | 18 +++-
 .../rest/services/v2/DefaultRoleService.java       |  8 +-
 .../rest/services/v2/NativeRoleServiceTest.java    | 96 ++++++++++++++++++++--
 8 files changed, 203 insertions(+), 14 deletions(-)

diff --git a/idea.run.configuration/All Rest Services.run.xml b/idea.run.configuration/All Rest Services.run.xml
index 07152e8..010200a 100644
--- a/idea.run.configuration/All Rest Services.run.xml	
+++ b/idea.run.configuration/All Rest Services.run.xml	
@@ -4,7 +4,7 @@
     <useClassPathOnly />
     <extension name="coverage">
       <pattern>
-        <option name="PATTERN" value="org.apache.archiva.redback.rest.services.v2.*" />
+        <option name="PATTERN" value="org.apache.archiva.redback.rest.api.services.v2.*" />
         <option name="ENABLED" value="true" />
       </pattern>
     </extension>
diff --git a/idea.run.configuration/V2 AuthenticationServiceTest.run.xml b/idea.run.configuration/V2 AuthenticationServiceTest.run.xml
index 2e822b7..116e14c 100644
--- a/idea.run.configuration/V2 AuthenticationServiceTest.run.xml	
+++ b/idea.run.configuration/V2 AuthenticationServiceTest.run.xml	
@@ -4,7 +4,7 @@
     <useClassPathOnly />
     <extension name="coverage">
       <pattern>
-        <option name="PATTERN" value="org.apache.archiva.redback.rest.services.v2.*" />
+        <option name="PATTERN" value="org.apache.archiva.redback.rest.api.services.v2.*" />
         <option name="ENABLED" value="true" />
       </pattern>
     </extension>
diff --git a/idea.run.configuration/org.apache.archiva.redback.rest.services.v2 in redback-rest-services.run.xml b/idea.run.configuration/org.apache.archiva.redback.rest.services.v2 in redback-rest-services.run.xml
index f4c9c8a..f4b1844 100644
--- a/idea.run.configuration/org.apache.archiva.redback.rest.services.v2 in redback-rest-services.run.xml	
+++ b/idea.run.configuration/org.apache.archiva.redback.rest.services.v2 in redback-rest-services.run.xml	
@@ -4,7 +4,7 @@
     <useClassPathOnly />
     <extension name="coverage">
       <pattern>
-        <option name="PATTERN" value="org.apache.archiva.redback.rest.services.v2.*" />
+        <option name="PATTERN" value="org.apache.archiva.redback.rest.api.services.v2.*" />
         <option name="ENABLED" value="true" />
       </pattern>
     </extension>
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Util.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Util.java
new file mode 100644
index 0000000..db4d4f9
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Util.java
@@ -0,0 +1,59 @@
+package org.apache.archiva.redback.rest.api;/*
+ * 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 javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * Central utility class that may be used by service implementations.
+ *
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+public class Util
+{
+    /**
+     * Returns <code>false</code>, if the given parameter is not present in the given uriInfo, or is present and set to 'false' or '0'.
+     * In all other cases it returns <code>true</code>.
+     *
+     * This means you can activate a flag by setting '?param', '?param=true', '?param=1', ...
+     * It is deactivated, if the parameter is absent, or '?param=false', or '?param=0'
+     *
+     * @param uriInfo the uriInfo context instance, that is used to check for the parameter
+     * @param queryParameterName the query parameter name
+     * @return
+     */
+    public static boolean isFlagSet( final UriInfo uriInfo, final String queryParameterName) {
+        MultivaluedMap<String, String> params = uriInfo.getQueryParameters( );
+        if (!params.containsKey( queryParameterName )) {
+            return false;
+        }
+        // parameter is available
+        String value = params.getFirst( queryParameterName );
+        // if its available but without a value it is flagged as present
+        if (StringUtils.isEmpty( value )) {
+            return true;
+        }
+        // if it has a value, we check for false values:
+        if ("false".equalsIgnoreCase( value ) || "0".equalsIgnoreCase( value )) {
+            return false;
+        }
+        return true;
+    }
+}
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 3aec8ba..ca7f688 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
@@ -362,6 +362,26 @@ public interface RoleService
     RoleInfo deleteRoleAssignment( @PathParam( "roleId" ) String roleId, @PathParam( "userId" ) String userId )
         throws RedbackServiceException;
 
+    /**
+     * This returns the list of assigned users to a given role. The flag "recurse" is a query parameter.
+     * If the query parameter exists and is not set to 'false', or '0', it will recurse all parent roles and return a list of users
+     * assigned to the current role and the parent roles up to the root.
+     * If the query parameter does not exist or is set to 'false' or '0', it will return only the users assigned directly
+     * to the given role.
+     *
+     * @param roleId the role identifier, for which the assigned users are returned
+     * @param recurse if the parameter does not exist or is set to 'false' or '0', only directly assigned users are returned.
+     *                If the parameter value is set to 'parentsOnly', the users assigned to all parent roles up to the root excluding the
+     *                given role are returned.
+     *                Otherwise all users assigned to the given role and all parent roles up to the root are returned.
+     * @param searchTerm the substring query term to search for in the user ids and names
+     * @param offset the offset index in the user list for paging
+     * @param limit the maximum number of users returned
+     * @param orderBy the order attributes for ordering
+     * @param order the order direction 'asc' (ascending), or 'desc' (descending)
+     * @return the list of user objects
+     * @throws RedbackServiceException
+     */
     @Path("{roleId}/user")
     @GET
     @Produces({APPLICATION_JSON})
@@ -372,7 +392,11 @@ public interface RoleService
             @Parameter(name = "offset", description = "The offset of the first element returned"),
             @Parameter(name = "limit", description = "Maximum number of items to return in the response"),
             @Parameter(name = "orderBy", description = "List of attribute used for sorting (user_id, fullName, email, created"),
-            @Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)")
+            @Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)"),
+            @Parameter(name = "recurse", description = "If not present, or set to 'false' or '0', only users assigned directly to this role are returned."+
+            " If present and set to 'parentsOnly', the list of users assigned to all parents of the given role up to the root."+
+                " If present and set to any other value than 'parentsOnly', 'false' or '0', the users assigned to this role or any parent role in the hierarchy"+
+                " up to the root are returned.")
         },
         security = {
             @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
@@ -388,11 +412,13 @@ public interface RoleService
         }
     )
     PagedResult<UserInfo> getRoleUsers(@PathParam( "roleId" ) String roleId,
+                                       @QueryParam("recurse") String recurse,
                                        @QueryParam("q") @DefaultValue( "" ) String searchTerm,
                                        @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
                                        @QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
                                        @QueryParam( "orderBy") @DefaultValue( "id" ) List<String> orderBy,
-                                       @QueryParam("order") @DefaultValue( "asc" ) String order) throws RedbackServiceException;
+                                       @QueryParam("order") @DefaultValue( "asc" ) String order
+                                       ) throws RedbackServiceException;
 
     /**
      * Updates a role. Attributes that are empty or null will be ignored.
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
index 48de1bb..a2b693d 100644
--- 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
@@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory;
 
 import javax.inject.Named;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -135,11 +136,24 @@ public class BaseRedbackService
         }
     }
 
-    protected List<User> getAssignedRedbackUsersRecursive( org.apache.archiva.redback.rbac.Role rbacRole ) throws RbacManagerException
+    protected List<User> getAssignedRedbackUsers( Role rbacRole ) {
+        try
+        {
+            return rbacManager.getUserAssignmentsForRoles( Arrays.asList( rbacRole.getId( ) ) ).stream( ).map(
+                assignment -> getRedbackUser( assignment.getPrincipal( ) )
+            ).collect( Collectors.toList( ) );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+    protected List<User> getAssignedRedbackUsersRecursive( final Role rbacRole, final boolean parentsOnly ) throws RbacManagerException
     {
         try
         {
-            return rbacManager.getUserAssignmentsForRoles( recurseRoles( rbacRole ).map( role -> role.getId( ) ).collect( Collectors.toList( ) ) )
+            return rbacManager.getUserAssignmentsForRoles( recurseRoles( rbacRole ).map( role -> role.getId( ) ).filter(roleId -> ((!parentsOnly) || ( !rbacRole.getId().equals(roleId)))).collect( Collectors.toList( ) ) )
                 .stream( ).map( assignment -> getRedbackUser( assignment.getPrincipal( ) ) ).collect( Collectors.toList( ) );
         }
         catch ( RuntimeException e )
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
index ea0c34c..b8d78a5 100644
--- 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
@@ -22,6 +22,7 @@ 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.rest.api.MessageKeys;
+import org.apache.archiva.redback.rest.api.Util;
 import org.apache.archiva.redback.rest.api.model.ErrorMessage;
 import org.apache.archiva.redback.rest.api.model.v2.PagedResult;
 import org.apache.archiva.redback.rest.api.model.v2.Role;
@@ -426,13 +427,16 @@ public class DefaultRoleService extends BaseRedbackService
     }
 
     @Override
-    public PagedResult<UserInfo> getRoleUsers( String roleId, String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order )  throws RedbackServiceException
+    public PagedResult<UserInfo> getRoleUsers( String roleId, String recurse,
+                                               String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order )  throws RedbackServiceException
     {
         boolean ascending = isAscending( order );
+        boolean recursePresent = Util.isFlagSet( uriInfo, "recurse" );
+        boolean parentsOnly = "parentsOnly".equals( recurse );
         try
         {
             org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRoleById( roleId );
-            List<User> rawUsers = getAssignedRedbackUsersRecursive( rbacRole );
+            List<User> rawUsers = recursePresent ? getAssignedRedbackUsersRecursive( rbacRole, parentsOnly ) : getAssignedRedbackUsers( rbacRole );
             return getUserInfoPagedResult( rawUsers, searchTerm, offset, limit, orderBy, ascending );
         }
         catch ( RbacObjectNotFoundException e )
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
index 125b8f0..48028c8 100644
--- 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
@@ -478,7 +478,7 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
     }
 
     @Test
-    void getAssignedUsers( )
+    void getAssignedUsersNonRecursive( )
     {
         String token = getAdminToken( );
         Map<String, Object> jsonAsMap = new HashMap<>( );
@@ -496,11 +496,54 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
                 .then( ).statusCode( 201 );
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .put( "system-administrator/user/aragorn" )
+                .put( "archiva-global-repository-observer/user/aragorn" )
                 .then( ).statusCode( 200 );
             Response result = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .get( "system-administrator/user" )
+                .get( "archiva-global-repository-observer/user" )
+                .prettyPeek()
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull(result);
+            PagedResult<UserInfo> userResult = result.getBody( ).jsonPath( ).getObject( "", PagedResult.class );
+            assertNotNull( userResult );
+            assertEquals( 1, userResult.getPagination( ).getTotalCount( ) );
+            List<UserInfo> users = result.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
+            assertArrayEquals( new String[] {"aragorn"}, users.stream( ).map( BaseUserInfo::getUserId ).sorted().toArray(String[]::new) );
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
+                .when( )
+                .delete( "aragorn" ).then( ).statusCode( 200 );
+        }
+
+    }
+
+    @Test
+    void getAssignedUsersRecursive( )
+    {
+        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 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "archiva-global-repository-observer/user/aragorn" )
+                .then( ).statusCode( 200 );
+            Response result = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .param( "recurse")
+                .get( "archiva-global-repository-observer/user" )
                 .prettyPeek()
                 .then( ).statusCode( 200 ).extract( ).response( );
             assertNotNull(result);
@@ -520,6 +563,49 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
     }
 
     @Test
+    void getAssignedUsersRecursiveParentsOnly( )
+    {
+        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 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "archiva-global-repository-observer/user/aragorn" )
+                .then( ).statusCode( 200 );
+            Response result = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .param( "recurse","parentsOnly")
+                .get( "archiva-global-repository-observer/user" )
+                .prettyPeek()
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull(result);
+            PagedResult<UserInfo> userResult = result.getBody( ).jsonPath( ).getObject( "", PagedResult.class );
+            assertNotNull( userResult );
+            assertEquals( 1, userResult.getPagination( ).getTotalCount( ) );
+            List<UserInfo> users = result.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
+            assertArrayEquals( new String[] {"admin"}, users.stream( ).map( BaseUserInfo::getUserId ).sorted().toArray(String[]::new) );
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
+                .when( )
+                .delete( "aragorn" ).then( ).statusCode( 200 );
+        }
+
+    }
+
+    @Test
     void assignRole( )
     {
         String token = getAdminToken( );
@@ -809,7 +895,7 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
                 .then()
                 .extract( ).response( );
             List<UserInfo> userList = response.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
-            assertEquals( 1, userList.size( ) );
+            assertEquals( 0, userList.size( ) );
         }
         finally
         {
@@ -880,7 +966,7 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
                 .then()
                 .extract( ).response( );
             List<UserInfo> userList = response.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
-            assertEquals( 2, userList.size( ) );
+            assertEquals( 1, userList.size( ) );
             assertTrue( userList.stream( ).filter( user -> "aragorn".equals( user.getUserId( ) ) ).findAny( ).isPresent( ) );
         }
         finally


[archiva-redback-core] 03/03: Adding role method

Posted by ma...@apache.org.
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 7de377dcb7ebf99a7853bc446f3f8186e6650747
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Wed Dec 23 00:06:54 2020 +0100

    Adding role method
---
 .../redback/rest/api/services/v2/RoleService.java  | 40 ++++++++++
 .../rest/services/v2/BaseRedbackService.java       |  8 +-
 .../rest/services/v2/DefaultRoleService.java       | 29 +++++++
 .../rest/services/v2/NativeRoleServiceTest.java    | 88 +++++++++++++++++++++-
 4 files changed, 163 insertions(+), 2 deletions(-)

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 ca7f688..86e20b9 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
@@ -420,6 +420,46 @@ public interface RoleService
                                        @QueryParam("order") @DefaultValue( "asc" ) String order
                                        ) throws RedbackServiceException;
 
+
+    @Path("{roleId}/unassigned")
+    @GET
+    @Produces({APPLICATION_JSON})
+    @RedbackAuthorization(permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION)
+    @Operation( summary = "Returns the users not assigned to the given role",
+        parameters = {
+            @Parameter(name = "q", description = "Search term"),
+            @Parameter(name = "offset", description = "The offset of the first element returned"),
+            @Parameter(name = "limit", description = "Maximum number of items to return in the response"),
+            @Parameter(name = "orderBy", description = "List of attribute used for sorting (user_id, fullName, email, created"),
+            @Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)"),
+            @Parameter(name = "recurse", description = "If not present, or set to 'false' or '0', only users assigned directly to this role are returned."+
+                " If present and set to 'parentsOnly', the list of users assigned to all parents of the given role up to the root."+
+                " If present and set to any other value than 'parentsOnly', 'false' or '0', the users assigned to this role or any parent role in the hierarchy"+
+                " up to the root are returned.")
+        },
+        security = {
+            @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+        },
+        responses = {
+            @ApiResponse( responseCode = "200",
+                description = "If the users could be retrieved"
+            ),
+            @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 )) )
+        }
+    )
+    PagedResult<UserInfo> getUnassignedUsers(@PathParam( "roleId" ) String roleId,
+                                       @QueryParam("recurse") String recurse,
+                                       @QueryParam("q") @DefaultValue( "" ) String searchTerm,
+                                       @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
+                                       @QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
+                                       @QueryParam( "orderBy") @DefaultValue( "id" ) List<String> orderBy,
+                                       @QueryParam("order") @DefaultValue( "asc" ) String order
+    ) throws RedbackServiceException;
+
+
     /**
      * Updates a role. Attributes that are empty or null will be ignored.
      *
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
index a2b693d..01fd95e 100644
--- 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
@@ -38,6 +38,7 @@ import org.slf4j.LoggerFactory;
 import javax.inject.Named;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -153,7 +154,11 @@ public class BaseRedbackService
     {
         try
         {
-            return rbacManager.getUserAssignmentsForRoles( recurseRoles( rbacRole ).map( role -> role.getId( ) ).filter(roleId -> ((!parentsOnly) || ( !rbacRole.getId().equals(roleId)))).collect( Collectors.toList( ) ) )
+            List<String> roles = recurseRoles( rbacRole ).map( role -> role.getId( ) ).filter( roleId -> ( ( !parentsOnly ) || ( !rbacRole.getId( ).equals( roleId ) ) ) ).collect( Collectors.toList( ) );
+            if (roles.size()==0) {
+                return Collections.emptyList( );
+            }
+            return rbacManager.getUserAssignmentsForRoles( roles )
                 .stream( ).map( assignment -> getRedbackUser( assignment.getPrincipal( ) ) ).collect( Collectors.toList( ) );
         }
         catch ( RuntimeException e )
@@ -235,6 +240,7 @@ public class BaseRedbackService
     {
         Predicate<User> filter = USER_QUERY_HELPER.getQueryFilter( q );
         long size = rawUsers.stream( ).filter( filter ).count( );
+        System.out.println( "Total " + size );
         List<UserInfo> users = rawUsers.stream( )
             .filter( filter )
             .sorted( USER_QUERY_HELPER.getComparator( orderBy, ascending ) ).skip( offset ).limit( limit )
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
index b8d78a5..d70f556 100644
--- 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
@@ -58,6 +58,7 @@ import java.util.HashMap;
 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;
@@ -450,6 +451,34 @@ public class DefaultRoleService extends BaseRedbackService
     }
 
     @Override
+    public PagedResult<UserInfo> getUnassignedUsers( String roleId, String recurse, String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order ) throws RedbackServiceException
+    {
+        boolean ascending = isAscending( order );
+        boolean recursePresent = Util.isFlagSet( uriInfo, "recurse" );
+        boolean parentsOnly = "parentsOnly".equals( recurse );
+        try
+        {
+            org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRoleById( roleId );
+            final Set<String> assignedUsers = (recursePresent ? getAssignedRedbackUsersRecursive( rbacRole, parentsOnly ) : getAssignedRedbackUsers( rbacRole ))
+                .stream( ).map( user -> user.getId()  ).collect( Collectors.toSet());
+            List<? extends User> rawUsers = userManager.getUsers( ascending ).stream( ).filter( user -> !assignedUsers.contains( user.getId( ) ) ).collect( Collectors.toList( ) );
+            return getUserInfoPagedResult( rawUsers, searchTerm, offset, limit, orderBy, ascending );
+        }
+        catch ( RbacObjectNotFoundException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, e.getMessage( ) ), 404 );
+        }
+        catch ( RbacManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) );
+        }
+        catch ( UserManagerException e )
+        {
+            throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USERMANAGER_FAIL, e.getMessage( ) ) );
+        }
+    }
+
+    @Override
     public RoleInfo updateRole( String roleId, Role role ) throws RedbackServiceException
     {
         try
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
index 48028c8..0b6c33c 100644
--- 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
@@ -501,7 +501,6 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
             Response result = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
                 .get( "archiva-global-repository-observer/user" )
-                .prettyPeek()
                 .then( ).statusCode( 200 ).extract( ).response( );
             assertNotNull(result);
             PagedResult<UserInfo> userResult = result.getBody( ).jsonPath( ).getObject( "", PagedResult.class );
@@ -520,6 +519,49 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
     }
 
     @Test
+    void getUnAssignedUsersNonRecursive( )
+    {
+        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 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "archiva-global-repository-observer/user/aragorn" )
+                .then( ).statusCode( 200 );
+            Response result = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .get( "archiva-global-repository-observer/unassigned" )
+                .prettyPeek()
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull(result);
+            PagedResult<UserInfo> userResult = result.getBody( ).jsonPath( ).getObject( "", PagedResult.class );
+            assertNotNull( userResult );
+            assertEquals( 2, userResult.getPagination( ).getTotalCount( ) );
+            List<UserInfo> users = result.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
+            assertFalse( users.stream( ).filter(user -> "aragorn".equals(user.getUserId())).findAny().isPresent() );
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
+                .when( )
+                .delete( "aragorn" ).then( ).statusCode( 200 );
+        }
+
+    }
+
+
+    @Test
     void getAssignedUsersRecursive( )
     {
         String token = getAdminToken( );
@@ -563,6 +605,50 @@ public class NativeRoleServiceTest extends AbstractNativeRestServices
     }
 
     @Test
+    void getUnAssignedUsersRecursive( )
+    {
+        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 );
+            given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .put( "archiva-global-repository-observer/user/aragorn" )
+                .then( ).statusCode( 200 );
+            Response result = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+                .when( )
+                .param( "recurse" )
+                .get( "archiva-global-repository-observer/unassigned" )
+                .prettyPeek()
+                .then( ).statusCode( 200 ).extract( ).response( );
+            assertNotNull(result);
+            PagedResult<UserInfo> userResult = result.getBody( ).jsonPath( ).getObject( "", PagedResult.class );
+            assertNotNull( userResult );
+            assertEquals( 1, userResult.getPagination( ).getTotalCount( ) );
+            List<UserInfo> users = result.getBody( ).jsonPath( ).getList( "data", UserInfo.class );
+            assertTrue( "guest".equals( users.get( 0 ).getUserId( ) ) );
+        }
+        finally
+        {
+            given( ).spec( getRequestSpec( token, getUserServicePath( ) ) ).contentType( JSON )
+                .when( )
+                .delete( "aragorn" ).then( ).statusCode( 200 );
+        }
+
+    }
+
+
+    @Test
     void getAssignedUsersRecursiveParentsOnly( )
     {
         String token = getAdminToken( );