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}