Archive for the ‘howto’ Category

Using sfDoctrineGuardUser’s external authentication

0 Comments

sfDoctrineGuardUser has a neat ability to allow you to use an external (the examples are all for LDAP, but would work for anything) authentication process for authenticating your users.

This is really helpful, assuming that you don’t mind manually adding all users in the LDAP directory to the sf_guard_user table. This is because the way that the plugin determines if you are authenticated in sfGuardValidatorUser::doClean contains this

// user exists?
if ($username &&
    $user = Doctrine::getTable('sfGuardUser')->retrieveByUsername($username))
{
      // password is ok?
      if ($user->getIsActive() && $user->checkPassword($password))
      {
        return array_merge($values, array('user' => $user));
      }
}

The user is retrieved from the database before anything else happens.
Unfortunately, that means that if the username doesn’t exist in the local database, you won’t ever get to the point where it will check against your fancy external authentication code.

This post is about how to resolve that issue.

Firstly, you need to change the form that is used for rendering the sgGuardAuth signin action. Do this by adding the following line to your app.yml under “all”

sf_guard_plugin_signin_form: myAuthSigninForm

as described on the symfony docs

Then you need to create that file (I put it in /apps/frontend/lib) and overwrite configure so that you can set it to use your custom validator

class sfGuardCustomFormSignin extends sfGuardFormSignin
{
    public function configure(){
        parent::configure();
        $this->validatorSchema->setPostValidator(new sfGuardValidatorCustomUser());
    }
}

This will allow you to use the custom validator that you are going to put in a library directory somewhere.

I put it here:
apps/frontend/modules/sfGuardAuth/lib/sfGuardValidatorCustomUser.class.php

This validator should extend sfGuardValidatorUser and you will need to overwrite doClean. In the example below the highlighted section is the code that we added to the doClean method.

class sfGuardValidatorCustomUser Extends sfGuardValidatorUser
{
protected function doClean($values)
  {

    $username = isset($values[$this->getOption('username_field')]) ? $values[$this->getOption('username_field')] : '';
    $password = isset($values[$this->getOption('password_field')]) ? $values[$this->getOption('password_field')] : '';

    //This is the authentication code that you want to replace it with
    if($user = myAuth::authenticate($username, $password)) {
        return array_merge($values, array('user' => $user));
    }

    if ($this->getOption('throw_global_error'))
    {
      throw new sfValidatorError($this, 'invalid');
    }

    throw new sfValidatorErrorSchema($this,
      array($this->getOption('username_field') =>
            new sfValidatorError($this, 'invalid')));
  }
}

Then all you need to do is to build out your custom authentication mechanism.

I have mine creating or updating the user if it doesn’t exist, because that makes it easy to keep sfDoctrineGuardUser happy.

The actual authentication mechanism exists in another class altogether.

class svExternalAuth
{
    public static function authenticate($username, $password){
        if( svCustomAuth::authenticate($username, $password) ) {
            return self::cleanUser($username, $password);
        }
    }

    //create and or update
    protected static function cleanUser($username, $password){
        $user = self::getUser($username);
        $user->setPassword($password);
        $user->setIsActive(true);
        //set other user aspects
        $user->save();
        return $user;
    }

    protected static function getUser($username){
        if($user = Doctrine::getTable('sfGuardUser')->
                   retrieveByUsername($username)) {
            //user exists, return it
            return $user;
        } else {
            //none found, create it.
           $user = new sfGuardUser();
           $user->setUsername($username);
           return $user;
        }
    }
}

Customizing Admin Generator Filters in Symfony Using Doctrine

0 Comments

How to extend the existing filters in the symfony doctrine admin generator

Here is our (simplified) schema:

Group: [id, group_name]
User: [username, group_id]
Phonenumber: [user_id, num]

Groups contain many users, and users contain many phone numbers.

The prebuilt admin generator will allow you to filter on any of the existing fields in the table for the object that you are viewing, so if we were to visit /phonenumber/index then we would see all the phonenumbers and be able to filter by user.

However, if we want to filter, for instance, by group, then we need to follow a few steps:

  • Add group_id to phonenumber/config/generator.yml filter list
    config:
        filter:
            display: [user_id, group_id]
    
  • Add widget/validator to PhonenumberFormFilter
    We need to alter the filter form to add the select box for groups, and add the validator so that we can sanitize and get the value back.

    public function configure()
    {
        $this->setWidget('group_id',
                         new sfWidgetFormDoctrineChoice(array('model' => 'Group',
                                                              'add_empty' => true)));
    
        $this->setValidator('group_id',
                            new sfValidatorDoctrineChoice(array('required' => false,
                                                                'model' => 'Group',
                                                                'column' => 'id')));
    }
    
  • Add field to PhonenumberFormFilter
    Just so that the filter knows what to filter on we add this method.

    public function getFields()
      {
          return array_merge((array('group_id' => 'ForeignKey''),
                             parent::getFields());
      }
    
  • Alter query to PhonenumberFormFilter
    This is where we alter the actual query. We are adding a join against the user table, and then a where with a group_id.

    public function addGroupIdColumnQuery(Doctrine_Query $query, $field, $value)
      {
           $query->innerJoin(sprintf('%s.%s', $query->getRootAlias(), 'User u'))
                  ->where('u.group_id = ?', $value);
      }
    
  • This method could certainly have gone in the PhonenumberTable class; in which case it would look like this
    public function addGroupIdColumnQuery(Doctrine_Query $query, $field, $value)
      {
          Doctrine::getTable('Phonenumber')->applyGroupIdFilter($query, $value);
      }