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

@@ -0,0 +1,32 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
/**
* Group Synchronization
*
* @package user
* @author Frederic Guillot
*/
class GroupSync extends Base
{
/**
* Synchronize group membership
*
* @access public
* @param integer $userId
* @param array $groupIds
*/
public function synchronize($userId, array $groupIds)
{
foreach ($groupIds as $groupId) {
$group = $this->group->getByExternalId($groupId);
if (! empty($group) && ! $this->groupMember->isMember($group['id'], $userId)) {
$this->groupMember->addUser($group['id'], $userId);
}
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
/**
* User Profile
*
* @package user
* @author Frederic Guillot
*/
class UserProfile extends Base
{
/**
* Assign provider data to the local user
*
* @access public
* @param integer $userId
* @param UserProviderInterface $user
* @return boolean
*/
public function assign($userId, UserProviderInterface $user)
{
$profile = $this->user->getById($userId);
$values = UserProperty::filterProperties($profile, UserProperty::getProperties($user));
$values['id'] = $userId;
if ($this->user->update($values)) {
$profile = array_merge($profile, $values);
$this->userSession->initialize($profile);
return true;
}
return false;
}
/**
* Synchronize user properties with the local database and create the user session
*
* @access public
* @param UserProviderInterface $user
* @return boolean
*/
public function initialize(UserProviderInterface $user)
{
if ($user->getInternalId()) {
$profile = $this->user->getById($user->getInternalId());
} elseif ($user->getExternalIdColumn() && $user->getExternalId()) {
$profile = $this->userSync->synchronize($user);
$this->groupSync->synchronize($profile['id'], $user->getExternalGroupIds());
}
if (! empty($profile)) {
$this->userSession->initialize($profile);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Kanboard\Core\User;
/**
* User Property
*
* @package user
* @author Frederic Guillot
*/
class UserProperty
{
/**
* Get filtered user properties from user provider
*
* @static
* @access public
* @param UserProviderInterface $user
* @return array
*/
public static function getProperties(UserProviderInterface $user)
{
$properties = array(
'username' => $user->getUsername(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'role' => $user->getRole(),
$user->getExternalIdColumn() => $user->getExternalId(),
);
$properties = array_merge($properties, $user->getExtraAttributes());
return array_filter($properties, array(__NAMESPACE__.'\UserProperty', 'isNotEmptyValue'));
}
/**
* Filter user properties compared to existing user profile
*
* @static
* @access public
* @param array $profile
* @param array $properties
* @return array
*/
public static function filterProperties(array $profile, array $properties)
{
$values = array();
foreach ($properties as $property => $value) {
if (array_key_exists($property, $profile) && ! self::isNotEmptyValue($profile[$property])) {
$values[$property] = $value;
}
}
return $values;
}
/**
* Check if a value is not empty
*
* @static
* @access public
* @param string $value
* @return boolean
*/
public static function isNotEmptyValue($value)
{
return $value !== null && $value !== '';
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Kanboard\Core\User;
/**
* User Provider Interface
*
* @package user
* @author Frederic Guillot
*/
interface UserProviderInterface
{
/**
* Return true to allow automatic user creation
*
* @access public
* @return boolean
*/
public function isUserCreationAllowed();
/**
* Get external id column name
*
* Example: google_id, github_id, gitlab_id...
*
* @access public
* @return string
*/
public function getExternalIdColumn();
/**
* Get internal id
*
* If a value is returned the user properties won't be updated in the local database
*
* @access public
* @return integer
*/
public function getInternalId();
/**
* Get external id
*
* @access public
* @return string
*/
public function getExternalId();
/**
* Get user role
*
* Return an empty string to not override role stored in the database
*
* @access public
* @return string
*/
public function getRole();
/**
* Get username
*
* @access public
* @return string
*/
public function getUsername();
/**
* Get user full name
*
* @access public
* @return string
*/
public function getName();
/**
* Get user email
*
* @access public
* @return string
*/
public function getEmail();
/**
* Get external group ids
*
* A synchronization is done at login time,
* the user will be member of those groups if they exists in the database
*
* @access public
* @return string[]
*/
public function getExternalGroupIds();
/**
* Get extra user attributes
*
* Example: is_ldap_user, disable_login_form, notifications_enabled...
*
* @access public
* @return array
*/
public function getExtraAttributes();
}

View File

@@ -0,0 +1,204 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
use Kanboard\Core\Security\Role;
/**
* User Session
*
* @package user
* @author Frederic Guillot
*/
class UserSession extends Base
{
/**
* Update user session
*
* @access public
* @param array $user
*/
public function initialize(array $user)
{
foreach (array('password', 'is_admin', 'is_project_admin', 'twofactor_secret') as $column) {
if (isset($user[$column])) {
unset($user[$column]);
}
}
$user['id'] = (int) $user['id'];
$user['is_ldap_user'] = isset($user['is_ldap_user']) ? (bool) $user['is_ldap_user'] : false;
$user['twofactor_activated'] = isset($user['twofactor_activated']) ? (bool) $user['twofactor_activated'] : false;
$this->sessionStorage->user = $user;
$this->sessionStorage->postAuthenticationValidated = false;
}
/**
* Get user application role
*
* @access public
* @return string
*/
public function getRole()
{
return $this->sessionStorage->user['role'];
}
/**
* Return true if the user has validated the 2FA key
*
* @access public
* @return bool
*/
public function isPostAuthenticationValidated()
{
return isset($this->sessionStorage->postAuthenticationValidated) && $this->sessionStorage->postAuthenticationValidated === true;
}
/**
* Validate 2FA for the current session
*
* @access public
*/
public function validatePostAuthentication()
{
$this->sessionStorage->postAuthenticationValidated = true;
}
/**
* Return true if the user has 2FA enabled
*
* @access public
* @return bool
*/
public function hasPostAuthentication()
{
return isset($this->sessionStorage->user['twofactor_activated']) && $this->sessionStorage->user['twofactor_activated'] === true;
}
/**
* Disable 2FA for the current session
*
* @access public
*/
public function disablePostAuthentication()
{
$this->sessionStorage->user['twofactor_activated'] = false;
}
/**
* Return true if the logged user is admin
*
* @access public
* @return bool
*/
public function isAdmin()
{
return isset($this->sessionStorage->user['role']) && $this->sessionStorage->user['role'] === Role::APP_ADMIN;
}
/**
* Get the connected user id
*
* @access public
* @return integer
*/
public function getId()
{
return isset($this->sessionStorage->user['id']) ? (int) $this->sessionStorage->user['id'] : 0;
}
/**
* Get username
*
* @access public
* @return integer
*/
public function getUsername()
{
return isset($this->sessionStorage->user['username']) ? $this->sessionStorage->user['username'] : '';
}
/**
* Check is the user is connected
*
* @access public
* @return bool
*/
public function isLogged()
{
return isset($this->sessionStorage->user) && ! empty($this->sessionStorage->user);
}
/**
* Get project filters from the session
*
* @access public
* @param integer $project_id
* @return string
*/
public function getFilters($project_id)
{
return ! empty($this->sessionStorage->filters[$project_id]) ? $this->sessionStorage->filters[$project_id] : 'status:open';
}
/**
* Save project filters in the session
*
* @access public
* @param integer $project_id
* @param string $filters
*/
public function setFilters($project_id, $filters)
{
$this->sessionStorage->filters[$project_id] = $filters;
}
/**
* Is board collapsed or expanded
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function isBoardCollapsed($project_id)
{
return ! empty($this->sessionStorage->boardCollapsed[$project_id]) ? $this->sessionStorage->boardCollapsed[$project_id] : false;
}
/**
* Set board display mode
*
* @access public
* @param integer $project_id
* @param boolean $is_collapsed
*/
public function setBoardDisplayMode($project_id, $is_collapsed)
{
$this->sessionStorage->boardCollapsed[$project_id] = $is_collapsed;
}
/**
* Set comments sorting
*
* @access public
* @param string $order
*/
public function setCommentSorting($order)
{
$this->sessionStorage->commentSorting = $order;
}
/**
* Get comments sorting direction
*
* @access public
* @return string
*/
public function getCommentSorting()
{
return empty($this->sessionStorage->commentSorting) ? 'ASC' : $this->sessionStorage->commentSorting;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
/**
* User Synchronization
*
* @package user
* @author Frederic Guillot
*/
class UserSync extends Base
{
/**
* Synchronize user profile
*
* @access public
* @param UserProviderInterface $user
* @return array
*/
public function synchronize(UserProviderInterface $user)
{
$profile = $this->user->getByExternalId($user->getExternalIdColumn(), $user->getExternalId());
$properties = UserProperty::getProperties($user);
if (! empty($profile)) {
$profile = $this->updateUser($profile, $properties);
} elseif ($user->isUserCreationAllowed()) {
$profile = $this->createUser($user, $properties);
}
return $profile;
}
/**
* Update user profile
*
* @access public
* @param array $profile
* @param array $properties
* @return array
*/
private function updateUser(array $profile, array $properties)
{
$values = UserProperty::filterProperties($profile, $properties);
if (! empty($values)) {
$values['id'] = $profile['id'];
$result = $this->user->update($values);
return $result ? array_merge($profile, $properties) : $profile;
}
return $profile;
}
/**
* Create user
*
* @access public
* @param UserProviderInterface $user
* @param array $properties
* @return array
*/
private function createUser(UserProviderInterface $user, array $properties)
{
$id = $this->user->create($properties);
if ($id === false) {
$this->logger->error('Unable to create user profile: '.$user->getExternalId());
return array();
}
return $this->user->getById($id);
}
}