[MRM-2008] Fix for group names with slashes

Changing the group name retrieval to attribute read. Using CompositeName and
LdapName to retrieve the result. Slashes are treated special in JNDI.
This commit is contained in:
Martin Stockhammer 2020-01-24 21:58:09 +01:00
parent cd9334ce74
commit 4a98784031
5 changed files with 275 additions and 179 deletions

View File

@ -19,10 +19,14 @@
* under the License.
*/
import javax.naming.CompositeName;
import javax.naming.InvalidNameException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
/**
*
@ -129,4 +133,43 @@ public static String getAttributeValueFromByteArray( Attributes attributes, Stri
return null;
}
/**
* Returns a LDAP name from a given RDN string. The <code>name</code> parameter must be a string
* representation of a composite name (as returned by ldapsearch result getName())
* @param name The string of the RDN (may be escaped)
* @return The LdapName that corresponds to this string
* @throws InvalidNameException If the string cannot be parsed as LDAP name
*/
public static LdapName getLdapNameFromString(final String name) throws InvalidNameException
{
CompositeName coName = new CompositeName( name );
LdapName ldapName = new LdapName( "" );
ldapName.addAll( coName );
return ldapName;
}
/**
* Returns the first RDN value that matches the given type.
* E.g. for the RDN ou=People,dc=test,dc=de, and type dc it will return 'test'.
*
* @param name the ldap name
* @param type the type of the RDN entry
* @return
*/
public static String findFirstRdnValue(LdapName name, String type) {
for ( Rdn rdn : name.getRdns() )
{
if ( rdn.getType( ).equals( type ) )
{
Object val = rdn.getValue( );
if (val!=null) {
return val.toString( );
} else {
return "";
}
}
}
return "";
}
}

View File

@ -48,6 +48,7 @@
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.util.ArrayList;
import java.util.Collection;
@ -96,7 +97,7 @@ public class DefaultLdapRoleMapper
private String baseDn;
private String ldapGroupMember = "uniqueMember";
private String ldapGroupMemberAttribute = "uniqueMember";
private boolean useDefaultRoleName = false;
@ -106,13 +107,28 @@ public class DefaultLdapRoleMapper
* possible to user cn=beer or uid=beer or sn=beer etc
* so make it configurable
*/
private String userIdAttribute = "uid";
public static String DEFAULT_USER_ID_ATTRIBUTE = "uid";
private String userIdAttribute = DEFAULT_USER_ID_ATTRIBUTE;
public static String DEFAULT_GROUP_NAME_ATTRIBUTE = "cn";
private String groupNameAttribute = DEFAULT_GROUP_NAME_ATTRIBUTE;
// True, if the member attribute stores the DN, otherwise the userkey is used as entry value
private boolean useDnAsMemberValue = true;
private static final String POSIX_GROUP = "posixGroup";
@PostConstruct
public void initialize( )
{
this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass );
if (StringUtils.equalsIgnoreCase( POSIX_GROUP, this.ldapGroupClass )) {
this.useDnAsMemberValue = false;
}
this.useDnAsMemberValue = userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_DN_AS_MEMBER_VALUE, this.useDnAsMemberValue );
this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, this.baseDn );
this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn );
@ -127,11 +143,30 @@ public void initialize()
this.useDefaultRoleName =
userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_ROLENAME, this.useDefaultRoleName );
this.userIdAttribute = userConf.getString( UserConfigurationKeys.LDAP_USER_ID_ATTRIBUTE, this.userIdAttribute );
this.userIdAttribute = userConf.getString( UserConfigurationKeys.LDAP_USER_ID_ATTRIBUTE, DEFAULT_USER_ID_ATTRIBUTE );
this.ldapGroupMember = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_MEMBER, this.ldapGroupMember );
this.ldapGroupMemberAttribute = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_MEMBER, this.ldapGroupMemberAttribute );
this.dnAttr = userConf.getString( UserConfigurationKeys.LDAP_DN_ATTRIBUTE, this.dnAttr );
this.groupNameAttribute = userConf.getString( UserConfigurationKeys.LDAP_GROUP_NAME_ATTRIBUTE, DEFAULT_GROUP_NAME_ATTRIBUTE );
}
private String getGroupNameFromResult( SearchResult searchResult ) throws NamingException
{
Attribute gNameAtt = searchResult.getAttributes( ).get( groupNameAttribute );
if ( gNameAtt != null )
{
return gNameAtt.get( ).toString( );
}
else
{
log.error( "Could not get group name from attribute {}. Group DN: {}", groupNameAttribute, searchResult.getNameInNamespace( ) );
return "";
}
}
public List<String> getAllGroups( DirContext context )
@ -146,6 +181,7 @@ public List<String> getAllGroups( DirContext context )
searchControls.setDerefLinkFlag( true );
searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
searchControls.setReturningAttributes( new String[]{ this.getLdapDnAttribute(), "objectClass", groupNameAttribute} );
String filter = "objectClass=" + getLdapGroupClass( );
@ -161,15 +197,12 @@ public List<String> getAllGroups( DirContext context )
while ( namingEnumeration.hasMore( ) )
{
SearchResult searchResult = namingEnumeration.next( );
String groupName = searchResult.getName();
// cn=blabla we only want bla bla
groupName = StringUtils.substringAfter( groupName, "=" );
log.debug( "found groupName: '{}", groupName );
String groupName = getGroupNameFromResult( searchResult );
if ( StringUtils.isNotEmpty( groupName ) )
{
log.debug( "Found groupName: '{}", groupName );
allGroups.add( groupName );
}
}
return allGroups;
@ -232,7 +265,7 @@ public boolean hasRole( DirContext context, String roleName )
String filter = "objectClass=" + getLdapGroupClass( );
namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
namingEnumeration = context.search( groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), filter, searchControls );
return namingEnumeration.hasMore( );
}
@ -300,7 +333,7 @@ public List<String> getGroupsMember( String group, DirContext context )
String filter = "objectClass=" + getLdapGroupClass( );
namingEnumeration = context.search( "cn=" + group + "," + getGroupsDn(), filter, searchControls );
namingEnumeration = context.search( groupNameAttribute + "=" + group + "," + getGroupsDn( ), filter, searchControls );
List<String> allMembers = new ArrayList<String>( );
@ -308,15 +341,14 @@ public List<String> getGroupsMember( String group, DirContext context )
{
SearchResult searchResult = namingEnumeration.next( );
Attribute uniqueMemberAttr = searchResult.getAttributes().get( getLdapGroupMember() );
Attribute uniqueMemberAttr = searchResult.getAttributes( ).get( getLdapGroupMemberAttribute( ) );
if ( uniqueMemberAttr != null )
{
NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
NamingEnumeration<?> allMembersEnum = uniqueMemberAttr.getAll( );
while ( allMembersEnum.hasMore( ) )
{
String userName = allMembersEnum.next();
// uid=blabla we only want bla bla
String userName = allMembersEnum.next( ).toString( );
userName = StringUtils.substringAfter( userName, "=" );
userName = StringUtils.substringBefore( userName, "," );
log.debug( "found userName for group {}: '{}", group, userName );
@ -346,11 +378,16 @@ public List<String> getGroupsMember( String group, DirContext context )
}
}
private String getUserDnFromId(String userKey) {
return new StringBuilder().append( this.userIdAttribute ).append( "=" ).append( userKey ).append( "," ).append(
getBaseDn( ) ).toString();
}
public List<String> getGroups( String username, DirContext context )
throws MappingException
{
List<String> userGroups = new ArrayList<String>();
Set<String> userGroups = new HashSet<String>( );
NamingEnumeration<SearchResult> namingEnumeration = null;
try
@ -360,18 +397,20 @@ public List<String> getGroups( String username, DirContext context )
searchControls.setDerefLinkFlag( true );
searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
String groupEntry = null;
try
{
//try to look the user up
User user = userManager.findUser( username );
if ( user instanceof LdapUser )
if ( user != null && user instanceof LdapUser )
{
LdapUser ldapUser = LdapUser.class.cast( user );
LdapUser ldapUser = (LdapUser) user ;
Attribute dnAttribute = ldapUser.getOriginalAttributes( ).get( getLdapDnAttribute( ) );
if ( dnAttribute != null )
{
groupEntry = String.class.cast( dnAttribute.get() );
groupEntry = dnAttribute.get( ).toString();
}
}
@ -387,23 +426,20 @@ public List<String> getGroups( String username, DirContext context )
if ( groupEntry == null )
{
//failed to look up the user's groupEntry directly
StringBuilder builder = new StringBuilder();
String posixGroup = "posixGroup";
if ( posixGroup.equals( getLdapGroupClass() ) )
if ( this.useDnAsMemberValue )
{
builder.append( username );
groupEntry = getUserDnFromId( username );
}
else
{
builder.append( this.userIdAttribute ).append( "=" ).append( username ).append( "," ).append(
getBaseDn() );
groupEntry = username;
}
groupEntry = builder.toString();
}
String filter =
new StringBuilder( ).append( "(&" ).append( "(objectClass=" + getLdapGroupClass( ) + ")" ).append(
"(" ).append( getLdapGroupMember() ).append( "=" ).append( Rdn.escapeValue(groupEntry) ).append( ")" ).append(
"(" ).append( getLdapGroupMemberAttribute( ) ).append( "=" ).append( Rdn.escapeValue( groupEntry ) ).append( ")" ).append(
")" ).toString( );
log.debug( "filter: {}", filter );
@ -412,49 +448,17 @@ public List<String> getGroups( String username, DirContext context )
while ( namingEnumeration.hasMore( ) )
{
SearchResult searchResult = namingEnumeration.next();
SearchResult groupSearchResult = namingEnumeration.next( );
String groupName = getGroupNameFromResult( groupSearchResult );
List<String> allMembers = new ArrayList<String>();
Attribute uniqueMemberAttr = searchResult.getAttributes().get( getLdapGroupMember() );
if ( uniqueMemberAttr != null )
{
NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
while ( allMembersEnum.hasMore() )
{
String userName = allMembersEnum.next();
//the original dn
allMembers.add( userName );
// uid=blabla we only want bla bla
userName = StringUtils.substringAfter( userName, "=" );
userName = StringUtils.substringBefore( userName, "," );
allMembers.add( userName );
}
close( allMembersEnum );
}
if ( allMembers.contains( username ) )
{
String groupName = searchResult.getName();
// cn=blabla we only want bla bla
groupName = StringUtils.substringAfter( groupName, "=" );
userGroups.add( groupName );
}
else if ( allMembers.contains( groupEntry ) )
{
String groupName = searchResult.getName();
// cn=blabla we only want bla bla
groupName = StringUtils.substringAfter( groupName, "=" );
if (StringUtils.isNotEmpty( groupName )) {
userGroups.add( groupName );
}
}
return userGroups;
return new ArrayList( userGroups );
}
catch ( LdapException e )
{
@ -564,16 +568,16 @@ public boolean saveRole( String roleName, DirContext context )
objectClass.add( "top" );
objectClass.add( "groupOfUniqueNames" );
attributes.put( objectClass );
attributes.put( "cn", groupName );
attributes.put( this.groupNameAttribute, groupName );
// attribute mandatory when created a group so add admin as default member
BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMember() );
BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMemberAttribute( ) );
basicAttribute.add( this.userIdAttribute + "=admin," + getBaseDn( ) );
attributes.put( basicAttribute );
try
{
String dn = "cn=" + groupName + "," + this.groupsDn;
String dn = this.groupNameAttribute + "=" + groupName + "," + this.groupsDn;
context.createSubcontext( dn, attributes );
@ -619,23 +623,23 @@ public boolean saveUserRole( String roleName, String username, DirContext contex
String filter = "objectClass=" + getLdapGroupClass( );
namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
namingEnumeration = context.search( this.groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), filter, searchControls );
while ( namingEnumeration.hasMore() )
if ( namingEnumeration.hasMore() )
{
SearchResult searchResult = namingEnumeration.next( );
Attribute attribute = searchResult.getAttributes().get( getLdapGroupMember() );
Attribute attribute = searchResult.getAttributes( ).get( getLdapGroupMemberAttribute( ) );
if ( attribute == null )
{
BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMember() );
BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMemberAttribute( ) );
basicAttribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn( ) );
context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
context.modifyAttributes( this.groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), new ModificationItem[]{
new ModificationItem( DirContext.ADD_ATTRIBUTE, basicAttribute )} );
}
else
{
attribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn( ) );
context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
context.modifyAttributes( this.groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), new ModificationItem[]{
new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute )} );
}
return true;
@ -690,17 +694,17 @@ public boolean removeUserRole( String roleName, String username, DirContext cont
String filter = "objectClass=" + getLdapGroupClass( );
namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
namingEnumeration = context.search( groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), filter, searchControls );
while ( namingEnumeration.hasMore() )
if ( namingEnumeration.hasMore() )
{
SearchResult searchResult = namingEnumeration.next( );
Attribute attribute = searchResult.getAttributes().get( getLdapGroupMember() );
Attribute attribute = searchResult.getAttributes( ).get( getLdapGroupMemberAttribute( ) );
if ( attribute != null )
{
BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMember() );
BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMemberAttribute( ) );
basicAttribute.add( this.userIdAttribute + "=" + username + "," + getGroupsDn( ) );
context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
context.modifyAttributes( groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), new ModificationItem[]{
new ModificationItem( DirContext.REMOVE_ATTRIBUTE, basicAttribute )} );
}
return true;
@ -733,6 +737,8 @@ public boolean removeUserRole( String roleName, String username, DirContext cont
}
}
public void removeAllRoles( DirContext context )
throws MappingException
{
@ -743,12 +749,7 @@ public void removeAllRoles( DirContext context )
{
for ( String groupName : groups )
{
String dn = "cn=" + groupName + "," + this.groupsDn;
context.unbind( dn );
log.debug( "deleted group with dn:'{}", dn );
removeGroupByName( context, groupName );
}
}
@ -763,20 +764,47 @@ public void removeAllRoles( DirContext context )
}
}
private void removeGroupByName( DirContext context, String groupName ) throws NamingException
{
NamingEnumeration<SearchResult> namingEnumeration = null;
try
{
SearchControls searchControls = new SearchControls( );
searchControls.setDerefLinkFlag( true );
searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
String filter = "(&(objectClass=" + getLdapGroupClass( ) + ")(" + groupNameAttribute + "=" + Rdn.escapeValue( groupName ) + "))";
// String filter = "(&(objectClass=" + getLdapGroupClass( ) + "))";
namingEnumeration = context.search( getGroupsDn( ), filter, searchControls );
// We delete only the first found group
if ( namingEnumeration != null && namingEnumeration.hasMore( ) )
{
SearchResult result = namingEnumeration.next( );
String dn = result.getNameInNamespace( );
context.unbind( new LdapName( dn ) );
log.debug( "Deleted group with dn:'{}", dn );
}
}
finally
{
closeNamingEnumeration( namingEnumeration );
}
}
public void removeRole( String roleName, DirContext context )
throws MappingException
{
String groupName = findGroupName( roleName );
if (StringUtils.isEmpty( groupName )) {
log.warn( "No group for the given role found: role={}", roleName );
return;
}
try
{
String dn = "cn=" + groupName + "," + this.groupsDn;
context.unbind( dn );
log.info( "deleted group with dn:'{}", dn );
removeGroupByName( context, groupName );
}
catch ( LdapException e )
@ -829,14 +857,14 @@ public void setBaseDn( String baseDn )
this.baseDn = baseDn;
}
public String getLdapGroupMember()
public String getLdapGroupMemberAttribute( )
{
return ldapGroupMember;
return ldapGroupMemberAttribute;
}
public void setLdapGroupMember( String ldapGroupMember )
public void setLdapGroupMemberAttribute( String ldapGroupMemberAttribute )
{
this.ldapGroupMember = ldapGroupMember;
this.ldapGroupMemberAttribute = ldapGroupMemberAttribute;
}
//-------------------

View File

@ -88,7 +88,7 @@ public class TestLdapRoleMapper
LdapConnectionFactory ldapConnectionFactory;
List<String> roleNames =
Arrays.asList( "Archiva System Administrator", "Internal Repo Manager", "Internal Repo Observer" );
Arrays.asList( "Archiva System Administrator", "Internal Repo Manager", "Internal Repo Observer", "Ldap Group Test Role" );
LdapConnection ldapConnection;
@ -109,6 +109,7 @@ public void setUp()
usersPerGroup.put( "internal-repo-manager", Arrays.asList( "admin", "user.9" ) );
usersPerGroup.put( "internal-repo-observer", Arrays.asList( "admin", "user.7", "user.8" ) );
usersPerGroup.put( "archiva-admin", Arrays.asList( "admin", "user.7" ) );
usersPerGroup.put( "archiva/group-with-slash", Arrays.asList( "user.8", "user.9" ) );
users = new ArrayList<String>( 4 );
users.add( "admin" );
@ -160,8 +161,13 @@ public void tearDown()
}
for ( Map.Entry<String, List<String>> group : usersPerGroup.entrySet() )
{
try
{
context.unbind( createGroupDn( group.getKey( ) ) );
} catch (Exception ex) {
// Ignore
}
}
context.unbind( suffix );
@ -298,7 +304,7 @@ public void getAllGroups()
log.info( "allGroups: {}", allGroups );
assertThat( allGroups ).isNotNull().isNotEmpty().contains( "archiva-admin",
assertThat( allGroups ).isNotNull().isNotEmpty().contains( "archiva/group-with-slash", "archiva-admin",
"internal-repo-manager" );
}
@ -331,7 +337,7 @@ public void getGroups()
groups = ldapRoleMapper.getGroups( "user.8", getDirContext() );
assertThat( groups ).isNotNull().isNotEmpty().hasSize( 1 ).contains( "internal-repo-observer" );
assertThat( groups ).isNotNull().isNotEmpty().hasSize( 2 ).contains( "internal-repo-observer", "archiva/group-with-slash" );
groups = ldapRoleMapper.getGroups( "user.7", getDirContext() );
@ -362,7 +368,7 @@ public void getRoles()
log.info( "roles for user.8: {}", roles );
assertThat( roles ).isNotNull().isNotEmpty().hasSize( 1 ).contains( "Internal Repo Observer" );
assertThat( roles ).isNotNull().isNotEmpty().hasSize( 2 ).contains( "Internal Repo Observer" );
}
@ -380,5 +386,19 @@ public void hasRoleNotFound()
assertFalse( ldapRoleMapper.hasRole( getDirContext(), "Australian wine is good but not as French! " ) );
}
@Test
public void removeRole() throws Exception
{
assertTrue( ldapRoleMapper.getAllGroups( getDirContext( ) ).contains( "archiva-admin" ) );
ldapRoleMapper.removeRole( "Archiva System Administrator", getDirContext() );
assertFalse( ldapRoleMapper.getAllGroups( getDirContext( ) ).contains( "archiva-admin" ) );
}
@Test
public void removeAllRoles() throws Exception
{
assertEquals( 4, ldapRoleMapper.getAllGroups( getDirContext( ) ).size() );
ldapRoleMapper.removeAllRoles( getDirContext() );
assertEquals( 0, ldapRoleMapper.getAllGroups( getDirContext( ) ).size() );
}
}

View File

@ -17,3 +17,4 @@
ldap.config.groups.role.archiva-admin=Archiva System Administrator
ldap.config.groups.role.internal-repo-manager=Internal Repo Manager
ldap.config.groups.role.internal-repo-observer=Internal Repo Observer
ldap.config.groups.role.archiva/group-with-slash=Ldap Group Test Role

View File

@ -92,10 +92,14 @@ public interface UserConfigurationKeys
String LDAP_GROUPS_USE_ROLENAME = "ldap.config.groups.use.rolename";
String LDAP_GROUPS_USE_DN_AS_MEMBER_VALUE = "ldap.config.groups.useDnAsMemberValue";
String LDAP_WRITABLE = "ldap.config.writable";
String LDAP_USER_ID_ATTRIBUTE = "ldap.config.user.attribute";
String LDAP_GROUP_NAME_ATTRIBUTE = "ldap.config.groups.name.attribute";
String APPLICATION_URL = "application.url";
String EMAIL_URL_PATH = "email.url.path";