ZF-3451: Inconsistencies in the Zend_Auth_Adapter.. api

Description

There are inconsistencies across the various supplied adapters for Zend_Auth. In particular the naming conventions for getting and setting usernames and password/credentials do not match up across the different adapters. In code I have recently developed I have need to 'if statement' certain aspects e.g.

1/ when setting credentials

        //because the zend supplied adapters don't all have the same interface
        //we need to check for existence of functions before calling them
        //@todo - raise bug with ZF and change when fixed
        if (self::$_authType=='digest' || self::$_authType=='ldap') {
                    self::$_authHandler->setUsername($username)
                               ->setPassword($password);
           } else {
                        self::$_authHandler->setIdentity($username)
                               ->setCredential($password);
        }

2/ when retrieving the username:

public function getIdentity(Zend_Auth_Result $res) {
    $id = $res->getIdentity();
    if (self::$_authType=='digest') {
        return $id['username'];
    } else {
        return $id;
    }
}

I think that the Zend_Auth_Adapter_Interface specification needs to be extended and all child adapters made to conform to it. I might suggest that at a minimum it needs to include:

public function authenticate(); //already there public function authenticateWithCredentials($username,$password); public function setUsername(string $username); public function setPassword(string $password); public function getUsername(); public function getPassword(); public function setOptions(array $options); //set adapter specific options public function getOptions();

Also Zend_Auth_Result should be extended by adding a getUsername() method to return just the username used to authenticate, in addition to the getIdentity() method which can continue to return the full identity array.

Comments

Assuming credentials are always a username and password is a common mistake in web applications. Credential can be a domain+username+password, a PKI certificate, a base 64 encoded blob (e.g. SPNEGO Single Sign-On), etc.

I think the following would be better:

public function authenticate($credential = null); public function setCredential($credential); public function getCredential();

So using the LDAP adapter might look like:

$ldap->authenticate(array($username, $password));

But I would allow adapters to provide adapter specific methods (e.g. setUsername(), setPassword()).

Also, I don't think Zend_Auth::getIdentity() should return anything but a string that uniquely identifies the user across all adapters used by the application. If you want more info about the user there could be a Zend_Auth::getAccount() method that returned an object with account related methods like setPassword(), etc. Of course that's beyond the scope of the current ZF effort but I'm just thinking ahead.

I've explained this approach in more detail on the ZF mailing list in the past:

http://nabble.com/Re:-Storing-additional-authentic…

Of course this is all just one man's opinion.

Mike

With respect to the credentials problem, the standard OO answer is "encapsulate what varies". Since we know credentials take many varied forms, we could introduce a "Zend_Auth_Credentials_Interface", then add a "setCredentials(Zend_Auth_Credentials_Interface $creds)" method to Zend_Auth_Adapter_Interface. Each auth adapter would then provide its own concrete implementation of the credentials interface to represent any kind of credentials it needs. Of course, the application would have be prepared to provide all the correct credentials, whatever form they take. But in situations like the reported issue where you want to use two different adapters, both based on a username and password, you could do something like this:

// MyCredentialsFactory would know (perhaps from application configuration) which concrete adapter is being used $creds = MyCredentialsFactory->create($username, $password);

$authAdapter->setCredentials($creds); $result = $authAdapter->authenticate();

The approach you outline regarding credentials is actually how the adapter implementation works currently, more or less. You instantiate your adapter, and typically pass the credentials in the constructor or pass them to appropriate concrete methods in the speciific adapter. This is in part because credentials do not need to consist necessarily of a username and password.

To a degree, I like the idea of a credential interface, but I think it's overkill, as credentialling was part of the design of the authentication adapters. That said, I think there are some benefits to making the shipped adapters more consistent -- but it will have to be something that waits until we can break backwards compatibility.