blob: 76c4fdad658d35196c4495abefc4eda61b41e726 [file] [log] [blame]
package org.apache.archiva.redback.rest.services.v2;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import io.restassured.filter.log.UrlDecoder;
import io.restassured.response.Response;
import org.apache.archiva.components.apacheds.ApacheDs;
import org.apache.archiva.redback.rest.api.Constants;
import org.apache.archiva.redback.rest.api.model.v2.Group;
import org.apache.archiva.redback.rest.api.model.v2.GroupMapping;
import org.apache.archiva.redback.rest.services.BaseSetup;
import org.apache.archiva.redback.rest.services.LdapInfo;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.inject.Inject;
import javax.inject.Named;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Martin Stockhammer <martin_s@apache.org>
*/
@ExtendWith( SpringExtension.class )
@ContextConfiguration(
locations = {"classpath:/ldap-spring-test.xml"} )
@TestInstance( TestInstance.Lifecycle.PER_CLASS )
@Tag("rest-native")
@TestMethodOrder( MethodOrderer.Random.class )
public class NativeGroupServiceTest extends AbstractNativeRestServices
{
protected String peopleSuffix;
protected String groupSuffix;
@Inject
@Named( value = "apacheDS#test" )
private ApacheDs apacheDs;
private InitialDirContext adminContext;
private LdapInfo ldapInfo;
private List<String> groups =
Arrays.asList( "Archiva System Administrator", "Internal Repo Manager", "Internal Repo Observer", "Test Group 1", "Test Group 2", "Test Group 3" );
public InitialDirContext getAdminContext() throws NamingException
{
if (this.ldapInfo.isRemote()) {
Hashtable<String, String> environment = new Hashtable<>( );
environment.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapInfo.getURL());
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, ldapInfo.getBindDN());
environment.put(Context.SECURITY_CREDENTIALS, ldapInfo.getBindPassword());
environment.put("com.sun.jndi.ldap.connect.pool", "true");
return new InitialDirContext(environment);
} else {
return apacheDs.getAdminContext( );
}
}
public List<String> getTestGroupList() {
return this.groups;
}
@Override
protected String getServicePath( )
{
return "/groups";
}
@Override
protected String getSpringConfigLocation( )
{
return "classpath*:spring-context.xml,classpath*:META-INF/spring-context.xml,classpath:/ldap-spring-test.xml";
}
@BeforeAll
void setup( ) throws Exception
{
setupNative( );
this.ldapInfo = BaseSetup.getLdapInfo( );
if (!ldapInfo.isRemote()) {
peopleSuffix = "ou=People,dc=archiva,dc=apache,dc=org";
log.info( "DN Suffix: {}", peopleSuffix );
if ( apacheDs.isStopped( ) )
{
groupSuffix = apacheDs.addSimplePartition( "test", new String[]{"archiva", "apache", "org"} ).getSuffix( );
log.info( "groupSuffix: {}", groupSuffix );
apacheDs.startServer( );
if ( !exists( apacheDs.getAdminContext( ), peopleSuffix ) )
{
BasicAttribute objectClass = new BasicAttribute( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
Attributes attributes = new BasicAttributes( true );
attributes.put( objectClass );
attributes.put( "organizationalUnitName", "foo" );
apacheDs.getAdminContext( ).createSubcontext( peopleSuffix, attributes );
}
}
} else {
this.peopleSuffix = "ou=People," + ldapInfo.getBaseDN( );
this.groupSuffix = ldapInfo.getBaseDN( );
}
}
@AfterAll
void shutdown( ) throws Exception
{
shutdownNative();
if (!this.ldapInfo.isRemote())
{
try
{
InitialDirContext context = null;
try
{
context = getAdminContext( );
context.unbind( this.peopleSuffix );
}
finally
{
try
{
if ( context != null ) context.close( );
}
catch ( Exception e )
{
log.error( "Error during context close {}", e.getMessage( ) );
}
try
{
apacheDs.stopServer( );
}
catch ( Exception e )
{
log.error( "Could not stop apacheds {}", e.getMessage( ) );
}
}
}
catch ( Exception e )
{
log.error( "Could not stop ldap {}", e.getMessage( ) );
}
}
getAdminContext().close();
}
@BeforeEach
public void initLdap( ) throws Exception
{
removeAllGroups( );
createGroups( );
}
@AfterEach
public void cleanupLdap( ) throws NamingException
{
removeAllGroups( );
}
protected boolean exists( DirContext context, String dn )
{
Object result = null;
try {
result = context.lookup( dn );
}
catch ( NameNotFoundException e ) {
return false;
}
catch ( NamingException e )
{
log.error( "Unknown error during lookup: {}", e.getMessage( ) );
}
return result != null;
}
private void removeAllGroups( )
{
InitialDirContext context = null;
try
{
context = getAdminContext( );
for ( String group : getTestGroupList() )
{
try
{
context.unbind( createGroupDn( group ) );
}
catch ( NamingException e )
{
// Ignore
}
}
}
catch ( NamingException e )
{
log.error( "Naming exception {}", e.getMessage( ) );
}
finally
{
try
{
if ( context != null ) context.close( );
}
catch ( Exception e )
{
log.error( "Error during context close {}", e.getMessage( ) );
}
}
}
private String createGroupDn( String cn )
{
return "cn=" + cn + "," + groupSuffix;
}
private void createGroup( DirContext context, String group_name, String dn )
throws Exception
{
if ( !exists( context, dn ) )
{
Attributes attributes = new BasicAttributes( true );
BasicAttribute objectClass = new BasicAttribute( "objectClass" );
objectClass.add( "top" );
objectClass.add( "groupOfUniqueNames" );
attributes.put( objectClass );
attributes.put( "cn", group_name );
BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
basicAttribute.add( "uid=admin," + peopleSuffix );
attributes.put( basicAttribute );
context.createSubcontext( dn, attributes );
}
else
{
log.error( "Group {} exists already", dn );
}
}
private void createGroups( )
throws Exception
{
InitialDirContext context = null;
try
{
context = getAdminContext( );
for ( String group : getTestGroupList() )
{
createGroup( context, group, createGroupDn( group ) );
}
}
finally
{
if ( context != null )
{
context.close( );
}
}
}
@Test
void getGroups() {
String token = getAdminToken( );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ).when( )
.get( ).then( ).statusCode( 200 ).extract( ).response( );
assertNotNull( response );
List<Group> data = response.body( ).jsonPath( ).getList( "data", Group.class );
assertNotNull( data );
assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
assertEquals( Integer.valueOf( Constants.DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
assertEquals( Integer.valueOf( 6 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
assertEquals( 6, data.size( ) );
String[] values = data.stream( ).map( ldapInfo -> ldapInfo.getName( ) ).sorted( ).collect( Collectors.toList( ) ).toArray( new String[0] );
assertArrayEquals( getTestGroupList( ).toArray( new String[0] ), values );
assertEquals( "uid=admin," + this.peopleSuffix, data.get( 0 ).getMemberList( ).get( 0 ) );
}
@Test
void getGroupsWithLimit() {
String token = getAdminToken( );
HashMap<String, Object> params = new HashMap<>( );
params.put( "limit", Long.valueOf( 3 ) );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.param( "limit", Long.valueOf( 3 ) )
.when( )
.get( ).then( ).statusCode( 200 ).extract( ).response( );
assertNotNull( response );
List<Group> data = response.body( ).jsonPath( ).getList( "data", Group.class );
assertNotNull( data );
assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
assertEquals( Integer.valueOf( 3 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
assertEquals( Integer.valueOf( 6 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
assertEquals( 3, data.size( ) );
String[] values = data.stream( ).map( ldapInfo -> ldapInfo.getName( ) ).sorted( ).collect( Collectors.toList( ) ).toArray( new String[0] );
assertArrayEquals( getTestGroupList( ).subList( 0, 3 ).toArray( new String[0] ), values );
assertEquals( "uid=admin," + this.peopleSuffix, data.get( 0 ).getMemberList( ).get( 0 ) );
}
@Test
void getGroupsWithOffset() {
String token = getAdminToken( );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.param( "offset", Long.valueOf( 2 ) )
.when( )
.get( ).then( ).statusCode( 200 ).extract( ).response( );
assertNotNull( response );
List<Group> data = response.body( ).jsonPath( ).getList( "data", Group.class );
assertNotNull( data );
assertEquals( Integer.valueOf( 2 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
assertEquals( Integer.valueOf( Constants.DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
assertEquals( Integer.valueOf( 6 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) );
assertEquals( 4, data.size( ) );
String[] values = data.stream( ).map( ldapInfo -> ldapInfo.getName( ) ).sorted( ).collect( Collectors.toList( ) ).toArray( new String[0] );
assertArrayEquals( getTestGroupList( ).subList( 2, getTestGroupList().size() ).toArray( new String[0] ), values );
assertEquals( "uid=admin," + this.peopleSuffix, data.get( 0 ).getMemberList( ).get( 0 ) );
}
@Test
void getGroupMapping() {
String token = getAdminToken( );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.get( "/mappings" )
.then( ).statusCode( 200 ).extract( ).response( );
assertNotNull( response );
assertEquals( 3, response.getBody( ).jsonPath( ).getList( "" ).size() );
}
@Test
void addGroupMapping() {
String token = getAdminToken( );
try
{
Map<String, Object> jsonAsMap = new HashMap<>( );
jsonAsMap.put( "group_name", "ldap group" );
jsonAsMap.put( "roles", Arrays.asList( "role1", "role2" ) );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.body( jsonAsMap )
.when( )
.post( "/mappings" )
.then( ).statusCode( 201 ).extract( ).response( );
assertNotNull( response );
assertNotNull( response.getHeader( "Location" ) );
assertTrue( UrlDecoder.urlDecode( response.getHeader( "Location" ), Charset.forName( "UTF-8" ), false ).endsWith( "/mappings/ldap group" ) );
} finally {
given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.delete( "/mappings/ldap group" )
.then( )
.statusCode( 200 );
}
}
@Test
void addAndGetGroupMapping() {
String token = getAdminToken( );
try
{
Map<String, Object> jsonAsMap = new HashMap<>( );
jsonAsMap.put( "group_name", "ldap group" );
jsonAsMap.put( "roles", Arrays.asList( "role1", "role2" ) );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.body( jsonAsMap )
.when( )
.post( "/mappings" )
.then( ).statusCode( 201 ).extract( ).response( );
assertNotNull( response );
response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.get( "/mappings" )
.then( ).statusCode( 200 ).extract( ).response( );
assertNotNull( response );
List<GroupMapping> resultList = response.getBody( ).jsonPath( ).getList( "", GroupMapping.class );
assertEquals( 4, response.getBody( ).jsonPath( ).getList( "" ).size() );
Optional<GroupMapping> found = resultList.stream( ).filter( map -> map.getGroupName( ).equals( "ldap group" ) && map.getRoles( ).size( ) == 2 && map.getRoles( ).contains( "role1" ) ).findAny( );
assertTrue( found.isPresent( ) );
} finally {
given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.delete( "/mappings/ldap group" )
.then( )
.statusCode( 200 );
}
}
@Test
void deleteGroupMapping() {
String token = getAdminToken( );
try
{
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.delete( "/mappings/archiva-admin" )
.then( )
.statusCode( 200 ).extract( ).response( );
} finally {
// Put it back
Map<String, Object> jsonAsMap = new HashMap<>( );
jsonAsMap.put( "group_name", "archiva-admin" );
jsonAsMap.put( "roles", Arrays.asList( "System Administrator" ) );
given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.body( jsonAsMap )
.when( )
.post( "/mappings" )
.then( ).statusCode( 201 );
}
}
@Test
void updateGroupMapping() {
String token = getAdminToken( );
try
{
List<String> list = Arrays.asList( "System Administrator", "role1", "role2", "role3" );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.body(list)
.put( "/mappings/archiva-admin" )
.then( )
.statusCode( 200 ).extract( ).response( );
} 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 );
}
}
@Test
void updateAndGetGroupMapping() {
String token = getAdminToken( );
try
{
// The default implementation of redback uses the value from the configuration persistently
// and adds the updates to the configuration.
List<String> list = Arrays.asList( "role1", "role2", "role3" );
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.body(list)
.put( "/mappings/archiva-admin" )
.then( )
.statusCode( 200 ).extract( ).response( );
response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
.when( )
.get( "/mappings" )
.then( ).statusCode( 200 ).extract( ).response( );
assertNotNull( response );
List<GroupMapping> resultList = response.getBody( ).jsonPath( ).getList( "", GroupMapping.class );
assertEquals( 3, response.getBody( ).jsonPath( ).getList( "" ).size() );
for (GroupMapping mapping : resultList) {
System.out.println( mapping.getGroupName( ) + "/" + mapping.getUniqueGroupName( ) );
for (String role : mapping.getRoles( )) {
System.out.println( "Role " + role );
}
}
Optional<GroupMapping> found = resultList.stream( ).filter( map -> map.getGroupName( ).equals( "archiva-admin" ) && map.getRoles( ).size( ) == 4 && map.getRoles( ).contains( "role3" ) ).findAny( );
assertTrue( found.isPresent( ) );
} 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 );
}
}
@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 );
}
}
}