Extending Zend Auth - A Zend Config Adapter

So in the last installment of this series, I provided an introduction to Zend_Auth, Zend_Auth_Adapter_Interface and Zend_Auth_Result and how to implement Zend_Auth_Adapter_Interface to implement a basic test adapter that can be used as a mock object in your testing.

If you missed it, check it out now, then come back and we'll continue on. If you've already read it, then let's continue now.

As I indicated last time, whilst being a perfectly valid implementation, the Test adapter was rather basic and didn't do very much. Like all good testing, you need flexibility and options. So in this installment, we're going to build an adapter based around Zend_Config. This will lead quite nicely in to the last part in the series which uses the wonderful MongoDB as the underlying resource for the adapter.

A Zend_Config Primer

If you're not familiar with Zend_Config, the manual states: “…is designed to simplify the access to, and the use of, configuration data within applications”. Just like Zend_Auth, Zend_Db and a host of the other components of the Zend Framework, Zend_Config also allows for the use of a series of config types through config adapters. The ones that are currently implemented are:

  • Ini
  • Xml
  • Json
  • Yaml

So no matter what type of file your configuration information is stored in, if it's one of these, or can be converted to one of them, you can use Zend_Config quickly and easily to retrieve configuration data for your application.

Given that, it makes it a good choice as the next level in advancement of adapters for us to create. So in this version, we're going to have a simple Xml file that contains the details of a number of users.

How will it work?

We're going to create a Zend_Config object from the Xml file, set it as a member variable of our new adapter and then, when the authenticate method is called, we're going to search in it to see if we have a user with the credentials set in username and password. Not much too it right? Exactly.

Out XML configuration file

 

<?xml version="1.0"?>
    <configdata>
        <production>
            <login>
                <users>
          <settermjd>
                    <password>5f4dcc3b5aa765d61d8327deb882cf99</host>
                    <firstname>matthew</firstname>
                    <lastname>setter</lastname>
                <company>malt blue</company>
                    <email>matthew@maltblue.com</email>
          </settermjd>
          <citizenj>
                    <password>56266a582ccab096b63eceaea36d1f4c</host>
                    <firstname>john</firstname>
                    <lastname>citizen</lastname>
            <company>malt blue</company>
                    <email>j.citizen@maltblue.com</email>
          </citizenj>
                </users>
            </login>
        </production>
        <staging extends="production">
            <login>
                <users>
          <settermjd>
                    <password>3c0e32a23e491092f5966071bdeda367</host>
                    <firstname>matthew</firstname>
                    <lastname>setter</lastname>
            <company>malt blue</company>
                    <email>matthew@maltblue.com</email>
              </settermjd>
                </users>
            </login>
        </staging>
    </configdata>

You can see in the xml above, that in production, we have 2 users, settermjd and citizenj. Both of them have the properties: password, firstname, lastname, company and email. Having a look in the staging environment configuration, you can see that only settermjd is there.

Now I'm not that narcissistic, but it shows how we can differentiate between different environments, making testing and development easier. Now on successful login, the information contained in the users details will be set in the identity object and returned in the Zend_Auth_Result object. Arguably nice, clean and efficient.

**NB: **Now what it's not is likely the way that you would build an adapter for heavy production use, but it does show another way to progress.

The Constructor

public function __construct(Zend_Config $config=null, $username = null,
  $password = null)
{
    if (!empty($config)) {
      if (!($config instanceof Zend_Config)) {
        throw new Zend_Auth_Adapter_Exception();
      }
      $this->_config = $config;
    }
    if (!empty($password)) {
      $this->_password = $password;
    }
    if (!empty($username) && ) {
      $this->_username = $username;
    }
}

This constructor augments the Test adapter constructor and takes a Zend_Config object as its first parameter. The original username and password continue to be used. We then add an extra check to ensure that the object passed in is a valid Zend_Config object before assigning it if it is; the method type hinting should take care of that for us for the most part.

The Authenticate Method

public function authenticate()
{
  if (empty($this->_username) || empty($this->_password)) {
    throw new Zend_Auth_Adapter_Exception();
  }

  // search for the username/password combination in the available list
  $userConfig = $this->_config->users->get($this->_username, null);

  if (is_null($userConfig)) {
    return new Zend_Auth_Result(
      Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND,
      array()
  );
} else {
  if (!empty($this->_credentialTreatment)) {
    $credential = $this->_credentialTreatment;
    if (!in_array($credential, $this->_allowedCredentials)) {
      return new Zend_Auth_Result(
        Zend_Auth_Result::FAILURE_UNCATEGORIZED,
    array()
      );
  }
  if ($userConfig->password != $credential($this->_password)) {
    return new Zend_Auth_Result(
      Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,
      array()
    );
  }
} else {
  if ($userConfig->password != $this->_password) {
    return new Zend_Auth_Result(
      Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,
      array()
    );
  }
}
}

return new Zend_Auth_Result(
  Zend_Auth_Result::SUCCESS,
  array(
    'username' => $this->_username,
    'firstName' => $userConfig->firstname,
    'lastName' => $userConfig->lastname,
    'emailAddress' => $userConfig->email,
    'company' => $userConfig->company,
  )
);
}

You can see that the authenticate method is also largely the same. As before, we check if we have a valid username and password. But now, that we also have a valid config object we validate that as well.

Following this, we search in the config object for the user to see if we have one that matches the username passed in. If we found them, we then move to validate the password. As we're getting more detailed this time, we've also lifted a method from Zend_Auth_DbTable; setCredentialTreatment. This allows us to set how we're going to treat the user credentials – in our case, the password field.

public function setCredentialTreatment($treatment)
    {
        $this->_credentialTreatment = $treatment;
        return $this;
    }

If it's not called, then the password is compared as is. If it's called, then the method that's set in it is used to compare the password value entered against the value we have stored. You'll note in the XML config that the password for each user is an MD5 hash. So later on we're going to call the method, setting md5 as the treatment.

Another point to note is that this is a fairly simple implementation. We've added an array $_allowedCredentials **which restricts the allowed choices of credential functions; specifically to **md5 and sha1. We could use call_func_array or something like that to expand this, but well, why take all the fun out of it.

If the user is available and the password matches we then retrieve the details of the user config object and create an identity object with that information and return it as normal.

If the user is not available, then we return the code Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID and an empty identity object. If the password's don't match, then we return the code Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND and an empty identity object. If the user attempts to set an invalid credential function then we return the code Zend_Auth_Result::FAILURE_UNCATEGORIZED and an empty identity object.

So, what did you think?

  • Too easy, too simplistic?
  • Didn't go far enough?

I'd love to hear your thoughts where you think this could go and where it could be done better.

If you liked what you read and would like to see more, please retweet it, or give it a like on Facebook or even give it some Digg love. And we always value you feedback and comments.

Matt's Pic About Matthew Setter

Matthew Setter, a passionate Australian, is the founder and chief-editor of Malt Blue. Follow him on Twitter at @maltblue, join in the discussion on Facebook and find out more about him on the team page.


About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.

Want To Be A Zend Framework Guru?

Drop your email in the box below, and get awesome tutorialsjust like this one — straight to your inbox, PLUS exclusive content only available by email.