]> source.dussan.org Git - archiva.git/blob
722ebcafff8d54fdeb5e21962b68cb518ba7d395
[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 )
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                 for ( String role : rolesPerGroup )
413                 {
414                     roles.add( role );
415                 }
416             }
417             else
418             {
419                 if ( this.useDefaultRoleName )
420                 {
421                     roles.add( group );
422                 }
423             }
424         }
425
426         return new ArrayList<String>( roles );
427     }
428
429     private void close( NamingEnumeration namingEnumeration )
430     {
431         if ( namingEnumeration != null )
432         {
433             try
434             {
435                 namingEnumeration.close();
436             }
437             catch ( NamingException e )
438             {
439                 log.warn( "fail to close namingEnumeration: {}", e.getMessage() );
440             }
441         }
442     }
443
444     public String getGroupsDn()
445     {
446         return this.groupsDn;
447     }
448
449     public String getLdapGroupClass()
450     {
451         return this.ldapGroupClass;
452     }
453
454     public void addLdapMapping( String role, String ldapGroup )
455     {
456         log.warn( "addLdapMapping not implemented" );
457     }
458
459     public void removeLdapMapping( String role )
460     {
461         log.warn( "removeLdapMapping not implemented" );
462     }
463
464     public void setLdapGroupMappings( Map<String, Collection<String>> mappings )
465         throws MappingException
466     {
467         log.warn( "setLdapGroupMappings not implemented" );
468     }
469
470     public Map<String, Collection<String>> getLdapGroupMappings()
471     {
472         Multimap<String, String> map = ArrayListMultimap.create();
473
474         Collection<String> keys = userConf.getKeys();
475
476         for ( String key : keys )
477         {
478             if ( key.startsWith( UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ) )
479             {
480                 String val = userConf.getString( key );
481                 String[] roles = StringUtils.split( val, ',' );
482                 for ( String role : roles )
483                 {
484                     map.put( StringUtils.substringAfter( key, UserConfigurationKeys.LDAP_GROUPS_ROLE_START_KEY ),
485                              role );
486                 }
487             }
488         }
489
490         return map.asMap();
491     }
492
493     public boolean saveRole( String roleName, DirContext context )
494         throws MappingException
495     {
496
497         if ( hasRole( context, roleName ) )
498         {
499             return true;
500         }
501
502         String groupName = findGroupName( roleName );
503
504         if ( groupName == null )
505         {
506             if ( this.useDefaultRoleName )
507             {
508                 groupName = roleName;
509             }
510             else
511             {
512                 log.warn( "skip group creation as no mapping fro roleName:'{}'", roleName );
513                 return false;
514             }
515         }
516
517         List<String> allGroups = getAllGroups( context );
518         if ( allGroups.contains( groupName ) )
519         {
520             log.info( "group {} already exists for role.", groupName, roleName );
521             return false;
522         }
523
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 );
530
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 );
536
537         try
538         {
539             String dn = "cn=" + groupName + "," + this.groupsDn;
540
541             context.createSubcontext( dn, attributes );
542
543             log.info( "created group with dn:'{}", dn );
544
545             return true;
546         }
547         catch ( NameAlreadyBoundException e )
548         {
549             log.info( "skip group '{}' creation as already exists", groupName );
550             return true;
551         }
552         catch ( LdapException e )
553         {
554             throw new MappingException( e.getMessage(), e );
555
556         }
557         catch ( NamingException e )
558         {
559             throw new MappingException( e.getMessage(), e );
560         }
561     }
562
563     public boolean saveUserRole( String roleName, String username, DirContext context )
564         throws MappingException
565     {
566
567         String groupName = findGroupName( roleName );
568
569         if ( groupName == null )
570         {
571             log.warn( "no group found for role '{}", roleName );
572             groupName = roleName;
573         }
574
575         NamingEnumeration<SearchResult> namingEnumeration = null;
576         try
577         {
578             SearchControls searchControls = new SearchControls();
579
580             searchControls.setDerefLinkFlag( true );
581             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
582
583             String filter = "objectClass=" + getLdapGroupClass();
584
585             namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
586
587             while ( namingEnumeration.hasMore() )
588             {
589                 SearchResult searchResult = namingEnumeration.next();
590                 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
591                 if ( attribute == null )
592                 {
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 ) } );
597                 }
598                 else
599                 {
600                     attribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn() );
601                     context.modifyAttributes( "cn=" + groupName + "," + getGroupsDn(), new ModificationItem[]{
602                         new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute ) } );
603                 }
604                 return true;
605             }
606
607             return false;
608         }
609         catch ( LdapException e )
610         {
611             throw new MappingException( e.getMessage(), e );
612         }
613         catch ( NamingException e )
614         {
615             throw new MappingException( e.getMessage(), e );
616         }
617
618         finally
619         {
620             if ( namingEnumeration != null )
621             {
622                 try
623                 {
624                     namingEnumeration.close();
625                 }
626                 catch ( NamingException e )
627                 {
628                     log.warn( "failed to close search results", e );
629                 }
630             }
631         }
632     }
633
634     public boolean removeUserRole( String roleName, String username, DirContext context )
635         throws MappingException
636     {
637         String groupName = findGroupName( roleName );
638
639         if ( groupName == null )
640         {
641             log.warn( "no group found for role '{}", roleName );
642             return false;
643         }
644
645         NamingEnumeration<SearchResult> namingEnumeration = null;
646         try
647         {
648
649             SearchControls searchControls = new SearchControls();
650
651             searchControls.setDerefLinkFlag( true );
652             searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
653
654             String filter = "objectClass=" + getLdapGroupClass();
655
656             namingEnumeration = context.search( "cn=" + groupName + "," + getGroupsDn(), filter, searchControls );
657
658             while ( namingEnumeration.hasMore() )
659             {
660                 SearchResult searchResult = namingEnumeration.next();
661                 Attribute attribute = searchResult.getAttributes().get( "uniquemember" );
662                 if ( attribute != null )
663                 {
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 ) } );
668                 }
669                 return true;
670             }
671
672             return false;
673         }
674         catch ( LdapException e )
675         {
676             throw new MappingException( e.getMessage(), e );
677         }
678         catch ( NamingException e )
679         {
680             throw new MappingException( e.getMessage(), e );
681         }
682
683         finally
684         {
685             if ( namingEnumeration != null )
686             {
687                 try
688                 {
689                     namingEnumeration.close();
690                 }
691                 catch ( NamingException e )
692                 {
693                     log.warn( "failed to close search results", e );
694                 }
695             }
696         }
697     }
698
699     public void removeAllRoles( DirContext context )
700         throws MappingException
701     {
702         //all mapped roles
703         Collection<String> groups = getLdapGroupMappings().keySet();
704
705         try
706         {
707             for ( String groupName : groups )
708             {
709
710                 String dn = "cn=" + groupName + "," + this.groupsDn;
711
712                 context.unbind( dn );
713
714                 log.debug( "deleted group with dn:'{}", dn );
715             }
716
717         }
718         catch ( LdapException e )
719         {
720             throw new MappingException( e.getMessage(), e );
721
722         }
723         catch ( NamingException e )
724         {
725             throw new MappingException( e.getMessage(), e );
726         }
727     }
728
729     public void removeRole( String roleName, DirContext context )
730         throws MappingException
731     {
732
733         String groupName = findGroupName( roleName );
734
735         try
736         {
737
738             String dn = "cn=" + groupName + "," + this.groupsDn;
739
740             context.unbind( dn );
741
742             log.info( "deleted group with dn:'{}", dn );
743
744         }
745         catch ( LdapException e )
746         {
747             throw new MappingException( e.getMessage(), e );
748
749         }
750         catch ( NamingException e )
751         {
752             throw new MappingException( e.getMessage(), e );
753         }
754     }
755
756     //---------------------------------
757     // setters for unit tests
758     //---------------------------------
759
760
761     public void setGroupsDn( String groupsDn )
762     {
763         this.groupsDn = groupsDn;
764     }
765
766     public void setLdapGroupClass( String ldapGroupClass )
767     {
768         this.ldapGroupClass = ldapGroupClass;
769     }
770
771     public void setUserConf( UserConfiguration userConf )
772     {
773         this.userConf = userConf;
774     }
775
776     public void setLdapConnectionFactory( LdapConnectionFactory ldapConnectionFactory )
777     {
778         this.ldapConnectionFactory = ldapConnectionFactory;
779     }
780
781     public String getBaseDn()
782     {
783         return baseDn;
784     }
785
786     public void setBaseDn( String baseDn )
787     {
788         this.baseDn = baseDn;
789     }
790
791     //-------------------
792     // utils methods
793     //-------------------
794
795     protected String findGroupName( String role )
796     {
797         Map<String, Collection<String>> mapping = getLdapGroupMappings();
798
799         for ( Map.Entry<String, Collection<String>> entry : mapping.entrySet() )
800         {
801             if ( entry.getValue().contains( role ) )
802             {
803                 return entry.getKey();
804             }
805         }
806         return null;
807     }
808
809
810     public String getUserIdAttribute()
811     {
812         return userIdAttribute;
813     }
814
815     public void setUserIdAttribute( String userIdAttribute )
816     {
817         this.userIdAttribute = userIdAttribute;
818     }
819 }