Element Level Security
Working with TDE's
The documentation around template driven extraction in combination with element level security is at best a bit sparse. It leaves room for interpretation which can lead to a false sense of security. This article will try to shed some light on the subject and provide some examples.
Introduction
Thinking about how document security affects template driven extraction, simply hiding documents (read: rows) from a user without permissions, one might think that the same would apply to columns that are based on elements that are protected by element level security (protected paths).
The following information is quoted from MarkLogic's FAQ on template driven extraction:
The triple index (which underlies both triples and rows) does not implement ELS. So if any part of a row or triple that a Template wants to project is protected via ELS, that row or triple will not be visible, and so no unauthorized user will see that row or triple.
There’s an exception when the security at the document-level is stronger than the ELS security. In that case the Template will cause the row or triple to be indexed, and user access will be governed by document-level security.
Source: https://developer.marklogic.com/learn/tde-faq
The first paragraph seems to suggest that the template will not be able to project the row if any part of it is protected by ELS. The second paragraph seems to suggest that the template will be able to project the row if the document-level security is stronger than the ELS security. This leaves room for interpretation, because the first paragraph seems to suggest that the template will not be able to project the row if any part of it is protected by ELS, regardless of the document-level security.
In order to find out what the actual behavior is, we'll have to do some testing.
The setup
In order to test the behavior we'll need a few things:
- Two roles to represent the different security levels
- Two users, each with different role assignments
- A protected path on part of the document structure
- Some documents with different permissions
- A TDE template on top of those documents
'use strict';
const sec = require('/MarkLogic/security.xqy');
xdmp.invokeFunction(
function() {
declareUpdate();
sec.createRole("cl-standard-role", "Standard Role", [], [], []);
sec.createRole("cl-super-role", "Super Role", [], [], []);
},
{
database: xdmp.database("Security")
}
);
'use strict';
const sec = require('/MarkLogic/security.xqy');
xdmp.invokeFunction(
function() {
declareUpdate();
sec.createUser("cl-standard-user", "Standard User", "password", ["cl-standard-role"], [], []);
sec.createUser("cl-super-user", "Super User", "password", ["cl-standard-role", "cl-super-role"], [], []);
},
{
database: xdmp.database("Security")
}
);
'use strict';
const sec = require('/MarkLogic/security.xqy');
xdmp.invokeFunction(
function() {
declareUpdate();
sec.protectPath("//envelope/bar", [], [xdmp.permission("cl-super-role", "read", "element")]);
},
{
database: xdmp.database("Security")
}
);
'use strict';
declareUpdate();
let doc = xdmp.toJSON({
"envelope": {
"id": 1,
"name": "Carmen Hernandez",
"birthday": "2003-12-10",
}
})
xdmp.documentInsert(
'/cleverllamas/doc1.json',
doc,
{
permissions: [
xdmp.permission('cl-standard-role', 'read'),
xdmp.permission('cl-standard-role', 'update')
],
collections: '/cleverllamas'
}
);
doc = xdmp.toJSON({
"envelope": {
"id": 2,
"name": "Dale Anderson",
"birthday": "1982-03-27",
}
})
xdmp.documentInsert(
'/cleverllamas/doc2.json',
doc,
{
permissions: [
xdmp.permission('cl-super-role', 'read'),
xdmp.permission('cl-super-role', 'update')
],
collections: '/cleverllamas'
}
);
doc = xdmp.toJSON({
"envelope": {
"id": 3,
"name": "George Taylor",
"birthday": "1993-09-02",
}
})
xdmp.documentInsert(
'/cleverllamas/doc3.json',
doc,
{
permissions: [
xdmp.permission('cl-standard-role', 'read'),
xdmp.permission('cl-standard-role', 'update'),
xdmp.permission('cl-super-role', 'read'),
xdmp.permission('cl-super-role', 'update')
],
collections: '/cleverllamas'
}
);
'use strict';
const tde = require('/MarkLogic/tde.xqy');
let template = xdmp.toJSON(
{
"template": {
"context": "/envelope",
"collections": [ "/cleverllamas"],
"rows": [
{
"schemaName": "cleverllamas",
"viewName": "view",
"columns": [
{
"name": "id",
"nullable": true,
"scalarType": "integer",
"val": "id"
},
{
"name": "name",
"nullable": true,
"scalarType": "string",
"val": "name"
},
{
"name": "birthday",
"nullable": true,
"scalarType": "string",
"val": "birthday"
}
]
}
]
}
}
);
tde.templateInsert(
'/tde/cl-template.json',
template,
[
xdmp.permission("cl-standard-role", "read")
]
);
The 'cl-standard-user' user has the 'cl-standard-role' role assigned allowing it acces to document 1 and 3. The 'cl-super-user' user has the 'cl-super-role' role and the 'cl-standard-role' role assigned allowing it access to documents 1, 2, and 3. In addition to that, the 'cl-super-role' role has access to the protected path.
The view provided by the TDE template is available to both users based on the 'cl-standard-role' role, which is assigned to both users.
The examples
With the security artifacts created and the documents loaded we can now start testing the behavior. We'll start by simply querying the documents using the 'cl-standard-user' user. This user has access to documents 1 and 3, but not to document 2. So as expected, the query returns only documents 1 and 3 and also does not return any of the elements that are protected by the protected path.
Next we'll query the documents using the 'cl-super-user' user. This user has access to all documents, so we expect to see all documents returned. And indeed we do. But we also see the elements that are protected by the protected path. This is exactly what we expected to see.
Now we'll try to get the documents using the TDE view. We'll start with the 'cl-standard-user' user. Similar to the previous example, we expect to see only documents 1 and 3 returned. And although we will be able to see the 'birthday' column (which is based on an element that is protected by the protected path), we will not be able to see its value. This is because the 'cl-standard-user' user does not have access to the protected path.
Finally we'll try to get the documents using the TDE view, but this time using the 'cl-super-user' user. We expect to see all documents returned and we also expect to see the value of the 'birthday' column. This is because the 'cl-super-user' user has access to the protected path. However, this is not what we see. We only see the value of the 'birthday' column for document 2.
Conclusion
The behavior of TDE in combination with element level security is a bit confusing. The documentation is not very clear on the subject and the behavior is not what one would expect. The documentation suggests that the template will not be able to project the row if any part of it is protected by ELS, regardless of the document-level security. However, the behavior is different.
The example shows that for the 'cl-standard-user' user, the template is able to project the rows for the document to which that user has access, but not the value of the 'birthday' column. It also shows that for the 'cl-super-user' user, the template is able to project the rows for all documents, but only the value of the 'birthday' column for the document for which document-level security matches the ELS security.
With this behavior being so much different from default document access, it is important to be aware of this when designing your security model.
Need Some Help?
Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!
- Introduction
- The setup
- The examples
- Conclusion