Skip to main content

Securing MarkLogic Server

XQuery Examples of Element Level Security

Run these queries on the Documents database using XQuery in Query Console. First run the queries in the context of els-user-1:

(: run this against the Documents database :)

xdmp:eval(
'cts:search(fn:doc(), cts:word-query("def"), "unfiltered"),
"-----------------------------------------------------",
cts:search(fn:doc(), cts:element-attribute-word-query(xs:QName("bar"), xs:QName("attr"), "test"), "unfiltered"), 
"----------------------------------------------------",
cts:search(fn:doc(), cts:json-property-value-query("bar", "2")),
"-----------------------------------------------------",
cts:search(fn:doc(), cts:element-attribute-word-query(xs:QName("reg"), xs:QName("expr"), "is"), "unfiltered")',
(),
  <options xmlns="xdmp:eval">
    <user-id>{xdmp:user("els-user-1")}</user-id>
  </options>
  )

=>
<?xml  version="1.0" encoding="UTF-8"?>
<root>
  <bar baz="2">def</bar>
  <bar attr="test1">ghi</bar>
</root>
-----------------------------------------------------

----------------------------------------------------
{
  "foo": 1, 
  "bar": "2", 
  "baz": {
    "bar": [
     3, 
     4
    ]
  }
}
-----------------------------------------------------

Notice that in the first query, all of the documents are returned, but the elements with protected paths are missing from the content:

<bar baz="1" attr="test">abc</bar>
"test": 5
<reg expr="this is a string">1</reg>

In the second query, the document does not show up at all because the query is searching on a protected path that els-user-1 is not allowed to see (protected path “/root/bar[@baz=1]”).

Note

If you are getting different results, check to see that you have set up your user roles correctly and added the query rolesets to the Security database.

Now, modify the query to use the context of the els-user-2 and run the queries again:

(: run this against the Documents database :)

xdmp:eval(
'cts:search(fn:doc(), cts:word-query("def"), "unfiltered"),
"-----------------------------------------------------",
cts:search(fn:doc(), cts:element-attribute-word-query(xs:QName("bar"), xs:QName("attr"), "test1"), "unfiltered"), 
"----------------------------------------------------",
cts:search(fn:doc(), cts:json-property-value-query("bar", "2")),
"-----------------------------------------------------",
cts:search(fn:doc(), cts:element-attribute-word-query(xs:QName("reg"), xs:QName("expr"), "is"), "unfiltered")',
(),
  <options xmlns="xdmp:eval">
    <user-id>{xdmp:user("els-user-2")}</user-id>
  </options>
  )

=>
<?xml  version="1.0" encoding="UTF-8"?>
<root>
  <bar baz="1" attr="test">abc</bar>
  <bar baz="2">def</bar>
  <bar attr="test1">ghi</bar>
</root>
-----------------------------------------------------
<?xml  version="1.0" encoding="UTF-8"?>
<root>
  <bar baz="1" attr="test">abc</bar>
  <bar baz="2">def</bar>
  <bar attr="test1">ghi</bar>
</root>
----------------------------------------------------
{
  "foo": 1, 
  "bar": "2", 
  "baz": {
    "bar": [
      3, 
      4
    ], 
    "test": 5
  }
}
-----------------------------------------------------
<?xml  version="1.0" encoding="UTF-8"?>
<root>
  <reg expr="this is a string">1</reg>
  <reg>2</reg>
</root>

This time all of the documents are returned, along with the protected elements. Notice that the one document is returned twice; two different queries find the same document.

Run the query one more time using the xdmp:eval() pattern as els-user-3 and notice that none of the documents are returned because els-user-3 does not have the basic permissions to read the documents.

(: run this against the Documents database :)

xdmp:eval(
'cts:search(fn:doc(), cts:word-query("def"), "unfiltered"),
"-----------------------------------------------------",
cts:search(fn:doc(), cts:element-attribute-word-query(xs:QName("bar"), xs:QName("attr"), "test1"), "unfiltered"), 
"----------------------------------------------------",
cts:search(fn:doc(), cts:json-property-value-query("bar", "2")),
"-----------------------------------------------------",
cts:search(fn:doc(), cts:element-attribute-word-query(xs:QName("reg"), xs:QName("expr"), "is"), "unfiltered")',
(),
  <options xmlns="xdmp:eval">
    <user-id>{xdmp:user("els-user-3")}</user-id>
  </options>
  )

=>
-----------------------------------------------------
-----------------------------------------------------
-----------------------------------------------------

Because els-user-3 does not have document level permissions, no documents are returned. You can use document level permissions along with element level security for additional security. See Combining Document and Element Level Permissions for more information.

Now unprotect the paths and run the previous query again without the protected paths to see difference in output. First unprotect the paths:

(: run this against the Security database :)

import module namespace sec="http://marklogic.com/xdmp/security" 
  at "/MarkLogic/security.xqy";

sec:unprotect-path("/root/bar[@baz=1]", ()),
sec:unprotect-path("test", ()),
sec:unprotect-path("/root/reg[fn:matches(@expr, 'is')]", ())

Note

Adding or unprotecting protected paths will trigger reindexing. After unprotecting elements, you must wait for reindexing to finish.

Unprotecting the paths does not remove them from the database. You will still see the protected paths in the Admin Interface or when you run fn:collection("http://marklogic.com/xdmp/protected-paths") against the Security database. But you will be able to see the whole document once the protected paths are unprotected if you have document permissions for the document. See Unprotecting or Removing Paths for more details.

Look through the code examples and run the queries using the xdmp:eval() pattern to change users. Run the queries in the context of the different users to better understand how the element level security logic works.