Welcome Guest, Not a member yet? Register   Sign In
Dealing with permissions (hooks / libraries)
#1

[eluser]Kyle Johnson[/eluser]
Okay, so I have a fairly full features permissions/roles system in place. It supports everything I need it to, so I don't want to switch authentication systems to something like FlexiAuth (although I believe I would have the same issue).

My question is really how can I check permissions BEFORE entering the method they're trying to access using a specific permission to access that method? By default I want to deny access.

I originally extended the CI_Controller and had the checks in the constructor based on the URI they were accessing. However this required that every function have its own permission.

The second thing I did was use the "hooks" to define a "controller specific" permission using the "post_controller_constructor" hook. I could then define explicit permissions in the beginning of each method, or if they have the controller permission they get access to everything.

Code:
class Welcome extends MY_Controller {
function __construct() {
  parent::__construct();
  $this->_controller_permission = 'client';
}

// after constructor finishes we check they have access to the 'client' permission
// if they don't they are redirected to a page that has only the requirement of being logged in

public function index()
{
  // this defaults to being allowed because they have the _controller_permission of 'client'
  // i would like this to be disallowed or revert to a higher level permission without explicitly requiring
  // setting the permission.  Any idea how to do that??
  echo "Hello index.";
}

public function test() {
  // explicitly set to require the 'client' permission
  $this->requires_permission('client');
  echo 'hello';
}

public function test2() {
  // will fail and redirect to a different page because they don't have the 'global/admin' permission
  $this->requires_permission('global/admin');
  echo 'hello2';
}

public function test3() {
  // can also just check permissions without redirecting
  if($this->requires_permission('global/admin',FALSE) {
   echo "You do not have permissions, but we didn't redirect you.";
  }
}
}

Any thoughts on how to accomplish this, or do I just have to suck it up and explicitly define higher permissions for each internal method and allow for a "controller" level permission being the default? Alternatively, suck it up and manage permissions for every controller/method?

Realistically, I think the only way to deal with the "deny by default" using this approach is to manage a permission for each method. If a controller level permission isn't set, then it defaults to "global/admin" which is only given to a small set of people. This is close to being "deny by default" ... but I'm curious to how you guys would (or think) set these types of permissions up.
#2

[eluser]PhilTem[/eluser]
I personally set up my RBACL the way that all permissions for a certain group are checked in the __construct() of the corresponding base-controller or the controller itself. Thus

Code:
<?php
ICF_Controller extends MX_Controller {}

class Public_Controller extends ICF_Controller {}

class Authenticated_Controller extends Public_Controller{}

class Admin_Controller extends Authenticated_Controller {
  public function __construct()
  {
    $this->acl->restrict('admin');
  }
}

class Users extends Admin_Controller {
  public function __construct()
  {
    $this->acl->restrict('admin.users');
  }
  
  public function create()
  {
    $this->acl->restrict('admin.users.create');
  }
}

This way I have finely granulated power of the resources accessed and the user gets an error message shown as soon as the first permission is denied.

I was thinking of not doing this and putting the permission logic in Acl::_construct() so that it takes $uri->uri_string(); and replaces slashes with dots to get the resource node. But then I thought, what if the URI is routed or does have nothing to do with the actual resource?

That's why I put all the restrict()-calls inside every method as the very first line. Might not be the DRY'est approach, but so far I don't have any other approach in mind... Oh wait, you could define a Controller-property that maps all the methods to its respective resource and then check it in the __construct() method automatically (or inside the _remap()-method()). But that just came to my mind, don't know if this is quickly implementable or fail-safe.

To briefly explain how my RBACL works:
There are certain resources mapped to roles. Users are assigned these roles and every role has "explicitly denied" or "explicitly allowed" permissions. By default, resources that are not set are denied. But that only applies to top-level resources (those that have one word and no dot inbetween - a dot is my way of getting resource levels). Now, the ACL checks for a permission to 'admin.users.create' which is not explicitly set. It strips of the '.create' part and looks for a permission 'admin.users'. Which is not explicitly set thus strips of '.users'. And finally tries 'admin'. This is either set (with denied or allowed) or, if it's not set, get's denied.

I guess, what your code is missing, is the recursive search for permissions i.e., going from more specific permissions to less specific permissions...

Did that solve your problem or help you in any way? Smile
#3

[eluser]Kyle Johnson[/eluser]
[quote author="PhilTem" date="1360237160"]
I was thinking of not doing this and putting the permission logic in Acl::_construct() so that it takes $uri->uri_string(); and replaces slashes with dots to get the resource node. But then I thought, what if the URI is routed or does have nothing to do with the actual resource?
[/quote]

That's why I wanted to stay away from URI based permissions, but I gave it a try. Mostly because I have some AJAX lookups that get called that really only require the higher level "client/inventory" permission, and didn't want to have to explicitly create 10 permissions just because I have 10 different AJAX calls.

[quote author="PhilTem" date="1360237160"]
Did that solve your problem or help you in any way? Smile
[/quote]

I think so. I will have the controller (or __construct() based) permission that they require to even get to the controller.

Each method inside should have an additional permission check as the first line. (I wanted a way to enforce that the called method has said permission check, but that seems difficult to implement without some sort of wrapper class.)

Users are redirected on the first permission they don't have access to.


#4

[eluser]Kyle Johnson[/eluser]
I was going over this with another developer today at the office, and we figured out a way of requiring permissions for each individual function that doesn't complicate the system very much.

My post_constructor_hook (entire class):
Code:
class Acl_Hook {
var $CI;
private $_pass_through_controllers = array('auth','wsvc'); // these controllers do not require immediate login credentials

function Acl_Hook() {
  $this->CI =& get_instance();
}

function check_permissions() {
  $class = $this->CI->router->class;
  if( ! in_array($class, $this->_pass_through_controllers)) {
   if ($this->CI instanceof MY_Controller) {
    $method = $this->CI->router->method;
    if(array_key_exists($method, $this->CI->perm_array)) {
     $this->CI->acl->restrict($this->CI->perm_array[$method]);
    } else {
     show_error(sprintf("No permission defined for %s/%s",$class,$method));
    }
   } else {
    show_error("The page you are accessing is using an unauthorized controller.  Please contact the administrator.",403,"Unauthorized Controller");
   }
  }
}
}

My application/config/hooks.php:
Code:
$hook['post_controller_constructor'][] = array(
  'class'    => 'Acl_Hook',
  'function' => 'check_permissions',
  'filename' => 'Acl_hook.php',
  'filepath' => 'hooks',
  'params'   => ""
);

Inside the constructor for each controller, we define all permissions.
Code:
function __construct() {
  parent::__construct();
  $this->perm_array = array(
    'index' => 'admin/MODULE_NAME/index',
    'create' => 'admin/MODULE_NAME/create'
    );
}

If a method is called that does not have a permission in the $perm_array, then it will give an error saying that the permission is not defined.

This process requires a bit more typing, but is more secure.




Theme © iAndrew 2016 - Forum software by © MyBB