BOBJ user management woes

Sometimes a seemingly simple and obvious request, such as having a user's full name as part of his account information, can prove more daunting than you might imagine.

— Binary Adventures

Disclaimer: this article is based on BI 4.2 SP02.

I received a request to populate the description field of all user accounts in BOBJ with their full name. This may sound like a strange request at first, but there are quite valid reasons for this:

  • The user accounts were pulled in from Active Directory.
  • The full name is stored in the displayName attribute in AD, which BOBJ doesn’t use. Thus, the user’s full name was not visible in the CMC.

The second issue has a solution as of version 4.x: the Custom Attribute Mapping feature allows you to define new user attributes, sourced from external systems. Thus, we can retrieve the displayName attribute and store it in e.g. SI_DISPLAY_NAME in the BOBJ repository.

Alas, you can’t see custom attributes when you’re looking at the user list in the CMC (only when viewing a user’s properties), nor can you use it in a search. So we’re still stuck with the nonsensical USER1234 user name in BOBJ.

A workaround is to use the description property. It’s visible in the user list and you can search on it as well. Great, but how do we fill the property (other than by hand)?

Version 4.2 to the rescue! The REST API has been expanded to allow (basic) user management, which includes updating user details, exactly what we need. A GET http://<baseURL>/users should give us all users, and GET http://<baseURL>/users/<SI_ID> returns a single user’s details.

First issue: the user listing only returns the first 50 users. To expand the number of users returned, you’ll need to add the pageSize=<n> URL parameter, where n is the number of entries you’d like returned. Think of it as a TOP n statement that you’d add to repository SQL queries.

Below is a sample of the data you’ll receive. Just a quick note: I’ve removed all of the metadata cruft that BOBJ returns in its JSON responses.

{
    "entries": [
        {
            "cuid": "AfRWaT5_13lsdLLf5bRMLKY",
            "description": "",
            "fullName": "",
            "id": 9836,
            "title": "USER1234",
            "type": "User",
            "tenant": "19"
        }
    ]
}

This may look like a good start, but on closer examination, you’ll notice that we’re missing a key attribute: the user’s full name, stored in the custom attribute.

Let’s see what kind of data we can fetch when using the GET http://<baseURL>/users/<SI_ID> call:

{
  "entries": [
    {
      "cuid": "AfRWaT5_131NlLLf5bRMLKY",
      "isAccountDisabled": false,
      "isPasswordToChangeAtNextLogon": false,
      "isPasswordChangeAllowed": false,
      "description": "",
      "fullName": "",
      "isPasswordExpiryAllowed": false,
      "title": "USER1234",
      "emailAddress": "",
      "usergroups": "[1]",
      "connection": 1,
      "id": 9836,
      "inboxID": 0
    }
  ]
}

Again, no sign of the custom attribute. This seems like a dead end. Luckily, among the new features introduced in BI 4.2 is the ability to execute a repository query through REST. This means that we can run the query below through GET http://<baseURL>/infostore/cmsQuery. Just remember that you’ll need to pass the pageSize query parameter again to fetch all the records.

SELECT si_id
      ,si_custom_mapped_attributes
  FROM ci_infoobjects, ci_systemobjects, ci_appobjects
 WHERE si_kind = 'user'

And here’s the result:

{
    "entries": [
        {
            "SI_ID": 9836,
            "SI_CUSTOM_MAPPED_ATTRIBUTES": [
                {
                    "SI_DISPLAYNAME": "Johnny Appleseed"
                }
            ]
        }
    ]
}

So all that’s left is to run through the list of users, call PUT http://<baseURL>/users/<SI_ID> for each of them to update their description and Bob’s your uncle, right?

If that was the case, the article would’ve ended here. As soon as you try the PUT HTTP call, you’ll notice some idiosyncrasies of the implementation:

  • The title attribute is always required in the JSON payload. Even if the user ID is part of the HTTP call, you still need to pass the username. If you don’t, you’ll be met with the rather mysterious error message Invalid object name.

  • If you don’t pass an attribute, it’s value will still be set. For example, if you leave the description attribute out of the JSON payload, the description will be cleared. Worse, if you don’t specify a value for connection (which is the user license, e.g. named or concurrent), it will be set to named. Interesting decision, isn’t it?

For my example (updating the user’s description), simply passing the description attribute caused the updated user accounts to be changed from concurrent to named users, up until the available named licenses were exhausted. Subsequent attempts resulted in HTTP 500 errors.

The final approach consisted of:

  1. Executing the CMS query to fetch all user data, including their custom attributes.
  2. Fetch the user details for each user (using GET http://<baseURL>/users/<SI_ID>).
  3. Modify the description attribute in the JSON response and use it to modify that user details (PUT http://<baseURL>/users/<SI_ID>).

Of course, this is only a one-off modification. Accounts created afterwards would still have a blank description field.