1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/kanboard_ynh.git synced 2024-09-03 19:36:17 +02:00

Merge pull request #22 from mbugeia/Dev

Update to Kanboard v1.0.24 with reverse-proxy-ldap plugin
This commit is contained in:
tostaki 2016-01-24 18:44:47 +01:00
commit 69ae0e4f34
754 changed files with 31659 additions and 23546 deletions

View file

@ -33,7 +33,8 @@ From command line:
Infos
-----
Kanboard v1.0.21
Kanboard v1.0.24
Reverse-Proxy Authentication with LDAP user provider plugin v1.0.0 https://github.com/kanboard/plugin-reverse-proxy-ldap
Yunohost forum thread: <https://forum.yunohost.org/t/kanboard-package/78>

View file

@ -28,19 +28,6 @@ define('MAIL_SMTP_ENCRYPTION', null); // Valid values are "null", "ssl" or "tls"
// Sendmail command to use when the transport is "sendmail"
define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
// Postmark API token (used to send emails through their API)
define('POSTMARK_API_TOKEN', '');
// Mailgun API key (used to send emails through their API)
define('MAILGUN_API_TOKEN', '');
// Mailgun domain name
define('MAILGUN_DOMAIN', '');
// Sendgrid API configuration
define('SENDGRID_API_USER', '');
define('SENDGRID_API_KEY', '');
// Database driver: sqlite, mysql or postgres (sqlite by default)
define('DB_DRIVER', 'mysql');
@ -63,7 +50,7 @@ define('DB_PORT', null);
define('LDAP_AUTH', false);
// LDAP server hostname
define('LDAP_SERVER', '');
define('LDAP_SERVER', 'localhost');
// LDAP server port (389 by default)
define('LDAP_PORT', 389);
@ -74,6 +61,10 @@ define('LDAP_SSL_VERIFY', true);
// Enable LDAP START_TLS
define('LDAP_START_TLS', false);
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
// Set to true if you want to preserve the case
define('LDAP_USERNAME_CASE_SENSITIVE', false);
// LDAP bind type: "anonymous", "user" or "proxy"
define('LDAP_BIND_TYPE', 'anonymous');
@ -84,43 +75,56 @@ define('LDAP_USERNAME', null);
// LDAP password to use for proxy mode
define('LDAP_PASSWORD', null);
// LDAP account base, i.e. root of all user account
// Example: ou=People,dc=example,dc=com
define('LDAP_ACCOUNT_BASE', '');
// LDAP DN for users
// Example for ActiveDirectory: CN=Users,DC=kanboard,DC=local
// Example for OpenLDAP: ou=People,dc=example,dc=com
define('LDAP_USER_BASE_DN', 'ou=users,dc=yunohost,dc=org');
// LDAP query pattern to use when searching for a user account
// LDAP pattern to use when searching for a user account
// Example for ActiveDirectory: '(&(objectClass=user)(sAMAccountName=%s))'
// Example for OpenLDAP: 'uid=%s'
define('LDAP_USER_PATTERN', '');
define('LDAP_USER_FILTER', 'uid=%s');
// Name of an attribute of the user account object which should be used as the full name of the user
define('LDAP_ACCOUNT_FULLNAME', 'displayname');
// Name of an attribute of the user account object which should be used as the email of the user
define('LDAP_ACCOUNT_EMAIL', 'mail');
// Name of an attribute of the user account object which should be used as the id of the user. (optional)
// LDAP attribute for username
// Example for ActiveDirectory: 'samaccountname'
// Example for OpenLDAP: 'uid'
define('LDAP_ACCOUNT_ID', '');
define('LDAP_USER_ATTRIBUTE_USERNAME', 'uid');
// LDAP Attribute for group membership
define('LDAP_ACCOUNT_MEMBEROF', 'memberof');
// LDAP attribute for user full name
// Example for ActiveDirectory: 'displayname'
// Example for OpenLDAP: 'cn'
define('LDAP_USER_ATTRIBUTE_FULLNAME', 'displayname');
// DN for administrators
// Example: CN=Kanboard Admins,CN=Users,DC=kanboard,DC=local
// LDAP attribute for user email
define('LDAP_USER_ATTRIBUTE_EMAIL', 'mail');
// LDAP attribute to find groups in user profile
define('LDAP_USER_ATTRIBUTE_GROUPS', 'memberof');
// Allow automatic LDAP user creation
define('LDAP_USER_CREATION', true);
// LDAP DN for administrators
// Example: CN=Kanboard-Admins,CN=Users,DC=kanboard,DC=local
define('LDAP_GROUP_ADMIN_DN', '');
// DN for project administrators
// Example: CN=Kanboard Project Admins,CN=Users,DC=kanboard,DC=local
define('LDAP_GROUP_PROJECT_ADMIN_DN', '');
// LDAP DN for managers
// Example: CN=Kanboard Managers,CN=Users,DC=kanboard,DC=local
define('LDAP_GROUP_MANAGER_DN', '');
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
// Set to true if you want to preserve the case
define('LDAP_USERNAME_CASE_SENSITIVE', false);
// Enable LDAP group provider for project permissions
// The end-user will be able to browse LDAP groups from the user interface and allow access to specified projects
define('LDAP_GROUP_PROVIDER', false);
// Automatically create user account
define('LDAP_ACCOUNT_CREATION', true);
// LDAP Base DN for groups
define('LDAP_GROUP_BASE_DN', '');
// LDAP group filter
// Example for ActiveDirectory: (&(objectClass=group)(sAMAccountName=%s*))
define('LDAP_GROUP_FILTER', '');
// LDAP attribute for the group name
define('LDAP_GROUP_ATTRIBUTE_NAME', 'cn');
// Enable/disable Google authentication
define('GOOGLE_AUTH', false);
@ -168,7 +172,7 @@ define('GITLAB_OAUTH_TOKEN_URL', 'https://gitlab.com/oauth/token');
define('GITLAB_API_URL', 'https://gitlab.com/api/v3/');
// Enable/disable the reverse proxy authentication
define('REVERSE_PROXY_AUTH', true);
define('REVERSE_PROXY_AUTH', false);
// Header name to use for the username
define('REVERSE_PROXY_USER_HEADER', 'REMOTE_USER');

View file

@ -10,12 +10,19 @@ admin=$(sudo yunohost app setting $app adminusername)
email=$(sudo yunohost user info $admin | grep mail: | sed "s/mail: //g")
db_pwd=$(sudo yunohost app setting $app mysqlpwd)
# flush php sessions before upgrade
sudo rm -rf /var/lib/php5/session/*
final_path=/var/www/$app
sudo mv $final_path $final_path.old
sudo mkdir -p $final_path
sudo cp -a ../sources/. $final_path
sudo cp -a $final_path.old/data $final_path/data
# restore data
sudo cp -a $final_path.old/data $final_path
# restore plugins
sudo cp -a $final_path.old/plugins $final_path
# delete temp directory
sudo rm -Rf $final_path.old
# Copy and edit config.php

View file

@ -1,11 +1,118 @@
Version 1.0.24
--------------
New features:
* Forgot Password
* Add drop-down menu on each board column title to close all tasks
* Add Malay language
* Add new API procedures for groups, roles, project permissions and to move/duplicate tasks to another project
Improvements:
* Avoid to send XHR request when a task has not moved after a drag and drop
* Set maximum dropzone height when the individual column scrolling is disabled
* Always show the search box in board selector
* Replace logout link by a drop-down menu
* Handle notification for group members attached to a project
* Return the highest role for a project when a user is member of multiple groups
* Show in user interface the saving state of the task
* Add drop-down menu for subtasks, categories, swimlanes, columns, custom filters, task links and groups
* Add new template hooks
* Application settings are not cached anymore in the session
* Do not check board status during task move
* Move validators to a separate namespace
* Improve and write unit tests for reports
* Reduce the number of SQL queries for project daily column stats
* Remove event subscriber to update date_moved field
* Make sure that some event subscribers are not executed multiple times
* Show rendering time of individual templates when debug mode is enabled
* Make sure that no events are fired if nothing has been modified in the task
* Make dashboard section title clickable
* Add unit tests for LastLogin
Bug fixes:
* Automatic action listeners were using the same instance
* Fix wrong link for category in task footer
* Unable to set currency rate with Postgres database
* Avoid automatic actions that change the color to fire subsequent events
* Unable to unassign a task from the API
* Revert back previous optimizations of TaskPosition (incompatibility with some environment)
Version 1.0.23
--------------
Breaking changes:
* Plugin API changes for Automatic Actions
* Automatic Action to close a task doesn't have the column parameter anymore (use the action "Close a task in a specific column")
* Action name stored in the database is now the absolute class name
* Core functionalities moved to external plugins:
- Github Webhook: https://github.com/kanboard/plugin-github-webhook
- Gitlab Webhook: https://github.com/kanboard/plugin-gitlab-webhook
- Bitbucket Webhook: https://github.com/kanboard/plugin-bitbucket-webhook
New features:
* Added support of user mentions (@username)
* Added report to compare working hours between open and closed tasks
* Added the possibility to define custom routes from plugins
* Added new method to remove metadata
Improvements:
* Improve Two-Factor activation and plugin API
* Improving performance during task position change (SQL queries are 3 times faster than before)
* Do not show window scrollbars when individual column scrolling is enabled
* Automatic Actions code improvements and unit tests
* Increase action name column length in actions table
Bug fixes:
* Fix compatibility issue with FreeBSD for session.hash_function parameter
* Fix wrong constant name that causes a PHP error in project management section
* Fix pagination in group members listing
* Avoid PHP error when enabling LDAP group provider with PHP < 5.5
Version 1.0.22
--------------
Breaking changes:
* LDAP configuration parameters changes (See documentation)
* SQL table changes:
- "users" table: added new column "role" and removed columns "is_admin" and "is_project_admin"
- "project_has_users" table: replaced column "is_owner" with column "role"
- Sqlite does not support alter table, old columns still there but unused
* API procedure changes:
- createUser
- createLdapUser
- updateUser
- updateTask
* Event removed: "session.bootstrap", use "app.boostrap" instead
New features:
* Add pluggable authentication and authorization system (complete rewrite)
* Add groups (teams/organization)
* Add LDAP groups synchronization
* Add project group permissions
* Add new project role Viewer
* Add generic LDAP client library
* Add search query attribute for task link
* Add the possibility to define API token in config file
* Add capability to reopen Gitlab issues
* Try to load config.php from /data if not available
Version 1.0.21
--------------
Breaking changes:
* Projects with duplicate name are now allowed:
For Postgres and Mysql the unique constraint is removed by database migration
However Sqlite does not support alter table, only new databases will have the unique constraint removed
* Projects with duplicate names are now allowed:
- For Postgres and Mysql the unique constraint is removed by database migration
- However Sqlite does not support alter table, only new databases will have the unique constraint removed
New features:
@ -14,7 +121,7 @@ New features:
Improvements:
* Dropdown menu entry are now clickable outside of the html link
* Dropdown menu entries are now clickable outside of the html link
* Improve error handling of plugins
* Use PHP7 function random_bytes() to generate tokens if available
* CSV task export show the assignee name in addition to the assignee username
@ -62,7 +169,7 @@ Improvements:
Bug fixes:
* People should not see any tasks during a search when they are not associated to a project
* Avoid to disable the default swimlane during renaming when there is no other activated swimlane
* Avoid disabling the default swimlane during renaming when there is no other activated swimlane
Version 1.0.19
--------------
@ -94,15 +201,15 @@ Improvements:
* Offer alternative method to create Mysql and Postgres databases (import sql dump)
* Make sure there is always a trailing slash for application_url
* Do not show the checkbox "Show default swimlane" when there is no active swimlanes
* Append filters instead of replacing value for users and categories dropdowns
* Append filters instead of replacing value for users and categories drop-downs
* Do not show empty swimlanes in public view
* Change swimlane layout to save space on the screen
* Add the possibility to set/unset max column height (column scrolling)
* Show "Open this task" in dropdown menu for closed tasks
* Show "Open this task" in drop-down menu for closed tasks
* Show assignee on card only when someone is assigned (hide nobody text)
* Highlight selected item in dropdown menus
* Highlight selected item in drop-down menus
* Gantt chart: change bar color according to task progress
* Replace color dropdown by color picker in task forms
* Replace color drop-down by color picker in task forms
* Creating another task stay in the popover (no full page refresh anymore)
* Avoid scrollbar in Gantt chart for row title on Windows platform
* Remove unnecessary margin for calendar header
@ -114,14 +221,14 @@ Improvements:
Others:
* Data directory permissions are not checked anymore
* Data directory permission are not checked anymore
* Data directory is not mandatory anymore for people that use a remote database and remote object storage
Bug fixes:
* Fix typo in template that prevent the Gitlab OAuth link to be displayed
* Fix typo in template that prevents Gitlab OAuth link to be displayed
* Fix Markdown preview links focus
* Avoid dropdown menu to be truncated inside a column with scrolling
* Avoid drop-down menu to be truncated inside a column with scrolling
* Deleting subtask doesn't update task time tracking
* Fix Mysql error about gitlab_id when creating remote user
* Fix subtask timer bug (event called recursively)
@ -139,7 +246,7 @@ New features:
* Add hide/show columns
* Add Gantt chart for projects and tasks
* Add new role "Project Administrator"
* Add login bruteforce protection with captcha and account lockdown
* Add login brute force protection with captcha and account lockdown
* Add new api procedures: getDefaultTaskColor(), getDefaultTaskColors() and getColorList()
* Add user api access
* Add config parameter to define session duration
@ -147,7 +254,7 @@ New features:
* Add start/end date for projects
* Add new automated action to change task color based on the task link
* Add milestone marker in board task
* Add search in task title when using an integer only input
* Add search for task title when using an integer only input
* Add Portuguese (European) translation
* Add Norwegian translation
@ -164,16 +271,16 @@ Improvements:
* Improve sidebar menus
* Add no referrer policy in meta tags
* Run automated unit tests with Sqlite/Mysql/Postgres on Travis-ci
* Add Makefile and remove the scripts directory
* Add Makefile and remove the "scripts" directory
Bug fixes:
* Wrong template name for subtasks tooltip due to previous refactoring
* Fix broken url for closed tasks in project view
* Fix permission issue when changing the url manually
* Fix bug task estimate is reseted when using subtask timer
* Fix bug task estimate is reset when using subtask timer
* Fix screenshot feature with Firefox 40
* Fix bug when uploading files with cyrilic characters
* Fix bug when uploading files with Cyrilic characters
Version 1.0.17
--------------
@ -187,14 +294,14 @@ New features:
* Added new dashboard layout
* Added new layout for board/calendar/list views
* Added filters helper for search forms
* Added settings option to disable subtask timer
* Added settings option to include or exclude closed tasks into CFD
* Added settings option to define the default task color
* Added setting option to disable subtask timer
* Added setting option to include or exclude closed tasks into CFD
* Added setting option to define the default task color
* Added new config option to disable automatic creation of LDAP accounts
* Added loading icon on board view
* Prompt user when moving or duplicate a task to another project
* Added current values when moving/duplicate a task to another project and add a loading icon
* Added memory consumption in debug log
* Added memory consumption to debug log
* Added form to create remote user
* Added edit form for user authentication
* Added config option to hide login form
@ -205,7 +312,7 @@ New features:
* Added new report: Lead and cycle time for projects
* Added new report: Average time spent into each column
* Added task analytics
* Added icon to set automatically the start date
* Added icon to set the start date automatically
* Added datetime picker for start date
Improvements:
@ -214,8 +321,8 @@ Improvements:
* Display user initials when tasks are in collapsed mode
* Show title in tooltip for collapsed tasks
* Improve alert box fadeout to avoid an empty space
* Set focus on the dropdown for category popover
* Make escape keyboard shorcut global
* Set focus on the drop-down for category popover
* Make escape keyboard shortcut global
* Check the box remember me by default
* Store redirect login url in session instead of using url parameter
* Update Gitlab webhook
@ -242,7 +349,7 @@ Translations:
Bug fixes:
* Screenshot dropdown: unexpected scroll down on the board view and focus lost when clicking on the drop zone
* Screenshot drop-down: unexpected scroll down on the board view and focus lost when clicking on the drop zone
* No creator when duplicating a task
* Avoid the creation of multiple subtask timer for the same task and user

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2015 Frédéric Guillot
Copyright (c) 2014-2016 Frédéric Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -3,7 +3,6 @@
namespace Kanboard\Action;
use Kanboard\Event\GenericEvent;
use Pimple\Container;
/**
* Base class for automatic actions
@ -13,6 +12,14 @@ use Pimple\Container;
*/
abstract class Base extends \Kanboard\Core\Base
{
/**
* Extended events
*
* @access private
* @var array
*/
private $compatibleEvents = array();
/**
* Flag for called listener
*
@ -27,7 +34,7 @@ abstract class Base extends \Kanboard\Core\Base
* @access private
* @var integer
*/
private $project_id = 0;
private $projectId = 0;
/**
* User parameters
@ -38,20 +45,25 @@ abstract class Base extends \Kanboard\Core\Base
private $params = array();
/**
* Attached event name
* Get automatic action name
*
* @access protected
* @var string
* @final
* @access public
* @return string
*/
protected $event_name = '';
final public function getName()
{
return '\\'.get_called_class();
}
/**
* Container instance
* Get automatic action description
*
* @access protected
* @var \Pimple\Container
* @abstract
* @access public
* @return string
*/
protected $container;
abstract public function getDescription();
/**
* Execute the action
@ -99,22 +111,6 @@ abstract class Base extends \Kanboard\Core\Base
*/
abstract public function hasRequiredCondition(array $data);
/**
* Constructor
*
* @access public
* @param \Pimple\Container $container Container
* @param integer $project_id Project id
* @param string $event_name Attached event name
*/
public function __construct(Container $container, $project_id, $event_name)
{
$this->container = $container;
$this->project_id = $project_id;
$this->event_name = $event_name;
$this->called = false;
}
/**
* Return class information
*
@ -123,7 +119,25 @@ abstract class Base extends \Kanboard\Core\Base
*/
public function __toString()
{
return get_called_class();
$params = array();
foreach ($this->params as $key => $value) {
$params[] = $key.'='.var_export($value, true);
}
return $this->getName().'('.implode('|', $params).'])';
}
/**
* Set project id
*
* @access public
* @return Base
*/
public function setProjectId($project_id)
{
$this->projectId = $project_id;
return $this;
}
/**
@ -134,7 +148,7 @@ abstract class Base extends \Kanboard\Core\Base
*/
public function getProjectId()
{
return $this->project_id;
return $this->projectId;
}
/**
@ -143,10 +157,12 @@ abstract class Base extends \Kanboard\Core\Base
* @access public
* @param string $name Parameter name
* @param mixed $value Value
* @param Base
*/
public function setParam($name, $value)
{
$this->params[$name] = $value;
return $this;
}
/**
@ -154,24 +170,25 @@ abstract class Base extends \Kanboard\Core\Base
*
* @access public
* @param string $name Parameter name
* @param mixed $default_value Default value
* @param mixed $default Default value
* @return mixed
*/
public function getParam($name, $default_value = null)
public function getParam($name, $default = null)
{
return isset($this->params[$name]) ? $this->params[$name] : $default_value;
return isset($this->params[$name]) ? $this->params[$name] : $default;
}
/**
* Check if an action is executable (right project and required parameters)
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action is executable
* @param array $data
* @param string $eventName
* @return bool
*/
public function isExecutable(array $data)
public function isExecutable(array $data, $eventName)
{
return $this->hasCompatibleEvent() &&
return $this->hasCompatibleEvent($eventName) &&
$this->hasRequiredProject($data) &&
$this->hasRequiredParameters($data) &&
$this->hasRequiredCondition($data);
@ -181,11 +198,12 @@ abstract class Base extends \Kanboard\Core\Base
* Check if the event is compatible with the action
*
* @access public
* @param string $eventName
* @return bool
*/
public function hasCompatibleEvent()
public function hasCompatibleEvent($eventName)
{
return in_array($this->event_name, $this->getCompatibleEvents());
return in_array($eventName, $this->getEvents());
}
/**
@ -197,7 +215,7 @@ abstract class Base extends \Kanboard\Core\Base
*/
public function hasRequiredProject(array $data)
{
return isset($data['project_id']) && $data['project_id'] == $this->project_id;
return isset($data['project_id']) && $data['project_id'] == $this->getProjectId();
}
/**
@ -222,10 +240,11 @@ abstract class Base extends \Kanboard\Core\Base
* Execute the action
*
* @access public
* @param \Event\GenericEvent $event Event data dictionary
* @return bool True if the action was executed or false when not executed
* @param \Kanboard\Event\GenericEvent $event
* @param string $eventName
* @return bool
*/
public function execute(GenericEvent $event)
public function execute(GenericEvent $event, $eventName)
{
// Avoid infinite loop, a listener instance can be called only one time
if ($this->called) {
@ -233,17 +252,44 @@ abstract class Base extends \Kanboard\Core\Base
}
$data = $event->getAll();
$result = false;
$executable = $this->isExecutable($data, $eventName);
$executed = false;
if ($this->isExecutable($data)) {
if ($executable) {
$this->called = true;
$result = $this->doAction($data);
$executed = $this->doAction($data);
}
if (DEBUG) {
$this->logger->debug(get_called_class().' => '.($result ? 'true' : 'false'));
$this->logger->debug($this.' ['.$eventName.'] => executable='.var_export($executable, true).' exec_success='.var_export($executed, true));
return $executed;
}
/**
* Register a new event for the automatic action
*
* @access public
* @param string $event
* @param string $description
*/
public function addEvent($event, $description = '')
{
if ($description !== '') {
$this->eventManager->register($event, $description);
}
return $result;
$this->compatibleEvents[] = $event;
return $this;
}
/**
* Get all compatible events of an automatic action
*
* @access public
* @return array
*/
public function getEvents()
{
return array_unique(array_merge($this->getCompatibleEvents(), $this->compatibleEvents));
}
}

View file

@ -2,10 +2,6 @@
namespace Kanboard\Action;
use Kanboard\Integration\BitbucketWebhook;
use Kanboard\Integration\GithubWebhook;
use Kanboard\Integration\GitlabWebhook;
/**
* Create automatically a comment from a webhook
*
@ -14,6 +10,17 @@ use Kanboard\Integration\GitlabWebhook;
*/
class CommentCreation extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Create a comment from an external provider');
}
/**
* Get the list of compatible events
*
@ -22,14 +29,7 @@ class CommentCreation extends Base
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_COMMENT,
GithubWebhook::EVENT_COMMIT,
BitbucketWebhook::EVENT_ISSUE_COMMENT,
BitbucketWebhook::EVENT_COMMIT,
GitlabWebhook::EVENT_COMMIT,
GitlabWebhook::EVENT_ISSUE_COMMENT,
);
return array();
}
/**
@ -67,9 +67,9 @@ class CommentCreation extends Base
{
return (bool) $this->comment->create(array(
'reference' => isset($data['reference']) ? $data['reference'] : '',
'comment' => empty($data['comment']) ? $data['commit_comment'] : $data['comment'],
'comment' => $data['comment'],
'task_id' => $data['task_id'],
'user_id' => empty($data['user_id']) ? 0 : $data['user_id'],
'user_id' => isset($data['user_id']) && $this->projectPermission->isAssignable($this->getProjectId(), $data['user_id']) ? $data['user_id'] : 0,
));
}
@ -82,6 +82,6 @@ class CommentCreation extends Base
*/
public function hasRequiredCondition(array $data)
{
return ! empty($data['comment']) || ! empty($data['commit_comment']);
return ! empty($data['comment']);
}
}

View file

@ -5,13 +5,24 @@ namespace Kanboard\Action;
use Kanboard\Model\Task;
/**
* Add a log of the triggering event to the task description.
* Add a comment of the triggering event to the task description.
*
* @package action
* @author Oren Ben-Kiki
*/
class TaskLogMoveAnotherColumn extends Base
class CommentCreationMoveTaskColumn extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Add a comment log when moving the task between columns');
}
/**
* Get the list of compatible events
*

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskAssignCategoryColor extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign automatically a category based on a color');
}
/**
* Get the list of compatible events
*

View file

@ -2,8 +2,6 @@
namespace Kanboard\Action;
use Kanboard\Integration\GithubWebhook;
/**
* Set a category automatically according to a label
*
@ -12,6 +10,17 @@ use Kanboard\Integration\GithubWebhook;
*/
class TaskAssignCategoryLabel extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Change the category based on an external label');
}
/**
* Get the list of compatible events
*
@ -20,9 +29,7 @@ class TaskAssignCategoryLabel extends Base
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_LABEL_CHANGE,
);
return array();
}
/**
@ -64,7 +71,7 @@ class TaskAssignCategoryLabel extends Base
{
$values = array(
'id' => $data['task_id'],
'category_id' => isset($data['category_id']) ? $data['category_id'] : $this->getParam('category_id'),
'category_id' => $this->getParam('category_id'),
);
return $this->taskModification->update($values);
@ -79,6 +86,6 @@ class TaskAssignCategoryLabel extends Base
*/
public function hasRequiredCondition(array $data)
{
return $data['label'] == $this->getParam('label');
return $data['label'] == $this->getParam('label') && empty($data['category_id']);
}
}

View file

@ -13,6 +13,17 @@ use Kanboard\Model\TaskLink;
*/
class TaskAssignCategoryLink extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign automatically a category based on a link');
}
/**
* Get the list of compatible events
*
@ -65,7 +76,7 @@ class TaskAssignCategoryLink extends Base
{
$values = array(
'id' => $data['task_id'],
'category_id' => isset($data['category_id']) ? $data['category_id'] : $this->getParam('category_id'),
'category_id' => $this->getParam('category_id'),
);
return $this->taskModification->update($values);

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskAssignColorCategory extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign automatically a color based on a category');
}
/**
* Get the list of compatible events
*
@ -67,7 +78,7 @@ class TaskAssignColorCategory extends Base
'color_id' => $this->getParam('color_id'),
);
return $this->taskModification->update($values);
return $this->taskModification->update($values, false);
}
/**

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskAssignColorColumn extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign a color when the task is moved to a specific column');
}
/**
* Get the list of compatible events
*
@ -68,7 +79,7 @@ class TaskAssignColorColumn extends Base
'color_id' => $this->getParam('color_id'),
);
return $this->taskModification->update($values);
return $this->taskModification->update($values, false);
}
/**

View file

@ -12,6 +12,17 @@ use Kanboard\Model\TaskLink;
*/
class TaskAssignColorLink extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Change task color when using a specific task link');
}
/**
* Get the list of compatible events
*
@ -67,7 +78,7 @@ class TaskAssignColorLink extends Base
'color_id' => $this->getParam('color_id'),
);
return $this->taskModification->update($values);
return $this->taskModification->update($values, false);
}
/**

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskAssignColorUser extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign a color to a specific user');
}
/**
* Get the list of compatible events
*
@ -68,7 +79,7 @@ class TaskAssignColorUser extends Base
'color_id' => $this->getParam('color_id'),
);
return $this->taskModification->update($values);
return $this->taskModification->update($values, false);
}
/**

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskAssignCurrentUser extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign the task to the person who does the action');
}
/**
* Get the list of compatible events
*
@ -22,7 +33,6 @@ class TaskAssignCurrentUser extends Base
{
return array(
Task::EVENT_CREATE,
Task::EVENT_MOVE_COLUMN,
);
}
@ -34,9 +44,7 @@ class TaskAssignCurrentUser extends Base
*/
public function getActionRequiredParameters()
{
return array(
'column_id' => t('Column'),
);
return array();
}
/**
@ -49,7 +57,6 @@ class TaskAssignCurrentUser extends Base
{
return array(
'task_id',
'column_id',
);
}
@ -83,6 +90,6 @@ class TaskAssignCurrentUser extends Base
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id');
return true;
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\Task;
/**
* Assign a task to the logged user on column change
*
* @package action
* @author Frederic Guillot
*/
class TaskAssignCurrentUserColumn extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign the task to the person who does the action when the column is changed');
}
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
Task::EVENT_MOVE_COLUMN,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'column_id' => t('Column'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
);
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
if (! $this->userSession->isLogged()) {
return false;
}
$values = array(
'id' => $data['task_id'],
'owner_id' => $this->userSession->getId(),
);
return $this->taskModification->update($values);
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id');
}
}

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskAssignSpecificUser extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Assign the task to a specific user');
}
/**
* Get the list of compatible events
*

View file

@ -2,9 +2,6 @@
namespace Kanboard\Action;
use Kanboard\Integration\GithubWebhook;
use Kanboard\Integration\BitbucketWebhook;
/**
* Assign a task to someone
*
@ -13,6 +10,17 @@ use Kanboard\Integration\BitbucketWebhook;
*/
class TaskAssignUser extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Change the assignee based on an external username');
}
/**
* Get the list of compatible events
*
@ -21,10 +29,7 @@ class TaskAssignUser extends Base
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE,
BitbucketWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE,
);
return array();
}
/**
@ -78,6 +83,6 @@ class TaskAssignUser extends Base
*/
public function hasRequiredCondition(array $data)
{
return true;
return $this->projectPermission->isAssignable($this->getProjectId(), $data['owner_id']);
}
}

View file

@ -2,11 +2,6 @@
namespace Kanboard\Action;
use Kanboard\Integration\GitlabWebhook;
use Kanboard\Integration\GithubWebhook;
use Kanboard\Integration\BitbucketWebhook;
use Kanboard\Model\Task;
/**
* Close automatically a task
*
@ -15,6 +10,17 @@ use Kanboard\Model\Task;
*/
class TaskClose extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Close a task');
}
/**
* Get the list of compatible events
*
@ -23,15 +29,7 @@ class TaskClose extends Base
*/
public function getCompatibleEvents()
{
return array(
Task::EVENT_MOVE_COLUMN,
GithubWebhook::EVENT_COMMIT,
GithubWebhook::EVENT_ISSUE_CLOSED,
GitlabWebhook::EVENT_COMMIT,
GitlabWebhook::EVENT_ISSUE_CLOSED,
BitbucketWebhook::EVENT_COMMIT,
BitbucketWebhook::EVENT_ISSUE_CLOSED,
);
return array();
}
/**
@ -42,17 +40,7 @@ class TaskClose extends Base
*/
public function getActionRequiredParameters()
{
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
case GitlabWebhook::EVENT_COMMIT:
case GitlabWebhook::EVENT_ISSUE_CLOSED:
case BitbucketWebhook::EVENT_COMMIT:
case BitbucketWebhook::EVENT_ISSUE_CLOSED:
return array();
default:
return array('column_id' => t('Column'));
}
return array();
}
/**
@ -63,17 +51,7 @@ class TaskClose extends Base
*/
public function getEventRequiredParameters()
{
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
case GitlabWebhook::EVENT_COMMIT:
case GitlabWebhook::EVENT_ISSUE_CLOSED:
case BitbucketWebhook::EVENT_COMMIT:
case BitbucketWebhook::EVENT_ISSUE_CLOSED:
return array('task_id');
default:
return array('task_id', 'column_id');
}
return array('task_id');
}
/**
@ -97,16 +75,6 @@ class TaskClose extends Base
*/
public function hasRequiredCondition(array $data)
{
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
case GitlabWebhook::EVENT_COMMIT:
case GitlabWebhook::EVENT_ISSUE_CLOSED:
case BitbucketWebhook::EVENT_COMMIT:
case BitbucketWebhook::EVENT_ISSUE_CLOSED:
return true;
default:
return $data['column_id'] == $this->getParam('column_id');
}
return true;
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Kanboard\Action;
use Kanboard\Model\Task;
/**
* Close automatically a task in a specific column
*
* @package action
* @author Frederic Guillot
*/
class TaskCloseColumn extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Close a task in a specific column');
}
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
Task::EVENT_MOVE_COLUMN,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array('column_id' => t('Column'));
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array('task_id', 'column_id');
}
/**
* Execute the action (close the task)
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
return $this->taskStatus->close($data['task_id']);
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id');
}
}

View file

@ -2,10 +2,6 @@
namespace Kanboard\Action;
use Kanboard\Integration\GithubWebhook;
use Kanboard\Integration\GitlabWebhook;
use Kanboard\Integration\BitbucketWebhook;
/**
* Create automatically a task from a webhook
*
@ -14,6 +10,17 @@ use Kanboard\Integration\BitbucketWebhook;
*/
class TaskCreation extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Create a task from an external provider');
}
/**
* Get the list of compatible events
*
@ -22,11 +29,7 @@ class TaskCreation extends Base
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_OPENED,
GitlabWebhook::EVENT_ISSUE_OPENED,
BitbucketWebhook::EVENT_ISSUE_OPENED,
);
return array();
}
/**

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskDuplicateAnotherProject extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Duplicate the task to another project');
}
/**
* Get the list of compatible events
*
@ -51,7 +62,6 @@ class TaskDuplicateAnotherProject extends Base
return array(
'task_id',
'column_id',
'project_id',
);
}
@ -65,7 +75,6 @@ class TaskDuplicateAnotherProject extends Base
public function doAction(array $data)
{
$destination_column_id = $this->board->getFirstColumn($this->getParam('project_id'));
return (bool) $this->taskDuplication->duplicateToProject($data['task_id'], $this->getParam('project_id'), null, $destination_column_id);
}

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskEmail extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Send a task by email to someone');
}
/**
* Get the list of compatible events
*

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskMoveAnotherProject extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Move the task to another project');
}
/**
* Get the list of compatible events
*

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskMoveColumnAssigned extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Move the task to another column when assigned to a user');
}
/**
* Get the list of compatible events
*
@ -51,7 +62,6 @@ class TaskMoveColumnAssigned extends Base
return array(
'task_id',
'column_id',
'project_id',
'owner_id'
);
}

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskMoveColumnCategoryChange extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Move the task to another column when the category is changed');
}
/**
* Get the list of compatible events
*
@ -50,7 +61,6 @@ class TaskMoveColumnCategoryChange extends Base
return array(
'task_id',
'column_id',
'project_id',
'category_id',
);
}
@ -71,7 +81,8 @@ class TaskMoveColumnCategoryChange extends Base
$data['task_id'],
$this->getParam('dest_column_id'),
$original_task['position'],
$original_task['swimlane_id']
$original_task['swimlane_id'],
false
);
}

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskMoveColumnUnAssigned extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Move the task to another column when assignee is cleared');
}
/**
* Get the list of compatible events
*
@ -51,7 +62,6 @@ class TaskMoveColumnUnAssigned extends Base
return array(
'task_id',
'column_id',
'project_id',
'owner_id'
);
}

View file

@ -2,9 +2,6 @@
namespace Kanboard\Action;
use Kanboard\Integration\GithubWebhook;
use Kanboard\Integration\BitbucketWebhook;
/**
* Open automatically a task
*
@ -13,6 +10,17 @@ use Kanboard\Integration\BitbucketWebhook;
*/
class TaskOpen extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Open a task');
}
/**
* Get the list of compatible events
*
@ -21,10 +29,7 @@ class TaskOpen extends Base
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_REOPENED,
BitbucketWebhook::EVENT_ISSUE_REOPENED,
);
return array();
}
/**

View file

@ -12,6 +12,17 @@ use Kanboard\Model\Task;
*/
class TaskUpdateStartDate extends Base
{
/**
* Get automatic action description
*
* @access public
* @return string
*/
public function getDescription()
{
return t('Automatically update the start date');
}
/**
* Get the list of compatible events
*
@ -66,7 +77,7 @@ class TaskUpdateStartDate extends Base
'date_started' => time(),
);
return $this->taskModification->update($values);
return $this->taskModification->update($values, false);
}
/**

View file

@ -0,0 +1,114 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\Task;
/**
* Average Lead and Cycle Time
*
* @package analytic
* @author Frederic Guillot
*/
class AverageLeadCycleTimeAnalytic extends Base
{
/**
* Build report
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function build($project_id)
{
$stats = array(
'count' => 0,
'total_lead_time' => 0,
'total_cycle_time' => 0,
'avg_lead_time' => 0,
'avg_cycle_time' => 0,
);
$tasks = $this->getTasks($project_id);
foreach ($tasks as &$task) {
$stats['count']++;
$stats['total_lead_time'] += $this->calculateLeadTime($task);
$stats['total_cycle_time'] += $this->calculateCycleTime($task);
}
$stats['avg_lead_time'] = $this->calculateAverage($stats, 'total_lead_time');
$stats['avg_cycle_time'] = $this->calculateAverage($stats, 'total_cycle_time');
return $stats;
}
/**
* Calculate average
*
* @access private
* @param array &$stats
* @param string $field
* @return float
*/
private function calculateAverage(array &$stats, $field)
{
if ($stats['count'] > 0) {
return (int) ($stats[$field] / $stats['count']);
}
return 0;
}
/**
* Calculate lead time
*
* @access private
* @param array &$task
* @return integer
*/
private function calculateLeadTime(array &$task)
{
$end = $task['date_completed'] ?: time();
$start = $task['date_creation'];
return $end - $start;
}
/**
* Calculate cycle time
*
* @access private
* @param array &$task
* @return integer
*/
private function calculateCycleTime(array &$task)
{
if (empty($task['date_started'])) {
return 0;
}
$end = $task['date_completed'] ?: time();
$start = $task['date_started'];
return $end - $start;
}
/**
* Get the 1000 last created tasks
*
* @access private
* @return array
*/
private function getTasks($project_id)
{
return $this->db
->table(Task::TABLE)
->columns('date_completed', 'date_creation', 'date_started')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
}
}

View file

@ -0,0 +1,153 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\Task;
/**
* Average Time Spent by Column
*
* @package analytic
* @author Frederic Guillot
*/
class AverageTimeSpentColumnAnalytic extends Base
{
/**
* Build report
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function build($project_id)
{
$stats = $this->initialize($project_id);
$this->processTasks($stats, $project_id);
$this->calculateAverage($stats);
return $stats;
}
/**
* Initialize default values for each column
*
* @access private
* @param integer $project_id
* @return array
*/
private function initialize($project_id)
{
$stats = array();
$columns = $this->board->getColumnsList($project_id);
foreach ($columns as $column_id => $column_title) {
$stats[$column_id] = array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => $column_title,
);
}
return $stats;
}
/**
* Calculate time spent for each tasks for each columns
*
* @access private
* @param array $stats
* @param integer $project_id
*/
private function processTasks(array &$stats, $project_id)
{
$tasks = $this->getTasks($project_id);
foreach ($tasks as &$task) {
foreach ($this->getTaskTimeByColumns($task) as $column_id => $time_spent) {
if (isset($stats[$column_id])) {
$stats[$column_id]['count']++;
$stats[$column_id]['time_spent'] += $time_spent;
}
}
}
}
/**
* Calculate averages
*
* @access private
* @param array $stats
*/
private function calculateAverage(array &$stats)
{
foreach ($stats as &$column) {
$this->calculateColumnAverage($column);
}
}
/**
* Calculate column average
*
* @access private
* @param array $column
*/
private function calculateColumnAverage(array &$column)
{
if ($column['count'] > 0) {
$column['average'] = (int) ($column['time_spent'] / $column['count']);
}
}
/**
* Get time spent for each column for a given task
*
* @access private
* @param array $task
* @return array
*/
private function getTaskTimeByColumns(array &$task)
{
$columns = $this->transition->getTimeSpentByTask($task['id']);
if (! isset($columns[$task['column_id']])) {
$columns[$task['column_id']] = 0;
}
$columns[$task['column_id']] += $this->getTaskTimeSpentInCurrentColumn($task);
return $columns;
}
/**
* Calculate time spent of a task in the current column
*
* @access private
* @param array $task
*/
private function getTaskTimeSpentInCurrentColumn(array &$task)
{
$end = $task['date_completed'] ?: time();
return $end - $task['date_moved'];
}
/**
* Fetch the last 1000 tasks
*
* @access private
* @param integer $project_id
* @return array
*/
private function getTasks($project_id)
{
return $this->db
->table(Task::TABLE)
->columns('id', 'date_completed', 'date_moved', 'column_id')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\Task;
/**
* Estimated/Spent Time Comparison
*
* @package analytic
* @author Frederic Guillot
*/
class EstimatedTimeComparisonAnalytic extends Base
{
/**
* Build report
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function build($project_id)
{
$rows = $this->db->table(Task::TABLE)
->columns('SUM(time_estimated) AS time_estimated', 'SUM(time_spent) AS time_spent', 'is_active')
->eq('project_id', $project_id)
->groupBy('is_active')
->findAll();
$metrics = array(
'open' => array(
'time_spent' => 0,
'time_estimated' => 0,
),
'closed' => array(
'time_spent' => 0,
'time_estimated' => 0,
),
);
foreach ($rows as $row) {
$key = $row['is_active'] == Task::STATUS_OPEN ? 'open' : 'closed';
$metrics[$key]['time_spent'] = $row['time_spent'];
$metrics[$key]['time_estimated'] = $row['time_estimated'];
}
return $metrics;
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
/**
* Task Distribution
*
* @package analytic
* @author Frederic Guillot
*/
class TaskDistributionAnalytic extends Base
{
/**
* Build report
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function build($project_id)
{
$metrics = array();
$total = 0;
$columns = $this->board->getColumns($project_id);
foreach ($columns as $column) {
$nb_tasks = $this->taskFinder->countByColumnId($project_id, $column['id']);
$total += $nb_tasks;
$metrics[] = array(
'column_title' => $column['title'],
'nb_tasks' => $nb_tasks,
);
}
if ($total === 0) {
return array();
}
foreach ($metrics as &$metric) {
$metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2);
}
return $metrics;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
/**
* User Distribution
*
* @package analytic
* @author Frederic Guillot
*/
class UserDistributionAnalytic extends Base
{
/**
* Build Report
*
* @access public
* @param integer $project_id
* @return array
*/
public function build($project_id)
{
$metrics = array();
$total = 0;
$tasks = $this->taskFinder->getAll($project_id);
$users = $this->projectUserRole->getAssignableUsersList($project_id);
foreach ($tasks as $task) {
$user = isset($users[$task['owner_id']]) ? $users[$task['owner_id']] : $users[0];
$total++;
if (! isset($metrics[$user])) {
$metrics[$user] = array(
'nb_tasks' => 0,
'percentage' => 0,
'user' => $user,
);
}
$metrics[$user]['nb_tasks']++;
}
if ($total === 0) {
return array();
}
foreach ($metrics as &$metric) {
$metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2);
}
ksort($metrics);
return array_values($metrics);
}
}

View file

@ -12,17 +12,17 @@ class Action extends \Kanboard\Core\Base
{
public function getAvailableActions()
{
return $this->action->getAvailableActions();
return $this->actionManager->getAvailableActions();
}
public function getAvailableActionEvents()
{
return $this->action->getAvailableEvents();
return $this->eventManager->getAll();
}
public function getCompatibleActionEvents($action_name)
{
return $this->action->getCompatibleEvents($action_name);
return $this->actionManager->getCompatibleEvents($action_name);
}
public function removeAction($action_id)
@ -32,22 +32,10 @@ class Action extends \Kanboard\Core\Base
public function getActions($project_id)
{
$actions = $this->action->getAllByProject($project_id);
foreach ($actions as $index => $action) {
$params = array();
foreach ($action['params'] as $param) {
$params[$param['name']] = $param['value'];
}
$actions[$index]['params'] = $params;
}
return $actions;
return $this->action->getAllByProject($project_id);
}
public function createAction($project_id, $event_name, $action_name, $params)
public function createAction($project_id, $event_name, $action_name, array $params)
{
$values = array(
'project_id' => $project_id,
@ -56,23 +44,23 @@ class Action extends \Kanboard\Core\Base
'params' => $params,
);
list($valid, ) = $this->action->validateCreation($values);
list($valid, ) = $this->actionValidator->validateCreation($values);
if (! $valid) {
return false;
}
// Check if the action exists
$actions = $this->action->getAvailableActions();
$actions = $this->actionManager->getAvailableActions();
if (! isset($actions[$action_name])) {
return false;
}
// Check the event
$action = $this->action->load($action_name, $project_id, $event_name);
$action = $this->actionManager->getAction($action_name);
if (! in_array($event_name, $action->getCompatibleEvents())) {
if (! in_array($event_name, $action->getEvents())) {
return false;
}

View file

@ -34,4 +34,14 @@ class App extends \Kanboard\Core\Base
{
return $this->color->getList();
}
public function getApplicationRoles()
{
return $this->role->getApplicationRoles();
}
public function getProjectRoles()
{
return $this->role->getProjectRoles();
}
}

View file

@ -3,7 +3,6 @@
namespace Kanboard\Api;
use JsonRPC\AuthenticationFailure;
use Symfony\Component\EventDispatcher\Event;
/**
* Base class
@ -24,15 +23,58 @@ class Auth extends Base
*/
public function checkCredentials($username, $password, $class, $method)
{
$this->container['dispatcher']->dispatch('api.bootstrap', new Event);
$this->dispatcher->dispatch('app.bootstrap');
if ($username !== 'jsonrpc' && ! $this->authentication->hasCaptcha($username) && $this->authentication->authenticate($username, $password)) {
if ($this->isUserAuthenticated($username, $password)) {
$this->checkProcedurePermission(true, $method);
$this->userSession->initialize($this->user->getByUsername($username));
} elseif ($username === 'jsonrpc' && $password === $this->config->get('api_token')) {
} elseif ($this->isAppAuthenticated($username, $password)) {
$this->checkProcedurePermission(false, $method);
} else {
throw new AuthenticationFailure('Wrong credentials');
}
}
/**
* Check user credentials
*
* @access public
* @param string $username
* @param string $password
* @return boolean
*/
private function isUserAuthenticated($username, $password)
{
return $username !== 'jsonrpc' &&
! $this->userLocking->isLocked($username) &&
$this->authenticationManager->passwordAuthentication($username, $password);
}
/**
* Check administrative credentials
*
* @access public
* @param string $username
* @param string $password
* @return boolean
*/
private function isAppAuthenticated($username, $password)
{
return $username === 'jsonrpc' && $password === $this->getApiToken();
}
/**
* Get API Token
*
* @access private
* @return string
*/
private function getApiToken()
{
if (defined('API_AUTHENTICATION_TOKEN')) {
return API_AUTHENTICATION_TOKEN;
}
return $this->config->get('api_token');
}
}

View file

@ -32,7 +32,7 @@ class Category extends \Kanboard\Core\Base
'name' => $name,
);
list($valid, ) = $this->category->validateCreation($values);
list($valid, ) = $this->categoryValidator->validateCreation($values);
return $valid ? $this->category->create($values) : false;
}
@ -43,7 +43,7 @@ class Category extends \Kanboard\Core\Base
'name' => $name,
);
list($valid, ) = $this->category->validateModification($values);
list($valid, ) = $this->categoryValidator->validateModification($values);
return $valid && $this->category->update($values);
}
}

View file

@ -25,15 +25,16 @@ class Comment extends \Kanboard\Core\Base
return $this->comment->remove($comment_id);
}
public function createComment($task_id, $user_id, $content)
public function createComment($task_id, $user_id, $content, $reference = '')
{
$values = array(
'task_id' => $task_id,
'user_id' => $user_id,
'comment' => $content,
'reference' => $reference,
);
list($valid, ) = $this->comment->validateCreation($values);
list($valid, ) = $this->commentValidator->validateCreation($values);
return $valid ? $this->comment->create($values) : false;
}
@ -45,7 +46,7 @@ class Comment extends \Kanboard\Core\Base
'comment' => $content,
);
list($valid, ) = $this->comment->validateModification($values);
list($valid, ) = $this->commentValidator->validateModification($values);
return $valid && $this->comment->update($values);
}
}

View file

@ -32,14 +32,18 @@ class File extends \Kanboard\Core\Base
}
} catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
return '';
}
return '';
}
public function createFile($project_id, $task_id, $filename, $blob)
{
return $this->file->uploadContent($project_id, $task_id, $filename, $blob);
try {
return $this->file->uploadContent($project_id, $task_id, $filename, $blob);
} catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
return false;
}
}
public function removeFile($file_id)

49
sources/app/Api/Group.php Normal file
View file

@ -0,0 +1,49 @@
<?php
namespace Kanboard\Api;
/**
* Group API controller
*
* @package api
* @author Frederic Guillot
*/
class Group extends \Kanboard\Core\Base
{
public function createGroup($name, $external_id = '')
{
return $this->group->create($name, $external_id);
}
public function updateGroup($group_id, $name = null, $external_id = null)
{
$values = array(
'id' => $group_id,
'name' => $name,
'external_id' => $external_id,
);
foreach ($values as $key => $value) {
if (is_null($value)) {
unset($values[$key]);
}
}
return $this->group->update($values);
}
public function removeGroup($group_id)
{
return $this->group->remove($group_id);
}
public function getGroup($group_id)
{
return $this->group->getById($group_id);
}
public function getAllGroups()
{
return $this->group->getAll();
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Kanboard\Api;
/**
* Group Member API controller
*
* @package api
* @author Frederic Guillot
*/
class GroupMember extends \Kanboard\Core\Base
{
public function getGroupMembers($group_id)
{
return $this->groupMember->getMembers($group_id);
}
public function addGroupMember($group_id, $user_id)
{
return $this->groupMember->addUser($group_id, $user_id);
}
public function removeGroupMember($group_id, $user_id)
{
return $this->groupMember->removeUser($group_id, $user_id);
}
public function isGroupMember($group_id, $user_id)
{
return $this->groupMember->isMember($group_id, $user_id);
}
}

View file

@ -72,7 +72,7 @@ class Link extends \Kanboard\Core\Base
'opposite_label' => $opposite_label,
);
list($valid, ) = $this->link->validateCreation($values);
list($valid, ) = $this->linkValidator->validateCreation($values);
return $valid ? $this->link->create($label, $opposite_label) : false;
}
@ -93,7 +93,7 @@ class Link extends \Kanboard\Core\Base
'label' => $label,
);
list($valid, ) = $this->link->validateModification($values);
list($valid, ) = $this->linkValidator->validateModification($values);
return $valid && $this->link->update($values);
}

View file

@ -20,7 +20,7 @@ class Me extends Base
public function getMyDashboard()
{
$user_id = $this->userSession->getId();
$projects = $this->project->getQueryColumnStats($this->projectPermission->getActiveMemberProjectIds($user_id))->findAll();
$projects = $this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id))->findAll();
$tasks = $this->taskFinder->getUserQuery($user_id)->findAll();
return array(
@ -32,7 +32,7 @@ class Me extends Base
public function getMyActivityStream()
{
$project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
return $this->projectActivity->getProjects($project_ids, 100);
}
@ -44,13 +44,13 @@ class Me extends Base
'is_private' => 1,
);
list($valid, ) = $this->project->validateCreation($values);
list($valid, ) = $this->projectValidator->validateCreation($values);
return $valid ? $this->project->create($values, $this->userSession->getId(), true) : false;
}
public function getMyProjectsList()
{
return $this->projectPermission->getMemberProjects($this->userSession->getId());
return $this->projectUserRole->getProjectsByUser($this->userSession->getId());
}
public function getMyOverdueTasks()
@ -60,7 +60,7 @@ class Me extends Base
public function getMyProjects()
{
$project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
$projects = $this->project->getAllByIds($project_ids);
return $this->formatProjects($projects);

View file

@ -69,7 +69,7 @@ class Project extends Base
'description' => $description
);
list($valid, ) = $this->project->validateCreation($values);
list($valid, ) = $this->projectValidator->validateCreation($values);
return $valid ? $this->project->create($values) : false;
}
@ -81,7 +81,7 @@ class Project extends Base
'description' => $description
);
list($valid, ) = $this->project->validateModification($values);
list($valid, ) = $this->projectValidator->validateModification($values);
return $valid && $this->project->update($values);
}
}

View file

@ -2,26 +2,71 @@
namespace Kanboard\Api;
use Kanboard\Core\Security\Role;
/**
* ProjectPermission API controller
* Project Permission API controller
*
* @package api
* @author Frederic Guillot
*/
class ProjectPermission extends \Kanboard\Core\Base
{
public function getProjectUsers($project_id)
{
return $this->projectUserRole->getAllUsers($project_id);
}
public function getAssignableUsers($project_id, $prepend_unassigned = false)
{
return $this->projectUserRole->getAssignableUsersList($project_id, $prepend_unassigned);
}
public function addProjectUser($project_id, $user_id, $role = Role::PROJECT_MEMBER)
{
return $this->projectUserRole->addUser($project_id, $user_id, $role);
}
public function addProjectGroup($project_id, $group_id, $role = Role::PROJECT_MEMBER)
{
return $this->projectGroupRole->addGroup($project_id, $group_id, $role);
}
public function removeProjectUser($project_id, $user_id)
{
return $this->projectUserRole->removeUser($project_id, $user_id);
}
public function removeProjectGroup($project_id, $group_id)
{
return $this->projectGroupRole->removeGroup($project_id, $group_id);
}
public function changeProjectUserRole($project_id, $user_id, $role)
{
return $this->projectUserRole->changeUserRole($project_id, $user_id, $role);
}
public function changeProjectGroupRole($project_id, $group_id, $role)
{
return $this->projectGroupRole->changeGroupRole($project_id, $group_id, $role);
}
// Deprecated
public function getMembers($project_id)
{
return $this->projectPermission->getMembers($project_id);
return $this->getProjectUsers($project_id);
}
// Deprecated
public function revokeUser($project_id, $user_id)
{
return $this->projectPermission->revokeMember($project_id, $user_id);
return $this->removeProjectUser($project_id, $user_id);
}
// Deprecated
public function allowUser($project_id, $user_id)
{
return $this->projectPermission->addMember($project_id, $user_id);
return $this->addProjectUser($project_id, $user_id);
}
}

View file

@ -36,7 +36,7 @@ class Subtask extends \Kanboard\Core\Base
'status' => $status,
);
list($valid, ) = $this->subtask->validateCreation($values);
list($valid, ) = $this->subtaskValidator->validateCreation($values);
return $valid ? $this->subtask->create($values) : false;
}
@ -58,7 +58,7 @@ class Subtask extends \Kanboard\Core\Base
}
}
list($valid, ) = $this->subtask->validateApiModification($values);
list($valid, ) = $this->subtaskValidator->validateApiModification($values);
return $valid && $this->subtask->update($values);
}
}

View file

@ -64,6 +64,16 @@ class Task extends Base
return $this->taskPosition->movePosition($project_id, $task_id, $column_id, $position, $swimlane_id);
}
public function moveTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
{
return $this->taskDuplication->moveToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id);
}
public function duplicateTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
{
return $this->taskDuplication->duplicateToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id);
}
public function createTask($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0,
$date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0,
$recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0,
@ -71,6 +81,14 @@ class Task extends Base
{
$this->checkProjectPermission($project_id);
if ($owner_id !== 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) {
return false;
}
if ($this->userSession->isLogged()) {
$creator_id = $this->userSession->getId();
}
$values = array(
'title' => $title,
'project_id' => $project_id,
@ -96,20 +114,28 @@ class Task extends Base
return $valid ? $this->taskCreation->create($values) : false;
}
public function updateTask($id, $title = null, $project_id = null, $color_id = null, $owner_id = null,
$creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null,
public function updateTask($id, $title = null, $color_id = null, $owner_id = null,
$date_due = null, $description = null, $category_id = null, $score = null,
$recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null,
$recurrence_timeframe = null, $recurrence_basedate = null, $reference = null)
{
$this->checkTaskPermission($id);
$project_id = $this->taskFinder->getProjectId($id);
if ($project_id === 0) {
return false;
}
if ($owner_id !== null && $owner_id != 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) {
return false;
}
$values = array(
'id' => $id,
'title' => $title,
'project_id' => $project_id,
'color_id' => $color_id,
'owner_id' => $owner_id,
'creator_id' => $creator_id,
'date_due' => $date_due,
'description' => $description,
'category_id' => $category_id,

View file

@ -2,7 +2,11 @@
namespace Kanboard\Api;
use Kanboard\Auth\Ldap;
use LogicException;
use Kanboard\Core\Security\Role;
use Kanboard\Core\Ldap\Client as LdapClient;
use Kanboard\Core\Ldap\ClientException as LdapException;
use Kanboard\Core\Ldap\User as LdapUser;
/**
* User API controller
@ -27,7 +31,7 @@ class User extends \Kanboard\Core\Base
return $this->user->remove($user_id);
}
public function createUser($username, $password, $name = '', $email = '', $is_admin = 0, $is_project_admin = 0)
public function createUser($username, $password, $name = '', $email = '', $role = Role::APP_USER)
{
$values = array(
'username' => $username,
@ -35,44 +39,53 @@ class User extends \Kanboard\Core\Base
'confirmation' => $password,
'name' => $name,
'email' => $email,
'is_admin' => $is_admin,
'is_project_admin' => $is_project_admin,
'role' => $role,
);
list($valid, ) = $this->user->validateCreation($values);
list($valid, ) = $this->userValidator->validateCreation($values);
return $valid ? $this->user->create($values) : false;
}
public function createLdapUser($username = '', $email = '', $is_admin = 0, $is_project_admin = 0)
public function createLdapUser($username)
{
$ldap = new Ldap($this->container);
$user = $ldap->lookup($username, $email);
try {
if (! $user) {
$ldap = LdapClient::connect();
$user = LdapUser::getUser($ldap, sprintf(LDAP_USER_FILTER, $username));
if ($user === null) {
$this->logger->info('User not found in LDAP server');
return false;
}
if ($user->getUsername() === '') {
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
}
$values = array(
'username' => $user->getUsername(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'role' => $user->getRole(),
'is_ldap_user' => 1,
);
return $this->user->create($values);
} catch (LdapException $e) {
$this->logger->error($e->getMessage());
return false;
}
$values = array(
'username' => $user['username'],
'name' => $user['name'],
'email' => $user['email'],
'is_ldap_user' => 1,
'is_admin' => $is_admin,
'is_project_admin' => $is_project_admin,
);
return $this->user->create($values);
}
public function updateUser($id, $username = null, $name = null, $email = null, $is_admin = null, $is_project_admin = null)
public function updateUser($id, $username = null, $name = null, $email = null, $role = null)
{
$values = array(
'id' => $id,
'username' => $username,
'name' => $name,
'email' => $email,
'is_admin' => $is_admin,
'is_project_admin' => $is_project_admin,
'role' => $role,
);
foreach ($values as $key => $value) {
@ -81,7 +94,7 @@ class User extends \Kanboard\Core\Base
}
}
list($valid, ) = $this->user->validateApiModification($values);
list($valid, ) = $this->userValidator->validateApiModification($values);
return $valid && $this->user->update($values);
}
}

View file

@ -1,49 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Model\User;
use Kanboard\Event\AuthEvent;
/**
* Database authentication
*
* @package auth
* @author Frederic Guillot
*/
class Database extends Base
{
/**
* Backend name
*
* @var string
*/
const AUTH_NAME = 'Database';
/**
* Authenticate a user
*
* @access public
* @param string $username Username
* @param string $password Password
* @return boolean
*/
public function authenticate($username, $password)
{
$user = $this->db
->table(User::TABLE)
->eq('username', $username)
->eq('disable_login_form', 0)
->eq('is_ldap_user', 0)
->findOne();
if (is_array($user) && password_verify($password, $user['password'])) {
$this->userSession->initialize($user);
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
return false;
}
}

View file

@ -0,0 +1,125 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
use Kanboard\Core\Security\SessionCheckProviderInterface;
use Kanboard\Model\User;
use Kanboard\User\DatabaseUserProvider;
/**
* Database Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterface, SessionCheckProviderInterface
{
/**
* User properties
*
* @access protected
* @var array
*/
protected $userInfo = array();
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'Database';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$user = $this->db
->table(User::TABLE)
->columns('id', 'password')
->eq('username', $this->username)
->eq('disable_login_form', 0)
->eq('is_ldap_user', 0)
->findOne();
if (! empty($user) && password_verify($this->password, $user['password'])) {
$this->userInfo = $user;
return true;
}
return false;
}
/**
* Check if the user session is valid
*
* @access public
* @return boolean
*/
public function isValidSession()
{
return $this->user->exists($this->userSession->getId());
}
/**
* Get user object
*
* @access public
* @return \Kanboard\User\DatabaseUserProvider
*/
public function getUser()
{
if (empty($this->userInfo)) {
return null;
}
return new DatabaseUserProvider($this->userInfo);
}
/**
* Set username
*
* @access public
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* Set password
*
* @access public
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
}

View file

@ -1,123 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Event\AuthEvent;
/**
* Github backend
*
* @package auth
*/
class Github extends Base
{
/**
* Backend name
*
* @var string
*/
const AUTH_NAME = 'Github';
/**
* OAuth2 instance
*
* @access private
* @var \Kanboard\Core\OAuth2
*/
private $service;
/**
* Authenticate a Github user
*
* @access public
* @param string $github_id Github user id
* @return boolean
*/
public function authenticate($github_id)
{
$user = $this->user->getByGithubId($github_id);
if (! empty($user)) {
$this->userSession->initialize($user);
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
return false;
}
/**
* Unlink a Github account for a given user
*
* @access public
* @param integer $user_id User id
* @return boolean
*/
public function unlink($user_id)
{
return $this->user->update(array(
'id' => $user_id,
'github_id' => '',
));
}
/**
* Update the user table based on the Github profile information
*
* @access public
* @param integer $user_id User id
* @param array $profile Github profile
* @return boolean
*/
public function updateUser($user_id, array $profile)
{
$user = $this->user->getById($user_id);
return $this->user->update(array(
'id' => $user_id,
'github_id' => $profile['id'],
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
));
}
/**
* Get OAuth2 configured service
*
* @access public
* @return Kanboard\Core\OAuth2
*/
public function getService()
{
if (empty($this->service)) {
$this->service = $this->oauth->createService(
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
$this->helper->url->to('oauth', 'github', array(), '', true),
GITHUB_OAUTH_AUTHORIZE_URL,
GITHUB_OAUTH_TOKEN_URL,
array()
);
}
return $this->service;
}
/**
* Get Github profile
*
* @access public
* @param string $code
* @return array
*/
public function getProfile($code)
{
$this->getService()->getAccessToken($code);
return $this->httpClient->getJson(
GITHUB_API_URL.'user',
array($this->getService()->getAuthorizationHeader())
);
}
}

View file

@ -0,0 +1,143 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
use Kanboard\User\GithubUserProvider;
/**
* Github Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class GithubAuth extends Base implements OAuthAuthenticationProviderInterface
{
/**
* User properties
*
* @access protected
* @var \Kanboard\User\GithubUserProvider
*/
protected $userInfo = null;
/**
* OAuth2 instance
*
* @access protected
* @var \Kanboard\Core\Http\OAuth2
*/
protected $service;
/**
* OAuth2 code
*
* @access protected
* @var string
*/
protected $code = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'Github';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$profile = $this->getProfile();
if (! empty($profile)) {
$this->userInfo = new GithubUserProvider($profile);
return true;
}
return false;
}
/**
* Set Code
*
* @access public
* @param string $code
* @return GithubAuth
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Get user object
*
* @access public
* @return GithubUserProvider
*/
public function getUser()
{
return $this->userInfo;
}
/**
* Get configured OAuth2 service
*
* @access public
* @return \Kanboard\Core\Http\OAuth2
*/
public function getService()
{
if (empty($this->service)) {
$this->service = $this->oauth->createService(
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
$this->helper->url->to('oauth', 'github', array(), '', true),
GITHUB_OAUTH_AUTHORIZE_URL,
GITHUB_OAUTH_TOKEN_URL,
array()
);
}
return $this->service;
}
/**
* Get Github profile
*
* @access public
* @return array
*/
public function getProfile()
{
$this->getService()->getAccessToken($this->code);
return $this->httpClient->getJson(
GITHUB_API_URL.'user',
array($this->getService()->getAuthorizationHeader())
);
}
/**
* Unlink user
*
* @access public
* @param integer $userId
* @return bool
*/
public function unlink($userId)
{
return $this->user->update(array('id' => $userId, 'github_id' => ''));
}
}

View file

@ -1,123 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Event\AuthEvent;
/**
* Gitlab backend
*
* @package auth
*/
class Gitlab extends Base
{
/**
* Backend name
*
* @var string
*/
const AUTH_NAME = 'Gitlab';
/**
* OAuth2 instance
*
* @access private
* @var \Kanboard\Core\OAuth2
*/
private $service;
/**
* Authenticate a Gitlab user
*
* @access public
* @param string $gitlab_id Gitlab user id
* @return boolean
*/
public function authenticate($gitlab_id)
{
$user = $this->user->getByGitlabId($gitlab_id);
if (! empty($user)) {
$this->userSession->initialize($user);
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
return false;
}
/**
* Unlink a Gitlab account for a given user
*
* @access public
* @param integer $user_id User id
* @return boolean
*/
public function unlink($user_id)
{
return $this->user->update(array(
'id' => $user_id,
'gitlab_id' => '',
));
}
/**
* Update the user table based on the Gitlab profile information
*
* @access public
* @param integer $user_id User id
* @param array $profile Gitlab profile
* @return boolean
*/
public function updateUser($user_id, array $profile)
{
$user = $this->user->getById($user_id);
return $this->user->update(array(
'id' => $user_id,
'gitlab_id' => $profile['id'],
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
));
}
/**
* Get OAuth2 configured service
*
* @access public
* @return Kanboard\Core\OAuth2
*/
public function getService()
{
if (empty($this->service)) {
$this->service = $this->oauth->createService(
GITLAB_CLIENT_ID,
GITLAB_CLIENT_SECRET,
$this->helper->url->to('oauth', 'gitlab', array(), '', true),
GITLAB_OAUTH_AUTHORIZE_URL,
GITLAB_OAUTH_TOKEN_URL,
array()
);
}
return $this->service;
}
/**
* Get Gitlab profile
*
* @access public
* @param string $code
* @return array
*/
public function getProfile($code)
{
$this->getService()->getAccessToken($code);
return $this->httpClient->getJson(
GITLAB_API_URL.'user',
array($this->getService()->getAuthorizationHeader())
);
}
}

View file

@ -0,0 +1,143 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
use Kanboard\User\GitlabUserProvider;
/**
* Gitlab Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class GitlabAuth extends Base implements OAuthAuthenticationProviderInterface
{
/**
* User properties
*
* @access private
* @var \Kanboard\User\GitlabUserProvider
*/
private $userInfo = null;
/**
* OAuth2 instance
*
* @access protected
* @var \Kanboard\Core\Http\OAuth2
*/
protected $service;
/**
* OAuth2 code
*
* @access protected
* @var string
*/
protected $code = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'Gitlab';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$profile = $this->getProfile();
if (! empty($profile)) {
$this->userInfo = new GitlabUserProvider($profile);
return true;
}
return false;
}
/**
* Set Code
*
* @access public
* @param string $code
* @return GitlabAuth
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Get user object
*
* @access public
* @return GitlabUserProvider
*/
public function getUser()
{
return $this->userInfo;
}
/**
* Get configured OAuth2 service
*
* @access public
* @return \Kanboard\Core\Http\OAuth2
*/
public function getService()
{
if (empty($this->service)) {
$this->service = $this->oauth->createService(
GITLAB_CLIENT_ID,
GITLAB_CLIENT_SECRET,
$this->helper->url->to('oauth', 'gitlab', array(), '', true),
GITLAB_OAUTH_AUTHORIZE_URL,
GITLAB_OAUTH_TOKEN_URL,
array()
);
}
return $this->service;
}
/**
* Get Gitlab profile
*
* @access public
* @return array
*/
public function getProfile()
{
$this->getService()->getAccessToken($this->code);
return $this->httpClient->getJson(
GITLAB_API_URL.'user',
array($this->getService()->getAuthorizationHeader())
);
}
/**
* Unlink user
*
* @access public
* @param integer $userId
* @return bool
*/
public function unlink($userId)
{
return $this->user->update(array('id' => $userId, 'gitlab_id' => ''));
}
}

View file

@ -1,124 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Event\AuthEvent;
/**
* Google backend
*
* @package auth
* @author Frederic Guillot
*/
class Google extends Base
{
/**
* Backend name
*
* @var string
*/
const AUTH_NAME = 'Google';
/**
* OAuth2 instance
*
* @access private
* @var \Kanboard\Core\OAuth2
*/
private $service;
/**
* Authenticate a Google user
*
* @access public
* @param string $google_id Google unique id
* @return boolean
*/
public function authenticate($google_id)
{
$user = $this->user->getByGoogleId($google_id);
if (! empty($user)) {
$this->userSession->initialize($user);
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
return false;
}
/**
* Unlink a Google account for a given user
*
* @access public
* @param integer $user_id User id
* @return boolean
*/
public function unlink($user_id)
{
return $this->user->update(array(
'id' => $user_id,
'google_id' => '',
));
}
/**
* Update the user table based on the Google profile information
*
* @access public
* @param integer $user_id User id
* @param array $profile Google profile
* @return boolean
*/
public function updateUser($user_id, array $profile)
{
$user = $this->user->getById($user_id);
return $this->user->update(array(
'id' => $user_id,
'google_id' => $profile['id'],
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
));
}
/**
* Get OAuth2 configured service
*
* @access public
* @return KanboardCore\OAuth2
*/
public function getService()
{
if (empty($this->service)) {
$this->service = $this->oauth->createService(
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
$this->helper->url->to('oauth', 'google', array(), '', true),
'https://accounts.google.com/o/oauth2/auth',
'https://accounts.google.com/o/oauth2/token',
array('https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile')
);
}
return $this->service;
}
/**
* Get Google profile
*
* @access public
* @param string $code
* @return array
*/
public function getProfile($code)
{
$this->getService()->getAccessToken($code);
return $this->httpClient->getJson(
'https://www.googleapis.com/oauth2/v1/userinfo',
array($this->getService()->getAuthorizationHeader())
);
}
}

View file

@ -0,0 +1,143 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
use Kanboard\User\GoogleUserProvider;
/**
* Google Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class GoogleAuth extends Base implements OAuthAuthenticationProviderInterface
{
/**
* User properties
*
* @access protected
* @var \Kanboard\User\GoogleUserProvider
*/
protected $userInfo = null;
/**
* OAuth2 instance
*
* @access protected
* @var \Kanboard\Core\Http\OAuth2
*/
protected $service;
/**
* OAuth2 code
*
* @access protected
* @var string
*/
protected $code = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'Google';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$profile = $this->getProfile();
if (! empty($profile)) {
$this->userInfo = new GoogleUserProvider($profile);
return true;
}
return false;
}
/**
* Set Code
*
* @access public
* @param string $code
* @return GoogleAuth
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Get user object
*
* @access public
* @return GoogleUserProvider
*/
public function getUser()
{
return $this->userInfo;
}
/**
* Get configured OAuth2 service
*
* @access public
* @return \Kanboard\Core\Http\OAuth2
*/
public function getService()
{
if (empty($this->service)) {
$this->service = $this->oauth->createService(
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
$this->helper->url->to('oauth', 'google', array(), '', true),
'https://accounts.google.com/o/oauth2/auth',
'https://accounts.google.com/o/oauth2/token',
array('https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile')
);
}
return $this->service;
}
/**
* Get Google profile
*
* @access public
* @return array
*/
public function getProfile()
{
$this->getService()->getAccessToken($this->code);
return $this->httpClient->getJson(
'https://www.googleapis.com/oauth2/v1/userinfo',
array($this->getService()->getAuthorizationHeader())
);
}
/**
* Unlink user
*
* @access public
* @param integer $userId
* @return bool
*/
public function unlink($userId)
{
return $this->user->update(array('id' => $userId, 'google_id' => ''));
}
}

View file

@ -1,521 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Event\AuthEvent;
/**
* LDAP model
*
* @package auth
* @author Frederic Guillot
*/
class Ldap extends Base
{
/**
* Backend name
*
* @var string
*/
const AUTH_NAME = 'LDAP';
/**
* Get LDAP server name
*
* @access public
* @return string
*/
public function getLdapServer()
{
return LDAP_SERVER;
}
/**
* Get LDAP bind type
*
* @access public
* @return integer
*/
public function getLdapBindType()
{
return LDAP_BIND_TYPE;
}
/**
* Get LDAP server port
*
* @access public
* @return integer
*/
public function getLdapPort()
{
return LDAP_PORT;
}
/**
* Get LDAP username (proxy auth)
*
* @access public
* @return string
*/
public function getLdapUsername()
{
return LDAP_USERNAME;
}
/**
* Get LDAP password (proxy auth)
*
* @access public
* @return string
*/
public function getLdapPassword()
{
return LDAP_PASSWORD;
}
/**
* Get LDAP Base DN
*
* @access public
* @return string
*/
public function getLdapBaseDn()
{
return LDAP_ACCOUNT_BASE;
}
/**
* Get LDAP account id attribute
*
* @access public
* @return string
*/
public function getLdapAccountId()
{
return LDAP_ACCOUNT_ID;
}
/**
* Get LDAP account email attribute
*
* @access public
* @return string
*/
public function getLdapAccountEmail()
{
return LDAP_ACCOUNT_EMAIL;
}
/**
* Get LDAP account name attribute
*
* @access public
* @return string
*/
public function getLdapAccountName()
{
return LDAP_ACCOUNT_FULLNAME;
}
/**
* Get LDAP account memberof attribute
*
* @access public
* @return string
*/
public function getLdapAccountMemberOf()
{
return LDAP_ACCOUNT_MEMBEROF;
}
/**
* Get LDAP admin group DN
*
* @access public
* @return string
*/
public function getLdapGroupAdmin()
{
return LDAP_GROUP_ADMIN_DN;
}
/**
* Get LDAP project admin group DN
*
* @access public
* @return string
*/
public function getLdapGroupProjectAdmin()
{
return LDAP_GROUP_PROJECT_ADMIN_DN;
}
/**
* Get LDAP username pattern
*
* @access public
* @param string $username
* @return string
*/
public function getLdapUserPattern($username)
{
return sprintf(LDAP_USER_PATTERN, $username);
}
/**
* Return true if the LDAP username is case sensitive
*
* @access public
* @return boolean
*/
public function isLdapAccountCaseSensitive()
{
return LDAP_USERNAME_CASE_SENSITIVE;
}
/**
* Return true if the automatic account creation is enabled
*
* @access public
* @return boolean
*/
public function isLdapAccountCreationEnabled()
{
return LDAP_ACCOUNT_CREATION;
}
/**
* Ge the list of attributes to fetch when reading the LDAP user entry
*
* Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
*
* @access public
* @return array
*/
public function getProfileAttributes()
{
return array_values(array_filter(array(
$this->getLdapAccountId(),
$this->getLdapAccountName(),
$this->getLdapAccountEmail(),
$this->getLdapAccountMemberOf()
)));
}
/**
* Authenticate the user
*
* @access public
* @param string $username Username
* @param string $password Password
* @return boolean
*/
public function authenticate($username, $password)
{
$username = $this->isLdapAccountCaseSensitive() ? $username : strtolower($username);
$result = $this->findUser($username, $password);
if (is_array($result)) {
$user = $this->user->getByUsername($username);
if (! empty($user)) {
// There is already a local user with that name
if ($user['is_ldap_user'] == 0) {
return false;
}
} else {
// We create automatically a new user
if ($this->isLdapAccountCreationEnabled() && $this->user->create($result) !== false) {
$user = $this->user->getByUsername($username);
} else {
return false;
}
}
// We open the session
$this->userSession->initialize($user);
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
return false;
}
/**
* Find the user from the LDAP server
*
* @access public
* @param string $username Username
* @param string $password Password
* @return boolean|array
*/
public function findUser($username, $password)
{
$ldap = $this->connect();
if ($ldap !== false && $this->bind($ldap, $username, $password)) {
return $this->getProfile($ldap, $username, $password);
}
return false;
}
/**
* LDAP connection
*
* @access public
* @return resource|boolean
*/
public function connect()
{
if (! function_exists('ldap_connect')) {
$this->logger->error('LDAP: The PHP LDAP extension is required');
return false;
}
// Skip SSL certificate verification
if (! LDAP_SSL_VERIFY) {
putenv('LDAPTLS_REQCERT=never');
}
$ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort());
if ($ldap === false) {
$this->logger->error('LDAP: Unable to connect to the LDAP server');
return false;
}
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 1);
ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 1);
if (LDAP_START_TLS && ! @ldap_start_tls($ldap)) {
$this->logger->error('LDAP: Unable to use ldap_start_tls()');
return false;
}
return $ldap;
}
/**
* LDAP authentication
*
* @access public
* @param resource $ldap
* @param string $username
* @param string $password
* @return boolean
*/
public function bind($ldap, $username, $password)
{
if ($this->getLdapBindType() === 'user') {
$ldap_username = sprintf($this->getLdapUsername(), $username);
$ldap_password = $password;
} elseif ($this->getLdapBindType() === 'proxy') {
$ldap_username = $this->getLdapUsername();
$ldap_password = $this->getLdapPassword();
} else {
$ldap_username = null;
$ldap_password = null;
}
if (! @ldap_bind($ldap, $ldap_username, $ldap_password)) {
$this->logger->error('LDAP: Unable to bind to server with: '.$ldap_username);
$this->logger->error('LDAP: bind type='.$this->getLdapBindType());
return false;
}
return true;
}
/**
* Get LDAP user profile
*
* @access public
* @param resource $ldap
* @param string $username
* @param string $password
* @return boolean|array
*/
public function getProfile($ldap, $username, $password)
{
$user_pattern = $this->getLdapUserPattern($username);
$entries = $this->executeQuery($ldap, $user_pattern);
if ($entries === false) {
$this->logger->error('LDAP: Unable to get user profile: '.$user_pattern);
return false;
}
if (@ldap_bind($ldap, $entries[0]['dn'], $password)) {
return $this->prepareProfile($ldap, $entries, $username);
}
if (DEBUG) {
$this->logger->debug('LDAP: wrong password for '.$entries[0]['dn']);
}
return false;
}
/**
* Build user profile from LDAP information
*
* @access public
* @param resource $ldap
* @param array $entries
* @param string $username
* @return boolean|array
*/
public function prepareProfile($ldap, array $entries, $username)
{
if ($this->getLdapAccountId() !== '') {
$username = $this->getEntry($entries, $this->getLdapAccountId(), $username);
}
return array(
'username' => $username,
'name' => $this->getEntry($entries, $this->getLdapAccountName()),
'email' => $this->getEntry($entries, $this->getLdapAccountEmail()),
'is_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupAdmin()),
'is_project_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupProjectAdmin()),
'is_ldap_user' => 1,
);
}
/**
* Check group membership
*
* @access public
* @param array $group_entries
* @param string $group_dn
* @return boolean
*/
public function isMemberOf(array $group_entries, $group_dn)
{
if (! isset($group_entries['count']) || empty($group_dn)) {
return false;
}
for ($i = 0; $i < $group_entries['count']; $i++) {
if ($group_entries[$i] === $group_dn) {
return true;
}
}
return false;
}
/**
* Retrieve info on LDAP user by username or email
*
* @access public
* @param string $username
* @param string $email
* @return boolean|array
*/
public function lookup($username = null, $email = null)
{
$query = $this->getLookupQuery($username, $email);
if ($query === '') {
return false;
}
// Connect and attempt anonymous or proxy binding
$ldap = $this->connect();
if ($ldap === false || ! $this->bind($ldap, null, null)) {
return false;
}
// Try to find user
$entries = $this->executeQuery($ldap, $query);
if ($entries === false) {
return false;
}
// User id not retrieved: LDAP_ACCOUNT_ID not properly configured
if (empty($username) && ! isset($entries[0][$this->getLdapAccountId()][0])) {
return false;
}
return $this->prepareProfile($ldap, $entries, $username);
}
/**
* Execute LDAP query
*
* @access private
* @param resource $ldap
* @param string $query
* @return boolean|array
*/
private function executeQuery($ldap, $query)
{
$sr = @ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes());
if ($sr === false) {
return false;
}
$entries = ldap_get_entries($ldap, $sr);
if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
return false;
}
return $entries;
}
/**
* Get the LDAP query to find a user
*
* @access private
* @param string $username
* @param string $email
* @return string
*/
private function getLookupQuery($username, $email)
{
if (! empty($username) && ! empty($email)) {
return '(&('.$this->getLdapUserPattern($username).')('.$this->getLdapAccountEmail().'='.$email.'))';
} elseif (! empty($username)) {
return $this->getLdapUserPattern($username);
} elseif (! empty($email)) {
return '('.$this->getLdapAccountEmail().'='.$email.')';
}
return '';
}
/**
* Return one entry from a list of entries
*
* @access private
* @param array $entries LDAP entries
* @param string $key Key
* @param string $default Default value if key not set in entry
* @return string
*/
private function getEntry(array $entries, $key, $default = '')
{
return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default;
}
/**
* Return subset of entries
*
* @access private
* @param array $entries
* @param string $key
* @param array $default
* @return array
*/
private function getEntries(array $entries, $key, $default = array())
{
return isset($entries[0][$key]) ? $entries[0][$key] : $default;
}
}

View file

@ -0,0 +1,172 @@
<?php
namespace Kanboard\Auth;
use LogicException;
use Kanboard\Core\Base;
use Kanboard\Core\Ldap\Client as LdapClient;
use Kanboard\Core\Ldap\ClientException as LdapException;
use Kanboard\Core\Ldap\User as LdapUser;
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
/**
* LDAP Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
{
/**
* User properties
*
* @access protected
* @var \Kanboard\User\LdapUserProvider
*/
protected $userInfo = null;
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'LDAP';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
try {
$client = LdapClient::connect($this->getLdapUsername(), $this->getLdapPassword());
$user = LdapUser::getUser($client, $this->username);
if ($user === null) {
$this->logger->info('User not found in LDAP server');
return false;
}
if ($user->getUsername() === '') {
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
}
if ($client->authenticate($user->getDn(), $this->password)) {
$this->userInfo = $user;
return true;
}
} catch (LdapException $e) {
$this->logger->error($e->getMessage());
}
return false;
}
/**
* Get user object
*
* @access public
* @return \Kanboard\User\LdapUserProvider
*/
public function getUser()
{
return $this->userInfo;
}
/**
* Set username
*
* @access public
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* Set password
*
* @access public
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* Get LDAP username (proxy auth)
*
* @access public
* @return string
*/
public function getLdapUsername()
{
switch ($this->getLdapBindType()) {
case 'proxy':
return LDAP_USERNAME;
case 'user':
return sprintf(LDAP_USERNAME, $this->username);
default:
return null;
}
}
/**
* Get LDAP password (proxy auth)
*
* @access public
* @return string
*/
public function getLdapPassword()
{
switch ($this->getLdapBindType()) {
case 'proxy':
return LDAP_PASSWORD;
case 'user':
return $this->password;
default:
return null;
}
}
/**
* Get LDAP bind type
*
* @access public
* @return integer
*/
public function getLdapBindType()
{
if (LDAP_BIND_TYPE !== 'user' && LDAP_BIND_TYPE !== 'proxy' && LDAP_BIND_TYPE !== 'anonymous') {
throw new LogicException('Wrong value for the parameter LDAP_BIND_TYPE');
}
return LDAP_BIND_TYPE;
}
}

View file

@ -1,323 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Http\Request;
use Kanboard\Event\AuthEvent;
use Kanboard\Core\Security\Token;
/**
* RememberMe model
*
* @package auth
* @author Frederic Guillot
*/
class RememberMe extends Base
{
/**
* Backend name
*
* @var string
*/
const AUTH_NAME = 'RememberMe';
/**
* SQL table name
*
* @var string
*/
const TABLE = 'remember_me';
/**
* Cookie name
*
* @var string
*/
const COOKIE_NAME = '__R';
/**
* 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();
}
/**
* Authenticate the user with the cookie
*
* @access public
* @return bool
*/
public function authenticate()
{
$credentials = $this->readCookie();
if ($credentials !== false) {
$record = $this->find($credentials['token'], $credentials['sequence']);
if ($record) {
// Update the sequence
$this->writeCookie(
$record['token'],
$this->update($record['token']),
$record['expiration']
);
// Create the session
$this->userSession->initialize($this->user->getById($record['user_id']));
// Do not ask 2FA for remember me session
$this->sessionStorage->postAuth['validated'] = true;
$this->container['dispatcher']->dispatch(
'auth.success',
new AuthEvent(self::AUTH_NAME, $this->userSession->getId())
);
return true;
}
}
return false;
}
/**
* 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 the current RememberMe session and the cookie
*
* @access public
* @param integer $user_id User id
*/
public function destroy($user_id)
{
$credentials = $this->readCookie();
if ($credentials !== false) {
$this->deleteCookie();
$this->db
->table(self::TABLE)
->eq('user_id', $user_id)
->eq('token', $credentials['token'])
->remove();
}
}
/**
* 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 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 update($token)
{
$new_sequence = Token::getToken();
$this->db
->table(self::TABLE)
->eq('token', $token)
->update(array('sequence' => $new_sequence));
return $new_sequence;
}
/**
* Encode the cookie
*
* @access public
* @param string $token Session token
* @param string $sequence Sequence token
* @return string
*/
public function encodeCookie($token, $sequence)
{
return implode('|', array($token, $sequence));
}
/**
* Decode the value of a cookie
*
* @access public
* @param string $value Raw cookie data
* @return array
*/
public function decodeCookie($value)
{
list($token, $sequence) = explode('|', $value);
return array(
'token' => $token,
'sequence' => $sequence,
);
}
/**
* Return true if the current user has a RememberMe cookie
*
* @access public
* @return bool
*/
public function hasCookie()
{
return ! empty($_COOKIE[self::COOKIE_NAME]);
}
/**
* Write and encode the cookie
*
* @access public
* @param string $token Session token
* @param string $sequence Sequence token
* @param string $expiration Cookie expiration
*/
public function writeCookie($token, $sequence, $expiration)
{
setcookie(
self::COOKIE_NAME,
$this->encodeCookie($token, $sequence),
$expiration,
$this->helper->url->dir(),
null,
Request::isHTTPS(),
true
);
}
/**
* Read and decode the cookie
*
* @access public
* @return mixed
*/
public function readCookie()
{
if (empty($_COOKIE[self::COOKIE_NAME])) {
return false;
}
return $this->decodeCookie($_COOKIE[self::COOKIE_NAME]);
}
/**
* Remove the cookie
*
* @access public
*/
public function deleteCookie()
{
setcookie(
self::COOKIE_NAME,
'',
time() - 3600,
$this->helper->url->dir(),
null,
Request::isHTTPS(),
true
);
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
use Kanboard\User\DatabaseUserProvider;
/**
* Rember Me Cookie Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class RememberMeAuth extends Base implements PreAuthenticationProviderInterface
{
/**
* User properties
*
* @access protected
* @var array
*/
protected $userInfo = array();
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'RememberMe';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$credentials = $this->rememberMeCookie->read();
if ($credentials !== false) {
$session = $this->rememberMeSession->find($credentials['token'], $credentials['sequence']);
if (! empty($session)) {
$this->rememberMeCookie->write(
$session['token'],
$this->rememberMeSession->updateSequence($session['token']),
$session['expiration']
);
$this->userInfo = $this->user->getById($session['user_id']);
return true;
}
}
return false;
}
/**
* Get user object
*
* @access public
* @return DatabaseUserProvider
*/
public function getUser()
{
if (empty($this->userInfo)) {
return null;
}
return new DatabaseUserProvider($this->userInfo);
}
}

View file

@ -1,83 +0,0 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Event\AuthEvent;
/**
* ReverseProxy backend
*
* @package auth
* @author Sylvain Veyrié
*/
class ReverseProxy extends Base
{
/**
* Backend name
*
* @var string
*/
const AUTH_NAME = 'ReverseProxy';
/**
* Get username from the reverse proxy
*
* @access public
* @return string
*/
public function getUsername()
{
return isset($_SERVER[REVERSE_PROXY_USER_HEADER]) ? $_SERVER[REVERSE_PROXY_USER_HEADER] : '';
}
/**
* Authenticate the user with the HTTP header
*
* @access public
* @return bool
*/
public function authenticate()
{
if (isset($_SERVER[REVERSE_PROXY_USER_HEADER])) {
$login = $_SERVER[REVERSE_PROXY_USER_HEADER];
$user = $this->user->getByUsername($login);
if (empty($user)) {
$this->createUser($login);
$user = $this->user->getByUsername($login);
}
$this->userSession->initialize($user);
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
return false;
}
/**
* Create automatically a new local user after the authentication
*
* @access private
* @param string $login Username
* @return bool
*/
private function createUser($login)
{
$email = strpos($login, '@') !== false ? $login : '';
if (REVERSE_PROXY_DEFAULT_DOMAIN !== '' && empty($email)) {
$email = $login.'@'.REVERSE_PROXY_DEFAULT_DOMAIN;
}
return $this->user->create(array(
'email' => $email,
'username' => $login,
'is_admin' => REVERSE_PROXY_DEFAULT_ADMIN === $login,
'is_ldap_user' => 1,
'disable_login_form' => 1,
));
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
use Kanboard\Core\Security\SessionCheckProviderInterface;
use Kanboard\User\ReverseProxyUserProvider;
/**
* Reverse-Proxy Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface
{
/**
* User properties
*
* @access protected
* @var \Kanboard\User\ReverseProxyUserProvider
*/
protected $userInfo = null;
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return 'ReverseProxy';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$username = $this->request->getRemoteUser();
if (! empty($username)) {
$this->userInfo = new ReverseProxyUserProvider($username);
return true;
}
return false;
}
/**
* Check if the user session is valid
*
* @access public
* @return boolean
*/
public function isValidSession()
{
return $this->request->getRemoteUser() === $this->userSession->getUsername();
}
/**
* Get user object
*
* @access public
* @return ReverseProxyUserProvider
*/
public function getUser()
{
return $this->userInfo;
}
}

View file

@ -0,0 +1,144 @@
<?php
namespace Kanboard\Auth;
use Otp\Otp;
use Otp\GoogleAuthenticator;
use Base32\Base32;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PostAuthenticationProviderInterface;
/**
* TOTP Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class TotpAuth extends Base implements PostAuthenticationProviderInterface
{
/**
* User pin code
*
* @access protected
* @var string
*/
protected $code = '';
/**
* Private key
*
* @access protected
* @var string
*/
protected $secret = '';
/**
* Get authentication provider name
*
* @access public
* @return string
*/
public function getName()
{
return t('Time-based One-time Password Algorithm');
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
$otp = new Otp;
return $otp->checkTotp(Base32::decode($this->secret), $this->code);
}
/**
* Called before to prompt the user
*
* @access public
*/
public function beforeCode()
{
}
/**
* Set validation code
*
* @access public
* @param string $code
*/
public function setCode($code)
{
$this->code = $code;
}
/**
* Generate secret
*
* @access public
* @return string
*/
public function generateSecret()
{
$this->secret = GoogleAuthenticator::generateRandom();
return $this->secret;
}
/**
* Set secret token
*
* @access public
* @param string $secret
*/
public function setSecret($secret)
{
$this->secret = $secret;
}
/**
* Get secret token
*
* @access public
* @return string
*/
public function getSecret()
{
return $this->secret;
}
/**
* Get QR code url
*
* @access public
* @param string $label
* @return string
*/
public function getQrCodeUrl($label)
{
if (empty($this->secret)) {
return '';
}
return GoogleAuthenticator::getQrCodeUrl('totp', $label, $this->secret);
}
/**
* Get key url (empty if no url can be provided)
*
* @access public
* @param string $label
* @return string
*/
public function getKeyUrl($label)
{
if (empty($this->secret)) {
return '';
}
return GoogleAuthenticator::getKeyUri('totp', $label, $this->secret);
}
}

View file

@ -18,17 +18,18 @@ class Action extends Base
public function index()
{
$project = $this->getProject();
$actions = $this->action->getAllByProject($project['id']);
$this->response->html($this->projectLayout('action/index', array(
'values' => array('project_id' => $project['id']),
'project' => $project,
'actions' => $this->action->getAllByProject($project['id']),
'available_actions' => $this->action->getAvailableActions(),
'available_events' => $this->action->getAvailableEvents(),
'available_params' => $this->action->getAllActionParameters(),
'actions' => $actions,
'available_actions' => $this->actionManager->getAvailableActions(),
'available_events' => $this->eventManager->getAll(),
'available_params' => $this->actionManager->getAvailableParameters($actions),
'columns_list' => $this->board->getColumnsList($project['id']),
'users_list' => $this->projectPermission->getMemberList($project['id']),
'projects_list' => $this->project->getList(false),
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
'projects_list' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'links_list' => $this->link->getList(0, false),
@ -53,7 +54,7 @@ class Action extends Base
$this->response->html($this->projectLayout('action/event', array(
'values' => $values,
'project' => $project,
'events' => $this->action->getCompatibleEvents($values['action_name']),
'events' => $this->actionManager->getCompatibleEvents($values['action_name']),
'title' => t('Automatic actions')
)));
}
@ -72,21 +73,21 @@ class Action extends Base
$this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
$action = $this->action->load($values['action_name'], $values['project_id'], $values['event_name']);
$action = $this->actionManager->getAction($values['action_name']);
$action_params = $action->getActionRequiredParameters();
if (empty($action_params)) {
$this->doCreation($project, $values + array('params' => array()));
}
$projects_list = $this->project->getList(false);
$projects_list = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
unset($projects_list[$project['id']]);
$this->response->html($this->projectLayout('action/params', array(
'values' => $values,
'action_params' => $action_params,
'columns_list' => $this->board->getColumnsList($project['id']),
'users_list' => $this->projectPermission->getMemberList($project['id']),
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
'projects_list' => $projects_list,
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
@ -115,7 +116,7 @@ class Action extends Base
*/
private function doCreation(array $project, array $values)
{
list($valid, ) = $this->action->validateCreation($values);
list($valid, ) = $this->actionValidator->validateCreation($values);
if ($valid) {
if ($this->action->create($values) !== false) {
@ -139,8 +140,8 @@ class Action extends Base
$this->response->html($this->projectLayout('action/remove', array(
'action' => $this->action->getById($this->request->getIntegerParam('action_id')),
'available_events' => $this->action->getAvailableEvents(),
'available_actions' => $this->action->getAvailableActions(),
'available_events' => $this->eventManager->getAll(),
'available_actions' => $this->actionManager->getAvailableActions(),
'project' => $project,
'title' => t('Remove an action')
)));

View file

@ -20,7 +20,7 @@ class Activity extends Base
$project = $this->getProject();
$this->response->html($this->template->layout('activity/project', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'events' => $this->projectActivity->getProject($project['id']),
'project' => $project,
'title' => t('%s\'s activity', $project['name'])

View file

@ -1,6 +1,7 @@
<?php
namespace Kanboard\Controller;
use Kanboard\Model\Task as TaskModel;
/**
* Project Analytic controller
@ -20,7 +21,7 @@ class Analytic extends Base
*/
private function layout($template, array $params)
{
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
$params['content_for_sublayout'] = $this->template->render($template, $params);
return $this->template->layout('analytic/layout', $params);
@ -34,17 +35,7 @@ class Analytic extends Base
public function leadAndCycleTime()
{
$project = $this->getProject();
$values = $this->request->getValues();
$this->projectDailyStats->updateTotals($project['id'], date('Y-m-d'));
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
$to = $this->request->getStringParam('to', date('Y-m-d'));
if (! empty($values)) {
$from = $values['from'];
$to = $values['to'];
}
list($from, $to) = $this->getDates();
$this->response->html($this->layout('analytic/lead_cycle_time', array(
'values' => array(
@ -52,7 +43,7 @@ class Analytic extends Base
'to' => $to,
),
'project' => $project,
'average' => $this->projectAnalytic->getAverageLeadAndCycleTime($project['id']),
'average' => $this->averageLeadCycleTimeAnalytic->build($project['id']),
'metrics' => $this->projectDailyStats->getRawMetrics($project['id'], $from, $to),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
@ -60,6 +51,32 @@ class Analytic extends Base
)));
}
/**
* Show comparison between actual and estimated hours chart
*
* @access public
*/
public function compareHours()
{
$project = $this->getProject();
$params = $this->getProjectFilters('analytic', 'compareHours');
$query = $this->taskFilter->create()->filterByProject($params['project']['id'])->getQuery();
$paginator = $this->paginator
->setUrl('analytic', 'compareHours', array('project_id' => $project['id']))
->setMax(30)
->setOrder(TaskModel::TABLE.'.id')
->setQuery($query)
->calculate();
$this->response->html($this->layout('analytic/compare_hours', array(
'project' => $project,
'paginator' => $paginator,
'metrics' => $this->estimatedTimeComparisonAnalytic->build($project['id']),
'title' => t('Compare hours for "%s"', $project['name']),
)));
}
/**
* Show average time spent by column
*
@ -71,7 +88,7 @@ class Analytic extends Base
$this->response->html($this->layout('analytic/avg_time_columns', array(
'project' => $project,
'metrics' => $this->projectAnalytic->getAverageTimeSpentByColumn($project['id']),
'metrics' => $this->averageTimeSpentColumnAnalytic->build($project['id']),
'title' => t('Average time spent into each column for "%s"', $project['name']),
)));
}
@ -87,7 +104,7 @@ class Analytic extends Base
$this->response->html($this->layout('analytic/tasks', array(
'project' => $project,
'metrics' => $this->projectAnalytic->getTaskRepartition($project['id']),
'metrics' => $this->taskDistributionAnalytic->build($project['id']),
'title' => t('Task repartition for "%s"', $project['name']),
)));
}
@ -103,7 +120,7 @@ class Analytic extends Base
$this->response->html($this->layout('analytic/users', array(
'project' => $project,
'metrics' => $this->projectAnalytic->getUserRepartition($project['id']),
'metrics' => $this->userDistributionAnalytic->build($project['id']),
'title' => t('User repartition for "%s"', $project['name']),
)));
}
@ -132,21 +149,14 @@ class Analytic extends Base
* Common method for CFD and Burdown chart
*
* @access private
* @param string $template
* @param string $column
* @param string $title
*/
private function commonAggregateMetrics($template, $column, $title)
{
$project = $this->getProject();
$values = $this->request->getValues();
$this->projectDailyColumnStats->updateTotals($project['id'], date('Y-m-d'));
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
$to = $this->request->getStringParam('to', date('Y-m-d'));
if (! empty($values)) {
$from = $values['from'];
$to = $values['to'];
}
list($from, $to) = $this->getDates();
$display_graph = $this->projectDailyColumnStats->countDays($project['id'], $from, $to) >= 2;
@ -163,4 +173,19 @@ class Analytic extends Base
'title' => t($title, $project['name']),
)));
}
private function getDates()
{
$values = $this->request->getValues();
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
$to = $this->request->getStringParam('to', date('Y-m-d'));
if (! empty($values)) {
$from = $values['from'];
$to = $values['to'];
}
return array($from, $to);
}
}

View file

@ -22,7 +22,7 @@ class App extends Base
*/
private function layout($template, array $params)
{
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
$params['content_for_sublayout'] = $this->template->render($template, $params);
return $this->template->layout('app/layout', $params);
@ -42,7 +42,7 @@ class App extends Base
->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id))
->setMax($max)
->setOrder('name')
->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveMemberProjectIds($user_id)))
->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id)))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects');
}
@ -169,7 +169,7 @@ class App extends Base
$this->response->html($this->layout('app/activity', array(
'title' => t('My activity stream'),
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveMemberProjectIds($user['id']), 100),
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id']), 100),
'user' => $user,
)));
}
@ -202,49 +202,4 @@ class App extends Base
'user' => $user,
)));
}
/**
* Render Markdown text and reply with the HTML Code
*
* @access public
*/
public function preview()
{
$payload = $this->request->getJson();
if (empty($payload['text'])) {
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
}
$this->response->html($this->helper->text->markdown($payload['text']));
}
/**
* Task autocompletion (Ajax)
*
* @access public
*/
public function autocomplete()
{
$search = $this->request->getStringParam('term');
$projects = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
if (empty($projects)) {
$this->response->json(array());
}
$filter = $this->taskFilterAutoCompleteFormatter
->create()
->filterByProjects($projects)
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
// Search by task id or by title
if (ctype_digit($search)) {
$filter->filterById($search);
} else {
$filter->filterByTitle($search);
}
$this->response->json($filter->format());
}
}

View file

@ -2,8 +2,6 @@
namespace Kanboard\Controller;
use Gregwar\Captcha\CaptchaBuilder;
/**
* Authentication controller
*
@ -24,7 +22,7 @@ class Auth extends Base
}
$this->response->html($this->template->layout('auth/index', array(
'captcha' => isset($values['username']) && $this->authentication->hasCaptcha($values['username']),
'captcha' => ! empty($values['username']) && $this->userLocking->hasCaptcha($values['username']),
'errors' => $errors,
'values' => $values,
'no_layout' => true,
@ -40,18 +38,11 @@ class Auth extends Base
public function check()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->authentication->validateForm($values);
$this->sessionStorage->hasRememberMe = ! empty($values['remember_me']);
list($valid, $errors) = $this->authValidator->validateForm($values);
if ($valid) {
if (isset($this->sessionStorage->redirectAfterLogin)
&& ! empty($this->sessionStorage->redirectAfterLogin)
&& ! filter_var($this->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
$redirect = $this->sessionStorage->redirectAfterLogin;
unset($this->sessionStorage->redirectAfterLogin);
$this->response->redirect($redirect);
}
$this->response->redirect($this->helper->url->to('app', 'index'));
$this->redirectAfterLogin();
}
$this->login($values, $errors);
@ -64,23 +55,23 @@ class Auth extends Base
*/
public function logout()
{
$this->authentication->backend('rememberMe')->destroy($this->userSession->getId());
$this->sessionManager->close();
$this->response->redirect($this->helper->url->to('auth', 'login'));
}
/**
* Display captcha image
* Redirect the user after the authentication
*
* @access public
* @access private
*/
public function captcha()
private function redirectAfterLogin()
{
$this->response->contentType('image/jpeg');
if (isset($this->sessionStorage->redirectAfterLogin) && ! empty($this->sessionStorage->redirectAfterLogin) && ! filter_var($this->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
$redirect = $this->sessionStorage->redirectAfterLogin;
unset($this->sessionStorage->redirectAfterLogin);
$this->response->redirect($redirect);
}
$builder = new CaptchaBuilder;
$builder->build();
$this->sessionStorage->captcha = $builder->getPhrase();
$builder->output();
$this->response->redirect($this->helper->url->to('app', 'index'));
}
}

View file

@ -2,8 +2,7 @@
namespace Kanboard\Controller;
use Pimple\Container;
use Symfony\Component\EventDispatcher\Event;
use Kanboard\Core\Security\Role;
/**
* Base controller
@ -14,36 +13,22 @@ use Symfony\Component\EventDispatcher\Event;
abstract class Base extends \Kanboard\Core\Base
{
/**
* Constructor
*
* @access public
* @param \Pimple\Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
if (DEBUG) {
$this->logger->debug('START_REQUEST='.$_SERVER['REQUEST_URI']);
}
}
/**
* Destructor
* Method executed before each action
*
* @access public
*/
public function __destruct()
public function beforeAction()
{
if (DEBUG) {
foreach ($this->db->getLogMessages() as $message) {
$this->logger->debug($message);
}
$this->sessionManager->open();
$this->dispatcher->dispatch('app.bootstrap');
$this->sendHeaders();
$this->authenticationManager->checkCurrentSession();
$this->logger->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nbQueries));
$this->logger->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT']));
$this->logger->debug('MEMORY='.$this->helper->text->bytes(memory_get_usage()));
$this->logger->debug('END_REQUEST='.$_SERVER['REQUEST_URI']);
if (! $this->applicationAuthorization->isAllowed($this->router->getController(), $this->router->getAction(), Role::APP_PUBLIC)) {
$this->handleAuthentication();
$this->handlePostAuthentication();
$this->checkApplicationAuthorization();
$this->checkProjectAuthorization();
}
}
@ -52,7 +37,7 @@ abstract class Base extends \Kanboard\Core\Base
*
* @access private
*/
private function sendHeaders($action)
private function sendHeaders()
{
// HTTP secure headers
$this->response->csp($this->container['cspRules']);
@ -60,7 +45,7 @@ abstract class Base extends \Kanboard\Core\Base
$this->response->xss();
// Allow the public board iframe inclusion
if (ENABLE_XFRAME && $action !== 'readonly') {
if (ENABLE_XFRAME && $this->router->getAction() !== 'readonly') {
$this->response->xframe();
}
@ -69,34 +54,14 @@ abstract class Base extends \Kanboard\Core\Base
}
}
/**
* Method executed before each action
*
* @access public
*/
public function beforeAction($controller, $action)
{
$this->sessionManager->open();
$this->sendHeaders($action);
$this->container['dispatcher']->dispatch('session.bootstrap', new Event);
if (! $this->acl->isPublicAction($controller, $action)) {
$this->handleAuthentication();
$this->handle2FA($controller, $action);
$this->handleAuthorization($controller, $action);
$this->sessionStorage->hasSubtaskInProgress = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
}
}
/**
* Check authentication
*
* @access public
* @access private
*/
public function handleAuthentication()
private function handleAuthentication()
{
if (! $this->authentication->isAuthenticated()) {
if (! $this->userSession->isLogged() && ! $this->authenticationManager->preAuthentication()) {
if ($this->request->isAjax()) {
$this->response->text('Not Authorized', 401);
}
@ -107,15 +72,17 @@ abstract class Base extends \Kanboard\Core\Base
}
/**
* Check 2FA
* Handle Post-Authentication (2FA)
*
* @access public
* @access private
*/
public function handle2FA($controller, $action)
private function handlePostAuthentication()
{
$controller = strtolower($this->router->getController());
$action = strtolower($this->router->getAction());
$ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout');
if ($ignore === false && $this->userSession->has2FA() && ! $this->userSession->check2FA()) {
if ($ignore === false && $this->userSession->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) {
if ($this->request->isAjax()) {
$this->response->text('Not Authorized', 401);
}
@ -125,11 +92,23 @@ abstract class Base extends \Kanboard\Core\Base
}
/**
* Check page access and authorization
* Check application authorization
*
* @access public
* @access private
*/
public function handleAuthorization($controller, $action)
private function checkApplicationAuthorization()
{
if (! $this->helper->user->hasAccess($this->router->getController(), $this->router->getAction())) {
$this->forbidden();
}
}
/**
* Check project authorization
*
* @access private
*/
private function checkProjectAuthorization()
{
$project_id = $this->request->getIntegerParam('project_id');
$task_id = $this->request->getIntegerParam('task_id');
@ -139,7 +118,7 @@ abstract class Base extends \Kanboard\Core\Base
$project_id = $this->taskFinder->getProjectId($task_id);
}
if (! $this->acl->isAllowed($controller, $action, $project_id)) {
if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($this->router->getController(), $this->router->getAction(), $project_id)) {
$this->forbidden();
}
}
@ -147,10 +126,10 @@ abstract class Base extends \Kanboard\Core\Base
/**
* Application not found page (404 error)
*
* @access public
* @access protected
* @param boolean $no_layout Display the layout or not
*/
public function notfound($no_layout = false)
protected function notfound($no_layout = false)
{
$this->response->html($this->template->layout('app/notfound', array(
'title' => t('Page not found'),
@ -161,11 +140,15 @@ abstract class Base extends \Kanboard\Core\Base
/**
* Application forbidden page
*
* @access public
* @access protected
* @param boolean $no_layout Display the layout or not
*/
public function forbidden($no_layout = false)
protected function forbidden($no_layout = false)
{
if ($this->request->isAjax()) {
$this->response->text('Access Forbidden', 403);
}
$this->response->html($this->template->layout('app/forbidden', array(
'title' => t('Access Forbidden'),
'no_layout' => $no_layout,
@ -209,7 +192,7 @@ abstract class Base extends \Kanboard\Core\Base
$content = $this->template->render($template, $params);
$params['task_content_for_layout'] = $content;
$params['title'] = $params['task']['project_name'].' &gt; '.$params['task']['title'];
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
return $this->template->layout('task/layout', $params);
}
@ -227,7 +210,7 @@ abstract class Base extends \Kanboard\Core\Base
$content = $this->template->render($template, $params);
$params['project_content_for_layout'] = $content;
$params['title'] = $params['project']['name'] === $params['title'] ? $params['title'] : $params['project']['name'].' &gt; '.$params['title'];
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
$params['sidebar_template'] = $sidebar_template;
return $this->template->layout('project/layout', $params);
@ -300,12 +283,15 @@ abstract class Base extends \Kanboard\Core\Base
* Common method to get project filters
*
* @access protected
* @param string $controller
* @param string $action
* @return array
*/
protected function getProjectFilters($controller, $action)
{
$project = $this->getProject();
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
$board_selector = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$board_selector = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
unset($board_selector[$project['id']]);
$filters = array(

View file

@ -51,7 +51,7 @@ class Board extends Base
$this->response->html($this->template->layout('board/view_private', array(
'categories_list' => $this->category->getList($params['project']['id'], false),
'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false),
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()),
'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['project']['id']),
'description' => $params['project']['description'],
@ -73,10 +73,6 @@ class Board extends Base
return $this->response->status(403);
}
if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
$this->response->text('Forbidden', 403);
}
$values = $this->request->getJson();
$result =$this->taskPosition->movePosition(
@ -101,22 +97,18 @@ class Board extends Base
*/
public function check()
{
if (! $this->request->isAjax()) {
return $this->response->status(403);
}
$project_id = $this->request->getIntegerParam('project_id');
$timestamp = $this->request->getIntegerParam('timestamp');
if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
$this->response->text('Forbidden', 403);
if (! $project_id || ! $this->request->isAjax()) {
return $this->response->status(403);
}
if (! $this->project->isModifiedSince($project_id, $timestamp)) {
return $this->response->status(304);
}
$this->response->html($this->renderBoard($project_id));
return $this->response->html($this->renderBoard($project_id));
}
/**
@ -126,14 +118,10 @@ class Board extends Base
*/
public function reload()
{
if (! $this->request->isAjax()) {
return $this->response->status(403);
}
$project_id = $this->request->getIntegerParam('project_id');
if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
$this->response->text('Forbidden', 403);
if (! $project_id || ! $this->request->isAjax()) {
return $this->response->status(403);
}
$values = $this->request->getJson();
@ -142,195 +130,6 @@ class Board extends Base
$this->response->html($this->renderBoard($project_id));
}
/**
* Get links on mouseover
*
* @access public
*/
public function tasklinks()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_tasklinks', array(
'links' => $this->taskLink->getAll($task['id']),
'task' => $task,
)));
}
/**
* Get subtasks on mouseover
*
* @access public
*/
public function subtasks()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_subtasks', array(
'subtasks' => $this->subtask->getAll($task['id']),
'task' => $task,
)));
}
/**
* Display all attachments during the task mouseover
*
* @access public
*/
public function attachments()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_files', array(
'files' => $this->file->getAll($task['id']),
'task' => $task,
)));
}
/**
* Display comments during a task mouseover
*
* @access public
*/
public function comments()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_comments', array(
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting())
)));
}
/**
* Display task description
*
* @access public
*/
public function description()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_description', array(
'task' => $task
)));
}
/**
* Change a task assignee directly from the board
*
* @access public
*/
public function changeAssignee()
{
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
$this->response->html($this->template->render('board/popover_assignee', array(
'values' => $task,
'users_list' => $this->projectPermission->getMemberList($project['id']),
'project' => $project,
)));
}
/**
* Validate an assignee modification
*
* @access public
*/
public function updateAssignee()
{
$values = $this->request->getValues();
list($valid, ) = $this->taskValidator->validateAssigneeModification($values);
if ($valid && $this->taskModification->update($values)) {
$this->flash->success(t('Task updated successfully.'));
} else {
$this->flash->failure(t('Unable to update your task.'));
}
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
}
/**
* Change a task category directly from the board
*
* @access public
*/
public function changeCategory()
{
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
$this->response->html($this->template->render('board/popover_category', array(
'values' => $task,
'categories_list' => $this->category->getList($project['id']),
'project' => $project,
)));
}
/**
* Validate a category modification
*
* @access public
*/
public function updateCategory()
{
$values = $this->request->getValues();
list($valid, ) = $this->taskValidator->validateCategoryModification($values);
if ($valid && $this->taskModification->update($values)) {
$this->flash->success(t('Task updated successfully.'));
} else {
$this->flash->failure(t('Unable to update your task.'));
}
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
}
/**
* Screenshot popover
*
* @access public
*/
public function screenshot()
{
$task = $this->getTask();
$this->response->html($this->template->render('file/screenshot', array(
'task' => $task,
'redirect' => 'board',
)));
}
/**
* Get recurrence information on mouseover
*
* @access public
*/
public function recurrence()
{
$task = $this->getTask();
$this->response->html($this->template->render('task/recurring_info', array(
'task' => $task,
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
)));
}
/**
* Display swimlane description in tooltip
*
* @access public
*/
public function swimlane()
{
$this->getProject();
$swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
$this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
}
/**
* Enable collapsed mode
*
@ -355,6 +154,7 @@ class Board extends Base
* Change display mode
*
* @access private
* @param boolean $mode
*/
private function changeDisplayMode($mode)
{
@ -372,6 +172,7 @@ class Board extends Base
* Render board
*
* @access private
* @param integer $project_id
*/
private function renderBoard($project_id)
{

View file

@ -0,0 +1,136 @@
<?php
namespace Kanboard\Controller;
/**
* Board Popover
*
* @package controller
* @author Frederic Guillot
*/
class BoardPopover extends Base
{
/**
* Change a task assignee directly from the board
*
* @access public
*/
public function changeAssignee()
{
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
$this->response->html($this->template->render('board/popover_assignee', array(
'values' => $task,
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
'project' => $project,
)));
}
/**
* Validate an assignee modification
*
* @access public
*/
public function updateAssignee()
{
$values = $this->request->getValues();
list($valid, ) = $this->taskValidator->validateAssigneeModification($values);
if ($valid && $this->taskModification->update($values)) {
$this->flash->success(t('Task updated successfully.'));
} else {
$this->flash->failure(t('Unable to update your task.'));
}
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
}
/**
* Change a task category directly from the board
*
* @access public
*/
public function changeCategory()
{
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
$this->response->html($this->template->render('board/popover_category', array(
'values' => $task,
'categories_list' => $this->category->getList($project['id']),
'project' => $project,
)));
}
/**
* Validate a category modification
*
* @access public
*/
public function updateCategory()
{
$values = $this->request->getValues();
list($valid, ) = $this->taskValidator->validateCategoryModification($values);
if ($valid && $this->taskModification->update($values)) {
$this->flash->success(t('Task updated successfully.'));
} else {
$this->flash->failure(t('Unable to update your task.'));
}
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
}
/**
* Screenshot popover
*
* @access public
*/
public function screenshot()
{
$task = $this->getTask();
$this->response->html($this->template->render('file/screenshot', array(
'task' => $task,
'redirect' => 'board',
)));
}
/**
* Confirmation before to close all column tasks
*
* @access public
*/
public function confirmCloseColumnTasks()
{
$project = $this->getProject();
$column_id = $this->request->getIntegerParam('column_id');
$swimlane_id = $this->request->getIntegerParam('swimlane_id');
$this->response->html($this->template->render('board/popover_close_all_tasks_column', array(
'project' => $project,
'nb_tasks' => $this->taskFinder->countByColumnAndSwimlaneId($project['id'], $column_id, $swimlane_id),
'column' => $this->board->getColumnTitleById($column_id),
'swimlane' => $this->swimlane->getNameById($swimlane_id) ?: t($project['default_swimlane']),
'values' => array('column_id' => $column_id, 'swimlane_id' => $swimlane_id),
)));
}
/**
* Close all column tasks
*
* @access public
*/
public function closeColumnTasks()
{
$project = $this->getProject();
$values = $this->request->getValues();
$this->taskStatus->closeTasksBySwimlaneAndColumn($values['swimlane_id'], $values['column_id']);
$this->flash->success(t('All tasks of the column "%s" and the swimlane "%s" have been closed successfully.', $this->board->getColumnTitleById($values['column_id']), $this->swimlane->getNameById($values['swimlane_id']) ?: t($project['default_swimlane'])));
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])));
}
}

View file

@ -0,0 +1,112 @@
<?php
namespace Kanboard\Controller;
/**
* Board Tooltip
*
* @package controller
* @author Frederic Guillot
*/
class BoardTooltip extends Base
{
/**
* Get links on mouseover
*
* @access public
*/
public function tasklinks()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_tasklinks', array(
'links' => $this->taskLink->getAll($task['id']),
'task' => $task,
)));
}
/**
* Get subtasks on mouseover
*
* @access public
*/
public function subtasks()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_subtasks', array(
'subtasks' => $this->subtask->getAll($task['id']),
'task' => $task,
)));
}
/**
* Display all attachments during the task mouseover
*
* @access public
*/
public function attachments()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_files', array(
'files' => $this->file->getAll($task['id']),
'task' => $task,
)));
}
/**
* Display comments during a task mouseover
*
* @access public
*/
public function comments()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_comments', array(
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting())
)));
}
/**
* Display task description
*
* @access public
*/
public function description()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_description', array(
'task' => $task
)));
}
/**
* Get recurrence information on mouseover
*
* @access public
*/
public function recurrence()
{
$task = $this->getTask();
$this->response->html($this->template->render('task/recurring_info', array(
'task' => $task,
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
)));
}
/**
* Display swimlane description in tooltip
*
* @access public
*/
public function swimlane()
{
$this->getProject();
$swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
$this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Kanboard\Controller;
use Gregwar\Captcha\CaptchaBuilder;
/**
* Captcha Controller
*
* @package controller
* @author Frederic Guillot
*/
class Captcha extends Base
{
/**
* Display captcha image
*
* @access public
*/
public function image()
{
$this->response->contentType('image/jpeg');
$builder = new CaptchaBuilder;
$builder->build();
$this->sessionStorage->captcha = $builder->getPhrase();
$builder->output();
}
}

View file

@ -57,7 +57,7 @@ class Category extends Base
$project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateCreation($values);
list($valid, $errors) = $this->categoryValidator->validateCreation($values);
if ($valid) {
if ($this->category->create($values)) {
@ -99,7 +99,7 @@ class Category extends Base
$project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateModification($values);
list($valid, $errors) = $this->categoryValidator->validateModification($values);
if ($valid) {
if ($this->category->update($values)) {

View file

@ -51,7 +51,7 @@ class Column extends Base
$values['title['.$column_id.']'] = $column_title;
}
list($valid, $errors) = $this->board->validateCreation($data);
list($valid, $errors) = $this->columnValidator->validateCreation($data);
if ($valid) {
if ($this->board->addColumn($project['id'], $data['title'], $data['task_limit'], $data['description'])) {
@ -94,7 +94,7 @@ class Column extends Base
$project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->board->validateModification($values);
list($valid, $errors) = $this->columnValidator->validateModification($values);
if ($valid) {
if ($this->board->updateColumn($values['id'], $values['title'], $values['task_limit'], $values['description'])) {

View file

@ -78,7 +78,7 @@ class Comment extends Base
$values = $this->request->getValues();
$ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
list($valid, $errors) = $this->comment->validateCreation($values);
list($valid, $errors) = $this->commentValidator->validateCreation($values);
if ($valid) {
if ($this->comment->create($values)) {
@ -127,7 +127,7 @@ class Comment extends Base
$comment = $this->getComment();
$values = $this->request->getValues();
list($valid, $errors) = $this->comment->validateModification($values);
list($valid, $errors) = $this->commentValidator->validateModification($values);
if ($valid) {
if ($this->comment->update($values)) {

View file

@ -20,7 +20,7 @@ class Config extends Base
*/
private function layout($template, array $params)
{
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
$params['values'] = $this->config->getAll();
$params['errors'] = array();
$params['config_content_for_layout'] = $this->template->render($template, $params);
@ -40,6 +40,9 @@ class Config extends Base
$values = $this->request->getValues();
switch ($redirect) {
case 'application':
$values += array('password_reset' => 0);
break;
case 'project':
$values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0, 'cfd_include_closed_tasks' => 0);
break;

View file

@ -20,7 +20,7 @@ class Currency extends Base
*/
private function layout($template, array $params)
{
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
$params['config_content_for_layout'] = $this->template->render($template, $params);
return $this->template->layout('config/layout', $params);
@ -38,7 +38,7 @@ class Currency extends Base
'values' => $values,
'errors' => $errors,
'rates' => $this->currency->getAll(),
'currencies' => $this->config->getCurrencies(),
'currencies' => $this->currency->getCurrencies(),
'title' => t('Settings').' &gt; '.t('Currency rates'),
)));
}
@ -51,7 +51,7 @@ class Currency extends Base
public function create()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->currency->validate($values);
list($valid, $errors) = $this->currencyValidator->validateCreation($values);
if ($valid) {
if ($this->currency->create($values['currency'], $values['rate'])) {

View file

@ -2,6 +2,8 @@
namespace Kanboard\Controller;
use Kanboard\Core\Security\Role;
/**
* Custom Filter management
*
@ -40,7 +42,7 @@ class Customfilter extends Base
$values = $this->request->getValues();
$values['user_id'] = $this->userSession->getId();
list($valid, $errors) = $this->customFilter->validateCreation($values);
list($valid, $errors) = $this->customFilterValidator->validateCreation($values);
if ($valid) {
if ($this->customFilter->create($values)) {
@ -119,7 +121,7 @@ class Customfilter extends Base
$values += array('append' => 0);
}
list($valid, $errors) = $this->customFilter->validateModification($values);
list($valid, $errors) = $this->customFilterValidator->validateModification($values);
if ($valid) {
if ($this->customFilter->update($values)) {
@ -137,7 +139,7 @@ class Customfilter extends Base
{
$user_id = $this->userSession->getId();
if ($filter['user_id'] != $user_id && (! $this->projectPermission->isManager($project['id'], $user_id) || ! $this->userSession->isAdmin())) {
if ($filter['user_id'] != $user_id && ($this->projectUserRole->getUserRole($project['id'], $user_id) === Role::PROJECT_MANAGER || ! $this->userSession->isAdmin())) {
$this->forbidden();
}
}

View file

@ -53,7 +53,7 @@ class Doc extends Base
}
$this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
)));
}
}

View file

@ -25,10 +25,8 @@ class Feed extends Base
$this->forbidden(true);
}
$projects = $this->projectPermission->getActiveMemberProjects($user['id']);
$this->response->xml($this->template->render('feed/user', array(
'events' => $this->projectActivity->getProjects(array_keys($projects)),
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id'])),
'user' => $user,
)));
}

View file

@ -20,13 +20,13 @@ class Gantt extends Base
if ($this->userSession->isAdmin()) {
$project_ids = $this->project->getAllIds();
} else {
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
}
$this->response->html($this->template->layout('gantt/projects', array(
'projects' => $this->projectGanttFormatter->filter($project_ids)->format(),
'title' => t('Gantt chart for all projects'),
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
)));
}
@ -66,7 +66,7 @@ class Gantt extends Base
}
$this->response->html($this->template->layout('gantt/project', $params + array(
'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false),
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
'sorting' => $sorting,
'tasks' => $filter->format(),
)));
@ -109,7 +109,7 @@ class Gantt extends Base
'column_id' => $this->board->getFirstColumn($project['id']),
'position' => 1
),
'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true),
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id'], true, false, true),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'swimlanes_list' => $this->swimlane->getList($project['id'], false, true),

View file

@ -0,0 +1,255 @@
<?php
namespace Kanboard\Controller;
/**
* Group Controller
*
* @package controller
* @author Frederic Guillot
*/
class Group extends Base
{
/**
* List all groups
*
* @access public
*/
public function index()
{
$paginator = $this->paginator
->setUrl('group', 'index')
->setMax(30)
->setOrder('name')
->setQuery($this->group->getQuery())
->calculate();
$this->response->html($this->template->layout('group/index', array(
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'title' => t('Groups').' ('.$paginator->getTotal().')',
'paginator' => $paginator,
)));
}
/**
* List all users
*
* @access public
*/
public function users()
{
$group_id = $this->request->getIntegerParam('group_id');
$group = $this->group->getById($group_id);
$paginator = $this->paginator
->setUrl('group', 'users', array('group_id' => $group_id))
->setMax(30)
->setOrder('username')
->setQuery($this->groupMember->getQuery($group_id))
->calculate();
$this->response->html($this->template->layout('group/users', array(
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'title' => t('Members of %s', $group['name']).' ('.$paginator->getTotal().')',
'paginator' => $paginator,
'group' => $group,
)));
}
/**
* Display a form to create a new group
*
* @access public
*/
public function create(array $values = array(), array $errors = array())
{
$this->response->html($this->template->layout('group/create', array(
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'errors' => $errors,
'values' => $values,
'title' => t('New group')
)));
}
/**
* Validate and save a new group
*
* @access public
*/
public function save()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->groupValidator->validateCreation($values);
if ($valid) {
if ($this->group->create($values['name']) !== false) {
$this->flash->success(t('Group created successfully.'));
$this->response->redirect($this->helper->url->to('group', 'index'));
} else {
$this->flash->failure(t('Unable to create your group.'));
}
}
$this->create($values, $errors);
}
/**
* Display a form to update a group
*
* @access public
*/
public function edit(array $values = array(), array $errors = array())
{
if (empty($values)) {
$values = $this->group->getById($this->request->getIntegerParam('group_id'));
}
$this->response->html($this->template->layout('group/edit', array(
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'errors' => $errors,
'values' => $values,
'title' => t('Edit group')
)));
}
/**
* Validate and save a group
*
* @access public
*/
public function update()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->groupValidator->validateModification($values);
if ($valid) {
if ($this->group->update($values) !== false) {
$this->flash->success(t('Group updated successfully.'));
$this->response->redirect($this->helper->url->to('group', 'index'));
} else {
$this->flash->failure(t('Unable to update your group.'));
}
}
$this->edit($values, $errors);
}
/**
* Form to associate a user to a group
*
* @access public
*/
public function associate(array $values = array(), array $errors = array())
{
$group_id = $this->request->getIntegerParam('group_id');
$group = $this->group->getbyId($group_id);
if (empty($values)) {
$values['group_id'] = $group_id;
}
$this->response->html($this->template->layout('group/associate', array(
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'users' => $this->user->prepareList($this->groupMember->getNotMembers($group_id)),
'group' => $group,
'errors' => $errors,
'values' => $values,
'title' => t('Add group member to "%s"', $group['name']),
)));
}
/**
* Add user to a group
*
* @access public
*/
public function addUser()
{
$values = $this->request->getValues();
if (isset($values['group_id']) && isset($values['user_id'])) {
if ($this->groupMember->addUser($values['group_id'], $values['user_id'])) {
$this->flash->success(t('Group member added successfully.'));
$this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $values['group_id'])));
} else {
$this->flash->failure(t('Unable to add group member.'));
}
}
$this->associate($values);
}
/**
* Confirmation dialog to remove a user from a group
*
* @access public
*/
public function dissociate()
{
$group_id = $this->request->getIntegerParam('group_id');
$user_id = $this->request->getIntegerParam('user_id');
$group = $this->group->getById($group_id);
$user = $this->user->getById($user_id);
$this->response->html($this->template->layout('group/dissociate', array(
'group' => $group,
'user' => $user,
'title' => t('Remove user from group "%s"', $group['name']),
)));
}
/**
* Remove a user from a group
*
* @access public
*/
public function removeUser()
{
$this->checkCSRFParam();
$group_id = $this->request->getIntegerParam('group_id');
$user_id = $this->request->getIntegerParam('user_id');
if ($this->groupMember->removeUser($group_id, $user_id)) {
$this->flash->success(t('User removed successfully from this group.'));
} else {
$this->flash->failure(t('Unable to remove this user from the group.'));
}
$this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $group_id)));
}
/**
* Confirmation dialog to remove a group
*
* @access public
*/
public function confirm()
{
$group_id = $this->request->getIntegerParam('group_id');
$group = $this->group->getById($group_id);
$this->response->html($this->template->layout('group/remove', array(
'group' => $group,
'title' => t('Remove group'),
)));
}
/**
* Remove a group
*
* @access public
*/
public function remove()
{
$this->checkCSRFParam();
$group_id = $this->request->getIntegerParam('group_id');
if ($this->group->remove($group_id)) {
$this->flash->success(t('Group removed successfully.'));
} else {
$this->flash->failure(t('Unable to remove this group.'));
}
$this->response->redirect($this->helper->url->to('group', 'index'));
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Kanboard\Controller;
/**
* Group Helper
*
* @package controller
* @author Frederic Guillot
*/
class GroupHelper extends Base
{
/**
* Group autocompletion (Ajax)
*
* @access public
*/
public function autocomplete()
{
$search = $this->request->getStringParam('term');
$groups = $this->groupManager->find($search);
$this->response->json($this->groupAutoCompleteFormatter->setGroups($groups)->format());
}
}

View file

@ -21,7 +21,7 @@ class Link extends Base
*/
private function layout($template, array $params)
{
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
$params['config_content_for_layout'] = $this->template->render($template, $params);
return $this->template->layout('config/layout', $params);
@ -67,7 +67,7 @@ class Link extends Base
public function save()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->link->validateCreation($values);
list($valid, $errors) = $this->linkValidator->validateCreation($values);
if ($valid) {
if ($this->link->create($values['label'], $values['opposite_label']) !== false) {
@ -108,7 +108,7 @@ class Link extends Base
public function update()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->link->validateModification($values);
list($valid, $errors) = $this->linkValidator->validateModification($values);
if ($valid) {
if ($this->link->update($values)) {

View file

@ -17,7 +17,7 @@ class Oauth extends Base
*/
public function google()
{
$this->step1('google');
$this->step1('Google');
}
/**
@ -27,7 +27,7 @@ class Oauth extends Base
*/
public function github()
{
$this->step1('github');
$this->step1('Github');
}
/**
@ -37,7 +37,7 @@ class Oauth extends Base
*/
public function gitlab()
{
$this->step1('gitlab');
$this->step1('Gitlab');
}
/**
@ -45,12 +45,12 @@ class Oauth extends Base
*
* @access public
*/
public function unlink($backend = '')
public function unlink()
{
$backend = $this->request->getStringParam('backend', $backend);
$backend = $this->request->getStringParam('backend');
$this->checkCSRFParam();
if ($this->authentication->backend($backend)->unlink($this->userSession->getId())) {
if ($this->authenticationManager->getProvider($backend)->unlink($this->userSession->getId())) {
$this->flash->success(t('Your external account is not linked anymore to your profile.'));
} else {
$this->flash->failure(t('Unable to unlink your external account.'));
@ -63,15 +63,16 @@ class Oauth extends Base
* Redirect to the provider if no code received
*
* @access private
* @param string $provider
*/
private function step1($backend)
private function step1($provider)
{
$code = $this->request->getStringParam('code');
if (! empty($code)) {
$this->step2($backend, $code);
$this->step2($provider, $code);
} else {
$this->response->redirect($this->authentication->backend($backend)->getService()->getAuthorizationUrl());
$this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl());
}
}
@ -79,30 +80,35 @@ class Oauth extends Base
* Link or authenticate the user
*
* @access private
* @param string $provider
* @param string $code
*/
private function step2($backend, $code)
private function step2($provider, $code)
{
$profile = $this->authentication->backend($backend)->getProfile($code);
$this->authenticationManager->getProvider($provider)->setCode($code);
if ($this->userSession->isLogged()) {
$this->link($backend, $profile);
$this->link($provider);
}
$this->authenticate($backend, $profile);
$this->authenticate($provider);
}
/**
* Link the account
*
* @access private
* @param string $provider
*/
private function link($backend, $profile)
private function link($provider)
{
if (empty($profile)) {
$authProvider = $this->authenticationManager->getProvider($provider);
if (! $authProvider->authenticate()) {
$this->flash->failure(t('External authentication failed'));
} else {
$this->userProfile->assign($this->userSession->getId(), $authProvider->getUser());
$this->flash->success(t('Your external account is linked to your profile successfully.'));
$this->authentication->backend($backend)->updateUser($this->userSession->getId(), $profile);
}
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
@ -112,10 +118,11 @@ class Oauth extends Base
* Authenticate the account
*
* @access private
* @param string $provider
*/
private function authenticate($backend, $profile)
private function authenticate($provider)
{
if (! empty($profile) && $this->authentication->backend($backend)->authenticate($profile['id'])) {
if ($this->authenticationManager->oauthAuthentication($provider)) {
$this->response->redirect($this->helper->url->to('app', 'index'));
} else {
$this->response->html($this->template->layout('auth/index', array(

View file

@ -0,0 +1,120 @@
<?php
namespace Kanboard\Controller;
/**
* Password Reset Controller
*
* @package controller
* @author Frederic Guillot
*/
class PasswordReset extends Base
{
/**
* Show the form to reset the password
*/
public function create(array $values = array(), array $errors = array())
{
$this->checkActivation();
$this->response->html($this->template->layout('password_reset/create', array(
'errors' => $errors,
'values' => $values,
'no_layout' => true,
)));
}
/**
* Validate and send the email
*/
public function save()
{
$this->checkActivation();
$values = $this->request->getValues();
list($valid, $errors) = $this->passwordResetValidator->validateCreation($values);
if ($valid) {
$this->sendEmail($values['username']);
$this->response->redirect($this->helper->url->to('auth', 'login'));
}
$this->create($values, $errors);
}
/**
* Show the form to set a new password
*/
public function change(array $values = array(), array $errors = array())
{
$this->checkActivation();
$token = $this->request->getStringParam('token');
$user_id = $this->passwordReset->getUserIdByToken($token);
if ($user_id !== false) {
$this->response->html($this->template->layout('password_reset/change', array(
'token' => $token,
'errors' => $errors,
'values' => $values,
'no_layout' => true,
)));
}
$this->response->redirect($this->helper->url->to('auth', 'login'));
}
/**
* Set the new password
*/
public function update()
{
$this->checkActivation();
$token = $this->request->getStringParam('token');
$values = $this->request->getValues();
list($valid, $errors) = $this->passwordResetValidator->validateModification($values);
if ($valid) {
$user_id = $this->passwordReset->getUserIdByToken($token);
if ($user_id !== false) {
$this->user->update(array('id' => $user_id, 'password' => $values['password']));
$this->passwordReset->disable($user_id);
}
$this->response->redirect($this->helper->url->to('auth', 'login'));
}
$this->change($values, $errors);
}
/**
* Send the email
*/
private function sendEmail($username)
{
$token = $this->passwordReset->create($username);
if ($token !== false) {
$user = $this->user->getByUsername($username);
$this->emailClient->send(
$user['email'],
$user['name'] ?: $user['username'],
t('Password Reset for Kanboard'),
$this->template->render('password_reset/email', array('token' => $token))
);
}
}
/**
* Check feature availability
*/
private function checkActivation()
{
if ($this->config->get('password_reset', 0) == 0) {
$this->response->redirect($this->helper->url->to('auth', 'login'));
}
}
}

View file

@ -20,7 +20,7 @@ class Project extends Base
if ($this->userSession->isAdmin()) {
$project_ids = $this->project->getAllIds();
} else {
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
}
$nb_projects = count($project_ids);
@ -33,7 +33,7 @@ class Project extends Base
->calculate();
$this->response->html($this->template->layout('project/index', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'paginator' => $paginator,
'nb_projects' => $nb_projects,
'title' => t('Projects').' ('.$nb_projects.')'
@ -160,16 +160,16 @@ class Project extends Base
$values = $this->request->getValues();
if (isset($values['is_private'])) {
if (! $this->helper->user->isProjectAdministrationAllowed($project['id'])) {
if (! $this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
unset($values['is_private']);
}
} elseif ($project['is_private'] == 1 && ! isset($values['is_private'])) {
if ($this->helper->user->isProjectAdministrationAllowed($project['id'])) {
if ($this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
$values += array('is_private' => 0);
}
}
list($valid, $errors) = $this->project->validateModification($values);
list($valid, $errors) = $this->projectValidator->validateModification($values);
if ($valid) {
if ($this->project->update($values)) {
@ -183,120 +183,6 @@ class Project extends Base
$this->edit($values, $errors);
}
/**
* Users list for the selected project
*
* @access public
*/
public function users()
{
$project = $this->getProject();
$this->response->html($this->projectLayout('project/users', array(
'project' => $project,
'users' => $this->projectPermission->getAllUsers($project['id']),
'title' => t('Edit project access list')
)));
}
/**
* Allow everybody
*
* @access public
*/
public function allowEverybody()
{
$project = $this->getProject();
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
list($valid, ) = $this->projectPermission->validateProjectModification($values);
if ($valid) {
if ($this->project->update($values)) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
}
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $project['id'])));
}
/**
* Allow a specific user (admin only)
*
* @access public
*/
public function allow()
{
$values = $this->request->getValues();
list($valid, ) = $this->projectPermission->validateUserModification($values);
if ($valid) {
if ($this->projectPermission->addMember($values['project_id'], $values['user_id'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
}
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
}
/**
* Change the role of a project member
*
* @access public
*/
public function role()
{
$this->checkCSRFParam();
$values = array(
'project_id' => $this->request->getIntegerParam('project_id'),
'user_id' => $this->request->getIntegerParam('user_id'),
'is_owner' => $this->request->getIntegerParam('is_owner'),
);
list($valid, ) = $this->projectPermission->validateUserModification($values);
if ($valid) {
if ($this->projectPermission->changeRole($values['project_id'], $values['user_id'], $values['is_owner'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
}
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
}
/**
* Revoke user access (admin only)
*
* @access public
*/
public function revoke()
{
$this->checkCSRFParam();
$values = array(
'project_id' => $this->request->getIntegerParam('project_id'),
'user_id' => $this->request->getIntegerParam('user_id'),
);
list($valid, ) = $this->projectPermission->validateUserModification($values);
if ($valid) {
if ($this->projectPermission->revokeMember($values['project_id'], $values['user_id'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
}
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
}
/**
* Remove a project
*
@ -413,17 +299,28 @@ class Project extends Base
*/
public function create(array $values = array(), array $errors = array())
{
$is_private = $this->request->getIntegerParam('private', $this->userSession->isAdmin() || $this->userSession->isProjectAdmin() ? 0 : 1);
$is_private = isset($values['is_private']) && $values['is_private'] == 1;
$this->response->html($this->template->layout('project/new', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'values' => empty($values) ? array('is_private' => $is_private) : $values,
'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()),
'values' => $values,
'errors' => $errors,
'is_private' => $is_private,
'title' => $is_private ? t('New private project') : t('New project'),
)));
}
/**
* Display a form to create a private project
*
* @access public
*/
public function createPrivate(array $values = array(), array $errors = array())
{
$values['is_private'] = 1;
$this->create($values, $errors);
}
/**
* Validate and save a new project
*
@ -432,7 +329,7 @@ class Project extends Base
public function save()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->project->validateCreation($values);
list($valid, $errors) = $this->projectValidator->validateCreation($values);
if ($valid) {
$project_id = $this->project->create($values, $this->userSession->getId(), true);

View file

@ -0,0 +1,177 @@
<?php
namespace Kanboard\Controller;
use Kanboard\Core\Security\Role;
/**
* Project Permission
*
* @package controller
* @author Frederic Guillot
*/
class ProjectPermission extends Base
{
/**
* Show all permissions
*
* @access public
*/
public function index(array $values = array(), array $errors = array())
{
$project = $this->getProject();
if (empty($values)) {
$values['role'] = Role::PROJECT_MEMBER;
}
$this->response->html($this->projectLayout('project_permission/index', array(
'project' => $project,
'users' => $this->projectUserRole->getUsers($project['id']),
'groups' => $this->projectGroupRole->getGroups($project['id']),
'roles' => $this->role->getProjectRoles(),
'values' => $values,
'errors' => $errors,
'title' => t('Project Permissions'),
)));
}
/**
* Allow everybody
*
* @access public
*/
public function allowEverybody()
{
$project = $this->getProject();
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
if ($this->project->update($values)) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $project['id'])));
}
/**
* Add user to the project
*
* @access public
*/
public function addUser()
{
$values = $this->request->getValues();
if ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
}
/**
* Revoke user access
*
* @access public
*/
public function removeUser()
{
$this->checkCSRFParam();
$values = array(
'project_id' => $this->request->getIntegerParam('project_id'),
'user_id' => $this->request->getIntegerParam('user_id'),
);
if ($this->projectUserRole->removeUser($values['project_id'], $values['user_id'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
}
/**
* Change user role
*
* @access public
*/
public function changeUserRole()
{
$project_id = $this->request->getIntegerParam('project_id');
$values = $this->request->getJson();
if (! empty($project_id) && ! empty($values) && $this->projectUserRole->changeUserRole($project_id, $values['id'], $values['role'])) {
$this->response->json(array('status' => 'ok'));
} else {
$this->response->json(array('status' => 'error'));
}
}
/**
* Add group to the project
*
* @access public
*/
public function addGroup()
{
$values = $this->request->getValues();
if (empty($values['group_id']) && ! empty($values['external_id'])) {
$values['group_id'] = $this->group->create($values['name'], $values['external_id']);
}
if ($this->projectGroupRole->addGroup($values['project_id'], $values['group_id'], $values['role'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
}
/**
* Revoke group access
*
* @access public
*/
public function removeGroup()
{
$this->checkCSRFParam();
$values = array(
'project_id' => $this->request->getIntegerParam('project_id'),
'group_id' => $this->request->getIntegerParam('group_id'),
);
if ($this->projectGroupRole->removeGroup($values['project_id'], $values['group_id'])) {
$this->flash->success(t('Project updated successfully.'));
} else {
$this->flash->failure(t('Unable to update this project.'));
}
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
}
/**
* Change group role
*
* @access public
*/
public function changeGroupRole()
{
$project_id = $this->request->getIntegerParam('project_id');
$values = $this->request->getJson();
if (! empty($project_id) && ! empty($values) && $this->projectGroupRole->changeGroupRole($project_id, $values['id'], $values['role'])) {
$this->response->json(array('status' => 'ok'));
} else {
$this->response->json(array('status' => 'error'));
}
}
}

View file

@ -4,6 +4,7 @@ namespace Kanboard\Controller;
use Kanboard\Model\User as UserModel;
use Kanboard\Model\Task as TaskModel;
use Kanboard\Core\Security\Role;
/**
* Project User overview
@ -23,7 +24,7 @@ class Projectuser extends Base
*/
private function layout($template, array $params)
{
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
$params['content_for_sublayout'] = $this->template->render($template, $params);
$params['filter'] = array('user_id' => $params['user_id']);
@ -37,17 +38,17 @@ class Projectuser extends Base
if ($this->userSession->isAdmin()) {
$project_ids = $this->project->getAllIds();
} else {
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
}
return array($user_id, $project_ids, $this->user->getList(true));
}
private function role($is_owner, $action, $title, $title_user)
private function role($role, $action, $title, $title_user)
{
list($user_id, $project_ids, $users) = $this->common();
$query = $this->projectPermission->getQueryByRole($project_ids, $is_owner)->callback(array($this->project, 'applyColumnStats'));
$query = $this->projectPermission->getQueryByRole($project_ids, $role)->callback(array($this->project, 'applyColumnStats'));
if ($user_id !== UserModel::EVERYBODY_ID) {
$query->eq(UserModel::TABLE.'.id', $user_id);
@ -101,7 +102,7 @@ class Projectuser extends Base
*/
public function managers()
{
$this->role(1, 'managers', t('People who are project managers'), 'Projects where "%s" is manager');
$this->role(Role::PROJECT_MANAGER, 'managers', t('People who are project managers'), 'Projects where "%s" is manager');
}
/**
@ -110,7 +111,7 @@ class Projectuser extends Base
*/
public function members()
{
$this->role(0, 'members', t('People who are project members'), 'Projects where "%s" is member');
$this->role(ROLE::PROJECT_MEMBER, 'members', t('People who are project members'), 'Projects where "%s" is member');
}
/**

View file

@ -12,7 +12,7 @@ class Search extends Base
{
public function index()
{
$projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$projects = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
$search = urldecode($this->request->getStringParam('search'));
$nb_tasks = 0;

View file

@ -48,7 +48,7 @@ class Subtask extends Base
$this->response->html($this->taskLayout('subtask/create', array(
'values' => $values,
'errors' => $errors,
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
'task' => $task,
)));
}
@ -63,7 +63,7 @@ class Subtask extends Base
$task = $this->getTask();
$values = $this->request->getValues();
list($valid, $errors) = $this->subtask->validateCreation($values);
list($valid, $errors) = $this->subtaskValidator->validateCreation($values);
if ($valid) {
if ($this->subtask->create($values)) {
@ -95,7 +95,7 @@ class Subtask extends Base
$this->response->html($this->taskLayout('subtask/edit', array(
'values' => empty($values) ? $subtask : $values,
'errors' => $errors,
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
'status_list' => $this->subtask->getStatusList(),
'subtask' => $subtask,
'task' => $task,
@ -113,7 +113,7 @@ class Subtask extends Base
$this->getSubtask();
$values = $this->request->getValues();
list($valid, $errors) = $this->subtask->validateModification($values);
list($valid, $errors) = $this->subtaskValidator->validateModification($values);
if ($valid) {
if ($this->subtask->update($values)) {

View file

@ -60,7 +60,7 @@ class Swimlane extends Base
{
$project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->swimlane->validateCreation($values);
list($valid, $errors) = $this->swimlaneValidator->validateCreation($values);
if ($valid) {
if ($this->swimlane->create($values)) {
@ -84,7 +84,7 @@ class Swimlane extends Base
$project = $this->getProject();
$values = $this->request->getValues() + array('show_default_swimlane' => 0);
list($valid, ) = $this->swimlane->validateDefaultModification($values);
list($valid, ) = $this->swimlaneValidator->validateDefaultModification($values);
if ($valid) {
if ($this->swimlane->updateDefault($values)) {
@ -126,7 +126,7 @@ class Swimlane extends Base
$project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->swimlane->validateModification($values);
list($valid, $errors) = $this->swimlaneValidator->validateModification($values);
if ($valid) {
if ($this->swimlane->update($values)) {

View file

@ -76,7 +76,7 @@ class Task extends Base
'link_label_list' => $this->link->getList(0, false),
'columns_list' => $this->board->getColumnsList($task['project_id']),
'colors_list' => $this->color->getList(),
'users_list' => $this->projectPermission->getMemberList($task['project_id'], true, false, false),
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id'], true, false, false),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'title' => $task['project_name'].' &gt; '.$task['title'],

View file

@ -0,0 +1,57 @@
<?php
namespace Kanboard\Controller;
/**
* Task Ajax Helper
*
* @package controller
* @author Frederic Guillot
*/
class TaskHelper extends Base
{
/**
* Render Markdown text and reply with the HTML Code
*
* @access public
*/
public function preview()
{
$payload = $this->request->getJson();
if (empty($payload['text'])) {
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
}
$this->response->html($this->helper->text->markdown($payload['text']));
}
/**
* Task autocompletion (Ajax)
*
* @access public
*/
public function autocomplete()
{
$search = $this->request->getStringParam('term');
$projects = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
if (empty($projects)) {
$this->response->json(array());
}
$filter = $this->taskFilterAutoCompleteFormatter
->create()
->filterByProjects($projects)
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
// Search by task id or by title
if (ctype_digit($search)) {
$filter->filterById($search);
} else {
$filter->filterByTitle($search);
}
$this->response->json($filter->format());
}
}

View file

@ -36,7 +36,7 @@ class Taskcreation extends Base
'errors' => $errors,
'values' => $values + array('project_id' => $project['id']),
'columns_list' => $this->board->getColumnsList($project['id']),
'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true),
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id'], true, false, true),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'swimlanes_list' => $swimlanes_list,

View file

@ -2,6 +2,8 @@
namespace Kanboard\Controller;
use Kanboard\Model\Project as ProjectModel;
/**
* Task Duplication controller
*
@ -107,7 +109,7 @@ class Taskduplication extends Base
private function chooseDestination(array $task, $template)
{
$values = array();
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
$projects_list = $this->projectUserRole->getProjectsByUser($this->userSession->getId(), array(ProjectModel::ACTIVE));
unset($projects_list[$task['project_id']]);
@ -117,7 +119,7 @@ class Taskduplication extends Base
$swimlanes_list = $this->swimlane->getList($dst_project_id, false, true);
$columns_list = $this->board->getColumnsList($dst_project_id);
$categories_list = $this->category->getList($dst_project_id);
$users_list = $this->projectPermission->getMemberList($dst_project_id);
$users_list = $this->projectUserRole->getAssignableUsersList($dst_project_id);
$values = $this->taskDuplication->checkDestinationProjectValues($task);
$values['project_id'] = $dst_project_id;

View file

@ -69,7 +69,7 @@ class Tasklink extends Base
$values = $this->request->getValues();
$ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
list($valid, $errors) = $this->taskLink->validateCreation($values);
list($valid, $errors) = $this->taskLinkValidator->validateCreation($values);
if ($valid) {
if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
@ -125,7 +125,7 @@ class Tasklink extends Base
$task = $this->getTask();
$values = $this->request->getValues();
list($valid, $errors) = $this->taskLink->validateModification($values);
list($valid, $errors) = $this->taskLinkValidator->validateModification($values);
if ($valid) {
if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) {

Some files were not shown because too many files have changed in this diff Show more