1 package org.apache.archiva.redback.common.ldap.role;
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import com.google.common.collect.ArrayListMultimap;
22 import com.google.common.collect.Multimap;
23 import org.apache.archiva.redback.common.ldap.MappingException;
24 import org.apache.archiva.redback.common.ldap.connection.LdapConnectionFactory;
25 import org.apache.archiva.redback.common.ldap.connection.LdapException;
26 import org.apache.archiva.redback.configuration.UserConfiguration;
27 import org.apache.archiva.redback.configuration.UserConfigurationKeys;
28 import org.apache.commons.lang.StringUtils;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.springframework.stereotype.Service;
33 import javax.annotation.PostConstruct;
34 import javax.inject.Inject;
35 import javax.inject.Named;
36 import javax.naming.NamingEnumeration;
37 import javax.naming.NamingException;
38 import javax.naming.directory.Attribute;
39 import javax.naming.directory.Attributes;
40 import javax.naming.directory.BasicAttribute;
41 import javax.naming.directory.BasicAttributes;
42 import javax.naming.directory.DirContext;
43 import javax.naming.directory.ModificationItem;
44 import javax.naming.directory.SearchControls;
45 import javax.naming.directory.SearchResult;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.HashSet;
50 import java.util.List;
55 * @author Olivier Lamy
58 @Service( "ldapRoleMapper#default" )
59 public class DefaultLdapRoleMapper
60 implements LdapRoleMapper
63 private Logger log = LoggerFactory.getLogger( getClass() );
66 private LdapConnectionFactory ldapConnectionFactory;
69 @Named( value = "userConfiguration#default" )
70 private UserConfiguration userConf;
72 //---------------------------
74 //---------------------------
76 private String ldapGroupClass = "groupOfUniqueNames";
78 private String groupsDn;
80 private String baseDn;
82 private boolean writableLdap = false;
84 private boolean useDefaultRoleName = false;
87 public void initialize()
89 this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass );
91 this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, this.baseDn );
93 this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn );
95 if ( StringUtils.isEmpty( this.groupsDn ) )
97 this.groupsDn = this.baseDn;
100 this.writableLdap = userConf.getBoolean( UserConfigurationKeys.LDAP_WRITABLE, this.writableLdap );
102 this.useDefaultRoleName =
103 userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_ROLENAME, this.useDefaultRoleName );
106 public String getLdapGroup( String role )
108 return userConf.getString( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY + role );
111 public List<String> getAllGroups( DirContext context )
112 throws MappingException
115 NamingEnumeration<SearchResult> namingEnumeration = null;
119 SearchControls searchControls = new SearchControls();
121 searchControls.setDerefLinkFlag( true );
122 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
124 String filter = "objectClass=" + getLdapGroupClass();
126 namingEnumeration = context.search( getGroupsDn(), filter, searchControls );
128 List<String> allGroups = new ArrayList<String>();
130 while ( namingEnumeration.hasMore() )
132 SearchResult searchResult = namingEnumeration.next();
134 String groupName = searchResult.getName();
135 // cn=blabla we only want bla bla
136 groupName = StringUtils.substringAfter( groupName, "=" );
138 log.debug( "found groupName: '{}", groupName );
140 allGroups.add( groupName );
146 catch ( LdapException e )
148 throw new MappingException( e.getMessage(), e );
150 catch ( NamingException e )
152 throw new MappingException( e.getMessage(), e );
157 close( namingEnumeration );
161 protected void closeNamingEnumeration( NamingEnumeration namingEnumeration )
163 if ( namingEnumeration != null )
167 namingEnumeration.close();
169 catch ( NamingException e )
171 log.warn( "failed to close NamingEnumeration", e );
176 public boolean hasRole( DirContext context, String roleName )
177 throws MappingException
179 String groupName = findGroupName( roleName );
181 if ( groupName == null )
183 if ( this.useDefaultRoleName )
185 groupName = roleName;
189 log.warn( "skip group creation as no mapping fro roleName:'{}'", roleName );
193 NamingEnumeration<SearchResult> namingEnumeration = null;
197 SearchControls searchControls = new SearchControls();
199 searchControls.setDerefLinkFlag( true );
200 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
202 String filter = "objectClass=" + getLdapGroupClass();
204 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
206 return namingEnumeration.hasMore();
208 catch ( LdapException e )
210 throw new MappingException( e.getMessage(), e );
212 catch ( NamingException e )
214 throw new MappingException( e.getMessage(), e );
219 close( namingEnumeration );
223 public List<String> getAllRoles( DirContext context )
224 throws MappingException
226 List<String> groups = getAllGroups( context );
228 if ( groups.isEmpty() )
230 return Collections.emptyList();
233 Set<String> roles = new HashSet<String>( groups.size() );
235 Map<String, Collection<String>> mapping = getLdapGroupMappings();
237 for ( String group : groups )
239 Collection<String> rolesPerGroup = mapping.get( group );
240 if ( rolesPerGroup != null )
242 for ( String role : rolesPerGroup )
249 return new ArrayList<String>( roles );
252 public List<String> getGroupsMember( String group, DirContext context )
253 throws MappingException
256 NamingEnumeration<SearchResult> namingEnumeration = null;
260 SearchControls searchControls = new SearchControls();
262 searchControls.setDerefLinkFlag( true );
263 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
265 String filter = "objectClass=" + getLdapGroupClass();
267 namingEnumeration = context.search( "cn=" + group + "," + getGroupsDn(), filter, searchControls );
269 List<String> allMembers = new ArrayList<String>();
271 while ( namingEnumeration.hasMore() )
273 SearchResult searchResult = namingEnumeration.next();
275 Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" );
277 if ( uniqueMemberAttr != null )
279 NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
280 while ( allMembersEnum.hasMore() )
282 String userName = allMembersEnum.next();
283 // uid=blabla we only want bla bla
284 userName = StringUtils.substringAfter( userName, "=" );
285 userName = StringUtils.substringBefore( userName, "," );
286 log.debug( "found userName for group {}: '{}", group, userName );
288 allMembers.add( userName );
290 close( allMembersEnum );
298 catch ( LdapException e )
300 throw new MappingException( e.getMessage(), e );
302 catch ( NamingException e )
304 throw new MappingException( e.getMessage(), e );
309 close( namingEnumeration );
313 public List<String> getGroups( String username, DirContext context )
314 throws MappingException
317 List<String> userGroups = new ArrayList<String>();
319 NamingEnumeration<SearchResult> namingEnumeration = null;
323 SearchControls searchControls = new SearchControls();
325 searchControls.setDerefLinkFlag( true );
326 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
329 new StringBuilder().append( "(&" ).append( "(objectClass=" + getLdapGroupClass() + ")" ).append(
330 "(uniquemember=" ).append( "uid=" + username + "," + this.getBaseDn() ).append( ")" ).append(
333 log.debug( "filter: {}", filter );
335 namingEnumeration = context.search( getGroupsDn(), filter, searchControls );
337 while ( namingEnumeration.hasMore() )
339 SearchResult searchResult = namingEnumeration.next();
341 List<String> allMembers = new ArrayList<String>();
343 Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" );
345 if ( uniqueMemberAttr != null )
347 NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
348 while ( allMembersEnum.hasMore() )
350 String userName = allMembersEnum.next();
351 // uid=blabla we only want bla bla
352 userName = StringUtils.substringAfter( userName, "=" );
353 userName = StringUtils.substringBefore( userName, "," );
354 allMembers.add( userName );
356 close( allMembersEnum );
359 if ( allMembers.contains( username ) )
361 String groupName = searchResult.getName();
362 // cn=blabla we only want bla bla
363 groupName = StringUtils.substringAfter( groupName, "=" );
364 userGroups.add( groupName );
373 catch ( LdapException e )
375 throw new MappingException( e.getMessage(), e );
377 catch ( NamingException e )
379 throw new MappingException( e.getMessage(), e );
383 close( namingEnumeration );
387 public List<String> getRoles( String username, DirContext context )
388 throws MappingException
390 List<String> groups = getGroups( username, context );
392 Map<String, Collection<String>> rolesMapping = getLdapGroupMappings();
394 Set<String> roles = new HashSet<String>( groups.size() );
396 for ( String group : groups )
398 Collection<String> rolesPerGroup = rolesMapping.get( group );
399 if ( rolesPerGroup != null )
401 for ( String role : rolesPerGroup )
408 return new ArrayList<String>( roles );
411 private void close( NamingEnumeration namingEnumeration )
413 if ( namingEnumeration != null )
417 namingEnumeration.close();
419 catch ( NamingException e )
421 log.warn( "fail to close namingEnumeration: {}", e.getMessage() );
426 public String getGroupsDn()
428 return this.groupsDn;
431 public String getLdapGroupClass()
433 return this.ldapGroupClass;
436 public void addLdapMapping( String role, String ldapGroup )
438 log.warn( "addLdapMapping not implemented" );
441 public void removeLdapMapping( String role )
443 log.warn( "removeLdapMapping not implemented" );
446 public void setLdapGroupMappings( Map<String, Collection<String>> mappings )
447 throws MappingException
449 log.warn( "setLdapGroupMappings not implemented" );
452 public Map<String, Collection<String>> getLdapGroupMappings()
454 Multimap<String, String> map = ArrayListMultimap.create();
456 Collection<String> keys = userConf.getKeys();
458 for ( String key : keys )
460 if ( key.startsWith( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ) )
462 String val = userConf.getString( key );
463 String[] roles = StringUtils.split( val, ',' );
464 for ( String role : roles )
466 map.put( StringUtils.substringAfter( key, UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ),
475 public boolean saveRole( String roleName, DirContext context )
476 throws MappingException
479 String groupName = findGroupName( roleName );
481 if ( groupName == null )
483 if ( this.useDefaultRoleName )
485 groupName = roleName;
489 log.warn( "skip group creation as no mapping fro roleName:'{}'", roleName );
494 List<String> allGroups = getAllGroups( context );
495 if ( allGroups.contains( groupName ) )
497 log.info( "group {} already exists for role.", groupName, roleName );
501 Attributes attributes = new BasicAttributes( true );
502 BasicAttribute objectClass = new BasicAttribute( "objectClass" );
503 objectClass.add( "top" );
504 objectClass.add( "groupOfUniqueNames" );
505 attributes.put( objectClass );
506 attributes.put( "cn", groupName );
508 // attribute mandatory when created a group so add admin as default member
509 // TODO make this default configurable
510 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
511 basicAttribute.add( "uid=admin," + getBaseDn() );
512 attributes.put( basicAttribute );
516 String dn = "cn=" + groupName + "," + this.groupsDn;
518 context.createSubcontext( dn, attributes );
520 log.info( "created group with dn:'{}", dn );
524 catch ( LdapException e )
526 throw new MappingException( e.getMessage(), e );
529 catch ( NamingException e )
531 throw new MappingException( e.getMessage(), e );
535 public boolean saveUserRole( String roleName, String username, DirContext context )
536 throws MappingException
539 String groupName = findGroupName( roleName );
541 if ( groupName == null )
543 log.warn( "no group found for role '{}", roleName );
544 groupName = roleName;
547 NamingEnumeration<SearchResult> namingEnumeration = null;
550 SearchControls searchControls = new SearchControls();
552 searchControls.setDerefLinkFlag( true );
553 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
555 String filter = "objectClass=" + getLdapGroupClass();
557 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
559 while ( namingEnumeration.hasMore() )
561 SearchResult searchResult = namingEnumeration.next();
562 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
563 if ( attribute == null )
565 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
566 basicAttribute.add( "uid=" + username + "," + getGroupsDn() );
567 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
568 new ModificationItem( DirContext.ADD_ATTRIBUTE, basicAttribute ) } );
572 attribute.add( "uid=" + username + "," + getGroupsDn() );
573 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
574 new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute ) } );
581 catch ( LdapException e )
583 throw new MappingException( e.getMessage(), e );
585 catch ( NamingException e )
587 throw new MappingException( e.getMessage(), e );
592 if ( namingEnumeration != null )
596 namingEnumeration.close();
598 catch ( NamingException e )
600 log.warn( "failed to close search results", e );
606 public boolean removeUserRole( String roleName, String username, DirContext context )
607 throws MappingException
609 String groupName = findGroupName( roleName );
611 if ( groupName == null )
613 log.warn( "no group found for role '{}", roleName );
617 NamingEnumeration<SearchResult> namingEnumeration = null;
621 SearchControls searchControls = new SearchControls();
623 searchControls.setDerefLinkFlag( true );
624 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
626 String filter = "objectClass=" + getLdapGroupClass();
628 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
630 while ( namingEnumeration.hasMore() )
632 SearchResult searchResult = namingEnumeration.next();
633 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
634 if ( attribute != null )
636 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
637 basicAttribute.add( "uid=" + username + "," + getGroupsDn() );
638 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
639 new ModificationItem( DirContext.REMOVE_ATTRIBUTE, basicAttribute ) } );
646 catch ( LdapException e )
648 throw new MappingException( e.getMessage(), e );
650 catch ( NamingException e )
652 throw new MappingException( e.getMessage(), e );
657 if ( namingEnumeration != null )
661 namingEnumeration.close();
663 catch ( NamingException e )
665 log.warn( "failed to close search results", e );
671 public void removeAllRoles( DirContext context )
672 throws MappingException
675 Collection<String> groups = getLdapGroupMappings().keySet();
679 for ( String groupName : groups )
682 String dn = "cn=" + groupName + "," + this.groupsDn;
684 context.unbind( dn );
686 log.debug( "deleted group with dn:'{}", dn );
690 catch ( LdapException e )
692 throw new MappingException( e.getMessage(), e );
695 catch ( NamingException e )
697 throw new MappingException( e.getMessage(), e );
701 public void removeRole( String roleName, DirContext context )
702 throws MappingException
705 String groupName = findGroupName( roleName );
710 String dn = "cn=" + groupName + "," + this.groupsDn;
712 context.unbind( dn );
714 log.info( "deleted group with dn:'{}", dn );
717 catch ( LdapException e )
719 throw new MappingException( e.getMessage(), e );
722 catch ( NamingException e )
724 throw new MappingException( e.getMessage(), e );
728 //---------------------------------
729 // setters for unit tests
730 //---------------------------------
733 public void setGroupsDn( String groupsDn )
735 this.groupsDn = groupsDn;
738 public void setLdapGroupClass( String ldapGroupClass )
740 this.ldapGroupClass = ldapGroupClass;
743 public void setUserConf( UserConfiguration userConf )
745 this.userConf = userConf;
748 public void setLdapConnectionFactory( LdapConnectionFactory ldapConnectionFactory )
750 this.ldapConnectionFactory = ldapConnectionFactory;
753 public String getBaseDn()
758 public void setBaseDn( String baseDn )
760 this.baseDn = baseDn;
763 //-------------------
765 //-------------------
767 protected String findGroupName( String role )
769 Map<String, Collection<String>> mapping = getLdapGroupMappings();
771 for ( Map.Entry<String, Collection<String>> entry : mapping.entrySet() )
773 if ( entry.getValue().contains( role ) )
775 return entry.getKey();