Permissions
This page presents the web application permission layer of BaseX, which can be used along with RESTXQ.
Non-trivial web applications require a user management: Users need to log in to a web site in order to get access to protected pages; Depending on their status (role, user group, …), they can be offered different views; etc. The light-weight permission layer simplifies permission checks a lot:
- Permission strings can be attached to RESTXQ functions.
- With security functions, you can ensure that access to RESTXQ functions will only be granted to clients with sufficient permissions.
Preliminaries
All permission annotations are assigned to the http://basex.org/modules/perm
namespace, which is statically bound to the perm
prefix.
Annotations
Permission Strings
With the %perm:allow
annotation, one or more permission strings can be attached to a RESTXQ function:
(:~ Login page (visible to everyone). :)
declare
%rest:path("/")
%output:method("html")
function local:login() {
<html>
Please log in:
<form action="/login-check" method="post">
<input name="name"/>
<input type="password" name="pass"/>
<input type="submit"/>
</form>
</html>
};
(:~ Main page (restricted to logged in users). :)
declare
%rest:path("/main")
%output:method("html")
function local:main() {
<html>
Welcome to the main page:
<a href='/main/admin'>admin area</a>,
<a href='/logout'>log out</a>.
</html>
};
(:~ Admin page. :)
declare
%rest:path("/main/admin")
%output:method("html")
%perm:allow("admin")
function local:admin() {
<html>
Welcome to the admin page.
</html>
};
The permission strings may denote ids, users, user groups, applications, or any other realms. It is completely up to the user which strings are used, and which functions will be annotated. In the given example code, only the last function has a %perm:allow
annotation.
Checking Permissions
Functions that are marked with %perm:check
are so-called Security Functions. These functions will be invoked before the actually requested function will be evaluated. Two arguments can be specified with the annotation:
- A path can be specified as first argument:
/
is assigned instead.- A variable can be specified in the second argument. A map with the following keys will be bound to that variable:
allow
: Permission strings attached to the requested function; may be empty.path
: Original path of the client request.method
: Method of the client request (GET
, POST
, …).authorization
: Value of the HTTP Authorization header string; may be empty.An example:
import module namespace Session = 'http://basex.org/modules/session';
(:~
: Global permission checks.
: Rejects any usage of the HTTP DELETE method.
:)
declare %perm:check %rest:DELETE function local:check() {
error((), 'Access denied to DELETE method.')
};
(:~
: Permission check: Area for logged-in users.
: Checks if a session id exists for the current user; if not, redirects to the login page.
:)
declare %perm:check('/main') function local:check-app() {
let $user := Session:get('id')
where empty($user)
return web:redirect('/')
};
(:~
: Permissions: Admin area.
: Checks if the current user is admin; if not, redirects to the main page.
: @param $perm map with permission data
:)
declare %perm:check('/main/admin', '{$perm}') function local:check-admin($perm) {
let $user := Session:get('id')
where not(user:list-details($user)/@permission = $perm?allow)
return web:redirect('/main')
};
Some notes:
- If several permission functions are available that match the user request, all of them will be called one after another. The function with the shortest path will be called first. Accordingly, in the example, if the
/main/admin
URL is requested, all three security functions will be run in the given order. - If a security function raises an error or returns any XQuery value (e.g. a redirection to another web page), no other functions will be invoked. This means that the function that has been requested by the client will only be evaluated if all security functions yield no result and no error.
- As shown in the first function, the
%perm:check
annotation can be combined with other RESTXQ annotations, excluding%rest:path
and%rest:error
. - In the example, it is assumed that a logged in user is bound to a session variable (see further below).
The permission layer was designed to provide as much flexibility as possible to the web application developer: It is possible to completely work without permission strings, and realize all access checks based on the request information (path, method, and properties returned by the Request Functions). It is also possible (but rather unhandy) to accompany each RESTXQ function by its individual security function. The bare minimum is a single %perm:check
function. Without this function, existing %perm:allow
annotations will be ignored.
Authentication
There are numerous ways how users can be authenticated in a web application (via OAuth, LDAP, …). The approach demonstrated on this page is pretty basic and straightforward:
- A login HTML page allows you to enter your credentials (username, password).
- A login check function checks if the typed in data matches one of the database users. If the input is valid, a session id will be set, and the user will be redirected to the main page. Otherwise, the redirection points back to the login page.
- A logout page deletes the session id.
The following lines of code complete the image:
declare
%rest:path("/login-check")
%rest:query-param("name", "{$name}")
%rest:query-param("pass", "{$pass}")
function local:login($name, $pass) {
try {
user:check($name, $pass),
Session:set('id', $name),
web:redirect("/main")
} catch user:* {
web:redirect("/")
}
};
declare
%rest:path("/logout")
function local:logout() {
Session:delete('id'),
web:redirect("/")
};
For a full round trip, check out the source code of the DBA that is bundled with BaseX.
Changelog
Version 9.1- Added:
authorization
value in permissions map variable
- Added: New permission layer added.