View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.ldap.client.template;
21  
22  
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.apache.directory.api.ldap.extras.controls.ppolicy_impl.PasswordPolicyDecorator;
27  import org.apache.directory.api.ldap.model.entry.Attribute;
28  import org.apache.directory.api.ldap.model.entry.Entry;
29  import org.apache.directory.api.ldap.model.entry.Value;
30  import org.apache.directory.api.ldap.model.exception.LdapException;
31  import org.apache.directory.api.ldap.model.message.AddRequest;
32  import org.apache.directory.api.ldap.model.message.AddResponse;
33  import org.apache.directory.api.ldap.model.message.BindRequest;
34  import org.apache.directory.api.ldap.model.message.BindRequestImpl;
35  import org.apache.directory.api.ldap.model.message.DeleteRequest;
36  import org.apache.directory.api.ldap.model.message.DeleteResponse;
37  import org.apache.directory.api.ldap.model.message.ModifyRequest;
38  import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
39  import org.apache.directory.api.ldap.model.message.ModifyResponse;
40  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
41  import org.apache.directory.api.ldap.model.message.ResultResponse;
42  import org.apache.directory.api.ldap.model.message.SearchRequest;
43  import org.apache.directory.api.ldap.model.message.SearchScope;
44  import org.apache.directory.api.ldap.model.name.Dn;
45  import org.apache.directory.ldap.client.api.EntryCursorImpl;
46  import org.apache.directory.ldap.client.api.LdapConnection;
47  import org.apache.directory.ldap.client.api.LdapConnectionPool;
48  import org.apache.directory.ldap.client.api.search.FilterBuilder;
49  import org.apache.directory.ldap.client.template.exception.LdapRequestUnsuccessfulException;
50  import org.apache.directory.ldap.client.template.exception.LdapRuntimeException;
51  import org.apache.directory.ldap.client.template.exception.PasswordException;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  
56  /**
57   * A facade for LDAP operations that handles all of the boiler plate code for 
58   * you allowing more concise operations through the use of callbacks.
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   * 
62   * @see <a href="http://en.wikipedia.org/wiki/Template_method_pattern">Template method pattern</a>
63   */
64  public class LdapConnectionTemplate implements LdapConnectionOperations, ModelFactory
65  {
66      private static final Logger LOG = LoggerFactory.getLogger( LdapConnectionTemplate.class );
67      private static final EntryMapper<Dn> DN_ENTRY_MAPPER = new EntryMapper<Dn>()
68      {
69          @Override
70          public Dn map( Entry entry ) throws LdapException
71          {
72              return entry.getDn();
73          }
74      };
75  
76      private LdapConnectionPool connectionPool;
77      private final PasswordPolicyDecorator passwordPolicyRequestControl;
78      private PasswordPolicyResponder passwordPolicyResponder;
79      private ModelFactory modelFactory;
80  
81  
82      /**
83       * Creates a new instance of LdapConnectionTemplate.
84       *
85       * @param connectionPool The pool to obtain connections from.
86       */
87      public LdapConnectionTemplate( LdapConnectionPool connectionPool )
88      {
89          LOG.debug( "creating new connection template from connectionPool" );
90          this.connectionPool = connectionPool;
91          this.passwordPolicyRequestControl = new PasswordPolicyDecorator(
92              connectionPool.getLdapApiService() );
93          this.passwordPolicyResponder = new PasswordPolicyResponderImpl(
94              connectionPool.getLdapApiService() );
95          this.modelFactory = new ModelFactoryImpl();
96      }
97  
98  
99      @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 }