Broadcast on Broadcast off
The Documentation for Project Zero has moved. Please update your bookmarks to: http://www.projectzero.org/documentation/
Table of
Contents...
Hide
 

To do list part IV - Security

We have our todo list application in good shape now, except there is no security yet. Let's look at what Zero provides in the realm of securing our application and modify our application from the hardcoded user johndoe to authenticating the user and controlling access.

The following concepts are covered in this tutorial:

  • Security configuration
  • Authentication and authorization

Security configuration

So far, we have done nothing to secure our application. Once again, we go back to the configuration file for a Zero application, zero.config, to specify how we intend to secure the to do list application and use the following steps. For this tutorial, we will use the project we created in the previous tutorial and build upon the same project to add security.

  1. Stop the simpletodo3 application.
  2. Double click zero.config.
  3. Add the following content to the zero.config file:
    @include "${/config/dependencies/zero.core}/config/security/rule.config"{
       "uri": "/.*",
       "authType" : "Basic",
       "groups" : ["ALL_AUTHENTICATED_USERS"]
    }
    
  4. Save the zero.config file

You are already familiar with the format used in the zero.config file to configure a Zero application. Let's look at the security configuration that we just added:

uri
Defines what is to be protected and defines the protected resources.
authType
Can either be Basic for basic authentication or Form for form based authentication.
groups
Defines the groups of users who are allowed to access the protected resources.

For more information on security configuration, see the Authentication and Authorization article.

Our application is secure and we have specified that any restful resource should only be accessed by someone whose identity has been verified. How do we verify someone's identity? We need a list of users who are authorized to access the application. Let's create such a list using the following steps:

  1. Select the config folder.
  2. Click File -> New -> File and name the new file zero.users as shown on the following screen.

    ZeroUsers.jpg

  3. Add the following content to the zero.users file:
    johndoe:bed128365216c019988915ed3add75fb:todogroup
    
  4. Save the zero.users file.

Working with the zero.users file

This file serves as a simple user registry that contains the username, encrypted password and the group(s) that the user belongs to. Currently, we have johndoe as the user, with passw0rd as the password, who belongs to the group todogroup. You might be wondering how we got from passw0rd to bed128365216c019988915ed3add75fb? That, and more, is explained in the file based user service article. There, you will also find instructions on how to create user accounts and generate your own user registry file.

Zero supports a file based user registry as well as an LDAP based directory for storing the information about user accounts. For more information on how to use these services, see the User Service article.

Authentication and authorization

The application is only accessible by johndoe now. In our earlier tutorial, we had hardcoded the user to always be johndoe. Let's change that now using the following steps:

  1. Create a file under /app/resources and name it todos.bnd
  2. Add the following contents to the file todos.bnd :
    owners/todos
    This line allows us to specify the ID of the owner on the URI, eg /owners/johndoe/todos. With the security enabled, only the owner can manipulate his or her todo list. The security will check the authenticated user ID against this owner ID. For more information on bnd, visit the resource handling article.
  3. Open the /app/resources/todos.groovy file for editing.
  4. Replace the content of the file with the following:
    /*
    * ============================================================================
    * (C) Copyright IBM Corporation 2006, 2007.  All Rights Reserved.
    * 
    * You may only copy, modify, and distribute these samples internally.
    * These samples have not been tested under all conditions and are provided
    * to you by IBM without obligation of support of any kind.
    *
    * IBM PROVIDES THESE SAMPLES "AS IS" SUBJECT TO ANY STATUTORY WARRANTIES THAT
    * CANNOT BE EXCLUDED. IBM MAKES NO WARRANTIES OR CONDITIONS, EITHER EXPRESS
    * OR IMPLIED, INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR
    * CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
    * NON-INFRINGEMENT REGARDING THESE SAMPLES OR TECHNICAL SUPPORT, IF ANY.
    * ============================================================================
    */
    def onList() {
            logger.INFO {"onList called"};
            def ownerId = request.params.ownersId[];
            def me = request.subject['remoteUser'];
            
            logger.INFO {"authenticated remote user: $me"};
            logger.INFO {"user ID on the URL: $ownerId"};
            
            if (!ownerId.equals(me)) {
                    request.status = HttpURLConnection.HTTP_FORBIDDEN;
            } else {
                    sendJSONData(zero.data.groovy.Manager.create('simpletodo'), me);
            }
    }
    
    def onCreate() {
            logger.INFO {"onCreate called"};
            
            def ownerId = request.params.ownersId[];
            def me = request.subject['remoteUser'];
            
            if (!ownerId.equals(me)) {
                    request.status = HttpURLConnection.HTTP_FORBIDDEN;
            } else {
                    def item = request.params.item[];
                    def priority = request.params.priority[];
                    logger.INFO {"Item to create: $item" };
                    if (item != null && item.size() > 0) {
                            def dm = zero.data.groovy.Manager.create('simpletodo');
                            def id = dm.insert("INSERT INTO todos (owner, item, priority, status) VALUES($me, $item, $priority, 0)", ['id']);
                            request.status=HttpURLConnection.HTTP_CREATED
                            request.headers.out.Location=getRequestedUri(false) +'/'+id
                            sendJSONData(dm, me);
                    }else {
                            request.status = HttpURLConnection.HTTP_NO_CONTENT;
                    }
            }
    }
    
    def onDeleteCollection() {
            logger.INFO {"onDeleteCollection not used in the app"};
    }
    
    def onRetrieve() {
            logger.INFO {"onRetrieve called"};
            
            def todosId = request.params.todosId[];
            def ownerId = request.params.ownersId[];
            def me = request.subject['remoteUser'];
            
            logger.INFO {"authenticated remote user: $me"};
            logger.INFO {"user ID on the URL: $ownerId"};
            
            if (!ownerId.equals(me)) {
                    request.status = HttpURLConnection.HTTP_FORBIDDEN;
            } else if (todosId == null) {
                    request.status = HttpURLConnection.HTTP_BAD_REQUEST;
            } else {
                    def dm = zero.data.groovy.Manager.create('simpletodo');
                    def data = dm.update("SELECT id, item, priority, status FROM todos  WHERE owner = ? AND id = ?", ownerId, todosId);
                    request.view="JSON";
                    request.json.output = data;
                    zero.core.views.ViewEngine.render();
            }
    }
    
    def onUpdate() {
            logger.INFO {"onUpdate called"};
            
            def todosId = request.params.todosId[];
            def ownerId = request.params.ownersId[];
            def me = request.subject['remoteUser'];
            logger.FINEST {"todosID = $todosId   ownerID = $ownerId  owner = $me"};
            
            if (!ownerId.equals(me)) {
                    request.status = HttpURLConnection.HTTP_FORBIDDEN;
            } else if (todosId == null) {
                    request.status = HttpURLConnection.HTTP_BAD_REQUEST;
            } else {
                    def status = request.params.status[];
                    def dm = zero.data.groovy.Manager.create('simpletodo');
                    logger.FINEST {"UPDATE todos SET status = $status WHERE owner = $me AND id = $todosId"};
                    dm.update("UPDATE todos SET status = $status WHERE owner = $me AND id = $todosId");
            }
    }
    
    def onDelete() {
            logger.INFO {"onDelete called"};
            def todosId = request.params.todosId[];
            def ownerId = request.params.ownersId[];
            def me = request.subject['remoteUser'];
            if (!ownerId.equals(me)) {
                    request.status = HttpURLConnection.HTTP_FORBIDDEN;
            } else if (todosId == null) {
                    request.status = HttpURLConnection.HTTP_BAD_REQUEST;
            } else {
                    def dm = zero.data.groovy.Manager.create('simpletodo');
                    dm.update("DELETE FROM todos WHERE owner = ? AND id = ?", [me, todosId]);
            }
    }
    
    def sendJSONData(dm, me) {
            data = dm.queryList("SELECT id, item, priority, status FROM todos WHERE owner = $me ORDER BY priority");
            logger.FINEST {"sending json data: "+data}; 
            
            request.headers.out."Content-Type"="application/json"
            request.view="JSON";
            request.json.output = data;
            render();
    }

Working with the todos.groovy file

Lets talk about what changes in todos.groovy. We first get the ownerId, which is the id of the person who owes the to do list that we are trying to access, and then we get the me, which is the remote user information that was provided when the user was challenged, and given the opportunity to provide login information. If they match, we go ahead and process the request, otherwise, we set the appropriate response code. Also, we have removed the hardcoded user johndoe and updated the code to reflect the logged in user as the owner when we update the to do list.

  1. Open the /app/public/todo.gt file for editing.
  2. Add the following contents to the todo.gt file:

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <!--
    /*
    * ============================================================================
    * (C) Copyright IBM Corporation 2006, 2007.  All Rights Reserved.
    * 
    * You may only copy, modify, and distribute these samples internally.
    * These samples have not been tested under all conditions and are provided
    * to you by IBM without obligation of support of any kind.
    *
    * IBM PROVIDES THESE SAMPLES "AS IS" SUBJECT TO ANY STATUTORY WARRANTIES THAT
    * CANNOT BE EXCLUDED. IBM MAKES NO WARRANTIES OR CONDITIONS, EITHER EXPRESS
    * OR IMPLIED, INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR
    * CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
    * NON-INFRINGEMENT REGARDING THESE SAMPLES OR TECHNICAL SUPPORT, IF ANY.
    * ============================================================================
    */
    -->
    
    <html>
            <head>
                    <title>Todo List</title>
                    <link REL=StyleSheet HREF="todo.css" TYPE="text/css">
                    
                    <script type="text/javascript" 
                    src="<%= getRelativeUri("/dojo/dojo.js.uncompressed.js") %>"
                    djConfig="parseOnLoad: true, isDebug: false">
                    </script>
                    
                    <script type="text/javascript">
                    dojo.require("dojo.parser");
                    dojo.require("dijit.TitlePane");
                    
                    var owner = "<%=request.subject['remoteUser']%>" 
                    var ownerURL = '<%= getRelativeUri("/resources/owners") %>';
                    
                    function onError(response) {
                    alert(response);
                    }
                    
                    
                    
                    function displayItems(response) {
                    var todolist = "";
                    var dellist = "";
                    var todolistSize = 0;
                    var dellistSize = 0;
                    for (var i=0; i<response.length; i++) {
                    var id = response[i].id;
                    var desc = response[i].item;
                    var priority = response[i].priority;
                    var status = (response[i].status == 0 ? false : true);
                    var class = 'undoneItem';
                    if (status) {
                    class = 'doneItem';
                    }
                    if (0 == priority) {
                    priority = "high";
                    } else if (1 == priority) {
                    priority = "medium";
                    } else { 
                    priority = "low";
                    }
                    var checked = (status ? "checked" : "");
                    var name = "todo"+i;
                    var item = '<input type="checkbox" name="'+name+
                    '" class="' + class + '" onclick="moveTodo('+name+')" value="'+id+'"'+checked+'>' +
                    '<SPAN CLASS="'+priority+'">' + desc + '</SPAN> <BR>';
                    //if status is true (checked), the item goes to delList
                    //otherwise, it remains in the todo list
                    if (status) { 
                    item.class="doneItem"
                    dellist += item;
                    dellistSize++;
                    } else {
                    item.class="undoneItem"
                    todolist += item;
                    todolistSize++;
                    }
                    }
                    var todoPane=dijit.byId("todoPane");
                    todoPane.setTitle("Your todo items: "+todolistSize);
                    var delPane=dijit.byId("delPane");
                    delPane.setTitle("Your completed items: "+dellistSize);
                    
                    document.getElementById("todoList").innerHTML = todolist;
                    document.getElementById("delList").innerHTML = dellist;
                    }
                    
                    function addTodo() {
                    var data = {item: document.todoForm.item.value, 
                    priority: document.todoForm.priority.selectedIndex};
                    
                    var req = dojo.xhrPost({
                    url: ownerURL + '/' + owner + '/todos',
                    content:  data,
                    sync: false
                    });
                    req.addCallbacks(getTodos, onError);
                    document.todoForm.reset();
                    
                    }
                    
                    function deleteTodos() {
                    var list = dojo.query('input.doneItem');
                    var size = list.length - 1;
                    for (var i=0; i<size; i++) {
                    var req = dojo.xhrDelete({
                    url: ownerURL + '/' + owner + '/todos/' +list[i].value,
                    sync: true
                    });
                    }
                    
                    // do this as two separate requests so that we can
                    // invoke the callback only when all deletes are finished
                    var req = dojo.xhrDelete({
                    url: ownerURL + '/' + owner + '/todos/' +list[size].value,
                    sync: false
                    });
                    
                    req.addCallbacks(getTodos, onError);
                    }
                    
                    
                    function moveTodo(checkbox) {
                    var req = dojo.xhrPut({
                    url: ownerURL + '/' + owner + '/todos/'+ checkbox.value,
                    content:  {status: (checkbox.checked? 1: 0)},
                    sync: false
                    });
                    req.addCallbacks(getTodos, onError);
                    }
                    
                    function getTodos() {
                    
                    
                    var req = dojo.xhrGet({
                    url: ownerURL + '/' + owner + '/todos',
                    handleAs: 'json-comment-optional',
                    sync: false
                    });
                    req.addCallbacks(displayItems, onError);
                    
                    }
                    
                    dojo.addOnLoad(function() {
                    getTodos();
                    });
                    
                    </script>
            </head>
            <body>
                    <div dojotype="dijit.TitlePane" title="Your todo items:"
                    id="todoPane">
                    <form name="todoForm">
                    <div id="todoList">
                    </div>
                    <br>
                    <input type="text" id="item"> <select name=
                    "priority">
                    <option value="1">
                    Priority one
                    </option>
                    
                    <option value="2">
                    Priority two
                    </option>
                    
                    <option value="3">
                    Priority three
                    </option>
                    </select> <input type="button" onclick="addTodo();"
                    value="Add">
                    </form>
                    </div>
                    
                    <div dojotype="dijit.TitlePane" title=
                    "Your completed items:" id="delPane" open="false">
                    <form name="delForm">
                    <div id="delList">
                    </div>
                    <br>
                    <input type="button" onclick="deleteTodos();"
                    value="Delete">
                    </form>
                    </div>
            </body>
    </html>

Working with the todo.gt file

Let's discuss what changes we have made to the todo.gt file. First, we obtain the logged in user and store in as owner. Any time that the to do list is accessed, we need to access it using the /resources/owners//todos format. This URI format is very readable: We are accessing the todo list for a particular user, and only that user can manipulate his or her todo items. For more information, visit the resource handling article.

Running the application

To run the application, use the following steps:

  1. Right-click the project and click Run As -> Project Zero Application.
  2. Using your browser, go to the http://localhost:8080/todo.gt site.
  3. Provide your username and password (For this tutorial we used johndoe as the username and passw0rd as the password as shown on the following screen:

    BasicChallenge.jpg

And... you are finished! You have created a rich and secure to do list Zero application that uses a database and a user registry. That was just too easy. Go ahead, check out Project Zero in depth and explore the possibilities !!!

<<< Previous

r41 - 04 Dec 2007 - 20:35:41 - ngawor
Syndicate this site RSS ATOM
Copyright 2007 © IBM Corporation | Privacy | Terms of Use | About this site