LDAP Schema

LDAP services are based on X.500 Directory Services, which are telecommunications standards. In telecommunications, interoperability is paramount. Competitors must cooperate to the extent that they use each others' systems. For directory services, the protocols for exchanging data and the descriptions of the data are standardized. LDAP defines schema that describe what attributes a given LDAP entry must have and may optionally have, and what attribute values can contain and how they can be matched. Formal schema definitions protect interoperability when many applications read and write to the same directory service. Directory data are much easier to share when you understand how to use LDAP schema.

LDAP Schema covers LDAP schema from the server administrator’s perspective. Administrators can update LDAP directory schema. DS servers support a large number of standard schema definitions by default. Administrators can also adjust how strictly each DS server applies schema definitions. For the list of standard definitions that DS servers provide, see Standard Schema.

As a script developer, you use the available schema, and accept the server’s application of schema when updating directory entries.

Read Schema

Directory servers publish information about services they provide as operational attributes of the root DSE. The root DSE is the entry with an empty string DN, "". DSE is an acronym for DSA-Specific Entry. DSA is an acronym for Directory System Agent. The DSE differs by server, but is generally nearly identical for replicas.

DS servers publish the DN of the entry holding schema definitions as the value of the attribute subschemaSubentry:

Find LDAP Schema

Look up the schema DN:

$ ldapsearch \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery \
 --baseDN dc=example,dc=com \
 --searchScope base \
 "(&)" \
 subschemaSubentry

dn: dc=example,dc=com
subschemaSubentry: cn=schema

By default, the DN for the schema entry is cn=schema.

The schema entry has the following attributes whose values are schema definitions:

attributeTypes

Attribute type definitions describe attributes of directory entries, such as givenName or mail.

Consider the following features of LDAP attributes:

  • Attributes can have multiple names.

    Many common attributes take advantage of this feature. For example, cn and commonName both refer to the same attribute type. The same is true of dc and domainComponent, l and localityName, and others.

  • The definition specifies the attribute’s syntax and the matching rules for indexing the attribute and searching its values for matches.

    The index for a telephone number is not the same as the index for a digital certificate. By default, you must take the attribute’s syntax into account when adding or updating its values.

  • LDAP attributes can have multiple values by default.

    Think of the values as a set, rather than an array. LDAP does not require directory servers to order the values in any particular way, and it does not allow duplicates.

    The definition must label the attribute type as SINGLE-VALUE to change this, even for boolean attributes. Keep this in mind when defining your own attributes.

  • Some attributes are intended for use by the directory server, rather than external applications.

    This is the case for example when you see NO-USER-MODIFICATION in the definition. These definitions also set USAGE to an operational attribute type: directoryOperation, distributedOperation, or dSAOperation.

objectClasses

Object class definitions identify the attribute types that an entry must have, and may have. Examples of object classes include person and organizationalUnit. Object classes inherit from other object classes. For example, inetOrgPerson inherits from person.

Object classes are specified as values of an entry’s objectClass attribute.

An object class can be one of the following:

  • Structural object classes define the core structure of the entry, generally representing a real-world object.

    By default, DS directory entries have a single structural object class or at least a single line of structural object class inheritance.

    The person object class is structural, for example.

  • Auxiliary object classes define additional characteristics of entries.

    The posixAccount object class is auxiliary, for example.

  • Abstract object classes define base characteristics for other object classes to inherit, and cannot themselves inherit from other object classes.

    The top object class from which others inherit is abstract, for example.

ldapSyntaxes

An attribute syntax constrains what directory clients can store as attribute values.

matchingRules

A Matching rule determines how the directory server compares attribute values to assertion values for LDAP search and LDAP compare operations.

For example, in a search having the filter (uid=bjensen) the assertion value is bjensen.

nameForms

A name form specifies which attribute can be used as the relative DN (RDN) for a structural object class.

dITStructureRules

A DIT structure rule defines a relationship between directory entries by identifying the name form allowed for subordinate entries of a given superior entry.

Object Class Schema

The schema entry in a server is large because it contains all of the schema definitions. Filter the results when reading a specific schema definition.

The example below reads the definition for the person object class:

$ grep \'person\' <(ldapsearch \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery \
 --baseDN "cn=schema" \
 --searchScope base \
 "(objectClass=subschema)" \
 objectClasses)

objectClasses: ( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) X-ORIGIN 'RFC 4519' X-SCHEMA-FILE '00-core.ldif' )

Notice the use of the object class name in grep \'person\' to filter search results.

The object class defines which attributes an entry of that object class must have, and which attributes the entry may optionally have. A person entry must have a cn and an sn attribute. A person entry may optionally have userPassword, telephoneNumber, seeAlso, and description attributes.

To determine definitions of those attributes, read the LDAP schema:

Attribute Schema

The following example shows you how to read the schema definition for the cn attribute:

$ grep \'cn\' <(ldapsearch \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery \
 --baseDN "cn=schema" \
 --searchScope base \
 "(objectClass=subschema)" \
 attributeTypes)

attributeTypes: ( 2.5.4.3 NAME ( 'cn' 'commonName' ) SUP name X-ORIGIN 'RFC 4519' X-SCHEMA-FILE '00-core.ldif' )

The cn attribute inherits its definition from the name attribute. That attribute definition indicates attribute syntax and matching rules as shown in the following example:

$ grep \'name\' <(ldapsearch \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery \
 --baseDN "cn=schema" \
 --searchScope base \
 "(objectClass=subschema)" \
 attributeTypes)

attributeTypes: ( 2.5.4.41 NAME 'name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' X-SCHEMA-FILE '00-core.ldif' )

This means that the server ignores case when matching a common name value. Use the OID to read the syntax as shown in the following example:

$ grep 1.3.6.1.4.1.1466.115.121.1.15 <(ldapsearch \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery \
 --baseDN "cn=schema" \
 --searchScope base \
 "(objectClass=subschema)" \
 ldapSyntaxes)

ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' X-ORIGIN 'RFC 4517' )

Taken together with the information for the name attribute, the common name attribute value is a Directory String of at most 32,768 characters. For details about syntaxes, read RFC 4517, Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching Rules. That document describes a Directory String as one or more UTF-8 characters.

Schema Errors

For the sake of interoperability and to avoid polluting directory data, scripts and applications should respect LDAP schema. In the simplest case, scripts and applications can use the schemas already defined.

DS servers do accept updates to schema definitions over LDAP while the server is running. This means that when a new application calls for attributes that are not yet defined by existing directory schemas, the directory administrator can easily add them, as described in Update LDAP Schema, as long as the new definitions do not conflict with existing definitions.

General purpose applications handle many different types of data. Such applications must manage schema compliance at run time. Software development kits provide mechanisms for reading schema definitions at run time, and checking whether entry data is valid according to the schema definitions.

Many scripts do not require run time schema checking. When schema checking is not required, it is sufficient to check schema-related LDAP result codes when writing to the directory:

LDAP result code: 17 (Undefined attribute type)

The requested operation failed because it referenced an attribute that is not defined in the server schema.

LDAP result code: 18 (Inappropriate matching)

The requested operation failed because it attempted to perform an inappropriate type of matching against an attribute.

LDAP result code: 20 (Attribute or value exists)

The requested operation failed because it would have resulted in a conflict with an existing attribute or attribute value in the target entry.

For example, the request tried to add a second value to a single-valued attribute.

LDAP result code: 21 (Invalid attribute syntax)

The requested operation failed because it violated the syntax for a specified attribute.

LDAP result code: 34 (Invalid DN syntax)

The requested operation failed because it would have resulted in an entry with an invalid or malformed DN.

LDAP result code: 64 (Naming violation)

The requested operation failed because it would have violated the server’s naming configuration.

For example, the request did not respect a name form definition.

LDAP result code: 65 (Object class violation)

The requested operation failed because it would have resulted in an entry that violated the server schema.

For example, the request tried to remove a required attribute, or tried to add an attribute that is not allowed.

LDAP result code: 69 (Object class mods prohibited)

The requested operation failed because it would have modified the object classes associated with an entry in an illegal manner.

When you encounter an error, take the time to read the additional information. The additional information from a server is often sufficient to allow you to resolve the problem directly.

Object Class Violations and Invalid Attribute Syntax show some common problems that can result from schema violations.

Object Class Violations

A number of schema violations show up as object class violations. The following request fails to add an undefined attribute:

$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery << EOF
dn: uid=bjensen,ou=People,dc=example,dc=com
changetype: modify
add: undefined
undefined: This attribute is not defined.
EOF

# The LDAP modify request failed: 65 (Object Class Violation)
# Additional Information:  Entry uid=bjensen,ou=People,dc=example,dc=com cannot be modified because the resulting entry would have violated the server schema: Entry "uid=bjensen,ou=People,dc=example,dc=com" violates the schema because it contains attribute "undefined" which is not allowed by any of the object classes in the entry

The solution is to define the undefined attribute, and to ensure that it is allowed by one of the object classes defined for the entry.

The following request fails to add a second structural object class:

$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery << EOF
dn: uid=bjensen,ou=People,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: organizationalUnit
EOF

# The LDAP modify request failed: 65 (Object Class Violation)
# Additional Information:  Entry uid=bjensen,ou=People,dc=example,dc=com cannot be modified because the resulting entry would have violated the server schema: Entry "uid=bjensen,ou=People,dc=example,dc=com" violates the schema because it contains multiple conflicting structural object classes "inetOrgPerson" and "organizationalUnit". Only a single structural object class is allowed in an entry

The solution in this case is to define only one structural object class for the entry. Either Babs Jensen is a person or an organizational unit, but not both.

Invalid Attribute Syntax

The following request fails to add an empty string as a common name attribute value:

$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery << EOF
dn: uid=bjensen,ou=People,dc=example,dc=com
changetype: modify
add: cn
cn:
EOF

# The LDAP modify request failed: 21 (Invalid Attribute Syntax)
# Additional Information:  When attempting to modify entry uid=bjensen,ou=People,dc=example,dc=com to add one or more values for attribute cn, value "" was found to be invalid according to the associated syntax: The operation attempted to assign a zero-length value to an attribute with the directory string syntax

As mentioned in Attribute Schema, a Directory String has one or more UTF-8 characters.

Workarounds

Follow the suggestions in Schema Errors as much as possible. In particular follow these rules of thumb:

  • Test with a private DS server to resolve schema issues before going live.

  • Adapt your scripts and applications to avoid violating schema definitions.

  • When existing schemas are not sufficient, request schema updates to add definitions that do not conflict with any already in use.

When it is not possible to respect the schema definitions, you can sometimes work around LDAP schema constraints without changing the server configuration. The schema defines an extensibleObject object class. The extensibleObject object class is auxiliary. It effectively allows entries to hold any user attribute, even attributes that are not defined in the schema.

ExtensibleObject

The following example adds one attribute that is undefined and another that is not allowed:

$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery << EOF
dn: uid=bjensen,ou=People,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: extensibleObject
-
add: undefined
undefined: This attribute is not defined in the LDAP schema.
-
add: serialNumber
serialNumber: This attribute is not allowed according to the object classes.
EOF

# MODIFY operation successful for DN uid=bjensen,ou=People,dc=example,dc=com

Use of the extensibleObject object class can be abused and can prevent interoperability. Restrict its use to cases where no better alternative is available.