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.io.IOException;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import org.apache.directory.api.ldap.extras.controls.ppolicy_impl.PasswordPolicyDecorator;
28  import org.apache.directory.api.ldap.model.entry.Attribute;
29  import org.apache.directory.api.ldap.model.entry.Entry;
30  import org.apache.directory.api.ldap.model.entry.Value;
31  import org.apache.directory.api.ldap.model.exception.LdapException;
32  import org.apache.directory.api.ldap.model.message.AddRequest;
33  import org.apache.directory.api.ldap.model.message.AddResponse;
34  import org.apache.directory.api.ldap.model.message.BindRequest;
35  import org.apache.directory.api.ldap.model.message.BindRequestImpl;
36  import org.apache.directory.api.ldap.model.message.DeleteRequest;
37  import org.apache.directory.api.ldap.model.message.DeleteResponse;
38  import org.apache.directory.api.ldap.model.message.ModifyRequest;
39  import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
40  import org.apache.directory.api.ldap.model.message.ModifyResponse;
41  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
42  import org.apache.directory.api.ldap.model.message.ResultResponse;
43  import org.apache.directory.api.ldap.model.message.SearchRequest;
44  import org.apache.directory.api.ldap.model.message.SearchScope;
45  import org.apache.directory.api.ldap.model.name.Dn;
46  import org.apache.directory.ldap.client.api.EntryCursorImpl;
47  import org.apache.directory.ldap.client.api.LdapConnection;
48  import org.apache.directory.ldap.client.api.LdapConnectionPool;
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 Logger logger = LoggerFactory.getLogger( LdapConnectionTemplate.class );
67      private static final EntryMapper<Dn> dnEntryMapper = new EntryMapper<Dn>() {
68          @Override
69          public Dn map( Entry entry ) throws LdapException
70          {
71              return entry.getDn();
72          }
73      };
74  
75      private LdapConnectionPool connectionPool;
76      private final PasswordPolicyDecorator passwordPolicyRequestControl;
77      private PasswordPolicyResponder passwordPolicyResponder;
78      private ModelFactory modelFactory;
79  
80  
81      /**
82       * Creates a new instance of LdapConnectionTemplate.
83       *
84       * @param connectionPool The pool to obtain connections from.
85       */
86      public LdapConnectionTemplate( LdapConnectionPool connectionPool )
87      {
88          this.connectionPool = connectionPool;
89          this.passwordPolicyRequestControl = new PasswordPolicyDecorator(
90              connectionPool.getLdapApiService() );
91          this.passwordPolicyResponder = new PasswordPolicyResponderImpl(
92              connectionPool.getLdapApiService() );
93          this.modelFactory = new ModelFactoryImpl();
94      }
95  
96  
97      @Override
98      public AddResponse add( Dn dn, final Attribute... attributes )
99      {
100         return add( dn,
101             new RequestBuilder<AddRequest>()
102             {
103                 @Override
104                 public void buildRequest( AddRequest request ) throws LdapException
105                 {
106                     request.getEntry().add( attributes );
107                 }
108             } );
109     }
110 
111 
112     @Override
113     public AddResponse add( Dn dn, RequestBuilder<AddRequest> requestBuilder )
114     {
115         AddRequest addRequest = newAddRequest( newEntry( dn ) );
116         try
117         {
118             requestBuilder.buildRequest( addRequest );
119         }
120         catch ( LdapException e )
121         {
122             throw new LdapRuntimeException( e );
123         }
124         return add( addRequest );
125     }
126 
127 
128     @Override
129     public AddResponse add( AddRequest addRequest )
130     {
131         LdapConnection connection = null;
132         try
133         {
134             connection = connectionPool.getConnection();
135             return connection.add( addRequest );
136         }
137         catch ( LdapException e )
138         {
139             throw new LdapRuntimeException( e );
140         }
141         finally
142         {
143             returnLdapConnection( connection );
144         }
145     }
146 
147 
148     @Override
149     public PasswordWarning authenticate( String baseDn, String filter, SearchScope scope, char[] password ) throws PasswordException
150     {
151         return authenticate( newSearchRequest( baseDn, filter, scope ), password );
152     }
153 
154 
155     @Override
156     public PasswordWarning authenticate( Dn baseDn, String filter, SearchScope scope, char[] password ) throws PasswordException
157     {
158         return authenticate( newSearchRequest( baseDn, filter, scope ), password );
159     }
160     
161     
162     @Override
163     public PasswordWarning authenticate( SearchRequest searchRequest, char[] password ) throws PasswordException
164     {
165         Dn userDn = searchFirst( searchRequest, dnEntryMapper );
166         if ( userDn == null ) {
167             throw new PasswordException().setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
168         }
169         
170         return authenticate( userDn, password );
171     }
172     
173     
174     @Override
175     public PasswordWarning authenticate( Dn userDn, char[] password ) throws PasswordException
176     {
177         LdapConnection connection = null;
178         try
179         {
180             connection = connectionPool.getUnboundConnection();
181             return authenticateConnection( connection, userDn, password );
182         }
183         catch ( LdapException e )
184         {
185             throw new LdapRuntimeException( e );
186         }
187         finally
188         {
189             safeCloseLdapConnection( connection );
190         }
191     }
192 
193 
194     private PasswordWarning authenticateConnection( final LdapConnection connection,
195         final Dn userDn, final char[] password ) throws PasswordException
196     {
197         return passwordPolicyResponder.process(
198             new PasswordPolicyOperation()
199             {
200                 @Override
201                 public ResultResponse process() throws LdapException
202                 {
203                     MemoryClearingBuffer passwordBuffer = MemoryClearingBuffer.newInstance( password );
204                     try
205                     {
206                         BindRequest bindRequest = new BindRequestImpl()
207                             .setDn( userDn )
208                             .setCredentials( passwordBuffer.getBytes() )
209                             .addControl( passwordPolicyRequestControl );
210 
211                         return connection.bind( bindRequest );
212                     }
213                     finally
214                     {
215                         passwordBuffer.clear();
216                     }
217                 }
218             } );
219     }
220 
221 
222     @Override
223     public DeleteResponse delete( Dn dn )
224     {
225         return delete( dn, null );
226     }
227 
228 
229     @Override
230     public DeleteResponse delete( Dn dn, RequestBuilder<DeleteRequest> requestBuilder )
231     {
232         DeleteRequest deleteRequest = newDeleteRequest( dn );
233         if ( requestBuilder != null )
234         {
235             try
236             {
237                 requestBuilder.buildRequest( deleteRequest );
238             }
239             catch ( LdapException e )
240             {
241                 throw new LdapRuntimeException( e );
242             }
243         }
244         return delete( deleteRequest );
245     }
246 
247 
248     @Override
249     public DeleteResponse delete( DeleteRequest deleteRequest )
250     {
251         LdapConnection connection = null;
252         try
253         {
254             connection = connectionPool.getConnection();
255             return connection.delete( deleteRequest );
256         }
257         catch ( LdapException e )
258         {
259             throw new LdapRuntimeException( e );
260         }
261         finally
262         {
263             returnLdapConnection( connection );
264         }
265     }
266 
267 
268     @Override
269     public <T> T execute( ConnectionCallback<T> connectionCallback )
270     {
271         LdapConnection connection = null;
272         try
273         {
274             connection = connectionPool.getConnection();
275             return connectionCallback.doWithConnection( connection );
276         }
277         catch ( LdapException e )
278         {
279             throw new LdapRuntimeException( e );
280         }
281         finally
282         {
283             returnLdapConnection( connection );
284         }
285     }
286 
287 
288     @Override
289     public <T> T lookup( Dn dn, EntryMapper<T> entryMapper )
290     {
291         return lookup( dn, null, entryMapper );
292     }
293 
294 
295     @Override
296     public <T> T lookup( Dn dn, String[] attributes, EntryMapper<T> entryMapper )
297     {
298         LdapConnection connection = null;
299         try
300         {
301             connection = connectionPool.getConnection();
302             Entry entry = attributes == null
303                 ? connection.lookup( dn )
304                 : connection.lookup( dn, attributes );
305             return entry == null ? null : entryMapper.map( entry );
306         }
307         catch ( LdapException e )
308         {
309             throw new LdapRuntimeException( e );
310         }
311         finally
312         {
313             returnLdapConnection( connection );
314         }
315     }
316 
317 
318     private void modifyPassword( final LdapConnection connection, final Dn userDn,
319         final char[] newPassword ) throws PasswordException
320     {
321         passwordPolicyResponder.process(
322             new PasswordPolicyOperation()
323             {
324                 @Override
325                 public ResultResponse process() throws PasswordException, LdapException
326                 {
327                     // Can't use Password Modify:
328                     // https://issues.apache.org/jira/browse/DIRSERVER-1935
329                     // So revert to regular Modify
330                     MemoryClearingBuffer newPasswordBuffer = MemoryClearingBuffer.newInstance( newPassword );
331                     try
332                     {
333                         ModifyRequest modifyRequest = new ModifyRequestImpl()
334                             .setName( userDn )
335                             .replace( "userPassword", newPasswordBuffer.getComputedBytes() )
336                             .addControl( passwordPolicyRequestControl );
337 
338                         return connection.modify( modifyRequest );
339                     }
340                     finally
341                     {
342                         newPasswordBuffer.clear();
343                     }
344                 }
345             } );
346 
347     }
348 
349     @Override
350     public void modifyPassword( Dn userDn, char[] newPassword ) 
351         throws PasswordException
352     {
353         modifyPassword( userDn, null, newPassword, true );
354     }
355 
356     @Override
357     public void modifyPassword( Dn userDn, char[] oldPassword,
358         char[] newPassword ) throws PasswordException
359     {
360         modifyPassword( userDn, oldPassword, newPassword, false );
361     }
362 
363     @Override
364     public void modifyPassword( Dn userDn, char[] oldPassword,
365         char[] newPassword, boolean asAdmin ) throws PasswordException
366     {
367         LdapConnection connection = null;
368         try
369         {
370             if ( asAdmin )
371             {
372                 connection = connectionPool.getConnection();
373             }
374             else
375             {
376                 connection = connectionPool.getUnboundConnection();
377                 authenticateConnection( connection, userDn, oldPassword );
378             }
379 
380             modifyPassword( connection, userDn, newPassword );
381         }
382         catch ( LdapException e )
383         {
384             throw new LdapRuntimeException( e );
385         }
386         finally
387         {
388             if ( asAdmin )
389             {
390                 returnLdapConnection( connection );
391             }
392             else
393             {
394                 safeCloseLdapConnection( connection );
395             }
396         }
397     }
398 
399 
400     @Override
401     public ModifyResponse modify( Dn dn, RequestBuilder<ModifyRequest> requestBuilder )
402     {
403         ModifyRequest modifyRequest = newModifyRequest( dn );
404         try
405         {
406             requestBuilder.buildRequest( modifyRequest );
407         }
408         catch ( LdapException e )
409         {
410             throw new LdapRuntimeException( e );
411         }
412         return modify( modifyRequest );
413     }
414 
415 
416     @Override
417     public ModifyResponse modify( ModifyRequest modifyRequest )
418     {
419         LdapConnection connection = null;
420         try
421         {
422             connection = connectionPool.getConnection();
423             return connection.modify( modifyRequest );
424         }
425         catch ( LdapException e )
426         {
427             throw new LdapRuntimeException( e );
428         }
429         finally
430         {
431             returnLdapConnection( connection );
432         }
433     }
434 
435 
436     @Override
437     public AddRequest newAddRequest( Entry entry )
438     {
439         return modelFactory.newAddRequest( entry );
440     }
441 
442 
443     @Override
444     public Attribute newAttribute( String name, byte[]... values )
445     {
446         return modelFactory.newAttribute( name, values );
447     }
448 
449 
450     @Override
451     public Attribute newAttribute( String name, String... values )
452     {
453         return modelFactory.newAttribute( name, values );
454     }
455 
456 
457     @Override
458     public Attribute newAttribute( String name, Value<?>... values )
459     {
460         return modelFactory.newAttribute( name, values );
461     }
462 
463 
464     @Override
465     public DeleteRequest newDeleteRequest( Dn dn )
466     {
467         return modelFactory.newDeleteRequest( dn );
468     }
469 
470 
471     @Override
472     public Dn newDn( String dn )
473     {
474         return modelFactory.newDn( dn );
475     }
476 
477 
478     @Override
479     public Entry newEntry( String dn )
480     {
481         return modelFactory.newEntry( dn );
482     }
483 
484 
485     @Override
486     public Entry newEntry( Dn dn )
487     {
488         return modelFactory.newEntry( dn );
489     }
490 
491 
492     @Override
493     public ModifyRequest newModifyRequest( String dn )
494     {
495         return modelFactory.newModifyRequest( dn );
496     }
497 
498 
499     @Override
500     public ModifyRequest newModifyRequest( Dn dn )
501     {
502         return modelFactory.newModifyRequest( dn );
503     }
504 
505 
506     @Override
507     public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope )
508     {
509         return modelFactory.newSearchRequest( baseDn, filter, scope );
510     }
511 
512 
513     @Override
514     public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope )
515     {
516         return modelFactory.newSearchRequest( baseDn, filter, scope );
517     }
518 
519 
520     @Override
521     public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope, String... attributes )
522     {
523         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
524     }
525 
526 
527     @Override
528     public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope, String... attributes )
529     {
530         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
531     }
532 
533 
534     @Override
535     public <T extends ResultResponse> T responseOrException( T response )
536     {
537         if ( ResultCodeEnum.SUCCESS != response.getLdapResult().getResultCode() )
538         {
539             throw new LdapRequestUnsuccessfulException( response );
540         }
541         return response;
542     }
543 
544 
545     private void returnLdapConnection( LdapConnection connection )
546     {
547         if ( connection != null )
548         {
549             try
550             {
551                 connectionPool.releaseConnection( connection );
552             }
553             catch ( LdapException e )
554             {
555                 throw new LdapRuntimeException( e );
556             }
557         }
558     }
559 
560 
561     private void safeCloseLdapConnection( LdapConnection connection )
562     {
563         if ( connection != null )
564         {
565             try
566             {
567                 connection.close();
568             }
569             catch ( IOException e )
570             {
571                 logger.error( "Unable to close ldap connection, might be leaking connections:", e.getMessage() );
572                 logger.debug( "Unable to close ldap connection, might be leaking connections:", e );
573             }
574         }
575     }
576 
577 
578     @Override
579     public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
580         EntryMapper<T> entryMapper )
581     {
582         return searchFirst( 
583             modelFactory.newSearchRequest( baseDn, filter, scope ), 
584             entryMapper );
585     }
586 
587 
588     @Override
589     public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
590         EntryMapper<T> entryMapper )
591     {
592         return searchFirst( 
593             modelFactory.newSearchRequest( baseDn, filter, scope ), 
594             entryMapper );
595     }
596 
597 
598     @Override
599     public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
600         String[] attributes, EntryMapper<T> entryMapper )
601     {
602         return searchFirst(
603             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ), 
604             entryMapper );
605     }
606 
607 
608     @Override
609     public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
610         String[] attributes, EntryMapper<T> entryMapper )
611     {
612         return searchFirst(
613             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ), 
614             entryMapper );
615     }
616     
617     
618     @Override
619     public <T> T searchFirst( SearchRequest searchRequest,
620         EntryMapper<T> entryMapper )
621     {
622         // in case the caller did not set size limit, we cache original value,
623         // set to 1, then set back to original value before returning...
624         long originalSizeLimit = searchRequest.getSizeLimit();
625         try
626         {
627             searchRequest.setSizeLimit( 1 );
628             List<T> entries = search( searchRequest, entryMapper );
629             return entries.isEmpty() ? null : entries.get( 0 );
630         }
631         finally
632         {
633             searchRequest.setSizeLimit( originalSizeLimit );
634         }
635     }
636 
637 
638     @Override
639     public <T> List<T> search( String baseDn, String filter, SearchScope scope,
640         EntryMapper<T> entryMapper )
641     {
642         return search( 
643             modelFactory.newSearchRequest( baseDn, filter, scope ), 
644             entryMapper );
645     }
646 
647 
648     @Override
649     public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
650         EntryMapper<T> entryMapper )
651     {
652         return search( 
653             modelFactory.newSearchRequest( baseDn, filter, scope ), 
654             entryMapper );
655     }
656 
657 
658     @Override
659     public <T> List<T> search( String baseDn, String filter, SearchScope scope,
660         String[] attributes, EntryMapper<T> entryMapper )
661     {
662         return search(
663             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ), 
664             entryMapper );
665     }
666 
667 
668     @Override
669     public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
670         String[] attributes, EntryMapper<T> entryMapper )
671     {
672         return search(
673             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ), 
674             entryMapper );
675     }
676 
677 
678     @Override
679     public <T> List<T> search( SearchRequest searchRequest,
680         EntryMapper<T> entryMapper )
681     {
682         List<T> entries = new ArrayList<T>();
683 
684         LdapConnection connection = null;
685         try
686         {
687             connection = connectionPool.getConnection();
688 
689             for ( Entry entry : new EntryCursorImpl( connection.search( searchRequest ) ) )
690             {
691                 entries.add( entryMapper.map( entry ) );
692             }
693         }
694         catch ( LdapException e )
695         {
696             throw new LdapRuntimeException( e );
697         }
698         finally
699         {
700             returnLdapConnection( connection );
701         }
702 
703         return entries;
704     }
705 
706 
707     /**
708      * Sets the <code>modelFactory</code> implementation for this facade.
709      *
710      * @param modelFactory The model factory implementation
711      */
712     public void setModelFactory( ModelFactory modelFactory )
713     {
714         this.modelFactory = modelFactory;
715     }
716 
717 
718     /**
719      * Sets the <code>passwordPolicyResponder</code> implementation for this
720      * facade.
721      *
722      * @param passwordPolicyResponder The password policy responder 
723      * implementation
724      */
725     public void setPasswordPolicyResponder( PasswordPolicyResponder passwordPolicyResponder )
726     {
727         this.passwordPolicyResponder = passwordPolicyResponder;
728     }
729 }