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 <[email protected]> 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 <[email protected]> + */ +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", "[email protected]" ); + 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", "[email protected]" ); + 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
