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