Directory Services 7.4.3

Collective attributes

Collective attributes provide a standard mechanism for inheriting attribute values. DS servers support standard collective attributes, described in RFC 3671. The inherited values appear on all the entries in a subtree, optionally filtered by object class. Standard collective attribute type names have the prefix c-.

DS servers extend collective attributes to make them easier to use. You can define any DS attribute as collective with the ;collective attribute option. Use LDAP filters in the subtree specification for fine-grained control over which entries inherit the values.

You establish an inheritance relationship between entries by specifying one of the following:

  • Which attribute holds the DN of the entry to inherit attributes from.

  • How to construct the RDN of the entry to inherit attributes from.

Class of service

This example defines attributes that depend on the user’s service level.

This example shows collective attributes that depend on a classOfService attribute value:

  • For entries with classOfService: bronze, mailQuota is 1 GB, and diskQuota is 10 GB.

  • For entries with classOfService: silver, mailQuota is 5 GB, and diskQuota is 50 GB.

  • For entries with classOfService: gold, mailQuota is 10 GB, anddiskQuota is 100 GB.

You define collective attributes in the user data using an LDAP subentry. As a result, collective attribute settings are replicated. Collective attributes use attributes defined in the directory schema. The following LDIF shows the schema definitions:

dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( example-class-of-service-attribute-type
NAME 'classOfService'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'DS Documentation Examples' )
-
add: attributeTypes
attributeTypes: ( example-class-of-service-disk-quota
NAME 'diskQuota'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'DS Documentation Examples' )
-
add: attributeTypes
attributeTypes: ( example-class-of-service-mail-quota
NAME 'mailQuota'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'DS Documentation Examples' )
-
add: objectClasses
objectClasses: ( example-class-of-service-object-class
NAME 'cos'
SUP top
AUXILIARY
MAY ( classOfService $ diskQuota $ mailQuota )
X-ORIGIN 'DS Documentation Examples' )

The collective attribute definitions set the quotas depending on class of service:

dn: cn=Bronze Class of Service,dc=example,dc=com
objectClass: collectiveAttributeSubentry
objectClass: extensibleObject
objectClass: subentry
objectClass: top
cn: Bronze Class of Service
diskQuota;collective: 10 GB
mailQuota;collective: 1 GB
subtreeSpecification: { base "ou=People", specificationFilter "(classOfService=bronze)" }

dn: cn=Silver Class of Service,dc=example,dc=com
objectClass: collectiveAttributeSubentry
objectClass: extensibleObject
objectClass: subentry
objectClass: top
cn: Silver Class of Service
diskQuota;collective: 50 GB
mailQuota;collective: 5 GB
subtreeSpecification: { base "ou=People", specificationFilter "(classOfService=silver)" }

dn: cn=Gold Class of Service,dc=example,dc=com
objectClass: collectiveAttributeSubentry
objectClass: extensibleObject
objectClass: subentry
objectClass: top
cn: Gold Class of Service
diskQuota;collective: 100 GB
mailQuota;collective: 10 GB
subtreeSpecification: { base "ou=People", specificationFilter "(classOfService=gold)" }

When the collective attributes are defined, you observe the results on user entries. Before trying this example, set up a directory server with the ds-evaluation profile:

$ 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 \
 "(uid=bjensen)" \
 classOfService mailQuota diskQuota

dn: uid=bjensen,ou=People,dc=example,dc=com
classOfService: bronze
mailQuota: 1 GB
diskQuota: 10 GB

$ 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 \
 "(uid=kvaughan)" \
 classOfService mailQuota diskQuota

dn: uid=kvaughan,ou=People,dc=example,dc=com
classOfService: silver
mailQuota: 5 GB
diskQuota: 50 GB

$ 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 \
 "(uid=scarter)" \
 classOfService mailQuota diskQuota

dn: uid=scarter,ou=People,dc=example,dc=com
classOfService: gold
mailQuota: 10 GB
diskQuota: 100 GB

Inherit from the manager

This example demonstrates how to set an employee’s department number using the manager’s department number.

The employee-manager relationship is defined by the employee’s manager attribute. Each manager attribute specifies the DN of a manager’s entry. The server looks up the manager’s department number to set the attribute on the employee’s entry.

The collective attribute subentry specifies that users inherit department number from their manager:

dn: cn=Inherit Department Number From Manager,dc=example,dc=com
objectClass: top
objectClass: subentry
objectClass: inheritedCollectiveAttributeSubentry
objectClass: inheritedFromDNCollectiveAttributeSubentry
cn: Inherit Department Number From Manager
subtreeSpecification: { base "ou=People", specificationFilter "(objectClass=posixAccount)" }
inheritFromDNAttribute: manager
inheritAttribute: departmentNumber

Babs Jensen’s manager is Torrey Rigden:

dn: uid=bjensen,ou=People,dc=example,dc=com
manager: uid=trigden, ou=People, dc=example,dc=com

Torrey’s department number is 3001:

dn: uid=trigden,ou=People,dc=example,dc=com
departmentNumber: 3001

Babs inherits her department number from Torrey. Before trying this example, set up a directory server with the ds-evaluation profile:

$ 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 \
 "(uid=bjensen)" \
 departmentNumber

dn: uid=bjensen,ou=People,dc=example,dc=com
departmentNumber: 3001

Inherit from the locality

This example demonstrates how to set a user’s default language preferences and street address based on locality.

For this example, the relationship between entries is based on locality. The collective attribute subentry specifies how to construct the RDN of the entry with the values to inherit:

dn: cn=Inherit From Locality,dc=example,dc=com
objectClass: top
objectClass: subentry
objectClass: inheritedCollectiveAttributeSubentry
objectClass: inheritedFromRDNCollectiveAttributeSubentry
cn: Inherit From Locality
subtreeSpecification: { base "ou=People", specificationFilter "(objectClass=posixAccount)" }
inheritFromBaseRDN: ou=Locations
inheritFromRDNAttribute: l
inheritFromRDNType: l
inheritAttribute: preferredLanguage
inheritAttribute: street
collectiveConflictBehavior: real-overrides-virtual

The RDN of the entry from which to inherit attributes is l=localityName,ou=Locations, where localityName is the value of the l (localityName) attribute on the user’s entry.

In other words, if the user’s entry has l: Bristol, then the RDN of the entry from which to inherit attributes starts with l=Bristol,ou=Locations. The actual entry looks like this:

dn: l=Bristol,ou=Locations,dc=example,dc=com
objectClass: top
objectClass: locality
objectClass: extensibleObject
l: Bristol
street: Broad Quay House, Prince Street
preferredLanguage: en-gb

The subentry specifies that the inherited attributes are preferred language and street address.

The object class extensibleObject allows the entry to take a preferred language. The object class extensibleObject means, "Let me add whatever attributes I want." The best practice is to add a custom auxiliary object class instead. The example uses shortcut extensibleObject for simplicity.

Notice the last line of the collective attribute subentry:

collectiveConflictBehavior: real-overrides-virtual

This line indicates that when a collective attribute clashes with a real attribute, the real value takes precedence over the virtual, collective value. You can set collectiveConflictBehavior to virtual-overrides-real for the opposite precedence, or to merge-real-and-virtual to keep both sets of values.

In this example, users can set their own language preferences. When users set language preferences manually, the user’s settings override the locality-based setting.

Sam Carter is located in Bristol. Sam has specified no preferred languages:

dn: uid=scarter,ou=People,dc=example,dc=com
l: Bristol

Sam inherits the street address and preferred language from the Bristol locality. Before trying this example, set up a directory server with the ds-evaluation profile:

$ 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 \
 "(uid=scarter)" \
 preferredLanguage street

dn: uid=scarter,ou=People,dc=example,dc=com
preferredLanguage: en-gb
street: Broad Quay House, Prince Street

Babs’s locality is San Francisco. Babs prefers English, but also knows Korean:

dn: uid=bjensen,ou=People,dc=example,dc=com
preferredLanguage: en, ko;q=0.8
l: San Francisco

Babs inherits the street address from the San Francisco locality, but keeps her language preferences:

$ 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 \
 "(uid=bjensen)" \
 preferredLanguage street

dn: uid=bjensen,ou=People,dc=example,dc=com
preferredLanguage: en, ko;q=0.8
street: 201 Mission Street Suite 2900

Inherit from a parent entry

This example demonstrates how to inherit a description from the parent entry.

The following collective attribute subentry specifies that entries inherit a description from their parent unless they already have a description:

dn: cn=Inherit Description From Parent,dc=example,dc=com
objectClass: top
objectClass: subentry
objectClass: inheritedCollectiveAttributeSubentry
objectClass: inheritedFromDNCollectiveAttributeSubentry
cn: Inherit Description From Parent
subtreeSpecification: { base "", minimum 2, specificationFilter "(objectClass=posixAccount)" }
inheritFromDNAttribute: entryDN
inheritFromDNParent: 1
inheritAttribute: description
collectiveConflictBehavior: real-overrides-virtual

In this example, inheritFromDNAttribute uses the virtual attribute entryDN. The setting inheritFromDNParent: 1 indicates that the server should locate the parent entry by removing one leading RDN from the entryDN. (To inherit from the grandparent entry, you would specify inheritFromDNParent: 2, for example.)

Sam Carter’s entry has no description attribute, and so inherits the parent’s description:

$ 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 \
 "(uid=scarter)" \
 description

dn: uid=scarter,ou=People,dc=example,dc=com
description: Description on ou=People
description: Served by my favorite directory server

Babs Jensen’s entry already has a description attribute, and so does not inherit from the parent:

$ 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 \
 "(uid=bjensen)" \
 description

dn: uid=bjensen,ou=People,dc=example,dc=com
description: Original description

Rename an attribute

You can rename a collective attribute with a special form of inheritAttribute value. In the collective attribute subentry, set:

inheritAttribute: <collective-attribute>:<inherited-attribute>

The following example inherits the postalAddress from the manager with the RFC 3671 name, c-PostalAddress:

dn: cn=Inherit postal address from manager,dc=example,dc=com
objectClass: top
objectClass: subentry
objectClass: inheritedCollectiveAttributeSubentry
objectClass: inheritedFromDNCollectiveAttributeSubentry
cn: Inherit postal address from manager
subtreeSpecification: { base "ou=People", specificationFilter "(objectClass=posixAccount)" }
inheritFromDNAttribute: manager
inheritAttribute: postalAddress:c-PostalAddress

The <collective-attribute> must be defined in the LDAP schema.

If the client uses the <inherited-attribute to filter or sort, it must also be defined in the schema.

In this example, the default LDAP schema defines both attributes.

Try the example with the following steps:

  1. Add the subentry:

    $ ldapmodify \
     --hostname localhost \
     --port 1636 \
     --useSsl \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --bindDN uid=admin \
     --bindPassword password << EOF
    dn: cn=Inherit postal address from manager,dc=example,dc=com
    objectClass: top
    objectClass: subentry
    objectClass: inheritedCollectiveAttributeSubentry
    objectClass: inheritedFromDNCollectiveAttributeSubentry
    cn: Inherit postal address from manager
    subtreeSpecification: { base "ou=People", specificationFilter "(objectClass=posixAccount)" }
    inheritFromDNAttribute: manager
    inheritAttribute: postalAddress:c-PostalAddress
    EOF
  2. Add a postalAddress to the manager’s entry:

    $ ldapmodify \
     --hostname localhost \
     --port 1636 \
     --useSsl \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --bindDN uid=admin \
     --bindPassword password << EOF
    dn: uid=trigden,ou=people,dc=example,dc=com
    changetype: modify
    add: postalAddress
    postalAddress: 1234 Main St.$Anytown, CA 12345$USA
    EOF
  3. Read c-PostalAddress on the employee’s entry:

    $ 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 \
     "(uid=bjensen)" \
     c-PostalAddress
    dn: uid=bjensen,ou=People,dc=example,dc=com
    c-PostalAddress: 1234 Main St., CA 12345

About subentry scope

LDAP subentries reside with the user data and so the server replicates them. Subentries hold operational data. They are not visible in search results unless explicitly requested. This section describes how a subentry’s subtreeSpecification attribute defines the scope of the subtree that the subentry applies to.

An LDAP subentry’s subtree specification identifies a subset of entries in a branch of the DIT. The subentry scope is these entries. In other words, these are the entries that the subentry affects.

The attribute value for a subtreeSpecification optionally includes the following parameters:

base

Indicates the entry, relative to the subentry’s parent, at the base of the subtree.

By default, the base is the subentry’s parent.

specificationFilter

Indicates an LDAP filter. Entries matching the filter are in scope.

DS servers extend the standard implementation to allow any search filter, not just an assertion about the objectClass attribute.

By default, all entries under the base entry are in scope.

The following illustration shows this for an example collective attribute subentry:

A subentry’s subtreeSpecification defines the scope of the subtree that the subentry applies to.

Notice that the base of ou=People on the subentry cn=Silver Class of Service,dc=example,dc=com indicates that the base entry is ou=People,dc=example,dc=com.

The filter "(classOfService=silver)" means that Kirsten Vaughan and Sam Carter’s entries are in scope. Babs Jensen’s entry, with classOfService: bronze does not match and is therefore not in scope. The ou=People organizational unit entry does not have a classOfService attribute, and so is not in scope, either.