]> source.dussan.org Git - archiva.git/blob
a379a94940724e7b364ce8525bde8ad87e0ca8a6
[archiva.git] /
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     private LdapConnectionFactory ldapConnectionFactory;
69
70     @Inject
71     @Named( value = "userConfiguration#default" )
72     private UserConfiguration userConf;
73
74     //---------------------------
75     // fields
76     //---------------------------
77
78     private String ldapGroupClass = "groupOfUniqueNames";
79
80     private String groupsDn;
81
82     private String baseDn;
83
84     private boolean useDefaultRoleName = false;
85
86     /**
87      * possible to user cn=beer or uid=beer or sn=beer etc
88      * so make it configurable
89      */
90     private String userIdAttribute = "uid";
91
92     @PostConstruct
93     public void initialize()
94     {
95         this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass );
96
97         this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, this.baseDn );
98
99         this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn );
100
101         if ( StringUtils.isEmpty( this.groupsDn ) )
102         {
103             this.groupsDn = this.baseDn;
104         }
105
106         this.useDefaultRoleName =
107             userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_ROLENAME, this.useDefaultRoleName );
108
109         this.userIdAttribute = userConf.getString( UserConfigurationKeys.LDAP_USER_ID_ATTRIBUTE, this.userIdAttribute );
110     }
111
112     public String getLdapGroup( String role )
113     {
114         return userConf.getString( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY + role );
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 = 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 = 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     public void addLdapMapping( String role, String ldapGroup )
452     {
453         log.warn( "addLdapMapping not implemented" );
454     }
455
456     public void removeLdapMapping( String role )
457     {
458         log.warn( "removeLdapMapping not implemented" );
459     }
460
461     public void setLdapGroupMappings( Map<String, Collection<String>> mappings )
462         throws MappingException
463     {
464         log.warn( "setLdapGroupMappings not implemented" );
465     }
466
467     public Map<String, Collection<String>> getLdapGroupMappings()
468     {
469         Multimap<String, String> map = ArrayListMultimap.create();
470
471         Collection<String> keys = userConf.getKeys();
472
473         for ( String key : keys )
474         {
475             if ( key.startsWith( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ) )
476             {
477                 String val = userConf.getString( key );
478                 String[] roles = StringUtils.split( val, ',' );
479                 for ( String role : roles )
480                 {
481                     map.put( StringUtils.substringAfter( key, UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ),
482                              role );
483                 }
484             }
485         }
486
487         return map.asMap();
488     }
489
490     public boolean saveRole( String roleName, DirContext context )
491         throws MappingException
492     {
493
494         if ( hasRole( context, roleName ) )
495         {
496             return true;
497         }
498
499         String groupName = findGroupName( roleName );
500
501         if ( groupName == null )
502         {
503             if ( this.useDefaultRoleName )
504             {
505                 groupName = roleName;
506             }
507             else
508             {
509                 log.warn( "skip group creation as no mapping fro roleName:'{}'", roleName );
510                 return false;
511             }
512         }
513
514         List<String> allGroups = getAllGroups( context );
515         if ( allGroups.contains( groupName ) )
516         {
517             log.info( "group {} already exists for role.", groupName, roleName );
518             return false;
519         }
520
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 );
527
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 );
533
534         try
535         {
536             String dn = "cn=" + groupName + "," + this.groupsDn;
537
538             context.createSubcontext( dn, attributes );
539
540             log.info( "created group with dn:'{}", dn );
541
542             return true;
543         }
544         catch ( NameAlreadyBoundException e )
545         {
546             log.info( "skip group '{}' creation as already exists", groupName );
547             return true;
548         }
549         catch ( LdapException e )
550         {
551             throw new MappingException( e.getMessage(), e );
552
553         }
554         catch ( NamingException e )
555         {
556             throw new MappingException( e.getMessage(), e );
557         }
558     }
559
560     public boolean saveUserRole( String roleName, String username, DirContext context )
561         throws MappingException
562     {
563
564         String groupName = findGroupName( roleName );
565
566         if ( groupName == null )
567         {
568             log.warn( "no group found for role '{}", roleName );
569             groupName = roleName;
570         }
571
572         NamingEnumeration<SearchResult> namingEnumeration = null;
573         try
574         {
575             SearchControls searchControls = new SearchControls();
576
577             searchControls.setDerefLinkFlag( true );
578             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
579
580             String filter = "objectClass=" + getLdapGroupClass();
581
582             namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
583
584             while ( namingEnumeration.hasMore() )
585             {
586                 SearchResult searchResult = namingEnumeration.next();
587                 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
588                 if ( attribute == null )
589                 {
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 ) } );
594                 }
595                 else
596                 {
597                     attribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
598                     context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
599                         new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute ) } );
600                 }
601                 return true;
602             }
603
604             return false;
605         }
606         catch ( LdapException e )
607         {
608             throw new MappingException( e.getMessage(), e );
609         }
610         catch ( NamingException e )
611         {
612             throw new MappingException( e.getMessage(), e );
613         }
614
615         finally
616         {
617             if ( namingEnumeration != null )
618             {
619                 try
620                 {
621                     namingEnumeration.close();
622                 }
623                 catch ( NamingException e )
624                 {
625                     log.warn( "failed to close search results", e );
626                 }
627             }
628         }
629     }
630
631     public boolean removeUserRole( String roleName, String username, DirContext context )
632         throws MappingException
633     {
634         String groupName = findGroupName( roleName );
635
636         if ( groupName == null )
637         {
638             log.warn( "no group found for role '{}", roleName );
639             return false;
640         }
641
642         NamingEnumeration<SearchResult> namingEnumeration = null;
643         try
644         {
645
646             SearchControls searchControls = new SearchControls();
647
648             searchControls.setDerefLinkFlag( true );
649             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
650
651             String filter = "objectClass=" + getLdapGroupClass();
652
653             namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
654
655             while ( namingEnumeration.hasMore() )
656             {
657                 SearchResult searchResult = namingEnumeration.next();
658                 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
659                 if ( attribute != null )
660                 {
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 ) } );
665                 }
666                 return true;
667             }
668
669             return false;
670         }
671         catch ( LdapException e )
672         {
673             throw new MappingException( e.getMessage(), e );
674         }
675         catch ( NamingException e )
676         {
677             throw new MappingException( e.getMessage(), e );
678         }
679
680         finally
681         {
682             if ( namingEnumeration != null )
683             {
684                 try
685                 {
686                     namingEnumeration.close();
687                 }
688                 catch ( NamingException e )
689                 {
690                     log.warn( "failed to close search results", e );
691                 }
692             }
693         }
694     }
695
696     public void removeAllRoles( DirContext context )
697         throws MappingException
698     {
699         //all mapped roles
700         Collection<String> groups = getLdapGroupMappings().keySet();
701
702         try
703         {
704             for ( String groupName : groups )
705             {
706
707                 String dn = "cn=" + groupName + "," + this.groupsDn;
708
709                 context.unbind( dn );
710
711                 log.debug( "deleted group with dn:'{}", dn );
712             }
713
714         }
715         catch ( LdapException e )
716         {
717             throw new MappingException( e.getMessage(), e );
718
719         }
720         catch ( NamingException e )
721         {
722             throw new MappingException( e.getMessage(), e );
723         }
724     }
725
726     public void removeRole( String roleName, DirContext context )
727         throws MappingException
728     {
729
730         String groupName = findGroupName( roleName );
731
732         try
733         {
734
735             String dn = "cn=" + groupName + "," + this.groupsDn;
736
737             context.unbind( dn );
738
739             log.info( "deleted group with dn:'{}", dn );
740
741         }
742         catch ( LdapException e )
743         {
744             throw new MappingException( e.getMessage(), e );
745
746         }
747         catch ( NamingException e )
748         {
749             throw new MappingException( e.getMessage(), e );
750         }
751     }
752
753     //---------------------------------
754     // setters for unit tests
755     //---------------------------------
756
757
758     public void setGroupsDn( String groupsDn )
759     {
760         this.groupsDn = groupsDn;
761     }
762
763     public void setLdapGroupClass( String ldapGroupClass )
764     {
765         this.ldapGroupClass = ldapGroupClass;
766     }
767
768     public void setUserConf( UserConfiguration userConf )
769     {
770         this.userConf = userConf;
771     }
772
773     public void setLdapConnectionFactory( LdapConnectionFactory ldapConnectionFactory )
774     {
775         this.ldapConnectionFactory = ldapConnectionFactory;
776     }
777
778     public String getBaseDn()
779     {
780         return baseDn;
781     }
782
783     public void setBaseDn( String baseDn )
784     {
785         this.baseDn = baseDn;
786     }
787
788     //-------------------
789     // utils methods
790     //-------------------
791
792     protected String findGroupName( String role )
793     {
794         Map<String, Collection<String>> mapping = getLdapGroupMappings();
795
796         for ( Map.Entry<String, Collection<String>> entry : mapping.entrySet() )
797         {
798             if ( entry.getValue().contains( role ) )
799             {
800                 return entry.getKey();
801             }
802         }
803         return null;
804     }
805
806
807     public String getUserIdAttribute()
808     {
809         return userIdAttribute;
810     }
811
812     public void setUserIdAttribute( String userIdAttribute )
813     {
814         this.userIdAttribute = userIdAttribute;
815     }
816 }