summaryrefslogtreecommitdiff
path: root/app/Model
diff options
context:
space:
mode:
Diffstat (limited to 'app/Model')
-rw-r--r--app/Model/Acl.php289
-rw-r--r--app/Model/Authentication.php194
-rw-r--r--app/Model/Group.php24
-rw-r--r--app/Model/GroupMember.php24
-rw-r--r--app/Model/Project.php5
-rw-r--r--app/Model/ProjectAnalytic.php2
-rw-r--r--app/Model/ProjectGroupRole.php187
-rw-r--r--app/Model/ProjectPermission.php447
-rw-r--r--app/Model/ProjectUserRole.php263
-rw-r--r--app/Model/RememberMeSession.php151
-rw-r--r--app/Model/TaskPermission.php4
-rw-r--r--app/Model/User.php129
-rw-r--r--app/Model/UserFilter.php80
-rw-r--r--app/Model/UserImport.php18
-rw-r--r--app/Model/UserLocking.php103
-rw-r--r--app/Model/UserNotification.php2
-rw-r--r--app/Model/UserSession.php195
17 files changed, 956 insertions, 1161 deletions
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
deleted file mode 100644
index 62f850cb..00000000
--- a/app/Model/Acl.php
+++ /dev/null
@@ -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;
- }
-}
diff --git a/app/Model/Authentication.php b/app/Model/Authentication.php
index 83d85433..d10f2bf8 100644
--- a/app/Model/Authentication.php
+++ b/app/Model/Authentication.php
@@ -2,7 +2,6 @@
namespace Kanboard\Model;
-use Kanboard\Core\Http\Request;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Gregwar\Captcha\CaptchaBuilder;
@@ -16,113 +15,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
*
* @access public
@@ -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
+ * Validate user locking
*
- * @access public
+ * @access private
* @param array $values Form values
- * @return boolean
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
- public function validateFormCaptcha(array $values)
+ private function validateLocking(array $values)
{
- if ($this->hasCaptcha($values['username'])) {
- if (! isset($this->sessionStorage->captcha)) {
- return false;
- }
+ $result = true;
+ $errors = array();
- $builder = new CaptchaBuilder;
- $builder->setPhrase($this->sessionStorage->captcha);
- return $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : '');
+ 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 true;
+ return array($result, $errors);
}
/**
- * Create remember me session if necessary
+ * Validate password syntax
*
* @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 validateCredentials(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->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);;
}
}
diff --git a/app/Model/Group.php b/app/Model/Group.php
index 82a8887b..36171ca4 100644
--- a/app/Model/Group.php
+++ b/app/Model/Group.php
@@ -44,6 +44,18 @@ class Group extends Base
}
/**
+ * 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
*
* @access public
@@ -55,6 +67,18 @@ class Group extends Base
}
/**
+ * 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
*
* @access public
diff --git a/app/Model/GroupMember.php b/app/Model/GroupMember.php
index 04e9d495..7ed5f733 100644
--- a/app/Model/GroupMember.php
+++ b/app/Model/GroupMember.php
@@ -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();
+ }
}
diff --git a/app/Model/Project.php b/app/Model/Project.php
index a7f93099..8a949ba6 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -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);
diff --git a/app/Model/ProjectAnalytic.php b/app/Model/ProjectAnalytic.php
index 92364c0c..e77a0368 100644
--- a/app/Model/ProjectAnalytic.php
+++ b/app/Model/ProjectAnalytic.php
@@ -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];
diff --git a/app/Model/ProjectGroupRole.php b/app/Model/ProjectGroupRole.php
new file mode 100644
index 00000000..87fdec10
--- /dev/null
+++ b/app/Model/ProjectGroupRole.php
@@ -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;
+ }
+}
diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php
index d9eef4db..b311c10b 100644
--- a/app/Model/ProjectPermission.php
+++ b/app/Model/ProjectPermission.php
@@ -2,11 +2,10 @@
namespace Kanboard\Model;
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
+use Kanboard\Core\Security\Role;
/**
- * Project permission model
+ * Project Permission
*
* @package model
* @author Frederic Guillot
@@ -14,117 +13,14 @@ use SimpleValidator\Validators;
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(
@@ -148,172 +44,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
*
* @access public
@@ -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
+ * @param integer $project_id
+ * @param integer $user_id
+ * @return boolean
*/
- public function getAllowedProjects($user_id)
+ public function isUserAllowed($project_id, $user_id)
{
- if ($this->user->isAdmin($user_id)) {
- return $this->project->getListByStatus(Project::ACTIVE);
+ if ($this->userSession->isAdmin()) {
+ return true;
}
- 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 in_array(
+ $this->projectUserRole->getUserRole($project_id, $user_id),
+ array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER, Role::PROJECT_VIEWER)
+ );
}
/**
- * Return a list of active project ids where the user is member
+ * Return true if the user is assignable
*
* @access public
- * @param integer $user_id User id
- * @return array
+ * @param integer $project_id
+ * @param integer $user_id
+ * @return boolean
*/
- public function getActiveMemberProjectIds($user_id)
+ public function isMember($project_id, $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 in_array($this->projectUserRole->getUSerRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER));
}
/**
- * Return a list of active projects where the user is member
+ * Get active project ids by user
*
* @access public
- * @param integer $user_id User id
+ * @param integer $user_id
* @return array
*/
- public function getActiveMemberProjects($user_id)
+ public function getActiveProjectIds($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');
+ return array_keys($this->projectUserRole->getProjectsByUser($user_id, array(Project::ACTIVE)));
}
/**
- * Copy user access from a project to another one
+ * Copy permissions to another project
*
- * @param integer $project_src Project Template
- * @return integer $project_dst Project that receives the copy
+ * @param integer $project_src_id Project Template
+ * @param integer $project_dst_id Project that receives the copy
* @return boolean
*/
- public function duplicate($project_src, $project_dst)
- {
- $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;
- }
- }
-
- 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)
+ public function duplicate($project_src_id, $project_dst_id)
{
- $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()
- );
- }
-
- /**
- * Validate allow everybody
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateProjectModification(array $values)
- {
- $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 array(
- $v->execute(),
- $v->getErrors()
- );
+ return $this->projectUserRole->duplicate($project_src_id, $project_dst_id) &&
+ $this->projectGroupRole->duplicate($project_src_id, $project_dst_id);
}
}
diff --git a/app/Model/ProjectUserRole.php b/app/Model/ProjectUserRole.php
new file mode 100644
index 00000000..28e6c8c6
--- /dev/null
+++ b/app/Model/ProjectUserRole.php
@@ -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;
+ }
+}
diff --git a/app/Model/RememberMeSession.php b/app/Model/RememberMeSession.php
new file mode 100644
index 00000000..8989a6d7
--- /dev/null
+++ b/app/Model/RememberMeSession.php
@@ -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;
+ }
+}
diff --git a/app/Model/TaskPermission.php b/app/Model/TaskPermission.php
index 4bbe6d1d..fac2153e 100644
--- a/app/Model/TaskPermission.php
+++ b/app/Model/TaskPermission.php
@@ -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;
diff --git a/app/Model/User.php b/app/Model/User.php
index 88361ce8..7142c258 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -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();
@@ -402,71 +371,6 @@ class User 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(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
*
* @access private
@@ -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'))));
diff --git a/app/Model/UserFilter.php b/app/Model/UserFilter.php
new file mode 100644
index 00000000..ff546e96
--- /dev/null
+++ b/app/Model/UserFilter.php
@@ -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;
+ }
+}
diff --git a/app/Model/UserImport.php b/app/Model/UserImport.php
index 3c9e7a57..0ec4e802 100644
--- a/app/Model/UserImport.php
+++ b/app/Model/UserImport.php
@@ -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')),
));
diff --git a/app/Model/UserLocking.php b/app/Model/UserLocking.php
new file mode 100644
index 00000000..67e4c244
--- /dev/null
+++ b/app/Model/UserLocking.php
@@ -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;
+ }
+}
diff --git a/app/Model/UserNotification.php b/app/Model/UserNotification.php
index 3d98ebe9..e00f23c5 100644
--- a/app/Model/UserNotification.php
+++ b/app/Model/UserNotification.php
@@ -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)
diff --git a/app/Model/UserSession.php b/app/Model/UserSession.php
deleted file mode 100644
index a687952b..00000000
--- a/app/Model/UserSession.php
+++ /dev/null
@@ -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;
- }
-}