001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2009-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2014 ForgeRock AS
026 */
027package org.forgerock.opendj.ldap;
028
029import java.util.Collection;
030import java.util.Collections;
031
032import org.forgerock.opendj.ldap.requests.Requests;
033import org.forgerock.opendj.ldap.requests.SearchRequest;
034import org.forgerock.opendj.ldap.responses.SearchResultEntry;
035import org.forgerock.opendj.ldap.schema.CoreSchema;
036import org.forgerock.util.Reject;
037import org.forgerock.util.Function;
038import org.forgerock.util.promise.NeverThrowsException;
039
040import com.forgerock.opendj.util.Collections2;
041
042/**
043 * The root DSE is a DSA-specific Entry (DSE) and not part of any naming context
044 * (or any subtree), and which is uniquely identified by the empty DN.
045 * <p>
046 * A Directory Server uses the root DSE to provide information about itself
047 * using the following set of attributes:
048 * <ul>
049 * <li>{@code altServer}: alternative Directory Servers
050 * <li>{@code namingContexts}: naming contexts
051 * <li>{@code supportedControl}: recognized LDAP controls
052 * <li>{@code supportedExtension}: recognized LDAP extended operations
053 * <li>{@code supportedFeatures}: recognized LDAP features
054 * <li>{@code supportedLDAPVersion}: LDAP versions supported
055 * <li>{@code supportedSASLMechanisms}: recognized SASL authentication
056 * mechanisms
057 * <li>{@code supportedAuthPasswordSchemes}: recognized authentication password
058 * schemes
059 * <li>{@code subschemaSubentry}: the name of the subschema subentry holding the
060 * schema controlling the Root DSE
061 * <li>{@code vendorName}: the name of the Directory Server implementer
062 * <li>{@code vendorVersion}: the version of the Directory Server
063 * implementation.
064 * </ul>
065 * The values provided for these attributes may depend on session- specific and
066 * other factors. For example, a server supporting the SASL EXTERNAL mechanism
067 * might only list "EXTERNAL" when the client's identity has been established by
068 * a lower level.
069 * <p>
070 * The root DSE may also include a {@code subschemaSubentry} attribute. If it
071 * does, the attribute refers to the subschema (sub)entry holding the schema
072 * controlling the root DSE. Clients SHOULD NOT assume that this subschema
073 * (sub)entry controls other entries held by the server.
074 *
075 * @see <a href="http://tools.ietf.org/html/rfc4512">RFC 4512 - Lightweight
076 *      Directory Access Protocol (LDAP): Directory Information Models </a>
077 * @see <a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing Vendor
078 *      Information in the LDAP Root DSE </a>
079 * @see <a href="http://tools.ietf.org/html/rfc3112">RFC 3112 - LDAP
080 *      Authentication Password Schema </a>
081 */
082public final class RootDSE {
083    private static final AttributeDescription ATTR_ALT_SERVER = AttributeDescription
084            .create(CoreSchema.getAltServerAttributeType());
085
086    private static final AttributeDescription ATTR_NAMING_CONTEXTS = AttributeDescription
087            .create(CoreSchema.getNamingContextsAttributeType());
088
089    private static final AttributeDescription ATTR_SUBSCHEMA_SUBENTRY = AttributeDescription
090            .create(CoreSchema.getSubschemaSubentryAttributeType());
091
092    private static final AttributeDescription ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES =
093            AttributeDescription.create(CoreSchema.getSupportedAuthPasswordSchemesAttributeType());
094
095    private static final AttributeDescription ATTR_SUPPORTED_CONTROL = AttributeDescription
096            .create(CoreSchema.getSupportedControlAttributeType());
097
098    private static final AttributeDescription ATTR_SUPPORTED_EXTENSION = AttributeDescription
099            .create(CoreSchema.getSupportedExtensionAttributeType());
100
101    private static final AttributeDescription ATTR_SUPPORTED_FEATURE = AttributeDescription
102            .create(CoreSchema.getSupportedFeaturesAttributeType());
103
104    private static final AttributeDescription ATTR_SUPPORTED_LDAP_VERSION = AttributeDescription
105            .create(CoreSchema.getSupportedLDAPVersionAttributeType());
106
107    private static final AttributeDescription ATTR_SUPPORTED_SASL_MECHANISMS = AttributeDescription
108            .create(CoreSchema.getSupportedSASLMechanismsAttributeType());
109
110    private static final AttributeDescription ATTR_VENDOR_NAME = AttributeDescription
111            .create(CoreSchema.getVendorNameAttributeType());
112
113    private static final AttributeDescription ATTR_VENDOR_VERSION = AttributeDescription
114            .create(CoreSchema.getVendorNameAttributeType());
115
116    private static final AttributeDescription ATTR_FULL_VENDOR_VERSION = AttributeDescription
117            .create(CoreSchema.getFullVendorVersionAttributeType());
118
119    private static final SearchRequest SEARCH_REQUEST = Requests.newSearchRequest(DN.rootDN(),
120            SearchScope.BASE_OBJECT, Filter.objectClassPresent(), ATTR_ALT_SERVER.toString(),
121            ATTR_NAMING_CONTEXTS.toString(), ATTR_SUPPORTED_CONTROL.toString(),
122            ATTR_SUPPORTED_EXTENSION.toString(), ATTR_SUPPORTED_FEATURE.toString(),
123            ATTR_SUPPORTED_LDAP_VERSION.toString(), ATTR_SUPPORTED_SASL_MECHANISMS.toString(),
124            ATTR_FULL_VENDOR_VERSION.toString(),
125            ATTR_VENDOR_NAME.toString(), ATTR_VENDOR_VERSION.toString(),
126            ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES.toString(), ATTR_SUBSCHEMA_SUBENTRY.toString(),
127            "*");
128
129    /**
130     * Asynchronously reads the Root DSE from the Directory Server using the
131     * provided connection.
132     * <p>
133     * If the Root DSE is not returned by the Directory Server then the request
134     * will fail with an {@link EntryNotFoundException}. More specifically, the
135     * returned promise will never return {@code null}.
136     *
137     * @param connection
138     *            A connection to the Directory Server whose Root DSE is to be
139     *            read.
140     * @param handler
141     *            A result handler which can be used to asynchronously process
142     *            the operation result when it is received, may be {@code null}.
143     * @return A promise representing the result of the operation.
144     * @throws UnsupportedOperationException
145     *             If the connection does not support search operations.
146     * @throws IllegalStateException
147     *             If the connection has already been closed, i.e. if
148     *             {@code isClosed() == true}.
149     * @throws NullPointerException
150     *             If the {@code connection} was {@code null}.
151     */
152    public static LdapPromise<RootDSE> readRootDSEAsync(final Connection connection,
153            final LdapResultHandler<? super RootDSE> handler) {
154        return connection.searchSingleEntryAsync(SEARCH_REQUEST).then(
155            new Function<SearchResultEntry, RootDSE, LdapException>() {
156                @Override
157                public RootDSE apply(SearchResultEntry result) {
158                    return valueOf(result);
159                }
160            });
161    }
162
163    /**
164     * Reads the Root DSE from the Directory Server using the provided
165     * connection.
166     * <p>
167     * If the Root DSE is not returned by the Directory Server then the request
168     * will fail with an {@link EntryNotFoundException}. More specifically, this
169     * method will never return {@code null}.
170     *
171     * @param connection
172     *            A connection to the Directory Server whose Root DSE is to be
173     *            read.
174     * @return The Directory Server's Root DSE.
175     * @throws LdapException
176     *             If the result code indicates that the request failed for some
177     *             reason.
178     * @throws UnsupportedOperationException
179     *             If the connection does not support search operations.
180     * @throws IllegalStateException
181     *             If the connection has already been closed, i.e. if
182     *             {@code isClosed() == true}.
183     * @throws NullPointerException
184     *             If the {@code connection} was {@code null}.
185     */
186    public static RootDSE readRootDSE(final Connection connection) throws LdapException {
187        final Entry entry = connection.searchSingleEntry(SEARCH_REQUEST);
188        return valueOf(entry);
189    }
190
191    /**
192     * Creates a new Root DSE instance backed by the provided entry.
193     * Modifications made to {@code entry} will be reflected in the returned
194     * Root DSE. The returned Root DSE instance is unmodifiable and attempts to
195     * use modify any of the returned collections will result in a
196     * {@code UnsupportedOperationException}.
197     *
198     * @param entry
199     *            The Root DSE entry.
200     * @return A Root DSE instance backed by the provided entry.
201     * @throws NullPointerException
202     *             If {@code entry} was {@code null} .
203     */
204    public static RootDSE valueOf(Entry entry) {
205        Reject.ifNull(entry);
206        return new RootDSE(entry);
207    }
208
209    private final Entry entry;
210
211    /** Prevent direct instantiation. */
212    private RootDSE(final Entry entry) {
213        this.entry = entry;
214    }
215
216    /**
217     * Returns an unmodifiable list of URIs referring to alternative Directory
218     * Servers that may be contacted when the Directory Server becomes
219     * unavailable.
220     * <p>
221     * URIs for Directory Servers implementing the LDAP protocol are written
222     * according to RFC 4516. Other kinds of URIs may be provided.
223     * <p>
224     * If the Directory Server does not know of any other Directory Servers that
225     * could be used, the returned list will be empty.
226     *
227     * @return An unmodifiable list of URIs referring to alternative Directory
228     *         Servers, which may be empty.
229     * @see <a href="http://tools.ietf.org/html/rfc4516">RFC 4516 - Lightweight
230     *      Directory Access Protocol (LDAP): Uniform Resource Locator </a>
231     */
232    public Collection<String> getAlternativeServers() {
233        return getMultiValuedAttribute(ATTR_ALT_SERVER, Functions.byteStringToString());
234    }
235
236    /**
237     * Returns the entry which backs this Root DSE instance. Modifications made
238     * to the returned entry will be reflected in this Root DSE.
239     *
240     * @return The underlying Root DSE entry.
241     */
242    public Entry getEntry() {
243        return entry;
244    }
245
246    /**
247     * Returns an unmodifiable list of DNs identifying the context prefixes of
248     * the naming contexts that the Directory Server masters or shadows (in part
249     * or in whole).
250     * <p>
251     * If the Directory Server does not master or shadow any naming contexts,
252     * the returned list will be empty.
253     *
254     * @return An unmodifiable list of DNs identifying the context prefixes of
255     *         the naming contexts, which may be empty.
256     */
257    public Collection<DN> getNamingContexts() {
258        return getMultiValuedAttribute(ATTR_NAMING_CONTEXTS, Functions.byteStringToDN());
259    }
260
261    /**
262     * Returns a string which represents the DN of the subschema subentry
263     * holding the schema controlling the Root DSE.
264     * <p>
265     * Clients SHOULD NOT assume that this subschema (sub)entry controls other
266     * entries held by the Directory Server.
267     *
268     * @return The DN of the subschema subentry holding the schema controlling
269     *         the Root DSE, or {@code null} if the DN is not provided.
270     */
271    public DN getSubschemaSubentry() {
272        return getSingleValuedAttribute(ATTR_SUBSCHEMA_SUBENTRY, Functions.byteStringToDN());
273    }
274
275    /**
276     * Returns an unmodifiable list of supported authentication password schemes
277     * which the Directory Server supports.
278     * <p>
279     * If the Directory Server does not support any authentication password
280     * schemes, the returned list will be empty.
281     *
282     * @return An unmodifiable list of supported authentication password
283     *         schemes, which may be empty.
284     * @see <a href="http://tools.ietf.org/html/rfc3112">RFC 3112 - LDAP
285     *      Authentication Password Schema </a>
286     */
287    public Collection<String> getSupportedAuthenticationPasswordSchemes() {
288        return getMultiValuedAttribute(ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES, Functions
289                .byteStringToString());
290    }
291
292    /**
293     * Returns an unmodifiable list of object identifiers identifying the
294     * request controls that the Directory Server supports.
295     * <p>
296     * If the Directory Server does not support any request controls, the
297     * returned list will be empty. Object identifiers identifying response
298     * controls may not be listed.
299     *
300     * @return An unmodifiable list of object identifiers identifying the
301     *         request controls, which may be empty.
302     */
303    public Collection<String> getSupportedControls() {
304        return getMultiValuedAttribute(ATTR_SUPPORTED_CONTROL, Functions.byteStringToString());
305    }
306
307    /**
308     * Returns an unmodifiable list of object identifiers identifying the
309     * extended operations that the Directory Server supports.
310     * <p>
311     * If the Directory Server does not support any extended operations, the
312     * returned list will be empty.
313     * <p>
314     * An extended operation generally consists of an extended request and an
315     * extended response but may also include other protocol data units (such as
316     * intermediate responses). The object identifier assigned to the extended
317     * request is used to identify the extended operation. Other object
318     * identifiers used in the extended operation may not be listed as values of
319     * this attribute.
320     *
321     * @return An unmodifiable list of object identifiers identifying the
322     *         extended operations, which may be empty.
323     */
324    public Collection<String> getSupportedExtendedOperations() {
325        return getMultiValuedAttribute(ATTR_SUPPORTED_EXTENSION, Functions.byteStringToString());
326    }
327
328    /**
329     * Returns an unmodifiable list of object identifiers identifying elective
330     * features that the Directory Server supports.
331     * <p>
332     * If the server does not support any discoverable elective features, the
333     * returned list will be empty.
334     *
335     * @return An unmodifiable list of object identifiers identifying the
336     *         elective features, which may be empty.
337     */
338    public Collection<String> getSupportedFeatures() {
339        return getMultiValuedAttribute(ATTR_SUPPORTED_FEATURE, Functions.byteStringToString());
340    }
341
342    /**
343     * Returns an unmodifiable list of the versions of LDAP that the Directory
344     * Server supports.
345     *
346     * @return An unmodifiable list of the versions.
347     */
348    public Collection<Integer> getSupportedLDAPVersions() {
349        return getMultiValuedAttribute(ATTR_SUPPORTED_LDAP_VERSION, Functions.byteStringToInteger());
350    }
351
352    /**
353     * Returns an unmodifiable list of the SASL mechanisms that the Directory
354     * Server recognizes and/or supports.
355     * <p>
356     * The contents of the returned list may depend on the current session state
357     * and may be empty if the Directory Server does not support any SASL
358     * mechanisms.
359     *
360     * @return An unmodifiable list of the SASL mechanisms, which may be empty.
361     * @see <a href="http://tools.ietf.org/html/rfc4513">RFC 4513 - Lightweight
362     *      Directory Access Protocol (LDAP): Authentication Methods and
363     *      Security Mechanisms </a>
364     * @see <a href="http://tools.ietf.org/html/rfc4422">RFC 4422 - Simple
365     *      Authentication and Security Layer (SASL) </a>
366     */
367    public Collection<String> getSupportedSASLMechanisms() {
368        return getMultiValuedAttribute(ATTR_SUPPORTED_SASL_MECHANISMS, Functions
369                .byteStringToString());
370    }
371
372    /**
373     * Returns a string which represents the name of the Directory Server
374     * implementer.
375     *
376     * @return The name of the Directory Server implementer, or {@code null} if
377     *         the vendor name is not provided.
378     * @see <a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing
379     *      Vendor Information in the LDAP Root DSE </a>
380     */
381    public String getVendorName() {
382        return getSingleValuedAttribute(ATTR_VENDOR_NAME, Functions.byteStringToString());
383    }
384
385    /**
386     * Returns a string which represents the version of the Directory Server
387     * implementation.
388     * <p>
389     * Note that this value is typically a release value comprised of a string
390     * and/or a string of numbers used by the developer of the LDAP server
391     * product. The returned string will be unique between two versions of the
392     * Directory Server, but there are no other syntactic restrictions on the
393     * value or the way it is formatted.
394     *
395     * @return The version of the Directory Server implementation, or
396     *         {@code null} if the vendor version is not provided.
397     * @see <a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing
398     *      Vendor Information in the LDAP Root DSE </a>
399     */
400    public String getVendorVersion() {
401        return getSingleValuedAttribute(ATTR_VENDOR_VERSION, Functions.byteStringToString());
402    }
403
404    /**
405     * Returns a string which represents the full version of the Directory Server
406     * implementation.
407     *
408     * @return The full version of the Directory Server implementation, or
409     *         {@code null} if the vendor version is not provided.
410     */
411    public String getFullVendorVersion() {
412        return getSingleValuedAttribute(ATTR_FULL_VENDOR_VERSION, Functions.byteStringToString());
413    }
414
415    private <N> Collection<N> getMultiValuedAttribute(
416            final AttributeDescription attributeDescription,
417        final Function<ByteString, N, NeverThrowsException> function) {
418        // The returned collection is unmodifiable because we may need to
419        // return an empty collection if the attribute does not exist in the
420        // underlying entry. If a value is then added to the returned empty
421        // collection it would require that an attribute is created in the
422        // underlying entry in order to maintain consistency.
423        final Attribute attr = entry.getAttribute(attributeDescription);
424        if (attr != null) {
425            return Collections.unmodifiableCollection(Collections2.transformedCollection(attr,
426                    function, Functions.objectToByteString()));
427        } else {
428            return Collections.emptySet();
429        }
430    }
431
432    private <N> N getSingleValuedAttribute(final AttributeDescription attributeDescription,
433        final Function<ByteString, N, NeverThrowsException> function) {
434        final Attribute attr = entry.getAttribute(attributeDescription);
435        if (attr == null || attr.isEmpty()) {
436            return null;
437        } else {
438            return function.apply(attr.firstValue());
439        }
440    }
441
442}