001package org.apache.archiva.redback.users.ldap.ctl;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.redback.common.ldap.MappingException;
023import org.apache.archiva.redback.common.ldap.user.LdapUser;
024import org.apache.archiva.redback.common.ldap.user.LdapUserMapper;
025import org.apache.archiva.redback.common.ldap.user.UserMapper;
026import org.apache.archiva.redback.configuration.UserConfiguration;
027import org.apache.archiva.redback.configuration.UserConfigurationKeys;
028import org.apache.archiva.redback.policy.PasswordEncoder;
029import org.apache.archiva.redback.policy.encoders.SHA1PasswordEncoder;
030import org.apache.archiva.redback.users.User;
031import org.apache.archiva.redback.users.UserManager;
032import org.apache.archiva.redback.users.ldap.LdapUserQuery;
033import org.apache.commons.lang3.StringUtils;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036import org.springframework.stereotype.Service;
037
038import javax.annotation.PostConstruct;
039import javax.inject.Inject;
040import javax.inject.Named;
041import javax.naming.NamingEnumeration;
042import javax.naming.NamingException;
043import javax.naming.directory.Attribute;
044import javax.naming.directory.Attributes;
045import javax.naming.directory.BasicAttribute;
046import javax.naming.directory.BasicAttributes;
047import javax.naming.directory.DirContext;
048import javax.naming.directory.SearchControls;
049import javax.naming.directory.SearchResult;
050import java.util.Collection;
051import java.util.HashMap;
052import java.util.HashSet;
053import java.util.LinkedHashSet;
054import java.util.LinkedList;
055import java.util.List;
056import java.util.Map;
057import java.util.Set;
058
059/**
060 * @author  jesse
061 */
062@Service
063public class DefaultLdapController
064    implements LdapController
065{
066
067    private Logger log = LoggerFactory.getLogger( getClass() );
068
069    @Inject
070    @Named(value = "userMapper#ldap")
071    private UserMapper mapper;
072
073    @Inject
074    @Named( value = "userConfiguration#default" )
075    private UserConfiguration userConf;
076
077    private boolean writableLdap = false;
078
079    private PasswordEncoder passwordEncoder;
080
081    private String baseDn;
082
083    private String groupsDn;
084
085    private String ldapGroupClass = "groupOfUniqueNames";
086
087    @PostConstruct
088    public void initialize()
089    {
090        this.writableLdap = userConf.getBoolean( UserConfigurationKeys.LDAP_WRITABLE, this.writableLdap );
091        this.baseDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_BASEDN, null );
092        this.passwordEncoder = new SHA1PasswordEncoder();
093        this.groupsDn = userConf.getConcatenatedList( UserConfigurationKeys.LDAP_GROUPS_BASEDN, this.groupsDn );
094        this.ldapGroupClass = userConf.getString( UserConfigurationKeys.LDAP_GROUPS_CLASS, this.ldapGroupClass );
095    }
096
097    /**
098     * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#removeUser(String, javax.naming.directory.DirContext)
099     */
100    public void removeUser( String principal, DirContext context )
101        throws LdapControllerException
102    {
103        // no op
104    }
105
106    /**
107     * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#updateUser(org.apache.archiva.redback.users.User, javax.naming.directory.DirContext)
108     */
109    public void updateUser( User user, DirContext context )
110        throws LdapControllerException, MappingException
111    {
112        // no op
113    }
114
115    /**
116     * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#userExists(String, javax.naming.directory.DirContext)
117     */
118    public boolean userExists( String key, DirContext context )
119        throws LdapControllerException
120    {
121        NamingEnumeration<SearchResult> results = null;
122        try
123        {
124            results = searchUsers( key, context );
125            return results.hasMoreElements();
126        }
127        catch ( NamingException e )
128        {
129            throw new LdapControllerException( "Error searching for the existence of user: " + key, e );
130        }
131        finally
132        {
133            if ( results != null )
134            {
135                try
136                {
137                    results.close();
138                }
139                catch ( NamingException e )
140                {
141                    log.warn( "Error closing search results", e );
142                }
143            }
144        }
145    }
146
147    protected NamingEnumeration<SearchResult> searchUsers( String key, DirContext context )
148        throws NamingException
149    {
150        LdapUserQuery query = new LdapUserQuery();
151        query.setUsername( key );
152        return searchUsers( context, null, query );
153    }
154
155    protected NamingEnumeration<SearchResult> searchUsers( DirContext context )
156        throws NamingException
157    {
158        return searchUsers( context, null, null );
159    }
160
161    protected NamingEnumeration<SearchResult> searchUsers( DirContext context, String[] returnAttributes )
162        throws NamingException
163    {
164        return searchUsers( context, returnAttributes, null );
165    }
166
167    protected NamingEnumeration<SearchResult> searchUsers( DirContext context, String[] returnAttributes,
168                                                           LdapUserQuery query )
169        throws NamingException
170    {
171        if ( query == null )
172        {
173            query = new LdapUserQuery();
174        }
175        SearchControls ctls = new SearchControls();
176
177        ctls.setDerefLinkFlag( true );
178        ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
179        ctls.setReturningAttributes( mapper.getReturningAttributes() );
180        ctls.setCountLimit( ( (LdapUserMapper) mapper ).getMaxResultCount() );
181
182        String finalFilter = new StringBuilder( "(&(objectClass=" + mapper.getUserObjectClass() + ")" ).append(
183            ( mapper.getUserFilter() != null ? mapper.getUserFilter() : "" ) ).append(
184            query.getLdapFilter( mapper ) + ")" ).toString();
185
186        log.debug( "Searching for users with filter: '{}' from base dn: {}", finalFilter, mapper.getUserBaseDn() );
187
188        return context.search( mapper.getUserBaseDn(), finalFilter, ctls );
189    }
190
191    /**
192     * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#getUsers(javax.naming.directory.DirContext)
193     */
194    public Collection<User> getUsers( DirContext context )
195        throws LdapControllerException, MappingException
196    {
197        NamingEnumeration<SearchResult> results = null;
198        try
199        {
200            results = searchUsers( context, null, null );
201            Set<User> users = new LinkedHashSet<User>();
202
203            while ( results.hasMoreElements() )
204            {
205                SearchResult result = results.nextElement();
206
207                users.add( mapper.getUser( result.getNameInNamespace(), result.getAttributes() ) );
208            }
209
210            return users;
211        }
212        catch ( NamingException e )
213        {
214            String message = "Failed to retrieve ldap information for users.";
215
216            throw new LdapControllerException( message, e );
217        }
218        finally
219        {
220            if ( results != null )
221            {
222                try
223                {
224                    results.close();
225                }
226                catch ( NamingException e )
227                {
228                    log.warn( "failed to close search results", e );
229                }
230            }
231        }
232    }
233
234    /**
235     * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#getUsersByQuery(org.apache.archiva.redback.users.ldap.LdapUserQuery, javax.naming.directory.DirContext)
236     */
237    public List<User> getUsersByQuery( LdapUserQuery query, DirContext context )
238        throws LdapControllerException, MappingException
239    {
240        NamingEnumeration<SearchResult> results = null;
241        try
242        {
243            results = searchUsers( context, null, query );
244            List<User> users = new LinkedList<User>();
245
246            while ( results.hasMoreElements() )
247            {
248                SearchResult result = results.nextElement();
249
250                users.add( mapper.getUser( result.getNameInNamespace(), result.getAttributes() ) );
251            }
252
253            return users;
254        }
255        catch ( NamingException e )
256        {
257            String message = "Failed to retrieve ldap information for users.";
258
259            throw new LdapControllerException( message, e );
260        }
261        finally
262        {
263            if ( results != null )
264            {
265                try
266                {
267                    results.close();
268                }
269                catch ( NamingException e )
270                {
271                    log.warn( "failed to close search results", e );
272                }
273            }
274        }
275    }
276
277    /**
278     * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#createUser(org.apache.archiva.redback.users.User, javax.naming.directory.DirContext, boolean)
279     */
280    public void createUser( User user, DirContext context, boolean encodePasswordIfChanged )
281        throws LdapControllerException, MappingException
282    {
283        if ( user == null )
284        {
285            return;
286        }
287        if ( user.getUsername().equals( UserManager.GUEST_USERNAME ) )
288        {
289            log.warn( "skip user '{}' creation" );
290            //We don't store guest
291            return;
292        }
293        boolean userExists = userExists( user.getUsername(), context );
294        if ( userExists )
295        {
296            log.debug( "user '{}' exists skip creation", user.getUsername() );
297            return;
298        }
299        if ( writableLdap )
300        {
301            try
302            {
303                bindUserObject( context, user );
304                log.info( "user {} created in ldap", user.getUsername() );
305            }
306            catch ( NamingException e )
307            {
308                throw new LdapControllerException( e.getMessage(), e );
309            }
310        }
311    }
312
313
314    private void bindUserObject( DirContext context, User user )
315        throws NamingException
316    {
317        Attributes attributes = new BasicAttributes( true );
318        BasicAttribute objectClass = new BasicAttribute( "objectClass" );
319        objectClass.add( "top" );
320        objectClass.add( "inetOrgPerson" );
321        objectClass.add( "person" );
322        objectClass.add( "organizationalperson" );
323        attributes.put( objectClass );
324        attributes.put( "cn", user.getUsername() );
325        attributes.put( "sn", "foo" );
326        if ( StringUtils.isNotEmpty( user.getEmail() ) )
327        {
328            attributes.put( "mail", user.getEmail() );
329        }
330
331        if ( userConf.getBoolean( UserConfigurationKeys.LDAP_BIND_AUTHENTICATOR_ALLOW_EMPTY_PASSWORDS, false )
332            && StringUtils.isNotEmpty( user.getPassword() ) )
333        {
334            attributes.put( "userPassword", passwordEncoder.encodePassword( user.getPassword() ) );
335        }
336        attributes.put( "givenName", "foo" );
337        context.createSubcontext( "cn=" + user.getUsername() + "," + this.getBaseDn(), attributes );
338    }
339
340    /**
341     * @see org.apache.archiva.redback.users.ldap.ctl.LdapController#getUser(String, javax.naming.directory.DirContext)
342     */
343    public LdapUser getUser( String username, DirContext context )
344        throws LdapControllerException, MappingException
345    {
346
347        log.debug( "Searching for user: {}", username );
348
349        LdapUserQuery query = new LdapUserQuery();
350        query.setUsername( username );
351
352        NamingEnumeration<SearchResult> result = null;
353        try
354        {
355            result = searchUsers( context, null, query );
356
357            if ( result.hasMoreElements() )
358            {
359                SearchResult next = result.nextElement();
360
361                log.info( "Found user: {}", username );
362
363                return mapper.getUser( next.getNameInNamespace(), next.getAttributes() );
364            }
365            else
366            {
367                return null;
368            }
369        }
370        catch ( NamingException e )
371        {
372            String message = "Failed to retrieve information for user: " + username;
373
374            throw new LdapControllerException( message, e );
375        }
376        finally
377        {
378            if ( result != null )
379            {
380                try
381                {
382                    result.close();
383                }
384                catch ( NamingException e )
385                {
386                    log.warn( "failed to close search results", e );
387                }
388            }
389        }
390    }
391
392    public Map<String, Collection<String>> findUsersWithRoles( DirContext dirContext )
393        throws LdapControllerException
394    {
395        Map<String, Collection<String>> usersWithRoles = new HashMap<String, Collection<String>>();
396
397        NamingEnumeration<SearchResult> namingEnumeration = null;
398        try
399        {
400
401            SearchControls searchControls = new SearchControls();
402
403            searchControls.setDerefLinkFlag( true );
404            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
405
406            String filter = "objectClass=" + getLdapGroupClass();
407
408            namingEnumeration = dirContext.search( getGroupsDn(), filter, searchControls );
409
410            while ( namingEnumeration.hasMore() )
411            {
412                SearchResult searchResult = namingEnumeration.next();
413
414                String groupName = searchResult.getName();
415                // cn=blabla we only want bla bla
416                groupName = StringUtils.substringAfter( groupName, "=" );
417
418                Attribute uniqueMemberAttr = searchResult.getAttributes().get( "uniquemember" );
419
420                if ( uniqueMemberAttr != null )
421                {
422                    NamingEnumeration<?> allMembersEnum = uniqueMemberAttr.getAll();
423                    while ( allMembersEnum.hasMore() )
424                    {
425                        String userName = allMembersEnum.next().toString();
426                        // uid=blabla we only want bla bla
427                        userName = StringUtils.substringAfter( userName, "=" );
428                        userName = StringUtils.substringBefore( userName, "," );
429                        Collection<String> roles = usersWithRoles.get( userName );
430                        if ( roles == null )
431                        {
432                            roles = new HashSet<String>();
433                        }
434
435                        roles.add( groupName );
436
437                        usersWithRoles.put( userName, roles );
438
439                    }
440                }
441
442                log.debug( "found groupName: '{}' with users: {}", groupName );
443
444            }
445
446            return usersWithRoles;
447        }
448        catch ( NamingException e )
449        {
450            throw new LdapControllerException( e.getMessage(), e );
451        }
452
453        finally
454        {
455
456            if ( namingEnumeration != null )
457            {
458                try
459                {
460                    namingEnumeration.close();
461                }
462                catch ( NamingException e )
463                {
464                    log.warn( "failed to close search results", e );
465                }
466            }
467        }
468    }
469
470    //-----------------------------
471    // setters/getters
472    //-----------------------------
473    public UserMapper getMapper()
474    {
475        return mapper;
476    }
477
478    public void setMapper( UserMapper mapper )
479    {
480        this.mapper = mapper;
481    }
482
483    public UserConfiguration getUserConf()
484    {
485        return userConf;
486    }
487
488    public void setUserConf( UserConfiguration userConf )
489    {
490        this.userConf = userConf;
491    }
492
493    public boolean isWritableLdap()
494    {
495        return writableLdap;
496    }
497
498    public void setWritableLdap( boolean writableLdap )
499    {
500        this.writableLdap = writableLdap;
501    }
502
503    public PasswordEncoder getPasswordEncoder()
504    {
505        return passwordEncoder;
506    }
507
508    public void setPasswordEncoder( PasswordEncoder passwordEncoder )
509    {
510        this.passwordEncoder = passwordEncoder;
511    }
512
513    public String getBaseDn()
514    {
515        return baseDn;
516    }
517
518    public void setBaseDn( String baseDn )
519    {
520        this.baseDn = baseDn;
521    }
522
523    public String getGroupsDn()
524    {
525        return groupsDn;
526    }
527
528    public void setGroupsDn( String groupsDn )
529    {
530        this.groupsDn = groupsDn;
531    }
532
533    public String getLdapGroupClass()
534    {
535        return ldapGroupClass;
536    }
537
538    public void setLdapGroupClass( String ldapGroupClass )
539    {
540        this.ldapGroupClass = ldapGroupClass;
541    }
542}