Adding additional REST V2 tests
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapper.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapper.java
index e66306d..afe3d5e 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapper.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapper.java
@@ -239,7 +239,8 @@
 
             searchControls.setDerefLinkFlag( true );
             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
-            searchControls.setReturningAttributes( new String[]{ this.getLdapDnAttribute(), "objectClass", groupNameAttribute} );
+            searchControls.setReturningAttributes( new String[]{ this.getLdapDnAttribute(), "objectClass", groupNameAttribute,
+            ldapGroupMemberAttribute} );
 
             String filter = "objectClass=" + getLdapGroupClass( );
 
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Group.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Group.java
index a4c5793..2b07e32 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Group.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Group.java
@@ -18,6 +18,8 @@
  * under the License.
  */
 
+import io.swagger.v3.oas.annotations.media.Schema;
+
 import javax.xml.bind.annotation.XmlRootElement;
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -27,6 +29,7 @@
  * @author Martin Stockhammer <martin_s@apache.org>
  */
 @XmlRootElement(name="group")
+@Schema(name="Group", description = "Group object")
 public class Group implements Serializable
 {
     private static final long serialVersionUID = -1842878251787304632L;
@@ -44,6 +47,7 @@
         this.name = name;
     }
 
+    @Schema(description = "The name of the group")
     public String getName( )
     {
         return name;
@@ -54,6 +58,7 @@
         this.name = name;
     }
 
+    @Schema(description = "The unique name of the group. Depends on the backend repository, e.g. the LDAP DN.")
     public String getUniqueName( )
     {
         return uniqueName;
@@ -64,6 +69,7 @@
         this.uniqueName = uniqueName;
     }
 
+    @Schema( description = "The group description, if available" )
     public String getDescription( )
     {
         return description;
@@ -74,6 +80,7 @@
         this.description = description;
     }
 
+    @Schema(description = "The list of members. The format of the member strings depends on the backend repository, e.g. for LDAP these may be the member DNs")
     public List<String> getMemberList( )
     {
         return memberList;
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PagedResult.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PagedResult.java
index 3168110..cc3d327 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PagedResult.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PagedResult.java
@@ -18,6 +18,8 @@
  * under the License.
  */
 
+import io.swagger.v3.oas.annotations.media.Schema;
+
 import javax.xml.bind.annotation.XmlRootElement;
 import java.util.List;
 
@@ -26,6 +28,7 @@
  * @author Martin Stockhammer <martin_s@apache.org>
  */
 @XmlRootElement(name="pagedResult")
+@Schema(name = "PagedResult", description = "Contains paged data. Pages are defined by limit and offset.")
 public class PagedResult<T>
 {
     PaginationInfo pagination;
@@ -44,6 +47,7 @@
         return new PagedResult( totalSize, offset, limit, element);
     }
 
+    @Schema(description = "This is the payload of the paged data. The type of data depends on the REST method. ")
     public T getData( )
     {
         return data;
@@ -54,6 +58,7 @@
         this.data = data;
     }
 
+    @Schema(description = "The pagination information")
     public PaginationInfo getPagination( )
     {
         return pagination;
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java
index 9d5dab5..4cd0fd8 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PaginationInfo.java
@@ -18,6 +18,8 @@
  * under the License.
  */
 
+import io.swagger.v3.oas.annotations.media.Schema;
+
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 
@@ -28,6 +30,7 @@
  * @author Martin Stockhammer <martin_s@apache.org>
  */
 @XmlRootElement(name="pagination")
+@Schema(name="PaginationInfo", description = "Contains paging information (limit, offset, totalCount)")
 public class PaginationInfo
 {
     long totalCount;
@@ -45,6 +48,7 @@
         this.limit = limit;
     }
 
+    @Schema(description = "The total number of data available.")
     public long getTotalCount( )
     {
         return totalCount;
@@ -55,6 +59,7 @@
         this.totalCount = totalCount;
     }
 
+    @Schema(description = "The offset of the first element of the returned dataset.")
     public long getOffset( )
     {
         return offset;
@@ -65,6 +70,7 @@
         this.offset = offset;
     }
 
+    @Schema(description = "The maximum number of elements returned per page.")
     public long getLimit( )
     {
         return limit;
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 6961e5f..c626085 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
@@ -22,6 +22,8 @@
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.security.SecurityScheme;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.apache.archiva.redback.authorization.RedbackAuthorization;
@@ -54,9 +56,11 @@
 @SecurityScheme( scheme = "BearerAuth", type = SecuritySchemeType.HTTP )
 @Tag(name = "v2")
 @Tag(name = "v2/Groups")
+@SecurityRequirement(name = "BearerAuth")
 public interface GroupService
 {
 
+    public static final String DEFAULT_PAGE_LIMIT = "1000";
 
     @Path( "" )
     @GET
@@ -67,8 +71,8 @@
             @ApiResponse( description = "List of group objects. The number of returned results depend on the pagination parameters offset and limit." )
         }
     )
-    PagedResult<List<Group>> getGroups( @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
-                                  @QueryParam( "limit" ) @DefaultValue( value = Integer.MAX_VALUE+"" ) Integer limit)
+    PagedResult<List<Group>> getGroups( @QueryParam( "offset" ) @DefaultValue( "0" ) Long offset,
+                                  @QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Long limit)
         throws RedbackServiceException;
 
 
@@ -138,7 +142,7 @@
     @Consumes( {MediaType.APPLICATION_JSON} )
     @Produces( {MediaType.APPLICATION_JSON} )
     @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
-    @Operation( summary = "Updates a multiple group mappings",
+    @Operation( summary = "Updates multiple group mappings",
         responses = {
             @ApiResponse( description = "The status of the update action" ),
             @ApiResponse( responseCode = "405", description = "Invalid input" )
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java
index 4c8eb31..3828c93 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/JacksonJsonConfigurator.java
@@ -18,8 +18,11 @@
  * under the License.
  */
 
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.json.JsonWriteFeature;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
@@ -50,6 +53,7 @@
         log.info( "configure jackson ObjectMapper" );
         objectMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
         objectMapper.enable( DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL );
+        objectMapper.enable( DeserializationFeature.USE_LONG_FOR_INTS );
         objectMapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector( objectMapper.getTypeFactory() ) );
         objectMapper.findAndRegisterModules( );
         objectMapper.registerModule( new JavaTimeModule( ) );
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 3821416..93ff99b 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
@@ -88,7 +88,7 @@
     }
 
     @Override
-    public PagedResult<List<Group>> getGroups( Integer offset, Integer limit ) throws RedbackServiceException
+    public PagedResult<List<Group>> getGroups( Long offset, Long limit ) throws RedbackServiceException
     {
         LdapConnection ldapConnection = null;
 
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/BaseSetup.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/BaseSetup.java
index db9f3bc..f1df561 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/BaseSetup.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/BaseSetup.java
@@ -29,6 +29,10 @@
     public static final String SYSPROP_SERVER_PORT = "archiva.rest.server.port";
     public static final String SYSPROP_SERVER_BASE_URI = "archiva.rest.server.baseuri";
     public static final String SYSPROP_SERVER_ADMIN_PWD = "rest.admin.pwd";
+    public static final String SYSPROP_LDAP_URL = "archiva.rest.ldap.url";
+    public static final String SYSPROP_LDAP_BASEDN = "archiva.rest.ldap.baseDn";
+    public static final String SYSPROP_LDAP_BINDDN = "archiva.rest.ldap.bindDn";
+    public static final String SYSPROP_LDAP_BINPWD = "archiva.rest.ldap.bindPwd";
 
     public static String DEFAULT_ADMIN_PWD = "Ackd245aer9sdfan";
 
@@ -49,4 +53,25 @@
         }
     }
 
+    public static boolean startServer()
+    {
+        return !"no".equals( System.getProperty( SYSPROP_START_SERVER, "yes" ).toLowerCase( ) );
+    }
+    public static String getServerPort()
+    {
+        return System.getProperty( SYSPROP_SERVER_PORT, "" );
+    }
+
+    public static String getBaseUri() {
+        return System.getProperty( SYSPROP_SERVER_BASE_URI, "http://localhost" );
+    }
+
+    public static LdapInfo getLdapInfo() {
+        String url = System.getProperty( SYSPROP_LDAP_URL, "" );
+        String baseDn = System.getProperty( SYSPROP_LDAP_BASEDN, "" );
+        String bindDn = System.getProperty( SYSPROP_LDAP_BINDDN, "" );
+        String bindPwd = System.getProperty( SYSPROP_LDAP_BINPWD, "" );
+
+        return new LdapInfo( url, baseDn, bindDn, bindPwd );
+    }
 }
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LdapInfo.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LdapInfo.java
new file mode 100644
index 0000000..ee52fef
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LdapInfo.java
@@ -0,0 +1,89 @@
+package org.apache.archiva.redback.rest.services;
+
+/*
+ * 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;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class LdapInfo
+{
+    String URL;
+    String baseDN;
+    String bindDN;
+    String bindPassword;
+    boolean remote;
+
+    public LdapInfo( )
+    {
+    }
+
+    public LdapInfo( String URL, String baseDN, String bindDN, String bindPassword )
+    {
+        this.URL = URL;
+        this.baseDN = baseDN;
+        this.bindDN = bindDN;
+        this.bindPassword = bindPassword;
+    }
+
+    public String getURL( )
+    {
+        return URL;
+    }
+
+    public void setURL( String URL )
+    {
+        this.URL = URL;
+    }
+
+    public String getBaseDN( )
+    {
+        return baseDN;
+    }
+
+    public void setBaseDN( String baseDN )
+    {
+        this.baseDN = baseDN;
+    }
+
+    public String getBindDN( )
+    {
+        return bindDN;
+    }
+
+    public void setBindDN( String bindDN )
+    {
+        this.bindDN = bindDN;
+    }
+
+    public String getBindPassword( )
+    {
+        return bindPassword;
+    }
+
+    public void setBindPassword( String bindPassword )
+    {
+        this.bindPassword = bindPassword;
+    }
+
+    public boolean isRemote() {
+        return StringUtils.isNotEmpty( URL ) && StringUtils.isNotEmpty( baseDN ) && StringUtils.isNotEmpty( bindDN );
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
index 1f33566..54b84ec 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
@@ -18,8 +18,10 @@
  * under the License.
  */
 
+import com.fasterxml.jackson.databind.ser.Serializers;
 import io.restassured.RestAssured;
 import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.response.Response;
 import io.restassured.specification.RequestSpecification;
 import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
 import org.apache.archiva.redback.rest.services.BaseSetup;
@@ -43,11 +45,13 @@
 import org.slf4j.LoggerFactory;
 import org.springframework.web.context.ContextLoaderListener;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
-import static io.restassured.RestAssured.baseURI;
-import static io.restassured.RestAssured.port;
+import static io.restassured.RestAssured.*;
+import static io.restassured.http.ContentType.JSON;
 import static org.apache.archiva.redback.rest.services.BaseSetup.*;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
@@ -58,6 +62,7 @@
  * @author Martin Stockhammer <martin_s@apache.org>
  */
 @Tag( "rest-native" )
+@Tag( "rest-v2" )
 public abstract class AbstractNativeRestServices
 {
     public static final int STOPPED = 0;
@@ -65,6 +70,9 @@
     public static final int STARTING = 2;
     public static final int STARTED = 3;
     public static final int ERROR = 4;
+    private final boolean startServer;
+    private final String serverPort;
+    private final String baseUri;
 
     private RequestSpecification requestSpec;
     protected Logger log = LoggerFactory.getLogger( getClass( ) );
@@ -75,10 +83,24 @@
     private UserManager userManager;
     private RoleManager roleManager;
 
+    private final boolean remoteService;
+
+    private String adminToken;
+    private String adminRefreshToken;
+
 
     public AbstractNativeRestServices( )
     {
+        this.startServer = BaseSetup.startServer( );
+        this.serverPort = BaseSetup.getServerPort( );
+        this.baseUri = BaseSetup.getBaseUri( );
 
+        if ( startServer )
+        {
+            this.remoteService = false;
+        } else {
+            this.remoteService = true;
+        }
     }
 
     protected abstract String getServicePath( );
@@ -208,7 +230,7 @@
         }
         else
         {
-            um.updateUser( adminUser, false);
+            um.updateUser( adminUser, false );
         }
         getRoleManager( ).assignRole( "system-administrator", adminUser.getUsername( ) );
     }
@@ -284,11 +306,7 @@
 
     protected void setupNative( ) throws Exception
     {
-        String startServer = System.getProperty( SYSPROP_START_SERVER, "yes" ).toLowerCase( );
-        String serverPort = System.getProperty( SYSPROP_SERVER_PORT, "" );
-        String baseUri = System.getProperty( SYSPROP_SERVER_BASE_URI, "http://localhost" );
-
-        if ( !"no".equals( startServer ) )
+        if ( this.startServer )
         {
             startServer( );
         }
@@ -310,24 +328,75 @@
             RestAssured.baseURI = "http://localhost";
         }
         String basePath = getBasePath( );
-        this.requestSpec = getRequestSpecBuilder().build( );
+        this.requestSpec = getRequestSpecBuilder( ).build( );
         RestAssured.basePath = basePath;
     }
 
-    protected RequestSpecBuilder getRequestSpecBuilder() {
-        return new RequestSpecBuilder().setBaseUri( baseURI )
+    protected RequestSpecBuilder getRequestSpecBuilder( )
+    {
+        return new RequestSpecBuilder( ).setBaseUri( baseURI )
             .setPort( port )
-            .setBasePath( getBasePath() )
+            .setBasePath( getBasePath( ) )
             .addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
-
     }
 
-    protected RequestSpecification getRequestSpec(String bearerToken) {
-        return getRequestSpecBuilder( ).addHeader( "Authorization", "Bearer " + bearerToken ).build();
+    protected RequestSpecBuilder getAuthRequestSpecBuilder( )
+    {
+        return new RequestSpecBuilder( ).setBaseUri( baseURI )
+            .setPort( port )
+            .setBasePath( new StringBuilder( )
+                .append( getContextRoot( ) )
+                .append( getServiceBasePath( ) ).append("/auth").toString() )
+            .addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
+    }
+
+    protected RequestSpecification getRequestSpec( String bearerToken )
+    {
+        return getRequestSpecBuilder( ).addHeader( "Authorization", "Bearer " + bearerToken ).build( );
     }
 
     protected void shutdownNative( ) throws Exception
     {
-        stopServer( );
+        if (startServer)
+        {
+            stopServer( );
+        }
+    }
+
+    protected org.apache.archiva.redback.rest.api.model.User addRemoteUser(String userid, String password, String fullName, String mail) {
+
+        return null;
+    }
+
+    protected void initAdminToken() {
+        Map<String, Object> jsonAsMap = new HashMap<>();
+        jsonAsMap.put( "grant_type", "authorization_code" );
+        jsonAsMap.put("user_id", getAdminUser());
+        jsonAsMap.put("password", getAdminPwd() );
+        Response result = given( ).spec( getAuthRequestSpecBuilder().build() )
+            .contentType( JSON )
+            .body( jsonAsMap )
+            .when( ).post( "/authenticate").then( ).statusCode( 200 )
+            .extract( ).response( );
+        this.adminToken = result.body( ).jsonPath( ).getString( "access_token" );
+        this.adminRefreshToken = result.body( ).jsonPath( ).getString( "refresh_token" );
+    }
+
+    protected String getAdminToken()  {
+        if (this.adminToken == null) {
+            initAdminToken();
+        }
+        return this.adminToken;
+    }
+
+    protected String getAdminRefreshToken()  {
+        if (this.adminRefreshToken == null) {
+            initAdminToken();
+        }
+        return this.adminRefreshToken;
+    }
+
+    public boolean isRemoteService() {
+        return this.remoteService;
     }
 }
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
index ca8ded4..ee4f19a 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
@@ -46,6 +46,7 @@
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -70,6 +71,8 @@
  */
 @ExtendWith( SpringExtension.class )
 @ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
+@Tag("rest-local")
+@Tag("rest-v2")
 public abstract class AbstractRestServicesTestV2
 {
 
@@ -234,6 +237,12 @@
         return "Basic " + Base64Utility.encode( ( uid + ":" + password ).getBytes() );
     }
 
+    public String getUserAuthzHeader(String user) {
+        assertNotNull( getJwtAuthenticator());
+        Token token = getJwtAuthenticator().generateToken( user );
+        return "Bearer " + token.getData( );
+    }
+
     public String getAdminAuthzHeader()
     {
         assertNotNull( getJwtAuthenticator());
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 c67f397..b58f6f1 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
@@ -29,6 +29,7 @@
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -60,6 +61,7 @@
 @ContextConfiguration(
     locations = {"classpath:/ldap-spring-test.xml"} )
 @TestInstance( TestInstance.Lifecycle.PER_CLASS )
+@Tag("rest-local")
 public class GroupServiceTest
     extends AbstractRestServicesTestV2
 {
@@ -294,7 +296,7 @@
         {
             GroupService service = getGroupService( authorizationHeader );
 
-            List<String> allGroups = service.getGroups( Integer.valueOf( 0 ), Integer.valueOf( Integer.MAX_VALUE ) ).getData( ).stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) );
+            List<String> allGroups = service.getGroups( Long.valueOf( 0 ), Long.valueOf( Long.MAX_VALUE ) ).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/NativeAuthenticationServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java
index 1022a5e..2124ade 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java
@@ -18,11 +18,7 @@
  * under the License.
  */
 
-import io.restassured.RestAssured;
-import io.restassured.builder.RequestSpecBuilder;
 import io.restassured.response.Response;
-import io.restassured.specification.RequestSpecification;
-import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Tag;
@@ -37,13 +33,12 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import static io.restassured.RestAssured.*;
+import static io.restassured.RestAssured.given;
 import static io.restassured.http.ContentType.JSON;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.springframework.core.annotation.MergedAnnotations.from;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
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
new file mode 100644
index 0000000..9def33d
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeGroupServiceTest.java
@@ -0,0 +1,366 @@
+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.http.ContentType;
+import io.restassured.response.Response;
+import org.apache.archiva.components.apacheds.ApacheDs;
+import org.apache.archiva.redback.rest.api.model.Group;
+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.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+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.util.Arrays;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static io.restassured.RestAssured.given;
+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")
+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 groupName, 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", groupName );
+            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( 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( 1000 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+        assertEquals( Integer.valueOf( 6 ), response.body( ).jsonPath( ).get( "pagination.totalCount" ) );
+        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( 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.totalCount" ) );
+        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( ContentType.JSON )
+            .param( "offset", Long.valueOf( 2 ) )
+            .when( )
+            .get( ).then( ).statusCode( 200 ).extract( ).response( );
+        System.out.println( response.print( ) );
+        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( 1000 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
+        assertEquals( Integer.valueOf( 6 ), response.body( ).jsonPath( ).get( "pagination.totalCount" ) );
+        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 ) );
+    }
+
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/UserServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/UserServiceTest.java
new file mode 100644
index 0000000..9c342e7
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/UserServiceTest.java
@@ -0,0 +1,528 @@
+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 com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+import org.apache.archiva.redback.rest.api.model.Operation;
+import org.apache.archiva.redback.rest.api.model.Permission;
+import org.apache.archiva.redback.rest.api.model.RequestTokenRequest;
+import org.apache.archiva.redback.rest.api.model.ResetPasswordRequest;
+import org.apache.archiva.redback.rest.api.model.TokenResponse;
+import org.apache.archiva.redback.rest.api.model.User;
+import org.apache.archiva.redback.rest.api.model.UserRegistrationRequest;
+import org.apache.archiva.redback.rest.api.services.UserService;
+import org.apache.archiva.redback.rest.services.FakeCreateAdminService;
+import org.apache.archiva.redback.rest.services.mock.EmailMessage;
+import org.apache.archiva.redback.rest.services.mock.MockJavaMailSender;
+import org.apache.archiva.redback.rest.services.mock.ServicesAssert;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+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.ws.rs.ForbiddenException;
+import javax.ws.rs.core.MediaType;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/**
+ * @author Olivier Lamy
+ */
+@ExtendWith( SpringExtension.class )
+@ContextConfiguration(
+    locations = {"classpath:/spring-context.xml"} )
+@TestInstance( TestInstance.Lifecycle.PER_CLASS )
+public class UserServiceTest
+    extends AbstractRestServicesTestV2
+{
+
+    @Inject
+    MockJavaMailSender mockJavaMailSender;
+
+    @AfterEach
+    void cleanup() {
+        mockJavaMailSender.getSendedEmails( ).clear( );
+    }
+
+    @BeforeAll
+    void setup( ) throws Exception
+    {
+        super.init( );
+        super.startServer( );
+    }
+
+    @AfterAll
+    void shutdown( ) throws Exception
+    {
+        super.stopServer( );
+        super.destroy( );
+    }
+
+    private UserService getUserService( String authzHeader )
+    {
+        UserService service =
+            JAXRSClientFactory.create( "http://localhost:" + getServerPort( ) + "/" + getRestServicesPath( ) + "/v2/redback/",
+                UserService.class, Collections.singletonList( new JacksonJaxbJsonProvider( ) ) );
+
+        // time out for debuging purpose
+        WebClient.getConfig( service ).getHttpConduit( ).getClient( ).setReceiveTimeout( getTimeout( ) );
+
+        if ( authzHeader != null )
+        {
+            WebClient.client( service ).header( "Authorization", authzHeader );
+        }
+        WebClient.client( service ).header( "Referer", "http://localhost:" + getServerPort( ) );
+        WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
+        WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
+
+        return service;
+    }
+
+    @Test
+    public void ping( )
+        throws Exception
+    {
+        Boolean res = getUserService( null ).ping( );
+        assertTrue( res );
+    }
+
+    @Test
+    public void getUsers( )
+        throws Exception
+    {
+        String adminHeader = getAdminAuthzHeader( );
+        UserService userService = getUserService( adminHeader );
+        List<User> users = userService.getUsers( );
+        assertNotNull( users );
+        assertFalse( users.isEmpty( ) );
+    }
+
+    @Test()
+    @Disabled
+    public void getUsersWithoutAuthz( )
+        throws Exception
+    {
+        UserService userService = getUserService( null );
+        assertThrows( ForbiddenException.class, ( ) -> {
+            try
+            {
+                userService.getUsers( );
+            }
+            catch ( ForbiddenException e )
+            {
+                assertEquals( 403, e.getResponse( ).getStatus( ) );
+                throw e;
+            }
+        } );
+
+
+    }
+
+    @Test
+    public void getNoPermissionNotAuthz( )
+    {
+
+        UserService userService = getUserService( null );
+        WebClient.client( userService ).header( "Origin", "http://localhost/myrequest" );
+
+        try
+        {
+            getFakeCreateAdminService( ).testAuthzWithoutKarmasNeededButAuthz( );
+        }
+        catch ( ForbiddenException e )
+        {
+            assertEquals( 403, e.getResponse( ).getStatus( ) );
+            throw e;
+        }
+    }
+
+    @Test
+    public void getNoPermissionAuthz( )
+    {
+
+        try
+        {
+            FakeCreateAdminService service = getFakeCreateAdminService( );
+
+            WebClient.client( service ).header( "Authorization", getAdminAuthzHeader( ) );
+
+            assertTrue( service.testAuthzWithoutKarmasNeededButAuthz( ) );
+
+        }
+        catch ( ForbiddenException e )
+        {
+            assertEquals( 403, e.getResponse( ).getStatus( ) );
+            throw e;
+        }
+    }
+
+    @Test
+    @Disabled
+    public void register( )
+        throws Exception
+    {
+        try
+        {
+            mockJavaMailSender.getSendedEmails( ).clear( );
+            UserService service = getUserService( getAdminAuthzHeader( ) );
+            User u = new User( );
+            u.setFullName( "the toto" );
+            u.setUsername( "toto" );
+            u.setEmail( "toto@toto.fr" );
+            u.setPassword( "toto123" );
+            u.setConfirmPassword( "toto123" );
+            String key = service.registerUser( new UserRegistrationRequest( u, "http://wine.fr/bordeaux" ) ).getKey( );
+
+            assertNotEquals( "-1", key );
+
+            ServicesAssert assertService =
+                JAXRSClientFactory.create( "http://localhost:" + getServerPort( ) + "/" + getRestServicesPath( ) + "/testsService/",
+                    ServicesAssert.class,
+                    Collections.singletonList( new JacksonJaxbJsonProvider( ) ) );
+
+            List<EmailMessage> emailMessages = assertService.getEmailMessageSended( );
+            assertEquals( 1, emailMessages.size( ) );
+            assertEquals( "toto@toto.fr", emailMessages.get( 0 ).getTos( ).get( 0 ) );
+
+            assertEquals( "Welcome", emailMessages.get( 0 ).getSubject( ) );
+            String messageContent = emailMessages.get( 0 ).getText( );
+
+            log.info( "messageContent: {}", messageContent );
+
+            assertNotNull( messageContent );
+            assertTrue( messageContent.contains( "Use the following URL to validate your account." ) );
+            assertTrue( messageContent.contains( "http://wine.fr/bordeaux" ) );
+            assertTrue( messageContent.contains( "toto" ) );
+
+            assertTrue( service.validateUserFromKey( key ).isSuccess( ) );
+
+            service = getUserService( getAdminAuthzHeader( ) );
+
+            u = service.getUser( "toto" );
+
+            assertNotNull( u );
+            assertTrue( u.isValidated( ) );
+            assertTrue( u.isPasswordChangeRequired( ) );
+
+            assertTrue( service.validateUserFromKey( key ).isSuccess( ) );
+
+        }
+        catch ( Exception e )
+        {
+            log.error( e.getMessage( ), e );
+            throw e;
+        }
+        finally
+        {
+            deleteUserQuietly( "toto" );
+        }
+
+    }
+
+    @Test
+    public void registerNoUrl( )
+        throws Exception
+    {
+        try
+        {
+            UserService service = getUserService( getAdminAuthzHeader( ) );
+            User u = new User( );
+            u.setFullName( "the toto" );
+            u.setUsername( "toto" );
+            u.setEmail( "toto@toto.fr" );
+            u.setPassword( "toto123" );
+            u.setConfirmPassword( "toto123" );
+            String key = service.registerUser( new UserRegistrationRequest( u, null ) ).getKey( );
+
+            assertNotEquals( "-1", key );
+
+            ServicesAssert assertService =
+                JAXRSClientFactory.create( "http://localhost:" + getServerPort( ) + "/" + getRestServicesPath( ) + "/testsService/",
+                    ServicesAssert.class,
+                    Collections.singletonList( new JacksonJaxbJsonProvider( ) ) );
+
+            List<EmailMessage> emailMessages = assertService.getEmailMessageSended( );
+            assertEquals( 1, emailMessages.size( ) );
+            assertEquals( "toto@toto.fr", emailMessages.get( 0 ).getTos( ).get( 0 ) );
+
+            assertEquals( "Welcome", emailMessages.get( 0 ).getSubject( ) );
+            String messageContent = emailMessages.get( 0 ).getText( );
+
+            log.info( "messageContent: {}", messageContent );
+            assertNotNull( messageContent );
+            assertTrue( messageContent.contains( "Use the following URL to validate your account." ) );
+            assertTrue( messageContent.contains( "http://localhost:" + getServerPort( ) ) );
+            assertTrue( messageContent.toLowerCase( ).contains( "toto" ) );
+
+            assertTrue( service.validateUserFromKey( key ).isSuccess( ) );
+
+            service = getUserService( getAdminAuthzHeader( ) );
+
+            u = service.getUser( "toto" );
+
+            assertNotNull( u );
+            assertTrue( u.isValidated( ) );
+            assertTrue( u.isPasswordChangeRequired( ) );
+
+            assertTrue( service.validateUserFromKey( key ).isSuccess( ) );
+
+        }
+        catch ( Exception e )
+        {
+            log.error( e.getMessage( ), e );
+            throw e;
+        }
+        finally
+        {
+            deleteUserQuietly( "toto" );
+        }
+
+    }
+
+    @Test
+    @Disabled
+    public void resetPassword( )
+        throws Exception
+    {
+        try
+        {
+            mockJavaMailSender.getSendedEmails().clear();
+
+            UserService service = getUserService( getAdminAuthzHeader( ) );
+            User u = new User( );
+            u.setFullName( "the toto" );
+            u.setUsername( "toto" );
+            u.setEmail( "toto@toto.fr" );
+            u.setPassword( "toto123" );
+            u.setConfirmPassword( "toto123" );
+            String key = service.registerUser( new UserRegistrationRequest( u, "http://wine.fr/bordeaux" ) ).getKey( );
+
+            assertNotEquals( "-1", key );
+
+            ServicesAssert assertService =
+                JAXRSClientFactory.create( "http://localhost:" + getServerPort( ) + "/" + getRestServicesPath( ) + "/testsService/",
+                    ServicesAssert.class,
+                    Collections.singletonList( new JacksonJaxbJsonProvider( ) ) );
+
+            WebClient.client( assertService ).accept( MediaType.APPLICATION_JSON_TYPE );
+            WebClient.client( assertService ).type( MediaType.APPLICATION_JSON_TYPE );
+
+            List<EmailMessage> emailMessages = assertService.getEmailMessageSended( );
+            assertEquals( 1, emailMessages.size( ) );
+            assertEquals( "toto@toto.fr", emailMessages.get( 0 ).getTos( ).get( 0 ) );
+
+            assertEquals( "Welcome", emailMessages.get( 0 ).getSubject( ) );
+            assertTrue(
+                emailMessages.get( 0 ).getText( ).contains( "Use the following URL to validate your account." ) );
+
+            assertTrue( service.validateUserFromKey( key ).isSuccess( ) );
+
+            service = getUserService( getAdminAuthzHeader( ) );
+
+            u = service.getUser( "toto" );
+
+            assertNotNull( u );
+            assertTrue( u.isValidated( ) );
+            assertTrue( u.isPasswordChangeRequired( ) );
+
+            assertTrue( service.validateUserFromKey( key ).isSuccess( ) );
+
+            assertTrue( service.resetPassword( new ResetPasswordRequest( "toto", "http://foo.fr/bar" ) ).isSuccess( ) );
+
+            emailMessages = assertService.getEmailMessageSended( );
+            assertEquals( 2, emailMessages.size( ) );
+            assertEquals( "toto@toto.fr", emailMessages.get( 1 ).getTos( ).get( 0 ) );
+
+            String messageContent = emailMessages.get( 1 ).getText( );
+
+            assertNotNull( messageContent );
+            assertTrue( messageContent.contains( "Password Reset" ) );
+            assertTrue( messageContent.contains( "Username: toto" ) );
+            assertTrue( messageContent.contains( "http://foo.fr/bar" ) );
+
+
+        }
+        catch ( Exception e )
+        {
+            log.error( e.getMessage( ), e );
+            throw e;
+        }
+        finally
+        {
+            deleteUserQuietly( "toto" );
+        }
+
+    }
+
+    private void deleteUserQuietly( String userName )
+    {
+        try
+        {
+            getUserService( getAdminAuthzHeader( ) ).deleteUser( userName );
+        }
+        catch ( Exception e )
+        {
+            log.warn( "ignore fail to delete user " + e.getMessage( ), e );
+        }
+    }
+
+    @Test
+    public void getAdminPermissions( )
+        throws Exception
+    {
+        Collection<Permission> permissions = getUserService( getAdminAuthzHeader( ) ).getUserPermissions( "admin" );
+        log.info( "admin permisssions: {}", permissions );
+    }
+
+    @Test
+    public void getGuestPermissions( )
+        throws Exception
+    {
+        createGuestIfNeeded( );
+        Collection<Permission> permissions = getUserService( null ).getCurrentUserPermissions( );
+        log.info( "guest permisssions: {}", permissions );
+    }
+
+    @Test
+    public void getAdminOperations( )
+        throws Exception
+    {
+        Collection<Operation> operations = getUserService( getAdminAuthzHeader( ) ).getUserOperations( "admin" );
+        log.info( "admin operations: {}", operations );
+    }
+
+    @Test
+    public void getGuestOperations( )
+        throws Exception
+    {
+        createGuestIfNeeded( );
+        Collection<Operation> operations = getUserService( null ).getCurrentUserOperations( );
+        log.info( "guest operations: {}", operations );
+    }
+
+    @Test
+    public void updateMe( )
+        throws Exception
+    {
+        User u = new User( );
+        u.setFullName( "the toto" );
+        u.setUsername( "toto" );
+        u.setEmail( "toto@toto.fr" );
+        u.setPassword( "toto123" );
+        u.setConfirmPassword( "toto123" );
+        u.setValidated( true );
+        getUserService( getAdminAuthzHeader( ) ).createUser( u );
+
+        u.setFullName( "the toto123" );
+        u.setEmail( "toto@titi.fr" );
+        u.setPassword( "toto1234" );
+        u.setPreviousPassword( "toto123" );
+        getUserService( getUserAuthzHeader( "toto" ) ).updateMe( u );
+
+        u = getUserService( getAdminAuthzHeader( ) ).getUser( "toto" );
+        assertEquals( "the toto123", u.getFullName( ) );
+        assertEquals( "toto@titi.fr", u.getEmail( ) );
+
+        u.setFullName( "the toto1234" );
+        u.setEmail( "toto@tititi.fr" );
+        u.setPassword( "toto12345" );
+        u.setPreviousPassword( "toto1234" );
+        getUserService( getUserAuthzHeader( "toto" )) .updateMe( u );
+
+        u = getUserService( getAdminAuthzHeader( ) ).getUser( "toto" );
+        assertEquals( "the toto1234", u.getFullName( ) );
+        assertEquals( "toto@tititi.fr", u.getEmail( ) );
+
+        getUserService( getAdminAuthzHeader( ) ).deleteUser( "toto" );
+    }
+
+    @Test
+    @Disabled
+    public void lockUnlockUser( )
+        throws Exception
+    {
+        try
+        {
+
+            // START SNIPPET: create-user
+            User user = new User( "toto", "toto the king", "toto@toto.fr", false, false );
+            user.setPassword( "foo123" );
+            user.setPermanent( false );
+            user.setPasswordChangeRequired( false );
+            user.setLocked( false );
+            user.setValidated( true );
+            UserService userService = getUserService( getAdminAuthzHeader( ) );
+            userService.createUser( user );
+            // END SNIPPET: create-user
+            user = userService.getUser( "toto" );
+            assertNotNull( user );
+            assertEquals( "toto the king", user.getFullName( ) );
+            assertEquals( "toto@toto.fr", user.getEmail( ) );
+            TokenResponse result = getLoginServiceV2( null ).logIn( new RequestTokenRequest( "toto", "foo123" ) );
+            getLoginServiceV2( "Bearer " + result.getAccessToken( ) ).pingWithAutz( );
+
+            userService.lockUser( "toto" );
+
+            assertTrue( userService.getUser( "toto" ).isLocked( ) );
+
+            userService.unlockUser( "toto" );
+
+            assertFalse( userService.getUser( "toto" ).isLocked( ) );
+        }
+        finally
+        {
+            getUserService( getAdminAuthzHeader( ) ).deleteUser( "toto" );
+            getUserService( getAdminAuthzHeader( ) ).removeFromCache( "toto" );
+            assertNull( getUserService( getAdminAuthzHeader( ) ).getUser( "toto" ) );
+        }
+    }
+
+    public void guestUserCreate( )
+        throws Exception
+    {
+        UserService userService = getUserService( getAdminAuthzHeader( ) );
+        assertNull( userService.getGuestUser( ) );
+        assertNull( userService.createGuestUser( ) );
+
+    }
+
+    protected void createGuestIfNeeded( )
+        throws Exception
+    {
+        UserService userService = getUserService( getAdminAuthzHeader( ) );
+        if ( userService.getGuestUser( ) == null )
+        {
+            userService.createGuestUser( );
+        }
+    }
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/META-INF/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/META-INF/spring-context.xml
index 13ce479..33a4410 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/META-INF/spring-context.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/META-INF/spring-context.xml
@@ -32,7 +32,7 @@
   -->
   <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
 
-  <jaxrs:server id="testServices" address="/fakeCreateAdminService">
+  <jaxrs:server name="testServices" address="/fakeCreateAdminService">
     <jaxrs:serviceBeans>
       <ref bean="fakeCreateAdminService"/>
     </jaxrs:serviceBeans>
@@ -42,7 +42,7 @@
 
    </jaxrs:server>
 
-  <jaxrs:server id="services" address="/testsService">
+  <jaxrs:server name="services" address="/testsService">
     <jaxrs:serviceBeans>
       <ref bean="servicesAssert"/>
     </jaxrs:serviceBeans>