001package org.apache.archiva.redback.common.ldap.role;
002/*
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 */
020
021import org.apache.archiva.redback.common.ldap.MappingException;
022import org.apache.archiva.redback.common.ldap.ObjectNotFoundException;
023import org.apache.archiva.redback.common.ldap.connection.LdapConnectionFactory;
024import org.apache.archiva.redback.common.ldap.connection.LdapException;
025import org.apache.archiva.redback.common.ldap.user.LdapUser;
026import org.apache.archiva.redback.configuration.UserConfiguration;
027import org.apache.archiva.redback.configuration.UserConfigurationKeys;
028import org.apache.archiva.redback.users.User;
029import org.apache.archiva.redback.users.UserManager;
030import org.apache.archiva.redback.users.UserManagerException;
031import org.apache.archiva.redback.users.UserNotFoundException;
032import org.apache.commons.lang3.StringUtils;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035import org.springframework.stereotype.Service;
036
037import javax.annotation.PostConstruct;
038import javax.inject.Inject;
039import javax.inject.Named;
040import javax.naming.NameAlreadyBoundException;
041import javax.naming.NameNotFoundException;
042import javax.naming.NamingEnumeration;
043import javax.naming.NamingException;
044import javax.naming.directory.Attribute;
045import javax.naming.directory.Attributes;
046import javax.naming.directory.BasicAttribute;
047import javax.naming.directory.BasicAttributes;
048import javax.naming.directory.DirContext;
049import javax.naming.directory.ModificationItem;
050import javax.naming.directory.SearchControls;
051import javax.naming.directory.SearchResult;
052import javax.naming.ldap.LdapName;
053import javax.naming.ldap.Rdn;
054import java.util.ArrayList;
055import java.util.Collection;
056import java.util.Collections;
057import java.util.HashSet;
058import java.util.List;
059import java.util.Map;
060import java.util.Set;
061
062/**
063 * @author Olivier Lamy
064 * @since 2.1
065 */
066@Service( "ldapRoleMapper#default" )
067public class DefaultLdapRoleMapper
068    implements LdapRoleMapper
069{
070
071    private Logger log = LoggerFactory.getLogger( getClass( ) );
072
073    @Inject
074    @Named( value = "ldapConnectionFactory#configurable" )
075    private LdapConnectionFactory ldapConnectionFactory;
076
077    @Inject
078    @Named( value = "userConfiguration#default" )
079    private UserConfiguration userConf;
080
081    @Inject
082    @Named( value = "ldapRoleMapperConfiguration#default" )
083    private LdapRoleMapperConfiguration ldapRoleMapperConfiguration;
084
085    @Inject
086    @Named( value = "userManager#default" )
087    private UserManager userManager;
088
089    //---------------------------
090    // fields
091    //---------------------------
092
093    private String ldapGroupClass = "groupOfUniqueNames";
094
095    private String groupsDn;
096
097    private String groupFilter;
098
099    private String baseDn;
100
101    private String ldapGroupMemberAttribute = "uniqueMember";
102
103    private boolean useDefaultRoleName = false;
104
105    private String dnAttr = "dn";
106
107    /**
108     * possible to user cn=beer or uid=beer or sn=beer etc
109     * so make it configurable
110     */
111    public static String DEFAULT_USER_ID_ATTRIBUTE = "uid";
112    private String userIdAttribute = DEFAULT_USER_ID_ATTRIBUTE;
113
114    public static String DEFAULT_GROUP_NAME_ATTRIBUTE = "cn";
115    private String groupNameAttribute = DEFAULT_GROUP_NAME_ATTRIBUTE;
116
117    public static String DEFAULT_DESCRIPTION_ATTRIBUTE = "description";
118    private String descriptionAttribute = DEFAULT_DESCRIPTION_ATTRIBUTE;
119
120    // True, if the member attribute stores the DN, otherwise the userkey is used as entry value
121    private boolean useDnAsMemberValue = true;
122
123    private static final String POSIX_GROUP = "posixGroup";
124
125    @PostConstruct
126    public void initialize( )
127    {
128        this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass );
129
130        if (StringUtils.equalsIgnoreCase( POSIX_GROUP, this.ldapGroupClass )) {
131            this.useDnAsMemberValue = false;
132        }
133
134        this.useDnAsMemberValue = userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_DN_AS_MEMBER_VALUE, this.useDnAsMemberValue );
135
136        this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, this.baseDn );
137
138        this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn );
139
140        if ( StringUtils.isEmpty( this.groupsDn ) )
141        {
142            this.groupsDn = this.baseDn;
143        }
144
145        this.groupFilter = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_FILTER, this.groupFilter );
146
147        this.useDefaultRoleName =
148            userConf.getBoolean( UserConfigurationKeys.LDAP_GROUPS_USE_ROLENAME, this.useDefaultRoleName );
149
150        this.userIdAttribute = userConf.getString( UserConfigurationKeys.LDAP_USER_ID_ATTRIBUTE, DEFAULT_USER_ID_ATTRIBUTE );
151
152        this.ldapGroupMemberAttribute = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_MEMBER, this.ldapGroupMemberAttribute );
153
154        this.dnAttr = userConf.getString( UserConfigurationKeys.LDAP_DN_ATTRIBUTE, this.dnAttr );
155
156        this.groupNameAttribute = userConf.getString( UserConfigurationKeys.LDAP_GROUP_NAME_ATTRIBUTE, DEFAULT_GROUP_NAME_ATTRIBUTE );
157
158        this.descriptionAttribute = userConf.getString( UserConfigurationKeys.LDAP_GROUP_DESCRIPTION_ATTRIBUTE, DEFAULT_DESCRIPTION_ATTRIBUTE );
159    }
160
161
162    private String getGroupNameFromResult( SearchResult searchResult ) throws NamingException
163    {
164        Attribute gNameAtt = searchResult.getAttributes( ).get( groupNameAttribute );
165        if ( gNameAtt != null )
166        {
167            return gNameAtt.get( ).toString( );
168        }
169        else
170        {
171            log.error( "Could not get group name from attribute {}. Group DN: {}", groupNameAttribute, searchResult.getNameInNamespace( ) );
172            return "";
173        }
174
175
176    }
177
178    public List<String> getAllGroups( DirContext context )
179        throws MappingException
180    {
181
182        NamingEnumeration<SearchResult> namingEnumeration = null;
183        try
184        {
185
186            SearchControls searchControls = new SearchControls( );
187
188            searchControls.setDerefLinkFlag( true );
189            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
190            searchControls.setReturningAttributes( new String[]{ this.getLdapDnAttribute(), "objectClass", groupNameAttribute} );
191
192            String filter = "objectClass=" + getLdapGroupClass( );
193
194            if ( !StringUtils.isEmpty( this.groupFilter ) )
195            {
196                filter = "(&(" + filter + ")(" + this.groupFilter + "))";
197            }
198
199            namingEnumeration = context.search( getGroupsDn( ), filter, searchControls );
200
201            List<String> allGroups = new ArrayList<String>( );
202
203            while ( namingEnumeration.hasMore( ) )
204            {
205                SearchResult searchResult = namingEnumeration.next( );
206                String groupName = getGroupNameFromResult( searchResult );
207                if ( StringUtils.isNotEmpty( groupName ) )
208                {
209                    log.debug( "Found groupName: '{}", groupName );
210                    allGroups.add( groupName );
211                }
212            }
213
214            return allGroups;
215        }
216        catch ( LdapException e )
217        {
218            throw new MappingException( e.getMessage( ), e );
219        }
220        catch ( NamingException e )
221        {
222            throw new MappingException( e.getMessage( ), e );
223        }
224
225        finally
226        {
227            close( namingEnumeration );
228        }
229    }
230
231    @Override
232    public List<LdapGroup> getAllGroupObjects( DirContext context ) throws MappingException
233    {
234
235        NamingEnumeration<SearchResult> namingEnumeration = null;
236        try
237        {
238
239            SearchControls searchControls = new SearchControls( );
240
241            searchControls.setDerefLinkFlag( true );
242            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
243            searchControls.setReturningAttributes( new String[]{ this.getLdapDnAttribute(), "objectClass", groupNameAttribute,
244            ldapGroupMemberAttribute} );
245
246            String filter = "objectClass=" + getLdapGroupClass( );
247
248            if ( !StringUtils.isEmpty( this.groupFilter ) )
249            {
250                filter = "(&(" + filter + ")(" + this.groupFilter + "))";
251            }
252
253            namingEnumeration = context.search( getGroupsDn( ), filter, searchControls );
254
255            List<LdapGroup> allGroups = new ArrayList<>( );
256
257            while ( namingEnumeration.hasMore( ) )
258            {
259                SearchResult searchResult = namingEnumeration.next( );
260                allGroups.add( getGroupFromResult( searchResult ) );
261            }
262
263            return allGroups;
264        }
265        catch ( LdapException e )
266        {
267            throw new MappingException( e.getMessage( ), e );
268        }
269        catch ( NamingException e )
270        {
271            throw new MappingException( e.getMessage( ), e );
272        }
273        finally
274        {
275            close( namingEnumeration );
276        }
277    }
278
279    @Override
280    public LdapGroup getGroupForName( DirContext context, String groupName ) throws MappingException
281    {
282        NamingEnumeration<SearchResult> namingEnumeration = null;
283        try
284        {
285
286            SearchControls searchControls = new SearchControls( );
287
288            searchControls.setDerefLinkFlag( true );
289            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
290            searchControls.setReturningAttributes( new String[]{this.getLdapDnAttribute( ), "objectClass", groupNameAttribute,
291                ldapGroupMemberAttribute} );
292
293            StringBuilder fiBuilder = new StringBuilder("(&(objectClass=" ).append( getLdapGroupClass( ) ).append(")");
294
295
296            if ( !StringUtils.isEmpty( this.groupFilter ) )
297            {
298                fiBuilder.append("(").append(this.groupFilter).append(")");
299            }
300            fiBuilder.append("(").append(this.groupNameAttribute)
301                .append("=").append(groupName).append("))");
302            namingEnumeration = context.search( getGroupsDn( ), fiBuilder.toString(), searchControls );
303            if (namingEnumeration.hasMore()) {
304                SearchResult result = namingEnumeration.next( );
305                return getGroupFromResult( result );
306            } else {
307                throw new ObjectNotFoundException( "Group not found " + groupName );
308            }
309        }
310        catch ( NamingException e )
311        {
312            log.error( "Naming error while searching for group {}: {}", groupName, e.getMessage( ) );
313            throw new MappingException( "Group search failed " + e.getMessage( ), e );
314        } finally
315        {
316            closeNamingEnumeration( namingEnumeration );
317        }
318    }
319
320    private LdapGroup getGroupFromResult(SearchResult searchResult) throws NamingException
321    {
322        LdapGroup group = new LdapGroup( searchResult.getNameInNamespace() );
323        Attribute attValue = searchResult.getAttributes( ).get( groupNameAttribute );
324        if ( attValue != null )
325        {
326            group.setName( attValue.get( ).toString( ) );
327        }
328        else
329        {
330            log.error( "Could not get group name from attribute {}. Group DN: {}", groupNameAttribute, searchResult.getNameInNamespace( ) );
331        }
332        attValue = searchResult.getAttributes( ).get( descriptionAttribute );
333        if (attValue!=null) {
334            group.setDescription( attValue.get( ).toString( ) );
335        }
336        Attribute memberValues = searchResult.getAttributes( ).get( ldapGroupMemberAttribute );
337        if (memberValues!=null)
338        {
339            NamingEnumeration<?> allMembersEnum = memberValues.getAll( );
340            try
341            {
342                while ( allMembersEnum.hasMore( ) )
343                {
344                    String memberValue = allMembersEnum.next( ).toString( );
345                    if ( !StringUtils.isEmpty( memberValue ) )
346                    {
347                        group.addMember( memberValue );
348                    }
349                }
350            } finally
351            {
352                if (allMembersEnum!=null) {
353                    closeNamingEnumeration( allMembersEnum );
354                }
355            }
356        }
357        return group;
358    }
359
360    protected void closeNamingEnumeration( NamingEnumeration namingEnumeration )
361    {
362        if ( namingEnumeration != null )
363        {
364            try
365            {
366                namingEnumeration.close( );
367            }
368            catch ( NamingException e )
369            {
370                log.warn( "failed to close NamingEnumeration", e );
371            }
372        }
373    }
374
375    public boolean hasRole( DirContext context, String roleName )
376        throws MappingException
377    {
378        String groupName = findGroupName( roleName );
379
380        if ( groupName == null )
381        {
382            if ( this.useDefaultRoleName )
383            {
384                groupName = roleName;
385            }
386            else
387            {
388                log.warn( "skip group creation as no mapping for roleName:'{}'", roleName );
389                return false;
390            }
391        }
392        NamingEnumeration<SearchResult> namingEnumeration = null;
393        try
394        {
395
396            SearchControls searchControls = new SearchControls( );
397
398            searchControls.setDerefLinkFlag( true );
399            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
400
401            String filter = "objectClass=" + getLdapGroupClass( );
402
403            namingEnumeration = context.search( groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), filter, searchControls );
404
405            return namingEnumeration.hasMore( );
406        }
407        catch ( NameNotFoundException e )
408        {
409            log.debug( "group {} for role {} not found", groupName, roleName );
410            return false;
411        }
412        catch ( LdapException e )
413        {
414            throw new MappingException( e.getMessage( ), e );
415        }
416        catch ( NamingException e )
417        {
418            throw new MappingException( e.getMessage( ), e );
419        }
420
421        finally
422        {
423            close( namingEnumeration );
424        }
425    }
426
427    public List<String> getAllRoles( DirContext context )
428        throws MappingException
429    {
430        List<String> groups = getAllGroups( context );
431
432        if ( groups.isEmpty( ) )
433        {
434            return Collections.emptyList( );
435        }
436
437        Set<String> roles = new HashSet<String>( groups.size( ) );
438
439        Map<String, Collection<String>> mapping = ldapRoleMapperConfiguration.getLdapGroupMappings( );
440
441        for ( String group : groups )
442        {
443            Collection<String> rolesPerGroup = mapping.get( group );
444            if ( rolesPerGroup != null )
445            {
446                for ( String role : rolesPerGroup )
447                {
448                    roles.add( role );
449                }
450            }
451        }
452
453        return new ArrayList<String>( roles );
454    }
455
456    public List<String> getGroupsMember( String group, DirContext context )
457        throws MappingException
458    {
459
460        NamingEnumeration<SearchResult> namingEnumeration = null;
461        try
462        {
463
464            SearchControls searchControls = new SearchControls( );
465
466            searchControls.setDerefLinkFlag( true );
467            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
468
469            String filter = "objectClass=" + getLdapGroupClass( );
470
471            namingEnumeration = context.search( groupNameAttribute + "=" + group + "," + getGroupsDn( ), filter, searchControls );
472
473            List<String> allMembers = new ArrayList<String>( );
474
475            while ( namingEnumeration.hasMore( ) )
476            {
477                SearchResult searchResult = namingEnumeration.next( );
478
479                Attribute uniqueMemberAttr = searchResult.getAttributes( ).get( getLdapGroupMemberAttribute( ) );
480
481                if ( uniqueMemberAttr != null )
482                {
483                    NamingEnumeration<?> allMembersEnum = uniqueMemberAttr.getAll( );
484                    while ( allMembersEnum.hasMore( ) )
485                    {
486                        String userName = allMembersEnum.next( ).toString( );
487                        // uid=blabla we only want bla bla
488                        userName = StringUtils.substringAfter( userName, "=" );
489                        userName = StringUtils.substringBefore( userName, "," );
490                        log.debug( "found userName for group {}: '{}", group, userName );
491
492                        allMembers.add( userName );
493                    }
494                    close( allMembersEnum );
495                }
496
497
498            }
499
500            return allMembers;
501        }
502        catch ( LdapException e )
503        {
504            throw new MappingException( e.getMessage( ), e );
505        }
506        catch ( NamingException e )
507        {
508            throw new MappingException( e.getMessage( ), e );
509        }
510
511        finally
512        {
513            close( namingEnumeration );
514        }
515    }
516
517    /*
518     * TODO: Should use LDAP search, as this may not work for users in subtrees
519     */
520    private String getUserDnFromId(String userKey) {
521        return new StringBuilder().append( this.userIdAttribute ).append( "=" ).append( userKey ).append( "," ).append(
522            getBaseDn( ) ).toString();
523    }
524
525    public List<String> getGroups( String username, DirContext context )
526        throws MappingException
527    {
528
529        Set<String> userGroups = new HashSet<String>( );
530
531        NamingEnumeration<SearchResult> namingEnumeration = null;
532        try
533        {
534
535            SearchControls searchControls = new SearchControls( );
536
537            searchControls.setDerefLinkFlag( true );
538            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
539
540
541            String groupEntry = null;
542            try
543            {
544                //try to look the user up
545                User user = userManager.findUser( username );
546                if ( user != null && user instanceof LdapUser )
547                {
548                    // TODO: This is some kind of memberOf retrieval, but will not work. Need to setup a memberOf Attribute
549                    LdapUser ldapUser = (LdapUser) user ;
550                    Attribute dnAttribute = ldapUser.getOriginalAttributes( ).get( getLdapDnAttribute( ) );
551                    if ( dnAttribute != null )
552                    {
553                        groupEntry = dnAttribute.get( ).toString();
554                    }
555
556                }
557            }
558            catch ( UserNotFoundException e )
559            {
560                log.warn( "Failed to look up user {}. Computing distinguished name manually", username, e );
561            }
562            catch ( UserManagerException e )
563            {
564                log.warn( "Failed to look up user {}. Computing distinguished name manually", username, e );
565            }
566            if ( groupEntry == null )
567            {
568                //failed to look up the user's groupEntry directly
569
570                if ( this.useDnAsMemberValue )
571                {
572                    groupEntry = getUserDnFromId( username );
573                }
574                else
575                {
576                    groupEntry = username;
577                }
578            }
579
580            String filter =
581                new StringBuilder( ).append( "(&" ).append( "(objectClass=" + getLdapGroupClass( ) + ")" ).append(
582                    "(" ).append( getLdapGroupMemberAttribute( ) ).append( "=" ).append( Rdn.escapeValue( groupEntry ) ).append( ")" ).append(
583                    ")" ).toString( );
584
585            log.debug( "filter: {}", filter );
586
587            namingEnumeration = context.search( getGroupsDn( ), filter, searchControls );
588
589            while ( namingEnumeration.hasMore( ) )
590            {
591                SearchResult groupSearchResult = namingEnumeration.next( );
592
593                String groupName = getGroupNameFromResult( groupSearchResult );
594
595                if (StringUtils.isNotEmpty( groupName )) {
596                    userGroups.add( groupName );
597                }
598
599
600            }
601
602            return new ArrayList( userGroups );
603        }
604        catch ( LdapException e )
605        {
606            throw new MappingException( e.getMessage( ), e );
607        }
608        catch ( NamingException e )
609        {
610            throw new MappingException( e.getMessage( ), e );
611        }
612        finally
613        {
614            close( namingEnumeration );
615        }
616    }
617
618    /*
619     * TODO: We should implement recursive group retrieval
620     *  Need a configuration flag, to activate recursion
621     */
622    @Override
623    public List<LdapGroup> getGroupObjects( String username, DirContext context ) throws MappingException
624    {
625        Set<LdapGroup> userGroups = new HashSet<>( );
626
627        NamingEnumeration<SearchResult> namingEnumeration = null;
628        try
629        {
630
631            SearchControls searchControls = new SearchControls( );
632
633            searchControls.setDerefLinkFlag( true );
634            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
635
636
637            String userIdentifier = null;
638            String userDn = null;
639            try
640            {
641                //try to look the user up
642                User user = userManager.findUser( username );
643                if ( user != null && user instanceof LdapUser )
644                {
645                    // TODO: This is some kind of memberOf retrieval, but will not work with DN.
646                    // We need a configuration entry for the memberOf attribute and a flag, if this should be used
647                    LdapUser ldapUser = (LdapUser) user ;
648                    Attribute dnAttribute = ldapUser.getOriginalAttributes( ).get( getLdapDnAttribute( ) );
649                    if ( dnAttribute != null )
650                    {
651                        userIdentifier = dnAttribute.get( ).toString();
652                    }
653                    userDn = ldapUser.getDn( );
654
655                }
656            }
657            catch ( UserNotFoundException e )
658            {
659                log.warn( "Failed to look up user {}. Computing distinguished name manually", username, e );
660            }
661            catch ( UserManagerException e )
662            {
663                log.warn( "Failed to look up user {}. Computing distinguished name manually", username, e );
664            }
665            if ( userIdentifier == null )
666            {
667                //failed to look up the user's groupEntry directly
668
669                if ( this.useDnAsMemberValue )
670                {
671                    userIdentifier = userDn;
672                }
673                else
674                {
675                    userIdentifier = username;
676                }
677            }
678
679            String filter =
680                new StringBuilder( ).append( "(&" ).append( "(objectClass=" + getLdapGroupClass( ) + ")" ).append(
681                    "(" ).append( getLdapGroupMemberAttribute( ) ).append( "=" ).append( Rdn.escapeValue( userIdentifier ) ).append( ")" ).append(
682                    ")" ).toString( );
683
684            log.debug( "filter: {}", filter );
685
686            namingEnumeration = context.search( getGroupsDn( ), filter, searchControls );
687
688            while ( namingEnumeration.hasMore( ) )
689            {
690                SearchResult groupSearchResult = namingEnumeration.next( );
691                LdapGroup groupName = getGroupFromResult( groupSearchResult );
692                userGroups.add( groupName );
693            }
694        }
695        catch ( LdapException e )
696        {
697            throw new MappingException( e.getMessage( ), e );
698        }
699        catch ( NamingException e )
700        {
701            throw new MappingException( e.getMessage( ), e );
702        }
703        finally
704        {
705            close( namingEnumeration );
706        }
707        return new ArrayList( userGroups );
708    }
709
710    public List<String> getRoles( String username, DirContext context, Collection<String> realRoles )
711        throws MappingException
712    {
713        List<String> groups = getGroups( username, context );
714
715        Map<String, Collection<String>> rolesMapping = ldapRoleMapperConfiguration.getLdapGroupMappings( );
716
717        Set<String> roles = new HashSet<String>( groups.size( ) );
718
719        for ( String group : groups )
720        {
721            Collection<String> rolesPerGroup = rolesMapping.get( group );
722            if ( rolesPerGroup != null )
723            {
724                roles.addAll( rolesPerGroup );
725            }
726            else
727            {
728                if ( this.useDefaultRoleName && realRoles != null && realRoles.contains( group ) )
729                {
730                    roles.add( group );
731                }
732            }
733        }
734
735        return new ArrayList<String>( roles );
736    }
737
738    private void close( NamingEnumeration namingEnumeration )
739    {
740        if ( namingEnumeration != null )
741        {
742            try
743            {
744                namingEnumeration.close( );
745            }
746            catch ( NamingException e )
747            {
748                log.warn( "fail to close namingEnumeration: {}", e.getMessage( ) );
749            }
750        }
751    }
752
753    public String getGroupsDn( )
754    {
755        return this.groupsDn;
756    }
757
758    public String getLdapGroupClass( )
759    {
760        return this.ldapGroupClass;
761    }
762
763    public String getLdapDnAttribute( )
764    {
765        return this.dnAttr;
766    }
767
768    public boolean saveRole( String roleName, DirContext context )
769        throws MappingException
770    {
771
772        if ( hasRole( context, roleName ) )
773        {
774            return true;
775        }
776
777        String groupName = findGroupName( roleName );
778
779        if ( groupName == null )
780        {
781            if ( this.useDefaultRoleName )
782            {
783                groupName = roleName;
784            }
785            else
786            {
787                log.warn( "skip group creation as no mapping for roleName:'{}'", roleName );
788                return false;
789            }
790        }
791
792        List<String> allGroups = getAllGroups( context );
793        if ( allGroups.contains( groupName ) )
794        {
795            log.info( "group {} already exists for role.", groupName, roleName );
796            return false;
797        }
798
799        Attributes attributes = new BasicAttributes( true );
800        BasicAttribute objectClass = new BasicAttribute( "objectClass" );
801        objectClass.add( "top" );
802        objectClass.add( "groupOfUniqueNames" );
803        attributes.put( objectClass );
804        attributes.put( this.groupNameAttribute, groupName );
805
806        // attribute mandatory when created a group so add admin as default member
807        BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMemberAttribute( ) );
808        basicAttribute.add( this.userIdAttribute + "=admin," + getBaseDn( ) );
809        attributes.put( basicAttribute );
810
811        try
812        {
813            String dn = this.groupNameAttribute + "=" + groupName + "," + this.groupsDn;
814
815            context.createSubcontext( dn, attributes );
816
817            log.info( "created group with dn:'{}", dn );
818
819            return true;
820        }
821        catch ( NameAlreadyBoundException e )
822        {
823            log.info( "skip group '{}' creation as already exists", groupName );
824            return true;
825        }
826        catch ( LdapException e )
827        {
828            throw new MappingException( e.getMessage( ), e );
829
830        }
831        catch ( NamingException e )
832        {
833            throw new MappingException( e.getMessage( ), e );
834        }
835    }
836
837    public boolean saveUserRole( String roleName, String username, DirContext context )
838        throws MappingException
839    {
840
841        String groupName = findGroupName( roleName );
842
843        if ( groupName == null )
844        {
845            log.warn( "no group found for role '{}", roleName );
846            groupName = roleName;
847        }
848
849        NamingEnumeration<SearchResult> namingEnumeration = null;
850        try
851        {
852            SearchControls searchControls = new SearchControls( );
853
854            searchControls.setDerefLinkFlag( true );
855            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
856
857            String filter = "objectClass=" + getLdapGroupClass( );
858
859            namingEnumeration = context.search( this.groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), filter, searchControls );
860
861            if ( namingEnumeration.hasMore( ) )
862            {
863                SearchResult searchResult = namingEnumeration.next( );
864                Attribute attribute = searchResult.getAttributes( ).get( getLdapGroupMemberAttribute( ) );
865                if ( attribute == null )
866                {
867                    BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMemberAttribute( ) );
868                    basicAttribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn( ) );
869                    context.modifyAttributes( this.groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), new ModificationItem[]{
870                        new ModificationItem( DirContext.ADD_ATTRIBUTE, basicAttribute )} );
871                }
872                else
873                {
874                    attribute.add( this.userIdAttribute + "=" + username + "," + getBaseDn( ) );
875                    context.modifyAttributes( this.groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), new ModificationItem[]{
876                        new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attribute )} );
877                }
878                return true;
879            }
880
881            return false;
882        }
883        catch ( LdapException e )
884        {
885            throw new MappingException( e.getMessage( ), e );
886        }
887        catch ( NamingException e )
888        {
889            throw new MappingException( e.getMessage( ), e );
890        }
891
892        finally
893        {
894            if ( namingEnumeration != null )
895            {
896                try
897                {
898                    namingEnumeration.close( );
899                }
900                catch ( NamingException e )
901                {
902                    log.warn( "failed to close search results", e );
903                }
904            }
905        }
906    }
907
908    public boolean removeUserRole( String roleName, String username, DirContext context )
909        throws MappingException
910    {
911        String groupName = findGroupName( roleName );
912
913        if ( groupName == null )
914        {
915            log.warn( "no group found for role '{}", roleName );
916            return false;
917        }
918
919        NamingEnumeration<SearchResult> namingEnumeration = null;
920        try
921        {
922
923            SearchControls searchControls = new SearchControls( );
924
925            searchControls.setDerefLinkFlag( true );
926            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
927
928            String filter = "objectClass=" + getLdapGroupClass( );
929
930            namingEnumeration = context.search( groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), filter, searchControls );
931
932            if ( namingEnumeration.hasMore( ) )
933            {
934                SearchResult searchResult = namingEnumeration.next( );
935                Attribute attribute = searchResult.getAttributes( ).get( getLdapGroupMemberAttribute( ) );
936                if ( attribute != null )
937                {
938                    BasicAttribute basicAttribute = new BasicAttribute( getLdapGroupMemberAttribute( ) );
939                    basicAttribute.add( this.userIdAttribute + "=" + username + "," + getGroupsDn( ) );
940                    context.modifyAttributes( groupNameAttribute + "=" + groupName + "," + getGroupsDn( ), new ModificationItem[]{
941                        new ModificationItem( DirContext.REMOVE_ATTRIBUTE, basicAttribute )} );
942                }
943                return true;
944            }
945
946            return false;
947        }
948        catch ( LdapException e )
949        {
950            throw new MappingException( e.getMessage( ), e );
951        }
952        catch ( NamingException e )
953        {
954            throw new MappingException( e.getMessage( ), e );
955        }
956
957        finally
958        {
959            if ( namingEnumeration != null )
960            {
961                try
962                {
963                    namingEnumeration.close( );
964                }
965                catch ( NamingException e )
966                {
967                    log.warn( "failed to close search results", e );
968                }
969            }
970        }
971    }
972
973
974
975    public void removeAllRoles( DirContext context )
976        throws MappingException
977    {
978        //all mapped roles
979        Collection<String> groups = ldapRoleMapperConfiguration.getLdapGroupMappings( ).keySet( );
980
981        try
982        {
983            for ( String groupName : groups )
984            {
985                removeGroupByName( context, groupName );
986            }
987
988        }
989        catch ( LdapException e )
990        {
991            throw new MappingException( e.getMessage( ), e );
992
993        }
994        catch ( NamingException e )
995        {
996            throw new MappingException( e.getMessage( ), e );
997        }
998    }
999
1000    private void removeGroupByName( DirContext context, String groupName ) throws NamingException
1001    {
1002        NamingEnumeration<SearchResult> namingEnumeration = null;
1003        try
1004        {
1005            SearchControls searchControls = new SearchControls( );
1006
1007            searchControls.setDerefLinkFlag( true );
1008            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
1009            String filter = "(&(objectClass=" + getLdapGroupClass( ) + ")(" + groupNameAttribute + "=" + Rdn.escapeValue( groupName ) + "))";
1010            // String filter = "(&(objectClass=" + getLdapGroupClass( ) + "))";
1011            namingEnumeration = context.search(  getGroupsDn( ), filter, searchControls );
1012
1013            // We delete only the first found group
1014            if ( namingEnumeration != null && namingEnumeration.hasMore( ) )
1015            {
1016                SearchResult result = namingEnumeration.next( );
1017                String dn = result.getNameInNamespace( );
1018                context.unbind( new LdapName( dn ) );
1019                log.debug( "Deleted group with dn:'{}", dn );
1020            }
1021        }
1022        finally
1023        {
1024            closeNamingEnumeration( namingEnumeration );
1025        }
1026    }
1027
1028    public void removeRole( String roleName, DirContext context )
1029        throws MappingException
1030    {
1031
1032        String groupName = findGroupName( roleName );
1033        if (StringUtils.isEmpty( groupName )) {
1034            log.warn( "No group for the given role found: role={}", roleName );
1035            return;
1036        }
1037        try
1038        {
1039
1040            removeGroupByName( context, groupName );
1041
1042        }
1043        catch ( LdapException e )
1044        {
1045            throw new MappingException( e.getMessage( ), e );
1046
1047        }
1048        catch ( NamingException e )
1049        {
1050            throw new MappingException( e.getMessage( ), e );
1051        }
1052    }
1053
1054    //------------------------------------
1055    // Mapping part
1056    //------------------------------------
1057
1058    //---------------------------------
1059    // setters for unit tests
1060    //---------------------------------
1061
1062
1063    public void setGroupsDn( String groupsDn )
1064    {
1065        this.groupsDn = groupsDn;
1066    }
1067
1068    public void setLdapGroupClass( String ldapGroupClass )
1069    {
1070        this.ldapGroupClass = ldapGroupClass;
1071    }
1072
1073    public void setUserConf( UserConfiguration userConf )
1074    {
1075        this.userConf = userConf;
1076    }
1077
1078    public void setLdapConnectionFactory( LdapConnectionFactory ldapConnectionFactory )
1079    {
1080        this.ldapConnectionFactory = ldapConnectionFactory;
1081    }
1082
1083    public String getBaseDn( )
1084    {
1085        return baseDn;
1086    }
1087
1088    public void setBaseDn( String baseDn )
1089    {
1090        this.baseDn = baseDn;
1091    }
1092
1093    public String getLdapGroupMemberAttribute( )
1094    {
1095        return ldapGroupMemberAttribute;
1096    }
1097
1098    public void setLdapGroupMemberAttribute( String ldapGroupMemberAttribute )
1099    {
1100        this.ldapGroupMemberAttribute = ldapGroupMemberAttribute;
1101    }
1102
1103    //-------------------
1104    // utils methods
1105    //-------------------
1106
1107    protected String findGroupName( String role )
1108        throws MappingException
1109    {
1110        Map<String, Collection<String>> mapping = ldapRoleMapperConfiguration.getLdapGroupMappings( );
1111
1112        for ( Map.Entry<String, Collection<String>> entry : mapping.entrySet( ) )
1113        {
1114            if ( entry.getValue( ).contains( role ) )
1115            {
1116                return entry.getKey( );
1117            }
1118        }
1119        return null;
1120    }
1121
1122
1123    public String getUserIdAttribute( )
1124    {
1125        return userIdAttribute;
1126    }
1127
1128    public void setUserIdAttribute( String userIdAttribute )
1129    {
1130        this.userIdAttribute = userIdAttribute;
1131    }
1132
1133    public boolean isUseDefaultRoleName( )
1134    {
1135        return useDefaultRoleName;
1136    }
1137
1138    public void setUseDefaultRoleName( boolean useDefaultRoleName )
1139    {
1140        this.useDefaultRoleName = useDefaultRoleName;
1141    }
1142}