Commit 2b711bc0 authored by Fabien Potencier's avatar Fabien Potencier

added support for the Symfony Security component

parent aee53c94
{
"hash": "2159a9aea3c462e2837553e85846d0ac",
"hash": "18fdd4879bd4a6f9f92e7d9e032d20cd",
"packages": [
{
"package": "pimple/pimple",
......@@ -34,6 +34,11 @@
"version": "dev-master",
"source-reference": "526d5d663f0b3170a91f916f912075609120e09a"
},
{
"package": "symfony/http-kernel",
"version": "dev-master",
"source-reference": "fd5935fb6cd03dbd06930f2e3065c931694a5c92"
},
{
"package": "symfony/http-kernel",
"version": "dev-master",
......@@ -41,9 +46,10 @@
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/http-kernel",
"package": "symfony/routing",
"version": "dev-master",
"source-reference": "fd5935fb6cd03dbd06930f2e3065c931694a5c92"
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/routing",
......@@ -51,10 +57,15 @@
"source-reference": "4eef37eee0961782dfe66a23df4fc280ff1a9e44"
},
{
"package": "symfony/routing",
"package": "symfony/security",
"version": "dev-master",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/security",
"version": "dev-master",
"source-reference": "cfbb58936b3b9e9b5c31d191ed8056acd2932eb8"
}
],
"packages-dev": [
......@@ -81,24 +92,24 @@
{
"package": "swiftmailer/swiftmailer",
"version": "dev-master",
"alias-pretty-version": "4.1.x-dev",
"alias-version": "4.1.9999999.9999999-dev"
"source-reference": "d33d54cc8a081b0b85734744936ede1ba230dd64"
},
{
"package": "swiftmailer/swiftmailer",
"version": "dev-master",
"source-reference": "d33d54cc8a081b0b85734744936ede1ba230dd64"
"alias-pretty-version": "4.1.x-dev",
"alias-version": "4.1.9999999.9999999-dev"
},
{
"package": "symfony/browser-kit",
"version": "dev-master",
"source-reference": "6d1864547be92e51972a416fae9460b8be4afe0e"
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/browser-kit",
"version": "dev-master",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
"source-reference": "6d1864547be92e51972a416fae9460b8be4afe0e"
},
{
"package": "symfony/css-selector",
......@@ -111,6 +122,11 @@
"version": "dev-master",
"source-reference": "d0a98b37fbb57188766fd7c7d757354397ee6ead"
},
{
"package": "symfony/dom-crawler",
"version": "dev-master",
"source-reference": "2e27527036c4cd608692718414835173c40f52bd"
},
{
"package": "symfony/dom-crawler",
"version": "dev-master",
......@@ -118,9 +134,10 @@
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/dom-crawler",
"package": "symfony/finder",
"version": "dev-master",
"source-reference": "2e27527036c4cd608692718414835173c40f52bd"
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/finder",
......@@ -128,7 +145,7 @@
"source-reference": "9ee9a907afeef52956187e862714a7702ca26590"
},
{
"package": "symfony/finder",
"package": "symfony/form",
"version": "dev-master",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
......@@ -139,7 +156,7 @@
"source-reference": "e9068070fab8919f63e1a4e6313325082f4a1aa2"
},
{
"package": "symfony/form",
"package": "symfony/locale",
"version": "dev-master",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
......@@ -150,10 +167,9 @@
"source-reference": "741210486db314ff288a44de2628da7ee31d383e"
},
{
"package": "symfony/locale",
"package": "symfony/monolog-bridge",
"version": "dev-master",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
"source-reference": "ee24f08e2e74ee964018ce9d5de2a37977f6ec6b"
},
{
"package": "symfony/monolog-bridge",
......@@ -161,11 +177,6 @@
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/monolog-bridge",
"version": "dev-master",
"source-reference": "ee24f08e2e74ee964018ce9d5de2a37977f6ec6b"
},
{
"package": "symfony/options-resolver",
"version": "dev-master",
......@@ -177,11 +188,6 @@
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/process",
"version": "dev-master",
"source-reference": "f4f101fc7c1adb8b157058dcc1715f28f1d53208"
},
{
"package": "symfony/process",
"version": "dev-master",
......@@ -189,9 +195,9 @@
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/translation",
"package": "symfony/process",
"version": "dev-master",
"source-reference": "db3e85934353a130d743b2ddd53dd678c8ebca12"
"source-reference": "f4f101fc7c1adb8b157058dcc1715f28f1d53208"
},
{
"package": "symfony/translation",
......@@ -200,10 +206,9 @@
"alias-version": "2.1.9999999.9999999-dev"
},
{
"package": "symfony/twig-bridge",
"package": "symfony/translation",
"version": "dev-master",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
"source-reference": "db3e85934353a130d743b2ddd53dd678c8ebca12"
},
{
"package": "symfony/twig-bridge",
......@@ -230,19 +235,13 @@
{
"package": "twig/twig",
"version": "dev-master",
"alias-pretty-version": "1.8.x-dev",
"alias-version": "1.8.9999999.9999999-dev"
"source-reference": "ca33207bb22fe6365d13bdaf034f936e30b53560"
},
{
"package": "twig/twig",
"version": "dev-master",
"alias-pretty-version": "1.8.x-dev",
"alias-version": "1.8.9999999.9999999-dev"
},
{
"package": "twig/twig",
"version": "dev-master",
"source-reference": "ca33207bb22fe6365d13bdaf034f936e30b53560"
}
],
"aliases": [
......
SecurityServiceProvider
=======================
The *SecurityServiceProvider* manages authentication and authorization for
your applications.
Parameters
----------
n/a
Services
--------
* **security.context**: The main entry point for the security provider. Use it
to get the current user token.
* **security.authentication_manager**: An instance of
`AuthenticationProviderManager
<http://api.symfony.com/master/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.html>`_,
responsible for authentication.
* **security.access_manager**: An instance of `AccessDecisionManager
<http://api.symfony.com/master/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.html>`_,
responsible for authorization.
* **security.session_strategy**: Define the session strategy used for
authentication (default to a migration strategy).
* **security.user_checker**: Checks user flags after authentication.
* **security.last_error**: Returns the last authentication errors when given a
Request object.
* **security.encoder_factory**: Defines the encoding strategies for user
passwords (default to use a digest algorithm for all users).
.. note::
The service provider defines many other services that are used internally
but rarely need to be customized.
Registering
-----------
.. code-block:: php
$app->register(new Silex\Provider\SecurityServiceProvider());
.. note::
The Symfony Security component does not come with the ``silex`` archives,
so you need to add it as a dependency to your ``composer.json`` file:
.. code-block:: json
"require": {
"symfony/security": "2.1.*"
}
Usage
-----
The Symfony Security component is powerful. To learn more about it, read the
`Symfony2 Security documentation
<http://symfony.com/doc/2.1/book/security.html>`_.
.. tip::
When a security configuration does not behave as expected, enable logging
(with the Monolog extension for instance) as the Security Component logs a
lot of interesting information about what it does and why.
Below is a list of recipes that cover some common use cases.
Accessing the current User
~~~~~~~~~~~~~~~~~~~~~~~~~~
The current user information is stored in a token that is accessible via the
``security.context`` service::
$token = $app['security.context']->getToken();
If there is no information about the user, the token is ``null``. If the user
is known, you can get it with a call to ``getUser()``::
if (null !== $token) {
$user = $token->getUser();
}
The user can be a string, and object with a ``_toString()`` method, or an
instance of `UserInterface
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/UserInterface.html>`_.
Securing a Path with HTTP Authentication
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following configuration uses HTTP basic authentication to secure URLs
under ``/admin/``::
$app['security.firewalls'] = array(
'admin' => array(
'pattern' => '^/admin/',
'http' => true,
'users' => array(
// raw password is foo
'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='),
),
),
);
The ``pattern`` is a regular expression; the ``http`` setting tells the
security layer to use HTTP basic authentication and the ``users`` entry
defines valid users.
Each user is defined with the following information:
* The role or an array of roles for the user (roles are strings beginning with
``ROLE_`` and ending with anything you want);
* The user encoded password.
.. caution::
All users must at least have one role associated with them.
The default configuration of the extension enforces encoded passwords. To
generate a valid encoded password from a raw password, use the
``security.encoder`` service::
// find the encoded password for foo
$password = $app['security.encoder']->encodePassword('foo', null);
The second argument is the salt to be used for the user (defaults to
``null``).
When the user is authenticated, the user stored in the token is an instance of
`User
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/User.html>`_
Securing a Path with a Form
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using a form to authenticate users is very similar to the above configuration.
Instead of using the ``http`` setting, use the ``form`` one and define these
two parameters:
* **login_path**: The login path where the user is redirected when he is
accessing a secured area without being authenticated so that he can enter
his credentials;
* **check_path**: The check URL used by Symfony to validate the credentials of
the user.
Here is how to secure all URLs under ``/admin/`` with a form::
$app['security.firewalls'] = array(
'admin' => array(
'pattern' => '^/admin/',
'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'),
'users' => array(
'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='),
),
),
);
Always keep in mind the following two golden rules:
* The ``login_path`` path must always be defined **outside** the secured area;
* The ``check_path`` path must always be defined **inside** the secured area.
For the login form to work, create a controller where you start the session::
$app->get('/login', function(Request $request) use ($app) {
$app['session']->start();
return $app['twig']->render('login.html', array(
'error' => $app['security.last_error']($request),
'last_username' => $app['session']->get('_security.last_username'),
));
});
The ``error`` and ``last_username`` variables contain the last authentication
error and the last username entered by the user in case of an authentication
error.
Create the associated template:
.. code-block:: jinja
<form action="{{ path('admin_login_check') }}" method="post">
{{ error }}
<input type="text" name="_username" value="{{ last_username }}" />
<input type="password" name="_password" value="" />
<input type="submit" />
</form>
.. note::
The ``admin_login_check`` route is automatically defined by Symfony and
its name is derived from the ``check_path`` value (all ``/`` are replaced
with ``_`` and the leading ``/`` is stripped).
Defining more than one Firewall
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You are not limited to define one firewall per project.
Configuring several firewalls is useful when you want to secure different
parts of your website with different authentication strategies or for
different users (like using an HTTP basic authentication for the website API
and a form to secure your website administration area).
It's also useful when you want to secure all URLs except the login form::
$app['security.firewalls'] = array(
'login' => array(
'pattern' => '^/login$',
),
'secured' => array(
'pattern' => '^.*$',
'form' => array('login_path' => '/login', 'check_path' => '/login_check'),
'users' => array(
'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='),
),
),
);
The order of the firewall configurations is significant as the first one to
match wins. The above configuration first ensures that the ``/login`` URL is
not secured (no authentication settings), and then it secures all other URLs.
Adding a Logout
~~~~~~~~~~~~~~~
When using a form for authentication, you can let users log out if you add the
``logout`` setting::
$app['security.firewalls'] = array(
'secured' => array(
'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'),
'logout' => array('logout_path' => '/logout'),
// ...
),
);
A route is automatically generated, based on the configured path (all ``/``
are replaced with ``_`` and the leading ``/`` is stripped):
.. code-block:: jinja
<a href="{{ path('logout') }}">Logout</a>
Allowing Anonymous Users
~~~~~~~~~~~~~~~~~~~~~~~~
When securing only some parts of your website, the user information are not
available in non-secured areas. To make the user accessible in such areas,
enabled the ``anonymous`` authentication mechanism::
$app['security.firewalls'] = array(
'unsecured' => array(
'anonymous' => true,
// ...
),
);
When enabling the anonymous setting, a user will always be accessible from the
security context; if the user is not authenticated, it returns the ``anon.``
string.
Checking User Roles
~~~~~~~~~~~~~~~~~~~
To check if a user is granted some role, use the ``isGranted()`` method on the
security context::
if ($app['security.context']->isGranted('ROLE_ADMIN') {
// ...
}
You can check roles in Twig templates too:
.. code-block:: jinja
{% if is_granted('ROLE_ADMIN') %}
<a href="?_switch_user=fabien">Switch to Fabien</a>
{% endif %}
You can check is a user is "fully authenticated" (not an anonymous user for
instance) with the special ``IS_AUTHENTICATED_FULLY`` role:
.. code-block:: jinja
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
<a href="{{ path('logout') }}">Logout</a>
{% else %}
<a href="{{ path('login') }}">Login</a>
{% endif %}
.. tip::
Don't use the ``getRoles()`` method to check user roles.
Impersonating a User
~~~~~~~~~~~~~~~~~~~~
If you want to be able to switch to another user (without knowing the user
credentials), enable the ``switch_user`` authentication strategy::
$app['security.firewalls'] = array(
'unsecured' => array(
'switch_user' => array('parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH'),
// ...
),
);
Switching to another user is now a matter of adding the ``_switch_user`` query
parameter to any URL when logged in as a user who has the
``ROLE_ALLOWED_TO_SWITCH`` role:
.. code-block:: jinja
{% if is_granted('ROLE_ALLOWED_TO_SWITCH') %}
<a href="?_switch_user=fabien">Switch to user Fabien</a>
{% endif %}
You can check that you are impersonating a user by checking the special
``ROLE_PREVIOUS_ADMIN``. This is useful for instance to allow the user to
switch back to his primary account:
.. code-block:: jinja
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
You are an admin but you've switched to another user,
<a href="?_switch_user=_exit"> exit</a> the switch.
{% endif %}
Defining a Role Hierarchy
~~~~~~~~~~~~~~~~~~~~~~~~~
Defining a role hierarchy allows to automatically grant users some additional
roles::
$app['security.role_hierarchy'] = array(
'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'),
);
With this configuration, all users with the ``ROLE_ADMIN`` role also
automatically have the ``ROLE_USER`` and ``ROLE_ALLOWED_TO_SWITCH`` roles.
Defining Access Rules
~~~~~~~~~~~~~~~~~~~~~
Roles are a great way to adapt the behavior of your website depending on
groups of users, but they can also be used to further secure some areas by
defining access rules::
$app['security.access_rules'] = array(
array('^/admin', 'ROLE_ADMIN', 'https'),
array('^.*$', 'ROLE_USER'),
);
With the above configuration, users must have the ``ROLE_ADMIN`` to access the
``/admin`` section of the website, and ``ROLE_USER`` for everything else.
Furthermore, the admin section can only be accessible via HTTPS (if that's not
the case, the user will be automatically redirected).
Defining a custom User Provider
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using an array of users is simple and useful when securing an admin section of
a personal website, but you can override this default mechanism with you own.
The ``users`` setting can be defined as a service that returns an instance of
`UserProvider
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/UserProviderInterface.html>`_::
'users' => $app->share(function () use ($app) {
return new UserProvider($app['db']);
}),
Here is a simple example of a user provider, where Doctrine DBAL is used to
store the users::
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\Table;
class UserProvider implements UserProviderInterface
{
private $conn;
public function __construct(Connection $conn)
{
$this->conn = $conn;
}
public function loadUserByUsername($username)
{
$stmt = $this->conn->executeQuery('SELECT * FROM users WHERE username = ?', array(strtolower($username)));
if (!$user = $stmt->fetch()) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
return new User($user['username'], $user['password'], explode(',', $user['roles']), true, true, true, true);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Symfony\Component\Security\Core\User\User';
}
}
In this example, instances of the default ``User`` class are created for the
users, but you can define your own class; the only requirement is that the
class must implement `UserInterface
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/UserInterface.html>`_
And here is the code that you can use to create the database schema and some
sample users::
$schema = $conn->getSchemaManager();
if (!$schema->tablesExist('users')) {
$users = new Table('users');
$users->addColumn('id', 'integer', array('unsigned' => true));
$users->setPrimaryKey(array('id'));
$users->addColumn('username', 'string', array('length' => 32));
$users->addUniqueIndex(array('username'));
$users->addColumn('password', 'string', array('length' => 255));
$users->addColumn('roles', 'string', array('length' => 255));
$schema->createTable($users);
$this->conn->executeQuery('INSERT INTO users (username, password, roles) VALUES ("fabien", "5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==", "ROLE_USER")');
$this->conn->executeQuery('INSERT INTO users (username, password, roles) VALUES ("admin", "5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==", "ROLE_ADMIN")');
}
.. tip::
If you are using the Doctrine ORM, the Symfony bridge for Doctrine
provides a user provider class that is able to load users from your
entities.
......@@ -614,7 +614,7 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
),
KernelEvents::CONTROLLER => 'onKernelController',
KernelEvents::RESPONSE => 'onKernelResponse',
KernelEvents::EXCEPTION => 'onKernelException',
KernelEvents::EXCEPTION => array('onKernelException', -10),
KernelEvents::TERMINATE => 'onKernelTerminate',
KernelEvents::VIEW => array('onKernelView', -10),
);
......
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\User\UserChecker;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Http\Firewall;
use Symfony\Component\Security\Http\FirewallMap;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\ContextListener;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\ChannelListener;
use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint;
use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint;
use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
use Symfony\Component\Security\Http\Logout\SessionLogoutHandler;
use Symfony\Component\Security\Http\AccessMap;
use Symfony\Component\Security\Http\HttpUtils;
/**
* Symfony Security component Provider.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SecurityServiceProvider implements ServiceProviderInterface
{
protected $fakeRoutes;
public function register(Application $app)
{
// used to register routes for login_check and logout
$this->fakeRoutes = array();
$that = $this;
$app['security.context'] = $app->share(function () use ($app) {
return new SecurityContext($app['security.authentication_manager'], $app['security.access_manager']);
});
$app['security.authentication_manager'] = $app->share(function () use ($app) {
$manager = new AuthenticationProviderManager($app['security.authentication_providers']);
$manager->setEventDispatcher($app['dispatcher']);
return $manager;
});
// by default, all users use the digest encoder
$app['security.encoder_factory'] = $app->share(function () use ($app) {
return new EncoderFactory(array(
'Symfony\Component\Security\Core\User\UserInterface' => $app['security.encoder.digest'],
));
});
$app['security.encoder.digest'] = $app->share(function () use ($app) {
return new MessageDigestPasswordEncoder();
});
$app['security.user_checker'] = $app->share(function () use ($app) {
return new UserChecker();
});
$app['security.access_manager'] = $app->share(function () use ($app) {
if (!isset($app['security.role_hierarchy'])) {
$app['security.role_hierarchy'] = array();
}
return new AccessDecisionManager(array(
new RoleHierarchyVoter(new RoleHierarchy($app['security.role_hierarchy'])),
new AuthenticatedVoter($app['security.trust_resolver']),
));
});
$app['security.firewall'] = $app->share(function () use ($app) {
return new Firewall($app['security.firewall_map'], $app['dispatcher']);
});
$app['security.channel_listener'] = $app->share(function () use ($app) {
return new ChannelListener(
$app['security.access_map'],
new RetryAuthenticationEntryPoint($app['request.http_port'], $app['request.https_port']),
$app['logger']
);
});
$app['security.firewall_map'] = $app->share(function () use ($app) {
$map = new FirewallMap();
$entryPoint = 'form';
$providers = array();
foreach ($app['security.firewalls'] as $name => $firewall) {
$pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null;
$users = isset($firewall['users']) ? $firewall['users'] : array();
unset($firewall['pattern'], $firewall['users']);
$protected = count($firewall);
$listeners = array($app['security.channel_listener']);
if ($protected) {
if (!isset($app['security.context_listener.'.$name])) {
if (!isset($app['security.user_provider.'.$name])) {
$app['security.user_provider.'.$name] = is_array($users) ? $app['security.user_provider.inmemory._proto']($users) : $users;
}
$app['security.context_listener.'.$name] = $app['security.context_listener._proto'](
$name,
array($app['security.user_provider.'.$name])
);
}
$listeners[] = $app['security.context_listener.'.$name];
}
if (count($firewall)) {
foreach (array('logout', 'pre_auth', 'form', 'http', 'remember_me', 'anonymous') as $type) {
if (isset($firewall[$type])) {
$options = $firewall[$type];
// normalize options
if (!is_array($options)) {
if (!$options) {
continue;
}
$options = array();
}
if ('http' == $type) {
$entryPoint = 'http';
}
if (!isset($app['security.authentication.'.$name.'.'.$type])) {
$app['security.authentication.'.$name.'.'.$type] = $app['security.authentication.'.$type.'._proto']($name, $options);
}
$listeners[] = $app['security.authentication.'.$name.'.'.$type];
}
}
if ($protected) {
$listeners[] = $app['security.access_listener'];
if (isset($firewall['switch_user'])) {
$listeners[] = $app['security.authentication.switch_user._proto']($name, $firewall['switch_user']);
}
}
}
if ($protected && !isset($app['security.exception_listener.'.$name])) {
$app['security.exception_listener.'.$name] = $app['security.exception_listener._proto']($entryPoint, $name);
}
$map->add(
is_string($pattern) ? new RequestMatcher($pattern) : $pattern,
$listeners,
$protected ? $app['security.exception_listener.'.$name] : null
);
}
return $map;
});
$app['security.authentication_providers'] = $app->share(function () use ($app) {
$providers = array();
foreach ($app['security.firewalls'] as $name => $firewall) {
unset($firewall['pattern'], $firewall['users']);
if (!count($firewall)) {
continue;
}
if (!isset($app['security.authentication_provider.'.$name])) {
$a = 'anonymous' == $name ? 'anonymous' : 'dao';
$app['security.authentication_provider.'.$name] = $app['security.authentication_provider.'.$a.'._proto']($name);
}
$providers[] = $app['security.authentication_provider.'.$name];
}
return $providers;
});
$app['security.access_listener'] = $app->share(function () use ($app) {
return new AccessListener(
$app['security.context'],
$app['security.access_manager'],
$app['security.access_map'],
$app['security.authentication_manager'],
$app['logger']
);
});
$app['security.access_map'] = $app->share(function () use ($app) {
$map = new AccessMap();
if (!isset($app['security.access_rules'])) {
$app['security.access_rules'] = array();
}
foreach ($app['security.access_rules'] as $rule) {
if (is_string($rule[0])) {
$rule[0] = new RequestMatcher($rule[0]);
}
$map->add($rule[0], (array) $rule[1], isset($rule[2]) ? $rule[2] : null);
}
return $map;
});
$app['security.trust_resolver'] = $app->share(function () use ($app) {
return new AuthenticationTrustResolver('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken');
});
$app['security.session_strategy'] = $app->share(function () use ($app) {
return new SessionAuthenticationStrategy('migrate');
});
$app['security.http_utils'] = $app->share(function () use ($app) {
return new HttpUtils();
});
$app['security.last_error'] = $app->protect(function (Request $request) {
if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
return $request->attributes->get(SecurityContextInterface::AUTHENTICATION_ERROR)->getMessage();
}
$session = $request->getSession();
if ($session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
$error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR)->getMessage();
$session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);
return $error;
}
});
// prototypes (used by the Firewall Map)
$app['security.context_listener._proto'] = $app->protect(function ($providerKey, $userProviders) use ($app) {
return new ContextListener(
$app['security.context'],
$userProviders,
$providerKey,
$app['logger'],
$app['dispatcher']
);
});
$app['security.user_provider.inmemory._proto'] = $app->protect(function ($params) use ($app) {
$users = array();
foreach ($params as $name => $user) {
$users[$name] = array('roles' => (array) $user[0], 'password' => $user[1]);
}
return new InMemoryUserProvider($users);
});
$app['security.exception_listener._proto'] = $app->protect(function ($entryPoint, $name) use ($app) {
if (!isset($app['security.entry_point.'.$entryPoint.'.'.$name])) {
$app['security.entry_point.'.$entryPoint.'.'.$name] = $app['security.entry_point.'.$entryPoint.'._proto']($name);
}
return new ExceptionListener(
$app['security.context'],
$app['security.trust_resolver'],
$app['security.http_utils'],
$app['security.entry_point.'.$entryPoint.'.'.$name],
null, // errorPage
null, // AccessDeniedHandlerInterface
$app['logger']
);
});
$app['security.authentication.form._proto'] = $app->protect(function ($providerKey, $options) use ($app, $that) {
$that->addFakeRoute(array('post', $tmp = isset($options['check_path']) ? $options['check_path'] : '/login_check', str_replace('/', '_', ltrim($tmp, '/'))));
return new UsernamePasswordFormAuthenticationListener(
$app['security.context'],
$app['security.authentication_manager'],
$app['security.session_strategy'],
$app['security.http_utils'],
$providerKey,
$options,
null, // AuthenticationSuccessHandlerInterface
null, // AuthenticationFailureHandlerInterface
$app['logger'],
$app['dispatcher'],
isset($options['with_csrf']) && $options['with_csrf'] && isset($app['form.csrf_provider']) ? $app['form.csrf_provider'] : null
);
});
$app['security.authentication.http._proto'] = $app->protect(function ($providerKey, $options) use ($app) {
return new BasicAuthenticationListener(
$app['security.context'],
$app['security.authentication_manager'],
$providerKey,
$app['security.entry_point.http'],
$app['logger']
);
});
$app['security.authentication.anonymous._proto'] = $app->protect(function ($providerKey, $options) use ($app) {
return new AnonymousAuthenticationListener(
$app['security.context'],
$providerKey,
$app['logger']
);
});
$app['security.authentication.logout._proto'] = $app->protect(function ($providerKey, $options) use ($app, $that) {
$that->addFakeRoute(array('get', $tmp = isset($options['logout_path']) ? $options['logout_path'] : '/logout', str_replace('/', '_', ltrim($tmp, '/'))));
$listener = new LogoutListener(
$app['security.context'],
$app['security.http_utils'],
$options,
null, // LogoutSuccessHandlerInterface
isset($options['with_csrf']) && $options['with_csrf'] && isset($app['form.csrf_provider']) ? $app['form.csrf_provider'] : null
);
$listener->addHandler(new SessionLogoutHandler());
return $listener;
});
$app['security.authentication.switch_user._proto'] = $app->protect(function ($name, $options) use ($app, $that) {
return new SwitchUserListener(
$app['security.context'],
$app['security.user_provider.'.$name],
$app['security.user_checker'],
$name,
$app['security.access_manager'],
$app['logger'],
isset($options['parameter']) ? $options['parameter'] : '_switch_user',
isset($options['role']) ? $options['role'] : 'ROLE_ALLOWED_TO_SWITCH',
$app['dispatcher']
);
});
$app['security.entry_point.form._proto'] = $app->protect(function ($name, $loginPath = '/login', $useForward = false) use ($app) {
return new FormAuthenticationEntryPoint($app, $app['security.http_utils'], $loginPath, $useForward);
});
$app['security.entry_point.http._proto'] = $app->protect(function ($name, $realName = 'Secured') use ($app) {
return new BasicAuthenticationEntryPoint($realName);
});
$app['security.authentication_provider.dao._proto'] = $app->protect(function ($name) use ($app) {
return new DaoAuthenticationProvider(
$app['security.user_provider.'.$name],
$app['security.user_checker'],
$name,
$app['security.encoder_factory']
);
});
$app['security.authentication_provider.anonymous._proto'] = $app->protect(function ($name) use ($app) {
return new AnonymousAuthenticationProvider($name);
});
}
public function boot(Application $app)
{
$app['dispatcher']->addListener('kernel.request', array($app['security.firewall'], 'onKernelRequest'), 8);
foreach ($this->fakeRoutes as $route) {
$method = $route[0];
$app->$method($route[1], function() {})->bind($route[2]);
}
}
public function addFakeRoute($route)
{
$this->fakeRoutes[] = $route;
}
}
......@@ -17,6 +17,7 @@ use Silex\ServiceProviderInterface;
use Symfony\Bridge\Twig\Extension\RoutingExtension as TwigRoutingExtension;
use Symfony\Bridge\Twig\Extension\TranslationExtension as TwigTranslationExtension;
use Symfony\Bridge\Twig\Extension\FormExtension as TwigFormExtension;
use Symfony\Bridge\Twig\Extension\SecurityExtension as TwigSecurityExtension;
/**
* Twig Provider.
......@@ -54,6 +55,10 @@ class TwigServiceProvider implements ServiceProviderInterface
$twig->addExtension(new TwigTranslationExtension($app['translator']));
}
if (isset($app['security.context'])) {
$twig->addExtension(new TwigSecurityExtension($app['security.context']));
}
if (isset($app['form.factory'])) {
if (!isset($app['twig.form.templates'])) {
$app['twig.form.templates'] = array('form_div_layout.html.twig');
......
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Silex\Tests\Provider;
use Silex\Application;
use Silex\WebTestCase;
use Silex\Provider\SecurityServiceProvider;
use Silex\Provider\SessionServiceProvider;
use Symfony\Component\HttpFoundation\Request;
/**
* SecurityServiceProvider
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SecurityServiceProviderTest extends WebTestCase
{
public function setUp()
{
if (!is_dir(__DIR__.'/../../../../vendor/symfony/security')) {
$this->markTestSkipped('Security dependency was not installed.');
}
parent::setUp();
}
public function test()
{
$app = $this->app;
$client = $this->createClient();
$client->request('get', '/');
$this->assertEquals('ANONYMOUS', $client->getResponse()->getContent());
$client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'bar'));
$this->assertEquals('Bad credentials', $app['security.last_error']($client->getRequest()));
// hack to re-close the session as the previous assertions re-opens it
$client->getRequest()->getSession()->save();
$client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'foo'));
$this->assertEquals('', $app['security.last_error']($client->getRequest()));
$client->getRequest()->getSession()->save();
$this->assertEquals(302, $client->getResponse()->getStatusCode());
$this->assertEquals('http://localhost/', $client->getResponse()->headers->get('Location'));
$client->request('get', '/');
$this->assertEquals('fabienAUTHENTICATED', $client->getResponse()->getContent());
$client->request('get', '/admin');
$this->assertEquals(403, $client->getResponse()->getStatusCode());
$client->request('get', '/logout');
$this->assertEquals(302, $client->getResponse()->getStatusCode());
$this->assertEquals('http://localhost/', $client->getResponse()->headers->get('Location'));
$client->request('get', '/');
$this->assertEquals('ANONYMOUS', $client->getResponse()->getContent());
$client->request('get', '/admin');
$this->assertEquals(302, $client->getResponse()->getStatusCode());
$this->assertEquals('http://localhost/login', $client->getResponse()->headers->get('Location'));
$client->request('post', '/login_check', array('_username' => 'admin', '_password' => 'foo'));
$this->assertEquals('', $app['security.last_error']($client->getRequest()));
$client->getRequest()->getSession()->save();
$this->assertEquals(302, $client->getResponse()->getStatusCode());
$this->assertEquals('http://localhost/admin', $client->getResponse()->headers->get('Location'));
$client->request('get', '/');
$this->assertEquals('adminAUTHENTICATEDADMIN', $client->getResponse()->getContent());
$client->request('get', '/admin');
$this->assertEquals('admin', $client->getResponse()->getContent());
}
public function createApplication()
{
$app = new Application();
$app->register(new SessionServiceProvider());
$app->register(new SecurityServiceProvider(), array(
'security.firewalls' => array(
'login' => array(
'pattern' => '^/login$',
),
'default' => array(
'pattern' => '^.*$',
'anonymous' => true,
'form' => true,
'logout' => true,
'users' => array(
// password is foo
'fabien' => array('ROLE_USER', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='),
'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='),
),
),
),
'security.access_rules' => array(
array('^/admin', 'ROLE_ADMIN'),
),
'security.role_hierarchy' => array(
'ROLE_ADMIN' => array('ROLE_USER'),
),
));
$app->get('/login', function(Request $request) use ($app) {
$app['session']->start();
return $app['security.last_error']($request);
});
$app->get('/', function() use ($app) {
$user = $app['security.context']->getToken()->getUser();
$content = is_object($user) ? $user->getUsername() : 'ANONYMOUS';
if ($app['security.context']->isGranted('IS_AUTHENTICATED_FULLY')) {
$content .= 'AUTHENTICATED';
}
if ($app['security.context']->isGranted('ROLE_ADMIN')) {
$content .= 'ADMIN';
}
return $content;
});
$app->get('/admin', function() use ($app) {
return 'admin';
});
$app['session.test'] = true;
return $app;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment