RocketSled Tutorial 6: The Pilot

Author: David Grinton thegrinch@workingsoftware.com.au

Table of Contents

Introduction

Welcome to RocketSled Tutorial 6.

In this tutorial we will add the Pilot package and use it to provide access control to our existing Handlers.

The Pilot controls access as follows:

Access is controlled through Access Levels, Groups, Users and Site Functions.

Each of the Handlers we have previously written corresponds to a Site Function. A Site Function has an Access Level assigned to it, which restricts access to that Site Function to Users who are members of a Group with a sufficient Access Level.

Access tables

First we need to add some tables to our database:

a) If you have sufficient access the Pilot will automatically create the required tables if they do not exist.

b) If the Pilot is not able to automatically create the tables, you can do it manually with:

$ mysql -u root --pass=PASS pastries < WEBROOT/yourdir/packages/pilot/pilot.sql

pilot.sql contains SQL statements to add tables to store Access Levels, Groups, Users and Site Functions, and creates Access Levels for Root (highest access) and Public (lowest access). It also creates a Group for system developers with Root access.

PilotApplication

Now we will change the MyApplication class so that it extends PilotApplication:

<?php
   class MyApplication extends PilotApplication
   {
      function MyApplication()
      {
         $this->Application();
      }
   }
?>

Login form

Now we need to add the login RSML and frm documents:

In WEBROOT/yourdir/packages/my/login.rsml.php add:

<fragment>
    <div id="pageContent">
        <view-form name="Login" />
    </div>
</fragment>

... and in WEBROOT/yourdir/packages/my/login.frm.php add:

<form handler="Home" name="Login">
    <form-error name="user_email">
        <li class="errorItem" id="user_emailErrorItem">MESSAGE</li>
    </form-error>
    <li id="user_emailFormItem">
        <label id="user_emailFormLabel" for="user_email">Email:</label>
        <text-line name="user_email">
            <validation type="REQUIRED">
                <error-message>You need to enter your email in order to
                login</error-message>
            </validation>
            <validation type="EMAIL">
                <error-message>The email address you entered appears not to
                be valid. Please try again</error-message>
            </validation>
        </text-line>
    </li>
    <form-error name="user_password">
        <li class="errorItem" id="user_passwordErrorItem">MESSAGE</li>
    </form-error>
    <li id="user_passwordFormItem">
        <label id="user_passwordFormLabel" for="user_password">Password:
        </label>
        <password name="user_password">
            <validation type="REQUIRED">
                <error-message>You need to enter your password in order to
                login</error-message>
            </validation>
        </password>
    </li>
    <li id="loginFormItem">
        <submit name="login" value="Login" />
    </li>
    <li id="redirectFormItem">
        <hidden name="redirect"/>
    </li>
</form>

Note the <hidden> tag at the bottom, we will use this to store the Handler the user attempted to access before they were redirected to the Home Handler (where applicable).

A well behaved login scheme

Next we will modify the Home class to display a login form for users who are not logged in, and redirect to the ListPastries (or whatever they tried before logging in) Handler for users who are logged in.

Edit home.class.php as follows:

<?php
    class Home extends Handler
    {
        function Home($name = '',$description = '')
        {
            $this->Handler($name,$description);
        }
 
        public function performHandlerTasks()
        {
            if(Application::formPosted())
            {
                if(Form::load('my.Login')->validate())
                {
                    try
                    {
                        PilotApplication::login(Application::param('user_email'),
                            Application::param('user_password'));
                        if ($r = Application::param('redirect'))
                            Application::redirectWithoutPost($r);
                        else
                            Application::redirectWithoutPost('AddPastry');
                    }
 
                    catch(UserNotFoundException $exc)
                    {
                        FormErrors::add('user_email','No user with that email is
                        registered');
                    }
 
                    catch(InvalidPasswordException $exc)
                    {
                        FormErrors::add('user_email','The password was
                        incorrect');
                    }}}
 
            else if(Application::param('logout'))
                PilotApplication::logout();
        }
 
        public function display()
        {
            $disp = Display::current();
            $disp->setTitle('Home Page');
            $form = Form::load('my.Login');
            $form->setInputValue('redirect',Application::param('redirect'));
            $disp->addForm($form);
            $disp->addView('page_content','my.Login');
            $disp->displaySiteTemplate();
        }
    }
?>

First assume that the form hasn't been posted and take a look at display(). Note the line $form->setInputValue()('redirect', Application::param('redirect')); which picks up any Handler we attempted to access before logging in and adds it to the form.

Next look at performHandlerTasks(). We use Application::formPosted() to check if a form has been posted, if so we load the Login form and attempt to validate it. Once validated, we try to login. Any problems with the login will throw exceptions which will prevent the Application::redirectWithoutPost() lines from executing, meaning the display() method will be called again and the form will be shown with the errors set with FormErrors::add(). These errors are added manually to the FormErrors system. When a <validation> node fails, it uses the same system to populate the <form-error> nodes in your FormML markup.

The Setup handler

Now we will extend the Pilot's setup handler so that we can set some access levels and add users.

Add WEBROOT/yourdir/packages/my/my_setup.class.php with the following code:

<?php
    class MySetup extends PilotSetup
    {
        function MySetup($name = '',$description = '')
        {
           $this->PilotSetup($name,$description);
        }
 
        public function display()
        {
           $disp = Display::current();
           $disp->setTitle('My System Setup');
           $this->setupDisplay($disp);
           $disp->addView('page_content','my.MySetup');
           $disp->displaySiteTemplate();
        }
    }
?>

In the above code we simply extend the PilotSetup class as it already does most of the work for us.

... we will also need WEBROOT/yourdir/packages/my/my_setup.rsml.php:

<fragment>
    <h2>Setup for My Package</h2>
    <view name="system_setup" />
</fragment>

The <view> tag above refers to a view defined in the PilotSetup class (for more details see PilotSetup::setupDisplay()), this view handles all of the forms required for access configuration.

You may wish to examine the PilotSetup handler, particularly the PilotSetup::setupDisplay() method - it would be possible to extend certain methods to provide a different interface to the access configuration.

Adding access levels

Now point a web browser at WEBROOT/yourdir/index.php and you should be redirected to the MySetup handler - the system behaves this way until we set up access levels for MySetup.

In the order specified:

Note that none of our Handlers provide a logout functionality, so at this point to log out you will have to visit WEBROOT/yourdir/reinitialise.php

Now try to visit index.php?h=ListPastries - this should display without requesting a login.

Finally visit index.php?h=Home then login - you should be redirected to the AddPastry handler which is what we specified should be the default handler after logging in.

Nicer errors

Just as a demonstration, visit WEBROOT/yourdir/reinitialise.php (this is to clear the session data effectively logging us out), now point your browser at WEBROOT/yourdir/?h=NotARealHandler, then try to visit index.php?h=MySetup - both of these should result in an ugly exception stating that we do not have authority to access the functions (even though one of them does not exist!).

To improve this behaviour somewhat, we can override the Application::handleAccessDenied() method in MyApplication as follows:

public function handleAccessDenied()
{
   if($h = Application::current()->handlerClass())
   {
       if(class_exists($h)&&PilotApplication::userIsGuest())
       {
          // logging in for the first time
          // set redirect param so that user gets the page they wanted
          // after login
          Application::setUrlParam('redirect',$h);
          Application::redirect('Home');
       }
       else if(class_exists($h))
       {
           //logged in but no access, display some sort of unauthorised
           //message
           echo "insert unauth msg here";
       }
       else
       {
           //junk in the URL or an old one ... custom 404
           echo "insert 404 here";
       }
   }
}

As per the comments, when a non-public Handler is requested it will redirect to the Home Handler so the user can login. Before redirecting, it calls Application::setUrlParam() to add the 'redirect' value to the URL, we will pick this up in the Home Handler and add it to the login form so that the user can be redirected to the desired page after logging in.

Also note the other branches which provide places to add code to handle other cases.

Now if you try to access WEBROOT/yourdir/?h=NotARealHandler you will see the "insert 404 here" message (which ideally would be replaced with a redirect to a 404 Handler).

If you attempt to access WEBROOT/yourdir/?h=MySetup you should be redirected to the Home Handler, and after logging in to the MySetup Handler.


Generated on Wed Oct 22 18:48:19 2008 for RocketSled by  doxygen 1.5.7