XRX/User Manager

< XRX

Motivation

edit

You want to manage a group of users of your web site, validate their roles on the site, and track user-dependent information. This application will manage a list of users, and associate with the users the number of attempted logins, their session timeout interval, and the roles they are assigned.

Method

edit

Our application will use Role-Based Access Control (RBAC). We will use several functions to perform user management:

 xmldb:login($collection, $user, $pass) will log the user into the system and verify they have write access to a collection.

Note that to log the user out, we will change the login to the user "guest."

 xmldb:get-current-user() will return the user-id of the current user.

We will also need to add a new function that will check to see if a given user has a role.

 auth:has-role(xmldb:get-current-user(), $role) as xs:boolean check to see if a user has a given role.

This will return a true() if the current user has a given role.

User Data

edit

The following is an example of a user's information. Note that we do not store the password in this file. The eXist system is used to store the user's password.

<user>
    <id>47</id>
    <user-id>jdoe</user-id>
    <person-given-name>John</person-given-name>
    <person-family-name>Doe</person-family-name>
    <account-active-indicator>true</account-active-indicator>
    <max-login-retrys>5</max-login-retrys>
    <session-timeout-minutes>60</session-timeout-minutes>
    <roles>
        <role>ba</role>
        <role>ba-admin</role>
    </roles>
</user>

In the above example, the user jdoe has the role of ba and ba-admin which means the user is classified as a business analyst and also has the ability to administer the business analyst tools in the system.

So the following function:

  auth:has-role('jdoe', 'ba')

will return true()

Login Form

edit

The following is a simplified login form that will allow the user to enter a login and a password and perform an HTTP post operation. Note that this is different from the eXist standard login in that it does not place the password on the URL using an HTTP get. This information is placed in a log file can compromise the security of the system.

xquery version "1.0";
import module namespace style ='http://code.google.com/p/xrx/style' at '/db/xrx/modules/style.xqm';
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

let $user := xmldb:get-current-user()

(: this gets the entire input URL :)
let $get-query-string := request:get-query-string()

(: were are only interested in the portion after the return= :)
let $return-uri := substring-after($get-query-string, 'return=')

return
<html>
   <head>
      <title>Login</title>
      {style:import-css()}
   </head>
   <body>
      {style:header()}
      <h1>Login</h1>
      <p>Current User = {$user}</p>
      <form action="login.xq" method="post">
            <table class="login" cellpadding="5">
                <tr>
                    <th colspan="2" align="left">Please Login</th>
                </tr>
                <tr>
                    <td align="left">Username:</td>
                    <td><input name="user" type="text" size="20"/></td>
                </tr>
                <tr>
                    <td align="left">Password:</td>
                    <td><input name="pass" type="password" size="20"/></td>
                </tr>
            </table>
            <input name="return" type="hidden" value="{$return-uri}" size="20"/>
            <input type="submit" value="Login"/>
        </form>
        <p>We will return to the following URI on success: {$return-uri}</p>
      {style:footer()}
   </body>
</html>

Login Verification Script

edit

The following script takes incoming login data from an HTTP POST operation and performs a login. HTTP POST data arrives from the request:get-data() format in the following key-value-pair format:

user=jdoe&pass=mypass&return=/exist/rest/db/xrx/apps/test/view/view-item.xq&id=47

Note that we assume that the user-id is in the first key-value pair and the pass is in the second. If you want a more general interface you can scan for the correct key in all the incoming form key-value pairs.

xquery version "1.0";
import module namespace style ='http://code.google.com/p/xrx/style' at '/db/xrx/modules/style.xqm';
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

let $data := request:get-data()
let $tokens := tokenize($data, '&amp;')
let $user := substring-after($tokens[1], 'user=')
let $pass := substring-after($tokens[2], 'pass=')
let $return := substring-after($tokens[3], 'return=')

(: this is required to fix the incomming URI encodings :)
let $unescape-uri := request:unescape-uri($return, 'UTF-8')

(: this is required to put the first parameter deliminator ? back in place of the ampersand :)
let $fix-first-param := replace($unescape-uri, 'xq&amp;', 'xq?')
let $login-success-indicator := xmldb:login("/db", $user, $pass)

return
<html>
   <head>
      <title>Login Result</title>
      {style:import-css()}
   </head>
   <body>
      {style:header()}
      {style:breadcrumb()}
      <h1>Login Status</h1>
      {if ($login-success-indicator)
         then <p>Your Login Was Successful <a href="{$fix-first-param}">Return</a></p>
         else <p>Login Failed <a href="login-form.xq">Try Again</a></p>
      }
      You are logged in to the root database collection as {xmldb:get-current-user()}
      <br/>
      <a href="logout.xq">Logout</a>
      {style:footer()}
   </body>
</html>

Logout Script

edit

The following script logs you out of the system. Technically, it just logs you in as the user that has read-only access.


xquery version "1.0";
import module namespace style='http://mdr.crossflow.com/style' at '/db/crossflo/modules/style.xq';
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

let $null := xmldb:login("/db", "guest", "guest") 

let $user := xmldb:get-current-user()

return
<html>
   <head>
      <title>Logout</title>
      {style:import-css()}
      {style:breadcrumb()}
   </head>
   <body>
      {style:header()}
      <h1>You have been logged out.</h1>
      <p>User = {$user}</p>
      <a href="login-form.xq">Login</a>
      {style:footer()}
   </body>
</html

Adding an Authorization Function

edit

We will now create an XQuery module that performs an authentication check for a given user.

Role-Based Conditional Display of Edit Functions

edit

Many screens have links to Edit forms that allow users to change the data. You can conditionally display these Edit links by adding the following code to each page that would normally have an Edit link:

{if (auth:has-role(xmldb:get-current-user(), 'ba'))
   then
      <div>
         <a href="../edit/edit.xq?id={$id}">Edit Item</a>
         <a href="../edit/delete-confirm.xq?id={$id}">Delete Item</a>
      </div>
   else 
      <span>User {xmldb:get-current-user()} has no roles with edit privileges 
         <a href="{auth:login-uri()}">Login</a>
     </span>
}

Checking for Authenticated User

edit

You can also add a function in any page that has sensitive information or functions by checking that the user is logged in. This can be done with a single function called auth:validate-user()

  request:get-session-attribute("user") - this function will return the user name associated with the login session
  response:redirect-to($uri as xs:anyURI) - this function will redirect the user to another URI such as a login panel.

Note that typically you want to make it easy to allow the user to bounce back to the screen that they were on. To do this you frequently want to add a parameter to the login.xq XQuery that will redirect the user back to where they came from.

  response:redirect-to('/exist/rest/db/xrx/apps/user-manager/login.xq?return=/exist/rest/db/myapp/edit/edit.xq?id=47')

The following is a sample function that can be placed at the top of all pages that require user authentication:

declare function auth:validate-user() as xs:string {
   let $url:= request:get-url()
   let $param-names := request:parameter-names()
   let $param-string :=for $count in 1 to count($param-names)
   let $param := $param-names[$count]
   let $value := request:get-parameter($param, '')
   let $return := if ($count=1) then (concat('?',$param,'=',$value)) else (concat('&amp;',$param,'=',$value))
        return
            $return
   let $return-param :=fn:string-join($param-string,'')
   let $get-session-name :=request:get-session-attribute("user")
   let $login-uri := '/exist/rest/db/xrx/apps/user-manager/login.xq?url='
return
   if ($get-session-name) then
      ($get-session-name)
   else(
       response:redirect-to(xs:anyURI(concat($string,$url,fn:string-join($param-string,'')))))
};

Back: XForms Generator Next: Content Routing