001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2012-2016 ForgeRock AS. 015 */ 016 017package org.forgerock.opendj.examples; 018 019import java.io.IOException; 020import java.util.Collection; 021 022import com.forgerock.opendj.ldap.controls.AffinityControl; 023import org.forgerock.opendj.ldap.ByteString; 024import org.forgerock.opendj.ldap.Connection; 025import org.forgerock.opendj.ldap.DecodeException; 026import org.forgerock.opendj.ldap.DecodeOptions; 027import org.forgerock.opendj.ldap.Entry; 028import org.forgerock.opendj.ldap.LdapException; 029import org.forgerock.opendj.ldap.Filter; 030import org.forgerock.opendj.ldap.LDAPConnectionFactory; 031import org.forgerock.opendj.ldap.ModificationType; 032import org.forgerock.opendj.ldap.ResultCode; 033import org.forgerock.opendj.ldap.RootDSE; 034import org.forgerock.opendj.ldap.SearchResultHandler; 035import org.forgerock.opendj.ldap.SearchResultReferenceIOException; 036import org.forgerock.opendj.ldap.SearchScope; 037import org.forgerock.opendj.ldap.SortKey; 038import org.forgerock.opendj.ldap.controls.ADNotificationRequestControl; 039import org.forgerock.opendj.ldap.controls.AssertionRequestControl; 040import org.forgerock.opendj.ldap.controls.AuthorizationIdentityRequestControl; 041import org.forgerock.opendj.ldap.controls.AuthorizationIdentityResponseControl; 042import org.forgerock.opendj.ldap.controls.EntryChangeNotificationResponseControl; 043import org.forgerock.opendj.ldap.controls.GetEffectiveRightsRequestControl; 044import org.forgerock.opendj.ldap.controls.ManageDsaITRequestControl; 045import org.forgerock.opendj.ldap.controls.MatchedValuesRequestControl; 046import org.forgerock.opendj.ldap.controls.PasswordExpiredResponseControl; 047import org.forgerock.opendj.ldap.controls.PasswordExpiringResponseControl; 048import org.forgerock.opendj.ldap.controls.PasswordPolicyRequestControl; 049import org.forgerock.opendj.ldap.controls.PasswordPolicyResponseControl; 050import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl; 051import org.forgerock.opendj.ldap.controls.PersistentSearchChangeType; 052import org.forgerock.opendj.ldap.controls.PersistentSearchRequestControl; 053import org.forgerock.opendj.ldap.controls.PostReadRequestControl; 054import org.forgerock.opendj.ldap.controls.PostReadResponseControl; 055import org.forgerock.opendj.ldap.controls.PreReadRequestControl; 056import org.forgerock.opendj.ldap.controls.PreReadResponseControl; 057import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl; 058import org.forgerock.opendj.ldap.controls.ServerSideSortRequestControl; 059import org.forgerock.opendj.ldap.controls.ServerSideSortResponseControl; 060import org.forgerock.opendj.ldap.controls.SimplePagedResultsControl; 061import org.forgerock.opendj.ldap.controls.SubentriesRequestControl; 062import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl; 063import org.forgerock.opendj.ldap.controls.VirtualListViewRequestControl; 064import org.forgerock.opendj.ldap.controls.VirtualListViewResponseControl; 065import org.forgerock.opendj.ldap.requests.BindRequest; 066import org.forgerock.opendj.ldap.requests.DeleteRequest; 067import org.forgerock.opendj.ldap.requests.ModifyRequest; 068import org.forgerock.opendj.ldap.requests.Requests; 069import org.forgerock.opendj.ldap.requests.SearchRequest; 070import org.forgerock.opendj.ldap.responses.BindResult; 071import org.forgerock.opendj.ldap.responses.Result; 072import org.forgerock.opendj.ldap.responses.SearchResultEntry; 073import org.forgerock.opendj.ldap.responses.SearchResultReference; 074import org.forgerock.opendj.ldif.ConnectionEntryReader; 075import org.forgerock.opendj.ldif.LDIFEntryWriter; 076 077/** 078 * This command-line client demonstrates use of LDAP controls. The client takes 079 * as arguments the host and port for the directory server, and expects to find 080 * the entries and access control instructions as defined in <a 081 * href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>. 082 * 083 * This client connects as <code>cn=Directory Manager</code> with password 084 * <code>password</code>. Not a best practice; in real code use application 085 * specific credentials to connect, and ensure that your application has access 086 * to use the LDAP controls needed. 087 */ 088public final class Controls { 089 090 /** 091 * Connect to the server, and then try to use some LDAP controls. 092 * 093 * @param args 094 * The command line arguments: host, port 095 */ 096 public static void main(final String[] args) { 097 if (args.length != 2) { 098 System.err.println("Usage: host port"); 099 System.err.println("For example: localhost 1389"); 100 System.exit(1); 101 } 102 final String host = args[0]; 103 final int port = Integer.parseInt(args[1]); 104 105 final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port); 106 try (Connection connection = factory.getConnection()) { 107 checkSupportedControls(connection); 108 109 final String user = "cn=Directory Manager"; 110 final char[] password = "password".toCharArray(); 111 connection.bind(user, password); 112 113 // Uncomment a method to run one of the examples. 114 115 //useAffinityControl(connection); 116 //useADNotificationRequestControl(connection); 117 //useAssertionControl(connection); 118 useAuthorizationIdentityRequestControl(connection); 119 // For the EntryChangeNotificationResponseControl see 120 // usePersistentSearchRequestControl() 121 //useGetEffectiveRightsRequestControl(connection); 122 //useManageDsaITRequestControl(connection); 123 //useMatchedValuesRequestControl(connection); 124 //usePasswordExpiredResponseControl(connection); 125 //usePasswordExpiringResponseControl(connection); 126 //usePasswordPolicyRequestControl(connection); 127 //usePermissiveModifyRequestControl(connection); 128 //usePersistentSearchRequestControl(connection); 129 //usePostReadRequestControl(connection); 130 //usePreReadRequestControl(connection); 131 //useProxiedAuthV2RequestControl(connection); 132 //useServerSideSortRequestControl(connection); 133 //useSimplePagedResultsControl(connection); 134 //useSubentriesRequestControl(connection); 135 //useSubtreeDeleteRequestControl(connection); 136 //useVirtualListViewRequestControl(connection); 137 138 } catch (final LdapException e) { 139 System.err.println(e.getMessage()); 140 System.exit(e.getResult().getResultCode().intValue()); 141 return; 142 } 143 } 144 145 /** 146 * Use the OpenDJ affinity control to bypass load balancing. 147 * <br> 148 * In other words, each request with a control having the same value 149 * is sent to the same LDAP server. 150 * 151 * @param connection Active connection to the directory server. 152 * @throws LdapException Operation failed. 153 */ 154 static void useAffinityControl(Connection connection) throws LdapException { 155 // --- JCite affinity --- 156 if (isSupported(AffinityControl.OID)) { 157 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 158 159 // Get an affinity control with a random value. 160 final AffinityControl control = AffinityControl.newControl(true); 161 162 final ModifyRequest modification = 163 Requests.newModifyRequest(dn) 164 .addControl(control) 165 .addModification(ModificationType.ADD, "description", 166 "Added with an Affinity control"); 167 connection.modify(modification); 168 169 final SearchRequest read = 170 Requests.newSearchRequest(dn, 171 SearchScope.BASE_OBJECT, "(&)", "description") 172 .addControl(control); 173 174 try (final ConnectionEntryReader reader = connection.search(read); 175 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out)) { 176 while (reader.hasNext()) { 177 writer.writeEntry(reader.readEntry()); 178 } 179 } catch (final IOException e) { 180 System.err.println(e.getMessage()); 181 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 182 } 183 } else { 184 System.err.println("AffinityControl not supported."); 185 } 186 // --- JCite affinity --- 187 } 188 189 /** 190 * Use the <a 191 * href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms676877(v=vs.85).aspx" 192 * >Microsoft LDAP Notification control</a> 193 * to register a change notification request for a search 194 * on Microsoft Active Directory. 195 * <p/> 196 * This client binds to Active Directory as 197 * {@code cn=Administrator,cn=users,dc=example,dc=com} 198 * with password {@code password}, 199 * and expects entries under {@code dc=example,dc=com}. 200 * 201 * @param connection Active connection to Active Directory server. 202 * @throws LdapException Operation failed. 203 */ 204 static void useADNotificationRequestControl(Connection connection) throws LdapException { 205 206 // --- JCite ADNotification --- 207 final String user = "cn=Administrator,cn=users,dc=example,dc=com"; 208 final char[] password = "password".toCharArray(); 209 connection.bind(user, password); 210 211 final String[] attributes = {"cn", 212 ADNotificationRequestControl.IS_DELETED_ATTR, 213 ADNotificationRequestControl.WHEN_CHANGED_ATTR, 214 ADNotificationRequestControl.WHEN_CREATED_ATTR}; 215 216 SearchRequest request = 217 Requests.newSearchRequest("dc=example,dc=com", 218 SearchScope.WHOLE_SUBTREE, "(objectclass=*)", attributes) 219 .addControl(ADNotificationRequestControl.newControl(true)); 220 221 ConnectionEntryReader reader = connection.search(request); 222 223 try { 224 while (reader.hasNext()) { 225 if (!reader.isReference()) { 226 SearchResultEntry entry = reader.readEntry(); // Updated entry 227 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 228 229 Boolean isDeleted = entry.parseAttribute( 230 ADNotificationRequestControl.IS_DELETED_ATTR 231 ).asBoolean(); 232 if (isDeleted != null && isDeleted) { 233 // Handle entry deletion 234 writer.writeComment("Deleted entry: " + entry.getName()); 235 writer.writeEntry(entry); 236 writer.flush(); 237 } 238 String whenCreated = entry.parseAttribute( 239 ADNotificationRequestControl.WHEN_CREATED_ATTR) 240 .asString(); 241 String whenChanged = entry.parseAttribute( 242 ADNotificationRequestControl.WHEN_CHANGED_ATTR) 243 .asString(); 244 if (whenCreated != null && whenChanged != null) { 245 if (whenCreated.equals(whenChanged)) { 246 // Handle entry addition 247 writer.writeComment("Added entry: " + entry.getName()); 248 writer.writeEntry(entry); 249 writer.flush(); 250 } else { 251 // Handle entry modification 252 writer.writeComment("Modified entry: " + entry.getName()); 253 writer.writeEntry(entry); 254 writer.flush(); 255 } 256 } 257 } else { 258 reader.readReference(); // Read and ignore reference 259 } 260 } 261 } catch (final LdapException e) { 262 System.err.println(e.getMessage()); 263 System.exit(e.getResult().getResultCode().intValue()); 264 } catch (final SearchResultReferenceIOException e) { 265 System.err.println("Got search reference(s): " + e.getReference().getURIs()); 266 } catch (final IOException e) { 267 System.err.println(e.getMessage()); 268 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 269 } 270 // --- JCite ADNotification --- 271 } 272 273 /** 274 * Use the LDAP assertion control to modify Babs Jensen's description if 275 * her entry does not have a description, yet. 276 * 277 * @param connection 278 * Active connection to LDAP server containing <a 279 * href="http://opendj.forgerock.org/Example.ldif" 280 * >Example.ldif</a> content. 281 * @throws LdapException 282 * Operation failed. 283 */ 284 static void useAssertionControl(Connection connection) throws LdapException { 285 // --- JCite assertion --- 286 if (isSupported(AssertionRequestControl.OID)) { 287 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 288 289 final ModifyRequest request = 290 Requests.newModifyRequest(dn) 291 .addControl(AssertionRequestControl.newControl( 292 true, Filter.valueOf("!(description=*)"))) 293 .addModification(ModificationType.ADD, "description", 294 "Created using LDAP assertion control"); 295 296 connection.modify(request); 297 298 try (final LDIFEntryWriter writer = new LDIFEntryWriter(System.out)) { 299 writer.writeEntry(connection.readEntry(dn, "description")); 300 } catch (final IOException e) { 301 System.err.println(e.getMessage()); 302 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 303 } 304 } else { 305 System.err.println("AssertionRequestControl not supported."); 306 } 307 // --- JCite assertion --- 308 } 309 310 /** 311 * Use the LDAP Authorization Identity Controls to get the authorization ID. 312 * 313 * @param connection 314 * Active connection to LDAP server containing <a 315 * href="http://opendj.forgerock.org/Example.ldif" 316 * >Example.ldif</a> content. 317 * @throws LdapException 318 * Operation failed. 319 */ 320 static void useAuthorizationIdentityRequestControl(Connection connection) throws LdapException { 321 // --- JCite authzid --- 322 if (isSupported(AuthorizationIdentityRequestControl.OID)) { 323 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 324 final char[] pwd = "hifalutin".toCharArray(); 325 326 System.out.println("Binding as " + dn); 327 final BindRequest request = 328 Requests.newSimpleBindRequest(dn, pwd) 329 .addControl(AuthorizationIdentityRequestControl.newControl(true)); 330 331 final BindResult result = connection.bind(request); 332 try { 333 final AuthorizationIdentityResponseControl control = 334 result.getControl(AuthorizationIdentityResponseControl.DECODER, 335 new DecodeOptions()); 336 System.out.println("Authorization ID returned: " 337 + control.getAuthorizationID()); 338 } catch (final DecodeException e) { 339 System.err.println(e.getMessage()); 340 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 341 } 342 } else { 343 System.err.println("AuthorizationIdentityRequestControl not supported."); 344 } 345 // --- JCite authzid --- 346 } 347 348 /** 349 * Use the GetEffectiveRights Request Control to determine what sort of 350 * access a user has to particular attributes on an entry. 351 * 352 * @param connection 353 * Active connection to LDAP server containing <a 354 * href="http://opendj.forgerock.org/Example.ldif" 355 * >Example.ldif</a> content. 356 * @throws LdapException 357 * Operation failed. 358 */ 359 static void useGetEffectiveRightsRequestControl(Connection connection) throws LdapException { 360 // --- JCite effective rights --- 361 if (isSupported(GetEffectiveRightsRequestControl.OID)) { 362 final String authDN = "uid=kvaughan,ou=People,dc=example,dc=com"; 363 364 final SearchRequest request = 365 Requests.newSearchRequest( 366 "dc=example,dc=com", SearchScope.WHOLE_SUBTREE, 367 "(uid=bjensen)", "cn", "aclRights", "aclRightsInfo") 368 .addControl(GetEffectiveRightsRequestControl.newControl( 369 true, authDN, "cn")); 370 371 final ConnectionEntryReader reader = connection.search(request); 372 try (final LDIFEntryWriter writer = new LDIFEntryWriter(System.out)) { 373 while (reader.hasNext()) { 374 if (!reader.isReference()) { 375 final SearchResultEntry entry = reader.readEntry(); 376 writer.writeEntry(entry); 377 } 378 } 379 } catch (final LdapException e) { 380 System.err.println(e.getMessage()); 381 System.exit(e.getResult().getResultCode().intValue()); 382 } catch (final SearchResultReferenceIOException e) { 383 System.err.println("Got search reference(s): " + e.getReference().getURIs()); 384 } catch (final IOException e) { 385 System.err.println(e.getMessage()); 386 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 387 } 388 } else { 389 System.err.println("GetEffectiveRightsRequestControl not supported."); 390 } 391 // --- JCite effective rights --- 392 } 393 394 /** 395 * Use the ManageDsaIT Request Control to show the difference between a 396 * referral accessed with and without use of the control. 397 * 398 * @param connection 399 * Active connection to LDAP server containing <a 400 * href="http://opendj.forgerock.org/Example.ldif" 401 * >Example.ldif</a> content. 402 * @throws LdapException 403 * Operation failed. 404 */ 405 static void useManageDsaITRequestControl(Connection connection) throws LdapException { 406 // --- JCite manage DsaIT --- 407 if (isSupported(ManageDsaITRequestControl.OID)) { 408 final String dn = "dc=ref,dc=com"; 409 410 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 411 try { 412 System.out.println("Referral without the ManageDsaIT control."); 413 SearchRequest request = Requests.newSearchRequest(dn, 414 SearchScope.SUBORDINATES, "(objectclass=*)", ""); 415 final ConnectionEntryReader reader = connection.search(request); 416 while (reader.hasNext()) { 417 if (reader.isReference()) { 418 final SearchResultReference ref = reader.readReference(); 419 System.out.println("Reference: " + ref.getURIs()); 420 } 421 } 422 423 System.out.println("Referral with the ManageDsaIT control."); 424 request.addControl(ManageDsaITRequestControl.newControl(true)); 425 final SearchResultEntry entry = connection.searchSingleEntry(request); 426 writer.writeEntry(entry); 427 writer.close(); 428 } catch (final LdapException e) { 429 System.err.println(e.getMessage()); 430 System.exit(e.getResult().getResultCode().intValue()); 431 } catch (final SearchResultReferenceIOException e) { 432 System.err.println("Got search reference(s): " + e.getReference().getURIs()); 433 } catch (final IOException e) { 434 System.err.println(e.getMessage()); 435 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 436 } 437 } else { 438 System.err.println("ManageDsaITRequestControl not supported."); 439 } 440 // --- JCite manage DsaIT --- 441 } 442 443 /** 444 * Use the Matched Values Request Control to show read only one attribute 445 * value. 446 * 447 * @param connection 448 * Active connection to LDAP server containing <a 449 * href="http://opendj.forgerock.org/Example.ldif" 450 * >Example.ldif</a> content. 451 * @throws LdapException 452 * Operation failed. 453 */ 454 static void useMatchedValuesRequestControl(Connection connection) throws LdapException { 455 // --- JCite matched values --- 456 if (isSupported(MatchedValuesRequestControl.OID)) { 457 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 458 final SearchRequest request = 459 Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT, 460 "(objectclass=*)", "cn") 461 .addControl(MatchedValuesRequestControl.newControl( 462 true, "(cn=Babs Jensen)")); 463 464 final SearchResultEntry entry = connection.searchSingleEntry(request); 465 System.out.println("Reading entry with matched values request."); 466 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 467 try { 468 writer.writeEntry(entry); 469 writer.close(); 470 } catch (final IOException e) { 471 System.err.println(e.getMessage()); 472 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 473 } 474 } else { 475 System.err.println("MatchedValuesRequestControl not supported."); 476 } 477 // --- JCite matched values --- 478 } 479 480 /** 481 * Check the Password Expired Response Control. To get this code to output 482 * something, you must first set up an appropriate password policy and wait 483 * for Barbara Jensen's password to expire. 484 * 485 * @param connection 486 * Active connection to LDAP server containing <a 487 * href="http://opendj.forgerock.org/Example.ldif" 488 * >Example.ldif</a> content. 489 */ 490 static void usePasswordExpiredResponseControl(Connection connection) { 491 // --- JCite password expired --- 492 if (isSupported(PasswordExpiredResponseControl.OID)) { 493 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 494 final char[] pwd = "hifalutin".toCharArray(); 495 496 try { 497 connection.bind(dn, pwd); 498 } catch (final LdapException e) { 499 final Result result = e.getResult(); 500 try { 501 final PasswordExpiredResponseControl control = 502 result.getControl(PasswordExpiredResponseControl.DECODER, 503 new DecodeOptions()); 504 if (control != null && control.hasValue()) { 505 System.out.println("Password expired for " + dn); 506 } 507 } catch (final DecodeException de) { 508 System.err.println(de.getMessage()); 509 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 510 } 511 } 512 } else { 513 System.err.println("PasswordExpiredResponseControl not supported."); 514 } 515 // --- JCite password expired --- 516 } 517 518 /** 519 * Check the Password Expiring Response Control. To get this code to output 520 * something, you must first set up an appropriate password policy and wait 521 * for Barbara Jensen's password to get old enough that the server starts 522 * warning about expiration. 523 * 524 * @param connection 525 * Active connection to LDAP server containing <a 526 * href="http://opendj.forgerock.org/Example.ldif" 527 * >Example.ldif</a> content. 528 * @throws LdapException 529 * Operation failed. 530 */ 531 static void usePasswordExpiringResponseControl(Connection connection) throws LdapException { 532 // --- JCite password expiring --- 533 if (isSupported(PasswordExpiringResponseControl.OID)) { 534 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 535 final char[] pwd = "hifalutin".toCharArray(); 536 537 final BindResult result = connection.bind(dn, pwd); 538 try { 539 final PasswordExpiringResponseControl control = 540 result.getControl(PasswordExpiringResponseControl.DECODER, 541 new DecodeOptions()); 542 if (control != null && control.hasValue()) { 543 System.out.println("Password for " + dn + " expires in " 544 + control.getSecondsUntilExpiration() + " seconds."); 545 } 546 } catch (final DecodeException de) { 547 System.err.println(de.getMessage()); 548 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 549 } 550 } else { 551 System.err.println("PasswordExpiringResponseControl not supported"); 552 } 553 // --- JCite password expiring --- 554 } 555 556 /** 557 * Use the Password Policy Request and Response Controls. To get this code 558 * to output something, you must first set up an appropriate password policy 559 * and wait for Barbara Jensen's password to get old enough that the server 560 * starts warning about expiration, or for the password to expire. 561 * 562 * @param connection 563 * Active connection to LDAP server containing <a 564 * href="http://opendj.forgerock.org/Example.ldif" 565 * >Example.ldif</a> content. 566 */ 567 static void usePasswordPolicyRequestControl(Connection connection) { 568 // --- JCite password policy --- 569 if (isSupported(PasswordPolicyRequestControl.OID)) { 570 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 571 final char[] pwd = "hifalutin".toCharArray(); 572 573 try { 574 final BindRequest request = Requests.newSimpleBindRequest(dn, pwd) 575 .addControl(PasswordPolicyRequestControl.newControl(true)); 576 577 final BindResult result = connection.bind(request); 578 579 final PasswordPolicyResponseControl control = 580 result.getControl(PasswordPolicyResponseControl.DECODER, 581 new DecodeOptions()); 582 if (control != null && control.getWarningType() != null) { 583 System.out.println("Password policy warning " 584 + control.getWarningType() + ", value " 585 + control.getWarningValue() + " for " + dn); 586 } 587 } catch (final LdapException e) { 588 final Result result = e.getResult(); 589 try { 590 final PasswordPolicyResponseControl control = 591 result.getControl(PasswordPolicyResponseControl.DECODER, 592 new DecodeOptions()); 593 if (control != null) { 594 System.out.println("Password policy error " 595 + control.getErrorType() + " for " + dn); 596 } 597 } catch (final DecodeException de) { 598 System.err.println(de.getMessage()); 599 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 600 } 601 } catch (final DecodeException e) { 602 System.err.println(e.getMessage()); 603 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 604 } 605 } else { 606 System.err.println("PasswordPolicyRequestControl not supported"); 607 } 608 // --- JCite password policy --- 609 } 610 611 /** 612 * Use Permissive Modify Request Control to try to add an attribute that 613 * already exists. 614 * 615 * @param connection 616 * Active connection to LDAP server containing <a 617 * href="http://opendj.forgerock.org/Example.ldif" 618 * >Example.ldif</a> content. 619 * @throws LdapException 620 * Operation failed. 621 */ 622 static void usePermissiveModifyRequestControl(Connection connection) throws LdapException { 623 // --- JCite permissive modify --- 624 if (isSupported(PermissiveModifyRequestControl.OID)) { 625 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 626 627 final ModifyRequest request = 628 Requests.newModifyRequest(dn) 629 .addControl(PermissiveModifyRequestControl.newControl(true)) 630 .addModification(ModificationType.ADD, "uid", "bjensen"); 631 632 connection.modify(request); 633 System.out.println("Permissive modify did not complain about " 634 + "attempt to add uid: bjensen to " + dn + "."); 635 } else { 636 System.err.println("PermissiveModifyRequestControl not supported"); 637 } 638 // --- JCite permissive modify --- 639 } 640 641 /** 642 * Use the LDAP PersistentSearchRequestControl to set up a persistent 643 * search. Also use the Entry Change Notification Response Control to get 644 * details about why an entry was returned for a persistent search. 645 * 646 * After you set this up, use another application to make changes to user 647 * entries under dc=example,dc=com. 648 * 649 * @param connection 650 * Active connection to LDAP server containing <a 651 * href="http://opendj.forgerock.org/Example.ldif" 652 * >Example.ldif</a> content. 653 * @throws LdapException 654 * Operation failed. 655 */ 656 static void usePersistentSearchRequestControl(Connection connection) throws LdapException { 657 // --- JCite psearch --- 658 if (isSupported(PersistentSearchRequestControl.OID)) { 659 final SearchRequest request = 660 Requests.newSearchRequest( 661 "dc=example,dc=com", SearchScope.WHOLE_SUBTREE, 662 "(objectclass=inetOrgPerson)", "cn") 663 .addControl(PersistentSearchRequestControl.newControl( 664 true, true, true, // isCritical, changesOnly, returnECs 665 PersistentSearchChangeType.ADD, 666 PersistentSearchChangeType.DELETE, 667 PersistentSearchChangeType.MODIFY, 668 PersistentSearchChangeType.MODIFY_DN)); 669 670 final ConnectionEntryReader reader = connection.search(request); 671 672 try { 673 while (reader.hasNext()) { 674 if (!reader.isReference()) { 675 final SearchResultEntry entry = reader.readEntry(); 676 System.out.println("Entry changed: " + entry.getName()); 677 678 final EntryChangeNotificationResponseControl control = 679 entry.getControl( 680 EntryChangeNotificationResponseControl.DECODER, 681 new DecodeOptions()); 682 683 final PersistentSearchChangeType type = control.getChangeType(); 684 System.out.println("Change type: " + type); 685 if (type.equals(PersistentSearchChangeType.MODIFY_DN)) { 686 System.out.println("Previous DN: " + control.getPreviousName()); 687 } 688 System.out.println("Change number: " + control.getChangeNumber()); 689 System.out.println(); // Add a blank line. 690 } 691 } 692 } catch (final DecodeException e) { 693 System.err.println(e.getMessage()); 694 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 695 } catch (final LdapException e) { 696 System.err.println(e.getMessage()); 697 System.exit(e.getResult().getResultCode().intValue()); 698 } catch (final SearchResultReferenceIOException e) { 699 System.err.println("Got search reference(s): " + e.getReference().getURIs()); 700 } 701 } else { 702 System.err.println("PersistentSearchRequestControl not supported."); 703 } 704 // --- JCite psearch --- 705 } 706 707 708 /** 709 * Use Post Read Controls to get entry content after a modification. 710 * 711 * @param connection 712 * Active connection to LDAP server containing <a 713 * href="http://opendj.forgerock.org/Example.ldif" 714 * >Example.ldif</a> content. 715 * @throws LdapException 716 * Operation failed. 717 */ 718 static void usePostReadRequestControl(Connection connection) throws LdapException { 719 // --- JCite post read --- 720 if (isSupported(PostReadRequestControl.OID)) { 721 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 722 723 final ModifyRequest request = 724 Requests.newModifyRequest(dn) 725 .addControl(PostReadRequestControl.newControl(true, "description")) 726 .addModification(ModificationType.REPLACE, 727 "description", "Using the PostReadRequestControl"); 728 729 final Result result = connection.modify(request); 730 try { 731 final PostReadResponseControl control = 732 result.getControl(PostReadResponseControl.DECODER, 733 new DecodeOptions()); 734 final Entry entry = control.getEntry(); 735 736 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 737 writer.writeEntry(entry); 738 writer.close(); 739 } catch (final DecodeException e) { 740 System.err.println(e.getMessage()); 741 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 742 } catch (final IOException e) { 743 System.err.println(e.getMessage()); 744 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 745 } 746 } else { 747 System.err.println("PostReadRequestControl not supported"); 748 } 749 // --- JCite post read --- 750 } 751 752 /** 753 * Use Pre Read Controls to get entry content before a modification. 754 * 755 * @param connection 756 * Active connection to LDAP server containing <a 757 * href="http://opendj.forgerock.org/Example.ldif" 758 * >Example.ldif</a> content. 759 * @throws LdapException 760 * Operation failed. 761 */ 762 static void usePreReadRequestControl(Connection connection) throws LdapException { 763 // --- JCite pre read --- 764 if (isSupported(PreReadRequestControl.OID)) { 765 final String dn = "uid=bjensen,ou=People,dc=example,dc=com"; 766 767 final ModifyRequest request = 768 Requests.newModifyRequest(dn) 769 .addControl(PreReadRequestControl.newControl(true, "mail")) 770 .addModification( 771 ModificationType.REPLACE, "mail", "modified@example.com"); 772 773 final Result result = connection.modify(request); 774 try { 775 final PreReadResponseControl control = 776 result.getControl(PreReadResponseControl.DECODER, 777 new DecodeOptions()); 778 final Entry entry = control.getEntry(); 779 780 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 781 writer.writeEntry(entry); 782 writer.close(); 783 } catch (final DecodeException e) { 784 System.err.println(e.getMessage()); 785 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 786 } catch (final IOException e) { 787 System.err.println(e.getMessage()); 788 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 789 } 790 } else { 791 System.err.println("PreReadRequestControl not supported"); 792 } 793 // --- JCite pre read --- 794 } 795 796 /** 797 * Use proxied authorization to modify an identity as another user. 798 * 799 * @param connection 800 * Active connection to LDAP server containing <a 801 * href="http://opendj.forgerock.org/Example.ldif" 802 * >Example.ldif</a> content. 803 * @throws LdapException 804 * Operation failed. 805 */ 806 static void useProxiedAuthV2RequestControl(Connection connection) throws LdapException { 807 // --- JCite proxied authzv2 --- 808 if (isSupported(ProxiedAuthV2RequestControl.OID)) { 809 final String bindDN = "cn=My App,ou=Apps,dc=example,dc=com"; 810 final String targetDn = "uid=bjensen,ou=People,dc=example,dc=com"; 811 final String authzId = "dn:uid=kvaughan,ou=People,dc=example,dc=com"; 812 813 final ModifyRequest request = 814 Requests.newModifyRequest(targetDn) 815 .addControl(ProxiedAuthV2RequestControl.newControl(authzId)) 816 .addModification(ModificationType.REPLACE, "description", 817 "Done with proxied authz"); 818 819 connection.bind(bindDN, "password".toCharArray()); 820 connection.modify(request); 821 final Entry entry = connection.readEntry(targetDn, "description"); 822 823 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 824 try { 825 writer.writeEntry(entry); 826 writer.close(); 827 } catch (final IOException e) { 828 System.err.println(e.getMessage()); 829 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 830 } 831 } else { 832 System.err.println("ProxiedAuthV2RequestControl not supported"); 833 } 834 // --- JCite proxied authzv2 --- 835 } 836 837 /** 838 * Use the server-side sort controls. 839 * 840 * @param connection 841 * Active connection to LDAP server containing <a 842 * href="http://opendj.forgerock.org/Example.ldif" 843 * >Example.ldif</a> content. 844 * @throws LdapException 845 * Operation failed. 846 */ 847 // --- JCite server-side sort --- 848 static void useServerSideSortRequestControl(Connection connection) throws LdapException { 849 if (isSupported(ServerSideSortRequestControl.OID)) { 850 final SearchRequest request = 851 Requests.newSearchRequest("ou=People,dc=example,dc=com", 852 SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn") 853 .addControl(ServerSideSortRequestControl.newControl( 854 true, new SortKey("cn"))); 855 856 final SearchResultHandler resultHandler = new MySearchResultHandler(); 857 final Result result = connection.search(request, resultHandler); 858 859 try { 860 final ServerSideSortResponseControl control = 861 result.getControl(ServerSideSortResponseControl.DECODER, 862 new DecodeOptions()); 863 if (control != null && control.getResult() == ResultCode.SUCCESS) { 864 System.out.println("# Entries are sorted."); 865 } else { 866 System.out.println("# Entries not necessarily sorted"); 867 } 868 } catch (final DecodeException e) { 869 System.err.println(e.getMessage()); 870 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 871 } 872 } else { 873 System.err.println("ServerSideSortRequestControl not supported"); 874 } 875 } 876 877 private static class MySearchResultHandler implements SearchResultHandler { 878 879 @Override 880 public boolean handleEntry(SearchResultEntry entry) { 881 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 882 try { 883 writer.writeEntry(entry); 884 writer.flush(); 885 } catch (final IOException e) { 886 System.err.println(e.getMessage()); 887 return false; 888 } 889 return true; 890 } 891 892 @Override 893 public boolean handleReference(SearchResultReference reference) { 894 System.out.println("Got a reference: " + reference); 895 return false; 896 } 897 } 898 // --- JCite server-side sort --- 899 900 /** 901 * Use the simple paged results mechanism. 902 * 903 * @param connection 904 * Active connection to LDAP server containing <a 905 * href="http://opendj.forgerock.org/Example.ldif" 906 * >Example.ldif</a> content. 907 * @throws LdapException 908 * Operation failed. 909 */ 910 static void useSimplePagedResultsControl(Connection connection) throws LdapException { 911 // --- JCite simple paged results --- 912 if (isSupported(SimplePagedResultsControl.OID)) { 913 ByteString cookie = ByteString.empty(); 914 SearchRequest request; 915 final SearchResultHandler resultHandler = new MySearchResultHandler(); 916 Result result; 917 918 int page = 1; 919 do { 920 System.out.println("# Simple paged results: Page " + page); 921 922 request = 923 Requests.newSearchRequest("dc=example,dc=com", 924 SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn") 925 .addControl(SimplePagedResultsControl.newControl( 926 true, 3, cookie)); 927 928 result = connection.search(request, resultHandler); 929 try { 930 SimplePagedResultsControl control = 931 result.getControl(SimplePagedResultsControl.DECODER, 932 new DecodeOptions()); 933 cookie = control.getCookie(); 934 } catch (final DecodeException e) { 935 System.err.println(e.getMessage()); 936 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 937 } 938 939 ++page; 940 } while (cookie.length() != 0); 941 } else { 942 System.err.println("SimplePagedResultsControl not supported"); 943 } 944 // --- JCite simple paged results --- 945 } 946 947 /** 948 * Use the subentries request control. 949 * 950 * @param connection 951 * Active connection to LDAP server containing <a 952 * href="http://opendj.forgerock.org/Example.ldif" 953 * >Example.ldif</a> content. 954 * @throws LdapException 955 * Operation failed. 956 */ 957 static void useSubentriesRequestControl(Connection connection) throws LdapException { 958 // --- JCite subentries --- 959 if (isSupported(SubentriesRequestControl.OID)) { 960 final SearchRequest request = 961 Requests.newSearchRequest("dc=example,dc=com", 962 SearchScope.WHOLE_SUBTREE, 963 "cn=*Class of Service", "cn", "subtreeSpecification") 964 .addControl(SubentriesRequestControl.newControl( 965 true, true)); 966 967 final ConnectionEntryReader reader = connection.search(request); 968 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out); 969 try { 970 while (reader.hasNext()) { 971 if (reader.isEntry()) { 972 final SearchResultEntry entry = reader.readEntry(); 973 writer.writeEntry(entry); 974 } 975 } 976 writer.close(); 977 } catch (final LdapException e) { 978 System.err.println(e.getMessage()); 979 System.exit(e.getResult().getResultCode().intValue()); 980 } catch (final SearchResultReferenceIOException e) { 981 System.err.println("Got search reference(s): " + e.getReference().getURIs()); 982 } catch (final IOException e) { 983 System.err.println(e.getMessage()); 984 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue()); 985 } 986 } else { 987 System.err.println("SubentriesRequestControl not supported"); 988 } 989 // --- JCite subentries --- 990 } 991 992 /** 993 * Use the subtree delete control. 994 * 995 * @param connection 996 * Active connection to LDAP server containing <a 997 * href="http://opendj.forgerock.org/Example.ldif" 998 * >Example.ldif</a> content. 999 * @throws LdapException 1000 * Operation failed. 1001 */ 1002 static void useSubtreeDeleteRequestControl(Connection connection) throws LdapException { 1003 // --- JCite tree delete --- 1004 if (isSupported(SubtreeDeleteRequestControl.OID)) { 1005 1006 final String dn = "ou=Apps,dc=example,dc=com"; 1007 final DeleteRequest request = 1008 Requests.newDeleteRequest(dn) 1009 .addControl(SubtreeDeleteRequestControl.newControl(true)); 1010 1011 final Result result = connection.delete(request); 1012 if (result.isSuccess()) { 1013 System.out.println("Successfully deleted " + dn 1014 + " and all entries below."); 1015 } else { 1016 System.err.println("Result: " + result.getDiagnosticMessage()); 1017 } 1018 } else { 1019 System.err.println("SubtreeDeleteRequestControl not supported"); 1020 } 1021 // --- JCite tree delete --- 1022 } 1023 1024 /** 1025 * Use the virtual list view controls. In order to set up OpenDJ directory 1026 * server to produce the following output with the example code, use OpenDJ 1027 * Control Panel > Manage Indexes > New VLV Index... to set up a 1028 * virtual list view index for people by last name, using the filter 1029 * {@code (|(givenName=*)(sn=*))}, and sorting first by surname, {@code sn}, 1030 * in ascending order, then by given name also in ascending order 1031 * 1032 * @param connection 1033 * Active connection to LDAP server containing <a 1034 * href="http://opendj.forgerock.org/Example.ldif" 1035 * >Example.ldif</a> content. 1036 * @throws LdapException 1037 * Operation failed. 1038 */ 1039 static void useVirtualListViewRequestControl(Connection connection) throws LdapException { 1040 // --- JCite vlv --- 1041 if (isSupported(VirtualListViewRequestControl.OID)) { 1042 ByteString contextID = ByteString.empty(); 1043 1044 // Add a window of 2 entries on either side of the first sn=Jensen entry. 1045 final SearchRequest request = 1046 Requests.newSearchRequest("ou=People,dc=example,dc=com", 1047 SearchScope.WHOLE_SUBTREE, "(sn=*)", "sn", "givenName") 1048 .addControl(ServerSideSortRequestControl.newControl( 1049 true, new SortKey("sn"))) 1050 .addControl( 1051 VirtualListViewRequestControl.newAssertionControl( 1052 true, 1053 ByteString.valueOfUtf8("Jensen"), 1054 2, 2, contextID)); 1055 1056 final SearchResultHandler resultHandler = new MySearchResultHandler(); 1057 final Result result = connection.search(request, resultHandler); 1058 1059 try { 1060 final ServerSideSortResponseControl sssControl = 1061 result.getControl(ServerSideSortResponseControl.DECODER, 1062 new DecodeOptions()); 1063 if (sssControl != null && sssControl.getResult() == ResultCode.SUCCESS) { 1064 System.out.println("# Entries are sorted."); 1065 } else { 1066 System.out.println("# Entries not necessarily sorted"); 1067 } 1068 1069 final VirtualListViewResponseControl vlvControl = 1070 result.getControl(VirtualListViewResponseControl.DECODER, 1071 new DecodeOptions()); 1072 System.out.println("# Position in list: " 1073 + vlvControl.getTargetPosition() + "/" 1074 + vlvControl.getContentCount()); 1075 } catch (final DecodeException e) { 1076 System.err.println(e.getMessage()); 1077 System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue()); 1078 } 1079 } else { 1080 System.err.println("VirtualListViewRequestControl not supported"); 1081 } 1082 // --- JCite vlv --- 1083 } 1084 1085 // --- JCite check support --- 1086 /** 1087 * Controls supported by the LDAP server. 1088 */ 1089 private static Collection<String> controls; 1090 1091 /** 1092 * Populate the list of supported LDAP control OIDs. 1093 * 1094 * @param connection 1095 * Active connection to the LDAP server. 1096 * @throws LdapException 1097 * Failed to get list of controls. 1098 */ 1099 static void checkSupportedControls(Connection connection) throws LdapException { 1100 controls = RootDSE.readRootDSE(connection).getSupportedControls(); 1101 } 1102 1103 /** 1104 * Check whether a control is supported. Call {@code checkSupportedControls} 1105 * first. 1106 * 1107 * @param control 1108 * Check support for this control, provided by OID. 1109 * @return True if the control is supported. 1110 */ 1111 static boolean isSupported(final String control) { 1112 return controls != null && controls.contains(control); 1113 } 1114 // --- JCite check support --- 1115 1116 /** 1117 * Constructor not used. 1118 */ 1119 private Controls() { 1120 // Not used. 1121 } 1122}