Rewrite of the authentication and authorization system
This commit is contained in:
32
app/Core/User/GroupSync.php
Normal file
32
app/Core/User/GroupSync.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
app/Core/User/UserProfile.php
Normal file
62
app/Core/User/UserProfile.php
Normal 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;
|
||||
}
|
||||
}
|
||||
70
app/Core/User/UserProperty.php
Normal file
70
app/Core/User/UserProperty.php
Normal 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 !== '';
|
||||
}
|
||||
}
|
||||
103
app/Core/User/UserProviderInterface.php
Normal file
103
app/Core/User/UserProviderInterface.php
Normal 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();
|
||||
}
|
||||
204
app/Core/User/UserSession.php
Normal file
204
app/Core/User/UserSession.php
Normal 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;
|
||||
}
|
||||
}
|
||||
76
app/Core/User/UserSync.php
Normal file
76
app/Core/User/UserSync.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user