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.NameAlreadyBoundException;
37 import javax.naming.NameNotFoundException;
38 import javax.naming.NamingEnumeration;
39 import javax.naming.NamingException;
40 import javax.naming.directory.Attribute;
41 import javax.naming.directory.Attributes;
42 import javax.naming.directory.BasicAttribute;
43 import javax.naming.directory.BasicAttributes;
44 import javax.naming.directory.DirContext;
45 import javax.naming.directory.ModificationItem;
46 import javax.naming.directory.SearchControls;
47 import javax.naming.directory.SearchResult;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.HashSet;
52 import java.util.List;
57 * @author Olivier Lamy
60 @Service("ldapRoleMapper#default")
61 public class DefaultLdapRoleMapper
62 implements LdapRoleMapper
65 private Logger log = LoggerFactory.getLogger( getClass() );
68 private LdapConnectionFactory ldapConnectionFactory;
71 @Named(value = "userConfiguration#default")
72 private UserConfiguration userConf;
74 //---------------------------
76 //---------------------------
78 private String ldapGroupClass = "groupOfUniqueNames";
80 private String groupsDn;
82 private String baseDn;
84 private boolean useDefaultRoleName = false;
87 * possible to user cn=beer or uid=beer or sn=beer etc
88 * so make it configurable
90 private String userIdAttribute = "uid";
93 public void initialize()
95 this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass );
97 this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, this.baseDn );
99 this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn );
101 if ( StringUtils.isEmpty( this.groupsDn ) )
103 this.groupsDn = this.baseDn;
106 this.useDefaultRoleName =
107 userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_ROLENAME, this.useDefaultRoleName );
109 this.userIdAttribute = userConf.getString( UserConfigurationKeys.LDAP_USER_ID_ATTRIBUTE, this.userIdAttribute );
112 public String getLdapGroup( String role )
114 return userConf.getString( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY + role );
117 public List<String> getAllGroups( DirContext context )
118 throws MappingException
121 NamingEnumeration<SearchResult> namingEnumeration = null;
125 SearchControls searchControls = new SearchControls();
127 searchControls.setDerefLinkFlag( true );
128 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
130 String filter = "objectClass=" + getLdapGroupClass();
132 namingEnumeration = context.search( getGroupsDn(), filter, searchControls );
134 List<String> allGroups = new ArrayList<String>();
136 while ( namingEnumeration.hasMore() )
138 SearchResult searchResult = namingEnumeration.next();
140 String groupName = searchResult.getName();
141 // cn=blabla we only want bla bla
142 groupName = StringUtils.substringAfter( groupName, "=" );
144 log.debug( "found groupName: '{}", groupName );
146 allGroups.add( groupName );
152 catch ( LdapException e )
154 throw new MappingException( e.getMessage(), e );
156 catch ( NamingException e )
158 throw new MappingException( e.getMessage(), e );
163 close( namingEnumeration );
167 protected void closeNamingEnumeration( NamingEnumeration namingEnumeration )
169 if ( namingEnumeration != null )
173 namingEnumeration.close();
175 catch ( NamingException e )
177 log.warn( "failed to close NamingEnumeration", e );
182 public boolean hasRole( DirContext context, String roleName )
183 throws MappingException
185 String groupName = findGroupName( roleName );
187 if ( groupName == null )
189 if ( this.useDefaultRoleName )
191 groupName = roleName;
195 log.warn( "skip group creation as no mapping for roleName:'{}'", roleName );
199 NamingEnumeration<SearchResult> namingEnumeration = null;
203 SearchControls searchControls = new SearchControls();
205 searchControls.setDerefLinkFlag( true );
206 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
208 String filter = "objectClass=" + getLdapGroupClass();
210 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
212 return namingEnumeration.hasMore();
214 catch ( NameNotFoundException e )
216 log.debug( "group {} for role {} not found", groupName, roleName );
219 catch ( LdapException e )
221 throw new MappingException( e.getMessage(), e );
223 catch ( NamingException e )
225 throw new MappingException( e.getMessage(), e );
230 close( namingEnumeration );
234 public List<String> getAllRoles( DirContext context )
235 throws MappingException
237 List<String> groups = getAllGroups( context );
239 if ( groups.isEmpty() )
241 return Collections.emptyList();
244 Set<String> roles = new HashSet<String>( groups.size() );
246 Map<String, Collection<String>> mapping = getLdapGroupMappings();
248 for ( String group : groups )
250 Collection<String> rolesPerGroup = mapping.get( group );
251 if ( rolesPerGroup != null )
253 for ( String role : rolesPerGroup )
260 return new ArrayList<String>( roles );
263 public List<String> getGroupsMember( String group, DirContext context )
264 throws MappingException
267 NamingEnumeration<SearchResult> namingEnumeration = null;
271 SearchControls searchControls = new SearchControls();
273 searchControls.setDerefLinkFlag( true );
274 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
276 String filter = "objectClass=" + getLdapGroupClass();
278 namingEnumeration = context.search( "cn=" + group + "," + getGroupsDn(), filter, searchControls );
280 List<String> allMembers = new ArrayList<String>();
282 while ( namingEnumeration.hasMore() )
284 SearchResult searchResult = namingEnumeration.next();
286 Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" );
288 if ( uniqueMemberAttr != null )
290 NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
291 while ( allMembersEnum.hasMore() )
293 String userName = allMembersEnum.next();
294 // uid=blabla we only want bla bla
295 userName = StringUtils.substringAfter( userName, "=" );
296 userName = StringUtils.substringBefore( userName, "," );
297 log.debug( "found userName for group {}: '{}", group, userName );
299 allMembers.add( userName );
301 close( allMembersEnum );
309 catch ( LdapException e )
311 throw new MappingException( e.getMessage(), e );
313 catch ( NamingException e )
315 throw new MappingException( e.getMessage(), e );
320 close( namingEnumeration );
324 public List<String> getGroups( String username, DirContext context )
325 throws MappingException
328 List<String> userGroups = new ArrayList<String>();
330 NamingEnumeration<SearchResult> namingEnumeration = null;
334 SearchControls searchControls = new SearchControls();
336 searchControls.setDerefLinkFlag( true );
337 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
340 new StringBuilder().append( "(&" ).append( "(objectClass=" + getLdapGroupClass() + ")" ).append(
341 "(uniquemember=" ).append( this.userIdAttribute + "=" + username + "," + this.getBaseDn() ).append(
342 ")" ).append( ")" ).toString();
344 log.debug( "filter: {}", filter );
346 namingEnumeration = context.search( getGroupsDn(), filter, searchControls );
348 while ( namingEnumeration.hasMore() )
350 SearchResult searchResult = namingEnumeration.next();
352 List<String> allMembers = new ArrayList<String>();
354 Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" );
356 if ( uniqueMemberAttr != null )
358 NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
359 while ( allMembersEnum.hasMore() )
361 String userName = allMembersEnum.next();
362 // uid=blabla we only want bla bla
363 userName = StringUtils.substringAfter( userName, "=" );
364 userName = StringUtils.substringBefore( userName, "," );
365 allMembers.add( userName );
367 close( allMembersEnum );
370 if ( allMembers.contains( username ) )
372 String groupName = searchResult.getName();
373 // cn=blabla we only want bla bla
374 groupName = StringUtils.substringAfter( groupName, "=" );
375 userGroups.add( groupName );
384 catch ( LdapException e )
386 throw new MappingException( e.getMessage(), e );
388 catch ( NamingException e )
390 throw new MappingException( e.getMessage(), e );
394 close( namingEnumeration );
398 public List<String> getRoles( String username, DirContext context )
399 throws MappingException
401 List<String> groups = getGroups( username, context );
403 Map<String, Collection<String>> rolesMapping = getLdapGroupMappings();
405 Set<String> roles = new HashSet<String>( groups.size() );
407 for ( String group : groups )
409 Collection<String> rolesPerGroup = rolesMapping.get( group );
410 if ( rolesPerGroup != null )
412 for ( String role : rolesPerGroup )
419 if ( this.useDefaultRoleName )
426 return new ArrayList<String>( roles );
429 private void close( NamingEnumeration namingEnumeration )
431 if ( namingEnumeration != null )
435 namingEnumeration.close();
437 catch ( NamingException e )
439 log.warn( "fail to close namingEnumeration: {}", e.getMessage() );
444 public String getGroupsDn()
446 return this.groupsDn;
449 public String getLdapGroupClass()
451 return this.ldapGroupClass;
454 public void addLdapMapping( String role, String ldapGroup )
456 log.warn( "addLdapMapping not implemented" );
459 public void removeLdapMapping( String role )
461 log.warn( "removeLdapMapping not implemented" );
464 public void setLdapGroupMappings( Map<String, Collection<String>> mappings )
465 throws MappingException
467 log.warn( "setLdapGroupMappings not implemented" );
470 public Map<String, Collection<String>> getLdapGroupMappings()
472 Multimap<String, String> map = ArrayListMultimap.create();
474 Collection<String> keys = userConf.getKeys();
476 for ( String key : keys )
478 if ( key.startsWith( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ) )
480 String val = userConf.getString( key );
481 String[] roles = StringUtils.split( val, ',' );
482 for ( String role : roles )
484 map.put( StringUtils.substringAfter( key, UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ),
493 public boolean saveRole( String roleName, DirContext context )
494 throws MappingException
497 if ( hasRole( context, roleName ) )
502 String groupName = findGroupName( roleName );
504 if ( groupName == null )
506 if ( this.useDefaultRoleName )
508 groupName = roleName;
512 log.warn( "skip group creation as no mapping fro roleName:'{}'", roleName );
517 List<String> allGroups = getAllGroups( context );
518 if ( allGroups.contains( groupName ) )
520 log.info( "group {} already exists for role.", groupName, roleName );
524 Attributes attributes = new BasicAttributes( true );
525 BasicAttribute objectClass = new BasicAttribute( "objectClass" );
526 objectClass.add( "top" );
527 objectClass.add( "groupOfUniqueNames" );
528 attributes.put( objectClass );
529 attributes.put( "cn", groupName );
531 // attribute mandatory when created a group so add admin as default member
532 // TODO make this default configurable
533 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
534 basicAttribute.add( this.userIdAttribute + "=admin," + getBaseDn() );
535 attributes.put( basicAttribute );
539 String dn = "cn=" + groupName + "," + this.groupsDn;
541 context.createSubcontext( dn, attributes );
543 log.info( "created group with dn:'{}", dn );
547 catch ( NameAlreadyBoundException e )
549 log.info( "skip group '{}' creation as already exists", groupName );
552 catch ( LdapException e )
554 throw new MappingException( e.getMessage(), e );
557 catch ( NamingException e )
559 throw new MappingException( e.getMessage(), e );
563 public boolean saveUserRole( String roleName, String username, DirContext context )
564 throws MappingException
567 String groupName = findGroupName( roleName );
569 if ( groupName == null )
571 log.warn( "no group found for role '{}", roleName );
572 groupName = roleName;
575 NamingEnumeration<SearchResult> namingEnumeration = null;
578 SearchControls searchControls = new SearchControls();
580 searchControls.setDerefLinkFlag( true );
581 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
583 String filter = "objectClass=" + getLdapGroupClass();
585 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
587 while ( namingEnumeration.hasMore() )
589 SearchResult searchResult = namingEnumeration.next();
590 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
591 if ( attribute == null )
593 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
594 basicAttribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
595 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
596 new ModificationItem( DirContext.ADD_ATTRIBUTE, basicAttribute ) } );
600 attribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
601 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
602 new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute ) } );
609 catch ( LdapException e )
611 throw new MappingException( e.getMessage(), e );
613 catch ( NamingException e )
615 throw new MappingException( e.getMessage(), e );
620 if ( namingEnumeration != null )
624 namingEnumeration.close();
626 catch ( NamingException e )
628 log.warn( "failed to close search results", e );
634 public boolean removeUserRole( String roleName, String username, DirContext context )
635 throws MappingException
637 String groupName = findGroupName( roleName );
639 if ( groupName == null )
641 log.warn( "no group found for role '{}", roleName );
645 NamingEnumeration<SearchResult> namingEnumeration = null;
649 SearchControls searchControls = new SearchControls();
651 searchControls.setDerefLinkFlag( true );
652 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
654 String filter = "objectClass=" + getLdapGroupClass();
656 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
658 while ( namingEnumeration.hasMore() )
660 SearchResult searchResult = namingEnumeration.next();
661 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
662 if ( attribute != null )
664 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
665 basicAttribute.add( this.userIdAttribute + "=" + username + "," + getGroupsDn() );
666 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
667 new ModificationItem( DirContext.REMOVE_ATTRIBUTE, basicAttribute ) } );
674 catch ( LdapException e )
676 throw new MappingException( e.getMessage(), e );
678 catch ( NamingException e )
680 throw new MappingException( e.getMessage(), e );
685 if ( namingEnumeration != null )
689 namingEnumeration.close();
691 catch ( NamingException e )
693 log.warn( "failed to close search results", e );
699 public void removeAllRoles( DirContext context )
700 throws MappingException
703 Collection<String> groups = getLdapGroupMappings().keySet();
707 for ( String groupName : groups )
710 String dn = "cn=" + groupName + "," + this.groupsDn;
712 context.unbind( dn );
714 log.debug( "deleted group with dn:'{}", dn );
718 catch ( LdapException e )
720 throw new MappingException( e.getMessage(), e );
723 catch ( NamingException e )
725 throw new MappingException( e.getMessage(), e );
729 public void removeRole( String roleName, DirContext context )
730 throws MappingException
733 String groupName = findGroupName( roleName );
738 String dn = "cn=" + groupName + "," + this.groupsDn;
740 context.unbind( dn );
742 log.info( "deleted group with dn:'{}", dn );
745 catch ( LdapException e )
747 throw new MappingException( e.getMessage(), e );
750 catch ( NamingException e )
752 throw new MappingException( e.getMessage(), e );
756 //---------------------------------
757 // setters for unit tests
758 //---------------------------------
761 public void setGroupsDn( String groupsDn )
763 this.groupsDn = groupsDn;
766 public void setLdapGroupClass( String ldapGroupClass )
768 this.ldapGroupClass = ldapGroupClass;
771 public void setUserConf( UserConfiguration userConf )
773 this.userConf = userConf;
776 public void setLdapConnectionFactory( LdapConnectionFactory ldapConnectionFactory )
778 this.ldapConnectionFactory = ldapConnectionFactory;
781 public String getBaseDn()
786 public void setBaseDn( String baseDn )
788 this.baseDn = baseDn;
791 //-------------------
793 //-------------------
795 protected String findGroupName( String role )
797 Map<String, Collection<String>> mapping = getLdapGroupMappings();
799 for ( Map.Entry<String, Collection<String>> entry : mapping.entrySet() )
801 if ( entry.getValue().contains( role ) )
803 return entry.getKey();
810 public String getUserIdAttribute()
812 return userIdAttribute;
815 public void setUserIdAttribute( String userIdAttribute )
817 this.userIdAttribute = userIdAttribute;