Skip to content

Instantly share code, notes, and snippets.

@mindplay-dk
Created March 12, 2014 21:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mindplay-dk/9516824 to your computer and use it in GitHub Desktop.
Save mindplay-dk/9516824 to your computer and use it in GitHub Desktop.
Authorization service and context
<?php
/**
* Provides authorization services
*/
class AuthorizationService
{
/** @var AuthorizationRules */
public $rules;
public function __construct(AuthorizationRules $rules)
{
$this->rules = $rules;
}
/**
* Factory method: creates an AuthorizationContext
*
* @return AuthorizationContext
*/
public function context($user_id)
{
$context = new AuthorizationContext($this->rules);
$context->user_id = $user_id;
return $context;
}
}
/**
* Defines stateless (context free) authorization rules (business logic)
*/
class AuthorizationRules
{
/** @return bool */
protected function isAdmin($user_id)
{
return $user_id === 1; // user with ID 1 is the admin
}
/** @return bool */
protected function isPostOwner($user_id, $post_id)
{
// in real life we would load $post model and check $post->author_id or something.
return $post_id == 2 && $user_id == 2; // mock: user 2 can edit post 2
}
/** @return bool */
protected function postIsActive($post_id)
{
}
/** @return bool */
public function canEditPost($user_id, $post_id)
{
return $this->isAdmin($user_id)
|| $this->isPostOwner($user_id, $post_id);
}
/** @return bool */
public function canDeletePost($user_id, $post_id)
{
return $this->isAdmin($user_id)
|| $this->isPostOwner($user_id, $post_id)
&& $this->postIsActive($post_id);
}
}
/**
* Model defines authorization parameters (as mutable state)
*
* Mirrors the shape of AuthorizationRules, but methods (typically) have no arguments
* and contain no logic - they simply delegate to the matching AuthorizationRule method.
*/
class AuthorizationContext
{
/** @var AuthorizationRules */
public $rules;
/** @param int $user_id */
public function __construct(AuthorizationRules $rules)
{
$this->rules = $rules;
}
/** @var int */
public $user_id;
/** @var int */
public $post_id;
/** @return bool */
public function canEditPost()
{
return $this->rules->canEditPost($this->user_id, $this->post_id);
}
/** @return bool */
public function canDeletePost()
{
return $this->rules->canDeletePost($this->user_id, $this->post_id);
}
}
// TEST:
function test($name, $result) {
echo $result === true
? "- OK: $name\n"
: "- FAIL: $name\n";
}
$auth = new AuthorizationService(new AuthorizationRules);
// direct checks via AuthorizationRules:
test('correctly initializes a new context', $auth->context(1)->user_id === 1);
test('can perform direct access control check', $auth->rules->canEditPost(1, 10));
$context = $auth->context(1); // user 1 is admin
$context->post_id = 10;
test('can perform indirect access control check', $context->canEditPost() === true); // admin can edit any post
$context->user_id = 2;
$context->post_id = 2;
test('can perform indirect access control check', $context->canEditPost() === true);
// that all works
// now put your AuthorizationService somewhere central in your application
// then add a simple filter class or "before action" method to the controller in your favorite framework.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment