Improving role user assignment search
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 <martin_s@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 @@
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 @@
@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 @@
}
)
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 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 @@
}
}
- 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.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 @@
}
@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 @@
}
@Test
- void getAssignedUsers( )
+ void getAssignedUsersNonRecursive( )
{
String token = getAdminToken( );
Map<String, Object> jsonAsMap = new HashMap<>( );
@@ -496,11 +496,54 @@
.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 @@
}
@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 @@
.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 @@
.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