summaryrefslogtreecommitdiff
path: root/app/Core/Security
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-12-05 20:31:27 -0500
committerFrederic Guillot <fred@kanboard.net>2015-12-05 20:31:27 -0500
commite9fedf3e5cd63aea4da7a71f6647ee427c62fa49 (patch)
treeabc2de5aebace4a2d7c94805552264dab6b10bc7 /app/Core/Security
parent346b8312e5ac877ce3192c2db3a26b500018bbb5 (diff)
Rewrite of the authentication and authorization system
Diffstat (limited to 'app/Core/Security')
-rw-r--r--app/Core/Security/AccessMap.php91
-rw-r--r--app/Core/Security/AuthenticationManager.php187
-rw-r--r--app/Core/Security/AuthenticationProviderInterface.php28
-rw-r--r--app/Core/Security/Authorization.php10
-rw-r--r--app/Core/Security/OAuthAuthenticationProviderInterface.php46
-rw-r--r--app/Core/Security/PasswordAuthenticationProviderInterface.php36
-rw-r--r--app/Core/Security/PostAuthenticationProviderInterface.php54
-rw-r--r--app/Core/Security/PreAuthenticationProviderInterface.php20
-rw-r--r--app/Core/Security/Role.php43
-rw-r--r--app/Core/Security/SessionCheckProviderInterface.php20
10 files changed, 516 insertions, 19 deletions
diff --git a/app/Core/Security/AccessMap.php b/app/Core/Security/AccessMap.php
index 10a29e1f..02a4ca45 100644
--- a/app/Core/Security/AccessMap.php
+++ b/app/Core/Security/AccessMap.php
@@ -19,6 +19,14 @@ class AccessMap
private $defaultRole = '';
/**
+ * Role hierarchy
+ *
+ * @access private
+ * @var array
+ */
+ private $hierarchy = array();
+
+ /**
* Access map
*
* @access private
@@ -40,15 +48,76 @@ class AccessMap
}
/**
+ * 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 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, $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 array $roles
+ * @param string $role
* @return Acl
*/
- public function add($controller, $method, array $roles)
+ 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];
- }
-
- if (isset($this->map[$controller]['*'])) {
- return $this->map[$controller]['*'];
+ foreach (array($method, '*') as $key) {
+ if (isset($this->map[$controller][$key])) {
+ return $this->getRoleHierarchy($this->map[$controller][$key]);
+ }
}
- return array($this->defaultRole);
+ return $this->getRoleHierarchy($this->defaultRole);
}
}
diff --git a/app/Core/Security/AuthenticationManager.php b/app/Core/Security/AuthenticationManager.php
new file mode 100644
index 00000000..cced58c0
--- /dev/null
+++ b/app/Core/Security/AuthenticationManager.php
@@ -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);
+ });
+ }
+}
diff --git a/app/Core/Security/AuthenticationProviderInterface.php b/app/Core/Security/AuthenticationProviderInterface.php
new file mode 100644
index 00000000..828e272c
--- /dev/null
+++ b/app/Core/Security/AuthenticationProviderInterface.php
@@ -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();
+}
diff --git a/app/Core/Security/Authorization.php b/app/Core/Security/Authorization.php
index a04b3720..980db048 100644
--- a/app/Core/Security/Authorization.php
+++ b/app/Core/Security/Authorization.php
@@ -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);
}
}
diff --git a/app/Core/Security/OAuthAuthenticationProviderInterface.php b/app/Core/Security/OAuthAuthenticationProviderInterface.php
new file mode 100644
index 00000000..c32339e0
--- /dev/null
+++ b/app/Core/Security/OAuthAuthenticationProviderInterface.php
@@ -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);
+}
diff --git a/app/Core/Security/PasswordAuthenticationProviderInterface.php b/app/Core/Security/PasswordAuthenticationProviderInterface.php
new file mode 100644
index 00000000..918a4aec
--- /dev/null
+++ b/app/Core/Security/PasswordAuthenticationProviderInterface.php
@@ -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);
+}
diff --git a/app/Core/Security/PostAuthenticationProviderInterface.php b/app/Core/Security/PostAuthenticationProviderInterface.php
new file mode 100644
index 00000000..88fc2fe5
--- /dev/null
+++ b/app/Core/Security/PostAuthenticationProviderInterface.php
@@ -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);
+}
diff --git a/app/Core/Security/PreAuthenticationProviderInterface.php b/app/Core/Security/PreAuthenticationProviderInterface.php
new file mode 100644
index 00000000..391e8d0f
--- /dev/null
+++ b/app/Core/Security/PreAuthenticationProviderInterface.php
@@ -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();
+}
diff --git a/app/Core/Security/Role.php b/app/Core/Security/Role.php
index 079ce14b..85d85743 100644
--- a/app/Core/Security/Role.php
+++ b/app/Core/Security/Role.php
@@ -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');
+ }
}
diff --git a/app/Core/Security/SessionCheckProviderInterface.php b/app/Core/Security/SessionCheckProviderInterface.php
new file mode 100644
index 00000000..232fe1db
--- /dev/null
+++ b/app/Core/Security/SessionCheckProviderInterface.php
@@ -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();
+}