This chapter describes some common scenarios for defining security policies in your applications. The scenarios shown here are by no means exhaustive. There are many possibilities for how to set up security in your applications. The following sections are included:
One simple way to restrict access to your MarkLogic Server application is to limit the users that have permission to run the application. If you load your Xquery code into a modules database, you can use an execute permission on the XQuery document itself to control who can run it. Then, a user must possess execute
permissions to run the module. To set up a module to do this, perform the following steps:
.xqy
extension, for example my_module.xqy
.execute
permissions on the XQuery document for a given role. For example, if you want users with the run_application
role to be able to execute an XQuery module with the URI http://modules/my_module.xqy
,
run a query similar to the following:xdmp:document-set-permissions("http://modules/my_module.xqy", xdmp:permission("run_application", "execute") )
run_application
role.run_application
role to the users who can run this application.Now only users with the run_application
role can execute this document.
Because your application could also contain amped functions, this technique can help restrict access to applications that use amps.
The role-based security model in MarkLogic Server combined with the supported authentication schemes provides numerous options for implementing application access control. This section describes common application access control alternatives:
For details on the different authentication schemes, see Types of Authentication.
This approach may be appropriate if security is not a concern for your MarkLogic Server implementation or if you are just getting started and want to explore the capabilities of MarkLogic Server before contemplating your security architecture. This scenario provides all of your users with the admin
role.
You can turn off access control for each HTTP or WebDAV server individually by following these steps using the Admin Interface:
application-level
for the authentication scheme.admin
role for the default user. For example, you may choose the admin
user you created when you installed MarkLogic.To assist with identifying users with the admin
role, the default user selection field places (admin
) next to admin
users.
In this scenario, all users accessing the application server are automatically logged in with a user that has the admin
role. By default, the admin
role has the privileges and permissions to perform any action and access any document in the server. Therefore, security is essentially turned off for the application. All users have full access to the application and database associated with the application server.
This approach allows you to restrict application access to users in your security database, and gives those users full access to all application servers defined in MarkLogic Server. There are multiple ways to achieve the same objective but this is the simplest way.
admin
role.digest
, basic
or digest-basic
authentication.admin
role and are treated as having all privileges. In this scenario, all users must authenticate with a username and password. Once they are authenticated, however, they have full access to all functions and data in the server.
This application access control method can be modified or extended to meet the requirements in many application scenarios. It uses more of the available security features and therefore requires a better understanding of the security model.
To limit application access to a subset of the users in the security database, perform the following steps using the Admin Interface:
exe-priv-app1
to represent the privilege to access the App Server.role-app1
that has exe-priv-app1
execute privilege.role-app1
to the roles of all users in the security database who should have access to this App Server.digest
, basic
or digest-basic
. If you want to use application-level authentication to achieve the same objective, a custom login page is required. See the next section for details.exe-priv-app1
for the privilege field. Once this is done, only the users who have the exe-priv-app1
by virtue of their role(s) are able to access this App Server.If you want any user in the security database to be able to access the application, leave the privilege field blank.
At this point, the application access control is configured.
This method of authentication also needs to be accompanied by the appropriate security configuration for both users and documents associated with this App Server. For example, functions such as xdmp:document-insert
and xdmp:document-loadthrow exceptions unless the user possesses the appropriate execute privileges. Also, users must have the appropriate default permissions (or specify the appropriate permissions with the API) when creating new documents in a database. Documents created by a user who does not have the admin
role must be created with at least one update permission or else the transaction throws an XDMP-MUSTHAVEUPDATE
exception. The update permission is required because otherwise once the documents are created no user (except users with the admin
role) would be able to access them, including the user who created them.
Digest and basic authentication use the browser's username and password prompt to obtain user credentials. The server then authenticates the credentials against the security database. There is no good way to create a custom login page using digest and basic authentication. To create custom login pages, you need to use application-level authentication.
To configure MarkLogic Server to use a custom login page for an App Server, perform the following steps using the Admin Interface:
nobody
as the default user. The nobody
user is automatically created when MarkLogic Server is installed. It is created with the following roles: rest-reader, rest-extension-user, app-user, harmonized-reader
and is given a password which is randomly generated.login.xqy
. login.xqy
the default page displayed by the application server. Do not require any privilege to access login.xqy
(that is, do not place xdmp:security-assert()
in the beginning of the code for login.xqy
. This makes login.xqy
accessible by nobody
, the default user specified above, until the actual user logs in with his credentials. login.xqy
page likely contains a snippet of code as shown below:...return if xdmp:login($username, $password) then ... protected page goes here... else ... redirect to login page or display error page...The rest of this example assumes that all valid users can access all the pages and functions within the application.
If you are using a modules database to store your code, the login.xqy
file still needs to have an execute
permission that allows the nobody
(or whichever is the default) user to access the module. For example, you can put an execute
permission paired with the app-user
role on the login.xqy
module document, and make sure the nobody
user has the app-user
role (which it does by default).
application-user-role
.application-privilege
. Add this privilege to the application-user-role
.application-user-role
to all users who are allowed to access the application.login.xqy
:try { xdmp:security-assert("application-privilege","execute") } catch($e) { xdmp:redirect-response("login.xqy") }
if(not(xdmp:has-privilege("application-privilege","execute"))) then ( xdmp:redirect-response("login.xqy") ) else ()
This ensures that only a user who has the application-privilege
by virtue of his role can access these protected pages.
Similar to the previous approach, this method of authentication requires the appropriate security configuration for users and documents. See Introduction to Security for background on the security model.
MarkLogic Server supports deployments in which a user is automatically given access to the application based on the client IP address.
Consider a scenario in which a user is automatically logged in if he is accessing the application locally (as local-user
) or from an approved subnet (as site-user
). Otherwise, the user is asked to login explicitly. The steps below describe how to configure MarkLogic Server to achieve this access control.
nobody
as the default user. The nobody
user is automatically created when MarkLogic Server is installed. It is created with the following roles: rest-reader, rest-extension-user, app-user, harmonized-reader
and is given a password which is randomly generated.default.xqy
.xquery version "1.0-ml" declare namespace widget ="http://widget.com" import module "http://widget.com" at "/login-routine.xqy" let $login := widget:try-ip-login() return if($login) then <html> <body> The protected page goes here. You are {xdmp:get-current-user()} </body> </html> else xdmp:redirect-response("login.xqy")
The try-ip-login
function is defined in login-routine.xqy
. It is used to determine if the user can be automatically logged in based on the client IP address. If the user cannot be logged in automatically, he is redirected to a login page called login.xqy
where he has to log in explicitly. See Using Custom Login Pages for example code for login.xqy
.
try-ip-login
:login-routine.xqy
and place the file in the Modules
directory within the MarkLogic Server program directory. You create an amp for try-ip-login
in login-routine.xqy
in the next code sample. For security reasons, all amped functions must be located in the specified Modules
directory or in the Modules
database for the App Server.login-routine.xqy
:xquery version "1.0-ml" module "http://widget.com" declare namespace widget ="http://widget.com"
define function try-ip-login()as xs:boolean { let $ip := xdmp:get-request-client-address() return if(compare($ip,"127.0.0.1") eq 0) then (:local host:) xdmp:login("localuser",()) else if(starts-with($ip,<approved-subnet>)) then xdmp:login("site-user",()) else false() }
If the user is accessing the application from an approved IP address, try-ip-login
logs in the user with username local-user
or site-user
as appropriate and returns true
. Otherwise, try-ip-login
returns false
.
In the code snippet above, the empty sequence () is supplied in place of the actual passwords for local-user
and site-user
. The pre-defined xdmp-login
execute privilege grants the right to call xdmp:login without the actual password. This makes it possible to create deployments in which users can be automatically logged in without storing user passwords outside the system.
try-ip-login
:login-role
. xdmp-login
execute privilege to login-role
. The xdmp-login
privilege gives a user of the login-role
the right to call xdmp:login for any user without supplying the password. An amp temporarily assigns additional role(s) to a user only for the execution of the specified function. The amp above gives any user who is executing try-ip-login() the login-role
temporarily for the execution of the function.
In this example, default.xqy
is executed as nobody
, the default user for the application. When the try-ip-login
function is called, the nobody
user is temporarily amped to the login-role
. The nobody
user is temporarily assigned the xdmp:login execute privilege by virtue of the login-role
. This enables nobody
to call xdmp:login in try-ip-login
for any user without the corresponding password. Once the login process is completed, the user can access the application with the permissions and privileges of local-user
or site-user
as appropriate.
local-user
and site-user
can access all the pages and functions within the application. application-user-role
.application-privilege
. Add this privilege to the application-user-role
.application-user-role
to local-user
and site-user
.try { xdmp:security-assert("application-privilege","execute") ... } catch($e) { xdmp:redirect-response("login.xqy") }
if(not(xdmp:has-privilege("application-privilege","execute"))) then ( xdmp:redirect-response("login.xqy") ) else ()
This ensures that only the user who has the application-privilege
by virtue of his role can access these protected pages.
In this scenario, assume that you want to implement a security model that enables your users to run any XQuery code stored in the modules database for a specific App Server with read-only permissions on all documents in the database.
Reviewing the MarkLogic security model, recall that users do not have permissions, documents have permissions. And permissions are made up of a role paired with a capability. Additionally, execute privileges protect code execution and URI privileges protect the creation of documents in a specific URI namespace. This example shows one way to implement the read-only user and is devided into the following parts:
To set up this example scenario, perform the following steps, using the Admin Interface:
ReadsStuff
.ReadOnly
and grant this user the ReadsStuff
role.WritesStuff
and grant this role the ReadsStuff
role.WritesStuff
role the any-uri
privilege, as well as any execute privileges needed for your application code.LoadsStuff
and grant this user the WritesStuff
role. When you load documents, load them as the LoadsStuff
user and give each document an update and insert permission for the WritesStuff
role and a read permission for the ReadsStuff
role.Here is sample code to create a set of permissions to do this as on option to either the xdmp:document-insert
function or the xdmp:document-load
function:
(xdmp:permission("ReadsStuff", "read"), xdmp:permission("WritesStuff", "insert"), xdmp:permission("WritesStuff", "update"))
An alternative to specifying the permissions when you load documents is to assign default permissions to the LoadsStuff
user or the WritesStuff
role.
If you are running a URL rewriter (or an error handler), you need to give the ReadsStuff
role to the nobody
user or whichever user is the default user for your App Server. When the URL rewriter executes, the request has not yet been authenticated, so it runs as the default user. The default user is nobody
unless you have specified a different default for your App Server. The best practice is to create another role, for example my-app-user
and add an execute permission for the URL rewriter and your error handler (if any) for the my-app-user
role. This is better because you do not want the nobody
user to have access to your database.