Notification of Changes

Applications that need change notification can use a persistent search or read the external change log.

Defined in the Internet-Draft, Persistent Search: A Simple LDAP Change Notification Mechanism, a persistent search is like a regular search that never returns. Every time a change happens in the scope of the search, the server returns an additional response:

  1. Grant access to perform a persistent search, by adding an ACI to use the persistent search control.

    Persistent searches consume server resources, so servers do not allow them by default. If an application does not have access, the request fails with an unavailable critical extension error:

    The LDAP search request failed: 12 (Unavailable Critical Extension)
    Additional Information:  The request control with Object Identifier (OID) "2.16.840.1.113730.3.4.3"
    cannot be used due to insufficient access rights

    The following command grants access under dc=example,dc=com to My App:

    $ 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: dc=example,dc=com
    changetype: modify
    add: aci
    aci: (targetcontrol = "PSearch")
     (version 3.0;acl "Allow Persistent Search for My App";
     allow (read)(userdn = "ldap:///cn=My App,ou=Apps,dc=example,dc=com");)
    EOF
  2. Start the persistent search.

    The following example initiates a persistent search, where notifications are sent for all update operations, only notifications about changed entries are returned, and no additional information are returned:

    $ ldapsearch \
     --hostname localhost \
     --port 1636 \
     --useSsl \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --bindDN 'cn=My App,ou=Apps,dc=example,dc=com' \
     --bindPassword password \
     --baseDN dc=example,dc=com \
     --persistentSearch ps:all:true:false \
     '(&)' >> /tmp/psearch.txt &
    
    $ export PSEARCH_PID=$!

    Notice the search filter, (&), which is always true, meaning that it matches all entries. For details on settings for a persistent search, see the --persistentSearch option in ldapsearch Options.

  3. Make changes that impact the persistent search results.

    Show commands

    To prepare to modify an entry, save the following LDIF in a file named description.ldif:

    dn: uid=bjensen,ou=People,dc=example,dc=com
    changetype: modify
    replace: description
    description: Hello, persistent search

    The following commands perform a modify operation, and a delete operation:

    $ 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
    replace: description
    description: Hello, persistent search
    EOF
    
    $ ldapdelete \
     --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 \
     uid=tpierce,ou=People,dc=example,dc=com
    Show persistent search results

    The result is the following responses to the persistent search:

    dn: uid=bjensen,ou=People,dc=example,dc=com
    objectClass: posixAccount
    objectClass: top
    objectClass: organizationalPerson
    objectClass: person
    objectClass: inetOrgPerson
    mail: bjensen@example.com
    roomNumber: 0209
    preferredLanguage: en, ko;q=0.8
    manager: uid=trigden, ou=People, dc=example,dc=com
    ou: Product Development
    ou: People
    givenName: Barbara
    telephoneNumber: +1 408 555 1862
    sn: Jensen
    cn: Barbara Jensen
    cn: Babs Jensen
    homeDirectory: /home/bjensen
    facsimileTelephoneNumber: +1 408 555 1992
    gidNumber: 1000
    userPassword: {PBKDF2-HMAC-SHA256}10000:<hash>
    uidNumber: 1076
    description: Hello, persistent search
    uid: bjensen
    l: San Francisco
    
    dn: uid=tpierce,ou=People,dc=example,dc=com
    objectClass: top
    objectClass: inetOrgPerson
    objectClass: posixAccount
    objectClass: organizationalPerson
    objectClass: person
    mail: tpierce@example.com
    roomNumber: 1383
    manager: uid=scarter, ou=People, dc=example,dc=com
    ou: Accounting
    ou: People
    givenName: Tobias
    telephoneNumber: +1 408 555 1531
    sn: Pierce
    cn: Tobias Pierce
    homeDirectory: /home/tpierce
    facsimileTelephoneNumber: +1 408 555 9332
    gidNumber: 1000
    userPassword: {PBKDF2-HMAC-SHA256}10000:<hash>
    uidNumber: 1042
    uid: tpierce
    l: Bristol
    departmentNumber: 1000
    preferredLanguage: en-gb
    street: Broad Quay House, Prince Street

    If the data is replicated, the results include the entry dc=example,dc=com. Replication updates the ds-sync-* operational attributes on dc=example,dc=com, and those changes appear in the results because the entry is in the scope of the persistent search.

  4. Terminate the persistent search.

    Interrupt the command with CTRL+C (SIGINT) or SIGTERM:

    $ kill -s SIGTERM $PSEARCH_PID

Use the External Change Log

You read the external change log over LDAP. When you poll the change log, you can get the list of updates that happened since your last request.

The external change log mechanism uses an LDAP control with OID 1.3.6.1.4.1.26027.1.5.4. This control allows the client application to bookmark the last changes seen. The control returns a cookie that the application sends to the server to read the next batch of changes.

These steps show the client binding as uid=admin to read the change log. Other accounts require sufficient access and privileges to read the change log. For instructions, see Let a User Read the Changelog:

  1. Send an initial search request using the LDAP control with no cookie value.

    In this example, two changes appear in the changelog:

    $ ldapsearch \
     --hostname localhost \
     --port 1636 \
     --useSsl \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --bindDN uid=admin \
     --bindPassword password \
     --baseDN cn=changelog \
     --control "ecl:false" \
     "(&)" \
     changes changeLogCookie targetDN
    
    # Public changelog exchange control(1.3.6.1.4.1.26027.1.5.4): <COOKIE1>
    dn: replicationCSN=<CSN1>,dc=example,dc=com,cn=changelog
    changes:: cmVwbGFjZTogZGVzY3JpcHRpb24KZGVzY3JpcHRpb246IE5ldyBkZXNjcmlwdGlvbgotCnJlcGxhY2U6IG1vZGlmaWVyc05hbWUKbW9kaWZpZXJzTmFtZTogdWlkPWJqZW5zZW4sb3U9UGVvcGxlLGRjPWV4YW1wbGUsZGM9Y29tCi0KcmVwbGFjZTogbW9kaWZ5VGltZXN0YW1wCm1vZGlmeVRpbWVzdGFtcDogMjAxNjEwMTQxNTA5MTJaCi0K
    targetDN: uid=bjensen,ou=People,dc=example,dc=com
    changeLogCookie: <COOKIE1>
    
    # Public changelog exchange control(1.3.6.1.4.1.26027.1.5.4): <COOKIE2>
    dn: replicationCSN=<CSN2>,dc=example,dc=com,cn=changelog
    changes:: cmVwbGFjZTogZGVzY3JpcHRpb24KZGVzY3JpcHRpb246IE5ldywgaW1wcm92ZWQgZGVzY3JpcHRpb24KLQpyZXBsYWNlOiBtb2RpZmllcnNOYW1lCm1vZGlmaWVyc05hbWU6IHVpZD1iamVuc2VuLG91PVBlb3BsZSxkYz1leGFtcGxlLGRjPWNvbQotCnJlcGxhY2U6IG1vZGlmeVRpbWVzdGFtcAptb2RpZnlUaW1lc3RhbXA6IDIwMTYxMDE0MTUwOTE5WgotCg==
    targetDN: uid=bjensen,ou=People,dc=example,dc=com
    changeLogCookie: <COOKIE2>

    The changes are base64-encoded. You can decode them using the base64 command. The following example decodes the first change:

    $ base64 decode --encodedData cmVwbGFjZTogZGVzY3JpcHRpb24KZGVzY3JpcHRpb246IE5ldyBkZXNjcmlwdGlvbgotCnJlcGxhY2U6IG1vZGlmaWVyc05hbWUKbW9kaWZpZXJzTmFtZTogdWlkPWJqZW5zZW4sb3U9UGVvcGxlLGRjPWV4YW1wbGUsZGM9Y29tCi0KcmVwbGFjZTogbW9kaWZ5VGltZXN0YW1wCm1vZGlmeVRpbWVzdGFtcDogMjAxNjEwMTQxNTA5MTJaCi0K
    
    replace: description
    description: New description
    -
    replace: modifiersName
    modifiersName: uid=bjensen,ou=People,dc=example,dc=com
    -
    replace: modifyTimestamp
    modifyTimestamp: <timestamp>
    -

    Notice the changeLogCookie value, which has the form base-dn:CSN. Here, CSN is a change sequence number.

  2. To start reading a particular change in the changelog, provide the cookie with the control:

    $ ldapsearch \
     --hostname localhost \
     --port 1636 \
     --useSsl \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --bindDN uid=admin \
     --bindPassword password \
     --baseDN cn=changelog \
     --control "ecl:false:$COOKIE1" \
     "(&)" \
     changes changeLogCookie targetDN
    
    # Public changelog exchange control(1.3.6.1.4.1.26027.1.5.4): <COOKIE2>
    dn: replicationCSN=<CSN2>,dc=example,dc=com,cn=changelog
    changes:: cmVwbGFjZTogZGVzY3JpcHRpb24KZGVzY3JpcHRpb246IE5ldywgaW1wcm92ZWQgZGVzY3JpcHRpb24KLQpyZXBsYWNlOiBtb2RpZmllcnNOYW1lCm1vZGlmaWVyc05hbWU6IHVpZD1iamVuc2VuLG91PVBlb3BsZSxkYz1leGFtcGxlLGRjPWNvbQotCnJlcGxhY2U6IG1vZGlmeVRpbWVzdGFtcAptb2RpZnlUaW1lc3RhbXA6IDIwMTYxMDE0MTUwOTE5WgotCg==
    targetDN: uid=bjensen,ou=People,dc=example,dc=com
    changeLogCookie: <COOKIE2>

    The following command decodes the changes:

    $ base64 decode --encodedData cmVwbGFjZTogZGVzY3JpcHRpb24KZGVzY3JpcHRpb246IE5ldywgaW1wcm92ZWQgZGVzY3JpcHRpb24KLQpyZXBsYWNlOiBtb2RpZmllcnNOYW1lCm1vZGlmaWVyc05hbWU6IHVpZD1iamVuc2VuLG91PVBlb3BsZSxkYz1leGFtcGxlLGRjPWNvbQotCnJlcGxhY2U6IG1vZGlmeVRpbWVzdGFtcAptb2RpZnlUaW1lc3RhbXA6IDIwMTYxMDE0MTUwOTE5WgotCg==
    
    replace: description
    description: New, improved description
    -
    replace: modifiersName
    modifiersName: uid=bjensen,ou=People,dc=example,dc=com
    -
    replace: modifyTimestamp
    modifyTimestamp: <timestamp>
    -
  3. If you lose the cookie, start over from the earliest available change by sending a request with no cookie.