Using Standard Controls
The 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 Requests
All 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 Controls
Whenever 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 Controls
The 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 Control
The 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 Controls
The 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 Control
The 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 Control
The 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 Control
The 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 Controls
The 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 Controls
The 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 Controls
The 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 Controls
The 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 Control
The 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 Control
The 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 Controls
The 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;
}
}
|