Day3 - Creating Administrator's Interface

Summary: Today I will move administrator's interface into proper location, will set up password authentication for the front-end and will add ability for admin to change user passwords.

What we just did is the very basics of the User Interface - only the start. Since we got our client all excited let's listen to our further requirements or "user stories" how they properly call them in Agile Development.

Manager should be able to access and edit all this data through Administrator's interface. Users need to be able to use frontend to signup themselves. They also should be able to login with their email and password and see their own data. They should be able to see what they have rented currently and in the past.

Not mutch, but let's try to split it into the list of the further tasks we will focus on:

  • Users certainly need their own login. Need to add email / password fields into database and turn on authentication
  • Registration of new users is probably needed
  • When logged in, users should see video list and rent movies

Setting up Administrator's interface

Right now we have added just added the pages for how they are, but it would be probably a good idea to separate them into a separate interface only available to the managers. To do that, we will create new Application Class, which will have it's own authentication rules and its own pages, but would share models.

Create a completely new directory called "admin" and inside there place the following files:

admin/index.php
include '../atk4/loader.php';
$api=new Admin('rentaladmin','default');
$api->main();
admin/config.php
// You can also include ../config.php from here

$config['atk']['base_path']='../atk4/';
$config['dsn']='mysql://root:root@localhost/dvdrental'// use your own
$config['url_postfix']='';
$config['url_prefix']='?page=';
$config['auth']['key']='secret';
admin/lib/Admin.php
class Admin extends ApiFrontend {

    public 
$is_admin=true;
    
    function 
init(){
        
parent::init();
        
$this->dbConnect();

        
$this->addLocation('..',array(
                    
'php'=>array(
                        
'lib',
                        
'atk4-addons/mvc',
                        
'atk4-addons/billing/lib',
                        
'atk4-addons/misc/lib',
                        )
                    ))
            ->
setParent($this->pathfinder->base_location);

        
$this->add('jUI');
        
$this->js()
            ->
_load('atk4_univ')
            ->
_load('ui.atk4_notify')
            ;

        
// Allow user: "admin", with password: "demo" to use this application
        
$this->add('BasicAuth')->allow('admin','demo')->check();

        
$menu=$this->add('Menu',null,'Menu');
        
$menu->addMenuItem('Manager','mgr');
    }
    function 
page_index($p){
        
$this->api->redirect('mgr');
    }
}

after you are done, move page/mgr.php into admin/page/mrg.php. You should be able now to add /admin/ at the end of your URL and be able to access your brand new administration interface. Location engine will properly look for models in the main lib directory, but pages must be defined locally to avoid unauthorized access.

With admin pages out of the way, we can now create authentication for our front-end users. Before that, however, we would need to perform database migration (alters). and the best way to do so would be to use "dbupdate" techniques.

Adding password for users

Create file doc/dbupdates/rental-001-01.sql containing:

alter table customer add email varchar(255);
alter table customer add password varchar(255);

Next you would need to copy, or sym-link atk4/tools/update.sh into doc/ and execute it. If it complains about missing 'config.php', then copy config-defaults.php into config.php. You should see this in your terminal:

$ cd doc/
$ ln -s ../atk4/tools/update.sh .
$ ./update.sh 
cat: ../../../config.php: No such file or directory
Error: Can't read config file
$ cd ..
$ cp config-default.php config.php

$ cd doc/
$ ./update.sh 
* Applying updates on database 'project'
 rental-001-01.sql... ok
* Done
$ 
WAIT! No command-line please!

Next you need to update customer's models by defining new field "email". We specifically leave out password from the model. This way UI elements will not be able to access it.

// add into lib/Model/Customer.php, init():

$this->addField('email');

We would like to create our own way for authenticating. There are two classes - BasicAuth and SQLAuth. While you could just use SQLAuth which already supports authentication against data-base, for the sake of excercise, let's use BaseAuth instead. It's very important that you learn how to create your own objects in Agile Toolkit using inheritance instead of only using existing ones. Create file lib/RentalAuth.php. (not admin/lib/RentalAuth.php!):

class RentalAuth extends BasicAuth {
    
/** Initialize our auth. Set model and password encryption */
    
function init(){
        
parent::init();
        
$this->setModel('Model_Customer');
        
$this->getModel()->addField('password')->system(true);

        
$this->usePasswordEncryption('sha256/salt');
    }

    
/** Do not show form, simply redirect to index page, if not authorized */
    
function check(){
        if(!
$this->isLoggedIn()){
            
$this->api->redirect('/');
        }
    }

    
/** Password validation routine, now using the model. */
    
function verifyCredentials($email,$password){
        
$model $this->getModel()->loadBy('email',$email);
        if(!
$model->isInstanceLoaded())return false;
        if(
$password == $model->get('password')){
            
$this->addInfo($model->get());
            unset(
$this->info['password']);
            return 
true;
        }else return 
false;

    }
}

When verification takes place, if it is successful we use addInfo() function which will accumulate more information about currently-logged user. Once this information is memorized, it will be accessible through $auth->get() at all times while user is logged-in.

And finally admin should be able to set passwords. Let's first create the User Interface for that. We are going to enhance standard CRUD a little. First, you should remember that CRUD object actually relies on MVCGrid and MVCForm to do all of it's work. After initializing CRUD you can interact with those components directly too. We will add new column to our CRUD. Open file admin/page/mgr.php and replace line containing add('CRUD')->setModel('Customer') with the following code:

        $crud=$tabs->addTab('Customers')->add('CRUD');
        
$crud->setModel('Customer');

        if(
$crud->grid){
            
$crud->grid->addColumn('prompt','set_password');
            if(
$_GET['set_password']){
                
$auth $this->add('RentalAuth');
                
$model $auth->getModel()->loadData($_GET['set_password']);
                
//$enc_p = $auth->encryptPassword($_GET['value'],$model->get('email'));
                //no longer needed as of version 4.2. Agile Toolkit 4.2 it will automatically add hook into user model to encrypt password when saved, but that only applies to the api->auth->mode
                
$model->set('password',$_GET['value'])->update();
                
$this->js()->univ()->successMessage('Changed password for '.$model->get('email'))
                    ->
execute();
            }
        }

Initialization code is executed during the initial render but also when the button is clicked. However during button-click the GET argument will be passed and output is expected to be JavaScript. The "prompt" column also will provide us with the "value" through the GET argument, which we will encrypt and save into database. Finally, we execute a chain to produce JavaScript action.

Password is common functionality. Isn't it a lot to do for simple functionality?

Test this

You should be able now to open /admin/ section in your browser, login with "admin" / "demo" and see the "Set Password" button in your user management section. Clicking on that button should ask you for a password, which should be then saved into a database encrypted.