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

@@ -1,289 +0,0 @@
<?php
namespace Kanboard\Model;
/**
* Access List
*
* @package model
* @author Frederic Guillot
*/
class Acl extends Base
{
/**
* Controllers and actions allowed from outside
*
* @access private
* @var array
*/
private $public_acl = array(
'auth' => array('login', 'check', 'captcha'),
'task' => array('readonly'),
'board' => array('readonly'),
'webhook' => '*',
'ical' => '*',
'feed' => '*',
'oauth' => array('google', 'github', 'gitlab'),
);
/**
* Controllers and actions for project members
*
* @access private
* @var array
*/
private $project_member_acl = array(
'board' => '*',
'comment' => '*',
'file' => '*',
'project' => array('show'),
'listing' => '*',
'activity' => '*',
'subtask' => '*',
'task' => '*',
'taskduplication' => '*',
'taskcreation' => '*',
'taskmodification' => '*',
'taskstatus' => '*',
'tasklink' => '*',
'timer' => '*',
'customfilter' => '*',
'calendar' => array('show', 'project'),
);
/**
* Controllers and actions for project managers
*
* @access private
* @var array
*/
private $project_manager_acl = array(
'action' => '*',
'analytic' => '*',
'category' => '*',
'column' => '*',
'export' => '*',
'taskimport' => '*',
'project' => array('edit', 'update', 'share', 'integrations', 'notifications', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'swimlane' => '*',
'gantt' => array('project', 'savetaskdate', 'task', 'savetask'),
);
/**
* Controllers and actions for project admins
*
* @access private
* @var array
*/
private $project_admin_acl = array(
'project' => array('remove'),
'projectuser' => '*',
'gantt' => array('projects', 'saveprojectdate'),
);
/**
* Controllers and actions for admins
*
* @access private
* @var array
*/
private $admin_acl = array(
'user' => array('index', 'create', 'save', 'remove', 'authentication'),
'userimport' => '*',
'config' => '*',
'link' => '*',
'currency' => '*',
'twofactor' => array('disable'),
);
/**
* Extend ACL rules
*
* @access public
* @param string $acl_name
* @param aray $rules
*/
public function extend($acl_name, array $rules)
{
$this->$acl_name = array_merge($this->$acl_name, $rules);
}
/**
* Return true if the specified controller/action match the given acl
*
* @access public
* @param array $acl Acl list
* @param string $controller Controller name
* @param string $action Action name
* @return bool
*/
public function matchAcl(array $acl, $controller, $action)
{
$controller = strtolower($controller);
$action = strtolower($action);
return isset($acl[$controller]) && $this->hasAction($action, $acl[$controller]);
}
/**
* Return true if the specified action is inside the list of actions
*
* @access public
* @param string $action Action name
* @param mixed $action Actions list
* @return bool
*/
public function hasAction($action, $actions)
{
if (is_array($actions)) {
return in_array($action, $actions);
}
return $actions === '*';
}
/**
* Return true if the given action is public
*
* @access public
* @param string $controller Controller name
* @param string $action Action name
* @return bool
*/
public function isPublicAction($controller, $action)
{
return $this->matchAcl($this->public_acl, $controller, $action);
}
/**
* Return true if the given action is for admins
*
* @access public
* @param string $controller Controller name
* @param string $action Action name
* @return bool
*/
public function isAdminAction($controller, $action)
{
return $this->matchAcl($this->admin_acl, $controller, $action);
}
/**
* Return true if the given action is for project managers
*
* @access public
* @param string $controller Controller name
* @param string $action Action name
* @return bool
*/
public function isProjectManagerAction($controller, $action)
{
return $this->matchAcl($this->project_manager_acl, $controller, $action);
}
/**
* Return true if the given action is for application managers
*
* @access public
* @param string $controller Controller name
* @param string $action Action name
* @return bool
*/
public function isProjectAdminAction($controller, $action)
{
return $this->matchAcl($this->project_admin_acl, $controller, $action);
}
/**
* Return true if the given action is for project members
*
* @access public
* @param string $controller Controller name
* @param string $action Action name
* @return bool
*/
public function isProjectMemberAction($controller, $action)
{
return $this->matchAcl($this->project_member_acl, $controller, $action);
}
/**
* Return true if the visitor is allowed to access to the given page
* We suppose the user already authenticated
*
* @access public
* @param string $controller Controller name
* @param string $action Action name
* @param integer $project_id Project id
* @return bool
*/
public function isAllowed($controller, $action, $project_id = 0)
{
// If you are admin you have access to everything
if ($this->userSession->isAdmin()) {
return true;
}
// If you access to an admin action, your are not allowed
if ($this->isAdminAction($controller, $action)) {
return false;
}
// Check project admin permissions
if ($this->isProjectAdminAction($controller, $action)) {
return $this->handleProjectAdminPermissions($project_id);
}
// Check project manager permissions
if ($this->isProjectManagerAction($controller, $action)) {
return $this->handleProjectManagerPermissions($project_id);
}
// Check project member permissions
if ($this->isProjectMemberAction($controller, $action)) {
return $project_id > 0 && $this->projectPermission->isMember($project_id, $this->userSession->getId());
}
// Other applications actions are allowed
return true;
}
/**
* Handle permission for project manager
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function handleProjectManagerPermissions($project_id)
{
if ($project_id > 0) {
if ($this->userSession->isProjectAdmin()) {
return $this->projectPermission->isMember($project_id, $this->userSession->getId());
}
return $this->projectPermission->isManager($project_id, $this->userSession->getId());
}
return false;
}
/**
* Handle permission for project admins
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function handleProjectAdminPermissions($project_id)
{
if (! $this->userSession->isProjectAdmin()) {
return false;
}
if ($project_id > 0) {
return $this->projectPermission->isMember($project_id, $this->userSession->getId());
}
return true;
}
}

View File

@@ -2,7 +2,6 @@
namespace Kanboard\Model;
use Kanboard\Core\Http\Request;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Gregwar\Captcha\CaptchaBuilder;
@@ -15,113 +14,6 @@ use Gregwar\Captcha\CaptchaBuilder;
*/
class Authentication extends Base
{
/**
* Load automatically an authentication backend
*
* @access public
* @param string $name Backend class name
* @return mixed
*/
public function backend($name)
{
if (! isset($this->container[$name])) {
$class = '\Kanboard\Auth\\'.ucfirst($name);
$this->container[$name] = new $class($this->container);
}
return $this->container[$name];
}
/**
* Check if the current user is authenticated
*
* @access public
* @return bool
*/
public function isAuthenticated()
{
// If the user is already logged it's ok
if ($this->userSession->isLogged()) {
// Check if the user session match an existing user
$userNotFound = ! $this->user->exists($this->userSession->getId());
$reverseProxyWrongUser = REVERSE_PROXY_AUTH && $this->backend('reverseProxy')->getUsername() !== $this->userSession->getUsername();
if ($userNotFound || $reverseProxyWrongUser) {
$this->backend('rememberMe')->destroy($this->userSession->getId());
$this->sessionManager->close();
return false;
}
return true;
}
// We try first with the RememberMe cookie
if (REMEMBER_ME_AUTH && $this->backend('rememberMe')->authenticate()) {
return true;
}
// Then with the ReverseProxy authentication
if (REVERSE_PROXY_AUTH && $this->backend('reverseProxy')->authenticate()) {
return true;
}
return false;
}
/**
* Authenticate a user by different methods
*
* @access public
* @param string $username Username
* @param string $password Password
* @return boolean
*/
public function authenticate($username, $password)
{
if ($this->user->isLocked($username)) {
$this->container['logger']->error('Account locked: '.$username);
return false;
} elseif ($this->backend('database')->authenticate($username, $password)) {
$this->user->resetFailedLogin($username);
return true;
} elseif (LDAP_AUTH && $this->backend('ldap')->authenticate($username, $password)) {
$this->user->resetFailedLogin($username);
return true;
}
$this->handleFailedLogin($username);
return false;
}
/**
* Return true if the captcha must be shown
*
* @access public
* @param string $username
* @return boolean
*/
public function hasCaptcha($username)
{
return $this->user->getFailedLogin($username) >= BRUTEFORCE_CAPTCHA;
}
/**
* Handle failed login
*
* @access public
* @param string $username
*/
public function handleFailedLogin($username)
{
$this->user->incrementFailedLogin($username);
if ($this->user->getFailedLogin($username) >= BRUTEFORCE_LOCKDOWN) {
$this->container['logger']->critical('Locking account: '.$username);
$this->user->lock($username, BRUTEFORCE_LOCKDOWN_DURATION);
}
}
/**
* Validate user login form
*
@@ -131,14 +23,14 @@ class Authentication extends Base
*/
public function validateForm(array $values)
{
list($result, $errors) = $this->validateFormCredentials($values);
$result = false;
$errors = array();
if ($result) {
if ($this->validateFormCaptcha($values) && $this->authenticate($values['username'], $values['password'])) {
$this->createRememberMeSession($values);
} else {
$result = false;
$errors['login'] = t('Bad username or password');
foreach (array('validateFields', 'validateLocking', 'validateCaptcha', 'validateCredentials') as $method) {
list($result, $errors) = $this->$method($values);
if (! $result) {
break;
}
}
@@ -148,11 +40,11 @@ class Authentication extends Base
/**
* Validate credentials syntax
*
* @access public
* @access private
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateFormCredentials(array $values)
private function validateFields(array $values)
{
$v = new Validator($values, array(
new Validators\Required('username', t('The username is required')),
@@ -167,40 +59,72 @@ class Authentication extends Base
}
/**
* Validate captcha
*
* @access public
* @param array $values Form values
* @return boolean
*/
public function validateFormCaptcha(array $values)
{
if ($this->hasCaptcha($values['username'])) {
if (! isset($this->sessionStorage->captcha)) {
return false;
}
$builder = new CaptchaBuilder;
$builder->setPhrase($this->sessionStorage->captcha);
return $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : '');
}
return true;
}
/**
* Create remember me session if necessary
* Validate user locking
*
* @access private
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
private function createRememberMeSession(array $values)
private function validateLocking(array $values)
{
if (REMEMBER_ME_AUTH && ! empty($values['remember_me'])) {
$credentials = $this->backend('rememberMe')
->create($this->userSession->getId(), Request::getIpAddress(), Request::getUserAgent());
$result = true;
$errors = array();
$this->backend('rememberMe')->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']);
if ($this->userLocking->isLocked($values['username'])) {
$result = false;
$errors['login'] = t('Your account is locked for %d minutes', BRUTEFORCE_LOCKDOWN_DURATION);
$this->logger->error('Account locked: '.$values['username']);
}
return array($result, $errors);
}
/**
* Validate password syntax
*
* @access private
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
private function validateCredentials(array $values)
{
$result = true;
$errors = array();
if (! $this->authenticationManager->passwordAuthentication($values['username'], $values['password'])) {
$result = false;
$errors['login'] = t('Bad username or password');
}
return array($result, $errors);
}
/**
* Validate captcha
*
* @access private
* @param array $values Form values
* @return boolean
*/
private function validateCaptcha(array $values)
{
$result = true;
$errors = array();
if ($this->userLocking->hasCaptcha($values['username'])) {
if (! isset($this->sessionStorage->captcha)) {
$result = false;
} else {
$builder = new CaptchaBuilder;
$builder->setPhrase($this->sessionStorage->captcha);
$result = $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : '');
if (! $result) {
$errors['login'] = t('Invalid captcha');
}
}
}
return array($result, $errors);;
}
}

View File

@@ -43,6 +43,18 @@ class Group extends Base
return $this->getQuery()->eq('id', $group_id)->findOne();
}
/**
* Get a specific group by external id
*
* @access public
* @param integer $external_id
* @return array
*/
public function getByExternalId($external_id)
{
return $this->getQuery()->eq('external_id', $external_id)->findOne();
}
/**
* Get all groups
*
@@ -54,6 +66,18 @@ class Group extends Base
return $this->getQuery()->asc('name')->findAll();
}
/**
* Search groups by name
*
* @access public
* @param string $input
* @return array
*/
public function search($input)
{
return $this->db->table(self::TABLE)->ilike('name', '%'.$input.'%')->findAll();
}
/**
* Remove a group
*

View File

@@ -65,8 +65,8 @@ class GroupMember extends Base
* Add user to a group
*
* @access public
* @param integer $group_id
* @param integer $user_id
* @param integer $group_id
* @param integer $user_id
* @return boolean
*/
public function addUser($group_id, $user_id)
@@ -81,8 +81,8 @@ class GroupMember extends Base
* Remove user from a group
*
* @access public
* @param integer $group_id
* @param integer $user_id
* @param integer $group_id
* @param integer $user_id
* @return boolean
*/
public function removeUser($group_id, $user_id)
@@ -92,4 +92,20 @@ class GroupMember extends Base
->eq('user_id', $user_id)
->remove();
}
/**
* Check if a user is member
*
* @access public
* @param integer $group_id
* @param integer $user_id
* @return boolean
*/
public function isMember($group_id, $user_id)
{
return $this->db->table(self::TABLE)
->eq('group_id', $group_id)
->eq('user_id', $user_id)
->exists();
}
}

View File

@@ -5,6 +5,7 @@ namespace Kanboard\Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
/**
* Project model
@@ -287,7 +288,7 @@ class Project extends Base
{
foreach ($projects as &$project) {
$this->getColumnStats($project);
$project = array_merge($project, $this->projectPermission->getProjectUsers($project['id']));
$project = array_merge($project, $this->projectUserRole->getAllUsersGroupedByRole($project['id']));
}
return $projects;
@@ -365,7 +366,7 @@ class Project extends Base
}
if ($add_user && $user_id) {
$this->projectPermission->addManager($project_id, $user_id);
$this->projectUserRole->addUser($project_id, $user_id, Role::PROJECT_MANAGER);
}
$this->category->createDefaultCategories($project_id);

View File

@@ -56,7 +56,7 @@ class ProjectAnalytic extends Base
$metrics = array();
$total = 0;
$tasks = $this->taskFinder->getAll($project_id);
$users = $this->projectPermission->getMemberList($project_id);
$users = $this->projectUserRole->getAssignableUsersList($project_id);
foreach ($tasks as $task) {
$user = isset($users[$task['owner_id']]) ? $users[$task['owner_id']] : $users[0];

View File

@@ -0,0 +1,187 @@
<?php
namespace Kanboard\Model;
use Kanboard\Core\Security\Role;
/**
* Project Group Role
*
* @package model
* @author Frederic Guillot
*/
class ProjectGroupRole extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_has_groups';
/**
* Get the list of project visible by the given user according to groups
*
* @access public
* @param integer $user_id
* @param array $status
* @return array
*/
public function getProjectsByUser($user_id, $status = array(Project::ACTIVE, Project::INACTIVE))
{
return $this->db
->hashtable(Project::TABLE)
->join(self::TABLE, 'project_id', 'id')
->join(GroupMember::TABLE, 'group_id', 'group_id', self::TABLE)
->eq(GroupMember::TABLE.'.user_id', $user_id)
->in(Project::TABLE.'.is_active', $status)
->getAll(Project::TABLE.'.id', Project::TABLE.'.name');
}
/**
* For a given project get the role of the specified user
*
* @access public
* @param integer $project_id
* @param integer $user_id
* @return string
*/
public function getUserRole($project_id, $user_id)
{
return $this->db->table(self::TABLE)
->join(GroupMember::TABLE, 'group_id', 'group_id', self::TABLE)
->eq(GroupMember::TABLE.'.user_id', $user_id)
->eq(self::TABLE.'.project_id', $project_id)
->findOneColumn('role');
}
/**
* Get all groups associated directly to the project
*
* @access public
* @param integer $project_id
* @return array
*/
public function getGroups($project_id)
{
return $this->db->table(self::TABLE)
->columns(Group::TABLE.'.id', Group::TABLE.'.name', self::TABLE.'.role')
->join(Group::TABLE, 'id', 'group_id')
->eq('project_id', $project_id)
->asc('name')
->findAll();
}
/**
* From groups get all users associated to the project
*
* @access public
* @param integer $project_id
* @return array
*/
public function getUsers($project_id)
{
return $this->db->table(self::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', self::TABLE.'.role')
->join(GroupMember::TABLE, 'group_id', 'group_id', self::TABLE)
->join(User::TABLE, 'id', 'user_id', GroupMember::TABLE)
->eq(self::TABLE.'.project_id', $project_id)
->asc(User::TABLE.'.username')
->findAll();
}
/**
* From groups get all users assignable to tasks
*
* @access public
* @param integer $project_id
* @return array
*/
public function getAssignableUsers($project_id)
{
return $this->db->table(self::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
->join(GroupMember::TABLE, 'group_id', 'group_id', self::TABLE)
->join(User::TABLE, 'id', 'user_id', GroupMember::TABLE)
->eq(self::TABLE.'.project_id', $project_id)
->in(self::TABLE.'.role', array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER))
->asc(User::TABLE.'.username')
->findAll();
}
/**
* Add a group to the project
*
* @access public
* @param integer $project_id
* @param integer $group_id
* @param string $role
* @return boolean
*/
public function addGroup($project_id, $group_id, $role)
{
return $this->db->table(self::TABLE)->insert(array(
'group_id' => $group_id,
'project_id' => $project_id,
'role' => $role,
));
}
/**
* Remove a group from the project
*
* @access public
* @param integer $project_id
* @param integer $group_id
* @return boolean
*/
public function removeGroup($project_id, $group_id)
{
return $this->db->table(self::TABLE)->eq('group_id', $group_id)->eq('project_id', $project_id)->remove();
}
/**
* Change a group role for the project
*
* @access public
* @param integer $project_id
* @param integer $group_id
* @param string $role
* @return boolean
*/
public function changeGroupRole($project_id, $group_id, $role)
{
return $this->db->table(self::TABLE)
->eq('group_id', $group_id)
->eq('project_id', $project_id)
->update(array(
'role' => $role,
));
}
/**
* Copy group access from a project to another one
*
* @param integer $project_src_id Project Template
* @return integer $project_dst_id Project that receives the copy
* @return boolean
*/
public function duplicate($project_src_id, $project_dst_id)
{
$rows = $this->db->table(self::TABLE)->eq('project_id', $project_src_id)->findAll();
foreach ($rows as $row) {
$result = $this->db->table(self::TABLE)->save(array(
'project_id' => $project_dst_id,
'group_id' => $row['group_id'],
'role' => $row['role'],
));
if (! $result) {
return false;
}
}
return true;
}
}

View File

@@ -2,129 +2,25 @@
namespace Kanboard\Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Kanboard\Core\Security\Role;
/**
* Project permission model
* Project Permission
*
* @package model
* @author Frederic Guillot
*/
class ProjectPermission extends Base
{
/**
* SQL table name for permissions
*
* @var string
*/
const TABLE = 'project_has_users';
/**
* Get a list of people that can be assigned for tasks
*
* @access public
* @param integer $project_id Project id
* @param bool $prepend_unassigned Prepend the 'Unassigned' value
* @param bool $prepend_everybody Prepend the 'Everbody' value
* @param bool $allow_single_user If there is only one user return only this user
* @return array
*/
public function getMemberList($project_id, $prepend_unassigned = true, $prepend_everybody = false, $allow_single_user = false)
{
$allowed_users = $this->getMembers($project_id);
if ($allow_single_user && count($allowed_users) === 1) {
return $allowed_users;
}
if ($prepend_unassigned) {
$allowed_users = array(t('Unassigned')) + $allowed_users;
}
if ($prepend_everybody) {
$allowed_users = array(User::EVERYBODY_ID => t('Everybody')) + $allowed_users;
}
return $allowed_users;
}
/**
* Get a list of members and managers with a single SQL query
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getProjectUsers($project_id)
{
$result = array(
'managers' => array(),
'members' => array(),
);
$users = $this->db
->table(self::TABLE)
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->asc('username')
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', self::TABLE.'.is_owner')
->findAll();
foreach ($users as $user) {
$key = $user['is_owner'] == 1 ? 'managers' : 'members';
$result[$key][$user['id']] = $user['name'] ?: $user['username'];
}
return $result;
}
/**
* Get a list of allowed people for a project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getMembers($project_id)
{
if ($this->isEverybodyAllowed($project_id)) {
return $this->user->getList();
}
return $this->getAssociatedUsers($project_id);
}
/**
* Get a list of owners for a project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getManagers($project_id)
{
$users = $this->db
->table(self::TABLE)
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->eq('is_owner', 1)
->asc('username')
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
->findAll();
return $this->user->prepareList($users);
}
/**
* Get query for project users overview
*
* @access public
* @param array $project_ids
* @param integer $is_owner
* @param string $role
* @return \PicoDb\Table
*/
public function getQueryByRole(array $project_ids, $is_owner = 0)
public function getQueryByRole(array $project_ids, $role)
{
if (empty($project_ids)) {
$project_ids = array(-1);
@@ -135,7 +31,7 @@ class ProjectPermission extends Base
->table(self::TABLE)
->join(User::TABLE, 'id', 'user_id')
->join(Project::TABLE, 'id', 'project_id')
->eq(self::TABLE.'.is_owner', $is_owner)
->eq(self::TABLE.'.role', $role)
->eq(Project::TABLE.'.is_private', 0)
->in(Project::TABLE.'.id', $project_ids)
->columns(
@@ -147,172 +43,6 @@ class ProjectPermission extends Base
);
}
/**
* Get a list of people associated to the project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAssociatedUsers($project_id)
{
$users = $this->db
->table(self::TABLE)
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->asc('username')
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
->findAll();
return $this->user->prepareList($users);
}
/**
* Get allowed and not allowed users for a project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAllUsers($project_id)
{
$users = array(
'allowed' => array(),
'not_allowed' => array(),
'managers' => array(),
);
$all_users = $this->user->getList();
$users['allowed'] = $this->getMembers($project_id);
$users['managers'] = $this->getManagers($project_id);
foreach ($all_users as $user_id => $username) {
if (! isset($users['allowed'][$user_id])) {
$users['not_allowed'][$user_id] = $username;
}
}
return $users;
}
/**
* Add a new project member
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function addMember($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
->save(array('project_id' => $project_id, 'user_id' => $user_id));
}
/**
* Remove a member
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function revokeMember($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->remove();
}
/**
* Add a project manager
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function addManager($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
->save(array('project_id' => $project_id, 'user_id' => $user_id, 'is_owner' => 1));
}
/**
* Change the role of a member
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @param integer $is_owner Is user owner of the project
* @return bool
*/
public function changeRole($project_id, $user_id, $is_owner)
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->update(array('is_owner' => (int) $is_owner));
}
/**
* Check if a specific user is member of a project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function isMember($project_id, $user_id)
{
if ($this->isEverybodyAllowed($project_id)) {
return true;
}
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->exists();
}
/**
* Check if a specific user is manager of a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function isManager($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->eq('is_owner', 1)
->exists();
}
/**
* Check if a specific user is allowed to access to a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function isUserAllowed($project_id, $user_id)
{
return $project_id === 0 || $this->user->isAdmin($user_id) || $this->isMember($project_id, $user_id);
}
/**
* Return true if everybody is allowed for the project
*
@@ -330,172 +60,59 @@ class ProjectPermission extends Base
}
/**
* Return a list of allowed active projects for a given user
* Return true if the user is allowed to access a project
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getAllowedProjects($user_id)
{
if ($this->user->isAdmin($user_id)) {
return $this->project->getListByStatus(Project::ACTIVE);
}
return $this->getActiveMemberProjects($user_id);
}
/**
* Return a list of projects where the user is member
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getMemberProjects($user_id)
{
return $this->db
->hashtable(Project::TABLE)
->beginOr()
->eq(self::TABLE.'.user_id', $user_id)
->eq(Project::TABLE.'.is_everybody_allowed', 1)
->closeOr()
->join(self::TABLE, 'project_id', 'id')
->getAll('projects.id', 'name');
}
/**
* Return a list of project ids where the user is member
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getMemberProjectIds($user_id)
{
return $this->db
->table(Project::TABLE)
->beginOr()
->eq(self::TABLE.'.user_id', $user_id)
->eq(Project::TABLE.'.is_everybody_allowed', 1)
->closeOr()
->join(self::TABLE, 'project_id', 'id')
->findAllByColumn('projects.id');
}
/**
* Return a list of active project ids where the user is member
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getActiveMemberProjectIds($user_id)
{
return $this->db
->table(Project::TABLE)
->beginOr()
->eq(self::TABLE.'.user_id', $user_id)
->eq(Project::TABLE.'.is_everybody_allowed', 1)
->closeOr()
->eq(Project::TABLE.'.is_active', Project::ACTIVE)
->join(self::TABLE, 'project_id', 'id')
->findAllByColumn('projects.id');
}
/**
* Return a list of active projects where the user is member
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getActiveMemberProjects($user_id)
{
return $this->db
->hashtable(Project::TABLE)
->beginOr()
->eq(self::TABLE.'.user_id', $user_id)
->eq(Project::TABLE.'.is_everybody_allowed', 1)
->closeOr()
->eq(Project::TABLE.'.is_active', Project::ACTIVE)
->join(self::TABLE, 'project_id', 'id')
->getAll('projects.id', 'name');
}
/**
* Copy user access from a project to another one
*
* @param integer $project_src Project Template
* @return integer $project_dst Project that receives the copy
* @param integer $project_id
* @param integer $user_id
* @return boolean
*/
public function duplicate($project_src, $project_dst)
public function isUserAllowed($project_id, $user_id)
{
$rows = $this->db
->table(self::TABLE)
->columns('project_id', 'user_id', 'is_owner')
->eq('project_id', $project_src)
->findAll();
foreach ($rows as $row) {
$result = $this->db
->table(self::TABLE)
->save(array(
'project_id' => $project_dst,
'user_id' => $row['user_id'],
'is_owner' => (int) $row['is_owner'], // (int) for postgres
));
if (! $result) {
return false;
}
if ($this->userSession->isAdmin()) {
return true;
}
return true;
}
/**
* Validate allow user
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateUserModification(array $values)
{
$v = new Validator($values, array(
new Validators\Required('project_id', t('The project id is required')),
new Validators\Integer('project_id', t('This value must be an integer')),
new Validators\Required('user_id', t('The user id is required')),
new Validators\Integer('user_id', t('This value must be an integer')),
new Validators\Integer('is_owner', t('This value must be an integer')),
));
return array(
$v->execute(),
$v->getErrors()
return in_array(
$this->projectUserRole->getUserRole($project_id, $user_id),
array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER, Role::PROJECT_VIEWER)
);
}
/**
* Validate allow everybody
* Return true if the user is assignable
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
* @param integer $project_id
* @param integer $user_id
* @return boolean
*/
public function validateProjectModification(array $values)
public function isMember($project_id, $user_id)
{
$v = new Validator($values, array(
new Validators\Required('id', t('The project id is required')),
new Validators\Integer('id', t('This value must be an integer')),
new Validators\Integer('is_everybody_allowed', t('This value must be an integer')),
));
return in_array($this->projectUserRole->getUSerRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER));
}
return array(
$v->execute(),
$v->getErrors()
);
/**
* Get active project ids by user
*
* @access public
* @param integer $user_id
* @return array
*/
public function getActiveProjectIds($user_id)
{
return array_keys($this->projectUserRole->getProjectsByUser($user_id, array(Project::ACTIVE)));
}
/**
* Copy permissions to another project
*
* @param integer $project_src_id Project Template
* @param integer $project_dst_id Project that receives the copy
* @return boolean
*/
public function duplicate($project_src_id, $project_dst_id)
{
return $this->projectUserRole->duplicate($project_src_id, $project_dst_id) &&
$this->projectGroupRole->duplicate($project_src_id, $project_dst_id);
}
}

View File

@@ -0,0 +1,263 @@
<?php
namespace Kanboard\Model;
use Kanboard\Core\Security\Role;
/**
* Project User Role
*
* @package model
* @author Frederic Guillot
*/
class ProjectUserRole extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_has_users';
/**
* Get the list of project visible by the given user
*
* @access public
* @param integer $user_id
* @param array $status
* @return array
*/
public function getProjectsByUser($user_id, $status = array(Project::ACTIVE, Project::INACTIVE))
{
$userProjects = $this->db
->hashtable(Project::TABLE)
->beginOr()
->eq(self::TABLE.'.user_id', $user_id)
->eq(Project::TABLE.'.is_everybody_allowed', 1)
->closeOr()
->in(Project::TABLE.'.is_active', $status)
->join(self::TABLE, 'project_id', 'id')
->getAll(Project::TABLE.'.id', Project::TABLE.'.name');
$groupProjects = $this->projectGroupRole->getProjectsByUser($user_id, $status);
$groups = $userProjects + $groupProjects;
asort($groups);
return $groups;
}
/**
* For a given project get the role of the specified user
*
* @access public
* @param integer $project_id
* @param integer $user_id
* @return string
*/
public function getUserRole($project_id, $user_id)
{
if ($this->projectPermission->isEverybodyAllowed($project_id)) {
return Role::PROJECT_MEMBER;
}
$role = $this->db->table(self::TABLE)->eq('user_id', $user_id)->eq('project_id', $project_id)->findOneColumn('role');
if (empty($role)) {
$role = $this->projectGroupRole->getUserRole($project_id, $user_id);
}
return $role;
}
/**
* Get all users associated directly to the project
*
* @access public
* @param integer $project_id
* @return array
*/
public function getUsers($project_id)
{
return $this->db->table(self::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', self::TABLE.'.role')
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->asc(User::TABLE.'.username')
->asc(User::TABLE.'.name')
->findAll();
}
/**
* Get all users (fetch users from groups)
*
* @access public
* @param integer $project_id
* @return array
*/
public function getAllUsers($project_id)
{
$userMembers = $this->getUsers($project_id);
$groupMembers = $this->projectGroupRole->getUsers($project_id);
$members = array_merge($userMembers, $groupMembers);
return $this->user->prepareList($members);
}
/**
* Get users grouped by role
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAllUsersGroupedByRole($project_id)
{
$users = array();
$userMembers = $this->getUsers($project_id);
$groupMembers = $this->projectGroupRole->getUsers($project_id);
$members = array_merge($userMembers, $groupMembers);
foreach ($members as $user) {
if (! isset($users[$user['role']])) {
$users[$user['role']] = array();
}
$users[$user['role']][$user['id']] = $user['name'] ?: $user['username'];
}
return $users;
}
/**
* Get list of users that can be assigned to a task (only Manager and Member)
*
* @access public
* @param integer $project_id
* @return array
*/
public function getAssignableUsers($project_id)
{
if ($this->projectPermission->isEverybodyAllowed($project_id)) {
return $this->user->getList();
}
$userMembers = $this->db->table(self::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->in(self::TABLE.'.role', array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER))
->findAll();
$groupMembers = $this->projectGroupRole->getAssignableUsers($project_id);
$members = array_merge($userMembers, $groupMembers);
return $this->user->prepareList($members);
}
/**
* Get list of users that can be assigned to a task (only Manager and Member)
*
* @access public
* @param integer $project_id Project id
* @param bool $unassigned Prepend the 'Unassigned' value
* @param bool $everybody Prepend the 'Everbody' value
* @param bool $singleUser If there is only one user return only this user
* @return array
*/
public function getAssignableUsersList($project_id, $unassigned = true, $everybody = false, $singleUser = false)
{
$users = $this->getAssignableUsers($project_id);
if ($singleUser && count($users) === 1) {
return $users;
}
if ($unassigned) {
$users = array(t('Unassigned')) + $users;
}
if ($everybody) {
$users = array(User::EVERYBODY_ID => t('Everybody')) + $users;
}
return $users;
}
/**
* Add a user to the project
*
* @access public
* @param integer $project_id
* @param integer $user_id
* @param string $role
* @return boolean
*/
public function addUser($project_id, $user_id, $role)
{
return $this->db->table(self::TABLE)->insert(array(
'user_id' => $user_id,
'project_id' => $project_id,
'role' => $role,
));
}
/**
* Remove a user from the project
*
* @access public
* @param integer $project_id
* @param integer $user_id
* @return boolean
*/
public function removeUser($project_id, $user_id)
{
return $this->db->table(self::TABLE)->eq('user_id', $user_id)->eq('project_id', $project_id)->remove();
}
/**
* Change a user role for the project
*
* @access public
* @param integer $project_id
* @param integer $user_id
* @param string $role
* @return boolean
*/
public function changeUserRole($project_id, $user_id, $role)
{
return $this->db->table(self::TABLE)
->eq('user_id', $user_id)
->eq('project_id', $project_id)
->update(array(
'role' => $role,
));
}
/**
* Copy user access from a project to another one
*
* @param integer $project_src_id Project Template
* @return integer $project_dst_id Project that receives the copy
* @return boolean
*/
public function duplicate($project_src_id, $project_dst_id)
{
$rows = $this->db->table(self::TABLE)->eq('project_id', $project_src_id)->findAll();
foreach ($rows as $row) {
$result = $this->db->table(self::TABLE)->save(array(
'project_id' => $project_dst_id,
'user_id' => $row['user_id'],
'role' => $row['role'],
));
if (! $result) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace Kanboard\Model;
use Kanboard\Core\Security\Token;
/**
* Remember Me Model
*
* @package model
* @author Frederic Guillot
*/
class RememberMeSession extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'remember_me';
/**
* Expiration (60 days)
*
* @var integer
*/
const EXPIRATION = 5184000;
/**
* Get a remember me record
*
* @access public
* @param $token
* @param $sequence
* @return mixed
*/
public function find($token, $sequence)
{
return $this->db
->table(self::TABLE)
->eq('token', $token)
->eq('sequence', $sequence)
->gt('expiration', time())
->findOne();
}
/**
* Get all sessions for a given user
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getAll($user_id)
{
return $this->db
->table(self::TABLE)
->eq('user_id', $user_id)
->desc('date_creation')
->columns('id', 'ip', 'user_agent', 'date_creation', 'expiration')
->findAll();
}
/**
* Create a new RememberMe session
*
* @access public
* @param integer $user_id User id
* @param string $ip IP Address
* @param string $user_agent User Agent
* @return array
*/
public function create($user_id, $ip, $user_agent)
{
$token = hash('sha256', $user_id.$user_agent.$ip.Token::getToken());
$sequence = Token::getToken();
$expiration = time() + self::EXPIRATION;
$this->cleanup($user_id);
$this
->db
->table(self::TABLE)
->insert(array(
'user_id' => $user_id,
'ip' => $ip,
'user_agent' => $user_agent,
'token' => $token,
'sequence' => $sequence,
'expiration' => $expiration,
'date_creation' => time(),
));
return array(
'token' => $token,
'sequence' => $sequence,
'expiration' => $expiration,
);
}
/**
* Remove a session record
*
* @access public
* @param integer $session_id Session id
* @return mixed
*/
public function remove($session_id)
{
return $this->db
->table(self::TABLE)
->eq('id', $session_id)
->remove();
}
/**
* Remove old sessions for a given user
*
* @access public
* @param integer $user_id User id
* @return bool
*/
public function cleanup($user_id)
{
return $this->db
->table(self::TABLE)
->eq('user_id', $user_id)
->lt('expiration', time())
->remove();
}
/**
* Return a new sequence token and update the database
*
* @access public
* @param string $token Session token
* @return string
*/
public function updateSequence($token)
{
$sequence = Token::getToken();
$this
->db
->table(self::TABLE)
->eq('token', $token)
->update(array('sequence' => $sequence));
return $sequence;
}
}

View File

@@ -2,6 +2,8 @@
namespace Kanboard\Model;
use Kanboard\Core\Security\Role;
/**
* Task permission model
*
@@ -20,7 +22,7 @@ class TaskPermission extends Base
*/
public function canRemoveTask(array $task)
{
if ($this->userSession->isAdmin() || $this->projectPermission->isManager($task['project_id'], $this->userSession->getId())) {
if ($this->userSession->isAdmin() || $this->projectUserRole->getUserRole($task['project_id'], $this->userSession->getId()) === Role::PROJECT_MANAGER) {
return true;
} elseif (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) {
return true;

View File

@@ -7,6 +7,7 @@ use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Kanboard\Core\Session\SessionManager;
use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
/**
* User model
@@ -57,8 +58,7 @@ class User extends Base
'username',
'name',
'email',
'is_admin',
'is_project_admin',
'role',
'is_ldap_user',
'notifications_enabled',
'google_id',
@@ -91,7 +91,7 @@ class User extends Base
$this->db
->table(User::TABLE)
->eq('id', $user_id)
->eq('is_admin', 1)
->eq('role', Role::APP_ADMIN)
->exists();
}
@@ -111,48 +111,17 @@ class User extends Base
* Get a specific user by the Google id
*
* @access public
* @param string $google_id Google unique id
* @param string $column
* @param string $id
* @return array|boolean
*/
public function getByGoogleId($google_id)
public function getByExternalId($column, $id)
{
if (empty($google_id)) {
if (empty($id)) {
return false;
}
return $this->db->table(self::TABLE)->eq('google_id', $google_id)->findOne();
}
/**
* Get a specific user by the Github id
*
* @access public
* @param string $github_id Github user id
* @return array|boolean
*/
public function getByGithubId($github_id)
{
if (empty($github_id)) {
return false;
}
return $this->db->table(self::TABLE)->eq('github_id', $github_id)->findOne();
}
/**
* Get a specific user by the Gitlab id
*
* @access public
* @param string $gitlab_id Gitlab user id
* @return array|boolean
*/
public function getByGitlabId($gitlab_id)
{
if (empty($gitlab_id)) {
return false;
}
return $this->db->table(self::TABLE)->eq('gitlab_id', $gitlab_id)->findOne();
return $this->db->table(self::TABLE)->eq($column, $id)->findOne();
}
/**
@@ -289,7 +258,7 @@ class User extends Base
}
$this->removeFields($values, array('confirmation', 'current_password'));
$this->resetFields($values, array('is_admin', 'is_ldap_user', 'is_project_admin', 'disable_login_form'));
$this->resetFields($values, array('is_ldap_user', 'disable_login_form'));
$this->convertNullFields($values, array('gitlab_id'));
$this->convertIntegerFields($values, array('gitlab_id'));
}
@@ -355,10 +324,10 @@ class User extends Base
// All private projects are removed
$project_ids = $db->table(Project::TABLE)
->eq('is_private', 1)
->eq(ProjectPermission::TABLE.'.user_id', $user_id)
->join(ProjectPermission::TABLE, 'project_id', 'id')
->findAllByColumn(Project::TABLE.'.id');
->eq('is_private', 1)
->eq(ProjectUserRole::TABLE.'.user_id', $user_id)
->join(ProjectUserRole::TABLE, 'project_id', 'id')
->findAllByColumn(Project::TABLE.'.id');
if (! empty($project_ids)) {
$db->table(Project::TABLE)->in('id', $project_ids)->remove();
@@ -401,71 +370,6 @@ class User extends Base
->save(array('token' => ''));
}
/**
* Get the number of failed login for the user
*
* @access public
* @param string $username
* @return integer
*/
public function getFailedLogin($username)
{
return (int) $this->db->table(self::TABLE)->eq('username', $username)->findOneColumn('nb_failed_login');
}
/**
* Reset to 0 the counter of failed login
*
* @access public
* @param string $username
* @return boolean
*/
public function resetFailedLogin($username)
{
return $this->db->table(self::TABLE)->eq('username', $username)->update(array('nb_failed_login' => 0, 'lock_expiration_date' => 0));
}
/**
* Increment failed login counter
*
* @access public
* @param string $username
* @return boolean
*/
public function incrementFailedLogin($username)
{
return $this->db->execute('UPDATE '.self::TABLE.' SET nb_failed_login=nb_failed_login+1 WHERE username=?', array($username)) !== false;
}
/**
* Check if the account is locked
*
* @access public
* @param string $username
* @return boolean
*/
public function isLocked($username)
{
return $this->db->table(self::TABLE)
->eq('username', $username)
->neq('lock_expiration_date', 0)
->gte('lock_expiration_date', time())
->exists();
}
/**
* Lock the account for the specified duration
*
* @access public
* @param string $username Username
* @param integer $duration Duration in minutes
* @return boolean
*/
public function lock($username, $duration = 15)
{
return $this->db->table(self::TABLE)->eq('username', $username)->update(array('lock_expiration_date' => time() + $duration * 60));
}
/**
* Common validation rules
*
@@ -475,11 +379,10 @@ class User extends Base
private function commonValidationRules()
{
return array(
new Validators\MaxLength('role', t('The maximum length is %d characters', 25), 25),
new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50),
new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), self::TABLE, 'id'),
new Validators\Email('email', t('Email address invalid')),
new Validators\Integer('is_admin', t('This value must be an integer')),
new Validators\Integer('is_project_admin', t('This value must be an integer')),
new Validators\Integer('is_ldap_user', t('This value must be an integer')),
);
}
@@ -585,9 +488,7 @@ class User extends Base
$v = new Validator($values, array_merge($rules, $this->commonPasswordValidationRules()));
if ($v->execute()) {
// Check password
if ($this->authentication->authenticate($this->userSession->getUsername(), $values['current_password'])) {
if ($this->authenticationManager->passwordAuthentication($this->userSession->getUsername(), $values['current_password'], false)) {
return array(true, array());
} else {
return array(false, array('current_password' => array(t('Wrong password'))));

80
app/Model/UserFilter.php Normal file
View File

@@ -0,0 +1,80 @@
<?php
namespace Kanboard\Model;
/**
* User Filter
*
* @package model
* @author Frederic Guillot
*/
class UserFilter extends Base
{
/**
* Search query
*
* @access private
* @var string
*/
private $input;
/**
* Query
*
* @access protected
* @var \PicoDb\Table
*/
protected $query;
/**
* Initialize filter
*
* @access public
* @param string $input
* @return UserFilter
*/
public function create($input)
{
$this->query = $this->db->table(User::TABLE);
$this->input = $input;
return $this;
}
/**
* Filter users by name or username
*
* @access public
* @return UserFilter
*/
public function filterByUsernameOrByName()
{
$this->query->beginOr()
->ilike('username', '%'.$this->input.'%')
->ilike('name', '%'.$this->input.'%')
->closeOr();
return $this;
}
/**
* Get all results of the filter
*
* @access public
* @return array
*/
public function findAll()
{
return $this->query->findAll();
}
/**
* Get the PicoDb query
*
* @access public
* @return \PicoDb\Table
*/
public function getQuery()
{
return $this->query;
}
}

View File

@@ -4,6 +4,7 @@ namespace Kanboard\Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Kanboard\Core\Security\Role;
use Kanboard\Core\Csv;
/**
@@ -36,7 +37,7 @@ class UserImport extends Base
'email' => 'Email',
'name' => 'Full Name',
'is_admin' => 'Administrator',
'is_project_admin' => 'Project Administrator',
'is_manager' => 'Manager',
'is_ldap_user' => 'Remote User',
);
}
@@ -75,10 +76,21 @@ class UserImport extends Base
{
$row['username'] = strtolower($row['username']);
foreach (array('is_admin', 'is_project_admin', 'is_ldap_user') as $field) {
foreach (array('is_admin', 'is_manager', 'is_ldap_user') as $field) {
$row[$field] = Csv::getBooleanValue($row[$field]);
}
if ($row['is_admin'] == 1) {
$row['role'] = Role::APP_ADMIN;
} elseif ($row['is_manager'] == 1) {
$row['role'] = Role::APP_MANAGER;
} else {
$row['role'] = Role::APP_USER;
}
unset($row['is_admin']);
unset($row['is_manager']);
$this->removeEmptyFields($row, array('password', 'email', 'name'));
return $row;
@@ -98,8 +110,6 @@ class UserImport extends Base
new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), User::TABLE, 'id'),
new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6),
new Validators\Email('email', t('Email address invalid')),
new Validators\Integer('is_admin', t('This value must be an integer')),
new Validators\Integer('is_project_admin', t('This value must be an integer')),
new Validators\Integer('is_ldap_user', t('This value must be an integer')),
));

103
app/Model/UserLocking.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
namespace Kanboard\Model;
/**
* User Locking Model
*
* @package model
* @author Frederic Guillot
*/
class UserLocking extends Base
{
/**
* Get the number of failed login for the user
*
* @access public
* @param string $username
* @return integer
*/
public function getFailedLogin($username)
{
return (int) $this->db->table(User::TABLE)
->eq('username', $username)
->findOneColumn('nb_failed_login');
}
/**
* Reset to 0 the counter of failed login
*
* @access public
* @param string $username
* @return boolean
*/
public function resetFailedLogin($username)
{
return $this->db->table(User::TABLE)
->eq('username', $username)
->update(array(
'nb_failed_login' => 0,
'lock_expiration_date' => 0,
));
}
/**
* Increment failed login counter
*
* @access public
* @param string $username
* @return boolean
*/
public function incrementFailedLogin($username)
{
return $this->db->table(User::TABLE)
->eq('username', $username)
->increment('nb_failed_login', 1);
}
/**
* Check if the account is locked
*
* @access public
* @param string $username
* @return boolean
*/
public function isLocked($username)
{
return $this->db->table(User::TABLE)
->eq('username', $username)
->neq('lock_expiration_date', 0)
->gte('lock_expiration_date', time())
->exists();
}
/**
* Lock the account for the specified duration
*
* @access public
* @param string $username Username
* @param integer $duration Duration in minutes
* @return boolean
*/
public function lock($username, $duration = 15)
{
return $this->db->table(User::TABLE)
->eq('username', $username)
->update(array(
'lock_expiration_date' => time() + $duration * 60
));
}
/**
* Return true if the captcha must be shown
*
* @access public
* @param string $username
* @param integer $tries
* @return boolean
*/
public function hasCaptcha($username, $tries = BRUTEFORCE_CAPTCHA)
{
return $this->getFailedLogin($username) >= $tries;
}
}

View File

@@ -155,7 +155,7 @@ class UserNotification extends Base
private function getProjectMembersWithNotificationEnabled($project_id, $exclude_user_id)
{
return $this->db
->table(ProjectPermission::TABLE)
->table(ProjectUserRole::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language', User::TABLE.'.notifications_filter')
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)

View File

@@ -1,195 +0,0 @@
<?php
namespace Kanboard\Model;
/**
* User Session
*
* @package model
* @author Frederic Guillot
*/
class UserSession extends Base
{
/**
* Update user session
*
* @access public
* @param array $user
*/
public function initialize(array $user)
{
if (isset($user['password'])) {
unset($user['password']);
}
if (isset($user['twofactor_secret'])) {
unset($user['twofactor_secret']);
}
$user['id'] = (int) $user['id'];
$user['is_admin'] = isset($user['is_admin']) ? (bool) $user['is_admin'] : false;
$user['is_project_admin'] = isset($user['is_project_admin']) ? (bool) $user['is_project_admin'] : false;
$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->postAuth = array('validated' => false);
}
/**
* Return true if the user has validated the 2FA key
*
* @access public
* @return bool
*/
public function check2FA()
{
return isset($this->sessionStorage->postAuth['validated']) && $this->sessionStorage->postAuth['validated'] === true;
}
/**
* Return true if the user has 2FA enabled
*
* @access public
* @return bool
*/
public function has2FA()
{
return isset($this->sessionStorage->user['twofactor_activated']) && $this->sessionStorage->user['twofactor_activated'] === true;
}
/**
* Disable 2FA for the current session
*
* @access public
*/
public function disable2FA()
{
$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['is_admin']) && $this->sessionStorage->user['is_admin'] === true;
}
/**
* Return true if the logged user is project admin
*
* @access public
* @return bool
*/
public function isProjectAdmin()
{
return isset($this->sessionStorage->user['is_project_admin']) && $this->sessionStorage->user['is_project_admin'] === true;
}
/**
* 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;
}
}