diff options
Diffstat (limited to 'app')
21 files changed, 213 insertions, 28 deletions
diff --git a/app/Api/Authorization/ProjectAuthorization.php b/app/Api/Authorization/ProjectAuthorization.php index 21ecf311..7dcdc445 100644 --- a/app/Api/Authorization/ProjectAuthorization.php +++ b/app/Api/Authorization/ProjectAuthorization.php @@ -23,13 +23,13 @@ class ProjectAuthorization extends Base protected function checkProjectPermission($class, $method, $project_id) { if (empty($project_id)) { - throw new AccessDeniedException('Project not found'); + throw new AccessDeniedException('Project Not Found'); } $role = $this->projectUserRoleModel->getUserRole($project_id, $this->userSession->getId()); if (! $this->apiProjectAuthorization->isAllowed($class, $method, $role)) { - throw new AccessDeniedException('Project access denied'); + throw new AccessDeniedException('Project Access Denied'); } } } diff --git a/app/Api/Authorization/TagAuthorization.php b/app/Api/Authorization/TagAuthorization.php new file mode 100644 index 00000000..247f57db --- /dev/null +++ b/app/Api/Authorization/TagAuthorization.php @@ -0,0 +1,23 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TagAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TagAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $tag_id) + { + if ($this->userSession->isLogged()) { + $tag = $this->tagModel->getById($tag_id); + + if (! empty($tag)) { + $this->checkProjectPermission($class, $method, $tag['project_id']); + } + } + } +} diff --git a/app/Api/Authorization/TaskAuthorization.php b/app/Api/Authorization/TaskAuthorization.php index db93b76b..6e044211 100644 --- a/app/Api/Authorization/TaskAuthorization.php +++ b/app/Api/Authorization/TaskAuthorization.php @@ -10,10 +10,10 @@ namespace Kanboard\Api\Authorization; */ class TaskAuthorization extends ProjectAuthorization { - public function check($class, $method, $category_id) + public function check($class, $method, $task_id) { if ($this->userSession->isLogged()) { - $this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($category_id)); + $this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($task_id)); } } } diff --git a/app/Api/Middleware/AuthenticationMiddleware.php b/app/Api/Middleware/AuthenticationMiddleware.php index c4fa874a..174dc467 100644 --- a/app/Api/Middleware/AuthenticationMiddleware.php +++ b/app/Api/Middleware/AuthenticationMiddleware.php @@ -31,7 +31,7 @@ class AuthenticationMiddleware extends Base implements MiddlewareInterface $this->sessionStorage->scope = 'API'; if ($this->isUserAuthenticated($username, $password)) { - $this->userSession->initialize($this->userModel->getByUsername($username)); + $this->userSession->initialize($this->userCacheDecorator->getByUsername($username)); } elseif (! $this->isAppAuthenticated($username, $password)) { $this->logger->error('API authentication failure for '.$username); throw new AuthenticationFailureException('Wrong credentials'); diff --git a/app/Api/Procedure/TagProcedure.php b/app/Api/Procedure/TagProcedure.php new file mode 100644 index 00000000..f1c06d01 --- /dev/null +++ b/app/Api/Procedure/TagProcedure.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Api\Authorization\TagAuthorization; + +/** + * Class TagProcedure + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class TagProcedure extends BaseProcedure +{ + public function getAllTags() + { + return $this->tagModel->getAll(); + } + + public function getTagsByProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTagsByProject', $project_id); + return $this->tagModel->getAllByProject($project_id); + } + + public function createTag($project_id, $tag) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTag', $project_id); + return $this->tagModel->findOrCreateTag($project_id, $tag); + } + + public function updateTag($tag_id, $tag) + { + TagAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTag', $tag_id); + return $this->tagModel->update($tag_id, $tag); + } + + public function removeTag($tag_id) + { + TagAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTag', $tag_id); + return $this->tagModel->remove($tag_id); + } +} diff --git a/app/Api/Procedure/TaskProcedure.php b/app/Api/Procedure/TaskProcedure.php index ee9242d1..af67f3de 100644 --- a/app/Api/Procedure/TaskProcedure.php +++ b/app/Api/Procedure/TaskProcedure.php @@ -87,9 +87,9 @@ class TaskProcedure extends BaseProcedure } 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, $priority = 0, - $recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0, - $recurrence_basedate = 0, $reference = '') + $date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0, $priority = 0, + $recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0, + $recurrence_basedate = 0, $reference = '', array $tags = array()) { ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTask', $project_id); @@ -120,6 +120,7 @@ class TaskProcedure extends BaseProcedure 'recurrence_basedate' => $recurrence_basedate, 'reference' => $reference, 'priority' => $priority, + 'tags' => $tags, ); list($valid, ) = $this->taskValidator->validateCreation($values); @@ -128,9 +129,9 @@ class TaskProcedure extends BaseProcedure } public function updateTask($id, $title = null, $color_id = null, $owner_id = null, - $date_due = null, $description = null, $category_id = null, $score = null, $priority = null, - $recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null, - $recurrence_timeframe = null, $recurrence_basedate = null, $reference = null) + $date_due = null, $description = null, $category_id = null, $score = null, $priority = null, + $recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null, + $recurrence_timeframe = null, $recurrence_basedate = null, $reference = null, $tags = null) { TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $id); $project_id = $this->taskFinderModel->getProjectId($id); @@ -159,6 +160,7 @@ class TaskProcedure extends BaseProcedure 'recurrence_basedate' => $recurrence_basedate, 'reference' => $reference, 'priority' => $priority, + 'tags' => $tags, )); list($valid) = $this->taskValidator->validateApiModification($values); diff --git a/app/Api/Procedure/TaskTagProcedure.php b/app/Api/Procedure/TaskTagProcedure.php new file mode 100644 index 00000000..8596f507 --- /dev/null +++ b/app/Api/Procedure/TaskTagProcedure.php @@ -0,0 +1,26 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\TaskAuthorization; + +/** + * Class TaskTagProcedure + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class TaskTagProcedure extends BaseProcedure +{ + public function setTaskTags($project_id, $task_id, array $tags) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setTaskTags', $task_id); + return $this->taskTagModel->save($project_id, $task_id, $tags); + } + + public function getTaskTags($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskTags', $task_id); + return $this->taskTagModel->getList($task_id); + } +} diff --git a/app/Auth/ReverseProxyAuth.php b/app/Auth/ReverseProxyAuth.php index 02afc302..bf71e71e 100644 --- a/app/Auth/ReverseProxyAuth.php +++ b/app/Auth/ReverseProxyAuth.php @@ -45,7 +45,7 @@ class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterfac $username = $this->request->getRemoteUser(); if (! empty($username)) { - $userProfile = $this->userModel->getByUsername($username); + $userProfile = $this->userCacheDecorator->getByUsername($username); $this->userInfo = new ReverseProxyUserProvider($username, $userProfile ?: array()); return true; } diff --git a/app/Controller/PasswordResetController.php b/app/Controller/PasswordResetController.php index a1780ed9..6189f946 100644 --- a/app/Controller/PasswordResetController.php +++ b/app/Controller/PasswordResetController.php @@ -109,7 +109,7 @@ class PasswordResetController extends BaseController $token = $this->passwordResetModel->create($username); if ($token !== false) { - $user = $this->userModel->getByUsername($username); + $user = $this->userCacheDecorator->getByUsername($username); $this->emailClient->send( $user['email'], diff --git a/app/Controller/ProjectFileController.php b/app/Controller/ProjectFileController.php index cbe48679..9c38f684 100644 --- a/app/Controller/ProjectFileController.php +++ b/app/Controller/ProjectFileController.php @@ -21,7 +21,7 @@ class ProjectFileController extends BaseController $this->response->html($this->template->render('project_file/create', array( 'project' => $project, - 'max_size' => $this->helper->text->phpToBytes(ini_get('upload_max_filesize')), + 'max_size' => $this->helper->text->phpToBytes(get_upload_max_size()), ))); } diff --git a/app/Controller/TaskFileController.php b/app/Controller/TaskFileController.php index 77c0c026..8a0971e4 100644 --- a/app/Controller/TaskFileController.php +++ b/app/Controller/TaskFileController.php @@ -40,7 +40,7 @@ class TaskFileController extends BaseController $this->response->html($this->template->render('task_file/create', array( 'task' => $task, - 'max_size' => $this->helper->text->phpToBytes(ini_get('upload_max_filesize')), + 'max_size' => $this->helper->text->phpToBytes(get_upload_max_size()), ))); } diff --git a/app/Controller/TaskImportController.php b/app/Controller/TaskImportController.php index aff2d390..3ce275a5 100644 --- a/app/Controller/TaskImportController.php +++ b/app/Controller/TaskImportController.php @@ -27,7 +27,7 @@ class TaskImportController extends BaseController 'project' => $project, 'values' => $values, 'errors' => $errors, - 'max_size' => ini_get('upload_max_filesize'), + 'max_size' => get_upload_max_size(), 'delimiters' => Csv::getDelimiters(), 'enclosures' => Csv::getEnclosures(), 'title' => t('Import tasks from CSV file'), diff --git a/app/Controller/UserImportController.php b/app/Controller/UserImportController.php index fec9a31d..6a9d5992 100644 --- a/app/Controller/UserImportController.php +++ b/app/Controller/UserImportController.php @@ -23,7 +23,7 @@ class UserImportController extends BaseController $this->response->html($this->template->render('user_import/show', array( 'values' => $values, 'errors' => $errors, - 'max_size' => ini_get('upload_max_filesize'), + 'max_size' => get_upload_max_size(), 'delimiters' => Csv::getDelimiters(), 'enclosures' => Csv::getEnclosures(), ))); diff --git a/app/Core/Base.php b/app/Core/Base.php index e7ccafaa..881cccbd 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -58,7 +58,8 @@ use Pimple\Container; * @property \Kanboard\Core\Paginator $paginator * @property \Kanboard\Core\Template $template * @property \Kanboard\Decorator\MetadataCacheDecorator $userMetadataCacheDecorator - * @property \Kanboard\Decorator\columnRestrictionCacheDecorator $columnRestrictionCacheDecorator + * @property \Kanboard\Decorator\UserCacheDecorator $userCacheDecorator + * @property \Kanboard\Decorator\ColumnRestrictionCacheDecorator $columnRestrictionCacheDecorator * @property \Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator $columnMoveRestrictionCacheDecorator * @property \Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator $projectRoleRestrictionCacheDecorator * @property \Kanboard\Model\ActionModel $actionModel diff --git a/app/Core/Markdown.php b/app/Core/Markdown.php index 799aefb4..4487bf2a 100644 --- a/app/Core/Markdown.php +++ b/app/Core/Markdown.php @@ -86,18 +86,23 @@ class Markdown extends Parsedown */ protected function inlineUserLink(array $Excerpt) { - if (! $this->isPublicLink && preg_match('/^@([^\s,!.:?]+)/', $Excerpt['text'], $matches)) { - $user_id = $this->container['userModel']->getIdByUsername($matches[1]); + if (! $this->isPublicLink && preg_match('/^@([^\s,!:?]+)/', $Excerpt['text'], $matches)) { + $username = rtrim($matches[1], '.'); + $user = $this->container['userCacheDecorator']->getByUsername($username); - if (! empty($user_id)) { - $url = $this->container['helper']->url->href('UserViewController', 'profile', array('user_id' => $user_id)); + if (! empty($user)) { + $url = $this->container['helper']->url->href('UserViewController', 'profile', array('user_id' => $user['id'])); return array( - 'extent' => strlen($matches[0]), + 'extent' => strlen($username) + 1, 'element' => array( - 'name' => 'a', - 'text' => $matches[0], - 'attributes' => array('href' => $url, 'class' => 'user-mention-link'), + 'name' => 'a', + 'text' => '@' . $username, + 'attributes' => array( + 'href' => $url, + 'class' => 'user-mention-link', + 'title' => $user['name'] ?: $user['username'], + ), ), ); } diff --git a/app/Decorator/UserCacheDecorator.php b/app/Decorator/UserCacheDecorator.php new file mode 100644 index 00000000..1cfe31c9 --- /dev/null +++ b/app/Decorator/UserCacheDecorator.php @@ -0,0 +1,59 @@ +<?php + +namespace Kanboard\Decorator; + +use Kanboard\Core\Cache\CacheInterface; +use Kanboard\Model\UserModel; + +/** + * Class UserCacheDecorator + * + * @package Kanboard\Decorator + * @author Frederic Guillot + */ +class UserCacheDecorator +{ + protected $cachePrefix = 'user_model:'; + + /** + * @var CacheInterface + */ + protected $cache; + + /** + * @var UserModel + */ + private $userModel; + + /** + * UserCacheDecorator constructor. + * + * @param CacheInterface $cache + * @param UserModel $userModel + */ + public function __construct(CacheInterface $cache, UserModel $userModel) + { + $this->cache = $cache; + $this->userModel = $userModel; + } + + /** + * Get a specific user by the username + * + * @access public + * @param string $username Username + * @return array + */ + public function getByUsername($username) + { + $key = $this->cachePrefix.$username; + $user = $this->cache->get($key); + + if ($user === null) { + $user = $this->userModel->getByUsername($username); + $this->cache->set($key, $user); + } + + return $user; + } +} diff --git a/app/Job/UserMentionJob.php b/app/Job/UserMentionJob.php index bbb27131..355095bb 100644 --- a/app/Job/UserMentionJob.php +++ b/app/Job/UserMentionJob.php @@ -58,7 +58,8 @@ class UserMentionJob extends BaseJob { $users = array(); - if (preg_match_all('/@([^\s,!.:?]+)/', $text, $matches)) { + if (preg_match_all('/@([^\s,!:?]+)/', $text, $matches)) { + array_walk($matches[1], function (&$username) { $username = rtrim($username, '.'); }); $users = $this->db->table(UserModel::TABLE) ->columns('id', 'username', 'name', 'email', 'language') ->eq('notifications_enabled', 1) diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index d5d1f260..2c9abec7 100644 --- a/app/ServiceProvider/ApiProvider.php +++ b/app/ServiceProvider/ApiProvider.php @@ -10,6 +10,7 @@ use Kanboard\Api\Procedure\CategoryProcedure; use Kanboard\Api\Procedure\ColumnProcedure; use Kanboard\Api\Procedure\CommentProcedure; use Kanboard\Api\Procedure\ProjectFileProcedure; +use Kanboard\Api\Procedure\TagProcedure; use Kanboard\Api\Procedure\TaskExternalLinkProcedure; use Kanboard\Api\Procedure\TaskFileProcedure; use Kanboard\Api\Procedure\GroupProcedure; @@ -25,6 +26,7 @@ use Kanboard\Api\Procedure\SwimlaneProcedure; use Kanboard\Api\Procedure\TaskMetadataProcedure; use Kanboard\Api\Procedure\TaskProcedure; use Kanboard\Api\Procedure\TaskLinkProcedure; +use Kanboard\Api\Procedure\TaskTagProcedure; use Kanboard\Api\Procedure\UserProcedure; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -71,9 +73,11 @@ class ApiProvider implements ServiceProviderInterface ->withObject(new TaskLinkProcedure($container)) ->withObject(new TaskExternalLinkProcedure($container)) ->withObject(new TaskMetadataProcedure($container)) + ->withObject(new TaskTagProcedure($container)) ->withObject(new UserProcedure($container)) ->withObject(new GroupProcedure($container)) ->withObject(new GroupMemberProcedure($container)) + ->withObject(new TagProcedure($container)) ->withBeforeMethod('beforeProcedure') ; diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index c2dad0e6..80882456 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -136,7 +136,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('BoardViewController', 'readonly', Role::APP_PUBLIC); $acl->add('ICalendarController', '*', Role::APP_PUBLIC); $acl->add('FeedController', '*', Role::APP_PUBLIC); - $acl->add('AvatarFileController', 'show', Role::APP_PUBLIC); + $acl->add('AvatarFileController', array('show', 'image'), Role::APP_PUBLIC); $acl->add('ConfigController', '*', Role::APP_ADMIN); $acl->add('TagController', '*', Role::APP_ADMIN); @@ -210,6 +210,8 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('TaskLinkProcedure', '*', Role::PROJECT_MEMBER); $acl->add('TaskExternalLinkProcedure', array('createExternalTaskLink', 'updateExternalTaskLink', 'removeExternalTaskLink'), Role::PROJECT_MEMBER); $acl->add('TaskProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskTagProcedure', array('setTaskTags'), Role::PROJECT_MEMBER); + $acl->add('TagProcedure', array('createTag', 'updateTag', 'removeTag'), Role::PROJECT_MEMBER); return $acl; } diff --git a/app/ServiceProvider/CacheProvider.php b/app/ServiceProvider/CacheProvider.php index e93dd502..af8a8e7a 100644 --- a/app/ServiceProvider/CacheProvider.php +++ b/app/ServiceProvider/CacheProvider.php @@ -8,6 +8,7 @@ use Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator; use Kanboard\Decorator\ColumnRestrictionCacheDecorator; use Kanboard\Decorator\MetadataCacheDecorator; use Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator; +use Kanboard\Decorator\UserCacheDecorator; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -40,6 +41,13 @@ class CacheProvider implements ServiceProviderInterface $container['cacheDriver'] = $container['memoryCache']; } + $container['userCacheDecorator'] = function($c) { + return new UserCacheDecorator( + $c['memoryCache'], + $c['userModel'] + ); + }; + $container['userMetadataCacheDecorator'] = function($c) { return new MetadataCacheDecorator( $c['cacheDriver'], diff --git a/app/functions.php b/app/functions.php index e732f308..9dd054fb 100644 --- a/app/functions.php +++ b/app/functions.php @@ -136,6 +136,16 @@ function build_app_version($ref, $commit_hash) } /** + * Get upload max size. + * + * @return string + */ +function get_upload_max_size() +{ + return min(ini_get('upload_max_filesize'), ini_get('post_max_size')); +} + +/** * Translate a string * * @return string |