Creating next generation REST API
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 9b44fe5..e66306d 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
@@ -113,6 +113,9 @@
     public static String DEFAULT_GROUP_NAME_ATTRIBUTE = "cn";
     private String groupNameAttribute = DEFAULT_GROUP_NAME_ATTRIBUTE;
 
+    public static String DEFAULT_DESCRIPTION_ATTRIBUTE = "description";
+    private String descriptionAttribute = DEFAULT_DESCRIPTION_ATTRIBUTE;
+
     // True, if the member attribute stores the DN, otherwise the userkey is used as entry value
     private boolean useDnAsMemberValue = true;
 
@@ -150,6 +153,8 @@
         this.dnAttr = userConf.getString( UserConfigurationKeys.LDAP_DN_ATTRIBUTE, this.dnAttr );
 
         this.groupNameAttribute = userConf.getString( UserConfigurationKeys.LDAP_GROUP_NAME_ATTRIBUTE, DEFAULT_GROUP_NAME_ATTRIBUTE );
+
+        this.descriptionAttribute = userConf.getString( UserConfigurationKeys.LDAP_GROUP_DESCRIPTION_ATTRIBUTE, DEFAULT_DESCRIPTION_ATTRIBUTE );
     }
 
 
@@ -222,6 +227,93 @@
         }
     }
 
+    @Override
+    public List<LdapGroup> getAllGroupObjects( DirContext context ) throws MappingException
+    {
+
+        NamingEnumeration<SearchResult> namingEnumeration = null;
+        try
+        {
+
+            SearchControls searchControls = new SearchControls( );
+
+            searchControls.setDerefLinkFlag( true );
+            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+            searchControls.setReturningAttributes( new String[]{ this.getLdapDnAttribute(), "objectClass", groupNameAttribute} );
+
+            String filter = "objectClass=" + getLdapGroupClass( );
+
+            if ( !StringUtils.isEmpty( this.groupFilter ) )
+            {
+                filter = "(&(" + filter + ")(" + this.groupFilter + "))";
+            }
+
+            namingEnumeration = context.search( getGroupsDn( ), filter, searchControls );
+
+            List<LdapGroup> allGroups = new ArrayList<>( );
+
+            while ( namingEnumeration.hasMore( ) )
+            {
+                SearchResult searchResult = namingEnumeration.next( );
+                allGroups.add( getGroupFromResult( searchResult ) );
+            }
+
+            return allGroups;
+        }
+        catch ( LdapException e )
+        {
+            throw new MappingException( e.getMessage( ), e );
+        }
+        catch ( NamingException e )
+        {
+            throw new MappingException( e.getMessage( ), e );
+        }
+        finally
+        {
+            close( namingEnumeration );
+        }
+    }
+
+    LdapGroup getGroupFromResult(SearchResult searchResult) throws NamingException
+    {
+        LdapGroup group = new LdapGroup( searchResult.getNameInNamespace() );
+        Attribute attValue = searchResult.getAttributes( ).get( groupNameAttribute );
+        if ( attValue != null )
+        {
+            group.setName( attValue.get( ).toString( ) );
+        }
+        else
+        {
+            log.error( "Could not get group name from attribute {}. Group DN: {}", groupNameAttribute, searchResult.getNameInNamespace( ) );
+        }
+        attValue = searchResult.getAttributes( ).get( descriptionAttribute );
+        if (attValue!=null) {
+            group.setDescription( attValue.get( ).toString( ) );
+        }
+        Attribute memberValues = searchResult.getAttributes( ).get( ldapGroupMemberAttribute );
+        if (memberValues!=null)
+        {
+            NamingEnumeration<?> allMembersEnum = memberValues.getAll( );
+            try
+            {
+                while ( allMembersEnum.hasMore( ) )
+                {
+                    String memberValue = allMembersEnum.next( ).toString( );
+                    if ( !StringUtils.isEmpty( memberValue ) )
+                    {
+                        group.addMember( memberValue );
+                    }
+                }
+            } finally
+            {
+                if (allMembersEnum!=null) {
+                    closeNamingEnumeration( allMembersEnum );
+                }
+            }
+        }
+        return group;
+    }
+
     protected void closeNamingEnumeration( NamingEnumeration namingEnumeration )
     {
         if ( namingEnumeration != null )
@@ -379,6 +471,9 @@
         }
     }
 
+    /*
+     * TODO: Should use LDAP search, as this may not work for users in subtrees
+     */
     private String getUserDnFromId(String userKey) {
         return new StringBuilder().append( this.userIdAttribute ).append( "=" ).append( userKey ).append( "," ).append(
             getBaseDn( ) ).toString();
@@ -407,6 +502,7 @@
                 User user = userManager.findUser( username );
                 if ( user != null && user instanceof LdapUser )
                 {
+                    // TODO: This is some kind of memberOf retrieval, but will not work. Need to setup a memberOf Attribute
                     LdapUser ldapUser = (LdapUser) user ;
                     Attribute dnAttribute = ldapUser.getOriginalAttributes( ).get( getLdapDnAttribute( ) );
                     if ( dnAttribute != null )
@@ -476,6 +572,98 @@
         }
     }
 
+    /*
+     * TODO: We should implement recursive group retrieval
+     *  Need a configuration flag, to activate recursion
+     */
+    @Override
+    public List<LdapGroup> getGroupObjects( String username, DirContext context ) throws MappingException
+    {
+        Set<LdapGroup> userGroups = new HashSet<>( );
+
+        NamingEnumeration<SearchResult> namingEnumeration = null;
+        try
+        {
+
+            SearchControls searchControls = new SearchControls( );
+
+            searchControls.setDerefLinkFlag( true );
+            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+
+
+            String userIdentifier = null;
+            String userDn = null;
+            try
+            {
+                //try to look the user up
+                User user = userManager.findUser( username );
+                if ( user != null && user instanceof LdapUser )
+                {
+                    // TODO: This is some kind of memberOf retrieval, but will not work with DN.
+                    // We need a configuration entry for the memberOf attribute and a flag, if this should be used
+                    LdapUser ldapUser = (LdapUser) user ;
+                    Attribute dnAttribute = ldapUser.getOriginalAttributes( ).get( getLdapDnAttribute( ) );
+                    if ( dnAttribute != null )
+                    {
+                        userIdentifier = dnAttribute.get( ).toString();
+                    }
+                    userDn = ldapUser.getDn( );
+
+                }
+            }
+            catch ( UserNotFoundException e )
+            {
+                log.warn( "Failed to look up user {}. Computing distinguished name manually", username, e );
+            }
+            catch ( UserManagerException e )
+            {
+                log.warn( "Failed to look up user {}. Computing distinguished name manually", username, e );
+            }
+            if ( userIdentifier == null )
+            {
+                //failed to look up the user's groupEntry directly
+
+                if ( this.useDnAsMemberValue )
+                {
+                    userIdentifier = userDn;
+                }
+                else
+                {
+                    userIdentifier = username;
+                }
+            }
+
+            String filter =
+                new StringBuilder( ).append( "(&" ).append( "(objectClass=" + getLdapGroupClass( ) + ")" ).append(
+                    "(" ).append( getLdapGroupMemberAttribute( ) ).append( "=" ).append( Rdn.escapeValue( userIdentifier ) ).append( ")" ).append(
+                    ")" ).toString( );
+
+            log.debug( "filter: {}", filter );
+
+            namingEnumeration = context.search( getGroupsDn( ), filter, searchControls );
+
+            while ( namingEnumeration.hasMore( ) )
+            {
+                SearchResult groupSearchResult = namingEnumeration.next( );
+                LdapGroup groupName = getGroupFromResult( groupSearchResult );
+                userGroups.add( groupName );
+            }
+        }
+        catch ( LdapException e )
+        {
+            throw new MappingException( e.getMessage( ), e );
+        }
+        catch ( NamingException e )
+        {
+            throw new MappingException( e.getMessage( ), e );
+        }
+        finally
+        {
+            close( namingEnumeration );
+        }
+        return new ArrayList( userGroups );
+    }
+
     public List<String> getRoles( String username, DirContext context, Collection<String> realRoles )
         throws MappingException
     {
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapperConfiguration.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapperConfiguration.java
index d49561e..0515c50 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapperConfiguration.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/DefaultLdapRoleMapperConfiguration.java
@@ -31,6 +31,7 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -109,4 +110,18 @@
         Map<String, Collection<String>> mappings = map.asMap();
         return mappings;
     }
+
+    @Override
+    public Collection<String> getLdapGroupMapping( String groupName ) throws MappingException
+    {
+        if (this.ldapMappings.containsKey( groupName )) {
+            return this.ldapMappings.get( groupName );
+        } else {
+            String value = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY + groupName );
+            if ( value != null) {
+                return Arrays.asList( StringUtils.split( "," ) );
+            }
+        }
+        throw new MappingException( "Mapping for group " + groupName + " not found" );
+    }
 }
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapGroup.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapGroup.java
new file mode 100644
index 0000000..86e48e2
--- /dev/null
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapGroup.java
@@ -0,0 +1,124 @@
+package org.apache.archiva.redback.common.ldap.role;
+
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Representation of a LDAP group
+ * @author Martin Stockhammer <martin_s@apache.org>
+ * @since 3.0
+ */
+public class LdapGroup
+{
+    String dn = "";
+    String name;
+    String description;
+    List<String> memberList;
+
+    public LdapGroup( )
+    {
+    }
+
+    public LdapGroup( String dn )
+    {
+        this.dn = dn;
+    }
+
+    public LdapGroup( String dn, String name, String displayName, String description )
+    {
+        this.dn = dn;
+        this.name = name;
+        this.description = description;
+    }
+
+    public String getDn( )
+    {
+        return dn;
+    }
+
+    public void setDn( String dn )
+    {
+        this.dn = dn;
+    }
+
+    public String getName( )
+    {
+        return name;
+    }
+
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    public String getDescription( )
+    {
+        return description;
+    }
+
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    public void addMember(String member) {
+        if (this.memberList==null) {
+            this.memberList = new ArrayList<>( );
+        }
+        this.memberList.add( member );
+    }
+
+    public void setMemberList( Collection<String> memberList) {
+        this.memberList = new ArrayList<>( memberList );
+    }
+
+    public List<String> getMemberList() {
+        return memberList;
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        LdapGroup ldapGroup = (LdapGroup) o;
+
+        return dn.equals( ldapGroup.dn );
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        return dn.hashCode( );
+    }
+
+    @Override
+    public String toString( )
+    {
+        final StringBuilder sb = new StringBuilder( "LdapGroup{" );
+        sb.append( "dn='" ).append( dn ).append( '\'' );
+        sb.append( ", name='" ).append( name ).append( '\'' );
+        sb.append( '}' );
+        return sb.toString( );
+    }
+}
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java
index 75fa37e..638f8ce 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java
@@ -43,6 +43,14 @@
         throws MappingException;
 
     /**
+     * read all groups from ldap
+     *
+     * @return all LDAP groups
+     */
+    List<LdapGroup> getAllGroupObjects( DirContext context )
+        throws MappingException;
+
+    /**
      * read all ldap groups then map to corresponding role (if no mapping found group is ignored)
      *
      * @return all roles
@@ -76,6 +84,9 @@
     List<String> getGroups( String username, DirContext context )
         throws MappingException;
 
+    List<LdapGroup> getGroupObjects( String username, DirContext context )
+        throws MappingException;
+
     List<String> getRoles( String username, DirContext context, Collection<String> realRoles )
         throws MappingException;
 
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapperConfiguration.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapperConfiguration.java
index 346db91..edcfb5d 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapperConfiguration.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapperConfiguration.java
@@ -62,6 +62,14 @@
     Map<String, Collection<String>> getLdapGroupMappings()
         throws MappingException;
 
+    /**
+     * Returns the mapping for the given group
+     * @param groupName the group name
+     * @return the list of roles
+     * @throws MappingException
+     */
+    Collection<String> getLdapGroupMapping(String groupName) throws MappingException;
+
     void setLdapGroupMappings( Map<String, List<String>> mappings )
         throws MappingException;
 }
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUser.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUser.java
index 941cf1f..1965ca3 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUser.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUser.java
@@ -31,6 +31,7 @@
 public class LdapUser
     implements User, Serializable
 {
+    private String dn;
 
     private String username;
 
@@ -249,4 +250,13 @@
         return userManagerId;
     }
 
+    public String getDn( )
+    {
+        return dn;
+    }
+
+    public void setDn( String dn )
+    {
+        this.dn = dn;
+    }
 }
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUserMapper.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUserMapper.java
index 5542c36..a2ee149 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUserMapper.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/LdapUserMapper.java
@@ -180,7 +180,7 @@
         return null;
     }
 
-    public LdapUser getUser( Attributes attributes )
+    public LdapUser getUser( String dn, Attributes attributes )
         throws MappingException
     {
         String userIdAttribute = getUserIdAttribute();
@@ -191,6 +191,7 @@
         String userId = LdapUtils.getAttributeValue( attributes, userIdAttribute, "username" );
 
         LdapUser user = new LdapUser( userId );
+        user.setDn( dn );
         user.setOriginalAttributes( attributes );
 
         user.setEmail( LdapUtils.getAttributeValue( attributes, emailAddressAttribute, "email address" ) );
diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/UserMapper.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/UserMapper.java
index 8108c1b..a800970 100644
--- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/UserMapper.java
+++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/user/UserMapper.java
@@ -29,7 +29,7 @@
  */
 public interface UserMapper
 {
-    LdapUser getUser( Attributes attributes )
+    LdapUser getUser( String dn, Attributes attributes )
         throws MappingException;
 
     Attributes getCreationAttributes( User user, boolean encodePasswordIfChanged )
diff --git a/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java b/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
index a231712..0e01af2 100644
--- a/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
+++ b/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
@@ -100,6 +100,8 @@
 
     String LDAP_GROUP_NAME_ATTRIBUTE = "ldap.config.groups.name.attribute";
 
+    String LDAP_GROUP_DESCRIPTION_ATTRIBUTE = "ldap.config.groups.description.attribute";
+
     String APPLICATION_URL = "application.url";
 
     String EMAIL_URL_PATH = "email.url.path";
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
new file mode 100644
index 0000000..9072207
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/Group.java
@@ -0,0 +1,91 @@
+package org.apache.archiva.redback.rest.api.model;
+
+/*
+ * 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 javax.xml.bind.annotation.XmlRootElement;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name="group")
+public class Group
+{
+    String name;
+    String uniqueName;
+    String description;
+    List<String> memberList;
+
+    public Group() {
+
+    }
+
+    public Group( String name )
+    {
+        this.name = name;
+    }
+
+    public String getName( )
+    {
+        return name;
+    }
+
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+    public String getUniqueName( )
+    {
+        return uniqueName;
+    }
+
+    public void setUniqueName( String uniqueName )
+    {
+        this.uniqueName = uniqueName;
+    }
+
+    public String getDescription( )
+    {
+        return description;
+    }
+
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+    public List<String> getMemberList( )
+    {
+        return memberList;
+    }
+
+    public void setMemberList( List<String> memberList )
+    {
+        this.memberList = memberList;
+    }
+
+    public void addMember(String member) {
+        if (this.memberList==null) {
+            this.memberList = new ArrayList<>( );
+        }
+        this.memberList.add( member );
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GroupMapping.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GroupMapping.java
new file mode 100644
index 0000000..4dd1fdc
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GroupMapping.java
@@ -0,0 +1,106 @@
+package org.apache.archiva.redback.rest.api.model;
+/*
+ * 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 javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * @author Olivier Lamy
+ * @since 2.1
+ */
+@XmlRootElement(name = "groupMapping")
+public class GroupMapping
+    implements Serializable
+{
+    private String group;
+
+    private Collection<String> roleNames;
+
+    public GroupMapping()
+    {
+        // no op
+    }
+
+    public GroupMapping( String group, Collection<String> roleNames )
+    {
+        this.group = group;
+        this.roleNames = roleNames;
+    }
+
+    public String getGroup()
+    {
+        return group;
+    }
+
+    public void setGroup( String group )
+    {
+        this.group = group;
+    }
+
+    public Collection<String> getRoleNames()
+    {
+        return roleNames;
+    }
+
+    public void setRoleNames( Collection<String> roleNames )
+    {
+        this.roleNames = roleNames;
+    }
+
+    @Override
+    public String toString()
+    {
+        final StringBuilder sb = new StringBuilder();
+        sb.append( "LdapGroupMapping" );
+        sb.append( "{group='" ).append( group ).append( '\'' );
+        sb.append( ", roleNames=" ).append( roleNames );
+        sb.append( '}' );
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        GroupMapping that = (GroupMapping) o;
+
+        if ( !group.equals( that.group ) )
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return group.hashCode();
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GroupMappingUpdateRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GroupMappingUpdateRequest.java
new file mode 100644
index 0000000..33746a8
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/GroupMappingUpdateRequest.java
@@ -0,0 +1,64 @@
+package org.apache.archiva.redback.rest.api.model;
+
+/*
+ * 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 javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Olivier Lamy
+ */
+@XmlRootElement( name = "groupMappingUpdateRequest" )
+public class GroupMappingUpdateRequest
+    implements Serializable
+{
+
+    private List<GroupMapping> groupMapping;
+
+    public GroupMappingUpdateRequest()
+    {
+        // no op
+    }
+
+    public List<GroupMapping> getGroupMapping()
+    {
+        if ( this.groupMapping == null )
+        {
+            this.groupMapping = new ArrayList<GroupMapping>();
+        }
+        return groupMapping;
+    }
+
+    public void setGroupMapping( List<GroupMapping> groupMapping )
+    {
+        this.groupMapping = groupMapping;
+    }
+
+    @Override
+    public String toString()
+    {
+        final StringBuilder sb = new StringBuilder( "LdapGroupMappingUpdateRequest{" );
+        sb.append( "ldapGroupMapping=" ).append( groupMapping );
+        sb.append( '}' );
+        return sb.toString();
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LdapGroupMappingService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LdapGroupMappingService.java
index fb32616..a0ea6b5 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LdapGroupMappingService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LdapGroupMappingService.java
@@ -43,6 +43,7 @@
  */
 @Path("/ldapGroupMappingService/")
 @Tag( name = "LDAP", description = "LDAP Service" )
+@Deprecated
 public interface LdapGroupMappingService
 {
     @Path("ldapGroups")
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
new file mode 100644
index 0000000..7770791
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/GroupService.java
@@ -0,0 +1,145 @@
+package org.apache.archiva.redback.rest.api.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.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
+import org.apache.archiva.redback.rest.api.model.ActionStatus;
+import org.apache.archiva.redback.rest.api.model.Group;
+import org.apache.archiva.redback.rest.api.model.GroupMapping;
+import org.apache.archiva.redback.rest.api.model.GroupMappingUpdateRequest;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author Olivier Lamy
+ * @since 2.1
+ */
+@Path( "/groups" )
+@Tag( name = "Groups", description = "Groups and Group to Role Mappings" )
+public interface GroupService
+{
+
+    @Path( "" )
+    @GET
+    @Produces( {MediaType.APPLICATION_JSON} )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+    @Operation( summary = "Get list of group objects",
+        responses = {
+            @ApiResponse( description = "List of group objects. The number of returned results depend on the pagination parameters offset and limit." )
+        }
+    )
+    List<Group> getGroups( @QueryParam( "offset" ) @DefaultValue( "0" ) Long offset,
+                           @QueryParam( "limit" ) @DefaultValue( value = Long.MAX_VALUE+"" ) Long limit)
+        throws RedbackServiceException;
+
+
+    @Path( "mappings" )
+    @GET
+    @Produces( {MediaType.APPLICATION_JSON} )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+    @Operation( summary = "Get list of group mappings",
+        responses = {
+            @ApiResponse( description = "List of group mappings" )
+        }
+    )
+    List<GroupMapping> getGroupMappings( )
+        throws RedbackServiceException;
+
+
+    @Path( "mappings" )
+    @POST
+    @Consumes( {MediaType.APPLICATION_JSON} )
+    @Produces( {MediaType.APPLICATION_JSON} )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+    @Operation( summary = "Adds a group mapping",
+        responses = {
+            @ApiResponse( description = "The status of the add action" ),
+            @ApiResponse( responseCode = "405", description = "Invalid input" )
+        }
+    )
+    ActionStatus addGroupMapping( @Parameter( description = "The data of the group mapping", required = true )
+                                          GroupMapping groupMapping )
+        throws RedbackServiceException;
+
+    @Path( "mappings/{group}" )
+    @DELETE
+    @Consumes( {MediaType.APPLICATION_JSON} )
+    @Produces( {MediaType.APPLICATION_JSON} )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+    @Operation( summary = "Deletes a group mapping",
+        responses = {
+            @ApiResponse( description = "The status of the delete action" ),
+            @ApiResponse( responseCode = "404", description = "Group mapping not found" )
+        }
+    )
+    ActionStatus removeGroupMapping( @Parameter( description = "The group name", required = true )
+                                         @PathParam( "group" ) String group )
+        throws RedbackServiceException;
+
+    @Path( "mappings/{group}" )
+    @PUT
+    @Consumes( {MediaType.APPLICATION_JSON} )
+    @Produces( {MediaType.APPLICATION_JSON} )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+    @Operation( summary = "Updates a group mapping",
+        responses = {
+            @ApiResponse( description = "The status of the update action" ),
+            @ApiResponse( responseCode = "404", description = "Group mapping not found" )
+        }
+    )
+    ActionStatus updateGroupMapping( @Parameter( description = "The group name", required = true )
+                                         @PathParam( "group" ) String groupName,
+                                     @Parameter( description = "The updated data of the group mapping", required = true )
+                                             GroupMapping groupMapping )
+        throws RedbackServiceException;
+
+
+    @Path( "mappings" )
+    @PUT
+    @Consumes( {MediaType.APPLICATION_JSON} )
+    @Produces( {MediaType.APPLICATION_JSON} )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.CONFIGURATION_EDIT_OPERATION )
+    @Operation( summary = "Updates a multiple group mappings",
+        responses = {
+            @ApiResponse( description = "The status of the update action" ),
+            @ApiResponse( responseCode = "405", description = "Invalid input" )
+        }
+    )
+    ActionStatus updateGroupMapping( @Parameter( description = "The list of group mapping updates", required = true )
+                                         GroupMappingUpdateRequest groupMappingUpdateRequest )
+        throws RedbackServiceException;
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/LoginService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/LoginService.java
new file mode 100644
index 0000000..ad77713
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/LoginService.java
@@ -0,0 +1,98 @@
+package org.apache.archiva.redback.rest.api.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 org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.redback.rest.api.model.ActionStatus;
+import org.apache.archiva.redback.rest.api.model.AuthenticationKeyResult;
+import org.apache.archiva.redback.rest.api.model.LoginRequest;
+import org.apache.archiva.redback.rest.api.model.PingResult;
+import org.apache.archiva.redback.rest.api.model.User;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+@Path( "/auth" )
+public interface LoginService
+{
+
+    @Path( "requestkey" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true )
+    AuthenticationKeyResult addAuthenticationKey( @QueryParam( "providerKey" ) String providedKey,
+                                                  @QueryParam( "principal" ) String principal, @QueryParam( "purpose" ) String purpose,
+                                                  @QueryParam( "expirationMinutes" ) int expirationMinutes )
+        throws RedbackServiceException;
+
+
+    @Path( "ping" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true )
+    PingResult ping()
+        throws RedbackServiceException;
+
+
+    @Path( "ping/authenticated" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = false, noPermission = true )
+    PingResult pingWithAutz()
+        throws RedbackServiceException;
+
+    /**
+     * check username/password and create a http session.
+     * So no more need of reuse username/password for all ajaxRequest
+     */
+    @Path( "authenticate" )
+    @POST
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    User logIn( LoginRequest loginRequest )
+        throws RedbackServiceException;
+
+    /**
+     * simply check if current user has an http session opened with authz passed and return user data
+     * @since 1.4
+     */
+    @Path( "isAuthenticated" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( noRestriction = true )
+    User isLogged()
+        throws RedbackServiceException;
+
+    /**
+     * clear user http session
+     * @since 1.4
+     */
+    @Path( "logout" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    ActionStatus logout()
+        throws RedbackServiceException;
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/PasswordService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/PasswordService.java
new file mode 100644
index 0000000..57e51c9
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/PasswordService.java
@@ -0,0 +1,66 @@
+package org.apache.archiva.redback.rest.api.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 org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.redback.rest.api.model.User;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4
+ */
+@Path( "/passwordService/" )
+public interface PasswordService
+{
+
+    /**
+     * used to change the password on first user connection after registration use.
+     * the key is mandatory and a control will be done on the username provided.
+     * <b>need to be logged by {@link UserService#validateUserFromKey(String)}</b>
+     * @return username
+     */
+    @GET
+    @Path( "changePasswordWithKey" )
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    User changePasswordWithKey( @QueryParam( "password" ) String password,
+                                  @QueryParam( "passwordConfirmation" ) String passwordConfirmation,
+                                  @QueryParam( "key" ) String key )
+        throws RedbackServiceException;
+
+    /**
+     * used to change the password on passwordChangeRequired state.
+     */
+    @GET
+    @Path( "changePassword" )
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    User changePassword( @QueryParam( "userName" ) String userName,
+                            @QueryParam( "previousPassword" ) String previousPassword,
+                            @QueryParam( "password" ) String password,
+                            @QueryParam( "passwordConfirmation" ) String passwordConfirmation )
+        throws RedbackServiceException;
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleManagementService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleManagementService.java
new file mode 100644
index 0000000..5aafa08
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleManagementService.java
@@ -0,0 +1,310 @@
+package org.apache.archiva.redback.rest.api.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 org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
+import org.apache.archiva.redback.rest.api.model.ActionStatus;
+import org.apache.archiva.redback.rest.api.model.Application;
+import org.apache.archiva.redback.rest.api.model.ApplicationRoles;
+import org.apache.archiva.redback.rest.api.model.AvailabilityStatus;
+import org.apache.archiva.redback.rest.api.model.Role;
+import org.apache.archiva.redback.rest.api.model.User;
+import org.apache.archiva.redback.rest.api.model.VerificationStatus;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author Olivier Lamy
+ */
+@Path( "/roleManagementService/" )
+public interface RoleManagementService
+{
+
+    @Path( "createTemplatedRole" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus createTemplatedRole( @QueryParam( "templateId" ) String templateId,
+                                      @QueryParam( "resource" ) String resource )
+        throws RedbackServiceException;
+
+    /**
+     * removes a role corresponding to the role Id that was manufactured with the given resource
+     *
+     * it also removes any user assignments for that role
+     *
+     * @param templateId
+     * @param resource
+     */
+    @Path( "removeTemplatedRole" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus removeTemplatedRole( @QueryParam( "templateId" ) String templateId,
+                                 @QueryParam( "resource" ) String resource )
+        throws RedbackServiceException;
+
+
+    /**
+     * allows for a role coming from a template to be renamed effectively swapping out the bits of it that
+     * were labeled with the oldResource with the newResource
+     *
+     * it also manages any user assignments for that role
+     *
+     * @param templateId
+     * @param oldResource
+     * @param newResource
+     */
+    @Path( "updateRole" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus updateRole( @QueryParam( "templateId" ) String templateId, @QueryParam( "oldResource" ) String oldResource,
+                        @QueryParam( "newResource" ) String newResource )
+        throws RedbackServiceException;
+
+
+    /**
+     * Assigns the role indicated by the roleId to the given principal
+     *
+     * @param roleId
+     * @param principal
+     */
+    @Path( "assignRole" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus assignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal )
+        throws RedbackServiceException;
+
+    /**
+     * Assigns the role indicated by the roleName to the given principal
+     *
+     * @param roleName
+     * @param principal
+     * @throws RedbackServiceException
+     */
+    @Path( "assignRoleByName" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus assignRoleByName( @QueryParam( "roleName" ) String roleName, @QueryParam( "principal" ) String principal )
+        throws RedbackServiceException;
+
+    /**
+     * Assigns the templated role indicated by the templateId
+     *
+     * fails if the templated role has not been created
+     *
+     * @param templateId
+     * @param resource
+     * @param principal
+     */
+    @Path( "assignTemplatedRole" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus assignTemplatedRole( @QueryParam( "templateId" ) String templateId,
+                                 @QueryParam( "resource" ) String resource,
+                                 @QueryParam( "principal" ) String principal )
+        throws RedbackServiceException;
+
+    /**
+     * Unassigns the role indicated by the role id from the given principal
+     *
+     * @param roleId
+     * @param principal
+     * @throws RedbackServiceException
+     */
+    @Path( "unassignRole" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus unassignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal )
+        throws RedbackServiceException;
+
+    /**
+     * Unassigns the role indicated by the role name from the given principal
+     *
+     * @param roleName
+     * @param principal
+     * @throws RedbackServiceException
+     */
+    @Path( "unassignRoleByName" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus unassignRoleByName( @QueryParam( "roleName" ) String roleName, @QueryParam( "principal" ) String principal )
+        throws RedbackServiceException;
+
+    /**
+     * true of a role exists with the given roleId
+     *
+     * @param roleId
+     * @return
+     * @throws RedbackServiceException
+     */
+    @Path( "roleExists" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    AvailabilityStatus roleExists( @QueryParam( "roleId" ) String roleId )
+        throws RedbackServiceException;
+
+    /**
+     * true of a role exists with the given roleId
+     *
+     * @param templateId
+     * @param resource
+     * @return
+     * @throws RedbackServiceException
+     */
+    @Path( "templatedRoleExists" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    AvailabilityStatus templatedRoleExists( @QueryParam( "templateId" ) String templateId,
+                                 @QueryParam( "resource" ) String resource )
+        throws RedbackServiceException;
+
+
+    /**
+     * Check a role template is complete in the RBAC store.
+     *
+     * @param templateId the templated role
+     * @param resource   the resource to verify
+     * @throws RedbackServiceException
+     */
+    @Path( "verifyTemplatedRole" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    VerificationStatus verifyTemplatedRole( @QueryParam( "templateId" ) String templateId,
+                                            @QueryParam( "resource" ) String resource )
+        throws RedbackServiceException;
+
+    /**
+     * @since 1.4
+     */
+    @Path( "getEffectivelyAssignedRoles/{username}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    List<Role> getEffectivelyAssignedRoles( @PathParam( "username" ) String username )
+        throws RedbackServiceException;
+
+
+    /**
+     * @since 2.0
+     */
+    @Path( "allRoles" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    List<Role> getAllRoles()
+        throws RedbackServiceException;
+
+    /**
+     * @since 2.0
+     */
+    @Path( "detailledAllRoles" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    List<Role> getDetailedAllRoles()
+        throws RedbackServiceException;
+
+
+    /**
+     * @since 2.0
+     */
+    @Path( "getApplications/{username}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    List<Application> getApplications( @PathParam( "username" ) String username )
+        throws RedbackServiceException;
+
+
+    /**
+     * @since 2.0
+     */
+    @Path( "getRole/{roleName}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    Role getRole( @PathParam( "roleName" ) String roleName )
+        throws RedbackServiceException;
+
+    /**
+     * @since 2.0
+     */
+    @Path( "updateRoleDescription" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus updateRoleDescription( @QueryParam( "roleName" ) String roleName,
+                                   @QueryParam( "roleDescription" ) String description )
+        throws RedbackServiceException;
+
+    /**
+     * update users assigned to a role
+     * @since 2.0
+     */
+    @Path( "updateRoleUsers" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus updateRoleUsers( Role role )
+        throws RedbackServiceException;
+
+    /**
+     * @since 2.0
+     */
+    @Path( "getApplicationRoles/{username}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    List<ApplicationRoles> getApplicationRoles( @PathParam( "username" ) String username )
+        throws RedbackServiceException;
+
+    /**
+     * update roles assigned to a user
+     * @since 2.0
+     */
+    @Path( "updateUserRoles" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION )
+    ActionStatus updateUserRoles( User user )
+        throws RedbackServiceException;
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
new file mode 100644
index 0000000..34a0ed7
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java
@@ -0,0 +1,258 @@
+package org.apache.archiva.redback.rest.api.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 org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
+import org.apache.archiva.redback.rest.api.model.ActionStatus;
+import org.apache.archiva.redback.rest.api.model.AvailabilityStatus;
+import org.apache.archiva.redback.rest.api.model.Operation;
+import org.apache.archiva.redback.rest.api.model.PasswordStatus;
+import org.apache.archiva.redback.rest.api.model.Permission;
+import org.apache.archiva.redback.rest.api.model.PingResult;
+import org.apache.archiva.redback.rest.api.model.RegistrationKey;
+import org.apache.archiva.redback.rest.api.model.ResetPasswordRequest;
+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.model.VerificationStatus;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.Collection;
+import java.util.List;
+
+@Path( "/userService/" )
+public interface UserService
+{
+    @Path( "getUser/{userName}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    User getUser( @PathParam( "userName" ) String username )
+        throws RedbackServiceException;
+
+
+    @Path( "getUsers" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_LIST_OPERATION )
+    List<User> getUsers()
+        throws RedbackServiceException;
+
+    @Path( "createUser" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_CREATE_OPERATION )
+    ActionStatus createUser( User user )
+        throws RedbackServiceException;
+
+
+    /**
+     * will create admin user only if not exists !! if exists will return false
+     */
+    @Path( "createAdminUser" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( noRestriction = true )
+    ActionStatus createAdminUser( User user )
+        throws RedbackServiceException;
+
+    @Path( "isAdminUserExists" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true )
+    AvailabilityStatus isAdminUserExists()
+        throws RedbackServiceException;
+
+
+    @Path( "deleteUser/{userName}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_DELETE_OPERATION )
+    ActionStatus deleteUser( @PathParam( "userName" ) String username )
+        throws RedbackServiceException;
+
+    @Path( "updateUser" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    ActionStatus updateUser( User user )
+        throws RedbackServiceException;
+
+    /**
+     * @since 2.0
+     */
+    @Path( "lockUser/{username}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    ActionStatus lockUser( @PathParam( "username" ) String username )
+        throws RedbackServiceException;
+
+    /**
+     * @since 2.0
+     */
+    @Path( "unlockUser/{username}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    ActionStatus unlockUser( @PathParam( "username" ) String username )
+        throws RedbackServiceException;
+
+
+    /**
+     * @since 2.0
+     */
+    @Path( "passwordChangeRequired/{username}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    PasswordStatus passwordChangeRequired( @PathParam( "username" ) String username )
+        throws RedbackServiceException;
+
+    /**
+     * update only the current user and this fields: fullname, email, password.
+     * the service verify the curent logged user with the one passed in the method
+     * @since 1.4
+     */
+    @Path( "updateMe" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = false, noPermission = true )
+    ActionStatus updateMe( User user )
+        throws RedbackServiceException;
+
+    @Path( "ping" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true )
+    PingResult ping()
+        throws RedbackServiceException;
+
+    @Path( "removeFromCache/{userName}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    ActionStatus removeFromCache( @PathParam( "userName" ) String username )
+        throws RedbackServiceException;
+
+    @Path( "getGuestUser" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    User getGuestUser()
+        throws RedbackServiceException;
+
+    @Path( "createGuestUser" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_EDIT_OPERATION )
+    User createGuestUser()
+        throws RedbackServiceException;
+
+    /**
+     * if redback is not configured for email validation is required, -1 is returned as key
+     * @since 1.4
+     */
+    @Path( "registerUser" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    RegistrationKey registerUser( UserRegistrationRequest userRegistrationRequest )
+        throws RedbackServiceException;
+
+
+    /**
+     * validate the key and the user with forcing a password change for next login.
+     * http session is created.
+     * @param key authentication key
+     * @since 1.4
+     */
+    @Path( "validateKey/{key}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    VerificationStatus validateUserFromKey( @PathParam( "key" ) String key )
+        throws RedbackServiceException;
+
+    /**
+     *
+     * @param resetPasswordRequest contains username for send a password reset email
+     * @since 1.4
+     */
+    @Path( "resetPassword" )
+    @POST
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    ActionStatus resetPassword( ResetPasswordRequest resetPasswordRequest )
+        throws RedbackServiceException;
+
+    /**
+     * @since 1.4
+     */
+    @Path( "getUserPermissions/{userName}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_LIST_OPERATION )
+    Collection<Permission> getUserPermissions( @PathParam( "userName" ) String userName )
+        throws RedbackServiceException;
+
+    /**
+     * @since 1.4
+     */
+    @Path( "getUserOperations/{userName}" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_LIST_OPERATION )
+    Collection<Operation> getUserOperations( @PathParam( "userName" ) String userName )
+        throws RedbackServiceException;
+
+    /**
+     * @return  the current logged user permissions, if no logged user guest permissions are returned
+     * @since 1.4
+     */
+    @Path( "getCurrentUserPermissions" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    Collection<Permission> getCurrentUserPermissions()
+        throws RedbackServiceException;
+
+    /**
+     * @return the current logged user operations, if no logged user guest operations are returned
+     * @since 1.4
+     */
+    @Path( "getCurrentUserOperations" )
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true, noPermission = true )
+    Collection<Operation> getCurrentUserOperations()
+        throws RedbackServiceException;
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UtilServices.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UtilServices.java
new file mode 100644
index 0000000..b8ffe38
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UtilServices.java
@@ -0,0 +1,57 @@
+package org.apache.archiva.redback.rest.api.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 org.apache.archiva.redback.authorization.RedbackAuthorization;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.Properties;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4
+ */
+@Path( "/utilServices/" )
+public interface UtilServices
+{
+
+    @Path( "getBundleResources" )
+    @GET
+    @Produces( { MediaType.TEXT_PLAIN } )
+    @RedbackAuthorization( noRestriction = true )
+    String getI18nResources( @QueryParam( "locale" ) String locale )
+        throws RedbackServiceException;
+
+    /**
+     * <b>not intended to be exposed as a REST service.</b>
+     * will load i18N resource org/apache/archiva/redback/users/messages in default en then in the asked locale.
+     * @param locale
+     * @return
+     * @throws RedbackServiceException
+     */
+    Properties getI18nProperties( String locale )
+        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 e4a8a0c..ff554b3 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
@@ -25,6 +25,7 @@
 import org.apache.archiva.redback.common.ldap.role.LdapRoleMapper;
 import org.apache.archiva.redback.common.ldap.role.LdapRoleMapperConfiguration;
 import org.apache.archiva.redback.rest.api.model.ActionStatus;
+import org.apache.archiva.redback.rest.api.model.Group;
 import org.apache.archiva.redback.rest.api.model.LdapGroupMapping;
 import org.apache.archiva.redback.rest.api.model.LdapGroupMappingUpdateRequest;
 import org.apache.archiva.redback.rest.api.model.StringList;
@@ -42,6 +43,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * @author Olivier Lamy
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
new file mode 100644
index 0000000..f6428df
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java
@@ -0,0 +1,235 @@
+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 org.apache.archiva.redback.common.ldap.MappingException;
+import org.apache.archiva.redback.common.ldap.connection.LdapConnection;
+import org.apache.archiva.redback.common.ldap.connection.LdapConnectionFactory;
+import org.apache.archiva.redback.common.ldap.connection.LdapException;
+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.rest.api.model.ActionStatus;
+import org.apache.archiva.redback.rest.api.model.Group;
+import org.apache.archiva.redback.rest.api.model.GroupMapping;
+import org.apache.archiva.redback.rest.api.model.GroupMappingUpdateRequest;
+import org.apache.archiva.redback.rest.api.model.StringList;
+import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
+import org.apache.archiva.redback.rest.api.services.v2.GroupService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * LDAP implementation of the group service
+ *
+ * @author Olivier Lamy
+ * @author Martin Stockhammer
+ * @since 3.0
+ */
+@Service("v2.groupService#rest")
+public class DefaultGroupService
+    implements GroupService
+{
+    private final Logger log = LoggerFactory.getLogger( getClass() );
+
+    @Inject
+    @Named(value = "ldapRoleMapper#default")
+    private LdapRoleMapper ldapRoleMapper;
+
+    @Inject
+    @Named(value = "ldapRoleMapperConfiguration#default")
+    private LdapRoleMapperConfiguration ldapRoleMapperConfiguration;
+
+    @Inject
+    @Named(value = "ldapConnectionFactory#configurable")
+    private LdapConnectionFactory ldapConnectionFactory;
+
+    private static final Group getGroupFromLdap( LdapGroup ldapGroup ) {
+        Group group = new Group( );
+        group.setName( ldapGroup.getName() );
+        group.setUniqueName( ldapGroup.getDn() );
+        group.setDescription( ldapGroup.getDescription() );
+        group.setMemberList( ldapGroup.getMemberList() );
+        return group;
+    }
+
+    @Override
+    public List<Group> getGroups( Long offset, Long limit ) throws RedbackServiceException
+    {
+        LdapConnection ldapConnection = null;
+
+        DirContext context = null;
+
+        try
+        {
+            ldapConnection = ldapConnectionFactory.getConnection();
+            context = ldapConnection.getDirContext();
+            return ldapRoleMapper.getAllGroupObjects( context ).stream( ).skip( offset ).limit( limit ).map( DefaultGroupService::getGroupFromLdap ).collect( Collectors.toList( ) );
+        }
+        catch ( LdapException | MappingException e )
+        {
+            log.error( e.getMessage(), e );
+            throw new RedbackServiceException( e.getMessage() );
+        }
+        finally
+        {
+            closeContext( context );
+            closeLdapConnection( ldapConnection );
+        }
+    }
+
+    @Override
+    public List<GroupMapping> getGroupMappings()
+        throws RedbackServiceException
+    {
+        try
+        {
+            Map<String, Collection<String>> map = ldapRoleMapperConfiguration.getLdapGroupMappings();
+            List<GroupMapping> ldapGroupMappings = new ArrayList<>( map.size( ) );
+            for ( Map.Entry<String, Collection<String>> entry : map.entrySet() )
+            {
+                GroupMapping ldapGroupMapping = new GroupMapping( entry.getKey(), entry.getValue() );
+                ldapGroupMappings.add( ldapGroupMapping );
+            }
+
+            return ldapGroupMappings;
+        }
+        catch ( MappingException e )
+        {
+            log.error( e.getMessage(), e );
+            throw new RedbackServiceException( e.getMessage() );
+        }
+    }
+
+    @Override
+    public ActionStatus addGroupMapping( GroupMapping ldapGroupMapping )
+        throws RedbackServiceException
+    {
+        try
+        {
+            ldapRoleMapperConfiguration.addLdapMapping( ldapGroupMapping.getGroup(),
+                                                        new ArrayList<>( ldapGroupMapping.getRoleNames() ) );
+        }
+        catch ( MappingException e )
+        {
+            log.error( e.getMessage(), e );
+            throw new RedbackServiceException( e.getMessage() );
+        }
+        return ActionStatus.SUCCESS;
+    }
+
+    @Override
+    public ActionStatus removeGroupMapping( String group )
+        throws RedbackServiceException
+    {
+        try
+        {
+            ldapRoleMapperConfiguration.removeLdapMapping( group );
+        }
+        catch ( MappingException e )
+        {
+            log.error( e.getMessage(), e );
+            throw new RedbackServiceException( e.getMessage() );
+        }
+        return ActionStatus.SUCCESS;
+    }
+
+    @Override
+    public ActionStatus updateGroupMapping( String groupName, GroupMapping groupMapping ) throws RedbackServiceException
+    {
+        try
+        {
+            ldapRoleMapperConfiguration.getLdapGroupMapping( groupName );
+        }
+        catch ( MappingException e )
+        {
+            throw new RedbackServiceException( "Group mapping not found ", 404 );
+        }
+        try
+        {
+            ldapRoleMapperConfiguration.updateLdapMapping( groupName,
+                new ArrayList<>( groupMapping.getRoleNames() ) );
+            return ActionStatus.SUCCESS;
+        }
+        catch ( MappingException e )
+        {
+            log.error( "Could not update mapping {}", e.getMessage( ) );
+            throw new RedbackServiceException( e.getMessage( ) );
+        }
+    }
+
+    @Override
+    public ActionStatus updateGroupMapping( GroupMappingUpdateRequest groupMappingUpdateRequest )
+        throws RedbackServiceException
+    {
+        try
+        {
+            for ( GroupMapping ldapGroupMapping : groupMappingUpdateRequest.getGroupMapping() )
+            {
+                ldapRoleMapperConfiguration.updateLdapMapping( ldapGroupMapping.getGroup(),
+                                                               new ArrayList<>( ldapGroupMapping.getRoleNames() ) );
+            }
+        }
+        catch ( MappingException e )
+        {
+            log.error( e.getMessage(), e );
+            throw new RedbackServiceException( e.getMessage() );
+        }
+        return ActionStatus.SUCCESS;
+    }
+
+    //------------------
+    // utils
+    //------------------
+
+    protected void closeLdapConnection( LdapConnection ldapConnection )
+    {
+        if ( ldapConnection != null )
+        {
+            ldapConnection.close();
+        }
+    }
+
+    protected void closeContext( DirContext context )
+    {
+        if ( context != null )
+        {
+            try
+            {
+                context.close();
+            }
+            catch ( NamingException e )
+            {
+                log.warn( "skip issue closing context: {}", e.getMessage() );
+            }
+        }
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
index 8e0441c..8850ee3 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
@@ -74,4 +74,26 @@
     </jaxrs:providers>
    </jaxrs:server>
 
+  <jaxrs:server address="/redback/v2">
+    <jaxrs:serviceBeans>
+      <ref bean="userService#rest"/>
+      <ref bean="loginService#rest"/>
+      <ref bean="roleManagementService#rest"/>
+      <ref bean="utilServices#rest"/>
+      <ref bean="passwordService#rest"/>
+      <ref bean="v2.groupService#rest"/>
+    </jaxrs:serviceBeans>
+
+    <jaxrs:providers>
+      <ref bean="jsonProvider"/>
+      <ref bean="xmlProvider"/>
+      <ref bean="authenticationInterceptor#rest"/>
+      <ref bean="permissionInterceptor#rest"/>
+      <ref bean="redbackServiceExceptionMapper"/>
+      <ref bean="passwordRuleViolationExceptionMapper"/>
+      <ref bean="requestValidationInterceptor#rest" />
+      <ref bean="threadLocalUserCleaner#rest"/>
+    </jaxrs:providers>
+  </jaxrs:server>
+
 </beans>
\ No newline at end of file
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
new file mode 100644
index 0000000..88c25de
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/GroupServiceTest.java
@@ -0,0 +1,273 @@
+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.components.apacheds.ApacheDs;
+import org.apache.archiva.redback.rest.api.model.GroupMapping;
+import org.apache.archiva.redback.rest.api.services.v2.GroupService;
+import org.apache.archiva.redback.rest.services.AbstractRestServicesTest;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.assertj.core.api.Condition;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+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 javax.ws.rs.core.MediaType;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Olivier Lamy
+ */
+@RunWith( SpringJUnit4ClassRunner.class )
+@ContextConfiguration(
+    locations = { "classpath:/ldap-spring-test.xml" } )
+@DirtiesContext( classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD )
+public class GroupServiceTest
+    extends AbstractRestServicesTest
+{
+
+    @Inject
+    @Named( value = "apacheDS#test" )
+    private ApacheDs apacheDs;
+
+    List<String> groups =
+        Arrays.asList( "Archiva System Administrator", "Internal Repo Manager", "Internal Repo Observer" );
+
+    private String suffix;
+
+    private String groupSuffix;
+
+    protected GroupService getGroupService( String authzHeader )
+    {
+        GroupService service =
+            JAXRSClientFactory.create( "http://localhost:" + getServerPort() + "/" + getRestServicesPath() + "/redback/v2/",
+                GroupService.class,
+                Collections.singletonList( new JacksonJaxbJsonProvider() ) );
+
+        // 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;
+    }
+
+    @Override
+    protected String getSpringConfigLocation()
+    {
+        return "classpath*:spring-context.xml,classpath*:META-INF/spring-context.xml,classpath:/ldap-spring-test.xml";
+    }
+
+    @Override
+    public void startServer()
+        throws Exception
+    {
+        super.startServer();
+
+        groupSuffix = apacheDs.addSimplePartition( "test", new String[]{ "archiva", "apache", "org" } ).getSuffix();
+
+        log.info( "groupSuffix: {}", groupSuffix );
+
+        suffix = "ou=People,dc=archiva,dc=apache,dc=org";
+
+        log.info( "DN Suffix: {}", suffix );
+
+        apacheDs.startServer();
+
+        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( suffix, attributes );
+
+        createGroups();
+    }
+
+    @Override
+    public void stopServer()
+        throws Exception
+    {
+
+        // cleanup ldap entries
+        InitialDirContext context = apacheDs.getAdminContext();
+
+        for ( String group : this.groups )
+        {
+            context.unbind( createGroupDn( group ) );
+        }
+
+        context.unbind( suffix );
+
+        context.close();
+
+        apacheDs.stopServer();
+
+        super.stopServer();
+    }
+
+    private void createGroups()
+        throws Exception
+    {
+        InitialDirContext context = apacheDs.getAdminContext();
+
+        for ( String group : groups )
+        {
+            createGroup( context, group, createGroupDn( group ) );
+        }
+
+    }
+
+    private void createGroup( DirContext context, String groupName, String dn )
+        throws Exception
+    {
+
+        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," + suffix );
+
+        attributes.put( basicAttribute );
+        context.createSubcontext( dn, attributes );
+    }
+
+    private String createGroupDn( String cn )
+    {
+        return "cn=" + cn + "," + groupSuffix;
+    }
+
+    @Test
+    public void getAllGroups()
+        throws Exception
+    {
+
+        try
+        {
+            GroupService service = getGroupService( authorizationHeader );
+
+            List<String> allGroups = service.getGroups( Long.valueOf( 0 ), Long.valueOf( Long.MAX_VALUE ) ).stream( ).map( group -> group.getName( ) ).collect( Collectors.toList( ) );
+
+            assertThat( allGroups ).isNotNull().isNotEmpty().hasSize( 3 ).containsAll( groups );
+        }
+        catch ( Exception e )
+        {
+            log.error( e.getMessage(), e );
+            throw e;
+        }
+    }
+
+    @Test
+    public void getGroupMappings()
+        throws Exception
+    {
+        try
+        {
+            GroupService service = getGroupService( authorizationHeader );
+
+            List<GroupMapping> mappings = service.getGroupMappings();
+
+            assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 3 );
+        }
+        catch ( Exception e )
+        {
+            log.error( e.getMessage(), e );
+            throw e;
+        }
+    }
+
+    @Test
+    public void addThenRemove()
+        throws Exception
+    {
+        try
+        {
+            GroupService service = getGroupService( authorizationHeader );
+
+            List<GroupMapping> mappings = service.getGroupMappings();
+
+            assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 3 );
+
+            GroupMapping groupMapping = new GroupMapping( "ldap group", Arrays.asList( "redback role" ) );
+
+            service.addGroupMapping( groupMapping );
+
+            mappings = service.getGroupMappings();
+
+            assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 4 ).are(
+                new Condition<GroupMapping>()
+                {
+                    @Override
+                    public boolean matches( GroupMapping mapping )
+                    {
+                        if ( StringUtils.equals( "ldap group", mapping.getGroup() ) )
+                        {
+                            assertThat( mapping.getRoleNames() ).isNotNull().isNotEmpty().containsOnly(
+                                "redback role" );
+                            return true;
+                        }
+
+                        return true;
+                    }
+                } );
+
+            service.removeGroupMapping( "ldap group" );
+
+            mappings = service.getGroupMappings();
+
+            assertThat( mappings ).isNotNull().isNotEmpty().hasSize( 3 );
+        }
+        catch ( Exception e )
+        {
+            log.error( e.getMessage(), e );
+            throw e;
+        }
+    }
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/ctl/DefaultLdapController.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/ctl/DefaultLdapController.java
index 90b0a58..efa4cee 100644
--- a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/ctl/DefaultLdapController.java
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/apache/archiva/redback/users/ldap/ctl/DefaultLdapController.java
@@ -204,7 +204,7 @@
             {
                 SearchResult result = results.nextElement();
 
-                users.add( mapper.getUser( result.getAttributes() ) );
+                users.add( mapper.getUser( result.getNameInNamespace(), result.getAttributes() ) );
             }
 
             return users;
@@ -247,7 +247,7 @@
             {
                 SearchResult result = results.nextElement();
 
-                users.add( mapper.getUser( result.getAttributes() ) );
+                users.add( mapper.getUser( result.getNameInNamespace(), result.getAttributes() ) );
             }
 
             return users;
@@ -360,7 +360,7 @@
 
                 log.info( "Found user: {}", username );
 
-                return mapper.getUser( next.getAttributes() );
+                return mapper.getUser( next.getNameInNamespace(), next.getAttributes() );
             }
             else
             {