adamnicholson / judge
Authorisation library
Installs: 21 783
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 2
Requires (Dev)
- flatbase/flatbase: dev-master
- phpunit/phpunit: ~4.5
This package is auto-updated.
Last update: 2024-10-17 23:11:00 UTC
README
Authorization package for PHP.
Getting Started
Roles & Identities
"Roles" are things that users of your application can do. These are represented simply as text strings, and are usually things like "EDIT_ORDERS", "VIEW_LOGS", etc.
"Identities" represent the users of your application. Like roles, they are represented as a string. An identity could be some account specific key like an email address or user ID, or it could be something completely different like "console" or "API", or even "anonymous".
Keeping this mental separation between accounts and identities is key - Judge doesn't have any knowledge of, or care about, accounts or users.
To get started with some basic role-based auth, there is no setup required. Just instantiate Judge
, and allow
an an identity access to a role:
$judge = new Judge(); // Grant access to edit orders $judge->allow('adam@example.com', 'EDIT_ORDERS'); $judge->check('adam@example.com', 'EDIT_ORDERS'); // true // Revoke access to edit orders $judge->deny('adam@example.com', 'EDIT_ORDERS'); $judge->check('adam@example.com', 'EDIT_ORDERS'); // false
Simple enough? Good, now let's go a little deeper.
Role Contexts
Simple role-based permissions suit a lot of use cases, but often you hit their limits quickly when an application starts to grow. Say you have a role for "EDIT_ORDERS" which applies to all orders, but you need a single identity to have access to only edit 1 specific order. How would you do this?
In Judge, the way you would achieve this is by using role "contexts". A role context is a third parameter you can pass to allow()
, deny()
and check()
to add more specificity to your rule. In practice, a context is usually used for unique identifiers such as an order ID:
$judge->allow('adam@example.com', 'EDIT_ORDERS', '5'); $judge->check('adam@example.com', 'EDIT_ORDERS', '5'); // true
The benefit of role contexts is due to their ability to inherit rules. If an rule has not been explicity granted/revoked for the exact role+context combination, then Judge will fallback to check if the identity has been granted/revoked for that role without the context:
$judge = new Judge(); // Grant access to edit all orders $judge->allow('adam@example.com', 'EDIT_ORDERS'); // Override the above, revoking access specifically to order "10" $judge->deny('adam@example.com', 'EDIT_ORDERS', '10'); $judge->check('adam@example.com', 'EDIT_ORDERS'); // true $judge->check('adam@example.com', 'EDIT_ORDERS', '5'); // true $judge->check('adam@example.com', 'EDIT_ORDERS', '10'); // false
Role Inheritence
Roles & contexts together can solve many auth requirements, but larger systems may need more complex inheritance rules. Here is where we introduce role "parents".
Configuring a role parent looks like this:
$judge->getRepository()->addRole('{ROLE}', '{PARENT_ROLE}');
If you call check()
but the identity has not been explicitly granted or revoked access to the role, but that role has a defined parent, then we fallback to check if a rule exists for the parent:
$judge = new Judge(); // Setup the "EDIT_ORDERS" role with the parent "ORDERS" $judge->getRepository()->addRole('EDIT_ORDERS', 'ORDERS'); // Grant access to ORDERS and all child roles $judge->allow('adam@example.com', 'ORDERS'); $judge->check('adam@example', 'EDIT_ORDERS'); // true
You can have as many levels in your role parent hierarchy as you like:
$judge = new Judge(); $repo = $judge->getRepository(); $repo->addRole('DELETE_ORDERS', 'CHANGE_ORDERS'); $repo->addRole('CHANGE_ORDERS', 'VIEW_ORDERS'); $repo->addRole('VIEW_ORDERS', 'ORDERS'); // Grant access to ORDERS and all children $judge->allow('adam@example.com', 'ORDERS'); $judge->check('adam@example', 'EDIT_ORDERS'); // true // Override the above for access to CHANGE_ORDERS and its children recursively $judge->deny('adam@example.com', 'CHANGE_ORDERS'); $judge->check('adam@example', 'ORDERS'); // true $judge->check('adam@example', 'VIEW_ORDERS'); // true $judge->check('adam@example', 'CHANGE_ORDERS'); // false $judge->check('adam@example', 'DELETE_ORDERS'); // false
Identity Inheritence
Identity inheritence works just like role inheritence:
$judge = new Judge(); $repo = $judge->getRepository(); $repo->addIdentity('adam', 'customer_service'); $judge->allow('customer_service', 'EDIT_ORDERS'); $judge->check('adam', 'EDIT_ORDERS'); // true
Putting it all together
Role contexts, role parents, and identity parents all stack, working together to give a truly robust authorisation system.
In order of precidence:
- Exact match of the identity-role-context combination passed
- Parent identities with the same role & context
- The identity + role passed without any context
- Parent identities with the same role without any context
- Parent roles with the original identity
- Parent roles with parent identities
Logic Flow
The Data Structure
Roles:
Identities:
Rules:
Persistence
The main problem with all of the examples so far is that roles & identities are not persisted across requests - meaning you'd need to reconfigure Judge to understand your identity/role hierarchies, and allow/deny all the relevant permissions, on every request.
These rules/roles/identities can be persisted between requests by using a Judge\Repository\Repository
, which can be passed to Judge::__construct()
as the first argument.
Judge ships with a number of Judge\Repository\Repository
implementations to use out of the box:
PDORepository
: Store data to a PDO compatible DB, like MySQL, SQLite, PostgreSQLFlatbaseRepository
: Store to a flat file databaseArrayRepository
: Store data in an array for the duration of this request
If you do not pass a
Repository
to Judge's constructor, thenArrayRepository
will be used by default.
PDORepository
Store data to a PDO compatible DB, like MySQL, SQLite, PostgreSQL
$pdo = new PDO('mysql:host=db;dbname=site', 'root'); $repo = new Judge\Repository\PDORepository($pdo); $judge = new Judge\Judge($repo);
You may wish to use the PDORepository
in conjunction with the LazyRepositoryWrapper
, so that you do not have to instantiate a PDO
connection until it is actually requested.
$repo = new Judge\Repository\LazyRepositoryWrapper(function () { $pdo = new PDO('mysql:host=db;dbname=site', 'root'); return new Judge\Repository\PDORepository($pdo); }); $judge = new Judge\Judge($repo);
ArrayRepository
Store data in an in-memory array for the duration of this process.
$judge = new Judge\Judge(new ArrayRepository);
FlatbaseRepository
Store to a flat file database.
$storage = new Flatbase\Storage\Filesystem('/some/storage/path'); $flatbase = new Flatbase\Flatbase($storage); $repo = new Judge\Repository\FlatbaseRepository($pdo); $judge = new Judge\Judge($repo);