Rewrite of the authentication and authorization system

This commit is contained in:
Frederic Guillot
2015-12-05 20:31:27 -05:00
parent 346b8312e5
commit e9fedf3e5c
255 changed files with 14114 additions and 9820 deletions

View File

@@ -18,6 +18,14 @@ class AccessMap
*/
private $defaultRole = '';
/**
* Role hierarchy
*
* @access private
* @var array
*/
private $hierarchy = array();
/**
* Access map
*
@@ -39,16 +47,77 @@ class AccessMap
return $this;
}
/**
* Define role hierarchy
*
* @access public
* @param string $role
* @param array $subroles
* @return Acl
*/
public function setRoleHierarchy($role, array $subroles)
{
foreach ($subroles as $subrole) {
if (isset($this->hierarchy[$subrole])) {
$this->hierarchy[$subrole][] = $role;
} else {
$this->hierarchy[$subrole] = array($role);
}
}
return $this;
}
/**
* Get computed role hierarchy
*
* @access public
* @param string $role
* @return array
*/
public function getRoleHierarchy($role)
{
$roles = array($role);
if (isset($this->hierarchy[$role])) {
$roles = array_merge($roles, $this->hierarchy[$role]);
}
return $roles;
}
/**
* Add new access rules
*
* @access public
* @param string $controller
* @param string $method
* @param array $roles
* @param string $controller Controller class name
* @param mixed $methods List of method name or just one method
* @param string $role Lowest role required
* @return Acl
*/
public function add($controller, $method, array $roles)
public function add($controller, $methods, $role)
{
if (is_array($methods)) {
foreach ($methods as $method) {
$this->addRule($controller, $method, $role);
}
} else {
$this->addRule($controller, $methods, $role);
}
return $this;
}
/**
* Add new access rule
*
* @access private
* @param string $controller
* @param string $method
* @param string $role
* @return Acl
*/
private function addRule($controller, $method, $role)
{
$controller = strtolower($controller);
$method = strtolower($method);
@@ -57,11 +126,7 @@ class AccessMap
$this->map[$controller] = array();
}
if (! isset($this->map[$controller][$method])) {
$this->map[$controller][$method] = array();
}
$this->map[$controller][$method] = $roles;
$this->map[$controller][$method] = $role;
return $this;
}
@@ -79,14 +144,12 @@ class AccessMap
$controller = strtolower($controller);
$method = strtolower($method);
if (isset($this->map[$controller][$method])) {
return $this->map[$controller][$method];
foreach (array($method, '*') as $key) {
if (isset($this->map[$controller][$key])) {
return $this->getRoleHierarchy($this->map[$controller][$key]);
}
}
if (isset($this->map[$controller]['*'])) {
return $this->map[$controller]['*'];
}
return array($this->defaultRole);
return $this->getRoleHierarchy($this->defaultRole);
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace Kanboard\Core\Security;
use LogicException;
use Kanboard\Core\Base;
use Kanboard\Core\User\UserProviderInterface;
use Kanboard\Event\AuthFailureEvent;
use Kanboard\Event\AuthSuccessEvent;
/**
* Authentication Manager
*
* @package security
* @author Frederic Guillot
*/
class AuthenticationManager extends Base
{
/**
* Event names
*
* @var string
*/
const EVENT_SUCCESS = 'auth.success';
const EVENT_FAILURE = 'auth.failure';
/**
* List of authentication providers
*
* @access private
* @var array
*/
private $providers = array();
/**
* Register a new authentication provider
*
* @access public
* @param AuthenticationProviderInterface $provider
* @return AuthenticationManager
*/
public function register(AuthenticationProviderInterface $provider)
{
$this->providers[$provider->getName()] = $provider;
return $this;
}
/**
* Register a new authentication provider
*
* @access public
* @param string $name
* @return AuthenticationProviderInterface|OAuthAuthenticationProviderInterface|PasswordAuthenticationProviderInterface|PreAuthenticationProviderInterface|OAuthAuthenticationProviderInterface
*/
public function getProvider($name)
{
if (! isset($this->providers[$name])) {
throw new LogicException('Authentication provider not found: '.$name);
}
return $this->providers[$name];
}
/**
* Execute providers that are able to validate the current session
*
* @access public
* @return boolean
*/
public function checkCurrentSession()
{
if ($this->userSession->isLogged() ) {
foreach ($this->filterProviders('SessionCheckProviderInterface') as $provider) {
if (! $provider->isValidSession()) {
unset($this->sessionStorage->user);
$this->preAuthentication();
return false;
}
}
}
return true;
}
/**
* Execute pre-authentication providers
*
* @access public
* @return boolean
*/
public function preAuthentication()
{
foreach ($this->filterProviders('PreAuthenticationProviderInterface') as $provider) {
if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) {
$this->dispatcher->dispatch(self::EVENT_SUCCESS, new AuthSuccessEvent($provider->getName()));
return true;
}
}
return false;
}
/**
* Execute username/password authentication providers
*
* @access public
* @param string $username
* @param string $password
* @param boolean $fireEvent
* @return boolean
*/
public function passwordAuthentication($username, $password, $fireEvent = true)
{
foreach ($this->filterProviders('PasswordAuthenticationProviderInterface') as $provider) {
$provider->setUsername($username);
$provider->setPassword($password);
if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) {
if ($fireEvent) {
$this->dispatcher->dispatch(self::EVENT_SUCCESS, new AuthSuccessEvent($provider->getName()));
}
return true;
}
}
if ($fireEvent) {
$this->dispatcher->dispatch(self::EVENT_FAILURE, new AuthFailureEvent($username));
}
return false;
}
/**
* Perform OAuth2 authentication
*
* @access public
* @param string $name
* @return boolean
*/
public function oauthAuthentication($name)
{
$provider = $this->getProvider($name);
if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) {
$this->dispatcher->dispatch(self::EVENT_SUCCESS, new AuthSuccessEvent($provider->getName()));
return true;
}
$this->dispatcher->dispatch(self::EVENT_FAILURE, new AuthFailureEvent);
return false;
}
/**
* Get the last Post-Authentication provider
*
* @access public
* @return PostAuthenticationProviderInterface
*/
public function getPostAuthenticationProvider()
{
$providers = $this->filterProviders('PostAuthenticationProviderInterface');
if (empty($providers)) {
throw new LogicException('You must have at least one Post-Authentication Provider configured');
}
return array_pop($providers);
}
/**
* Filter registered providers by interface type
*
* @access private
* @param string $interface
* @return array
*/
private function filterProviders($interface)
{
$interface = '\Kanboard\Core\Security\\'.$interface;
return array_filter($this->providers, function(AuthenticationProviderInterface $provider) use ($interface) {
return is_a($provider, $interface);
});
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Kanboard\Core\Security;
/**
* Authentication Provider Interface
*
* @package security
* @author Frederic Guillot
*/
interface AuthenticationProviderInterface
{
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName();
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate();
}

View File

@@ -16,17 +16,17 @@ class Authorization
* @access private
* @var AccessMap
*/
private $acl;
private $accessMap;
/**
* Constructor
*
* @access public
* @param AccessMap $acl
* @param AccessMap $accessMap
*/
public function __construct(AccessMap $acl)
public function __construct(AccessMap $accessMap)
{
$this->acl = $acl;
$this->accessMap = $accessMap;
}
/**
@@ -40,7 +40,7 @@ class Authorization
*/
public function isAllowed($controller, $method, $role)
{
$roles = $this->acl->getRoles($controller, $method);
$roles = $this->accessMap->getRoles($controller, $method);
return in_array($role, $roles);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Kanboard\Core\Security;
/**
* OAuth2 Authentication Provider Interface
*
* @package security
* @author Frederic Guillot
*/
interface OAuthAuthenticationProviderInterface extends AuthenticationProviderInterface
{
/**
* Get user object
*
* @access public
* @return UserProviderInterface
*/
public function getUser();
/**
* Unlink user
*
* @access public
* @param integer $userId
* @return bool
*/
public function unlink($userId);
/**
* Get configured OAuth2 service
*
* @access public
* @return Kanboard\Core\Http\OAuth2
*/
public function getService();
/**
* Set OAuth2 code
*
* @access public
* @param string $code
* @return OAuthAuthenticationProviderInterface
*/
public function setCode($code);
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Kanboard\Core\Security;
/**
* Password Authentication Provider Interface
*
* @package security
* @author Frederic Guillot
*/
interface PasswordAuthenticationProviderInterface extends AuthenticationProviderInterface
{
/**
* Get user object
*
* @access public
* @return UserProviderInterface
*/
public function getUser();
/**
* Set username
*
* @access public
* @param string $username
*/
public function setUsername($username);
/**
* Set password
*
* @access public
* @param string $password
*/
public function setPassword($password);
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Kanboard\Core\Security;
/**
* Post Authentication Provider Interface
*
* @package security
* @author Frederic Guillot
*/
interface PostAuthenticationProviderInterface extends AuthenticationProviderInterface
{
/**
* Set user pin-code
*
* @access public
* @param string $code
*/
public function setCode($code);
/**
* Set secret token (fetched from user profile)
*
* @access public
* @param string $secret
*/
public function setSecret($secret);
/**
* Get secret token (will be saved in user profile)
*
* @access public
* @return string
*/
public function getSecret();
/**
* Get QR code url (empty if no QR can be provided)
*
* @access public
* @param string $label
* @return string
*/
public function getQrCodeUrl($label);
/**
* Get key url (empty if no url can be provided)
*
* @access public
* @param string $label
* @return string
*/
public function getKeyUrl($label);
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Kanboard\Core\Security;
/**
* Pre-Authentication Provider Interface
*
* @package security
* @author Frederic Guillot
*/
interface PreAuthenticationProviderInterface extends AuthenticationProviderInterface
{
/**
* Get user object
*
* @access public
* @return UserProviderInterface
*/
public function getUser();
}

View File

@@ -18,4 +18,47 @@ class Role
const PROJECT_MANAGER = 'project-manager';
const PROJECT_MEMBER = 'project-member';
const PROJECT_VIEWER = 'project-viewer';
/**
* Get application roles
*
* @access public
* @return array
*/
public function getApplicationRoles()
{
return array(
self::APP_ADMIN => t('Administrator'),
self::APP_MANAGER => t('Manager'),
self::APP_USER => t('User'),
);
}
/**
* Get project roles
*
* @access public
* @return array
*/
public function getProjectRoles()
{
return array(
self::PROJECT_MANAGER => t('Project Manager'),
self::PROJECT_MEMBER => t('Project Member'),
self::PROJECT_VIEWER => t('Project Viewer'),
);
}
/**
* Get application roles
*
* @access public
* @param string $role
* @return string
*/
public function getRoleName($role)
{
$roles = $this->getApplicationRoles() + $this->getProjectRoles();
return isset($roles[$role]) ? $roles[$role] : t('Unknown');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Kanboard\Core\Security;
/**
* Session Check Provider Interface
*
* @package security
* @author Frederic Guillot
*/
interface SessionCheckProviderInterface
{
/**
* Check if the user session is valid
*
* @access public
* @return boolean
*/
public function isValidSession();
}