psecio

Protecting your application with PropAuth (Property-based Policy evaluation)

Library: PropAuth (Property-based policy evaluation)

I’ve been working on a library for a while now that kind of distills down some of the ideas of property-based authorization (like XACML) and makes it a bit more accessible to the average developer. Property-based evaluation can be a little tricky to get your head around if you’re used to the usual RBAC world. Let me introduce it briefly.

Property-based evaluation is more or less what it sounds like: a system checks the properties of an object (or objects) and looks for different kinds of matches. That much is pretty simple but then you get into the “policies” aspect. This is where the real power comes in. With policies you can define the pass/fail requirements for the checks against an object and see if there’s a good enough match. With something like XACML it gets pretty complicated as it defines policies with XML documents (and we all know how “simple” XML is). There’s all sorts of different combining algorithms for the results like “first wins” or “all must match”. These can, of course, be nested and combined themselves leading to pretty complex policies and a mess if you’re not careful.

So, back to PropAuth now. In the work that I’ve been doing I’ve only really seen the need for a more streamlined version of this kind of evaluation. Some of the overall flexibility that XACML provides hasn’t been included in PropAuth, but I haven’t found much of a need for that so far anyway (like nested policies). The PropAuth library provides some of the basic property evaluation handling and policy creation I think could replace a lot of the mish-mash of role-based access control functionality out there and make for much more reusable code.

First off, to install just use Composer:

composer require psecio/propauth

And here’s a simple example of a policy evaluation:

<?php
require_once 'vendor/autoload.php';

$enforcer = new \Psecio\PropAuth\Enforcer();
$myUser = (object)[
    'username' => 'ccornutt'
];

$myPolicy = new \Psecio\PropAuth\Policy();
$myPolicy->hasUsername('ccornutt');

echo 'Result: '.var_export($enforcer->evaluate($myUser, $myPolicy), true);
?>

Here’s a quick summary of what’s happening here: the Enforcer object is the “frontend” that handles most of the work. You pass in a subject for the evaluation and the policy to evaluate against. In this case I’ve used the “hasUsername” check to look at the “username” property on the object and check to see if it matches the “ccornutt” value. The “evaluate” method is then called and a true/false result is returned…in this case true as our “myUser” object has a matching username.

This only scratches the surface of what the PropAuth library can do but it gives you an idea of how the evaluations are set up.

There’s also a simplified interface you can use with the same library to perform an authentication of a user with a system that uses bcrypted passwords (like with the password hashing API):

<?php
require_once 'vendor/autoload.php';

$password = $_POST['password'];

$myUser = (object)[
    'username' => 'ccornutt',
    'password' => password_hash('test1234', PASSWORD_DEFAULT)
];

$gate = new \Psecio\PropAuth\Gateway($myUser);
$subject = $gate->authenticate($password);
?>

If the password matches, you’ll be given an authenticated version of the “subject” back from the “authenticate” call. If not, you’ll get a false back on failure. Naturally, since this is all powered by the same library, you can also throw in policy checks too (warning: a bit more complicated example here):

<?php
require_once 'vendor/autoload.php';

$password = $_POST['password'];

$myUser = (object)[
    'username' => 'ccornutt',
    'password' => password_hash('test1234', PASSWORD_DEFAULT),
    'groups' => ['group1']
];

$policy = Policy::instance()->hasGroups(['group1', 'group2'], Policy::ANY);
$context = new Context([
    'policies' => PolicySet::instance()->add('policy1', $policy)
]);

$gate = new Gateway($myUser, $context);
if ($gate->authenticate($password) !== false && $gate->can('policy1')) {
    echo "They're authenticated and they have one of the required groups in the policy!";
}

?>

There’s a lot more going on here with a Policy Set and some of the “instance” calls but it’s all explained in the PropAuth documentation. I’d be interested in your feedback on the library and if you think it might be useful in your apps.

I’ve also put together a Provider for Laravel 5 applications that makes it simpler to incorporate the checks into both the controllers and your Blade templates, evaluating the policies directly.

There’s also a bit more detailed tutorial on using PropAuth over on the websec.io site with more information.

Laravel Route Protection with Invoke

I started on a tool a while back to “scratch an itch” in a personal project to make it easier to protect endpoints based on the requested URL. The Invoke library makes it possible to detect the route requested and ensure a set of criteria are met to be sure a user can access a resource. This isn’t anything new or revolutionary, but it is something I couldn’t find separate and effectively decoupled from other tools. It’s loosely based on how Symfony (v1) does it’s route protection, right down to the .yaml file that it uses for configuration. In my case, I was using it in a Slim framework-based application to evaluate the current user to see if they had the required groups and permissions.

More recently, though, I’ve been messing with Laravel and since they’ve been putting a heavy emphasis on middleware, I thought I’d see how tough an integration might be. I wanted to use Invoke to check my Laravel user to see if they met my requirements. Fortunately, it turned out to be super easy.

Here’s how I did it – hopefully it can be useful for you. I’ll provide one caveat though: Laravel’s default auth handling only sets up users, not groups/permissions, so you’ll need a way to manage those but that’s outside the scope of this. You’ll see how it integrates in a bit.

First off, we need to get Invoke installed via Composer:

composer require psecio/invoke

Once that’s installed, we need to set up our middleware. This will go in with the rest of the default middleware in the app/Http/Middleware folder in your application. Create a file called InvokeMiddleware.php with this code:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth as Auth;

class InvokeMiddleware
{
    /**
     * Handle an incoming request and validate against Invoke rules
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $en = new \Psecio\Invoke\Enforcer(app_path().'/../config/routes.yml');
        $user = Auth::user();

        $allowed = $en->isAuthorized(
            new \App\InvokeUser($user),
            new \Psecio\Invoke\Resource($request->path())
        );

        if ($allowed !== true) {
            return response('Unauthorized.', 401);
        }

        return $next($request);
    }
}

You’ll see there’s a reference to an InvokeUser class in there. Let’s make that next. In app/InvokeUser.php put this code:

<?php

namespace App;

class InvokeUser implements \Psecio\Invoke\UserInterface
{
	private $user;

	public function __construct($user)
	{
		$this->user = $user;
	}

	public function getGroups()
	{
		// This is where you fetch groups
		return [];
	}

	public function getPermissions()
	{
		// This is where you fetch permissions
		return [];
	}

	public function isAuthed()
	{
		return ($this->user !== null);
	}

}

Then, to turn it on, edit your app/Http/Kernel.php class and add this to the list of $middleware:

\App\Http\Middleware\InvokeMiddleware::class,

Viola, you’re set to go using Invoke. Now, the next step is to define your rules. You’ll notice in the middleware above we’re loading the config/routes.yml configuration file for our rules. Let’s make one of those with a super simple example. In app/config/routes.yml put:

/:
  protected: on

This configuration is telling Invoke that, when you hit the base URL of your application (“/”) it’s protected. This means that it requires a logged in user. If you’ve just added this to your application and try to load the main page without a user, you’ll be given the unhappy “Forbidden” message instead of your lovely site. It’s the check in InvokeUser::isAuthed that evaluates for this, checking to see if the user is null (no valid logged in user).

That’s it…it’s a pretty simple integration to get just get bare minimum up and running. If you’re interested in how to add group and permission checking to this, forge ahead and keep reading.

So we have our basic yaml configuration file with protection turned on. Say we wanted to add in group and permission checks too. I’ve already talked some about this kind of handling in a different post but I’ve more recently simplified it even more, no longer requiring extra classes in the mix.

Let’s start by changing our configuration file to tell Invoke that we want to be sure the user is in the “admin” group and has a permission of “delete_user” to access the /admin/user/delete resource:

/admin/user/delete:
  protected: on
  groups: [admin]
  permissions: [delete_user]

When you fire off the page request for that URL, Invoke will try to call the InvokeUser::getGroups and InvokeUser::getPermissions methods to return the user’s current permission set. Before it required you to use classes that implemented the InvokeGroup and InvokePermission interfaces for each group/permission. I streamlined this since it’s really only evaluating string matches and allowed those methods to either return a set of objects or of strings. Let’s update the InvokeUser class to hard-code in some groups/permissions for return:

<?php

namespace App;

class InvokeUser implements \Psecio\Invoke\UserInterface
{
        /** ...more code... */

	public function getGroups()
	{
		return ['admin','froods'];
	}

	public function getPermissions()
	{
		return ['delete_user','view_user','update_user'];
	}
        /** ...more code... */
}

Ideally you’d be fetching these groups and permissions from some role-based access control system (maybe, say Gatekeeper) and returning real values. These hard-coded values will work for now.

Since the user has all the requirements, Invoke is happy and they’re able to move along and delete all the users they want.

I’ve tried to keep the class as simple as possible to use and I’m definitely open to suggestions. There’s a few additions I’d though about including adding HTTP method matching (different rules for POST than GET) and other match types than just groups and permissions. Let me know if you’d like to see something else included in Invoke – I’d love to chat!

Gatekeeper & Policies

I’ve been working on a system for a while now, inspired by the work that was done on the Sentry project, to provide a role-based access control system that was not only more well-maintained but also built on the foundation they provided to add in some new features. My “little project” Gatekeeper has really grown over time and (I think) really evolved into something that’s quite useful.

With this progression in mind, I’ve recently added another new feature that sits on top of the permissions and groups system that allows you to create reusable policies. Policies are a common concept when it comes to access control. They can make performing complex operations a lot simpler and, in the case of how it’s implemented here, make it much more reusable across the entire system (or multiple systems). Checking user permissions and groups is a relatively simple operation if you’re just doing one or two checks. You’d end up with something like this:

<?php
use \Psecio\Gatekeeper\Gatekeeper as g;

$user = g::findUserById(1);
if ($user->inGroup("group1") && $user->hasPermission("perm1")) {
    echo "Good to go!";
}

?>

In this case, the check is relatively simple but there’s one think that any DRY code advocate could tell you – this exact check would need to be reproduced throughout the entire application exactly as stated to ensure the evaluation is the same. Even worse, if the requirements changed you’d have to work across the entire application and replace all instances with the new logic.

This is where policies can come in very handy. With the functionality that Gatekeeper includes, they’re dead simple to use too. The key is in their use of the Symfony Expression Language component. This language allows you to define text strings that represent logic and allow for more complex and self-contained evaluation. Enough talk, let’s see how we can use these policies to perform the same check as above.

<?php
// First we'll make the policy - this only needs to be done once
Gatekeeper::createPolicy(array(
    'name' => 'admin-test1',
    'expression' => '"group1" in user.groups.getName() and "perm1" in user.permissions.getName()',
    'description' => 'See if a user has "permission1" and is in "group1"'
));

// Now, we need to evaluate the user against the policy
if (Gatekeeper::evaluatePolicy('admin-test1', $user) === true) {
    echo 'They have the permission! Rock on!';
}

?>

It’s a little more verbose than the previous example, but you can see how it would fetch the permissions and groups for the user and check it against the set of names. In this case the getName function is a magic method that filters the collection and returns a set of the name property values as an array. This way it can be used with the incheck. Once the policy is in place, then any time you need to perform that evaluation, all you need to do is call the evaluatePolicy method with the information and it will always execute the same logic making it super portable and DRY.

I also mentioned how it helps with changing requirements. All you’d need to do here is change the policy contents (the expression string) and all of the code already in place will now evaluate with that new logic with no code changes required. Easy, huh?

I hope this functionality will be useful if you’re a Gatekeeper user or, if you’re not, may give you a reason to check it out. I’m also interested to hear if you think this might make for a good stand-alone component, abstracted out from the Gatekeeper system. It’s integrated right now because of the known model/relationship structure but it’s not hard to pull it out and make it abstract enough to use for other systems.