Skip over navigation
Documentation
You are currently viewing documentation for a previously released version of OroCRM. See the latest long-term support version.

Security

Introduction

The OroSecurityBundle sits on top of the Symfony security layer to reach protect your resources. This means that each user of your application is granted access to a particular subset of your company’s resources. Coincidentally, they have to be prevented from accessing resources, access was not granted to them.

Access Control Lists

Access Control Lists are an essential part of the Symfony Security component. They are leveraged by the OroSecurityBundle to fulfill the requirements of companies in the business context.

Access Levels

Access can be granted to a user for a certain resource on several levels. The lowest level is the User level. Being on this level means that users can only access resources that have been assigned to them. At the other end of the hierarchy is the System level. Users at this level have the permission to access all records within the whole system without exception. The security bundle comes with the following five levels (ordered up from the bottom of the hierarchy):

LevelConstantDescription
UserBASIC_LEVELThe user is granted access to their own records.
Business UnitLOCAL_LEVELThe user is given access to the records in records in all business units they are assign to.
DivisionDEEP_LEVELThis is the same as the Business Unit level except that the user can also access all resources that are owned by subordinate units of the business units they are assigned to.
OrganizationGLOBAL_LEVELThe user is given access to all records within the organization, regardless of the business unit the object belongs to or the user is assigned to.
SystemSYSTEM_LEVELThe user can access all objects within the system.

Each record is associated with an owning organization. When a user logs into the system, they work in the scope of one of their organizations. If a user is a member in several organizations, they can switch the organization scope that is used to perform access checks.

Note

For all access levels, there is a class constant defined in the AccessLevel class. Its value is shown in the Constant column.

There are two special constants AccessLevel::UNKNOWN (unknown acccess level, should not be assigned to a user) and AccessLevel::NONE_LEVEL (globally deny access for the user).

Permissions

A user can be assigned different modes to a resource. These modes describe what they are allowed to do with the resource. Namely, these permissions are:

Permission 
VIEWWhether or not a user is allowed to view a record.
CREATEWhether or not a user is allowed to create a record.
EDITWhether or not a user is allowed to modify a record.
DELETEWhether or not a user is allowed to delete a record.
ASSIGNWhether or not a user is allowed to assign a record to another user. This permission is only evaluated when an entity is edited.

See also

Read the official documentation for a first insight in the usage of ACLs and more complex Access Control List examples.

Configuring Entities

To be able to protect access to your entities, you first have to configure which permissions can be granted for a user to them. Use the security scope in the defaultValues section of the @Config annotation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/DemoBundle/Entity/Product.php
namespace Acme\DemoBundle\Entity;

use Oro\Bundle\EntityConfigBundle\Metadata\Annotation\Config;

/**
 * @Config(
 *   defaultValues={
 *     ...
 *     "security"={
 *       "type"="ACL",
 *       "permissions"="All",
 *       "group_name="DemoGroup"
 *   }
 * )
 */
class Product
{
    // ...
}

By default (or when using the special ALL value for the permissions property as in the example above), any available permission can be granted to a user on an entity. If you want to restrict the available permissions for an entity, you can list them separated explicitly. For example, you limit it to the VIEW and EDIT permissions:

1
2
3
4
5
6
7
8
9
/**
 * ...
 *     "security"={
 *       "type"="ACL",
 *       "permissions"="VIEW;EDIT",
 *       "group_name"="DemoGroup"
 *     }
 * ...
 */

Protecting Resources

After having configured which permissions a user can be granted to a particular entity, you have to make sure that the permissions are taken into account when checking if a user has access to a resource. Depending on the resource, this check can be performed automatically by the OroSecurityBundle or require some additional configuration made by you.

Restricting Access to Controller Methods

Let us assume that you have configured an entity to be protectable via ACLs. You have granted some of its objects to a set of users. Now you can now control who can enter certain resources through the controller method. Restricting access can be done in two different ways:

  1. Use the @Acl annotation on a controller method, providing the entity class name and the permission to check for:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // src/Acme/DemoBundle/Controller/ProductController.php
    namespace Acme\DemoBundle\Controller;
    
    use Oro\Bundle\SecurityBundle\Annotation\Acl;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    
    class ProductController extends Controller
    {
        /**
         * @Acl(
         *   id="product_edit",
         *   type="entity",
         *   class="AcmeDemoBundle:Product",
         *   permission="EDIT"
         * )
         */
        public function editAction()
        {
            // ...
        }
    }
    
  2. When you need to perform a particular check repeatedly, writing @Acl over and over again becomes a tedious task. This becomes even a more serious issue when your requirements change and you have to change a lot of ACLs. Luckily, you can configure an ACL globally in your bundle configuration and refer to using the ACL id using the @AclAncestor annotation.

    The ACL configuration from the example above looks like this:

    1
    2
    3
    4
    5
    6
    # src/Acme/DemoBundle/Resources/config/oro/acls.yml
    acls:
        product_edit:
            type: entity
            class: AcmeDemoBundle:Product
            permission: EDIT
    

    The annotation of your controller method becomes a lot smaller then:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
     // src/Acme/DemoBundle/Controller/ProductController.php
     namespace Acme\DemoBundle\Controller;
    
     use Oro\Bundle\SecurityBundle\Annotation\AclAncestor;
     use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    
     class ProductController extends Controller
     {
         /**
          * @AclAncestor("product_edit")
          */
         public function editAction()
         {
             // ...
         }
     }
    

    Sometimes you want to protect a controller method coming from code that you do not control. Therefore, you cannot add the @AclAncestor annotation to it. Use the bindings key in the YAML configuration of your ACL to define which method(s) should be protected:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # src/Acme/DemoBundle/Resources/config/oro/acls.yml
    acls:
        product_edit:
            type: entity
            class: AcmeDemoBundle:Product
            permission: EDIT
            bindings:
                - class: Acme\DemoBundle\Controller\ProductController
                  method: editAction
    

See also

You can read detailed explanations for all available YAML configuration options in the reference section.

Using Param Converters

When the @Acl annotation is used without a param converter, the user’s permission is checked on the class level. This means that the user is granted access as long as their access level is not NONE.

When using the @ParamConverter annotation from the SensioFrameworkExtraBundle together with the @Acl annotation, the routing parameters are first converted into the corresponding Doctrine entity object. Then, access will be checked based on the queried object.

See also

It is also possible to protect Doctrine queries.

Data Grids

Records that are part of a data grid are automatically protected by the OroSecurityBundle. View permissions are attached to each record of the data grid.

Protecting Custom DQL Queries

When building custom Doctrine DQL queries, you may want to reduce the result set being returned to the set of domain objects the user is granted access to. To achieve this, use the ACL helper provided by the OroSecurityBundle:

// src/Acme/DemoBundle/Controller/DemoController.php
namespace Acme\DemoBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DemoController extends Controller
{
    public function protectedAction()
    {
        $repository = $this->getDoctrine()->getRepository('AcmeDemoBundle:Product');
        $queryBuilder = $repository
            ->createQueryBuilder('p');
            ->where('p.price > :price')
            ->orderBy('p.price', 'ASC')
            ->setParameter('price', 19.99);
        $aclHelper = $this->get('oro_security.acl_helper');
        $query = $aclHelper->apply($queryBuilder, 'VIEW');

        // ...
    }
}

In this example, first, a query is built that selects all products from the database which are more expensive than 19.99 order by their price. Then, the query builder is passed to the apply() method of the oro_security.acl_helper service. This service, an instance of the AclHelper class, modifies the query only to the return entities the user has access to.

Manual Access Checks

If you need to manually check the access of the current user to a certain object, you can use the isGranted() method from the oro_security.security_facade service for this:

// src/Acme/DemoBundle/Controller/DemoController.php
namespace Acme\DemoBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class DemoController extends Controller
{
    public function protectedAction()
    {
        $entity = ...;
        $securityFacade = $this->get('oro_security.security_facade');

        if (!$securityFacade->isGranted('VIEW', $entity)) {
            throw new AccessDeniedException();
        }

        // ...
    }
}

Examples

The following sections provide some insight on how the ACL checks work. It is assumed that there are two organizations, Main Organization and Second Organization. The Main Organization contains the Main Business Unit, Second Organization contains Second Business Unit. Child Business Unit is a subordinate of Second Business Unit. Additionally, the following users have been created:

UserCreated in OrganizationCreated in Business UnitAssigned to
JohnMain OrganizationMain Business Unit
  • Main Business Unit
  • Child Business Unit
MaryMain OrganizationMain Business Unit
  • Main Business Unit
  • Second Business Unit
MikeSecond OrganizationChild Business Unit
  • Child Business Unit
RobertSecond OrganizationSecond Business Unit
  • Main Business Unit
  • Second Business Unit
MarkSecond OrganizationSecond Business Unit 

User Ownership

Imagine that each user created two accounts (one in Main Organization and one in Second Organization):

Created byMain OrganizationSecond Organization
JohnAccount AAccount E
MaryAccount BAccount F
MikeAccount GAccount C
RobertAccount HAccount D
MarkAccount IAccount J
../../../_images/user-ownership.png

The users can now access the accounts depending on the organization context they login into as described below:

John

Access LevelMain OrganizationSecond Organization
User
  • Account A
  • Account E
Business Unit
  • Account A
  • Account B
  • Account H
  • Account E
  • Account C
Division
  • Account A
  • Account B
  • Account H
  • Account E
  • Account C
Organization
  • Account A
  • Account B
  • Account H
  • Account G
  • Account I
  • Account E
  • Account C
  • Account D
  • Account F
  • Account J

Mary

Access LevelMain OrganizationSecond Organization
User
  • Account B
  • Account F
Business Unit
  • Account B
  • Account A
  • Account H
  • Account F
  • Account D
Division
  • Account B
  • Account A
  • Account H
  • Account F
  • Account D
  • Account C
  • Account E
Organization
  • Account B
  • Account A
  • Account H
  • Account G
  • Account I
  • Account F
  • Account D
  • Account C
  • Account E
  • Account J

Mike

The user Mike cannot login into the Main Organization.

Access LevelSecond Organization
User
  • Account C
Business Unit
  • Account C
  • Account E
Division
  • Account C
  • Account E
Organization
  • Account C
  • Account E
  • Account D
  • Account F
  • Account J

Robert

Access LevelMain OrganizationSecond Organization
User
  • Account H
  • Account D
Business Unit
  • Account H
  • Account A
  • Account B
  • Account D
  • Account F
  • Account E
Division
  • Account H
  • Account A
  • Account B
  • Account D
  • Account F
  • Account E
  • Account C
Organization
  • Account H
  • Account A
  • Account B
  • Account G
  • Account I
  • Account D
  • Account F
  • Account E
  • Account C
  • Account J

Mark

The user Mark cannot login into the Main Organization.

Access LevelSecond Organization
User
  • Account J
Business Unit
  • Account J
Division
  • Account J
Organization
  • Account J
  • Account F
  • Account E
  • Account C
  • Account D

Business Unit Ownership

When the ownership type is “Business Unit”, access cannot be granted on the user level. The minimum acccess level is the Business Unit level.

Imagine that the following data has been created:

AccountOrganizationBusiness Unit
Account AMain OrganizationBusiness Unit A
Account BMain OrganizationBusiness Unit A
Account CSecond OrganizationBusiness Unit C
Account DSecond OrganizationBusiness Unit B
Account ESecond OrganizationBusiness Unit B
../../../_images/business-unit-ownership.png

The users can now access the accounts as described below:

John

Access LevelMain OrganizationSecond Organization
Business Unit
  • Account A
  • Account B
  • Account C
Division
  • Account A
  • Account B
  • Account C
Organization
  • Account A
  • Account B
  • Account C
  • Account D
  • Account E

Mary

Access LevelMain OrganizationSecond Organization
Business Unit
  • Account A
  • Account B
  • Account D
  • Account E
Division
  • Account A
  • Account B
  • Account D
  • Account E
  • Account C
Organization
  • Account A
  • Account B
  • Account D
  • Account E
  • Account C

Mike

The user Mark cannot login into the Main Organization.

Access LevelSecond Organization
User
  • Account J
Business Unit
  • Account J
Division
  • Account J
Organization
  • Account J
  • Account F
  • Account E
  • Account C
  • Account D

Robert

Access LevelMain OrganizationSecond Organization
Business Unit
  • Account A
  • Account B
  • Account C
Division
  • Account A
  • Account B
  • Account C
Organization
  • Account A
  • Account B
  • Account C
  • Account D
  • Account E

Mark

The user Mark cannot login into the Main Organization.

Access LevelSecond Organization
User
  • Account J
Business Unit
  • Account J
Division
  • Account J
Organization
  • Account J
  • Account F
  • Account E
  • Account C
  • Account D

Organization Ownership

When the ownership type is “Organization”, access cannot be granted on the user level, the business level or the division level. The minimum acccess level is the Organization level.

Imagine that the following data has been created:

AccountOrganization
Account AMain Organization
Account BMain Organization
Account CSecond Organization
Account DSecond Organization
Account ESecond Organization
../../../_images/organization-ownership.png

The users can now access the accounts as described below:

John, Mary, Robert

Access LevelMain OrganizationSecond Organization
Organization
  • Account A
  • Account B
  • Account C
  • Account D
  • Account E

Mike, Mark

The users cannot login into the Main Organization.

Access LevelSecond Organization
Organization
  • Account C
  • Account D
  • Account E
Browse maintained versions:2.62.32.01.12
Forums
Back to top