UnboundID LDAP SDK for Java
Using Standard ControlsThe UnboundID LDAP SDK for Java provides support for a number of common controls that are either defined in RFCs, or are based on IETF Internet Drafts that are considered stable enough to be implemented by many directory server implementations. Including Controls in RequestsAll of the request objects provided by the UnboundID LDAP SDK for Java allow you to include controls in the request. You can include the controls in the constructor when creating the request object, or you can use the getControls, setControls, addControl, removeControl, and clearControls methods to interact with the set of request controls after the request has been created. Note that while the LDAP SDK does not provide an explicit UnboundRequest object, you can include one or more controls in an unbind request by calling the close(Control[] controls) method in the LDAPConnection class. Accessing Response ControlsWhenever an operation completes successfully, it will return an LDAPResult object (or one of its subclasses, like BindResult). The set of response controls provided by the server can be obtained using the getResponseControls method on that LDAPResult object. If an operation does not complete successfully, then the SDK may instead throw an LDAPException (or one of its subclasses, like LDAPSearchException). In this case, the set of response controls provided by the server can be obtained using the getResponseControls method on that LDAPException object. Search operations may cause the server to send multiple response messages to the client (one for each entry or reference, and then a final search result done message to indicate that the search is complete). When the search completes, the SearchResult object returned (or LDAPSearchException that is thrown) will have the controls included in the search result done message, and they can be obtained using the getResponseControls method as described above. However, each search result entry or search result reference may include its own set of controls, and the getControls method may be used to obtain that set of controls from the associated SearchResultEntry or SearchResultReference object. Note that whenever the LDAP SDK receives a response containing one or more controls, it will attempt to decode that control as the most specific type of object that it can. For example, if a response control has an OID of "2.16.840.1.113730.3.4.15", then the LDAP SDK will attempt to decode it as an AuthorizationIdentityResponseControl rather than just as a generic Control object. This will automatically happen for all types of response controls supported by the UnboundID LDAP SDK for Java, but if you define your own custom control for use with the SDK, then you may call the Control.registerDecodableControl method to allow the SDK to attempt to perform this specific decoding for that custom response control. The Authorization Identity ControlsThe authorization identity request and response controls are defined in RFC 3829 and may be used by the server to request information about the new authorization identity for a client connection after a bind operation is processed. The AuthorizationIdentityRequestControl may be included in a bind request to indicate that the server should include a corresponding AuthorizationIdentityResponseControl in the bind response. If the bind is successful, then the bind response may include this response control and its getAuthorizationID method may be used to obtain the authorization ID returned by the server. For example, the following code may be used to authenticate to the server and try to retrieve the authorization identity from the response:
String authzID = null;
BindRequest bindRequest =
new SimpleBindRequest("uid=john.doe,ou=People,dc=example,dc=com",
"password",
new AuthorizationIdentityRequestControl());
BindResult bindResult = connection.bind(bindRequest);
for (Control c : bindResult.getResponseControls())
{
if (c instanceof AuthorizationIdentityResponseControl)
{
AuthorizationIdentityResponseControl authzControl =
(AuthorizationIdentityResponseControl) c;
authzID = authzControl.getAuthorizationID();
break;
}
}
The LDAP Assertion Request ControlThe LDAP assertion request control is defined in RFC 4528. It provides the ability to define a search filter that must match the target entry in order for the associated operation to be processed. If the target entry does not match the assertion filter, then the server should not process that operation but instead return a response with the "Authorization Failed" result (which in most cases will cause the SDK to throw an LDAPException for the associated operation). There is no corresponding response control. For example, the following code may be used to modify an entry to set an entry's "accountBalance" value to "543.21" only if the current value is "1234.56" (which can help prevent against race conditions that result from multiple concurrent changes to the value):
Modification mod =
new Modification(ModificationType.REPLACE, "accountBalance", "543.21");
ModifyRequest modifyRequest =
new ModifyRequest("uid=john.doe,ou=People,dc=example,dc=com", mod);
modifyRequest.addControl(
new AssertionRequestControl("(accountBalance=1234.56)"));
try
{
LDAPResult modifyResult = connection.modify(modifyRequest);
// If we've gotten here, then the modification was successful.
}
catch (LDAPException le)
{
if (le.getResultCode() == ResultCode.ASSERTION_FAILED)
{
// The modification failed because the accountBalance value wasn't
// what we thought it was.
}
else
{
// The modification failed for some other reason.
}
}
The LDAP Read Entry ControlsThe LDAP read entry controls are defined in RFC 4527 and make it possible to retrieve the contents of an entry either immediately before or immediately after processing an operation. The pre-read request control may be used with delete, modify, and modify DN operations to retrieve the entry as it appeared immediately before the operation, and the post-read control may be used with the add, modify, and modify DN operations to retrieve the entry as it appeared immediately after the operation. The PreReadRequestControl and PostReadRequestControl objects are very similar, and may specify an optional set of attributes to be included in the entry that is returned. Similarly, the corresponding PreReadResponseControl and PostReadResponseControl objects are also nearly identical, and provide access to the requested entry through the getEntry method. For example, the following code will increment the value of the "testCounter" attribute by one and will then use the post-read controls to determine what the new value is:
Modification mod = new Modification(ModificationType.INCREMENT,
"testCounter", 1);
ModifyRequest modifyRequest =
new ModifyRequest("uid=john.doe,ou=People,dc=example,dc=com", mod);
modifyRequest.addControl(new PostReadRequestControl("testCounter"));
LDAPResult modifyResult = connection.modify(modifyRequest);
Integer newValue = null;
for (Control c : modifyResult.getResponseControls())
{
if (c instanceof PostReadResponseControl)
{
ReadOnlyEntry e = ((PostReadResponseControl) c).getEntry();
if (e.hasAttribute("testCounter"))
{
newValue = Integer.parseInt(e.getAttributeValue("testCounter"));
}
}
}
The LDAP Subentries Request ControlThe LDAP subentries request control is defined in draft-ietf-ldup-subentry, and may be used to indicate that a search operation should include matching entries that have the ldapSubentry object class (which are normally excluded from search results). There is no corresponding response control. The following example illustrates the use of the LDAP subentries request control to retrieve all subentries defined below "dc=example,dc=com":
SearchRequest searchRequest =
new SearchRequest("dc=example,dc=com", SearchScope.SUB,
"(objectClass=ldapSubentry)");
searchRequest.addControl(new SubentriesRequestControl());
SearchResult searchResult = connection.search(searchRequest);
for (SearchResultEntry e : searchResult.getSearchEntries())
{
// Do something with the entry.
}
The ManageDsaIT Request ControlThe ManageDsaIT request control is defined in RFC 3296 and may be used to request that the directory server treat all entries as if they were regular entries. There is no corresponding response control. For example, if the entry "uid=john.doe,ou=People,dc=example,dc=com" is actually a smart referral that points to the entry "uid=johnathan.doe,ou=People,dc=example,dc=com", then normal attempts to interact with the "uid=john.doe,ou=People,dc=example,dc=com" entry would cause the server to send a referral informing the client that it should operate on the "uid=johnathan.doe,ou=People,dc=example,dc=com" entry instead. If you really do want to interact with the "uid=john.doe,ou=People,dc=example,dc=com" entry (e.g., to delete the referral), then it will be necessary to include the ManageDsaIT request control, as follows:
DeleteRequest deleteRequest =
new DeleteRequest("uid=john.doe,ou=People,dc=example,dc=com");
deleteRequest.addControl(new ManageDsaITRequestControl());
LDAPResult deleteResult = connection.delete(deleteRequest);
The Matched Values ControlThe matched values request control is defined in RFC 3876 and may be used in a search operation to request that only attribute values matching one or more filters should be included in matching entries. This can be useful, for example, if an attribute has a large number of values and you are only interested in values matching a specified set of criteria. There is no corresponding response control. For example, the following code will cause only "myIntValues" values to be returned which are greater than or equal to 5:
SearchRequest searchRequest =
new SearchRequest("uid=john.doe,ou=People,dc=example,dc=com",
SearchScope.BASE, "(objectClass=*)",
"myIntValues");
searchRequest.addControl(new MatchedValuesRequestControl(
MatchedValuesFilter.createGreaterOrEqualFilter("myIntValues", "5"));
SearchResult searchResult = connection.search(searchRequest);
The Password Expired and Password Expiring Response ControlsThe password expired and password expiring controls are defined in draft-vchu-ldap-pwd-policy and may be returned by the server with a bind response to indicate that the user's password either is expired or is about to expire. If the password is about to expire, then the password expiring response control will include the length of time until the password actually expires. For example:
BindRequest bindRequest =
new SimpleBindRequest("uid=john.doe,ou=People,dc=example,dc=com",
"password");
try
{
BindResult bindResult = connection.bind(bindRequest);
for (Control c : bindResult.getResponseControls())
{
if (c instanceof PasswordExpiringControl)
{
System.err.println("WARNING: Your password will expire in " +
((PasswordExpiringControl) c).getSecondsUntilExpiration() +
" seconds.");
}
else if (c instanceof PasswordExpiredControl)
{
System.err.println("WARNING: You must change your password " +
"before you will be allowed to perform any other operations.");
}
}
}
catch (LDAPException le)
{
for (Control c : le.getResponseControls())
{
if (c instanceof PasswordExpiredControl)
{
System.err.println("ERROR: Your password is expired.");
}
}
}
The Persistent Search and Entry Change Notification ControlsThe persistent search request control is defined in draft-ietf-ldapext-psearch and may be included in a search request to indicate that the server should send a search result entry message each time an entry matching the associated search criteria is updated in the server. This type of search is somewhat unique in that it generally doesn't end on its own, and it can also return the same entry multiple times (or at least different versions of that entry) if the same entry is updated multiple times. Note that because persistent searches don't necessarily have a defined end and you will want to be able to do something with entries as soon as they are updated, then you should only use the persistent search request control in a search that uses a SearchResultListener to handle the entries that get returned. Those entries may optionally contain the entry change notification control, which can include additional information about the update, including the type of operation (add, delete, modify, or modify DN), and potentially the change number and/or previous DN (if it was a modify DN operation). For example, the following code will begin an asynchronous search including the persistent search control that will notify the client of all changes to entries at or below "dc=example,dc=com".
SearchRequest searchRequest =
new SearchRequest(myAsyncSearchListener, "dc=example,dc=com",
SearchScope.SUB, "(objectClass=*)");
searchRequest.addControl(new PersistentSearchRequestControl(
PersistentSearchChangeType.allChangeTypes(), true, true);
AsyncRequestID asyncRequestID = connection.asyncSearch(searchRequest);
The Proxied Authorization Request ControlsThe proxied authorization request controls may be used to request that an operation be processed under the authority of another user. Proxied authorization is frequently used in conjunction with connection pools because it allows the client to maintain a set of connections authenticated as a given user (which has permission to use the proxied authorization control) and use those connections to perform operations in the server under the authority of the actual end user that triggered the request. There is no corresponding response control. The UnboundID LDAP SDK for Java supports two different versions of the proxied authorization control. A number of directory servers implement support for the proxied authorization V1 control, which is defined in early versions of the draft-weltman-ldapv3-proxy Internet Draft. More recently, however, this specification has been updated and released as RFC 4370 to define the proxied authorization V2 control. The two controls have different OIDs, and also take different arguments. The target user for the proxied authorization V1 control must be a distinguished name, whereas the target user for the proxied authorization V2 control must be an authorization ID (as defined in section 9 of RFC 2829, e.g., "dn:uid=john.doe,ou=People,dc=example,dc=com" or "u:john.doe"). In general, it is recommended to use the proxied authorization V2 control in preference to V1 when possible, since it has a more well-defined specification. For example, the following code demonstrates the use of the proxied authorization V2 request control to delete an entry under the authority of the user with DN "uid=john.doe,ou=People,dc=example,dc=com":
DeleteRequest deleteRequest =
new DeleteRequest("cn=no longer needed,dc=example,dc=com");
deleteRequest.addControl(new ProxiedAuthorizationV2RequestControl(
"dn:uid=john.doe,ou=People,dc=example,dc=com"));
try
{
LDAPResult deleteResult = connection.delete(deleteRequest);
// If we get here, then the delete was successful.
}
catch (LDAPException le)
{
if (le.getResultCode() == ResultCode.AUTHORIZATION_DENIED)
{
// The authenticated user does not have permission to use the proxied
// authorization control.
}
else
{
// Some other problem occurred that prevented the delete from succeeding.
}
}
The Server-Side Sort ControlsThe server-side sort request control may be used to request that the server sort the set of matching entries before returning them to the client. A corresponding response control can provide information about the result of the sort processing. Note that this can be an expensive operation in some cases, so LDAP client developers that may wish to use this feature should first discuss it with an administrator of the target directory to determine whether that is appropriate and if the server is appropriately configured to process such requests efficiently. As an alternative, it may be desirable to perform client-side sorting, which will significantly reduce the load on the server. The following example demonstrates the use of the server-side sort controls to request that the server return all users in the sales department, sorted by last name and then by first name.
SearchRequest searchRequest =
new SearchRequest("dc=example,dc=com", SearchScope.SUB, "(ou=Sales)");
searchRequest.addControl(new ServerSideSortRequestControl(
new SortKey("sn"), new SortKey("givenName")));
SearchResult searchResult = connection.search(searchRequest);
The Simple Paged Results ControlThe simple paged results control is defined in RFC 2696 and allows the client to read "pages" of results (where each "page" contains a subset of the overall set of matching entries). It is similar to the virtual list view control discussed below, although it does not provide as many options, but also does not include the constraint that the results be sorted. Some servers support the simple paged results control but not the virtual list view control, or vice versa, so it is recommended to contact your directory administrator to determine what options are available. The simple paged results control is both a request and response control. On the first request to the server, it should contain a page size but no "cookie" value. When the server has finished sending the specified number of entries, it will include the simple paged results control in the search result done message, and if there are more results to return then that response will include a cookie value that can be used to help the server figure out where to resume processing for the next page of results. The following code demonstrates the use of the simple paged results control to return all users in the "Sales" department, retrieving up to 10 entries at a time:
SearchRequest searchRequest =
new SearchRequest("dc=example,dc=com", SearchScope.SCOPE_SUB,
"(ou=Sales)");
ASN1OctetString cookie = null;
do
{
searchRequest.setControls(
new Control[] { new SimplePagedResultsControl(10, cookie) });
SearchResult searchResult = connection.search(searchRequest);
// Do something with the entries that are returned.
cookie = null;
for (Control c : searchResult.getResponseControls())
{
if (c instanceof SimplePagedResultsControl)
{
cookie = ((SimplePagedResultsControl) c).getCookie();
}
}
} while (cookie != null);
The Subtree Delete Request ControlThe subtree delete request control is defined in draft-armijo-ldap-treedelete and may be used to request that the server should delete the specified entry and all of its subordinates. Without this control, if an entry has one or more subordinates, then the server will return a "not allowed on non-leaf" result and will refuse to process the delete. There is no corresponding response control. Note that performing a subtree delete on an entry with a large number of subordinates can be a very expensive operation. If you wish to use this control on a large subtree, then it is recommended that you first discuss it with the directory administrator. The following example demonstrates the use of the subtree delete control:
DeleteRequest deleteRequest =
new DeleteRequest("cn=small subtree,dc=example,dc=com");
deleteRequest.addControl(new SubtreeDeleteRequestControl());
LDAPResult deleteResult = connection.delete(deleteRequest);
The Virtual List View ControlsThe virtual list view controls are defined in draft-ietf-ldapext-ldapv3-vlv, and may be used to retrieve arbitrary pages of results matching the given search criteria. It is similar to the simple paged results control, but requires that the search request also include the server-side sort request (which is not a requirement with the simple paged results control) and also allows the client to request arbitrary pages of the results (whereas the simple paged results control only allows sequential iteration through the result pages). The page of results to be returned can be specified either by offset (the position of the target entry in the sorted result set) or based on the value of the primary sort key (e.g., start at the entry with a primary sort key value greater than or equal to "k"). Another benefit of the virtual list view control over the simple paged results control is that it can provide the client with an estimate of the total number of entries that match the search. This is important to help the client know when it has reached the end of the results. The following example illustrates the use of the virtual list view request control to return all users in the "Sales" department, retrieving up to 10 entries at a time:
ServerSideSortRequestControl sortRequest =
new ServerSideSortRequestControl(new SortKey("sn"),
new SortKey("givenName"));
SearchRequest searchRequest =
new SearchRequest("dc=example,dc=com", SearchScope.SCOPE_SUB,
"(ou=Sales)");
int offset = 1;
int contentCount = 0;
ASN1OctetString contextID = null;
while (true)
{
VirtualListViewRequestControl vlvRequest =
new VirtualListViewRequestControl(offset, 0, 9, contentCount,
contextID);
searchRequest.setControls(new Control[] { sortRequest, vlvRequest });
SearchResult searchResult = connection.search(searchRequest);
// Do something with the entries that are returned.
contentCount = -1;
for (Control c : searchResult.getResponseControls())
{
if (c instanceof VirtualListViewResponseControl)
{
VirtualListViewResponseControl vlvResponse =
(VirtualListViewResponseControl) c;
contentCount = vlvResponse.getContentCount();
contextID = vlvResponse.getContextID();
}
}
offset += 10;
if (offset > contentCount)
{
break;
}
}
|

