* fix authentication storage with native php session

* render flash messages with twig extension
* remove events authentication adapter since unused
* unit tests
parent 262ecc21
......@@ -23,6 +23,10 @@ $container['view'] = function ($c) {
$c->get('request')->getUri()
));
$view->addExtension(new Twig_Extension_Debug());
$view->addExtension(new GrEduLabs\Twig\Extension\Flash(
$c->get('flash'),
'flash.twig'
));
return $view;
};
......@@ -76,55 +80,77 @@ $container['db'] = function ($c) {
// Authentication service
$container['Service\\Authentication\\DbAdapter'] = function ($c) {
$container['authentication_db_adapter'] = function ($c) {
return new \GrEduLabs\Authentication\Adapter\Pdo($c->get('db'));
};
$container['Service\\Authentication\\CasAdapter'] = function ($c) {
$container['authentication_cas_adapter'] = function ($c) {
$settings = $c->get('settings');
return new GrEduLabs\Authentication\Adapter\Cas($settings['phpcas']);
};
$container['Service\\Authentication\\Storage'] = function ($c) {
return new \GrEduLabs\Authentication\Storage\PhpSession($_SESSION);
$container['authentication_storage'] = function ($c) {
return new \GrEduLabs\Authentication\Storage\PhpSession();
};
$container['Service\\Authentication'] = function ($c) {
$container['authentication_service'] = function ($c) {
return new \Zend\Authentication\AuthenticationService(
$c->get('Service\\Authentication\\Storage')
$c->get('authentication_storage')
);
};
$container['maybe_identity'] = function ($c) {
return function ($call) use ($c) {
$authService = $c->get('authentication_service');
if (!$authService->hasIdentity()) {
return;
}
$identity = $authService->getIdentity();
if (method_exists($identity, $call)) {
$args = array_slice(func_get_args(), 1);
return call_user_func_array([$identity, $call], $args);
}
if (property_exists($identity, $call)) {
return $identity->{$call};
}
return;
};
};
// Actions
$container['GrEduLabs\\Action\\User\\Login'] = function ($c) {
return new GrEduLabs\Action\User\Login(
$c->get('view'),
function ($identity, $credential) use ($c) {
$service = $c->get('Service\\Authentication');
$adapter = $c->get('Service\\Authentication\\DbAdapter');
$adapter->setIdentity($identity)
->setCredential($credential);
return $service->authenticate($adapter);
}
);
$service = $service = $c->get('authentication_service');
$adapter = $c->get('authentication_db_adapter');
$service->setAdapter($adapter);
return new GrEduLabs\Action\User\Login($c->get('view'), $service, $adapter, $c->get('flash'));
};
$container['GrEduLabs\\Action\\User\\LoginSso'] = function ($c) {
return new GrEduLabs\Action\User\LoginSso(function () use ($c) {
$service = $c->get('Service\\Authentication');
$adapter = $c->get('Service\\Authentication\\CasAdapter');
return $service->authenticate($adapter);
});
$service = $c->get('authentication_service');
$adapter = $c->get('authentication_cas_adapter');
$service->setAdapter($adapter);
return new GrEduLabs\Action\User\LoginSso(
$service,
$c->get('flash'),
$c->get('router')->pathFor('index'),
$c->get('router')->pathFor('user.login')
);
};
$container['GrEduLabs\\Action\\User\\Logout'] = function ($c) {
return new GrEduLabs\Action\User\Logout(
$c->get('Service\\Authentication'),
$c->get('authentication_service'),
$c->get('router')->pathFor('index')
);
};
......@@ -8,13 +8,15 @@
*/
$app->get('/', function ($request, $response, $args) use ($app) {
$container = $app->getContainer();
$logger = $container['logger'];
$view = $container['view'];
$app->get('/', function ($request, $response, $args) {
$logger = $this->get('logger');
$view = $this->get('view');
$identity = $this->get('maybe_identity');
$logger->info('Home page dispatched');
$view->render($response, 'home.twig');
$view->render($response, 'home.twig', [
'user' => $identity('uid'),
]);
return $response;
})->setName('index');
......
......@@ -11,7 +11,11 @@ namespace GrEduLabs\Action\User;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Flash\Messages;
use Slim\Views\Twig;
use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Adapter\ValidatableAdapterInterface;
use Zend\Authentication\AuthenticationServiceInterface;
class Login
{
......@@ -21,31 +25,55 @@ class Login
protected $view;
/**
* @var callable
* @var AuthenticationServiceInterface
*/
protected $authenticate;
protected $authService;
/**
* @var AdapterInterface
*/
protected $authAdapter;
/**
* @var Messages
*/
protected $flash;
/**
* Constructor
* @param Twig $view
* @param AuthenticationServiceInterface $authService
* @param AdapterInterface $authAdapter
* @param Messages $flash
*/
public function __construct(
Twig $view,
callable $authenticate
AuthenticationServiceInterface $authService,
AdapterInterface $authAdapter,
Messages $flash
) {
$this->view = $view;
$this->authenticate = $authenticate;
$this->authService = $authService;
$this->authAdapter = $authAdapter;
$this->flash = $flash;
$this->authService->setAdapter($this->authAdapter);
}
public function __invoke(ServerRequestInterface $req, ResponseInterface $res, array $args = [])
{
if ($req->isPost()) {
$authenticate = $this->authenticate;
$result = $authenticate(
$req->getParam('email'),
$req->getParam('password')
);
var_dump($result);
if ($this->authAdapter instanceof ValidatableAdapterInterface) {
$this->authAdapter->setIdentity($req->getParam('identity'))
->setCredential($req->getParam('credential'));
}
$result = $this->authService->authenticate();
if (!$result->isValid()) {
$this->flash->addMessage('danger', reset($result->getMessages()));
return $res->withRedirect($req->getUri());
}
return $res->withRedirect('/');
}
return $this->view->render($res, 'user/login.twig');
......
......@@ -11,32 +11,60 @@ namespace GrEduLabs\Action\User;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;
use Slim\Flash\Messages;
use Zend\Authentication\AuthenticationServiceInterface;
class LoginSso
{
/**
* @var callable
* @var AuthenticationServiceInterface
*/
protected $authenticate;
protected $authService;
/**
* @var Messages
*/
protected $flash;
/**
* @var string
*/
protected $successUrl;
/**
* @var string
*/
protected $failureUrl;
/**
* Constructor
* @param Twig $view
* @param AuthenticationServiceInterface $authService
* @param Messages $flash
*/
public function __construct(callable $authenticate)
{
$this->authenticate = $authenticate;
public function __construct(
AuthenticationServiceInterface $authService,
Messages $flash,
$successUrl,
$failureUrl
) {
$this->authService = $authService;
$this->flash = $flash;
$this->successUrl = $successUrl;
$this->failureUrl = $failureUrl;
}
public function __invoke(ServerRequestInterface $req, ResponseInterface $res, array $args = [])
{
$authenticate = $this->authenticate;
$result = $authenticate();
public function __invoke(
ServerRequestInterface $req,
ResponseInterface $res
) {
$result = $this->authService->authenticate();
if (!$result->isValid()) {
$this->flash->addMessage('danger', reset($result->getMessages()));
var_dump($result);
return $res->withRedirect($this->failureUrl);
}
return $res;
return $res->withRedirect($this->successUrl);
}
}
......@@ -30,7 +30,6 @@ class Logout
$redirectUrl
) {
$this->authService = $authService;
$this->router = $router;
$this->redirectUrl = $redirectUrl;
}
......
<?php
/**
* gredu_labs
*
* @link https://github.com/eellak/gredu_labs for the canonical source repository
* @copyright Copyright (c) 2008-2015 Greek Free/Open Source Software Society (https://gfoss.ellak.gr/)
* @license GNU GPLv3 http://www.gnu.org/licenses/gpl-3.0-standalone.html
*/
namespace GrEduLabs\Authentication\Adapter;
use Exception;
use Zend\Authentication\Adapter\AbstractAdapter;
use Zend\Authentication\Result;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\EventsCapableInterface;
class Events extends AbstractAdapter implements EventsCapableInterface
{
const EVENT_AUTH = 'authenticate';
/**
* @var EventManagerInterface
*/
protected $events;
/**
* Construct adapter
*
* @param EventManagerInterface $events
*/
public function __construct(EventManagerInterface $events = null)
{
if (null !== $events) {
$this->setEventManager($events);
}
}
/**
* Retrieve the event manager
*
* Lazy-loads an EventManager instance if none registered.
*
* @return EventManagerInterface
*/
public function getEventManager()
{
if (null === $this->events) {
$this->setEventManager(new EventManager());
}
return $this->events;
}
protected function setEventManager(EventManagerInterface $events)
{
$events->setIdentifiers([__CLASS__, get_class($this)]);
$this->events = $events;
}
public function authenticate()
{
try {
$identity = $this->getEventManager()->triggerUntil(function ($identity) {
return !!$identity;
}, self::EVENT_AUTH, $this, [
'identity' => $this->getIdentity(),
'credential' => $this->getCredential(),
])->last();
} catch (Exception $e) {
return new Result(Result::FAILURE_UNCATEGORIZED, null, [$e->getMessage()]);
}
if (!$identity) {
return new Result(Result::FAILURE, null, ['Authentication failure']);
}
return new Result(Result::SUCCESS, $identity, ['Authentication success']);
}
}
......@@ -31,7 +31,7 @@ class Identity implements JsonSerializable
public function __get($name)
{
if (property_exists($name, $this)) {
if (property_exists($this, $name)) {
return $this->{$name};
}
......
......@@ -23,12 +23,6 @@ class PhpSession implements StorageInterface
*/
const MEMBER_DEFAULT = 'storage';
/**
* Session array
*
* @var array
*/
protected $session;
/**
* Session namespace
......@@ -51,7 +45,7 @@ class PhpSession implements StorageInterface
* @param mixed $namespace
* @param mixed $member
*/
public function __construct(array &$session, $namespace = null, $member = null)
public function __construct($namespace = null, $member = null)
{
if ($namespace !== null) {
$this->namespace = $namespace;
......@@ -59,8 +53,6 @@ class PhpSession implements StorageInterface
if ($member !== null) {
$this->member = $member;
}
$this->session =& $session;
$this->session[$this->namespace] = [];
}
/**
......@@ -91,7 +83,7 @@ class PhpSession implements StorageInterface
*/
public function isEmpty()
{
return !isset($this->session[$this->namespace][$this->member]);
return !isset($_SESSION[$this->namespace][$this->member]);
}
/**
......@@ -104,7 +96,7 @@ class PhpSession implements StorageInterface
*/
public function read()
{
return $this->session[$this->namespace][$this->member];
return $_SESSION[$this->namespace][$this->member];
}
/**
......@@ -116,7 +108,7 @@ class PhpSession implements StorageInterface
*/
public function write($contents)
{
$this->session[$this->namespace][$this->member] = $contents;
$_SESSION[$this->namespace][$this->member] = $contents;
}
/**
......@@ -127,6 +119,6 @@ class PhpSession implements StorageInterface
*/
public function clear()
{
unset($this->session[$this->namespace][$this->member]);
unset($_SESSION[$this->namespace][$this->member]);
}
}
<?php
/**
* gredu_labs
*
* @link https://github.com/eellak/gredu_labs for the canonical source repository
* @copyright Copyright (c) 2008-2015 Greek Free/Open Source Software Society (https://gfoss.ellak.gr/)
* @license GNU GPLv3 http://www.gnu.org/licenses/gpl-3.0-standalone.html
*/
namespace GrEduLabs\Twig\Extension;
use Slim\Flash\Messages;
use Twig_Environment;
use Twig_Extension;
use Twig_SimpleFunction;
class Flash extends Twig_Extension
{
/**
* @var Twig_Environment
*/
protected $environment;
/**
* @var Messages
*/
protected $flash;
/**
* @var string
*/
protected $template;
public function __construct(Messages $flash, $template)
{
$this->flash = $flash;
$this->template = $template;
}
public function getName()
{
return 'slim-flash';
}
public function initRuntime(Twig_Environment $environment)
{
$this->environment = $environment;
}
public function getFunctions()
{
return [
new Twig_SimpleFunction('flash', [$this, 'messages'], [
'needs_environment' => true,
'is_safe' => ['all'],
]),
];
}
public function messages()
{
$response = '';
$allMessages = $this->flash->getMessages();
if (!empty($allMessages)) {
foreach ($allMessages as $class => $messages) {
$response .= $this->environment->render($this->template, [
'class' => $class,
'messages' => $messages,
]);
}
}
return $response;
}
}
<ul class="alert alert-{{ class }}" role="alert">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% extends "layout.twig" %}
{% block content %}
Welcome
Welcome {{ user }}
<a href="{{ path_for('user.login') }}">Login</a>
{% endblock %}
\ No newline at end of file
......@@ -7,6 +7,7 @@
<link href="{{ base_url() }}/css/style.css" rel="stylesheet" type="text/css">
</head>
<body>
{{ flash() }}
{% block content %}{% endblock %}
<script src="//code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
......
......@@ -7,7 +7,7 @@
<div class="col-xs-12 col-sm-5">
<div class="input-group">
<span class="input-group-addon">@</span>
<input name="email" type="text" value="{{ vm.username }}" class="form-control" required autocomplete="off">
<input name="identity" type="text" value="" class="form-control" required autocomplete="off">
</div>
</div>
</div>
......@@ -18,7 +18,7 @@
<span class="input-group-addon">
<i class="glyphicon glyphicon-lock"></i>
</span>
<input name="password" type="password" value="" class="form-control" required autocomplete="off">
<input name="credential" type="password" value="" class="form-control" required autocomplete="off">
</div>
</div>
</div>
......
......@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "3b10c4ba7156b5945f769c486d73237f",
"content-hash": "93b55f3a345026fb7f0e020a6d479b11",
"hash": "40b5bd0e81652838fc8051f261b47421",
"content-hash": "0e0d863dd17813ff2a9fa920f5714e31",
"packages": [
{
"name": "container-interop/container-interop",
......@@ -784,6 +784,56 @@
],
"time": "2015-09-17 14:06:43"
},
{
"name": "zendframework/zend-permissions-acl",
"version": "2.5.1",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-permissions-acl.git",
"reference": "7f1aac3bf99d0be8f71fe4ae79981338be8a08dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-permissions-acl/zipball/7f1aac3bf99d0be8f71fe4ae79981338be8a08dc",
"reference": "7f1aac3bf99d0be8f71fe4ae79981338be8a08dc",
"shasum": ""
},
"require": {
"php": ">=5.3.23"
},
"require-dev": {
"fabpot/php-cs-fixer": "1.7.*",
"phpunit/phpunit": "~4.0",
"zendframework/zend-di": "~2.5",
"zendframework/zend-servicemanager": "~2.5"
},
"suggest": {
"zendframework/zend-servicemanager": "To support Zend\\Permissions\\Acl\\Assertion\\AssertionManager plugin manager usage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev",
"dev-develop": "2.6-dev"
}
},
"autoload": {
"psr-4": {
"Zend\\Permissions\\Acl\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "provides a lightweight and flexible access control list (ACL) implementation for privileges management",
"homepage": "https://github.com/zendframework/zend-permissions-acl",
"keywords": [
"acl",
"zf2"
],
"time": "2015-06-03 15:32:02"
},
{
"name": "zendframework/zend-stdlib",
"version": "2.7.4",
......@@ -2177,16 +2227,16 @@
},
{
"name": "symfony/console",
"version": "v2.8.1",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a"
"reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
"reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
"url": "https://api.github.com/repos/symfony/console/zipball/d0239fb42f98dd02e7d342f793c5d2cdee0c478d",
"reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d",
"shasum": ""
},
"require": {
......@@ -2233,7 +2283,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-12-22 10:25:57"
"time": "2016-01-14 08:33:16"
},
{
"name": "symfony/event-dispatcher",
......
......@@ -41,6 +41,10 @@ class DependenciesTest extends \PHPUnit_Framework_TestCase
$app = new \Slim\App(self::$settings);
require __DIR__ . '/../app/dependencies.php';
self::$container = $container;
}
public static function tearDownAfterClass()
{
@session_destroy();
}
......@@ -70,19 +74,19 @@ class DependenciesTest extends \PHPUnit_Framework_TestCase
public function testDbAuthAdapter()
{
$adapter = self::$container->get('Service\\Authentication\\DbAdapter');
$adapter = self::$container->get('authentication_db_adapter');
$this->assertInstanceOf('\GrEduLabs\Authentication\Adapter\Pdo', $adapter);
}
public function testAuthStorage()
{
$storage = self::$container->get('Service\\Authentication\\Storage');
$storage = self::$container->get('authentication_storage');
$this->assertInstanceOf('\Zend\Authentication\Storage\StorageInterface', $storage);
}
public function testAuthService()
{
$service = self::$container->get('Service\\Authentication');
$service = self::$container->get('authentication_service');
$this->assertInstanceOf('\Zend\Authentication\AuthenticationService', $service);
}
}
<?php
/**
* gredu_labs
*
* @link https://github.com/eellak/gredu_labs for the canonical source repository
* @copyright Copyright (c) 2008-2015 Greek Free/Open Source Software Society (https://gfoss.ellak.gr/)
* @license GNU GPLv3 http://www.gnu.org/licenses/gpl-3.0-standalone.html
*/
namespace GrEduLabsTest\Action\User;
use GrEduLabs\Action\User\LoginSso;
use Slim\Http\Response;
use Zend\Authentication\Result;
class LoginSsoTest extends \PHPUnit_Framework_TestCase
{
private $action;
private $authService;
private $successUrl = '/some/success/url';
private $failureUrl = '/some/failure/url';
protected function setUp()
{
$this->authService =