Chapter 3. Document Security

MarkLogic provides a robust, role-based security model. Most of the functions expect to work with the IDs of roles or users, but names are much easier for humans to process. These recipes provide easier insight into who can see what.

List User Permissions on a Document

Problem

You want a list of a particular user’s permissions on a document.

Solution

Applies to MarkLogic versions 7 and higher

The xdmp:document-get-permissions() function will get all permissions, but you can narrow this down after identifying the user’s roles:

let $roles := xdmp:user-roles("some-user")
return
  xdmp:document-get-permissions("/content/some-doc.json")
    [sec:role-id = $roles]/sec:capability/fn:string()

The result will be a sequence of permission strings from among read, update, insert, and execute.

Discussion

Permissions are assigned to a document by role. Users are also assigned roles, and through them gain access to documents.

The first step of this recipe is to gather the roles that the specified user has. The xdmp:user-roles() function returns both the roles that the user has been directly granted and any inherited roles.

With the roles in hand, we can retrieve all the permissions on the target document, then use some XPath to retrieve just the ones we are interested in.

Note that the sec namespace is available by default—you do not need to declare it.

Get Permissions with Role Names

Problem

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

Solution

Applies to MarkLogic versions 7 and higher

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 xmlns="http://www.w3.org/1999/xhtml">{
          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>
)

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; either function allows us to run a block of code in a different execution context. There’s a big advantage to invoke: the function has access to the local variables, so we don’t need to pass in the role ID to look up as an external variable, as we would with xdmp:eval. We also avoid having code in a string, 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-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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.186.178