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
{