Problem

Get the permissions on a document, decorated with the names of the roles.

Solution

Applies to MarkLogic versions 7+

We want to get not just the IDs of the roles, but their names as well. This requires calling sec:get-role-names(), which must be run against the Security database. However, xdmp:document-get-permissions() must be run against the database containing the document about which we want the information.

import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy";
declare function local:dump-perms($uri)
{
  for $perm in xdmp:document-get-permissions($uri)
  let $role-name :=
    xdmp:invoke-function(
      function() {
        try {
          sec:get-role-names($perm/sec:role-id)
        }
        catch($ex) {()}   
      }, 
      <options xmlns="xdmp:eval">
        <database>{xdmp:security-database()}</database>
      </options>
    )
  return
    <role id="{$perm/sec:role-id}" name="{$role-name}" capability="{$perm/sec:capability}"></role>
};
local:dump-perms("/content/doc1.json")

Sample output:

(
  <role id="7054712474775191582" name="RunDMC-author" capability="update"></role>
  <role id="13533095337080026511" name="RunDMC-role" capability="read"></role>
)

Required Privileges:

  • https://marklogic.com/xdmp/privileges/get-role-names
  • https://marklogic.com/xdmp/privileges/xdmp-invoke

Discussion

When we get permissions for a document, we typically get something like this:

(
  <sec:permission>
    <sec:capability>read</sec:capability>
    <sec:role-id>324978243</sec:role-id>
  </sec:permission>,
  <sec:permission>
    <sec:capability>read</sec:capability>
    <sec:role-id>32493478578243</sec:role-id>
  </sec:permission>,
  <sec:permission>
    <sec:capability>update</sec:capability>
    <sec:role-id>32493478578243</sec:role-id>
  </sec:permission>
)

That provides the essential information, but to be useful to people, we really need the role names, not just the ids. This recipe looks up the names. sec:get-role-names() gives us the role names, with the requirement that the function be run against the Security database. In order to do that, we’re calling xdmp:invoke-function(). We could have used xdmp:eval() here, but there’s a big advantage to invoke: the function has access to the local environment, so we don’t need to pass in the role ID to look up as an external variable. We also avoid having code in strings, which is generally harder to maintain.

Notice the try/catch. sec:get-role-names() will throw an error if called with a role ID that is not in the Security database. How can this happen?

Suppose we have a role, role-1. We insert a document, giving role-1 read and update permissions.

xquery version "1.0-ml";

xdmp:document-insert(
  "/example.xml",
  <doc>
    <content>important stuff here</content>
  </doc>,
  (xdmp:permission("role-1", "read"),
   xdmp:permission("role-1", "update"))
)

Some time later, we change our roles, adding role-2. We give role-2 read and update permissions on the same document.

xquery version "1.0-ml";

xdmp:document-add-permissions(
  "/example.xml",
  (xdmp:permission("role-2", "read"),
   xdmp:permission("role-2", "update"))
)

Right now, if we run the recipe above, here’s the output we get:

<role id="3480302512589563034" name="role-2" capability="update"></role>
<role id="3480302512589563034" name="role-2" capability="read"></role>
<role id="3480302512512133719" name="role-1" capability="read"></role>
<role id="3480302512512133719" name="role-1" capability="update"></role>

Now suppose that role-1 gets deleted, due to changing security requirements or implementation. When a role is deleted, it is removed from all users and the record of it is removed from the Security database. However, the indexes are not updated to reflect that the role no longer exists — doing so could be a very large operation, if the role had permissions on many documents. Note that this is not a security problem, because no user has that role anymore. However, it does mean that our document still lists permissions for this orphaned role. If an invalid ID gets passed to sec:get-role-names(), then the function will throw an error. This is why we have the try/catch in place: to allow us to continue gathering information on known roles. After removing role-1, here is the result of calling the recipe:

<role id="3480302512589563034" name="role-2" capability="update"></role>
<role id="3480302512589563034" name="role-2" capability="read"></role>
<role id="3480302512512133719" name="" capability="read"></role>
<role id="3480302512512133719" name="" capability="update"></role>

The empty name indicates an orphaned role. If we prefer to suppress those results, we can add where $role-name ne "" to the FLWOR statement. We can also use this to discover orphaned roles, which can be cleaned up by using xdmp:document-set-permissions() with the valid ones.

Learn More

Advanced Security

Explore all technical resources related to advanced security in MarkLogic.

MarkLogic Security Course

This course walks through how to securely manage data inside the MarkLogic database.

MarkLogic Security Guide

Read over how to use the role-based security model in MarkLogic Server.

This website uses cookies.

By continuing to use this website you are giving consent to cookies being used in accordance with the MarkLogic Privacy Statement.