OpenDJ: REST to LDAP, part 4

OpenDJ Community Logo Since my last post on the subject, Matt and Jean-Noël have added additional features in REST/HTTP access to OpenDJ data, as well as fixes for issues found in testing.

  • The patch operation is now supported.
  • The HTTP connection handler now has its own access log.

Patch

OpenDJ now lets you patch JSON resources in addition to replacing them entirely. For example, you can replace an old email address with a new one. The data for the HTTP PATCH request would be an array with a single email value to replace that of the “emailAddress” field.

[
  {
    "operation": "replace",
    "field": "/contactInformation/emailAddress",
    "value": "babs@example.com"
  }
]

OpenDJ supports four patch “operation” types.

  1. “add”: ensures the target field of the resource contains the value(s) you provide, creating parent fields as necessary.
  2. “remove”: ensures the target field does not contain the value(s) you provide. If you do not provide a value, the entire field is removed if it already exists.
  3. “replace”: like a “remove” followed by an “add”, this removes existing values, replacing them with the values you provide.
  4. “increment”: increments numeric value(s) when you provide a positive number value. Decrements when you provide a negative number value.

For details on the patch operation types, in particular on how OpenDJ handles single-valued fields versus multi-valued fields, see the draft documentation that’s currently in review.

The “field” value points to the field you aim to change. An important nuance here is that OpenDJ handles arrays as sets. So you cannot have duplicates, and it is not meaningful to reference a value in array by its index. For many operations with identity data, you probably want sets anyway. For example, here’s how you would add Babs Jensen’s entry to a group.

$ curl
 --user kvaughan:bribery
 --request PATCH
 --header "Content-Type: application/json"
 --data '[
  {
    "operation": "add",
    "field": "/members",
    "value": [
      {
        "_id": "bjensen"
      }
    ]
  }
 ]'

http://opendj.example.com:8080/groups/Directory%20Administrators

 ?_prettyPrint=true
{
  "_rev" : "00000000b70c881a",
  "schemas" : [ "urn:scim:schemas:core:1.0" ],
  "_id" : "Directory Administrators",
  "displayName" : "Directory Administrators",
  "meta" : {
    "lastModified" : "2013-05-13T16:40:23Z"
  },
  "members" : [ {
    "_id" : "kvaughan",
    "displayName" : "Kirsten Vaughan"
  }, {
    "_id" : "rdaugherty",
    "displayName" : "Robert Daugherty"
  }, {
    "_id" : "bjensen",
    "displayName" : "Barbara Jensen"
  }, {
    "_id" : "hmiller",
    "displayName" : "Harry Miller"
  } ]
}

With the patch operation, you can also use resource revision numbers to make sure that you only patch the resource if it still is the version that you expect.

$ curl
 --user kvaughan:bribery
 "http://opendj.example.com:8080/users/bjensen?_prettyPrint=true&_fields=_rev"
{
  "_rev" : "00000000c1b6d4c7"
}
$ curl
 --user kvaughan:bribery
 --request PATCH
 --header "If-Match: 00000000c1b6d4c7"
 --header "Content-Type: application/json"
 --data '[
  {
    "operation": "add",
    "field": "/contactInformation/emailAddress",
    "value": "babs@example.com"
  }
 ]'

http://opendj.example.com:8080/users/bjensen?_prettyPrint=true

{
  "_rev" : "00000000f946d377",
  "schemas" : [ "urn:scim:schemas:core:1.0" ],
  "contactInformation" : {
    "telephoneNumber" : "+1 408 555 1862",
    "emailAddress" : "babs@example.com"
  },
  "_id" : "bjensen",
  "name" : {
    "familyName" : "Jensen",
    "givenName" : "Barbara"
  },
  "userName" : "babs@example.com",
  "displayName" : "Barbara Jensen",
  "meta" : {
    "lastModified" : "2013-05-13T16:56:33Z"
  },
  "manager" : [ {
    "_id" : "trigden",
    "displayName" : "Torrey Rigden"
  } ]
}

HTTP Access Log

HTTP access to OpenDJ is now logged in logs/http-access, next to the other OpenDJ log files. The HTTP access log format is what you would expect to see in a web server.

localhost kvaughan [13/May/2013:18:54:34 +0200] "GET /users/bjensen/_prettyPrint=true&_fields=_rev HTTP/1.1" 200 "curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5"
localhost kvaughan [13/May/2013:18:55:37 +0200] "PATCH /users/bjensen/_prettyPrint=true HTTP/1.1" 200 "curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5"

Internal operations performed by the HTTP connection handler are logged to the LDAP access log, logs/access. However, these are hidden by default, because by default the suppress-internal-operations property on the (LDAP) file based access log publisher is set to true. Set it to false if you want to see those internal operations in the LDAP access log. (To see this advanced property when running the dsconfig command interactively, try /path/to/opendj/bin/dsconfig --advanced.)

Although this OpenDJ and the gateway now support each of the CRUDPAQ operations, there are still some improvements on the way, from additional actions (think password modify) to paged results and sorting for searches. I’m still holding off on documenting how to configure authentication, expecting that we will see a simplification of the configuration model.

More to come…

Leave a Comment

Filed under Directory Services and LDAP

OpenDJ: REST to LDAP, part 3

OpenDJ Community Logo Since my last post on this topic, there have been some important changes in RESTful access to OpenDJ.

Here’s a short list.

  • Jean-Noël added an HTTP connection handler to OpenDJ directory server.

    All you have to do is enable the HTTP connection handler, and you can start using the RESTful API without installing any other software.

  • Matt added authentication support to let you do:
    • HTTP Basic authentication (with different ways to map the user ID to an entry)
    • OpenIDM-style authentication (using headers, by default X-OpenIDM-Username and X-OpenIDM-Password)
    • Something else if you want to write your own Servlet Filter to handle authentication
  • Matt added support for deleting resources.
  • Matt renamed the REST API HTTP query request parameter _filter to _queryFilter (which is more consistent with _queryId and _queryExpression in OpenIDM).

HTTP Connection Handler

Enable the HTTP connection handler after installing OpenDJ.

$ dsconfig set-connection-handler-prop \
--hostname opendj.example.com --port 4444 \
--bindDN "cn=Directory Manager" --bindPassword password \
--handler-name "HTTP Connection Handler" \
--set enabled:true --no-prompt

Once you have enabled the HTTP connection handler, OpenDJ listens on port 8080 by default. The paths start at the root context. (As of this writing, April 5, 2013, you also need to authenticate for GET.)

$ curl --user bjensen:hifalutin \

http://opendj.example.com:8080/users/bjensen?_prettyPrint=true

{
  "schemas" : [ "urn:scim:schemas:core:1.0" ],
  "contactInformation" : {
    "telephoneNumber" : "+1 408 555 1862",
    "emailAddress" : "bjensen@example.com"
  },
  "_id" : "bjensen",
  "name" : {
    "familyName" : "Jensen",
    "givenName" : "Barbara"
  },
  "userName" : "bjensen@example.com",
  "displayName" : "Barbara Jensen",
  "manager" : [ {
    "_id" : "trigden",
    "displayName" : "Torrey Rigden"
  } ]
}

You configure the HTTP connection handler in the same way you configure the REST LDAP gateway, by using a JSON format configuration file. By default, the file is /path/to/OpenDJ/config/http-config.json.

Authentication Support

Matt implemented authentication support a couple of weeks ago. Right now the docs are out of sync with the trunk.  I am waiting on an enhancement, OPENDJ-827 (Simplify Rest2LDAP’s authentication configuration model), before updating the appendix on configuration.

In the meantime, if you want to start reconfiguring authentication now, read the comments the configuration file.

Deleting Resources

As of this writing, April 5, 2013, you can delete resources through the gateway, though not yet through the HTTP connection handler. That is likely to change soon.

By default, the configuration has "useSubtreeDelete" : true, so if you try to delete as a user without access rights to perform a tree delete, you can get an error because that control is not in the ACIs.

$ curl --request DELETE \
--user kvaughan:bribery \

http://opendj.example.com:8080/rest2ldap/users/bjensen?_prettyPrint=true

{
  "code" : 500,
  "reason" : "Internal Server Error",
  "message" : "Unavailable Critical Extension: The request control with Object Identifier (OID) \"1.2.840.113556.1.4.805\" cannot be used due to insufficient access rights"
}

If you set "useSubtreeDelete" : false in the configuration file, opendj-rest2ldap-servlet.json and reload the gateway, then you can use delete for individual resources.

$ curl --request DELETE \
--user kvaughan:bribery \

http://opendj.example.com:8080/rest2ldap/users/bjensen?_prettyPrint=true

{
  "_rev" : "000000007f413b85",
  "schemas" : [ "urn:scim:schemas:core:1.0" ],
  "contactInformation" : {
    "telephoneNumber" : "+1 408 555 1862",
    "emailAddress" : "bjensen@example.com"
  },
  "_id" : "bjensen",
  "name" : {
    "familyName" : "Jensen",
    "givenName" : "Barbara"
  },
  "userName" : "bjensen@example.com",
  "displayName" : "Barbara Jensen",
  "manager" : [ {
    "_id" : "trigden",
    "displayName" : "Torrey Rigden"
  } ]
}

As you can see, the delete returns the resource you removed.

You can also use the resource revision in an If-Match header to assert that the resource to delete is in fact the same version of the resource that you expect.

To try this, get the revision.

$ curl --user kvaughan:bribery \
"http://opendj.example.com:8080/rest2ldap/users/ajensen?_fields=_rev&_prettyPrint=true"
{
  "_rev" : "000000002222a818"
}

If you have the wrong revision for the current resource and use the assertion, the delete fails.

$ curl --request DELETE \
--header "If-Match: wrong-rev" \
--user kvaughan:bribery \

http://opendj.example.com:8080/rest2ldap/users/ajensen?_prettyPrint=true

{
  "code" : 412,
  "reason" : "Precondition Failed",
  "message" : "Assertion Failed: Entry uid=ajensen,ou=People,dc=example,dc=com cannot be removed because the request contained an LDAP assertion control and the associated filter did not match the contents of the entry"
}

If you have the right revision, the delete can complete successfully.

$ curl --request DELETE \
--header "If-Match: 000000002222a818" \
--user kvaughan:bribery \

http://opendj.example.com:8080/rest2ldap/users/ajensen?_prettyPrint=true

{
  "_rev" : "000000007eea3869",
  "schemas" : [ "urn:scim:schemas:core:1.0" ],
  "contactInformation" : {
    "telephoneNumber" : "+1 408 555 7892",
    "emailAddress" : "ajensen@example.com"
  },
  "_id" : "ajensen",
  "name" : {
    "familyName" : "Jensen",
    "givenName" : "Allison"
  },
  "userName" : "ajensen@example.com",
  "displayName" : "Allison Jensen",
  "manager" : [ {
    "_id" : "kwinters",
    "displayName" : "Kelly Winters"
  } ]
}

More to come…

1 Comment

Filed under Directory Services and LDAP

OpenDJ: REST to LDAP Gateway, part 2

OpenDJ Community LogoThe REST to LDAP gateway that you read about a few days ago is continuing to improve. If you look at the latest configuration, you will notice some changes:

  • DN reference mapping lets you map LDAP attributes like manager and uniqueMember that take DN values.
  • The "/users" mapping adds "manager" and "groups" fields, taking advantage of the reference mapping capability.
  • The default configuration now also includes a "/groups" mapping.
  • The "/users" and "/groups" mappings add "meta" fields to JSON resources. In the default configuration the "meta" field shows information about when the underlying entry was created and last modified.
  • ID and revision attributes in the default config are now "_id" and "_rev" as in OpenIDM.

The reference mapping prevents RESTful client applications from having to understand LDAP DNs. For example, if you look at Babs Jensen’s entry through LDAP, you notice that her manager is trigden.

$ ldapsearch -p 1389 -b dc=example,dc=com "(uid=bjensen)" cn manager
dn: uid=bjensen,ou=People,dc=example,dc=com
cn: Barbara Jensen
cn: Babs Jensen
manager: uid=trigden, ou=People, dc=example,dc=com

In the JSON resource for Babs Jensen, trigden is mapped to "_id", and the manager’s name is pulled out of the referenced entry, so you can now see who trigden is without doing another search.

$ curl 'http://opendj.example.com:8080/rest2ldap/users/bjensen?_fields=displayName,manager&_prettyPrint=true'
{
  "displayName" : "Barbara Jensen",
  "manager" : [ {
    "_id" : "trigden",
    "displayName" : "Torrey Rigden"
  } ]
}

If you look at Torrey Rigden’s entry through LDAP, you see the groups that Torrey is a member of are also referenced by DN.

$ ldapsearch -p 1389 -b dc=example,dc=com "(uid=trigden)" cn isMemberOf
dn: uid=trigden,ou=People,dc=example,dc=com
cn: Torrey Rigden
isMemberOf: cn=PD Managers,ou=groups,dc=example,dc=com

The REST to LDAP gateway uses the reference mapping for this as well.

$ curl 'http://opendj.example.com:8080/rest2ldap/users/trigden?_fields=displayName,groups&_prettyPrint=true'
{
  "displayName" : "Torrey Rigden",
  "groups" : [ {
    "_id" : "PD Managers"
  } ]
}

The "/groups" mapping gives you what you would expect, using the reference mapping for members.

$ curl 'http://opendj.example.com:8080/rest2ldap/groups?_filter=true&_fields=displayName,members&_prettyPrint=true'
{
  "result" : [ {
    "displayName" : "Accounting Managers",
    "members" : [ {
      "_id" : "scarter",
      "displayName" : "Sam Carter"
    }, {
      "_id" : "tmorris",
      "displayName" : "Ted Morris"
    } ]
  }, {
    "displayName" : "Directory Administrators",
    "members" : [ {
      "_id" : "kvaughan",
      "displayName" : "Kirsten Vaughan"
    }, {
      "_id" : "hmiller",
      "displayName" : "Harry Miller"
    }, {
      "_id" : "rdaugherty",
      "displayName" : "Robert Daugherty"
    } ]
  }, {
    "displayName" : "HR Managers",
    "members" : [ {
      "_id" : "kvaughan",
      "displayName" : "Kirsten Vaughan"
    }, {
      "_id" : "cschmith",
      "displayName" : "Chris Schmith"
    } ]
  }, {
    "displayName" : "PD Managers",
    "members" : [ {
      "_id" : "kwinters",
      "displayName" : "Kelly Winters"
    }, {
      "_id" : "trigden",
      "displayName" : "Torrey Rigden"
    } ]
  }, {
    "displayName" : "QA Managers",
    "members" : [ {
      "_id" : "abergin",
      "displayName" : "Andy Bergin"
    }, {
      "_id" : "jwalker",
      "displayName" : "John Walker"
    } ]
  } ],
  "resultCount" : 5,
  "pagedResultsCookie" : null,
  "remainingPagedResults" : -1
}

Get the updated REST to LDAP gateway for yourself and look at opendj-rest2ldap-servlet.json to see how this works in the configuration. Updates to the doc for these changes are in review.

Still more to come…

1 Comment

Filed under Directory Services and LDAP

OpenDJ: REST to LDAP Gateway

OpenDJ Community LogoThe REST to LDAP mapping is a new feature for the next release of OpenDJ. Although it is still early and the features is not finished yet, you can already play with some of the new capabilities.

REST to LDAP exposes directory data over HTTP as JSON resources. This opens up directory data to applications that do not support LDAP.

The initial artifact for serving JSON resources is a REST to LDAP gateway, which is a Servlet. (The Servlet will be followed by a REST to LDAP connection handler within OpenDJ.) To give the gateway a try, run it with an OpenDJ directory server in back.

As of this writing, the gateway .war is not yet posted on the builds page. Try one of these options instead:

  • You can download a build of the .war from the ForgeRock Maven repository under opendj-rest2ldap-servlet/3.0.0-SNAPSHOT/.
  • You can get the code, and then build and run the gateway yourself.
    $ svn co https://svn.forgerock.org/opendj/trunk/opendj3
    $ cd opendj3/opendj-rest2ldap-servlet && mvn jetty:run

    By default, the gateway listens on localhost:8080, and connects to the directory server on localhost:1389 as cn=Directory Manager with password password. The gateway is also configured to work out of the box with sample data (entries under dc=example,dc=com, either those generated at setup time or loaded from Example.ldif).

With a default OpenDJ configuration, you probably only need to start the directory server. (Don’t have OpenDJ? Try the quick install.)

$ /path/to/OpenDJ/bin/start-ds

If the defaults do not work for your setup, know that the configuration file for the gateway is opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json. If you downloaded and deployed the gateway .war, then find opendj-rest2ldap-servlet.json under the directory where you deployed the .war. Also see the links to the doc below.

Once the gateway and OpenDJ are configured and started, REST clients can access the gateway to get at directory data, as in the following example.

$ curl http://localhost:8080/users/bjensen?_prettyPrint=true
{
  "id" : "bjensen",
  "rev" : "000000002f43b789",
  "schemas" : [ "urn:scim:schemas:core:1.0" ],
  "contactInformation" : {
    "telephoneNumber" : "+1 408 555 1862",
    "emailAddress" : "bjensen@example.com"
  },
  "name" : {
    "familyName" : "Jensen",
    "givenName" : "Barbara"
  },
  "userName" : "bjensen@example.com",
  "displayName" : "Barbara Jensen"
}

At this early stage, the gateway supports three of the seven CRUDPAQ verbs: create, read, and query.

There is a bit of documentation for REST to LDAP. All of this is still in progress.

More to come…

1 Comment

Filed under Directory Services and LDAP

OpenDJ: Changing server certificates

The default validity period for OpenDJ self-signed certificates used to be 730 days. Those of you who generated keys & self-signed certificates a couple of years ago could have certificates that will expire soon.

There’s a new chapter in the OpenDJ Administration Guide that covers Changing Server Certificates, including not only replacing the keys for regular connection handlers, but also generating new keys & self-signed certs used to protect replication traffic.

You will find the Configuring Servers For Client Application Access chapter has been beefed up with more examples of manipulating certificates using the Java keytool command, including CA-signed certificates. Plus, the Performing LDAP Operations chapter now also demonstrates Authenticating Using a Certificate, showing the steps to have an application, for example, authenticate to OpenDJ without a user name and password.

Leave a Comment

Filed under Directory Services and LDAP, Docs

CRUDPAQ

Maybe you noticed that Matt has been building a foundation for common ForgeRock REST APIs. What makes the APIs common is that they all let you do the same seven kinds of operations.

  1. Create: Add a resource that does not yet exist
  2. Read: Retrieve a single resource
  3. Update: Replace an existing resource
  4. Delete: Remove an existing resource
  5. Patch: Modify part of an existing resource
  6. Action: Perform a predefined action
  7. Query: List a set of resources

When you hear someone from ForgeRock talking about CRUDPAQ, there’s nothing dirty involved. Just a mnemonic device.

(Note: In practice, action operations depend on the resource server, so actions might differ from server to server. Also, some bits of work remain. For example, patch is still to be finished as of this writing.)

Matt put together a JSON Resource Java library. With that he built a JSON Resource Servlet, which implements an in-memory store for your resources. (See the project page for instructions on getting JSON Resource Servlet up and running.) JSON Resource Servlet provides a simple example of how these common REST APIs are likely to look and feel.

Matt included two endpoints by default, /users and /groups. Objects in this version do not have schema, so you can add whatever you like. I’ve played with arrays of SCIM-like users and groups based on the traditional example data we use in the directory server world.

The advantage of this is that it’s all language independent. Take your pick.

After getting a rise out of Jake’s cool work, especially on the OpenIDM UI, I felt it was high time to learn some JavaScript.

Even with my beginner’s confusion about JavaScript, it did not take long to write CRUD and Q functions for use in building a web page. (Creating a page that doesn’t look like I pulled it out of a time capsule from 1996 is a different story, however. My stance with respect to modern web development is 2nd or 3rd order ignorance, depending on the topic.) There are slicker ways to do this, but as you can see, it is not hard to…

Create a resource from an object and a resource URI (ending in the resource ID like /users/bjensen, or more likely /users/e7fa64c0-70dc-47fb-b102-7106ee480421):

function create(object, uri) {
    var xhr = new XMLHttpRequest();
    xhr.open('PUT', uri, false);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.setRequestHeader("If-None-Match", "*");
    xhr.send(JSON.stringify(object));
    return xhr.responseText;
}

Read a resource, optionally limiting the resource fields in the response (check the response field “_id” for the resource ID):

function read(uri, fields) {
    fields = (typeof fields === "undefined") ? [] : fields;

    var args = "";
    if (fields.length > 0) {
        args = "?_fields=" + fields[0];
        for (var i = 1; i < fields.length; i++) {
            args = args + "," + fields[i];
        }
    }
    uri = uri + args;

    var xhr = new XMLHttpRequest();
    xhr.open('GET', uri, false);
    xhr.send("");
    return xhr.responseText;
}

Update a resource, for which you need the revision (check the resource “_rev” field):

function update(object, revision, uri) {
    var xhr = new XMLHttpRequest();
    xhr.open('PUT', uri, false);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.setRequestHeader("If-Match", revision);
    xhr.send(JSON.stringify(object));
    return xhr.responseText;
}

Delete a resource:

function remove(uri) { // delete is a keyword
    var xhr = new XMLHttpRequest();
    xhr.open('DELETE', uri, false);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.send("");
    return xhr.responseText;
}

Query resources. Query lets you do a lot more than this example (filters, sorting, paging, … Scroll down the JSON Resource Servlet page to the section on Querying/listing resources). This one just lists resources. Here containerUri is /users, or /groups:

function queryObjects(containerUri, fields) {
    fields = (typeof fields === "undefined") ? [] : fields;

    var query = containerUri + "?_filter=true";
    var args = "";
    if (fields.length > 0) {
        args = "&_fields=" + fields[0];
        for (var i = 1; i < fields.length; i++) {
            args = args + "," + fields[i];
        }
        query = query + args;
    }

    var xhr = new XMLHttpRequest();
    xhr.open('GET', query, false);
    xhr.send("");

    var allObjects = [];
    if (xhr.status === 200) { // OK
        allObjects = JSON.parse(xhr.responseText);
        if (allObjects.result.length > 0) {
            allObjects = allObjects.result;
        } else {
            allObjects = [];
        }
    }
    return allObjects;
}

Not shown here are actions. The JSON Resource Servlet for example lets you create a resource and let the servlet create the ID. (Hint: Use a POST with ?_action=create to the container URI.)

As you can see, the basic operations are fairly easy to use.

By the way, not all ForgeRock REST APIs will look like this. The REST API for OAuth 2.0 in OpenAM will not, for example, because that API is already clearly defined at the IETF. But there’s a good chance that many APIs will follow this pattern.

1 Comment

Filed under Tools

Docs: .example vs. .com

In OpenAM docs we often need to differentiate between second-level DNS domains. When writing about federation, for example, we need to distinguish among providers. In the real world, many providers use .com as the top-level domain and their brand as the second level domain. Hence we have ended up accidentally squatting on other people’s property, like idp.com or sp.com.

Read them again: idp.com and sp.com.

If they look like Internet domain names, that is because they are.

Now read these: idp.example and sp.example.

Hesitation? Actually these are the “correct” examples for use in documentation.

The string “.com” is self-evidently obvious as a top-level domain even to people who cannot say what DNS stands for or what it does. So adding something in front of .com makes it instantly recognizable: “Oh, that’s an Internet name.”

Unfortunately, we ought not use .com in documentation where we need to differentiate by second-level domain. Almost all *.com domains can belong to somebody else.

As mentioned, there is a workaround. RFC 2606 sets aside .example, .example.com, .example.net, .example.org for use in documentation. It also sets aside others like .test.

".test" is recommended for use in testing of current or new DNS
related code.

Similar story with .invalid and .localhost. These are reserved but not suited to most documentation.

Our docs do use .example.com, .example.net, .example.org. But those hide the differentiation in the top-level domain instead of where we want it in this case, which is the second level. We would have idp.example.net and sp.example.com, which is technically correct but harder to understand when reading.

The reserved top-level .example looks weird to anybody who has not read RFC 2606. I have read RFC 2606 and it still looks weird to me. idp.example in the doc is technically correct. The trade off is that readers have to slow down and think, “What the heck is idp.example? (Quick search…) Oh, these writers have read RFC 2606, which says .example is like .com, except that it’s reserved as community property so that it can be used in documentation. Okay, what was I thinking about before I got stuck on that…?”

Even though the reserved names in RFC 2606 can violate the rule of least surprise, as long as we are consistent in applying RFC 2606, then .example might be the best choice in the long term.

First, using .example is the best correct workaround that we know of at this point.

Second, using .example might actually encourage people who like the challenge of getting their heads around reasonably complex topics such as access management federation. After one stumbles on .example once or twice, one looks it up and learns what is “correct.” This once again confirms the reader’s status as a power user who knows sundry Internet arcana, turning the stumbling block into its own reward.

Third, it also makes us writers look like we know what we are writing about. We even pay attention to details like RFC 2606. (On the other hand, now that we pay attention to RFC 2606, we probably should be extra careful about the really important details, too.)

Leave a Comment

Filed under Access Management, Docs