In Defiance of Titles

Just another WordPress.com weblog

Posts Tagged ‘strategy pattern

Flexible User Authentication with Zend_Auth

with 3 comments

The Zend_Auth component of the Zend Framework can really help simplify the process of developing a custom authentication system for your next web application. The basic process is already very well-documented, so let’s try something a bit more complex.

For this example, we’re going to allow our users to authenticate in one of multiple ways: e.g., against a database table, against an LDAP server, or by OpenID [1]. Zend_Auth already provides the necessary authentication adapters, so what we’ll be concerned with here is how to implement all three systems without ending up in an FSUC situation.

As I see it, the controller layer ought to have very little awareness of the underlying authentication mechanisms. Here’s what such a controller might look like:

class AuthController extends Zend_Controller
{
  public function loginAction()
  {
    // one composite authentication adapter encapsulates
    // all the possible auth strategies, and provides appropriate
    // form instances for each
    $adapter = new My_Auth_Adapter_Multipath();
    $adapter->setStrategy($this->_getParam('via', null));
    $form = $adapter->getForm();
    $this->view->form = $form;

    // we'll also need an empty user object for this
    $user = new My_User(array());
    $adapter->setUser($user);

    // and then, if the user has submitted the form and it
    // passes simple validation, go ahead and try to authenticate
    if ($adapter->shouldAuthenticate($this->getRequest(), 
                                     $this->getResponse()) 
     && $form->isValid($this->_getAllParams())) {
      $user->populate($form->getValues());

      $auth = Zend_Auth::getInstance();
      $this->view->authResult = $auth->authenticate($adapter);
      $this->render('result');
    }
  }
}

Looks pretty clean to me. The view script is even cleaner:

<h1><?= $this->translate('Log in') ?></h1>
<?= $this->form ?>
<h2><?= $this->translate('Other ways to log in') ?></h2>
<ul>
  <li><a href="<?= $this->url(array('via' => 'email')) ?>"><?= $this->translate('Email') ?></a></li>
  <li><a href="<?= $this->url(array('via' => 'euid')) ?>"><?= $this->translate('EUID') ?></a></li>
  <li><a href="<?= $this->url(array('via' => 'openid')) ?>"><?= $this->translate('OpenID') ?></a></li>
</ul>

The real magic is all happening behind the scenes in the model layer. Let’s take a look at the authentication adapter next:

class My_Auth_Adapter_Multipath implements Zend_Auth_Adapter_Interface
{
  protected $_strategies;

  protected $_user;
  protected $_strategy;
  protected $_request;
  protected $_response;

  public function __construct()
  {
    $this->_strategies = new Zend_Loader_PluginLoader(array(
      'My_Auth_Strategy' => 'My/Auth/Strategy/',
    ));
  }

  public function shouldAuthenticate(Zend_Controller_Request_Http $request, 
                                     Zend_Controller_Response_Http $response)
  {
    if (null === $this->_strategy) {
      throw new Zend_Auth_Adapter_Exception('cannot determine; must set strategy first');
    }
    $this->setRequest($request);
    $this->setResponse($response);
    return $this->_strategy->shouldAuthenticate();
  }

  public function authenticate()
  {
    if (null === $this->_user || null === $this->_strategy) {
      throw new Zend_Auth_Adapter_Exception('must provide both user and strategy');
    }
    return $this->_strategy->authenticate();
  }

  public function getForm()
  {
    if (null === $this->_strategy) {
      throw new Zend_Auth_Adapter_Exception('must provide strategy first');
    }
    return $this->_strategy->getForm();
  }

  public function getUser()
  {
    return $this->_user;
  }

  public function setUser(My_User $user)
  {
    $this->_user = $user;
    return $this;
  }

  public function getStrategy()
  {
    return $this->_strategy;
  }

  public function setStrategy($strategy)
  {
    if (!($strategy instanceof My_Auth_Strategy_Interface)) {
      $strategyClass = $this->_strategies->load(ucfirst($strategy));
      $strategy = new $strategyClass();
      $strategy->setAdapter($this);
    }
    $this->_strategy = $strategy;
    return $this;
  }

  public function getRequest()
  {
    return $this->_request;
  }

  public function setRequest(Zend_Controller_Request_Http $request)
  {
    $this->_request = $request;
    return $this;
  }

  public function getResponse()
  {
    return $this->_response;
  }

  public function setResponse(Zend_Controller_Response_Http $response)
  {
    $this->_response = $response;
    return $this;
  }
}

A few things worth noting here. First…usually in this type of workflow the controller would check $this->getRequest()->isPost() prior to firing the authentication method; however, certain authentication strategies may require authentication to fire on other conditions (for instance, the OpenID adapter should fire when the openid_mode parameter is set, regardless of the request method). So, we leave it up to the adapter’s shouldAuthenticate() method to determine if the request warrants authentication.

Second, note that we still don’t have much in the manner of authentication code; that’s because, in order to keep things as flexible as possible, we’ve offloaded the actual authentication work to an arbitrary collection of strategy classes [2], provided dynamically through a Zend_Loader_PluginLoader instance (to make this worth it, we’ll also eventually want to add methods for registering new plugin paths, but I’ve left that out for the sake of brevity). Each strategy class will need to conform to the following interface:

interface My_Auth_Strategy_Interface extends Zend_Auth_Adapter_Interface
{
  public function setAdapter(My_Auth_Adapter_Multipath $adapter);
  public function getForm();
  public function shouldAuthenticate();
}

Internally, each authentication strategy will simply configure one of the core Zend_Auth adapters, run its authentication method, and, if successful, ensure that the identity returned in the Zend_Auth_Result instance is the completely-loaded user object. Let’s take a look at the simplest of the three examples, which checks the user’s email address and password against a backend database table:

class My_Auth_Strategy_Email implements My_Auth_Strategy_Interface
{
  protected $_adapter;

  public function setAdapter(My_Auth_Adapter_Multipath $adapter)
  {
    $this->_adapter = $adapter;
    return $this;
  }

  public function getForm()
  {
    $form = new Zend_Form();
    $form->addElement('text', 'email');
    $form->email->setLabel('Email address')
                ->setRequired(true)
                ->setFilters(array('StringTrim'))
                ->setValidators(array(
                  array('EmailAddress'),
                ));
    $form->addElement('password', 'pword');
    $form->pword->setLabel('Password')
                ->setRequired(true);
    $form->addElement('submit', 'submitBtn');
    $form->submitBtn->setLabel('Submit');
    return $form;
  }

  public function shouldAuthenticate()
  {
    return $this->_adapter->getRequest()->isPost();
  }

  public function authenticate()
  {
    $user = $this->_adapter->getUser();
    if (null === $user->email || null === $user->pword) {
      throw new Zend_Auth_Adapter_Exception('must provide email and password');
    }

    // use an internal Zend_Auth_Adapter_DbTable instance
    // to do the actual authentication
    $internalAdapter = new Zend_Auth_Adapter_DbTable(
      Zend_Registry::get('dbAdapter'),
      'users',
      'email',
      'pword'
    );
    $internalAdapter->setIdentity($user->email)
                    ->setCredential($user->pword);

    $result = $internalAdapter->authenticate();
    // per the stated requirements, we also want to make
    // sure that Zend_Auth stores a fully-completed user
    // object as the user's identity; so, we'll populate the
    // user object from the retrieved row and then set up
    // a new result object containing the correct identity
    if ($result->isValid()) {
      $user->populate($internalAdapter->getResultRowObject());
    }
    $result = new Zend_Auth_Result($result->getCode(), $user, $result->getMessages());
    return $result;
  }
}

We’ll use the same principle in designing the remaining two authentication methods (LDAP, which our users will know as their “EUID” or “Enterprise User ID”, and OpenID). I’ve left getForm() and setAdapter() out of these next examples, because the basic technique won’t be much different.

class My_Auth_Strategy_Euid implements My_Auth_Strategy_Interface
{
  // ...

  public function shouldAuthenticate()
  {
    return $this->_adapter->getRequest()->isPost();
  }

  public function authenticate()
  {
    $user = $this->_adapter->getUser();
    if (null === $user->euid || null === $user->euidPword) {
      throw new Zend_Auth_Adapter_Exception('must provide EUID and EUID password');
    }

    $ldapOptions = $this->_getLdapOptions();
    $internalAdapter = new Zend_Auth_Adapter_Ldap($ldapOptions, 
                                                  $user->euid, 
                                                  $user->euidPword);
    $result = $internalAdapter->authenticate();
    if ($result->isValid()) {
      // again, we'll need to populate the user object from the
      // database here, only this time we'll need to actually load
      // the user in manually since LDAP is a different system
      $table = new My_Db_Table_Users();
      $select = $table->select()->where('euid = ?', $user->euid);
      $userRow = $table->fetchRow($select);
      if (null === $userRow) {
        // the user has a valid LDAP account, but doesn't have an
        // account on our site yet, so we'll need to create one
        // programmatically...I won't demonstrate this here, though
      }
      $user->populate($userRow);
    }
    $result = new Zend_Auth_Result($result->getCode(), $user, $result->getMessages());
    return $result;
  }

  // ...
}

And finally, a strategy class for OpenID. Note that this is a bit more difficult owing to the necessity of client redirects; the OpenID strategy needs to be aware of quite a few more details, most of which are stored in the request object injected from the controller during the shouldAuthenticate() method.

class My_Auth_Strategy_Openid implements My_Auth_Strategy_Interface
{
  // ...

  public function shouldAuthenticate()
  {
    $request = $this->_adapter->getRequest();
    if ($request->isPost() 
     && $request->getParam('openid_action', null) == 'login' 
     && null !== $request->getParam('openid_identifier', null)) {
      return true;
    }
    if (null !== $request->getParam('openid_mode', null)) {
      return true;
    }
    return false;
  }

  public function authenticate()
  {
    $user = $this->_adapter->getUser();
    if (null === $user->openid_identifier) {
      throw new Zend_Auth_Adapter_Exception('must provide openid');
    }

    $storageDir = APPLICATION_PATH . '/../temp/openid_storage';
    $storage = new Zend_OpenId_Consumer_Storage_File($storageDir);

    $internalAdapter = new Zend_Auth_Adapter_OpenId(
      $user->openid_identifier,
      $storage,
      null,
      null,
      null,
      $this->_adapter->getResponse()
    );

    $result = $internalAdapter->authenticate();
    if ($result->isValid()) {
      // again, load the user from the database and populate
      // the user object; note that if the user doesn't yet have
      // an account on our site, the above code would be very
      // easy to modify such that it uses the OpenID simple
      // registration extension to create the user automatically
    }
    $result = new Zend_Auth_Result($result->getCode(), $user, $result->getMessages());
    return $result;
  }
}

The end result of all this is an extremely flexible (and extremely extensible) user authentication system with very little business logic in the controller.

Footnotes

  1. [Back] The idea is similar, but not identical, to the Zend_Auth_Adapter_Chain proposal from January 2008; the main difference here is that instead of authenticating against a series of several adapters, we’re simply going to have one main adapter that orchestrates the whole procedure via easily-selected strategies. Only one authentication technique will ultimately fire.
  2. [Back] If you look at the code later on, you’ll see that my authentication “strategy” interface is just an extension of Zend_Auth_Adapter_Interface allowing each implementation to provide a few extra standard features (forms, request analysis, etc.). I suppose it would have been reasonable to simply call them adapters, but I decided on using a new (still fairly standard) name to make it clear that they’re doing more than the standard Zend_Auth adapter would do. It was also necessary to namespace these classes separately from any other Zend_Auth adapters that might be included in a given application, so that the plugin loader never accidentally loads a different implementation of Zend_Auth_Adapter_Interface.
Advertisements

Written by jazzslider

April 9, 2009 at 6:52 am

Spades and the Strategy Pattern

with 5 comments

So lately my wife and I have been playing quite a bit of spades with some good friends of ours; if you’ve never played, it’s quite fun, but you don’t want to be on my team ­čÖé

The thing is, it strikes me as the kind of game a well-informed computer would be great at; that is, if one could remember everything that’s been played in a given hand and, from that, calculate the probability of any of one’s cards being beaten in a given trick, one could win the game far more easily. ┬áBut I have my doubts about this, so as any good developer would, I decided to test it programmatically.

Here’s the project (and I deliberately didn’t check to see if anyone’s done this before): write a command-line PHP script that runs any number of automated spades games, involving a variety of players utilizing different play algorithms. ┬áSince the algorithms are what we’re most interested in, I decided to use the strategy pattern: each player is an instance of the same basic Spades_Player class, but each has its own instance of one of several Spades_Strategy_Interface implementations that controls how it bids and how it chooses the card to play. ┬áHere are the interfaces:

interface Spades_Player_Interface
{
  public function __construct(Spades_Strategy_Interface $strategy, $name = null);

  // player should have an identity
  public function getName();

  // player is part of a particular Spades_Game, and
  // is seated in a particular position from 0 to 3
  public function getGame();
  public function setGame(Spades_Game $game);
  public function getPosition();
  public function setPosition($position);

  // player has several cards
  public function getCards();
  public function receiveCard(Spades_Card $card);

  // actual playing methods
  public function placeBid();
  public function playCard(Spades_Trick $trick);

  // player should also be able to respond to certain events
  public function preHand(Spades_Hand $hand);
  public function preTrick(Spades_Trick $trick);
  public function postPlay(Spades_Trick $trick, Spades_Play $play);
  public function postTrick(Spades_Trick $trick);
  public function postHand(Spades_Hand $hand);
}

Now on to the strategy pattern. You’ll notice a lot of the same methods here; as I understand it, that’s the idea behind this particular pattern. It implements a lot of its owner’s methods so that different instances of the same owner class can have very different behaviors. Here’s the code I used:

interface Spades_Strategy_Interface
{
  // strategy should know its player
  public function getPlayer();
  public function setPlayer(Spades_Player_Interface $player);

  // convenience methods for getting state information from the player
  public function getGame();
  public function getPosition();
  public function getCards();

  // player hook implementations
  public function preHand(Spades_Hand $hand);
  public function preTrick(Spades_Trick $trick);
  public function postPlay(Spades_Trick $trick, Spades_Play $play);
  public function postTrick(Spades_Trick $trick);
  public function postHand(Spades_Hand $hand);

  public function placeBid();
  public function playCard(Spades_Trick $trick);
}

And, just so you see how it works in practice, here’s a sample method from my actual Spades_Player implementation:

class Spades_Player implements Spades_Player_Interface
{
  // ...
  public function playCard(Spades_Trick $trick)
  {
    $toPlay = $this->_strategy->playCard($trick);
    $trick->receivePlay(new Spades_Play($toPlay, $this));
    return $toPlay;
  }
  // ...
}

So there it is, nice and simple. Testing a new strategy algorithm is as simple as defining a new PHP class.

It’s also probably a good idea at this point to define some sort of reference strategy to test against…something that any intelligent spades player ought to be able to beat. How about randomizing it? (Note that I’ve implemented a lot of the hook methods in a separate abstract class, so there’s a lot less to write here than the interface suggests.)

class Spades_Strategy_Random extends Spades_Strategy_Abstract
{
  public function placeBid()
  {
    return mt_rand(0, 4);
  }

  public function playCard(Spades_Trick $trick)
  {
    $playable = $trick->getPlayableCards($this->getCards());
    return $playable[array_rand($playable)];
  }
}

Hmm, that kind of looks like the strategy I use.

Anyway, there are quite a few places we could go from here; I’ve already implemented most of the classes you see mentioned in the code above (Spades_Game, Spades_Hand, Spades_Trick, Spades_Play, and Spades_Card), but the real fun is in writing new strategies. Some ideas I’ve had:

  • Probability-based: the player remembers which cards have been played and figures out how likely it is any of its playable cards can be beaten by any of its opponents. (More on this later; I’m not quite sure about the math here.)
  • Evolutionary: the player starts out playing at random, but always remembers whether a given card ends up beating a given trick state. For instance, if it plays the Ace of Clubs on top of the Two of Clubs and then ends up winning the trick, it’ll be more likely to play the Ace of Clubs against the Two of Clubs in later hands.
  • Cheater: you’ll notice that the Spades_Player::getCards() method is public, and astute readers may have already guessed that Spades_Game implements a public getPlayers() method; as a result, unscrupulous players would technically be able to take a peak at their opponents’ hands and play accordingly.

That’s all I’ve got for now; if anyone is interested, I may post the full code later once it’s finished. Thanks for reading!

Written by jazzslider

March 12, 2009 at 6:26 am