* 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) { ...@@ -23,6 +23,10 @@ $container['view'] = function ($c) {
$c->get('request')->getUri() $c->get('request')->getUri()
)); ));
$view->addExtension(new Twig_Extension_Debug()); $view->addExtension(new Twig_Extension_Debug());
$view->addExtension(new GrEduLabs\Twig\Extension\Flash(
$c->get('flash'),
'flash.twig'
));
return $view; return $view;
}; };
...@@ -76,55 +80,77 @@ $container['db'] = function ($c) { ...@@ -76,55 +80,77 @@ $container['db'] = function ($c) {
// Authentication service // Authentication service
$container['Service\\Authentication\\DbAdapter'] = function ($c) { $container['authentication_db_adapter'] = function ($c) {
return new \GrEduLabs\Authentication\Adapter\Pdo($c->get('db')); 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'); $settings = $c->get('settings');
return new GrEduLabs\Authentication\Adapter\Cas($settings['phpcas']); return new GrEduLabs\Authentication\Adapter\Cas($settings['phpcas']);
}; };
$container['Service\\Authentication\\Storage'] = function ($c) { $container['authentication_storage'] = function ($c) {
return new \GrEduLabs\Authentication\Storage\PhpSession($_SESSION); return new \GrEduLabs\Authentication\Storage\PhpSession();
}; };
$container['Service\\Authentication'] = function ($c) { $container['authentication_service'] = function ($c) {
return new \Zend\Authentication\AuthenticationService( 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 // Actions
$container['GrEduLabs\\Action\\User\\Login'] = function ($c) { $container['GrEduLabs\\Action\\User\\Login'] = function ($c) {
return new GrEduLabs\Action\User\Login( $service = $service = $c->get('authentication_service');
$c->get('view'), $adapter = $c->get('authentication_db_adapter');
function ($identity, $credential) use ($c) { $service->setAdapter($adapter);
$service = $c->get('Service\\Authentication');
$adapter = $c->get('Service\\Authentication\\DbAdapter'); return new GrEduLabs\Action\User\Login($c->get('view'), $service, $adapter, $c->get('flash'));
$adapter->setIdentity($identity)
->setCredential($credential);
return $service->authenticate($adapter);
}
);
}; };
$container['GrEduLabs\\Action\\User\\LoginSso'] = function ($c) { $container['GrEduLabs\\Action\\User\\LoginSso'] = function ($c) {
return new GrEduLabs\Action\User\LoginSso(function () use ($c) { $service = $c->get('authentication_service');
$service = $c->get('Service\\Authentication'); $adapter = $c->get('authentication_cas_adapter');
$adapter = $c->get('Service\\Authentication\\CasAdapter'); $service->setAdapter($adapter);
return $service->authenticate($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) { $container['GrEduLabs\\Action\\User\\Logout'] = function ($c) {
return new GrEduLabs\Action\User\Logout( return new GrEduLabs\Action\User\Logout(
$c->get('Service\\Authentication'), $c->get('authentication_service'),
$c->get('router')->pathFor('index') $c->get('router')->pathFor('index')
); );
}; };
...@@ -8,13 +8,15 @@ ...@@ -8,13 +8,15 @@
*/ */
$app->get('/', function ($request, $response, $args) use ($app) { $app->get('/', function ($request, $response, $args) {
$container = $app->getContainer(); $logger = $this->get('logger');
$logger = $container['logger']; $view = $this->get('view');
$view = $container['view']; $identity = $this->get('maybe_identity');
$logger->info('Home page dispatched'); $logger->info('Home page dispatched');
$view->render($response, 'home.twig'); $view->render($response, 'home.twig', [
'user' => $identity('uid'),
]);
return $response; return $response;
})->setName('index'); })->setName('index');
......
...@@ -11,7 +11,11 @@ namespace GrEduLabs\Action\User; ...@@ -11,7 +11,11 @@ namespace GrEduLabs\Action\User;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Slim\Flash\Messages;
use Slim\Views\Twig; use Slim\Views\Twig;
use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Adapter\ValidatableAdapterInterface;
use Zend\Authentication\AuthenticationServiceInterface;
class Login class Login
{ {
...@@ -21,31 +25,55 @@ class Login ...@@ -21,31 +25,55 @@ class Login
protected $view; protected $view;
/** /**
* @var callable * @var AuthenticationServiceInterface
*/ */
protected $authenticate; protected $authService;
/**
* @var AdapterInterface
*/
protected $authAdapter;
/**
* @var Messages
*/
protected $flash;
/** /**
* Constructor * Constructor
* @param Twig $view * @param Twig $view
* @param AuthenticationServiceInterface $authService
* @param AdapterInterface $authAdapter
* @param Messages $flash
*/ */
public function __construct( public function __construct(
Twig $view, Twig $view,
callable $authenticate AuthenticationServiceInterface $authService,
AdapterInterface $authAdapter,
Messages $flash
) { ) {
$this->view = $view; $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 = []) public function __invoke(ServerRequestInterface $req, ResponseInterface $res, array $args = [])
{ {
if ($req->isPost()) { if ($req->isPost()) {
$authenticate = $this->authenticate; if ($this->authAdapter instanceof ValidatableAdapterInterface) {
$result = $authenticate( $this->authAdapter->setIdentity($req->getParam('identity'))
$req->getParam('email'), ->setCredential($req->getParam('credential'));
$req->getParam('password') }
); $result = $this->authService->authenticate();
var_dump($result); 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'); return $this->view->render($res, 'user/login.twig');
......
...@@ -11,32 +11,60 @@ namespace GrEduLabs\Action\User; ...@@ -11,32 +11,60 @@ namespace GrEduLabs\Action\User;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig; use Slim\Flash\Messages;
use Zend\Authentication\AuthenticationServiceInterface;
class LoginSso class LoginSso
{ {
/** /**
* @var callable * @var AuthenticationServiceInterface
*/ */
protected $authenticate; protected $authService;
/**
* @var Messages
*/
protected $flash;
/**
* @var string
*/
protected $successUrl;
/**
* @var string
*/
protected $failureUrl;
/** /**
* Constructor * Constructor
* @param Twig $view * @param AuthenticationServiceInterface $authService
* @param Messages $flash
*/ */
public function __construct(callable $authenticate) public function __construct(
{ AuthenticationServiceInterface $authService,
$this->authenticate = $authenticate; 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 = []) public function __invoke(
{ ServerRequestInterface $req,
$authenticate = $this->authenticate; ResponseInterface $res
$result = $authenticate(); ) {
$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 ...@@ -30,7 +30,6 @@ class Logout
$redirectUrl $redirectUrl
) { ) {
$this->authService = $authService; $this->authService = $authService;
$this->router = $router;
$this->redirectUrl = $redirectUrl; $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 ...@@ -31,7 +31,7 @@ class Identity implements JsonSerializable
public function __get($name) public function __get($name)
{ {
if (property_exists($name, $this)) { if (property_exists($this, $name)) {
return $this->{$name}; return $this->{$name};
} }
......
...@@ -23,12 +23,6 @@ class PhpSession implements StorageInterface ...@@ -23,12 +23,6 @@ class PhpSession implements StorageInterface
*/ */
const MEMBER_DEFAULT = 'storage'; const MEMBER_DEFAULT = 'storage';
/**
* Session array
*
* @var array
*/
protected $session;
/** /**
* Session namespace * Session namespace
...@@ -51,7 +45,7 @@ class PhpSession implements StorageInterface ...@@ -51,7 +45,7 @@ class PhpSession implements StorageInterface
* @param mixed $namespace * @param mixed $namespace
* @param mixed $member * @param mixed $member
*/ */
public function __construct(array &$session, $namespace = null, $member = null) public function __construct($namespace = null, $member = null)
{ {
if ($namespace !== null) { if ($namespace !== null) {
$this->namespace = $namespace; $this->namespace = $namespace;
...@@ -59,8 +53,6 @@ class PhpSession implements StorageInterface ...@@ -59,8 +53,6 @@ class PhpSession implements StorageInterface
if ($member !== null) { if ($member !== null) {
$this->member = $member; $this->member = $member;
} }
$this->session =& $session;
$this->session[$this->namespace] = [];
} }
/** /**
...@@ -91,7 +83,7 @@ class PhpSession implements StorageInterface ...@@ -91,7 +83,7 @@ class PhpSession implements StorageInterface
*/ */
public function isEmpty() 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 ...@@ -104,7 +96,7 @@ class PhpSession implements StorageInterface
*/ */
public function read() public function read()
{ {
return $this->session[$this->namespace][$this->member]; return $_SESSION[$this->namespace][$this->member];
} }
/** /**
...@@ -116,7 +108,7 @@ class PhpSession implements StorageInterface ...@@ -116,7 +108,7 @@ class PhpSession implements StorageInterface
*/ */
public function write($contents) 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 ...@@ -127,6 +119,6 @@ class PhpSession implements StorageInterface
*/ */
public function clear() 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" %} {% extends "layout.twig" %}
{% block content %} {% block content %}
Welcome Welcome {{ user }}
<a href="{{ path_for('user.login') }}">Login</a> <a href="{{ path_for('user.login') }}">Login</a>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<link href="{{ base_url() }}/css/style.css" rel="stylesheet" type="text/css"> <link href="{{ base_url() }}/css/style.css" rel="stylesheet" type="text/css">
</head> </head>
<body> <body>
{{ flash() }}
{% block content %}{% endblock %} {% block content %}{% endblock %}
<script src="//code.jquery.com/jquery-2.2.0.min.js"></script> <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> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<div class="col-xs-12 col-sm-5"> <div class="col-xs-12 col-sm-5">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon">@</span> <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> </div>
</div> </div>
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
<span class="input-group-addon"> <span class="input-group-addon">
<i class="glyphicon glyphicon-lock"></i> <i class="glyphicon glyphicon-lock"></i>
</span> </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> </div>
</div> </div>
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "3b10c4ba7156b5945f769c486d73237f", "hash": "40b5bd0e81652838fc8051f261b47421",
"content-hash": "93b55f3a345026fb7f0e020a6d479b11", "content-hash": "0e0d863dd17813ff2a9fa920f5714e31",
"packages": [ "packages": [
{ {
"name": "container-interop/container-interop", "name": "container-interop/container-interop",
...@@ -784,6 +784,56 @@ ...@@ -784,6 +784,56 @@
], ],
"time": "2015-09-17 14:06:43" "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",