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.
You want a list of a particular user’s permissions on a document.
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
.
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 the permissions on a document, decorated with the names of the roles.
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"
)
(<role
id=
"7054712474775191582"
name=
"RunDMC-author"
capability=
"update"
></role>
<role
id=
"13533095337080026511"
name=
"RunDMC-role"
capability=
"read"
></role>
)
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.
3.137.186.178