diff --git a/components/console/introduction.rst b/components/console/introduction.rst index 47313027092..bd5a2e601c3 100755 --- a/components/console/introduction.rst +++ b/components/console/introduction.rst @@ -20,6 +20,15 @@ You can install the component in many different ways: * Use the official Git repository (https://github.com/symfony/Console); * :doc:`Install it via Composer` (``symfony/console`` on `Packagist`_). +.. note:: + + Windows does not support ANSI colors by default so the Console Component detects and + disables colors where Windows does not have support. However, if Windows is not + configured with an ANSI driver and your console commands invoke other scripts which + emit ANSI color sequences, they will be shown as raw escape characters. + + To enable ANSI colour support for Windows, please install `ANSICON_`. + Creating a basic Command ------------------------ @@ -452,3 +461,4 @@ Learn More! * :doc:`/components/console/single_command_tool` .. _Packagist: https://packagist.org/packages/symfony/console +.. _ANSICON: http://adoxa.3eeweb.com/ansicon/ diff --git a/components/index.rst b/components/index.rst index 55ac4c8d3a7..a572203ac97 100644 --- a/components/index.rst +++ b/components/index.rst @@ -17,6 +17,7 @@ The Components locale process routing/index + security/index serializer templating yaml diff --git a/components/map.rst.inc b/components/map.rst.inc index 7a45073e7eb..1b31ab9054c 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -65,6 +65,13 @@ * :doc:`/components/serializer` +* :doc:`/components/security/index` + + * :doc:`/components/security/introduction` + * :doc:`/components/security/firewall` + * :doc:`/components/security/authentication` + * :doc:`/components/security/authorization` + * **Templating** * :doc:`/components/templating` diff --git a/components/security/authentication.rst b/components/security/authentication.rst new file mode 100644 index 00000000000..bca9a8b1ddb --- /dev/null +++ b/components/security/authentication.rst @@ -0,0 +1,211 @@ +.. index:: + single: Security, Authentication Manager + +Authentication +============== + +When a request points to a secured area, and one of the listeners from the +firewall map is able to extract the user's credentials from the current +:class:`Symfony\\Component\\HttpFoundation\\Request` object, it should create +a token, containing these credentials. The next thing the listener should +do is ask the authentication manager to validate the given token, and return +an authenticated token when the supplied credentials were found to be valid. +The listener should then store the authenticated token in the security context:: + + use Symfony\Component\Security\Http\Firewall\ListenerInterface; + use Symfony\Component\Security\Core\SecurityContextInterface; + use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; + use Symfony\Component\HttpKernel\Event\GetResponseEvent; + use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; + + class SomeAuthenticationListener implements ListenerInterface + { + /** + * @var SecurityContextInterface + */ + private $securityContext; + + /** + * @var AuthenticationManagerInterface + */ + private $authenticationManager; + + /** + * @var string Uniquely identifies the secured area + */ + private $providerKey; + + // ... + + public function handle(GetResponseEvent $event) + { + $request = $event->getRequest(); + + $username = ...; + $password = ...; + + $unauthenticatedToken = new UsernamePasswordToken( + $username, + $password, + $this->providerKey + ); + + $authenticatedToken = $this + ->authenticationManager + ->authenticate($unauthenticatedToken); + + $this->securityContext->setToken($authenticatedToken); + } + } + +.. note:: + + A token can be of any class, as long as it implements + :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface`. + +The authentication manager +-------------------------- + +The default authentication manager is an instance of +:class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager`:: + + use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; + + // instances of Symfony\Component\Security\Core\Authentication\AuthenticationProviderInterface + $providers = array(...); + + $authenticationManager = new AuthenticationProviderManager($providers); + + try { + $authenticatedToken = $authenticationManager + ->authenticate($unauthenticatedToken); + } catch (AuthenticationException $failed) { + // authentication failed + } + +The ``AuthenticationProviderManager``, when instantiated, receives several +authentication providers, each supporting a different type of token. + +.. note:: + + You may of course write your own authentication manager, it only has + to implement :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`. + +.. _authentication_providers: + +Authentication providers +------------------------ + +Each provider (since it implements +:class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface`) +has a method :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::supports` +by which the ``AuthenticationProviderManager`` +can determine if it supports the given token. If this is the case, the +manager then calls the provider's method :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::authenticate`. This method +should return an authenticated token or throw an :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException` +(or any other exception extending it). + +Authenticating users by their username and password +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An authentication provider will attempt to authenticate a user based on +the credentials he provided. Usually these are a username and a password. +Most web applications store their user's username and a hash of the user's +password combined with a randomly generated salt. This means that the average +authentication would consist of fetching the salt and the hashed password +from the user data storage, hash the password the user has just provided +(e.g. using a login form) with the salt and compare both to determine if +the given password is valid. + +This functionality is offered by the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`. +It fetches the user's data from a :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface``, +uses a :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface` +to create a hash of the password and returns an authenticated token if the +password was valid:: + + use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; + use Symfony\Component\Security\Core\User\UserChecker; + use Symfony\Component\Security\Core\User\InMemoryUserProvider; + use Symfony\Component\Security\Core\Encoder\EncoderFactory; + + $userProvider = new InMemoryUserProvider( + array('admin' => array( + // password is "foo" + 'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==', + 'roles' => array('ROLE_ADMIN'), + ), + ); + + // for some extra checks: is account enabled, locked, expired, etc.? + $userChecker = new UserChecker(); + + // an array of password encoders (see below) + $encoderFactory = new EncoderFactory(...); + + $provider = new DaoAuthenticationProvider( + $userProvider, + $userChecker, + 'secured_area', + $encoderFactory + ); + + $provider->authenticate($unauthenticatedToken); + +.. note:: + + The example above demonstrates the use of the "in-memory" user provider, + but you may use any user provider, as long as it implements + :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. + It is also possible to let multiple user providers try to find the user's + data, using the :class:`Symfony\\Component\\Security\\Core\\User\\ChainUserProvider`. + +The password encoder factory +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider` +uses an encoder factory to create a password encoder for a given type of +user. This allows you to use different encoding strategies for different +types of users. The default :class:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory` +receives an array of encoders:: + + use Symfony\Component\Security\Core\Encoder\EncoderFactory; + use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; + + $defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000); + $weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1); + + $encoders = array( + 'Symfony\\Component\\Security\\Core\\User\\User' => $defaultEncoder, + 'Acme\\Entity\\LegacyUser' => $weakEncoder, + ..., + ); + + $encoderFactory = new EncoderFactory($encoders); + +Each encoder should implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface` +or be an array with a ``class`` and an ``arguments`` key, which allows the +encoder factory to construct the encoder only when it is needed. + +Password encoders +~~~~~~~~~~~~~~~~~ + +When the :method:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory::getEncoder` +method of the password encoder factory is called with the user object as +its first argument, it will return an encoder of type :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface` +which should be used to encode this user's password:: + + // fetch a user of type Acme\Entity\LegacyUser + $user = ... + + $encoder = $encoderFactory->getEncoder($user); + + // will return $weakEncoder (see above) + + $encodedPassword = $encoder->encodePassword($password, $user->getSalt()); + + // check if the password is valid: + + $validPassword = $encoder->isPasswordValid( + $user->getPassword(), + $password, + $user->getSalt()); diff --git a/components/security/authorization.rst b/components/security/authorization.rst new file mode 100644 index 00000000000..5a73c46409f --- /dev/null +++ b/components/security/authorization.rst @@ -0,0 +1,239 @@ +.. index:: + single: Security, Authorization + +Authorization +============= + +When any of the authentication providers (see :ref:`authentication_providers`) +has verified the still unauthenticated token, an authenticated token will +be returned. The authentication listener should set this token directly +in the :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface` +using its :method:`Symfony\\Component\\Security\\Core\\SecurityContextInterface::setToken` +method. + +From then on, the user is authenticated, i.e. means identified. +Now, other parts of the application can use the token to decide whether +or not the user may request a certain URI, or modify a certain object. +This decision will be made by an instance of :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface`. + +An authorization decision will always be based on a few things: + +The current token + For instance, the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles` + method may be used to retrieve the roles of the current user (e.g. + "ROLE_SUPER_ADMIN"), or a decision may be based on the class of the token. +A set of attributes + Each attribute stands for a certain right the user should have, e.g. + "ROLE_ADMIN" to make sure the user is an administrator. +An object (optional) + Any object on which to decide, e.g. the current :class:`Symfony\\Component\\HttpFoundation\\Request` + object, or an object for which access control needs to be checked, like + an article or a comment object. + +Access decision manager +----------------------- + +Since deciding whether or not a user is authorized to perform a certain +action can be a complicated process, the standard :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager` +itself depends on multiple voters, and makes a final verdict based on all +the votes (either positive, negative or neutral) it has received. It +recognizes several strategies: + +``affirmative`` (default) + Grant access as soon as any voter returns an affirmative response + +``consensus`` + Grant access if there are more voters granting access then there are denying + +``unanimous`` + Only grant access if none of the voters has denied access + +.. code-block:: php + + use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; + + // instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface + $voters = array(...); + + // one of "affirmative", "consensus", "unanimous" + $strategy = ...; + + // whether or not to grant access when all voters abstain + $allowIfAllAbstainDecisions = ...; + + // whether or not to grant access when there is no majority (applies only to the "consensus" strategy) + $allowIfEqualGrantedDeniedDecisions = ...; + + $accessDecisionManager = new AccessDecisionManager( + $voters, + $strategy, + $allowIfAllAbstainDecisions, + $allowIfEqualGrantedDeniedDecisions + ); + +Voters +------ + +Voters are instances +of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, +which means they have to implement a few methods which allows the decision +manager to use them: + +``supportsAttribute($attribute)`` + Will be used to check if the voter knows how to handle the given attribute. +``supportsClass($class)`` + Will be used to check if the voter is able to grant or deny access for + an object of the given class. +``vote(TokenInterface $token, $object, array $attributes)`` + This method will do the actual voting and return a value equal to one + of the class constants of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, + i.e. ``VoterInterface::ACCESS_GRANTED``, ``VoterInterface::ACCESS_DENIED`` + or ``VoterInterface::ACCESS_ABSTAIN``. + +The security component contains some standard voters which cover many use +cases: + +The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter` +voter supports the attributes "IS_AUTHENTICATED_FULLY", "IS_AUTHENTICATED_REMEMBERED", +and "IS_AUTHENTICATED_ANONYMOUSLY" and grants access based on the current +level of authentication, i.e. is the user fully authenticated, or only based +on a "remember-me" cookie, or even authenticated anonymously? + +.. code-block:: php + + use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; + + $anonymousClass = 'Symfony\Component\Security\Core\Authentication\Token\AnonymousToken'; + $rememberMeClass = 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'; + + $trustResolver = new AuthenticationTrustResolver($anonymousClass, $rememberMeClass); + + $authenticatedVoter = new AuthenticatedVoter($trustResolver); + + // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface + $token = ...; + + // any object + $object = ...; + + $vote = $authenticatedVoter->vote($token, $object, array('IS_AUTHENTICATED_FULLY'); + +The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` +supports attributes starting with "ROLE_" and grants access to the user +when the required "ROLE_*" attributes can all be found in the array of +roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles` +method. + +.. code-block:: php + + use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; + + $roleVoter = new RoleVoter('ROLE_'); + + $roleVoter->vote($token, $object, 'ROLE_ADMIN'); + +The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter` +extends :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` +and provides some additional functionality: it knows how to handle a +hierarchy of roles. For instance, a "ROLE_SUPER_ADMIN" role may have subroles +"ROLE_ADMIN" and "ROLE_USER", so that when a certain object requires the +user to have the "ROLE_ADMIN" role, it grants access to users who in fact +have the "ROLE_ADMIN" role, but also to users having the "ROLE_SUPER_ADMIN" +role. + +.. code-block:: php + + use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; + use Symfony\Component\Security\Core\Role\RoleHierarchy; + + $hierarchy = array( + 'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_USER'), + ); + + $roleHierarchy = new RoleHierarchy($hierarchy); + + $roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy); + +.. note:: + + When you make your own voter, you may of course use its constructor + to inject any dependencies it needs to come to a decision. + +Roles +----- + +Roles are objects that give expression to a certain right the user has. +The only requirement is that they implement :class:`Symfony\\Component\\Security\\Core\\Role\\RoleInterface`, +which means they should also have a :method:`Symfony\\Component\\Security\\Core\\Role\\Role\\RoleInterface::getRole` +method that returns a string representation of the role itself. The default +:class:`Symfony\\Component\\Security\\Core\\Role\\Role` simply returns its +first constructor argument:: + + use Symfony\Component\Security\Core\Role\Role; + + $role = new Role('ROLE_ADMIN'); + + // will echo 'ROLE_ADMIN' + echo $role->getRole(); + +.. note:: + + Most authentication tokens extend from :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\AbstractToken`, + which means that the roles given to its constructor, will be + automatically converted from strings to these simple ``Role`` objects. + +Using the decision manager +-------------------------- + +The access listener +~~~~~~~~~~~~~~~~~~~ + +Normally, the access decision manager will already be asked to decide whether +or not the current user is entitled to make the current request. This is done +by the :class:`Symfony\\Component\\Security\\Http\\Firewall\\AccessListener`, +which is one of the firewall listeners (see :ref:`firewall_listeners`) that +will be triggered for each request matching the firewall map (see :ref:`firewall`). + +It uses an access map (which should be an instance of :class:`Symfony\\Component\\Security\\Http\\AccessMapInterface`) +which contains request matchers and a corresponding set of attributes that +are required for the current user to get access to the application. + +.. code-block:: php + + use Symfony\Component\Security\Http\AccessMap; + use Symfony\Component\HttpFoundation\RequestMatcher; + use Symfony\Component\Security\Http\Firewall\AccessListener; + + $accessMap = new AccessMap(); + $requestMatcher = new RequestMatcher('^/admin'); + $accessMap->add($requestMatcher, array('ROLE_ADMIN')); + + $accessListener = new AccessListener( + $securityContext, + $accessDecisionManager, + $accessMap, + $authenticationManager + ); + +Security context +~~~~~~~~~~~~~~~~ + +The access decision manager is also available to other parts of the application +by means of the :method:`Symfony\\Component\\Security\\Core\\SecurityContext::isGranted` +method of the :class:`Symfony\\Component\\Security\\Core\\SecurityContext`. +A call to this method will directly delegate the question to the access +decision manager. + +.. code-block:: php + + use Symfony\Component\Security\SecurityContext; + use Symfony\Component\Security\Core\Exception\AccessDeniedException; + + $securityContext = new SecurityContext( + $authenticationManager, + $accessDecisionManager + ); + + if (!$securityContext->isGranted('ROLE_ADMIN')) { + throw new AccessDeniedException(); + } diff --git a/components/security/firewall.rst b/components/security/firewall.rst new file mode 100644 index 00000000000..72f74a344c4 --- /dev/null +++ b/components/security/firewall.rst @@ -0,0 +1,117 @@ +.. index:: + single: Security, Firewall + +The Security Context +==================== + +Central to the Security Component is the security context, which is an instance +of :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`. When all +steps in the process of authenticating the user have been taken successfully, +the security context may be asked if the authenticated user has access +to a certain action or resource of the application. + +.. code-block:: php + + use Symfony\Component\Security\SecurityContext; + use Symfony\Component\Security\Core\Exception\AccessDeniedException; + + $securityContext = new SecurityContext(); + + // ... authenticate the user + + if (!$securityContext->isGranted('ROLE_ADMIN')) { + throw new AccessDeniedException(); + } + +.. _firewall: + +A firewall for HTTP requests +============================ + +Authenticating a user is done by the firewall. An application may have +multiple secured areas, so the firewall is configured using a map of these +secured areas. For each of these areas, the map contains a request matcher +and a collection of listeners. The request matcher gives the firewall the +ability to find out if the current request points to a secured area. +The listeners are then asked if the current request can be used to authenticate +the user. + +.. code-block:: php + + use Symfony\Component\Security\Http\FirewallMap; + use Symfony\Component\HttpFoundation\RequestMatcher; + use Symfony\Component\Security\Http\Firewall\ExceptionListener; + + $map = new FirewallMap(); + + $requestMatcher = new RequestMatcher('^/secured-area/'); + + // instances of Symfony\Component\Security\Http\Firewall\ListenerInterface + $listeners = array(...); + + $exceptionListener = new ExceptionListener(...); + + $map->add($requestMatcher, $listeners, $exceptionListener); + +The firewall map will be given to the firewall as it's first argument, together +with the event dispatcher that is used by the :class:`Symfony\\Component\\HttpKernel\\HttpKernel`. + +.. code-block:: php + + use Symfony\Component\Security\Http\Firewall; + use Symfony\Component\HttpKernel\KernelEvents; + + // the EventDispatcher used by the HttpKernel + $dispatcher = ...; + + $firewall = new Firewall($map, $dispatcher); + + $dispatcher->addListener(KernelEvents::REQUEST, array($firewall, 'onKernelRequest'); + +The firewall is registered to listen to the ``kernel.request`` event that +will be dispatched by the ``HttpKernel`` at the beginning of each request +it processes. This way, the firewall may prevent the user from going any +further than allowed. + +.. _firewall_listeners: + +Firewall listeners +------------------ + +When the firewall gets notified of the ``kernel.request`` event, it asks +the firewall map if the request matches one of the secured areas. The first +secured area that matches the request, will return a set of corresponding +firewall listeners (which each implement :class:`Symfony\\Component\\Security\\Http\\Firewall\\ListenerInterface`). +These listeners will all be asked to handle the current request. This basically +means: find out if the current request contains any information by which +the user might be authenticated (for instance the Basic HTTP authentication +listener checks if the request has a header called "PHP_AUTH_USER"). + +Exception listener +------------------ + +If any of the listeners throws an :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`, +the exception listener that was provided when adding secured areas to the +firewall map will jump in. + +The exception listener determines what happens next, based on the arguments +it received when it was created. It may start the authentication procedure, +maybe ask the user to supply his credentials again (when he has only been +authenticated based on a "remember-me" cookie), or transform the exception +into an :class:`Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException`, +which will eventually result in an "HTTP/1.1 403: Access Denied" response. + +Entry points +------------ + +When the user is not authenticated at all (i.e. when the security context +has no token yet), the firewall's entry point will be called to "start" +the authentication process. An entry point should implement +:class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`, +which has only one method: :method:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface::start`. +This method receives the current :class:`Symfony\\Component\\HttpFoundation\\Request` +object and the exception by which the exception listener was triggered. +The method should return a :class:`Symfony\\Component\\HttpFoundation\\Response` +object, for instance the page containing the login form, or in the case +of Basic HTTP authentication a response with a "WWW-Authenticate" header, +which will prompt the user to supply his username and password. diff --git a/components/security/index.rst b/components/security/index.rst new file mode 100644 index 00000000000..c735740690d --- /dev/null +++ b/components/security/index.rst @@ -0,0 +1,10 @@ +Security +======== + +.. toctree:: + :maxdepth: 2 + + introduction + firewall + authentication + authorization \ No newline at end of file diff --git a/components/security/introduction.rst b/components/security/introduction.rst new file mode 100644 index 00000000000..6dee91130ca --- /dev/null +++ b/components/security/introduction.rst @@ -0,0 +1,31 @@ +.. index:: + single: Security + +The Security Component +====================== + +Introduction +------------ + +The Security Component provides a complete security system for your web +application. It ships with facilities for authenticating using HTTP basic +or digest authentication, interactive form login or X.509 certificate login, +but also allows you to implement your own authentication strategies. +Furthermore, the component provides ways to authorize authenticated users +based on their roles, and it contains an advanced ACL system. + +Installation +------------ + +You can install the component in many different ways: + +* Use the official Git repository (https://github.com/symfony/Security); +* Install it via PEAR ( `pear.symfony.com/Security`); +* Install it via Composer (`symfony/security` on Packagist). + +Sections +-------- + +* :doc:`/components/security/firewall` +* :doc:`/components/security/authentication` +* :doc:`/components/security/authorization`