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}