Making ldapconnection autocloseable
diff --git a/redback-authentication/redback-authentication-providers/redback-authentication-ldap/src/main/java/org/apache/archiva/redback/authentication/ldap/LdapBindAuthenticator.java b/redback-authentication/redback-authentication-providers/redback-authentication-ldap/src/main/java/org/apache/archiva/redback/authentication/ldap/LdapBindAuthenticator.java
index 3c46c78..53cb43f 100644
--- a/redback-authentication/redback-authentication-providers/redback-authentication-ldap/src/main/java/org/apache/archiva/redback/authentication/ldap/LdapBindAuthenticator.java
+++ b/redback-authentication/redback-authentication-providers/redback-authentication-ldap/src/main/java/org/apache/archiva/redback/authentication/ldap/LdapBindAuthenticator.java
@@ -191,7 +191,14 @@
{
if ( ldapConnection != null )
{
- ldapConnection.close();
+ try
+ {
+ ldapConnection.close();
+ }
+ catch ( NamingException e )
+ {
+ log.error( "Could not close connection: {}", e.getMessage( ), e );
+ }
}
}
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/DefaultLdapConnection.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/DefaultLdapConnection.java
index 1a6c555..7583a46 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/DefaultLdapConnection.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/DefaultLdapConnection.java
@@ -27,10 +27,12 @@
import javax.naming.directory.DirContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
+import java.io.IOException;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.naming.spi.NamingManager;
/**
@@ -51,6 +53,8 @@
private List<Rdn> baseDnRdns;
+ private AtomicBoolean open = new AtomicBoolean( false );
+
public DefaultLdapConnection( LdapConnectionConfiguration config, Rdn subRdn )
throws LdapException
{
@@ -79,6 +83,7 @@
try
{
context = (DirContext) NamingManager.getInitialContext( e );
+ this.open.set( true );
}
catch ( NamingException ex )
{
@@ -108,6 +113,7 @@
try
{
context = (DirContext) NamingManager.getInitialContext( e );
+ this.open.set( true );
}
catch ( NamingException ex )
{
@@ -225,22 +231,28 @@
}
@Override
- public void close()
+ public void close() throws NamingException
{
- try
+ if (this.open.compareAndSet( true, false ))
{
- if ( context != null )
+ try
{
- context.close();
+ if ( context != null )
+ {
+ context.close( );
+ }
}
- }
- catch ( NamingException ex )
- {
- log.info( "skip error closing ldap connection {}", ex.getMessage() );
- }
- finally
- {
- context = null;
+ catch ( NamingException ex )
+ {
+ log.info( "skip error closing ldap connection {}", ex.getMessage( ) );
+ throw ex;
+ }
+ finally
+ {
+ this.context = null;
+ }
+ } else {
+ log.warn( "Connection already closed "+this.baseDnRdns );
}
}
@@ -263,6 +275,11 @@
@Override
public DirContext getDirContext()
{
- return context;
+ if (this.open.get())
+ {
+ return context;
+ } else {
+ throw new RuntimeException( "Connection closed " + this.getBaseDnRdns( ) );
+ }
}
}
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/LdapConnection.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/LdapConnection.java
index 2b7243c..e4f6b7a 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/LdapConnection.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/connection/LdapConnection.java
@@ -19,6 +19,7 @@
* under the License.
*/
+import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.Rdn;
import java.util.Hashtable;
@@ -27,12 +28,13 @@
/**
* @author Olivier Lamy
*/
-public interface LdapConnection
+public interface LdapConnection extends AutoCloseable
{
Hashtable<Object, Object> getEnvironment()
throws LdapException;
- void close();
+ @Override
+ void close() throws NamingException;
LdapConnectionConfiguration getConfiguration();
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java
index 1797150..1f668fc 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java
@@ -49,6 +49,8 @@
import javax.ws.rs.core.UriInfo;
import java.util.List;
+import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT;
+
/**
* @author Olivier Lamy
* @author Martin Stockhammer
@@ -66,12 +68,23 @@
@Produces( {MediaType.APPLICATION_JSON} )
@RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
@Operation( summary = "Get list of group objects",
+ 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 (id, name, description, assignable)"),
+ @Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)")
+ },
+
responses = {
@ApiResponse( description = "List of group objects. The number of returned results depend on the pagination parameters offset and limit." )
}
)
- PagedResult<Group> getGroups( @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
- @QueryParam( "limit" ) @DefaultValue( value = Constants.DEFAULT_PAGE_LIMIT ) Integer limit)
+ PagedResult<Group> getGroups( @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;
@@ -109,6 +122,20 @@
throws RedbackServiceException;
@Path( "mappings/{group}" )
+ @GET
+ @Produces( {MediaType.APPLICATION_JSON} )
+ @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+ @Operation( summary = "Returns the list of roles of a given group mapping",
+ responses = {
+ @ApiResponse( responseCode = "200", description = "If the list could be returned" ),
+ @ApiResponse( responseCode = "404", description = "Group mapping not found" )
+ }
+ )
+ List<String> getGroupMapping( @Parameter( description = "The group name", required = true )
+ @PathParam( "group" ) String group )
+ throws RedbackServiceException;
+
+ @Path( "mappings/{group}" )
@DELETE
@Consumes( {MediaType.APPLICATION_JSON} )
@Produces( {MediaType.APPLICATION_JSON} )
@@ -140,5 +167,20 @@
List<String> roles )
throws RedbackServiceException;
-
+ @Path( "mappings/{group}/roles/{roleId}" )
+ @PUT
+ @Consumes( {MediaType.APPLICATION_JSON} )
+ @Produces( {MediaType.APPLICATION_JSON} )
+ @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+ @Operation( summary = "Adds a role to a given group mapping.",
+ responses = {
+ @ApiResponse( responseCode = "200", description = "If the update was successful" ),
+ }
+ )
+ Response addRolesToGroupMapping( @Parameter( description = "The group name", required = true )
+ @PathParam( "group" ) String groupName,
+ @PathParam( "roleId" )
+ @Parameter( description = "The id of the role to add to the mapping", required = true )
+ String roleId )
+ throws RedbackServiceException;
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java
index 1d7a2f8..eb5ad02 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLdapGroupMappingService.java
@@ -172,7 +172,14 @@
{
if ( ldapConnection != null )
{
- ldapConnection.close();
+ try
+ {
+ ldapConnection.close();
+ }
+ catch ( NamingException e )
+ {
+ log.error( "Could not close connection: {}", e.getMessage( ), e );
+ }
}
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java
index a2f3ec1..80ccb5e 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java
@@ -19,6 +19,7 @@
*/
import org.apache.archiva.components.rest.model.PagedResult;
+import org.apache.archiva.components.rest.util.QueryHelper;
import org.apache.archiva.redback.common.ldap.MappingException;
import org.apache.archiva.redback.common.ldap.ObjectNotFoundException;
import org.apache.archiva.redback.common.ldap.connection.LdapConnection;
@@ -27,6 +28,7 @@
import org.apache.archiva.redback.common.ldap.role.LdapGroup;
import org.apache.archiva.redback.common.ldap.role.LdapRoleMapper;
import org.apache.archiva.redback.common.ldap.role.LdapRoleMapperConfiguration;
+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.Group;
@@ -47,9 +49,13 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.BiPredicate;
import java.util.stream.Collectors;
/**
@@ -65,7 +71,29 @@
public class DefaultGroupService
implements GroupService
{
- private final Logger log = LoggerFactory.getLogger( getClass() );
+ private static final Logger log = LoggerFactory.getLogger( DefaultGroupService.class );
+
+ private static final String[] DEFAULT_SEARCH_FIELDS = {"name", "uniqueName"};
+ private static final Map<String, BiPredicate<String, LdapGroup>> FILTER_MAP = new HashMap<>( );
+ private static final Map<String, Comparator<LdapGroup>> ORDER_MAP = new HashMap<>( );
+ private static final QueryHelper<LdapGroup> QUERY_HELPER;
+
+ static
+ {
+
+ QUERY_HELPER = new QueryHelper<>( FILTER_MAP, ORDER_MAP, DEFAULT_SEARCH_FIELDS );
+ QUERY_HELPER.addStringFilter( "name", LdapGroup::getName );
+ QUERY_HELPER.addStringFilter( "uniqueName", LdapGroup::getDn );
+ QUERY_HELPER.addStringFilter( "description", LdapGroup::getDescription );
+
+ // The simple Comparator.comparing(attribute) is not null safe
+ // As there are attributes that may have a null value, we have to use a comparator with nullsLast(naturalOrder)
+ // and the wrapping Comparator.nullsLast(Comparator.comparing(attribute)) does not work, because the attribute is not checked by the nullsLast-Comparator
+ QUERY_HELPER.addNullsafeFieldComparator( "name", LdapGroup::getName );
+ QUERY_HELPER.addNullsafeFieldComparator( "uniqueName", LdapGroup::getDn );
+ QUERY_HELPER.addNullsafeFieldComparator( "description", LdapGroup::getDescription );
+ }
+
@Context //injected response proxy supporting multiple threads
private HttpServletResponse response;
@@ -98,20 +126,15 @@
}
@Override
- public PagedResult<Group> getGroups( Integer offset, Integer limit ) throws RedbackServiceException
+ public PagedResult<Group> getGroups( String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order ) throws RedbackServiceException
{
- LdapConnection ldapConnection = null;
-
- DirContext context = null;
-
- try
+ try(LdapConnection ldapConnection = this.ldapConnectionFactory.getConnection())
{
- ldapConnection = ldapConnectionFactory.getConnection();
- context = ldapConnection.getDirContext();
+ DirContext context = ldapConnection.getDirContext();
List<LdapGroup> groups = ldapRoleMapper.getAllGroupObjects( context );
return PagedResult.of( groups.size( ), offset, limit, groups.stream( ).skip( offset ).limit( limit ).map( DefaultGroupService::getGroupFromLdap ).collect( Collectors.toList( ) ) );
}
- catch ( LdapException e )
+ catch ( NamingException e )
{
log.error( "LDAP Error {}", e.getMessage(), e );
throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_LDAP_GENERIC ) );
@@ -119,11 +142,6 @@
log.error( "Mapping Error {}", e.getMessage(), e );
throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_MAPPING, e.getMessage( ) ) );
}
- finally
- {
- closeContext( context );
- closeLdapConnection( ldapConnection );
- }
}
/**
@@ -242,6 +260,66 @@
}
}
+ @Override
+ public List<String> getGroupMapping( String groupName ) throws RedbackServiceException
+ {
+ Collection<String> mapping;
+ try
+ {
+ mapping = ldapRoleMapperConfiguration.getLdapGroupMapping( groupName );
+ return new ArrayList<>( mapping );
+ }
+ catch ( MappingException e )
+ {
+ throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_MAPPING_NOT_FOUND ), 404 );
+ }
+ }
+
+
+ @Override
+ public Response addRolesToGroupMapping( String groupName, String roleId ) throws RedbackServiceException
+ {
+ Collection<String> mapping;
+ try
+ {
+ mapping = ldapRoleMapperConfiguration.getLdapGroupMapping( groupName );
+ }
+ catch ( MappingException e )
+ {
+ try
+ {
+ ldapRoleMapperConfiguration.addLdapMapping( groupName, Arrays.asList( groupName) );
+ return Response.ok( ).build( );
+ }
+ catch ( MappingException mappingException )
+ {
+ log.error( "Could not update mapping {}", e.getMessage( ) );
+ throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_MAPPING, e.getMessage( ) ) );
+ }
+ }
+ if (mapping!=null)
+ {
+ try
+ {
+ ArrayList<String> newList = new ArrayList<>( mapping );
+ if (!newList.contains( roleId )) {
+ newList.add( roleId );
+ ldapRoleMapperConfiguration.updateLdapMapping( groupName,
+ newList );
+ }
+ return Response.ok( ).build( );
+ }
+ catch ( MappingException e )
+ {
+ log.error( "Could not update mapping {}", e.getMessage( ) );
+ throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_MAPPING, e.getMessage( ) ) );
+ }
+ } else {
+ throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_MAPPING_NOT_FOUND ), 404 );
+ }
+
+ }
+
//------------------
// utils
//------------------
@@ -250,7 +328,14 @@
{
if ( ldapConnection != null )
{
- ldapConnection.close();
+ try
+ {
+ ldapConnection.close();
+ }
+ catch ( NamingException e )
+ {
+ log.error( "Could not close LDAP connection {}", e.getMessage( ) );
+ }
}
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java
index 0966ecc..66399eb 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java
@@ -316,7 +316,7 @@
{
GroupService service = getGroupService( authorizationHeader );
- List<String> allGroups = service.getGroups( 0 , Integer.MAX_VALUE ).getData( ).stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) );
+ List<String> allGroups = service.getGroups( "", 0 , Integer.MAX_VALUE, Arrays.asList( "name" ), "asc" ).getData( ).stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) );
assertNotNull( allGroups );
assertEquals( 3, allGroups.size( ) );
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeGroupServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeGroupServiceTest.java
index db8dfc3..76c4fda 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeGroupServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeGroupServiceTest.java
@@ -529,4 +529,40 @@
}
}
+ @Test
+ void addRoleToGroupMapping() {
+ String token = getAdminToken( );
+ try
+ {
+ List<String> list = Arrays.asList( "System Administrator", "role1", "role2", "role3" );
+ given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+ .when( )
+ .body(list)
+ .put( "/mappings/archiva-admin" )
+ .then( )
+ .statusCode( 200 ).extract( ).response( );
+ given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+ .when( )
+ .put( "/mappings/archiva-admin/roles/roletest004" )
+ .then( )
+ .statusCode( 200 ).extract( ).response( );
+ Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+ .when( )
+ .get( "/mappings/archiva-admin" )
+ .then( ).statusCode( 200 ).extract( ).response( );
+ assertNotNull( response );
+ List<String> resultList = response.getBody( ).jsonPath( ).getList( "", String.class );
+ assertEquals( 5, resultList.size( ) );
+ assertTrue( resultList.contains( "roletest004" ) );
+ } finally {
+ // Put it back
+ List<String> list = Arrays.asList( "System Administrator" );
+ given( ).spec( getRequestSpec( token ) ).contentType( JSON )
+ .body( list )
+ .when( )
+ .put( "/mappings/archiva-admin" )
+ .then( ).statusCode( 200 );
+ }
+ }
+
}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java
index f38a46d..d7969b2 100644
--- a/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java
@@ -330,7 +330,14 @@
{
if ( ldapConnection != null )
{
- ldapConnection.close();
+ try
+ {
+ ldapConnection.close();
+ }
+ catch ( NamingException e )
+ {
+ log.error( "Could not close connection: {}", e.getMessage( ), e );
+ }
}
}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/LdapUserManager.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/LdapUserManager.java
index a6a6388..ac27a3a 100644
--- a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/LdapUserManager.java
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/LdapUserManager.java
@@ -44,6 +44,7 @@
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
+import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import java.util.ArrayList;
import java.util.Collections;
@@ -494,7 +495,14 @@
{
if ( ldapConnection != null )
{
- ldapConnection.close();
+ try
+ {
+ ldapConnection.close();
+ }
+ catch ( NamingException e )
+ {
+ log.error( "Could not close connection: {}", e.getMessage( ), e );
+ }
}
}