GitHub

Overview

This bundle enhances the Symfony2 Security Component by adding several new features.

Features:

  • powerful expression-based authorization language
  • method security authorization
  • authorization configuration via annotations

Installation

Add the following to your deps file:

[JMSSecurityExtraBundle]
    git=https://github.com/schmittjoh/JMSSecurityExtraBundle.git
    target=/bundles/JMS/SecurityExtraBundle

; Dependencies:
;--------------
[metadata]
    git=https://github.com/schmittjoh/metadata.git
    version=1.1.0 ; <- make sure to get 1.1, not 1.0

; see https://github.com/schmittjoh/JMSAopBundle/blob/master/Resources/doc/index.rst
[JMSAopBundle]
    git=https://github.com/schmittjoh/JMSAopBundle.git
    target=/bundles/JMS/AopBundle

[cg-library]
    git=https://github.com/schmittjoh/cg-library.git

; This dependency is optional (you need it if you are using non-service controllers):
; see https://github.com/schmittjoh/JMSDiExtraBundle/blob/master/Resources/doc/index.rst
[JMSDiExtraBundle]
    git=https://github.com/schmittjoh/JMSDiExtraBundle.git
    target=/bundles/JMS/DiExtraBundle

Then register the bundle with your kernel:

// in AppKernel::registerBundles()
$bundles = array(
    // ...
    new JMS\AopBundle\JMSAopBundle(),
    new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
    new JMS\DiExtraBundle\JMSDiExtraBundle($this),
    // ...
);

Make sure that you also register the namespaces with the autoloader:

// app/autoload.php
$loader->registerNamespaces(array(
    // ...
    'JMS'              => __DIR__.'/../vendor/bundles',
    'Metadata'         => __DIR__.'/../vendor/metadata/src',
    'CG'               => __DIR__.'/../vendor/cg-library/src',
    // ...
));

Configuration

Below, you find the default configuration:

# app/config/config.yml
jms_security_extra:
    # Whether you want to secure all services (true), or only secure specific
    # services (false); see also below
    secure_all_services: false

    # Enabling this setting will add an additional special attribute "IS_IDDQD".
    # Anybody with this attribute will effectively bypass all security checks.
    enable_iddqd_attribute: false

    # Enables expression language
    expressions: false

    # Allows you to disable some, or all built-in voters
    voters:
        disable_authenticated: false
        disable_role:          false
        disable_acl:           false

    # Allows you to specify access control rules for specific methods, such
    # as controller actions
    method_access_control: { }

Expression-based Authorization Language

The expression language is a very powerful alternative to the simple attributes of the security voting system. They allow to perform complex access decision checks, and because they are compiled down to raw PHP, they are much faster than the built-in voters. Also they are lazy-loading by nature, so you will also save some resources for example by not having to initialize the entire ACL system on each request.

Programmatic Usage

You can execute expressions programmatically by using the isGranted method of the SecurityContext. Some examples:

use JMS\SecurityExtraBundle\Security\Authorization\Expression\Expression;

$securityContext->isGranted(array(new Expression('hasRole("A")')));
$securityContext->isGranted(array(new Expression('hasRole("A") or (hasRole("B") and hasRole("C"))')));
$securityContext->isGranted(array(new Expression('hasPermission(object, "VIEW")'), $object));
$securityContext->isGranted(array(new Expression('token.getUsername() == "Johannes"')));

Twig Usage

You can check expressions from Twig templates using the is_expr_granted function. Some examples:

is_expr_granted("hasRole('FOO')")
is_expr_granted("hasPermission(object, 'VIEW')", object)

Usage in Access Control

You can also use expressions in the access_control:

security:
    access_control:
        - { path: ^/foo, access: "hasRole('FOO') and hasRole('BAR')" }

Annotation-based Usage

see @PreAuthorize in the annotation reference

Reference

Expression Description
hasRole(‘ROLE’) Checks whether the token has a certain role.
hasAnyRole(‘ROLE1’, ‘ROLE2’, ...) Checks whether the token has any of the given roles.
isAnonymous() Checks whether the token is anonymous.
isRememberMe() Checks whether the token is remember me.
isFullyAuthenticated() Checks whether the token is fully authenticated.
isAuthenticated() Checks whether the token is not anonymous.
hasPermission(var, ‘PERMISSION’) Checks whether the token has the given permission for the given object (requires the ACL system).
token Variable that refers to the token which is currently in the security context.
user Variable that refers to the user which is currently in the security context.
object Variable that refers to the object for which access is being requested.
#*paramName* Any identifier prefixed with # refers to a parameter of the same name that is passed to the method where the expression is used.
and / && Binary “and” operator
or / || Binary “or” operator
== Binary “is equal” operator
not / ! Negation operator

Method Security Authorization

Generally, you can secure all public, or protected methods which are non-static, and non-final. Private methods cannot be secured. You can also add metadata for abstract methods, or interfaces which will then be applied to their concrete implementations automatically.

Access Control via DI configuration

You can specify access control expressions in the DI configuration:

# config.yml
jms_security_extra:
    method_access_control:
        ':loginAction$': 'isAnonymous()'
        'AcmeFooBundle:.*:deleteAction': 'hasRole("ROLE_ADMIN")'
        '^MyNamespace\MyService::foo$': 'hasPermission(#user, "VIEW")'

The pattern is a case-sensitive regular expression which is matched against two notations. The first match is being used.

First, your pattern is matched against the notation for non-service controllers. This obviously is only done if your class is actually a controller, e.g. AcmeFooBundle:Add:new for a controller named AddController and a method named newAction in a sub-namespace Controller in a bundle named AcmeFooBundle.

Last, your pattern is matched against the concatenation of the class name, and the method name that is being called, e.g. My\Fully\Qualified\ClassName::myMethodName.

Note: If you would like to secure non-service controllers, the JMSDiExtraBundle must be installed.

Access Control via Annotations

If you like to secure a service with annotations, you need to enable annotation configuration for this service:

<service id="foo" class="Bar">
    <tag name="security.secure_service"/>
</service>

In case, you like to configure all services via annotations, you can also set secure_all_services to true. Then, you do not need to add a tag for each service.

Annotations

@PreAuthorize

This annotation lets you define an expression (see the expression language paragraph) which is executed prior to invoking a method:

<?php

use JMS\SecurityExtraBundle\Annotation\PreAuthorize;

class MyService
{
    /** @PreAuthorize("hasRole('A') or (hasRole('B') and hasRole('C'))") */
    public function secureMethod()
    {
        // ...
    }
}

@Secure

This annotation lets you define who is allowed to invoke a method:

<?php

use JMS\SecurityExtraBundle\Annotation\Secure;

class MyService
{
    /**
     * @Secure(roles="ROLE_USER, ROLE_FOO, ROLE_ADMIN")
     */
    public function secureMethod()
    {
        // ...
    }
}

@SecureParam

This annotation lets you define restrictions for parameters which are passed to the method. This is only useful if the parameters are domain objects:

<?php

use JMS\SecurityExtraBundle\Annotation\SecureParam;

class MyService
{
    /**
     * @SecureParam(name="comment", permissions="EDIT, DELETE")
     * @SecureParam(name="post", permissions="OWNER")
     */
    public function secureMethod($comment, $post)
    {
        // ...
    }
}

@SecureReturn

This annotation lets you define restrictions for the value which is returned by the method. This is also only useful if the returned value is a domain object:

<?php

use JMS\SecurityExtraBundle\Annotation\SecureReturn;

class MyService
{
    /**
     * @SecureReturn(permissions="VIEW")
     */
    public function secureMethod()
    {
        // ...

        return $domainObject;
    }
}

@RunAs

This annotation lets you specifiy roles which are added only for the duration of the method invocation. These roles will not be taken into consideration for before, or after invocation access decisions.

This is typically used to implement a two-tier service layer where you have public and private services, and private services are only to be invoked through a specific public service:

<?php

use JMS\SecurityExtraBundle\Annotation\Secure;
use JMS\SecurityExtraBundle\Annotation\RunAs;

class MyPrivateService
{
    /**
     * @Secure(roles="ROLE_PRIVATE_SERVICE")
     */
    public function aMethodOnlyToBeInvokedThroughASpecificChannel()
    {
        // ...
    }
}

class MyPublicService
{
    protected $myPrivateService;

    /**
     * @Secure(roles="ROLE_USER")
     * @RunAs(roles="ROLE_PRIVATE_SERVICE")
     */
    public function canBeInvokedFromOtherServices()
    {
        return $this->myPrivateService->aMethodOnlyToBeInvokedThroughASpecificChannel();
    }
}

@SatisfiesParentSecurityPolicy

This must be defined on a method that overrides a method which has security metadata. It is there to ensure that you are aware the security of the overridden method cannot be enforced anymore, and that you must copy over all annotations if you want to keep them.