This project has retired. For details please refer to its Attic page.
DefaultLdapRoleMapper xref
View Javadoc

1   package org.apache.archiva.redback.common.ldap.role;
2   /*
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
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
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
18   * under the License.
19   */
20  
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;
32  
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;
53  import java.util.Map;
54  import java.util.Set;
55  
56  /**
57   * @author Olivier Lamy
58   * @since 2.1
59   */
60  @Service( "ldapRoleMapper#default" )
61  public class DefaultLdapRoleMapper
62      implements LdapRoleMapper
63  {
64  
65      private Logger log = LoggerFactory.getLogger( getClass() );
66  
67      @Inject
68      @Named( value = "ldapConnectionFactory#configurable" )
69      private LdapConnectionFactory ldapConnectionFactory;
70  
71      @Inject
72      @Named( value = "userConfiguration#default" )
73      private UserConfiguration userConf;
74  
75      @Inject
76      @Named( value = "ldapRoleMapperConfiguration#default" )
77      private LdapRoleMapperConfiguration ldapRoleMapperConfiguration;
78  
79      //---------------------------
80      // fields
81      //---------------------------
82  
83      private String ldapGroupClass = "groupOfUniqueNames";
84  
85      private String groupsDn;
86  
87      private String baseDn;
88  
89      private boolean useDefaultRoleName = false;
90  
91      /**
92       * possible to user cn=beer or uid=beer or sn=beer etc
93       * so make it configurable
94       */
95      private String userIdAttribute = "uid";
96  
97      @PostConstruct
98      public void initialize()
99      {
100         this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass );
101 
102         this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, this.baseDn );
103 
104         this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn );
105 
106         if ( StringUtils.isEmpty( this.groupsDn ) )
107         {
108             this.groupsDn = this.baseDn;
109         }
110 
111         this.useDefaultRoleName =
112             userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_ROLENAME, this.useDefaultRoleName );
113 
114         this.userIdAttribute = userConf.getString( UserConfigurationKeys.LDAP_USER_ID_ATTRIBUTE, this.userIdAttribute );
115     }
116 
117     public List<String> getAllGroups( DirContext context )
118         throws MappingException
119     {
120 
121         NamingEnumeration<SearchResult> namingEnumeration = null;
122         try
123         {
124 
125             SearchControls searchControls = new SearchControls();
126 
127             searchControls.setDerefLinkFlag( true );
128             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
129 
130             String filter = "objectClass=" + getLdapGroupClass();
131 
132             namingEnumeration = context.search( getGroupsDn(), filter, searchControls );
133 
134             List<String> allGroups = new ArrayList<String>();
135 
136             while ( namingEnumeration.hasMore() )
137             {
138                 SearchResult searchResult = namingEnumeration.next();
139 
140                 String groupName = searchResult.getName();
141                 // cn=blabla we only want bla bla
142                 groupName = StringUtils.substringAfter( groupName, "=" );
143 
144                 log.debug( "found groupName: '{}", groupName );
145 
146                 allGroups.add( groupName );
147 
148             }
149 
150             return allGroups;
151         }
152         catch ( LdapException e )
153         {
154             throw new MappingException( e.getMessage(), e );
155         }
156         catch ( NamingException e )
157         {
158             throw new MappingException( e.getMessage(), e );
159         }
160 
161         finally
162         {
163             close( namingEnumeration );
164         }
165     }
166 
167     protected void closeNamingEnumeration( NamingEnumeration namingEnumeration )
168     {
169         if ( namingEnumeration != null )
170         {
171             try
172             {
173                 namingEnumeration.close();
174             }
175             catch ( NamingException e )
176             {
177                 log.warn( "failed to close NamingEnumeration", e );
178             }
179         }
180     }
181 
182     public boolean hasRole( DirContext context, String roleName )
183         throws MappingException
184     {
185         String groupName = findGroupName( roleName );
186 
187         if ( groupName == null )
188         {
189             if ( this.useDefaultRoleName )
190             {
191                 groupName = roleName;
192             }
193             else
194             {
195                 log.warn( "skip group creation as no mapping for roleName:'{}'", roleName );
196                 return false;
197             }
198         }
199         NamingEnumeration<SearchResult> namingEnumeration = null;
200         try
201         {
202 
203             SearchControls searchControls = new SearchControls();
204 
205             searchControls.setDerefLinkFlag( true );
206             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
207 
208             String filter = "objectClass=" + getLdapGroupClass();
209 
210             namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
211 
212             return namingEnumeration.hasMore();
213         }
214         catch ( NameNotFoundException e )
215         {
216             log.debug( "group {} for role {} not found", groupName, roleName );
217             return false;
218         }
219         catch ( LdapException e )
220         {
221             throw new MappingException( e.getMessage(), e );
222         }
223         catch ( NamingException e )
224         {
225             throw new MappingException( e.getMessage(), e );
226         }
227 
228         finally
229         {
230             close( namingEnumeration );
231         }
232     }
233 
234     public List<String> getAllRoles( DirContext context )
235         throws MappingException
236     {
237         List<String> groups = getAllGroups( context );
238 
239         if ( groups.isEmpty() )
240         {
241             return Collections.emptyList();
242         }
243 
244         Set<String> roles = new HashSet<String>( groups.size() );
245 
246         Map<String, Collection<String>> mapping = ldapRoleMapperConfiguration.getLdapGroupMappings();
247 
248         for ( String group : groups )
249         {
250             Collection<String> rolesPerGroup = mapping.get( group );
251             if ( rolesPerGroup != null )
252             {
253                 for ( String role : rolesPerGroup )
254                 {
255                     roles.add( role );
256                 }
257             }
258         }
259 
260         return new ArrayList<String>( roles );
261     }
262 
263     public List<String> getGroupsMember( String group, DirContext context )
264         throws MappingException
265     {
266 
267         NamingEnumeration<SearchResult> namingEnumeration = null;
268         try
269         {
270 
271             SearchControls searchControls = new SearchControls();
272 
273             searchControls.setDerefLinkFlag( true );
274             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
275 
276             String filter = "objectClass=" + getLdapGroupClass();
277 
278             namingEnumeration = context.search( "cn=" + group + "," + getGroupsDn(), filter, searchControls );
279 
280             List<String> allMembers = new ArrayList<String>();
281 
282             while ( namingEnumeration.hasMore() )
283             {
284                 SearchResult searchResult = namingEnumeration.next();
285 
286                 Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" );
287 
288                 if ( uniqueMemberAttr != null )
289                 {
290                     NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
291                     while ( allMembersEnum.hasMore() )
292                     {
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 );
298 
299                         allMembers.add( userName );
300                     }
301                     close( allMembersEnum );
302                 }
303 
304 
305             }
306 
307             return allMembers;
308         }
309         catch ( LdapException e )
310         {
311             throw new MappingException( e.getMessage(), e );
312         }
313         catch ( NamingException e )
314         {
315             throw new MappingException( e.getMessage(), e );
316         }
317 
318         finally
319         {
320             close( namingEnumeration );
321         }
322     }
323 
324     public List<String> getGroups( String username, DirContext context )
325         throws MappingException
326     {
327 
328         List<String> userGroups = new ArrayList<String>();
329 
330         NamingEnumeration<SearchResult> namingEnumeration = null;
331         try
332         {
333 
334             SearchControls searchControls = new SearchControls();
335 
336             searchControls.setDerefLinkFlag( true );
337             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
338 
339             String filter =
340                 new StringBuilder().append( "(&" ).append( "(objectClass=" + getLdapGroupClass() + ")" ).append(
341                     "(uniquemember=" ).append( this.userIdAttribute + "=" + username + "," + this.getBaseDn() ).append(
342                     ")" ).append( ")" ).toString();
343 
344             log.debug( "filter: {}", filter );
345 
346             namingEnumeration = context.search( getGroupsDn(), filter, searchControls );
347 
348             while ( namingEnumeration.hasMore() )
349             {
350                 SearchResult searchResult = namingEnumeration.next();
351 
352                 List<String> allMembers = new ArrayList<String>();
353 
354                 Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" );
355 
356                 if ( uniqueMemberAttr != null )
357                 {
358                     NamingEnumeration<String> allMembersEnum = (NamingEnumeration<String>) uniqueMemberAttr.getAll();
359                     while ( allMembersEnum.hasMore() )
360                     {
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 );
366                     }
367                     close( allMembersEnum );
368                 }
369 
370                 if ( allMembers.contains( username ) )
371                 {
372                     String groupName = searchResult.getName();
373                     // cn=blabla we only want bla bla
374                     groupName = StringUtils.substringAfter( groupName, "=" );
375                     userGroups.add( groupName );
376 
377                 }
378 
379 
380             }
381 
382             return userGroups;
383         }
384         catch ( LdapException e )
385         {
386             throw new MappingException( e.getMessage(), e );
387         }
388         catch ( NamingException e )
389         {
390             throw new MappingException( e.getMessage(), e );
391         }
392         finally
393         {
394             close( namingEnumeration );
395         }
396     }
397 
398     public List<String> getRoles( String username, DirContext context, Collection<String> realRoles )
399         throws MappingException
400     {
401         List<String> groups = getGroups( username, context );
402 
403         Map<String, Collection<String>> rolesMapping = ldapRoleMapperConfiguration.getLdapGroupMappings();
404 
405         Set<String> roles = new HashSet<String>( groups.size() );
406 
407         for ( String group : groups )
408         {
409             Collection<String> rolesPerGroup = rolesMapping.get( group );
410             if ( rolesPerGroup != null )
411             {
412                 roles.addAll( rolesPerGroup );
413             }
414             else
415             {
416                 if ( this.useDefaultRoleName && realRoles != null && realRoles.contains( group ) )
417                 {
418                     roles.add( group );
419                 }
420             }
421         }
422 
423         return new ArrayList<String>( roles );
424     }
425 
426     private void close( NamingEnumeration namingEnumeration )
427     {
428         if ( namingEnumeration != null )
429         {
430             try
431             {
432                 namingEnumeration.close();
433             }
434             catch ( NamingException e )
435             {
436                 log.warn( "fail to close namingEnumeration: {}", e.getMessage() );
437             }
438         }
439     }
440 
441     public String getGroupsDn()
442     {
443         return this.groupsDn;
444     }
445 
446     public String getLdapGroupClass()
447     {
448         return this.ldapGroupClass;
449     }
450 
451 
452     public boolean saveRole( String roleName, DirContext context )
453         throws MappingException
454     {
455 
456         if ( hasRole( context, roleName ) )
457         {
458             return true;
459         }
460 
461         String groupName = findGroupName( roleName );
462 
463         if ( groupName == null )
464         {
465             if ( this.useDefaultRoleName )
466             {
467                 groupName = roleName;
468             }
469             else
470             {
471                 log.warn( "skip group creation as no mapping for roleName:'{}'", roleName );
472                 return false;
473             }
474         }
475 
476         List<String> allGroups = getAllGroups( context );
477         if ( allGroups.contains( groupName ) )
478         {
479             log.info( "group {} already exists for role.", groupName, roleName );
480             return false;
481         }
482 
483         Attributes attributes = new BasicAttributes( true );
484         BasicAttribute objectClass = new BasicAttribute( "objectClass" );
485         objectClass.add( "top" );
486         objectClass.add( "groupOfUniqueNames" );
487         attributes.put( objectClass );
488         attributes.put( "cn", groupName );
489 
490         // attribute mandatory when created a group so add admin as default member
491         // TODO make this default configurable
492         BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
493         basicAttribute.add( this.userIdAttribute + "=admin," + getBaseDn() );
494         attributes.put( basicAttribute );
495 
496         try
497         {
498             String dn = "cn=" + groupName + "," + this.groupsDn;
499 
500             context.createSubcontext( dn, attributes );
501 
502             log.info( "created group with dn:'{}", dn );
503 
504             return true;
505         }
506         catch ( NameAlreadyBoundException e )
507         {
508             log.info( "skip group '{}' creation as already exists", groupName );
509             return true;
510         }
511         catch ( LdapException e )
512         {
513             throw new MappingException( e.getMessage(), e );
514 
515         }
516         catch ( NamingException e )
517         {
518             throw new MappingException( e.getMessage(), e );
519         }
520     }
521 
522     public boolean saveUserRole( String roleName, String username, DirContext context )
523         throws MappingException
524     {
525 
526         String groupName = findGroupName( roleName );
527 
528         if ( groupName == null )
529         {
530             log.warn( "no group found for role '{}", roleName );
531             groupName = roleName;
532         }
533 
534         NamingEnumeration<SearchResult> namingEnumeration = null;
535         try
536         {
537             SearchControls searchControls = new SearchControls();
538 
539             searchControls.setDerefLinkFlag( true );
540             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
541 
542             String filter = "objectClass=" + getLdapGroupClass();
543 
544             namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
545 
546             while ( namingEnumeration.hasMore() )
547             {
548                 SearchResult searchResult = namingEnumeration.next();
549                 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
550                 if ( attribute == null )
551                 {
552                     BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
553                     basicAttribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
554                     context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
555                         new ModificationItem( DirContext.ADD_ATTRIBUTE, basicAttribute ) } );
556                 }
557                 else
558                 {
559                     attribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
560                     context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
561                         new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute ) } );
562                 }
563                 return true;
564             }
565 
566             return false;
567         }
568         catch ( LdapException e )
569         {
570             throw new MappingException( e.getMessage(), e );
571         }
572         catch ( NamingException e )
573         {
574             throw new MappingException( e.getMessage(), e );
575         }
576 
577         finally
578         {
579             if ( namingEnumeration != null )
580             {
581                 try
582                 {
583                     namingEnumeration.close();
584                 }
585                 catch ( NamingException e )
586                 {
587                     log.warn( "failed to close search results", e );
588                 }
589             }
590         }
591     }
592 
593     public boolean removeUserRole( String roleName, String username, DirContext context )
594         throws MappingException
595     {
596         String groupName = findGroupName( roleName );
597 
598         if ( groupName == null )
599         {
600             log.warn( "no group found for role '{}", roleName );
601             return false;
602         }
603 
604         NamingEnumeration<SearchResult> namingEnumeration = null;
605         try
606         {
607 
608             SearchControls searchControls = new SearchControls();
609 
610             searchControls.setDerefLinkFlag( true );
611             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
612 
613             String filter = "objectClass=" + getLdapGroupClass();
614 
615             namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
616 
617             while ( namingEnumeration.hasMore() )
618             {
619                 SearchResult searchResult = namingEnumeration.next();
620                 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
621                 if ( attribute != null )
622                 {
623                     BasicAttribute basicAttribute = new BasicAttribute( "uniquemember" );
624                     basicAttribute.add( this.userIdAttribute + "=" + username + "," + getGroupsDn() );
625                     context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
626                         new ModificationItem( DirContext.REMOVE_ATTRIBUTE, basicAttribute ) } );
627                 }
628                 return true;
629             }
630 
631             return false;
632         }
633         catch ( LdapException e )
634         {
635             throw new MappingException( e.getMessage(), e );
636         }
637         catch ( NamingException e )
638         {
639             throw new MappingException( e.getMessage(), e );
640         }
641 
642         finally
643         {
644             if ( namingEnumeration != null )
645             {
646                 try
647                 {
648                     namingEnumeration.close();
649                 }
650                 catch ( NamingException e )
651                 {
652                     log.warn( "failed to close search results", e );
653                 }
654             }
655         }
656     }
657 
658     public void removeAllRoles( DirContext context )
659         throws MappingException
660     {
661         //all mapped roles
662         Collection<String> groups = ldapRoleMapperConfiguration.getLdapGroupMappings().keySet();
663 
664         try
665         {
666             for ( String groupName : groups )
667             {
668 
669                 String dn = "cn=" + groupName + "," + this.groupsDn;
670 
671                 context.unbind( dn );
672 
673                 log.debug( "deleted group with dn:'{}", dn );
674             }
675 
676         }
677         catch ( LdapException e )
678         {
679             throw new MappingException( e.getMessage(), e );
680 
681         }
682         catch ( NamingException e )
683         {
684             throw new MappingException( e.getMessage(), e );
685         }
686     }
687 
688     public void removeRole( String roleName, DirContext context )
689         throws MappingException
690     {
691 
692         String groupName = findGroupName( roleName );
693 
694         try
695         {
696 
697             String dn = "cn=" + groupName + "," + this.groupsDn;
698 
699             context.unbind( dn );
700 
701             log.info( "deleted group with dn:'{}", dn );
702 
703         }
704         catch ( LdapException e )
705         {
706             throw new MappingException( e.getMessage(), e );
707 
708         }
709         catch ( NamingException e )
710         {
711             throw new MappingException( e.getMessage(), e );
712         }
713     }
714 
715     //------------------------------------
716     // Mapping part
717     //------------------------------------
718 
719     //---------------------------------
720     // setters for unit tests
721     //---------------------------------
722 
723 
724     public void setGroupsDn( String groupsDn )
725     {
726         this.groupsDn = groupsDn;
727     }
728 
729     public void setLdapGroupClass( String ldapGroupClass )
730     {
731         this.ldapGroupClass = ldapGroupClass;
732     }
733 
734     public void setUserConf( UserConfiguration userConf )
735     {
736         this.userConf = userConf;
737     }
738 
739     public void setLdapConnectionFactory( LdapConnectionFactory ldapConnectionFactory )
740     {
741         this.ldapConnectionFactory = ldapConnectionFactory;
742     }
743 
744     public String getBaseDn()
745     {
746         return baseDn;
747     }
748 
749     public void setBaseDn( String baseDn )
750     {
751         this.baseDn = baseDn;
752     }
753 
754     //-------------------
755     // utils methods
756     //-------------------
757 
758     protected String findGroupName( String role )
759         throws MappingException
760     {
761         Map<String, Collection<String>> mapping = ldapRoleMapperConfiguration.getLdapGroupMappings();
762 
763         for ( Map.Entry<String, Collection<String>> entry : mapping.entrySet() )
764         {
765             if ( entry.getValue().contains( role ) )
766             {
767                 return entry.getKey();
768             }
769         }
770         return null;
771     }
772 
773 
774     public String getUserIdAttribute()
775     {
776         return userIdAttribute;
777     }
778 
779     public void setUserIdAttribute( String userIdAttribute )
780     {
781         this.userIdAttribute = userIdAttribute;
782     }
783 
784     public boolean isUseDefaultRoleName()
785     {
786         return useDefaultRoleName;
787     }
788 
789     public void setUseDefaultRoleName( boolean useDefaultRoleName )
790     {
791         this.useDefaultRoleName = useDefaultRoleName;
792     }
793 }