From f9753e91d288c4d87d6a83ffe994d312eae5a3fd Mon Sep 17 00:00:00 2001
From: Frédéric Guillot
Date: Sun, 25 May 2014 15:02:27 -0400
Subject: Add subtasks
---
app/Templates/subtask_edit.php | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 app/Templates/subtask_edit.php
(limited to 'app/Templates/subtask_edit.php')
diff --git a/app/Templates/subtask_edit.php b/app/Templates/subtask_edit.php
new file mode 100644
index 00000000..3080cdad
--- /dev/null
+++ b/app/Templates/subtask_edit.php
@@ -0,0 +1,30 @@
+
+
+
--
cgit v1.2.3
From 445ef6d1481745cd4e7af7e671f534a25d4495dc Mon Sep 17 00:00:00 2001
From: Frédéric Guillot
Date: Wed, 28 May 2014 15:14:52 -0400
Subject: Add CSRF protections
---
app/Controller/Action.php | 1 +
app/Controller/Base.php | 25 +++++++++-
app/Controller/Board.php | 76 ++++++++++++++++------------
app/Controller/Category.php | 1 +
app/Controller/Comment.php | 1 +
app/Controller/Config.php | 4 ++
app/Controller/File.php | 1 +
app/Controller/Project.php | 18 ++-----
app/Controller/Subtask.php | 1 +
app/Controller/Task.php | 3 ++
app/Controller/User.php | 20 +++-----
app/Core/Request.php | 24 ++++++++-
app/Core/Response.php | 4 +-
app/Core/Security.php | 87 +++++++++++++++++++++++++++++++++
app/Model/Base.php | 19 -------
app/Model/Config.php | 7 +--
app/Model/Project.php | 3 +-
app/Model/RememberMe.php | 8 +--
app/Schema/Mysql.php | 2 +-
app/Schema/Sqlite.php | 4 +-
app/Templates/action_index.php | 2 +-
app/Templates/action_params.php | 2 +-
app/Templates/action_remove.php | 2 +-
app/Templates/app_forbidden.php | 9 ++++
app/Templates/board_assign.php | 2 +-
app/Templates/board_edit.php | 8 +--
app/Templates/board_remove.php | 2 +-
app/Templates/board_show.php | 2 +-
app/Templates/category_edit.php | 2 +-
app/Templates/category_index.php | 1 +
app/Templates/category_remove.php | 2 +-
app/Templates/comment_create.php | 2 +-
app/Templates/comment_edit.php | 1 +
app/Templates/comment_remove.php | 2 +-
app/Templates/config_index.php | 10 ++--
app/Templates/file_new.php | 1 +
app/Templates/file_remove.php | 2 +-
app/Templates/layout.php | 2 +-
app/Templates/project_edit.php | 1 +
app/Templates/project_forbidden.php | 9 ----
app/Templates/project_index.php | 4 +-
app/Templates/project_new.php | 1 +
app/Templates/project_remove.php | 2 +-
app/Templates/project_users.php | 4 +-
app/Templates/subtask_create.php | 2 +
app/Templates/subtask_edit.php | 2 +
app/Templates/subtask_remove.php | 2 +-
app/Templates/task_close.php | 2 +-
app/Templates/task_edit.php | 2 +
app/Templates/task_edit_description.php | 2 +
app/Templates/task_new.php | 2 +
app/Templates/task_open.php | 2 +-
app/Templates/task_remove.php | 2 +-
app/Templates/user_edit.php | 6 ++-
app/Templates/user_forbidden.php | 9 ----
app/Templates/user_login.php | 2 +
app/Templates/user_new.php | 2 +
app/Templates/user_remove.php | 2 +-
app/helpers.php | 10 ++++
assets/js/board.js | 4 +-
tests/Base.php | 2 +
61 files changed, 299 insertions(+), 140 deletions(-)
create mode 100644 app/Core/Security.php
create mode 100644 app/Templates/app_forbidden.php
delete mode 100644 app/Templates/project_forbidden.php
delete mode 100644 app/Templates/user_forbidden.php
(limited to 'app/Templates/subtask_edit.php')
diff --git a/app/Controller/Action.php b/app/Controller/Action.php
index 2aa85c14..11dc3b29 100644
--- a/app/Controller/Action.php
+++ b/app/Controller/Action.php
@@ -129,6 +129,7 @@ class Action extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$action = $this->action->getById($this->request->getIntegerParam('action_id'));
if ($action && $this->action->remove($action['id'])) {
diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index 5829fc36..9b695a82 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -3,6 +3,7 @@
namespace Controller;
use Core\Registry;
+use Core\Security;
use Core\Translator;
use Model\LastLogin;
@@ -160,6 +161,28 @@ abstract class Base
$this->response->html($this->template->layout('app_notfound', array('title' => t('Page not found'))));
}
+ /**
+ * Application forbidden page
+ *
+ * @access public
+ */
+ public function forbidden()
+ {
+ $this->response->html($this->template->layout('app_forbidden', array('title' => t('Access Forbidden'))));
+ }
+
+ /**
+ * Check if the CSRF token from the URL is correct
+ *
+ * @access protected
+ */
+ protected function checkCSRFParam()
+ {
+ if (! Security::validateCSRFToken($this->request->getStringParam('csrf_token'))) {
+ $this->forbidden();
+ }
+ }
+
/**
* Check if the current user have access to the given project
*
@@ -171,7 +194,7 @@ abstract class Base
if ($this->acl->isRegularUser()) {
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->redirect('?controller=project&action=forbidden');
+ $this->forbidden();
}
}
}
diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index 53fdeab9..67072895 100644
--- a/app/Controller/Board.php
+++ b/app/Controller/Board.php
@@ -4,6 +4,7 @@ namespace Controller;
use Model\Project as ProjectModel;
use Model\User as UserModel;
+use Core\Security;
/**
* Board controller
@@ -20,6 +21,7 @@ class Board extends Base
*/
public function moveUp()
{
+ $this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$column_id = $this->request->getIntegerParam('column_id');
@@ -35,6 +37,7 @@ class Board extends Base
*/
public function moveDown()
{
+ $this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$column_id = $this->request->getIntegerParam('column_id');
@@ -344,6 +347,7 @@ class Board extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
if ($column && $this->board->removeColumn($column['id'])) {
@@ -362,25 +366,31 @@ class Board extends Base
*/
public function save()
{
- $project_id = $this->request->getIntegerParam('project_id');
- $values = $this->request->getValues();
+ if ($this->request->isAjax()) {
- if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->text('Not Authorized', 401);
- }
+ $project_id = $this->request->getIntegerParam('project_id');
+ $values = $this->request->getValues();
- if (isset($values['positions'])) {
- $this->board->saveTasksPosition($values['positions']);
- }
+ if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ if (isset($values['positions'])) {
+ $this->board->saveTasksPosition($values['positions']);
+ }
- $this->response->html(
- $this->template->load('board_show', array(
- 'current_project_id' => $project_id,
- 'board' => $this->board->get($project_id),
- 'categories' => $this->category->getList($project_id, false),
- )),
- 201
- );
+ $this->response->html(
+ $this->template->load('board_show', array(
+ 'current_project_id' => $project_id,
+ 'board' => $this->board->get($project_id),
+ 'categories' => $this->category->getList($project_id, false),
+ )),
+ 201
+ );
+ }
+ else {
+ $this->response->status(401);
+ }
}
/**
@@ -390,24 +400,30 @@ class Board extends Base
*/
public function check()
{
- $project_id = $this->request->getIntegerParam('project_id');
- $timestamp = $this->request->getIntegerParam('timestamp');
+ if ($this->request->isAjax()) {
- if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->text('Not Authorized', 401);
- }
+ $project_id = $this->request->getIntegerParam('project_id');
+ $timestamp = $this->request->getIntegerParam('timestamp');
- if ($this->project->isModifiedSince($project_id, $timestamp)) {
- $this->response->html(
- $this->template->load('board_show', array(
- 'current_project_id' => $project_id,
- 'board' => $this->board->get($project_id),
- 'categories' => $this->category->getList($project_id, false),
- ))
- );
+ if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ if ($this->project->isModifiedSince($project_id, $timestamp)) {
+ $this->response->html(
+ $this->template->load('board_show', array(
+ 'current_project_id' => $project_id,
+ 'board' => $this->board->get($project_id),
+ 'categories' => $this->category->getList($project_id, false),
+ ))
+ );
+ }
+ else {
+ $this->response->status(304);
+ }
}
else {
- $this->response->status(304);
+ $this->response->status(401);
}
}
}
diff --git a/app/Controller/Category.php b/app/Controller/Category.php
index f96c1d4a..9b73f207 100644
--- a/app/Controller/Category.php
+++ b/app/Controller/Category.php
@@ -175,6 +175,7 @@ class Category extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$project = $this->getProject();
$category = $this->getCategory($project['id']);
diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php
index 47eaf6b6..a0a11fc8 100644
--- a/app/Controller/Comment.php
+++ b/app/Controller/Comment.php
@@ -178,6 +178,7 @@ class Comment extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$task = $this->getTask();
$comment = $this->getComment();
diff --git a/app/Controller/Config.php b/app/Controller/Config.php
index b4a5b8d3..daa57790 100644
--- a/app/Controller/Config.php
+++ b/app/Controller/Config.php
@@ -76,6 +76,7 @@ class Config extends Base
*/
public function downloadDb()
{
+ $this->checkCSRFParam();
$this->response->forceDownload('db.sqlite.gz');
$this->response->binary($this->config->downloadDatabase());
}
@@ -87,6 +88,7 @@ class Config extends Base
*/
public function optimizeDb()
{
+ $this->checkCSRFParam();
$this->config->optimizeDatabase();
$this->session->flash(t('Database optimization done.'));
$this->response->redirect('?controller=config');
@@ -99,6 +101,7 @@ class Config extends Base
*/
public function tokens()
{
+ $this->checkCSRFParam();
$this->config->regenerateTokens();
$this->session->flash(t('All tokens have been regenerated.'));
$this->response->redirect('?controller=config');
@@ -111,6 +114,7 @@ class Config extends Base
*/
public function removeRememberMeToken()
{
+ $this->checkCSRFParam();
$this->rememberMe->remove($this->request->getIntegerParam('id'));
$this->response->redirect('?controller=config&action=index#remember-me');
}
diff --git a/app/Controller/File.php b/app/Controller/File.php
index 38cb82ab..3c8c32d1 100644
--- a/app/Controller/File.php
+++ b/app/Controller/File.php
@@ -111,6 +111,7 @@ class File extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
diff --git a/app/Controller/Project.php b/app/Controller/Project.php
index e539f364..0de67691 100644
--- a/app/Controller/Project.php
+++ b/app/Controller/Project.php
@@ -12,19 +12,6 @@ use Model\Task as TaskModel;
*/
class Project extends Base
{
- /**
- * Display access forbidden page
- *
- * @access public
- */
- public function forbidden()
- {
- $this->response->html($this->template->layout('project_forbidden', array(
- 'menu' => 'projects',
- 'title' => t('Access Forbidden')
- )));
- }
-
/**
* Task search for a given project
*
@@ -254,6 +241,7 @@ class Project extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
if ($project_id && $this->project->remove($project_id)) {
@@ -272,6 +260,7 @@ class Project extends Base
*/
public function enable()
{
+ $this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
if ($project_id && $this->project->enable($project_id)) {
@@ -290,6 +279,7 @@ class Project extends Base
*/
public function disable()
{
+ $this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
if ($project_id && $this->project->disable($project_id)) {
@@ -353,6 +343,8 @@ class Project extends Base
*/
public function revoke()
{
+ $this->checkCSRFParam();
+
$values = array(
'project_id' => $this->request->getIntegerParam('project_id'),
'user_id' => $this->request->getIntegerParam('user_id'),
diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php
index 5ef193c8..1c217fa2 100644
--- a/app/Controller/Subtask.php
+++ b/app/Controller/Subtask.php
@@ -170,6 +170,7 @@ class Subtask extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$task = $this->getTask();
$subtask = $this->getSubtask();
diff --git a/app/Controller/Task.php b/app/Controller/Task.php
index 68e3728a..d44ba268 100644
--- a/app/Controller/Task.php
+++ b/app/Controller/Task.php
@@ -218,6 +218,7 @@ class Task extends Base
*/
public function close()
{
+ $this->checkCSRFParam();
$task = $this->getTask();
if ($this->task->close($task['id'])) {
@@ -252,6 +253,7 @@ class Task extends Base
*/
public function open()
{
+ $this->checkCSRFParam();
$task = $this->getTask();
if ($this->task->open($task['id'])) {
@@ -286,6 +288,7 @@ class Task extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$task = $this->getTask();
if ($this->task->remove($task['id'])) {
diff --git a/app/Controller/User.php b/app/Controller/User.php
index e3fd8253..fca33b28 100644
--- a/app/Controller/User.php
+++ b/app/Controller/User.php
@@ -10,19 +10,6 @@ namespace Controller;
*/
class User extends Base
{
- /**
- * Display access forbidden page
- *
- * @access public
- */
- public function forbidden()
- {
- $this->response->html($this->template->layout('user_forbidden', array(
- 'menu' => 'users',
- 'title' => t('Access Forbidden')
- )));
- }
-
/**
* Logout and destroy session
*
@@ -30,6 +17,7 @@ class User extends Base
*/
public function logout()
{
+ $this->checkCSRFParam();
$this->rememberMe->destroy($this->acl->getUserId());
$this->session->close();
$this->response->redirect('?controller=user&action=login');
@@ -42,7 +30,9 @@ class User extends Base
*/
public function login()
{
- if (isset($_SESSION['user'])) $this->response->redirect('?controller=app');
+ if (isset($_SESSION['user'])) {
+ $this->response->redirect('?controller=app');
+ }
$this->response->html($this->template->layout('user_login', array(
'errors' => array(),
@@ -236,6 +226,7 @@ class User extends Base
*/
public function remove()
{
+ $this->checkCSRFParam();
$user_id = $this->request->getIntegerParam('user_id');
if ($user_id && $this->user->remove($user_id)) {
@@ -298,6 +289,7 @@ class User extends Base
*/
public function unlinkGoogle()
{
+ $this->checkCSRFParam();
if ($this->google->unlink($this->acl->getUserId())) {
$this->session->flash(t('Your Google Account is not linked anymore to your profile.'));
}
diff --git a/app/Core/Request.php b/app/Core/Request.php
index 7e9f24ac..6bc738be 100644
--- a/app/Core/Request.php
+++ b/app/Core/Request.php
@@ -2,6 +2,8 @@
namespace Core;
+use Core\Security;
+
/**
* Request class
*
@@ -58,7 +60,12 @@ class Request
public function getValues()
{
if (! empty($_POST)) {
- return $_POST;
+
+ if (Security::validateCSRFFormToken($_POST)) {
+ return $_POST;
+ }
+
+ return array();
}
$result = json_decode($this->getBody(), true);
@@ -116,6 +123,19 @@ class Request
*/
public function isAjax()
{
- return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
+ return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
+ }
+
+ /**
+ * Return a HTTP header value
+ *
+ * @access public
+ * @param string $name Header name
+ * @return string
+ */
+ public function getHeader($name)
+ {
+ $name = 'HTTP_'.str_replace('-', '_', strtoupper($name));
+ return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
}
}
diff --git a/app/Core/Response.php b/app/Core/Response.php
index 11d7567a..aee029af 100644
--- a/app/Core/Response.php
+++ b/app/Core/Response.php
@@ -18,8 +18,10 @@ class Response
public function nocache()
{
header('Pragma: no-cache');
- header('Cache-Control: no-cache, must-revalidate');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
+
+ // Use no-store due to a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=28035
+ header('Cache-Control: no-store, must-revalidate');
}
/**
diff --git a/app/Core/Security.php b/app/Core/Security.php
new file mode 100644
index 00000000..0bd7c991
--- /dev/null
+++ b/app/Core/Security.php
@@ -0,0 +1,87 @@
+db = $db;
$this->event = $event;
}
-
- /**
- * Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid()
- *
- * @static
- * @access public
- * @return string Random token
- */
- public static function generateToken()
- {
- if (function_exists('openssl_random_pseudo_bytes')) {
- return bin2hex(\openssl_random_pseudo_bytes(16));
- }
- else if (ini_get('open_basedir') === '' && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
- return hash('sha256', file_get_contents('/dev/urandom', false, null, 0, 30));
- }
-
- return hash('sha256', uniqid(mt_rand(), true));
- }
}
diff --git a/app/Model/Config.php b/app/Model/Config.php
index 23abd8b5..469e6447 100644
--- a/app/Model/Config.php
+++ b/app/Model/Config.php
@@ -5,6 +5,7 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Core\Translator;
+use Core\Security;
/**
* Config model
@@ -29,7 +30,7 @@ class Config extends Base
*/
public function getTimezones()
{
- $timezones = \timezone_identifiers_list();
+ $timezones = timezone_identifiers_list();
return array_combine(array_values($timezones), $timezones);
}
@@ -171,12 +172,12 @@ class Config extends Base
*/
public function regenerateTokens()
{
- $this->db->table(self::TABLE)->update(array('webhooks_token' => $this->generateToken()));
+ $this->db->table(self::TABLE)->update(array('webhooks_token' => Security::generateToken()));
$projects = $this->db->table(Project::TABLE)->findAllByColumn('id');
foreach ($projects as $project_id) {
- $this->db->table(Project::TABLE)->eq('id', $project_id)->update(array('token' => $this->generateToken()));
+ $this->db->table(Project::TABLE)->eq('id', $project_id)->update(array('token' => Security::generateToken()));
}
}
}
diff --git a/app/Model/Project.php b/app/Model/Project.php
index 9fbb0806..e1465012 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -5,6 +5,7 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Event\TaskModification;
+use Core\Security;
/**
* Project model
@@ -363,7 +364,7 @@ class Project extends Base
{
$this->db->startTransaction();
- $values['token'] = self::generateToken();
+ $values['token'] = Security::generateToken();
if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
diff --git a/app/Model/RememberMe.php b/app/Model/RememberMe.php
index 1494b14a..c9ef819f 100644
--- a/app/Model/RememberMe.php
+++ b/app/Model/RememberMe.php
@@ -2,6 +2,8 @@
namespace Model;
+use Core\Security;
+
/**
* RememberMe model
*
@@ -174,8 +176,8 @@ class RememberMe extends Base
*/
public function create($user_id, $ip, $user_agent)
{
- $token = hash('sha256', $user_id.$user_agent.$ip.$this->generateToken());
- $sequence = $this->generateToken();
+ $token = hash('sha256', $user_id.$user_agent.$ip.Security::generateToken());
+ $sequence = Security::generateToken();
$expiration = time() + self::EXPIRATION;
$this->cleanup($user_id);
@@ -225,7 +227,7 @@ class RememberMe extends Base
*/
public function update($token, $sequence)
{
- $new_sequence = $this->generateToken();
+ $new_sequence = Security::generateToken();
$this->db
->table(self::TABLE)
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 3df38dee..5c7e256f 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -263,6 +263,6 @@ function version_1($pdo)
$pdo->exec("
INSERT INTO config
(webhooks_token)
- VALUES ('".\Model\Base::generateToken()."')
+ VALUES ('".\Core\Security::generateToken()."')
");
}
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index 663c7d34..bfe81c13 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -209,7 +209,7 @@ function version_3($pdo)
foreach ($results as &$result) {
$rq = $pdo->prepare('UPDATE projects SET token=? WHERE id=?');
- $rq->execute(array(\Model\Base::generateToken(), $result['id']));
+ $rq->execute(array(\Core\Security::generateToken(), $result['id']));
}
}
}
@@ -284,6 +284,6 @@ function version_1($pdo)
$pdo->exec("
INSERT INTO config
(language, webhooks_token)
- VALUES ('en_US', '".\Model\Base::generateToken()."')
+ VALUES ('en_US', '".\Core\Security::generateToken()."')
");
}
diff --git a/app/Templates/action_index.php b/app/Templates/action_index.php
index b515ccaa..36c333a9 100644
--- a/app/Templates/action_index.php
+++ b/app/Templates/action_index.php
@@ -56,7 +56,7 @@
= t('Add an action') ?>
diff --git a/app/Templates/app_forbidden.php b/app/Templates/app_forbidden.php
new file mode 100644
index 00000000..0c035404
--- /dev/null
+++ b/app/Templates/app_forbidden.php
@@ -0,0 +1,9 @@
+
+
+
+
+ = t('Access Forbidden') ?>
+
+
\ No newline at end of file
diff --git a/app/Templates/board_assign.php b/app/Templates/board_assign.php
index 74448a5c..6f92b375 100644
--- a/app/Templates/board_assign.php
+++ b/app/Templates/board_assign.php
@@ -18,7 +18,7 @@
= t('Change assignee for the task "%s"', $values['title']) ?>