How to Restrict Admin Roles Even Further in Magento

Magento’s built in administrative roles and permissions system are very robust, allowing any developer to add any ACL (Access Control List) paths that they want in their module’s etc/adminhtml.xml file. (As a side note, it is standard Magento practice to place ACL within this file and not within your config.xml file or any other file which is parsed into the loaded Magento configuration.) ACL allows us to restrict admin roles to view or edit certain sections of the administrative backend. For a great, in depth look at ACL, check out Alan Storm’s blog post.

To follow along, download our Demac Adminpermissions Module.

Why Restrict Admin Roles in Magento?

We recently had a client request that, in addition to administrators being restricted to viewing certain sections of the backend, they wanted certain roles to restrict seeing parts of collections loaded. Specifically, admins with certain roles that are marked as “restrictive roles” should be able to only see orders placed from certain provinces in Canada. Instead of hardcoding which roles should do what, we gave them full control over who sees and can edit what. This blog post is mainly explaining a specific situation, with sections explained so you can change the code to accommodate changing admin permissions / collections in the backend yourself!

Adding the ‘Restrict Role by Region’ Dropdown

We can see in the above screenshot that we’ve added a field for restricting the administrator role by region! This is found in System > Permissions > Roles. To insert this here, we’ll need to do some layout XML and templating work. Here is our adminhtml layout xml file content:

<?xml version=”1.0″?>

<layout>

   <!– add region multiselect to admin roles edit page –>

   <adminhtml_permissions_role_editrole>

       <reference name=”adminhtml.permissions.tab.rolesedit”>

           <block type=”demac_adminpermissions/role_region” as=”roleRegion” template=”demac/adminpermissions/rolesedit/region.phtml” />

       </reference>

   </adminhtml_permissions_role_editrole>

</layout>

We can see that we’re using the existing adminhtml_permissions_role_editrole handle, and we’ll insert our own block in here which uses its own template. We’re not modifying any controllers here for saving this value, but instead using an observer hooking into the event admin_permissions_role_prepare_save. Let’s look into our Observer.php file for our function which hooks into that:

public function saveRolesPermissions($observer)

   {

       $request = $observer->getEvent()->getRequest();

       $role = $observer->getEvent()->getObject();

       $restrictByRegion = (bool) $request->getParam(‘restrict_by_region’);

       $role->setRestrictByRegion($restrictByRegion);

   }

Pretty simple, lean observer method, but this is setting data on the role which is not installed by Magento by default (specifically, ‘restrict_by_region’ is not a field associated with admin roles in default Magento.) Let’s take a look at our install script below:

$installer = $this;

/* @var $installer Mage_Core_Model_Resource_Setup */

$installer->startSetup();

$adminRolesTableRoles = $installer->getTable(‘admin/role’);

$installer->getConnection()->addColumn($adminRolesTableRoles, ‘restrict_by_region’, “tinyint(1) NOT NULL DEFAULT 0”);

$adminUserTableRoles = $installer->getTable(‘admin/user’);

$installer->getConnection()->addColumn($adminUserTableRoles, ‘region_restrictions’, “VARCHAR(255)”);

$installer->endSetup();

We can see that we’re adding a column to the admin_role table, and a column to the admin_user table. The way our attribute works is that we can set a yes/no flag on a user role for if they will be restricted by region, and if an admin user has that role, they will have the following multi-select when editing the admin role:

This multiselect will save as comma-separated values in our admin_user table under the column that we just created, ‘region_restrictions’. It saves via an observer method as well (event is admin_user_save_before and the method we’re using is named saveUserRegionRestriction.)

Adding the ‘Restrict by Region’ Multiselect

This multiselect is added by rewriting the Mage_Adminhtml_Block_Permissions_User_Edit_Tab_Main class and is added via the addField() method as seen in this code snippet:

if (Mage::helper(‘demac_adminpermissions’)->canRestrictByRegion($model)) {

               // By Demac

               // Add our custom multiselect for provinces / states / regions

               $fieldset->addField(‘region_restrictions’, ‘multiselect’,

                   array(

                       ‘name’      => ‘region_restrictions[]’,

                       ‘label’     => Mage::helper(‘adminhtml’)->__(‘Restrict by Region’),

                       ‘id’        => ‘region_restrictions’,

                       ‘values’    => Mage::getModel(‘demac_adminpermissions/system_region’)->getRegionValuesForForm()

                   )

               );

           }

Actually Changing the Content Admins See

Perhaps the most important part of this module is to actually filter the order grid collection to reflect the current admin user’s restrictions. Within the observer event sales_order_grid_collection_load_before, we can hook into the order grid collection before it’s even loaded and make the appropriate modifications there.

public function filterOrdersByAdminRegionRestrictions($observer)

   {

       $user = Mage::getSingleton(‘admin/session’)->getUser();

       $orderGridCollection = $observer->getEvent()->getOrderGridCollection();

       if ($user->getRegionRestrictions()) {

           $this->_filterByRegionRestriction($user, $orderGridCollection);

       }

   }

We can see here that we just hook into the event and then call _filterByRegionRestriction() to do the dirty work:

protected function _filterByRegionRestriction($user, $collection)

   {

       $regionRestrictions = explode(‘,’, $user->getRegionRestrictions());

       $collection

           ->getSelect()

           ->distinct()

           ->join(array(‘oa’ => ‘sales_flat_order_address’), ‘main_table.entity_id = oa.parent_id’, array(‘region_id’));

       $collection->addFieldToFilter(‘oa.address_type’, ‘shipping’);

       $orConditions = array();

       foreach ($regionRestrictions as $regionRestriction) {

           $orConditions[] = array(

               ‘eq’    => intval($regionRestriction)

           );

       }

       $collection

           ->addFieldToFilter(

               array(‘oa.region_id’),

               array(

                   $orConditions

               )

           );

       return $collection;

   }

With a simple join on sales_flat_order_address, we can find the order address information and filter based on that. Since we have a multi-select we need to establish or conditions if there’s more than one province selected. This is the perfect function to modify if you want any custom logic on what appears within your collection.

That is the basis of Demac AdminPermissions. To test it out, assign a province to your restricted role and see if you can only see orders from that province.