I am using a middleware to redirect to login page if session is not set.
$app->get('/dashboard', function (Request $request, Response $response, $args) {
include_once('employee-portal/dashboard.php');
return $response;})->add(new AuthMiddleware('counter', true, $app->getContainer()));
and my middleware:
class AuthMiddleware implements MiddlewareInterface{
private $min_role = 'counter';
private $redirect = true;
private $container = null;
public function __construct($role_required, $login_redirect, $container)
{
$this->min_role = $role_required;
$this->redirect = $login_redirect;
$this->container = $container;
}
public function __invoke($request, $response, $next)
{
if ($this->userIsAuthorised()) {
return $next($request, $response);
} else {
if ($this->redirect) {
/**
* #var \Slim\Router router`
*/
return $response->withRedirect(Details::getBaseUrl() . '/login' . '?ref=' . $request->getUri());
}
return $response->withStatus(401)->write("Sorry boss you are not authorised to see my secret");
}
}
private function userIsAuthorised()
{
if ($this->min_role == 'counter') {
return true;
} else if (SessionManager::isLoggedIn()) {
if ($_SESSION['user_type']=='counter') {
return true;
}
}
return false;
} }
but this doesn't works.
i can even see the dashboard page without login. and even after login i cannot access the $_SESSION['user_type'] session variable.
any help would be appriciated. thanks in advance.
You are passing 'counter' into your AuthMiddleware constructor, causing it to always return true in the first if() statement of userIsAuthorised() method.
if ($this->min_role == 'counter')
will always be true because you set $this->min = 'counter' in your constructor. Try rewriting the new AuthMiddleware() and constructor so that you just pass in the container. Before calling the new AuthMiddleware() you can do the following:
$container['min_role'] = 'counter' if you need it elsewhere in your app.
I tried to follow the recommendations from this topic: zend framework 2 + routing database
I have a route class:
namespace Application\Router;
use Zend\Mvc\Router\Http\RouteInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\Mvc\Router\RouteMatch;
class Content implements RouteInterface, ServiceLocatorAwareInterface {
protected $defaults = array();
protected $routerPluginManager = null;
public function __construct(array $defaults = array()) {
$this->defaults = $defaults;
}
public function setServiceLocator(\Zend\ServiceManager\ServiceLocatorInterface $routerPluginManager) {
$this->routerPluginManager = $routerPluginManager;
}
public function getServiceLocator() {
return $this->routerPluginManager;
}
public static function factory($options = array()) {
if ($options instanceof \Traversable) {
$options = ArrayUtils::iteratorToArray($options);
} elseif (!is_array($options)) {
throw new InvalidArgumentException(__METHOD__ . ' expects an array or Traversable set of options');
}
if (!isset($options['defaults'])) {
$options['defaults'] = array();
}
return new static($options['defaults']);
}
public function match(Request $request, $pathOffset = null) {
if (!method_exists($request, 'getUri')) {
return null;
}
$uri = $request->getUri();
$fullPath = $uri->getPath();
$path = substr($fullPath, $pathOffset);
$alias = trim($path, '/');
$options = $this->defaults;
$options = array_merge($options, array(
'path' => $alias
));
return new RouteMatch($options);
}
public function assemble(array $params = array(), array $options = array()) {
if (array_key_exists('path', $params)) {
return '/' . $params['path'];
}
return '/';
}
public function getAssembledParams() {
return array();
}
}
Pay attention that the match() function returns object of the instance of Zend\Mvc\Router\RouteMatch
However in the file Zend\Mvc\Router\Http\TreeRouteStack it checks for object to be the instance of RouteMatch (without prefix of namespace)
if (
($match = $route->match($request, $baseUrlLength, $options)) instanceof RouteMatch
&& ($pathLength === null || $match->getLength() === $pathLength)
)
And the condition fails in my case because of the namespace.
Any suggestions?
Ok, i figured out what the problem was.
Instead of returning Zend\Mvc\Router\RouteMatch I should return Zend\Mvc\Router\Http\RouteMatch
This fixed my problem
I have been giving myself a headache over this all day,
I've been learning Symfony over the past couple days and my problem is simple, I need to be able to log in with 3 parameters (username, password and company)
The same question has been asked here, however the asker never provided a solution:
Custom Login using a Third Parameter
I have tried to start by cloning Symfony's inbuilt login by implementing a Token, Listener, Provider and Factory but whenever I insert my key into security.yml and try to log in it takes me to /login (No route found for "GET /login")
Anyone care to make my day?
EDIT:
OK, I Just tried again and now I have this error:
Catchable Fatal Error: "Argument 1 passed to Test\SampleBundle\Security\Authentication\Provider\MyProvider::__construct() must be an instance of Symfony\Component\Security\Core\User\UserCheckerInterface, instance of Symfony\Bridge\Doctrine\Security\User\EntityUserProvider given"
Again all I'm trying to do is duplicate Symfonys current user/password login functionality
MyToken.php:
namespace Test\SampleBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class MyToken extends AbstractToken
{
private $credentials;
private $providerKey;
public function __construct($user, $credentials, $providerKey, array $roles = array())
{
parent::__construct($roles);
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->setUser($user);
$this->credentials = $credentials;
$this->providerKey = $providerKey;
parent::setAuthenticated(count($roles) > 0);
}
public function setAuthenticated($isAuthenticated)
{
if ($isAuthenticated) {
throw new \LogicException('Cannot set this token to trusted after instantiation.');
}
parent::setAuthenticated(false);
}
public function getCredentials()
{
return $this->credentials;
}
public function getProviderKey()
{
return $this->providerKey;
}
public function eraseCredentials()
{
parent::eraseCredentials();
$this->credentials = null;
}
public function serialize()
{
return serialize(array($this->credentials, $this->providerKey, parent::serialize()));
}
public function unserialize($serialized)
{
list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized);
parent::unserialize($parentStr);
}
}
MyListener.php:
<?php
namespace Test\SampleBundle\Security\Firewall;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Test\SampleBundle\Security\Authentication\Token\MyToken;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class MyListener extends AbstractAuthenticationListener
{
private $csrfProvider;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfProviderInterface $csrfProvider = null)
{
parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
'username_parameter' => '_username' . 'b',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'intention' => 'authenticate',
'post_only' => true,
), $options), $logger, $dispatcher);
$this->csrfProvider = $csrfProvider;
}
protected function requiresAuthentication(Request $request)
{
if ($this->options['post_only'] && !$request->isMethod('POST')) {
return false;
}
return parent::requiresAuthentication($request);
}
protected function attemptAuthentication(Request $request)
{
if (null !== $this->csrfProvider) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
if ($this->options['post_only']) {
$username = trim($request->request->get($this->options['username_parameter'], null, true));
$password = $request->request->get($this->options['password_parameter'], null, true);
} else {
$username = trim($request->get($this->options['username_parameter'], null, true));
$password = $request->get($this->options['password_parameter'], null, true);
}
$request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new MyToken($username, $password, $this->providerKey));
}
}
MyProivder.php:
namespace Test\SampleBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Test\SampleBundle\Security\Authentication\Token\MyToken;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
class MyProvider implements AuthenticationProviderInterface
{
private $hideUserNotFoundExceptions;
private $userChecker;
private $providerKey;
public function __construct(UserCheckerInterface $userChecker, $providerKey, $hideUserNotFoundExceptions = true)
{
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->userChecker = $userChecker;
$this->providerKey = $providerKey;
$this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
}
public function authenticate(TokenInterface $token)
{
if (!$this->supports($token)) {
return null;
}
$username = $token->getUsername();
if (empty($username)) {
$username = 'NONE_PROVIDED';
}
try {
$user = $this->retrieveUser($username, $token);
} catch (UsernameNotFoundException $notFound) {
if ($this->hideUserNotFoundExceptions) {
throw new BadCredentialsException('Bad credentials', 0, $notFound);
}
$notFound->setUsername($username);
throw $notFound;
}
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.');
}
try {
$this->userChecker->checkPreAuth($user);
$this->checkAuthentication($user, $token);
$this->userChecker->checkPostAuth($user);
} catch (BadCredentialsException $e) {
if ($this->hideUserNotFoundExceptions) {
throw new BadCredentialsException('Bad credentials', 0, $e);
}
throw $e;
}
$authenticatedToken = new MyToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles());
$authenticatedToken->setAttributes($token->getAttributes());
return $authenticatedToken;
}
public function supports(TokenInterface $token)
{
return $token instanceof MyToken && $this->providerKey === $token->getProviderKey();
}
/**
* {#inheritdoc}
*/
protected function checkAuthentication(UserInterface $user, MyToken $token)
{
$currentUser = $token->getUser();
if ($currentUser instanceof UserInterface) {
if ($currentUser->getPassword() !== $user->getPassword()) {
throw new BadCredentialsException('The credentials were changed from another session.');
}
} else {
if ("" === ($presentedPassword = $token->getCredentials())) {
throw new BadCredentialsException('The presented password cannot be empty.');
}
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
throw new BadCredentialsException('The presented password is invalid.');
}
}
}
/**
* {#inheritdoc}
*/
protected function retrieveUser($username, MyToken $token)
{
$user = $token->getUser();
if ($user instanceof UserInterface) {
return $user;
}
try {
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
return $user;
} catch (UsernameNotFoundException $notFound) {
$notFound->setUsername($username);
throw $notFound;
} catch (\Exception $repositoryProblem) {
$ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem);
$ex->setToken($token);
throw $ex;
}
}
}
MyFactory.php
<?php
namespace Test\SampleBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class MyFactory implements SecurityFactoryInterface
{
public function __construct()
{
$this->addOption('username_parameter', '_username');
$this->addOption('password_parameter', '_password');
$this->addOption('csrf_parameter', '_csrf_token');
$this->addOption('intention', 'authenticate');
$this->addOption('post_only', true);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'mylogin';
}
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.mylogin.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('mylogin.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.mylogin.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('mylogin.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function addConfiguration(NodeDefinition $node)
{
}
protected function createEntryPoint($container, $id, $config, $defaultEntryPoint)
{
$entryPointId = 'security.authentication.form_entry_point.'.$id;
$container
->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point'))
->addArgument(new Reference('security.http_utils'))
->addArgument($config['login_path'])
->addArgument($config['use_forward'])
;
return $entryPointId;
}
final public function addOption($name, $default = null)
{
$this->options[$name] = $default;
}
}
Services.yml:
services:
mylogin.security.authentication.provider:
class: Test\SampleBundle\Security\Authentication\Provider\MyProvider
arguments: ['', %kernel.cache_dir%/security/nonces]
mylogin.security.authentication.listener:
class: Test\SampleBundle\Security\Firewall\MyListener
arguments: [#security.context, #security.authentication.manager]
And finally injector:
namespace Test\SampleBundle;
use Test\SampleBundle\DependencyInjection\Security\Factory\MyFactory;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class TestSampleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new MyFactory());
}
}
There may be a few errors in the above code as I have been playing around with stuff to get it working
I want to retrieve an instance of Zend_Session_Namespace within my models but I don't want them to have a concrete dependency on Zend's implementation (so I can mock it for it testing).
The session instance needs some configuration passed to it at call time. My other dependencies do not and can be configured during the bootstap process.
I have a very basic DI container, borrowed from Fabien Potencier:
class Lib_Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
I'm using this to wire up my dependencies:
$container = new Lib_Container;
$container->session = function($c) {
return new Zend_Session_Namespace($c->sessionName);
};
...
I'm using these dependencies within my base model (I don't want my model to know so much about my container configuration):
class Lib_Model {
protected $_container;
protected $_sessionName = 'default';
protected $_sessionInstance;
public function __construct($container) {
$this->_container = $container;
}
public function getDB() {
return $this->_container->database;
}
public function getRequest() {
return $this->_container->request;
}
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_container->sessionName = $ns;
$this->_sessionInstance[$ns] = $this->_container->session;
}
return $this->_sessionInstance[$ns];
}
}
This enables my subclasses to retrieve a session instance reasonably conveniently:
class Model_User extends Lib_Model {
protected $_sessionName = 'user';
public function loggedIn() {
$session = $this->getSession();
return ($session && $session->loggedIn) ? true : false;
}
}
Or by passing the session namespace as an argument:
$session = $this->getSession('admin');
However, my Lib_Model::getSession() method is more complex than I would like, and knows too much about my DI container. Ideally want to obtain an instance of Zend_Session_Namespace by calling:
class Lib_Model {
protected $_sessionName = 'default';
protected $_sessionFactory;
...
public function __construct($container) {
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
}
I appreciate my DI container is checking if it's services are callable (e.g. anonymous functions) and executing them. If I remove this behaviour the auto-wiring element will crumble?
Any ideas how I can achieve $container->session('my_namespace') to return the equivalent of new Zend_Session_Namespace('my_namespace')?
Update: I thought I was on to something by changing the configuration of my container:
$container->session = function($c) {
$s = function($namespace) {
return new Zend_Session_Namespace($namespace);
};
return $s;
};
So that $container->session would return a function. Updating my Lib_Model class:
Lib_Model {
private $_sessionFactory;
...
public function __construct($container) {
...
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns]))
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
return $this->_sessionInstance[$ns];
}
}
Unfortunately this gives me a 500 internal server error :(
I resolved the 500 internal server error by adjusting Lib_Model::getSession() slightly:
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$sessionFactory = $this->_session;
$this->_sessionInstance[$ns] = $sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
I put together a simple script slowly building up it's complexity until it dawned on me I was calling an undefined method on Lib_Model, though no error message was displayed by PHP running under apache.
$f = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $f();
$hello("World");
unset($hello);
// second test
class Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
$c = new Container;
$c->h = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $c->h;
$hello("Bert");
// third test
class MyTest {
public $attr;
}
$test = new MyTest;
$test->attr = $c->h;
$test->attr("Ernie");
Test output:
$ php -f test.php
Hello World
Hello Bert
PHP Fatal error: Call to undefined method MyTest::attr() in /home/greg/test.php on line 53
Are there any libraries to protect against CSRF(PHP5.1/5.2) or do I need to create on myself? I use this snippet from Chris, but without a library I am getting a lot of duplication on every page.
I found this library for PHP5.3, but I am wondering if there are any on PHP5.1/5.2 because I don't believe yet all hosting support PHP5.3.
Since I use Kohana - I've just extended couple of its core classes. It can be used in any code with a little changes though:
class Form extends Kohana_Form
{
public static function open($action = NULL, array $attributes = null)
{
if (is_null($action))
{
$action = Request::current()->uri . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : '');
}
$open = parent::open($action, $attributes);
$open .= parent::hidden(self::csrf_token_field(), self::csrf_token());
return $open;
}
public static function csrf_token_field()
{
return 'csrf_token';
}
public static function csrf_token()
{
$session = Session::instance();
$token = $session->get(self::csrf_token_field());
if (!$token)
{
$session->set(self::csrf_token_field(), $token = md5(uniqid()));
}
return $token;
}
}
class Validate extends Kohana_Validate
{
public function __construct(array $array, $csrf = true)
{
parent::__construct($array);
if ($csrf)
$this->add_csrf();
}
public static function factory(array $array, $csrf = true)
{
return new Validate($array, $csrf);
}
private function add_csrf()
{
$this->rules(form::csrf_token_field(), array(
'not_empty' => array(),
'csrf' => array()
));
}
protected function csrf($token)
{
return $token == form::csrf_token();
}
}