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