001/*
002 *   Licensed to the Apache Software Foundation (ASF) under one
003 *   or more contributor license agreements.  See the NOTICE file
004 *   distributed with this work for additional information
005 *   regarding copyright ownership.  The ASF licenses this file
006 *   to you under the Apache License, Version 2.0 (the
007 *   "License"); you may not use this file except in compliance
008 *   with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *   Unless required by applicable law or agreed to in writing,
013 *   software distributed under the License is distributed on an
014 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *   KIND, either express or implied.  See the License for the
016 *   specific language governing permissions and limitations
017 *   under the License.
018 *
019 */
020package org.apache.directory.ldap.client.template;
021
022
023import java.util.ArrayList;
024import java.util.List;
025
026import org.apache.directory.api.ldap.extras.controls.ppolicy_impl.PasswordPolicyDecorator;
027import org.apache.directory.api.ldap.model.entry.Attribute;
028import org.apache.directory.api.ldap.model.entry.Entry;
029import org.apache.directory.api.ldap.model.entry.Value;
030import org.apache.directory.api.ldap.model.exception.LdapException;
031import org.apache.directory.api.ldap.model.message.AddRequest;
032import org.apache.directory.api.ldap.model.message.AddResponse;
033import org.apache.directory.api.ldap.model.message.BindRequest;
034import org.apache.directory.api.ldap.model.message.BindRequestImpl;
035import org.apache.directory.api.ldap.model.message.DeleteRequest;
036import org.apache.directory.api.ldap.model.message.DeleteResponse;
037import org.apache.directory.api.ldap.model.message.ModifyRequest;
038import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
039import org.apache.directory.api.ldap.model.message.ModifyResponse;
040import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
041import org.apache.directory.api.ldap.model.message.ResultResponse;
042import org.apache.directory.api.ldap.model.message.SearchRequest;
043import org.apache.directory.api.ldap.model.message.SearchScope;
044import org.apache.directory.api.ldap.model.name.Dn;
045import org.apache.directory.ldap.client.api.EntryCursorImpl;
046import org.apache.directory.ldap.client.api.LdapConnection;
047import org.apache.directory.ldap.client.api.LdapConnectionPool;
048import org.apache.directory.ldap.client.api.search.FilterBuilder;
049import org.apache.directory.ldap.client.template.exception.LdapRequestUnsuccessfulException;
050import org.apache.directory.ldap.client.template.exception.LdapRuntimeException;
051import org.apache.directory.ldap.client.template.exception.PasswordException;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055
056/**
057 * A facade for LDAP operations that handles all of the boiler plate code for 
058 * you allowing more concise operations through the use of callbacks.
059 *
060 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
061 * 
062 * @see <a href="http://en.wikipedia.org/wiki/Template_method_pattern">Template method pattern</a>
063 */
064public class LdapConnectionTemplate implements LdapConnectionOperations, ModelFactory
065{
066    private static final Logger LOG = LoggerFactory.getLogger( LdapConnectionTemplate.class );
067    private static final EntryMapper<Dn> DN_ENTRY_MAPPER = new EntryMapper<Dn>()
068    {
069        @Override
070        public Dn map( Entry entry ) throws LdapException
071        {
072            return entry.getDn();
073        }
074    };
075
076    private LdapConnectionPool connectionPool;
077    private final PasswordPolicyDecorator passwordPolicyRequestControl;
078    private PasswordPolicyResponder passwordPolicyResponder;
079    private ModelFactory modelFactory;
080
081
082    /**
083     * Creates a new instance of LdapConnectionTemplate.
084     *
085     * @param connectionPool The pool to obtain connections from.
086     */
087    public LdapConnectionTemplate( LdapConnectionPool connectionPool )
088    {
089        LOG.debug( "creating new connection template from connectionPool" );
090        this.connectionPool = connectionPool;
091        this.passwordPolicyRequestControl = new PasswordPolicyDecorator(
092            connectionPool.getLdapApiService() );
093        this.passwordPolicyResponder = new PasswordPolicyResponderImpl(
094            connectionPool.getLdapApiService() );
095        this.modelFactory = new ModelFactoryImpl();
096    }
097
098
099    @Override
100    public AddResponse add( Dn dn, final Attribute... attributes )
101    {
102        return add( dn,
103            new RequestBuilder<AddRequest>()
104            {
105                @Override
106                public void buildRequest( AddRequest request ) throws LdapException
107                {
108                    request.getEntry().add( attributes );
109                }
110            } );
111    }
112
113
114    @Override
115    public AddResponse add( Dn dn, RequestBuilder<AddRequest> requestBuilder )
116    {
117        AddRequest addRequest = newAddRequest( newEntry( dn ) );
118        try
119        {
120            requestBuilder.buildRequest( addRequest );
121        }
122        catch ( LdapException e )
123        {
124            throw new LdapRuntimeException( e );
125        }
126        return add( addRequest );
127    }
128
129
130    @Override
131    public AddResponse add( AddRequest addRequest )
132    {
133        LdapConnection connection = null;
134        try
135        {
136            connection = connectionPool.getConnection();
137            return connection.add( addRequest );
138        }
139        catch ( LdapException e )
140        {
141            throw new LdapRuntimeException( e );
142        }
143        finally
144        {
145            returnLdapConnection( connection );
146        }
147    }
148
149
150    @Override
151    public PasswordWarning authenticate( String baseDn, String filter, SearchScope scope, char[] password )
152        throws PasswordException
153    {
154        return authenticate( newSearchRequest( baseDn, filter, scope ), password );
155    }
156
157
158    @Override
159    public PasswordWarning authenticate( Dn baseDn, String filter, SearchScope scope, char[] password )
160        throws PasswordException
161    {
162        return authenticate( newSearchRequest( baseDn, filter, scope ), password );
163    }
164
165
166    @Override
167    public PasswordWarning authenticate( SearchRequest searchRequest, char[] password ) throws PasswordException
168    {
169        Dn userDn = searchFirst( searchRequest, DN_ENTRY_MAPPER );
170        if ( userDn == null )
171        {
172            throw new PasswordException().setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
173        }
174
175        return authenticate( userDn, password );
176    }
177
178
179    @Override
180    public PasswordWarning authenticate( Dn userDn, char[] password ) throws PasswordException
181    {
182        LdapConnection connection = null;
183        try
184        {
185            connection = connectionPool.getConnection();
186            return authenticateConnection( connection, userDn, password );
187        }
188        catch ( LdapException e )
189        {
190            throw new LdapRuntimeException( e );
191        }
192        finally
193        {
194            returnLdapConnection( connection );
195        }
196    }
197
198
199    private PasswordWarning authenticateConnection( final LdapConnection connection,
200        final Dn userDn, final char[] password ) throws PasswordException
201    {
202        return passwordPolicyResponder.process(
203            new PasswordPolicyOperation()
204            {
205                @Override
206                public ResultResponse process() throws LdapException
207                {
208                    MemoryClearingBuffer passwordBuffer = MemoryClearingBuffer.newInstance( password );
209                    try
210                    {
211                        BindRequest bindRequest = new BindRequestImpl()
212                            .setDn( userDn )
213                            .setCredentials( passwordBuffer.getBytes() )
214                            .addControl( passwordPolicyRequestControl );
215
216                        return connection.bind( bindRequest );
217                    }
218                    finally
219                    {
220                        passwordBuffer.clear();
221                    }
222                }
223            } );
224    }
225
226
227    @Override
228    public DeleteResponse delete( Dn dn )
229    {
230        return delete( dn, null );
231    }
232
233
234    @Override
235    public DeleteResponse delete( Dn dn, RequestBuilder<DeleteRequest> requestBuilder )
236    {
237        DeleteRequest deleteRequest = newDeleteRequest( dn );
238        if ( requestBuilder != null )
239        {
240            try
241            {
242                requestBuilder.buildRequest( deleteRequest );
243            }
244            catch ( LdapException e )
245            {
246                throw new LdapRuntimeException( e );
247            }
248        }
249        return delete( deleteRequest );
250    }
251
252
253    @Override
254    public DeleteResponse delete( DeleteRequest deleteRequest )
255    {
256        LdapConnection connection = null;
257        try
258        {
259            connection = connectionPool.getConnection();
260            return connection.delete( deleteRequest );
261        }
262        catch ( LdapException e )
263        {
264            throw new LdapRuntimeException( e );
265        }
266        finally
267        {
268            returnLdapConnection( connection );
269        }
270    }
271
272
273    @Override
274    public <T> T execute( ConnectionCallback<T> connectionCallback )
275    {
276        LdapConnection connection = null;
277        try
278        {
279            connection = connectionPool.getConnection();
280            return connectionCallback.doWithConnection( connection );
281        }
282        catch ( LdapException e )
283        {
284            throw new LdapRuntimeException( e );
285        }
286        finally
287        {
288            returnLdapConnection( connection );
289        }
290    }
291
292
293    @Override
294    public <T> T lookup( Dn dn, EntryMapper<T> entryMapper )
295    {
296        return lookup( dn, null, entryMapper );
297    }
298
299
300    @Override
301    public <T> T lookup( Dn dn, String[] attributes, EntryMapper<T> entryMapper )
302    {
303        LdapConnection connection = null;
304        try
305        {
306            connection = connectionPool.getConnection();
307            Entry entry = attributes == null
308                ? connection.lookup( dn )
309                : connection.lookup( dn, attributes );
310            return entry == null ? null : entryMapper.map( entry );
311        }
312        catch ( LdapException e )
313        {
314            throw new LdapRuntimeException( e );
315        }
316        finally
317        {
318            returnLdapConnection( connection );
319        }
320    }
321
322
323    private void modifyPassword( final LdapConnection connection, final Dn userDn,
324        final char[] newPassword ) throws PasswordException
325    {
326        passwordPolicyResponder.process(
327            new PasswordPolicyOperation()
328            {
329                @Override
330                public ResultResponse process() throws PasswordException, LdapException
331                {
332                    // Can't use Password Modify:
333                    // https://issues.apache.org/jira/browse/DIRSERVER-1935
334                    // So revert to regular Modify
335                    MemoryClearingBuffer newPasswordBuffer = MemoryClearingBuffer.newInstance( newPassword );
336                    try
337                    {
338                        ModifyRequest modifyRequest = new ModifyRequestImpl()
339                            .setName( userDn )
340                            .replace( "userPassword", newPasswordBuffer.getComputedBytes() )
341                            .addControl( passwordPolicyRequestControl );
342
343                        return connection.modify( modifyRequest );
344                    }
345                    finally
346                    {
347                        newPasswordBuffer.clear();
348                    }
349                }
350            } );
351
352    }
353
354
355    @Override
356    public void modifyPassword( Dn userDn, char[] newPassword )
357        throws PasswordException
358    {
359        modifyPassword( userDn, null, newPassword, true );
360    }
361
362
363    @Override
364    public void modifyPassword( Dn userDn, char[] oldPassword,
365        char[] newPassword ) throws PasswordException
366    {
367        modifyPassword( userDn, oldPassword, newPassword, false );
368    }
369
370
371    @Override
372    public void modifyPassword( Dn userDn, char[] oldPassword,
373        char[] newPassword, boolean asAdmin ) throws PasswordException
374    {
375        LdapConnection connection = null;
376        try
377        {
378            connection = connectionPool.getConnection();
379            if ( !asAdmin )
380            {
381                authenticateConnection( connection, userDn, oldPassword );
382            }
383
384            modifyPassword( connection, userDn, newPassword );
385        }
386        catch ( LdapException e )
387        {
388            throw new LdapRuntimeException( e );
389        }
390        finally
391        {
392            returnLdapConnection( connection );
393        }
394    }
395
396
397    @Override
398    public ModifyResponse modify( Dn dn, RequestBuilder<ModifyRequest> requestBuilder )
399    {
400        ModifyRequest modifyRequest = newModifyRequest( dn );
401        try
402        {
403            requestBuilder.buildRequest( modifyRequest );
404        }
405        catch ( LdapException e )
406        {
407            throw new LdapRuntimeException( e );
408        }
409        return modify( modifyRequest );
410    }
411
412
413    @Override
414    public ModifyResponse modify( ModifyRequest modifyRequest )
415    {
416        LdapConnection connection = null;
417        try
418        {
419            connection = connectionPool.getConnection();
420            return connection.modify( modifyRequest );
421        }
422        catch ( LdapException e )
423        {
424            throw new LdapRuntimeException( e );
425        }
426        finally
427        {
428            returnLdapConnection( connection );
429        }
430    }
431
432
433    @Override
434    public AddRequest newAddRequest( Entry entry )
435    {
436        return modelFactory.newAddRequest( entry );
437    }
438
439
440    @Override
441    public Attribute newAttribute( String name )
442    {
443        return modelFactory.newAttribute( name );
444    }
445
446
447    @Override
448    public Attribute newAttribute( String name, byte[]... values )
449    {
450        return modelFactory.newAttribute( name, values );
451    }
452
453
454    @Override
455    public Attribute newAttribute( String name, String... values )
456    {
457        return modelFactory.newAttribute( name, values );
458    }
459
460
461    @Override
462    public Attribute newAttribute( String name, Value<?>... values )
463    {
464        return modelFactory.newAttribute( name, values );
465    }
466
467
468    @Override
469    public DeleteRequest newDeleteRequest( Dn dn )
470    {
471        return modelFactory.newDeleteRequest( dn );
472    }
473
474
475    @Override
476    public Dn newDn( String dn )
477    {
478        return modelFactory.newDn( dn );
479    }
480
481
482    @Override
483    public Entry newEntry( String dn )
484    {
485        return modelFactory.newEntry( dn );
486    }
487
488
489    @Override
490    public Entry newEntry( Dn dn )
491    {
492        return modelFactory.newEntry( dn );
493    }
494
495
496    @Override
497    public ModifyRequest newModifyRequest( String dn )
498    {
499        return modelFactory.newModifyRequest( dn );
500    }
501
502
503    @Override
504    public ModifyRequest newModifyRequest( Dn dn )
505    {
506        return modelFactory.newModifyRequest( dn );
507    }
508
509
510    @Override
511    public SearchRequest newSearchRequest( String baseDn, FilterBuilder filter, SearchScope scope )
512    {
513        return modelFactory.newSearchRequest( baseDn, filter, scope );
514    }
515
516
517    @Override
518    public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope )
519    {
520        return modelFactory.newSearchRequest( baseDn, filter, scope );
521    }
522
523
524    @Override
525    public SearchRequest newSearchRequest( Dn baseDn, FilterBuilder filter, SearchScope scope )
526    {
527        return modelFactory.newSearchRequest( baseDn, filter, scope );
528    }
529
530
531    @Override
532    public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope )
533    {
534        return modelFactory.newSearchRequest( baseDn, filter, scope );
535    }
536
537
538    @Override
539    public SearchRequest newSearchRequest( String baseDn, FilterBuilder filter, SearchScope scope, String... attributes )
540    {
541        return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
542    }
543
544
545    @Override
546    public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope, String... attributes )
547    {
548        return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
549    }
550
551
552    @Override
553    public SearchRequest newSearchRequest( Dn baseDn, FilterBuilder filter, SearchScope scope, String... attributes )
554    {
555        return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
556    }
557
558
559    @Override
560    public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope, String... attributes )
561    {
562        return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
563    }
564
565
566    @Override
567    public <T extends ResultResponse> T responseOrException( T response )
568    {
569        if ( ResultCodeEnum.SUCCESS != response.getLdapResult().getResultCode() )
570        {
571            throw new LdapRequestUnsuccessfulException( response );
572        }
573        return response;
574    }
575
576
577    private void returnLdapConnection( LdapConnection connection )
578    {
579        if ( connection != null )
580        {
581            try
582            {
583                connectionPool.releaseConnection( connection );
584            }
585            catch ( LdapException e )
586            {
587                throw new LdapRuntimeException( e );
588            }
589        }
590    }
591
592
593    @Override
594    public <T> List<T> search( String baseDn, FilterBuilder filter, SearchScope scope,
595        EntryMapper<T> entryMapper )
596    {
597        return search(
598            modelFactory.newSearchRequest( baseDn, filter, scope ),
599            entryMapper );
600    }
601
602
603    @Override
604    public <T> List<T> search( String baseDn, String filter, SearchScope scope,
605        EntryMapper<T> entryMapper )
606    {
607        return search(
608            modelFactory.newSearchRequest( baseDn, filter, scope ),
609            entryMapper );
610    }
611
612
613    @Override
614    public <T> List<T> search( Dn baseDn, FilterBuilder filter, SearchScope scope,
615        EntryMapper<T> entryMapper )
616    {
617        return search(
618            modelFactory.newSearchRequest( baseDn, filter, scope ),
619            entryMapper );
620    }
621
622
623    @Override
624    public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
625        EntryMapper<T> entryMapper )
626    {
627        return search(
628            modelFactory.newSearchRequest( baseDn, filter, scope ),
629            entryMapper );
630    }
631
632
633    @Override
634    public <T> List<T> search( String baseDn, FilterBuilder filter, SearchScope scope,
635        String[] attributes, EntryMapper<T> entryMapper )
636    {
637        return search(
638            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
639            entryMapper );
640    }
641
642
643    @Override
644    public <T> List<T> search( String baseDn, String filter, SearchScope scope,
645        String[] attributes, EntryMapper<T> entryMapper )
646    {
647        return search(
648            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
649            entryMapper );
650    }
651
652
653    @Override
654    public <T> List<T> search( Dn baseDn, FilterBuilder filter, SearchScope scope,
655        String[] attributes, EntryMapper<T> entryMapper )
656    {
657        return search(
658            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
659            entryMapper );
660    }
661
662
663    @Override
664    public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
665        String[] attributes, EntryMapper<T> entryMapper )
666    {
667        return search(
668            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
669            entryMapper );
670    }
671
672
673    @Override
674    public <T> List<T> search( SearchRequest searchRequest,
675        EntryMapper<T> entryMapper )
676    {
677        List<T> entries = new ArrayList<>();
678
679        LdapConnection connection = null;
680        try
681        {
682            connection = connectionPool.getConnection();
683
684            for ( Entry entry : new EntryCursorImpl( connection.search( searchRequest ) ) )
685            {
686                entries.add( entryMapper.map( entry ) );
687            }
688        }
689        catch ( LdapException e )
690        {
691            throw new LdapRuntimeException( e );
692        }
693        finally
694        {
695            returnLdapConnection( connection );
696        }
697
698        return entries;
699    }
700
701
702    @Override
703    public <T> T searchFirst( String baseDn, FilterBuilder filter, SearchScope scope,
704        EntryMapper<T> entryMapper )
705    {
706        return searchFirst(
707            modelFactory.newSearchRequest( baseDn, filter, scope ),
708            entryMapper );
709    }
710
711
712    @Override
713    public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
714        EntryMapper<T> entryMapper )
715    {
716        return searchFirst(
717            modelFactory.newSearchRequest( baseDn, filter, scope ),
718            entryMapper );
719    }
720
721
722    @Override
723    public <T> T searchFirst( Dn baseDn, FilterBuilder filter, SearchScope scope,
724        EntryMapper<T> entryMapper )
725    {
726        return searchFirst(
727            modelFactory.newSearchRequest( baseDn, filter, scope ),
728            entryMapper );
729    }
730
731
732    @Override
733    public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
734        EntryMapper<T> entryMapper )
735    {
736        return searchFirst(
737            modelFactory.newSearchRequest( baseDn, filter, scope ),
738            entryMapper );
739    }
740
741
742    @Override
743    public <T> T searchFirst( String baseDn, FilterBuilder filter, SearchScope scope,
744        String[] attributes, EntryMapper<T> entryMapper )
745    {
746        return searchFirst(
747            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
748            entryMapper );
749    }
750
751
752    @Override
753    public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
754        String[] attributes, EntryMapper<T> entryMapper )
755    {
756        return searchFirst(
757            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
758            entryMapper );
759    }
760
761
762    @Override
763    public <T> T searchFirst( Dn baseDn, FilterBuilder filter, SearchScope scope,
764        String[] attributes, EntryMapper<T> entryMapper )
765    {
766        return searchFirst(
767            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
768            entryMapper );
769    }
770
771
772    @Override
773    public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
774        String[] attributes, EntryMapper<T> entryMapper )
775    {
776        return searchFirst(
777            modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
778            entryMapper );
779    }
780
781
782    @Override
783    public <T> T searchFirst( SearchRequest searchRequest,
784        EntryMapper<T> entryMapper )
785    {
786        // in case the caller did not set size limit, we cache original value,
787        // set to 1, then set back to original value before returning...
788        long originalSizeLimit = searchRequest.getSizeLimit();
789        try
790        {
791            searchRequest.setSizeLimit( 1 );
792            List<T> entries = search( searchRequest, entryMapper );
793            return entries.isEmpty() ? null : entries.get( 0 );
794        }
795        finally
796        {
797            searchRequest.setSizeLimit( originalSizeLimit );
798        }
799    }
800
801
802    /**
803     * Sets the <code>modelFactory</code> implementation for this facade.
804     *
805     * @param modelFactory The model factory implementation
806     */
807    public void setModelFactory( ModelFactory modelFactory )
808    {
809        this.modelFactory = modelFactory;
810    }
811
812
813    /**
814     * Sets the <code>passwordPolicyResponder</code> implementation for this
815     * facade.
816     *
817     * @param passwordPolicyResponder The password policy responder 
818     * implementation
819     */
820    public void setPasswordPolicyResponder( PasswordPolicyResponder passwordPolicyResponder )
821    {
822        this.passwordPolicyResponder = passwordPolicyResponder;
823    }
824}