Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] [BC BREAK] Refactor of authentication layer #554

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,12 @@ public function getServiceConfig()
{
return array(
'invokables' => array(
'ZfcUser\Authentication\Adapter\Db' => 'ZfcUser\Authentication\Adapter\Db',
'ZfcUser\Authentication\Storage\Db' => 'ZfcUser\Authentication\Storage\Db',
'ZfcUser\Form\Login' => 'ZfcUser\Form\Login',
'zfcuser_user_service' => 'ZfcUser\Service\User',
),
'factories' => array(
'zfcuser_module_options' => 'ZfcUser\Factory\ModuleOptionsFactory',
'zfcuser_auth_service' => 'ZfcUser\Factory\AuthenticationServiceFactory',
'ZfcUser\Authentication\Adapter\AdapterChain' => 'ZfcUser\Authentication\Adapter\AdapterChainServiceFactory',
'zfcuser_login_form' => 'ZfcUser\Factory\Form\LoginFormFactory',
'zfcuser_register_form' => 'ZfcUser\Factory\Form\RegisterFormFactory',
'zfcuser_user_mapper' => 'ZfcUser\Factory\UserMapperFactory',
Expand Down
17 changes: 14 additions & 3 deletions config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,36 @@
),
'service_manager' => array(
'invokables' => array(
'ZfcUser\Authentication\Adapter\Db' => 'ZfcUser\Authentication\Adapter\Db',
'ZfcUser\Authentication\Storage\Db' => 'ZfcUser\Authentication\Storage\Db',
'ZfcUser\Form\Login' => 'ZfcUser\Form\Login',
'zfcuser_user_service' => 'ZfcUser\Service\User',
'zfcuser_authentication_storage_backend_session' => 'Zend\Authentication\Storage\Session',
),
'factories' => array(
'zfcuser_module_options' => 'ZfcUser\Factory\ModuleOptionsFactory',
'zfcuser_auth_service' => 'ZfcUser\Factory\AuthenticationServiceFactory',
'ZfcUser\Authentication\Adapter\AdapterChain' => 'ZfcUser\Authentication\Adapter\AdapterChainServiceFactory',
'ZfcUser\Authentication\Adapter\AdapterChain' => 'ZfcUser\Factory\Authentication\Adapter\AdapterChainFactory',
'ZfcUser\Authentication\Adapter\MapperUsername' => 'ZfcUser\Factory\Authentication\Adapter\MapperUsernameFactory',
'ZfcUser\Authentication\Adapter\MapperEmail' => 'ZfcUser\Factory\Authentication\Adapter\MapperEmailFactory',
'zfcuser_authentication_credentialprocessor_bcrypt' => 'ZfcUser\Factory\Authentication\CredentialProcessor\BcryptFactory',
'ZfcUser\Authentication\Storage\Mapper' => 'ZfcUser\Factory\Authentication\Storage\MapperFactory',
'zfcuser_login_form' => 'ZfcUser\Factory\Form\LoginFormFactory',
'zfcuser_register_form' => 'ZfcUser\Factory\Form\RegisterFormFactory',
'zfcuser_change_password_form' => 'ZfcUser\Factory\Form\ChangePasswordFormFactory',
'zfcuser_change_email_form' => 'ZfcUser\Factory\Form\ChangeEmailFormFactory',
'zfcuser_user_mapper' => 'ZfcUser\Factory\UserMapperFactory',
'zfcuser_user_hydrator' => 'ZfcUser\Factory\Mapper\UserHydratorFactory',
),
'delegators' => array(
'zfcuser_authentication_storage_backend_session' => array(
'ZfcUser\Factory\Authentication\Listener\RegenerateSessionIdentifierFactory'
),
),
'aliases' => array(
'zfcuser_register_form_hydrator' => 'zfcuser_user_hydrator',
'zfcuser_zend_db_adapter' => 'Zend\Db\Adapter\Adapter',
'zfcuser_authentication_credentialprocessor' => 'zfcuser_authentication_credentialprocessor_bcrypt',
'zfcuser_authentication_storage' => 'ZfcUser\Authentication\Storage\Mapper',
'zfcuser_authentication_storage_backend' => 'zfcuser_authentication_storage_backend_session',
),
),
'controller_plugins' => array(
Expand Down
2 changes: 1 addition & 1 deletion config/zfcuser.global.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ $settings = array(
* Default value: array containing 'ZfcUser\Authentication\Adapter\Db' with priority 100
* Accepted values: array containing services that implement 'ZfcUser\Authentication\Adapter\ChainableAdapter'
*/
'auth_adapters' => array( 100 => 'ZfcUser\Authentication\Adapter\Db' ),
'auth_adapters' => array( 100 => 'ZfcUser\Authentication\Adapter\MapperEmail' ),

/**
* Enable Display Name
Expand Down
70 changes: 0 additions & 70 deletions src/ZfcUser/Authentication/Adapter/AbstractAdapter.php

This file was deleted.

180 changes: 63 additions & 117 deletions src/ZfcUser/Authentication/Adapter/AdapterChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,139 +2,85 @@

namespace ZfcUser\Authentication\Adapter;

use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Result as AuthenticationResult;
use Zend\EventManager\Event;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Stdlib\ResponseInterface as Response;
use ZfcBase\EventManager\EventProvider;
use ZfcUser\Exception;

class AdapterChain extends EventProvider implements AdapterInterface
use Zend\Authentication\Adapter\AbstractAdapter;
use Zend\Stdlib\PriorityList;
use Zend\Authentication\Result;
use Zend\EventManager\EventManagerAwareTrait;

/**
* Chainable authentication adapter for Zend\Authentication
*
* Allows multiple authentication adapters to be tried in succession,
* breaking the chain on the first adapter to return a successful result
*/
class AdapterChain extends AbstractAdapter
{
use EventManagerAwareTrait;

/**
* @var AdapterChainEvent
*/
protected $event;

/**
* Returns the authentication result
*
* @return AuthenticationResult
* @var PriorityList
*/
public function authenticate()
protected $adapters;

public function __construct()
{
$e = $this->getEvent();

$result = new AuthenticationResult(
$e->getCode(),
$e->getIdentity(),
$e->getMessages()
);

$this->resetAdapters();

return $result;
$this->adapters = new PriorityList();
$this->adapters->isLIFO(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please expand function name, LastInFirstOut?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hahahaha yeah ok :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stdlib is a bit of a mess in that regard. SplPriorityQueue and SplQueue are wrappers around the SPL classes to make them serializable. PriorityQueue uses Zend\Stdlib\SplPriorityQueue internally to get around SplPriorityQueue's destructive iterator, while PriorityList is an entirely standalone implementation. PriorityList with LIFO=false is functionally equivalent to PriorityQueue, just implemented differently. PriorityList with LIFO=true is technically a priority stack, but there is no PriorityStack in Stdlib. The only difference between PriorityStack and PriorityQueue is how they break priority ties. As far as I can tell it's all necessitated by odd behaviours in the Spl classes. And all of this has absolutely nothing to do with this PR, I just got off-track but I spent the time writing it so I might as well post it.

I'm going to try to sub in \SplPriorityQueue to see if it will work for this use case. If not, I'll sub in Zend\Stdlib\PriorityQueue which is equivalent to what I have currently but will cause no future head-scratching over isLIFO(false).

}

/**
* prepareForAuthentication
*
* @param Request $request
* @return Response|bool
* @throws Exception\AuthenticationEventException
* Attach an authentication adapter to the chain at the specified priority
*
* @param type $name
* @param AbstractAdapter $adapter
* @param type $priority
* @return \ZfcUser\Authentication\Adapter\AdapterChain
*/
public function prepareForAuthentication(Request $request)
public function attach($name, AbstractAdapter $adapter, $priority = 1)
{
$e = $this->getEvent();
$e->setRequest($request);

$this->getEventManager()->trigger('authenticate.pre', $e);

$result = $this->getEventManager()->trigger('authenticate', $e, function ($test) {
return ($test instanceof Response);
});

if ($result->stopped()) {
if ($result->last() instanceof Response) {
return $result->last();
}

throw new Exception\AuthenticationEventException(
sprintf(
'Auth event was stopped without a response. Got "%s" instead',
is_object($result->last()) ? get_class($result->last()) : gettype($result->last())
)
);
}

if ($e->getIdentity()) {
$this->getEventManager()->trigger('authenticate.success', $e);
return true;
}

$this->getEventManager()->trigger('authenticate.fail', $e);
return false;
}

/**
* resetAdapters
*
* @return AdapterChain
*/
public function resetAdapters()
{
$listeners = $this->getEventManager()->getListeners('authenticate');
foreach ($listeners as $listener) {
$listener = $listener->getCallback();
if (is_array($listener) && $listener[0] instanceof ChainableAdapter) {
$listener[0]->getStorage()->clear();
}
}
$argv = compact('name', 'adapter', 'priority');
$this->getEventManager()->trigger(__FUNCTION__, $this, $argv);

$this->adapters->insert($name, $adapter, $priority);
return $this;
}

/**
* logoutAdapters
*
* @return AdapterChain
*/
public function logoutAdapters()
{
//Adapters might need to perform additional cleanup after logout
$this->getEventManager()->trigger('logout', $this->getEvent());
}


/**
* Get the auth event
* Cycles through the attached adapters, short-circuiting when a
* successful authentication result is returned. Adapters are executed
* in descending priority order, with adapters at the same priority level
* executed in order of registration (FIFO)
*
* @return AdapterChainEvent
* @return Result
*/
public function getEvent()
public function authenticate()
{
if (null === $this->event) {
$this->setEvent(new AdapterChainEvent);
$this->event->setTarget($this);
$response = $this->getEventManager()->trigger(__FUNCTION__ . '.pre', $this);
if ($response->stopped()) {
return $response->last();
}
return $this->event;
}

/**
* Set an event to use during dispatch
*
* By default, will re-cast to AdapterChainEvent if another event type is provided.
*
* @param Event $e
* @return AdapterChain
*/
public function setEvent(Event $e)
{
if (!$e instanceof AdapterChainEvent) {
$eventParams = $e->getParams();
$e = new AdapterChainEvent();
$e->setParams($eventParams);

foreach ($this->adapters as $adapter) {
$adapter->setIdentity($this->getIdentity());
$adapter->setCredential($this->getCredential());

$result = $adapter->authenticate();
if ($result->isValid()) {
$argv = compact('adapter', 'result');
$this->getEventManager()->trigger(__FUNCTION__ . '.success', $this, $argv);
return $result;
}
}
$this->event = $e;
return $this;

//@TODO throw an exception if no result? (no adapters tried)

if (!isset($result) || ! $result instanceof Result) {
$result = new Result(Result::FAILURE_UNCATEGORIZED, null);
}

$argv = compact('result');
$this->getEventManager()->trigger(__FUNCTION__ . '.failure', $this, $argv);

return $result;
}
}
Loading