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, Collection<String> realRoles )
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 roles.addAll( rolesPerGroup );
416 if ( this.useDefaultRoleName && realRoles != null && realRoles.contains( group ) )
423 return new ArrayList<String>( roles );
426 private void close( NamingEnumeration namingEnumeration )
428 if ( namingEnumeration != null )
432 namingEnumeration.close();
434 catch ( NamingException e )
436 log.warn( "fail to close namingEnumeration: {}", e.getMessage() );
441 public String getGroupsDn()
443 return this.groupsDn;
446 public String getLdapGroupClass()
448 return this.ldapGroupClass;
451 public void addLdapMapping( String role, String ldapGroup )
453 log.warn( "addLdapMapping not implemented" );
456 public void removeLdapMapping( String role )
458 log.warn( "removeLdapMapping not implemented" );
461 public void setLdapGroupMappings( Map<String, Collection<String>> mappings )
462 throws MappingException
464 log.warn( "setLdapGroupMappings not implemented" );
467 public Map<String, Collection<String>> getLdapGroupMappings()
469 Multimap<String, String> map = ArrayListMultimap.create();
471 Collection<String> keys = userConf.getKeys();
473 for ( String key : keys )
475 if ( key.startsWith( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ) )
477 String val = userConf.getString( key );
478 String[] roles = StringUtils.split( val, ',' );
479 for ( String role : roles )
481 map.put( StringUtils.substringAfter( key, UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ),
490 public boolean saveRole( String roleName, DirContext context )
491 throws MappingException
494 if ( hasRole( context, roleName ) )
499 String groupName = findGroupName( roleName );
501 if ( groupName == null )
503 if ( this.useDefaultRoleName )
505 groupName = roleName;
509 log.warn( "skip group creation as no mapping fro roleName:'{}'", roleName );
514 List<String> allGroups = getAllGroups( context );
515 if ( allGroups.contains( groupName ) )
517 log.info( "group {} already exists for role.", groupName, roleName );
521 Attributes attributes = new BasicAttributes( true );
522 BasicAttribute objectClass = new BasicAttribute( "objectClass" );
523 objectClass.add( "top" );
524 objectClass.add( "groupOfUniqueNames" );
525 attributes.put( objectClass );
526 attributes.put( "cn", groupName );
528 // attribute mandatory when created a group so add admin as default member
529 // TODO make this default configurable
530 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
531 basicAttribute.add( this.userIdAttribute + "=admin," + getBaseDn() );
532 attributes.put( basicAttribute );
536 String dn = "cn=" + groupName + "," + this.groupsDn;
538 context.createSubcontext( dn, attributes );
540 log.info( "created group with dn:'{}", dn );
544 catch ( NameAlreadyBoundException e )
546 log.info( "skip group '{}' creation as already exists", groupName );
549 catch ( LdapException e )
551 throw new MappingException( e.getMessage(), e );
554 catch ( NamingException e )
556 throw new MappingException( e.getMessage(), e );
560 public boolean saveUserRole( String roleName, String username, DirContext context )
561 throws MappingException
564 String groupName = findGroupName( roleName );
566 if ( groupName == null )
568 log.warn( "no group found for role '{}", roleName );
569 groupName = roleName;
572 NamingEnumeration<SearchResult> namingEnumeration = null;
575 SearchControls searchControls = new SearchControls();
577 searchControls.setDerefLinkFlag( true );
578 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
580 String filter = "objectClass=" + getLdapGroupClass();
582 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
584 while ( namingEnumeration.hasMore() )
586 SearchResult searchResult = namingEnumeration.next();
587 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
588 if ( attribute == null )
590 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
591 basicAttribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
592 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
593 new ModificationItem( DirContext.ADD_ATTRIBUTE, basicAttribute ) } );
597 attribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
598 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
599 new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute ) } );
606 catch ( LdapException e )
608 throw new MappingException( e.getMessage(), e );
610 catch ( NamingException e )
612 throw new MappingException( e.getMessage(), e );
617 if ( namingEnumeration != null )
621 namingEnumeration.close();
623 catch ( NamingException e )
625 log.warn( "failed to close search results", e );
631 public boolean removeUserRole( String roleName, String username, DirContext context )
632 throws MappingException
634 String groupName = findGroupName( roleName );
636 if ( groupName == null )
638 log.warn( "no group found for role '{}", roleName );
642 NamingEnumeration<SearchResult> namingEnumeration = null;
646 SearchControls searchControls = new SearchControls();
648 searchControls.setDerefLinkFlag( true );
649 searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
651 String filter = "objectClass=" + getLdapGroupClass();
653 namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
655 while ( namingEnumeration.hasMore() )
657 SearchResult searchResult = namingEnumeration.next();
658 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
659 if ( attribute != null )
661 BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
662 basicAttribute.add( this.userIdAttribute + "=" + username + "," + getGroupsDn() );
663 context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
664 new ModificationItem( DirContext.REMOVE_ATTRIBUTE, basicAttribute ) } );
671 catch ( LdapException e )
673 throw new MappingException( e.getMessage(), e );
675 catch ( NamingException e )
677 throw new MappingException( e.getMessage(), e );
682 if ( namingEnumeration != null )
686 namingEnumeration.close();
688 catch ( NamingException e )
690 log.warn( "failed to close search results", e );
696 public void removeAllRoles( DirContext context )
697 throws MappingException
700 Collection<String> groups = getLdapGroupMappings().keySet();
704 for ( String groupName : groups )
707 String dn = "cn=" + groupName + "," + this.groupsDn;
709 context.unbind( dn );
711 log.debug( "deleted group with dn:'{}", dn );
715 catch ( LdapException e )
717 throw new MappingException( e.getMessage(), e );
720 catch ( NamingException e )
722 throw new MappingException( e.getMessage(), e );
726 public void removeRole( String roleName, DirContext context )
727 throws MappingException
730 String groupName = findGroupName( roleName );
735 String dn = "cn=" + groupName + "," + this.groupsDn;
737 context.unbind( dn );
739 log.info( "deleted group with dn:'{}", dn );
742 catch ( LdapException e )
744 throw new MappingException( e.getMessage(), e );
747 catch ( NamingException e )
749 throw new MappingException( e.getMessage(), e );
753 //---------------------------------
754 // setters for unit tests
755 //---------------------------------
758 public void setGroupsDn( String groupsDn )
760 this.groupsDn = groupsDn;
763 public void setLdapGroupClass( String ldapGroupClass )
765 this.ldapGroupClass = ldapGroupClass;
768 public void setUserConf( UserConfiguration userConf )
770 this.userConf = userConf;
773 public void setLdapConnectionFactory( LdapConnectionFactory ldapConnectionFactory )
775 this.ldapConnectionFactory = ldapConnectionFactory;
778 public String getBaseDn()
783 public void setBaseDn( String baseDn )
785 this.baseDn = baseDn;
788 //-------------------
790 //-------------------
792 protected String findGroupName( String role )
794 Map<String, Collection<String>> mapping = getLdapGroupMappings();
796 for ( Map.Entry<String, Collection<String>> entry : mapping.entrySet() )
798 if ( entry.getValue().contains( role ) )
800 return entry.getKey();
807 public String getUserIdAttribute()
809 return userIdAttribute;
812 public void setUserIdAttribute( String userIdAttribute )
814 this.userIdAttribute = userIdAttribute;