diff options
24 files changed, 308 insertions, 284 deletions
diff --git a/app/Auth/RememberMe.php b/app/Auth/RememberMe.php index 0290e36c..fd8ed8bb 100644 --- a/app/Auth/RememberMe.php +++ b/app/Auth/RememberMe.php @@ -3,9 +3,9 @@ namespace Kanboard\Auth; use Kanboard\Core\Base; -use Kanboard\Core\Request; +use Kanboard\Core\Http\Request; use Kanboard\Event\AuthEvent; -use Kanboard\Core\Security; +use Kanboard\Core\Security\Token; /** * RememberMe model @@ -165,8 +165,8 @@ class RememberMe extends Base */ public function create($user_id, $ip, $user_agent) { - $token = hash('sha256', $user_id.$user_agent.$ip.Security::generateToken()); - $sequence = Security::generateToken(); + $token = hash('sha256', $user_id.$user_agent.$ip.Token::getToken()); + $sequence = Token::getToken(); $expiration = time() + self::EXPIRATION; $this->cleanup($user_id); @@ -216,7 +216,7 @@ class RememberMe extends Base */ public function update($token) { - $new_sequence = Security::generateToken(); + $new_sequence = Token::getToken(); $this->db ->table(self::TABLE) diff --git a/app/Controller/Base.php b/app/Controller/Base.php index a955b12c..829e0ad2 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -3,9 +3,6 @@ namespace Kanboard\Controller; use Pimple\Container; -use Kanboard\Core\Security; -use Kanboard\Core\Request; -use Kanboard\Core\Response; use Symfony\Component\EventDispatcher\Event; /** @@ -17,22 +14,6 @@ use Symfony\Component\EventDispatcher\Event; abstract class Base extends \Kanboard\Core\Base { /** - * Request instance - * - * @accesss protected - * @var \Kanboard\Core\Request - */ - protected $request; - - /** - * Response instance - * - * @accesss protected - * @var \Kanboard\Core\Response - */ - protected $response; - - /** * Constructor * * @access public @@ -41,11 +22,9 @@ abstract class Base extends \Kanboard\Core\Base public function __construct(Container $container) { $this->container = $container; - $this->request = new Request; - $this->response = new Response; if (DEBUG) { - $this->container['logger']->debug('START_REQUEST='.$_SERVER['REQUEST_URI']); + $this->logger->debug('START_REQUEST='.$_SERVER['REQUEST_URI']); } } @@ -57,14 +36,14 @@ abstract class Base extends \Kanboard\Core\Base public function __destruct() { if (DEBUG) { - foreach ($this->container['db']->getLogMessages() as $message) { - $this->container['logger']->debug($message); + foreach ($this->db->getLogMessages() as $message) { + $this->logger->debug($message); } - $this->container['logger']->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nbQueries)); - $this->container['logger']->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT'])); - $this->container['logger']->debug('MEMORY='.$this->helper->text->bytes(memory_get_usage())); - $this->container['logger']->debug('END_REQUEST='.$_SERVER['REQUEST_URI']); + $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']); } } @@ -201,7 +180,7 @@ abstract class Base extends \Kanboard\Core\Base */ protected function checkCSRFParam() { - if (! Security::validateCSRFToken($this->request->getStringParam('csrf_token'))) { + if (! $this->token->validateCSRFToken($this->request->getStringParam('csrf_token'))) { $this->forbidden(); } } diff --git a/app/Core/Base.php b/app/Core/Base.php index d402fb37..11f4e31b 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -12,18 +12,20 @@ use Pimple\Container; * * @property \Kanboard\Core\Helper $helper * @property \Kanboard\Core\Mail\Client $emailClient - * @property \Kanboard\Core\HttpClient $httpClient * @property \Kanboard\Core\Paginator $paginator - * @property \Kanboard\Core\Request $request + * @property \Kanboard\Core\Http\Client $httpClient + * @property \Kanboard\Core\Http\Request $request + * @property \Kanboard\Core\Http\Router $router + * @property \Kanboard\Core\Http\Response $response * @property \Kanboard\Core\Session $session * @property \Kanboard\Core\Template $template * @property \Kanboard\Core\OAuth2 $oauth - * @property \Kanboard\Core\Router $router * @property \Kanboard\Core\Lexer $lexer * @property \Kanboard\Core\ObjectStorage\ObjectStorageInterface $objectStorage * @property \Kanboard\Core\Cache\Cache $memoryCache * @property \Kanboard\Core\Plugin\Hook $hook * @property \Kanboard\Core\Plugin\Loader $pluginLoader + * @property \Kanboard\Core\Security\Token $token * @property \Kanboard\Integration\BitbucketWebhook $bitbucketWebhook * @property \Kanboard\Integration\GithubWebhook $githubWebhook * @property \Kanboard\Integration\GitlabWebhook $gitlabWebhook diff --git a/app/Core/HttpClient.php b/app/Core/Http/Client.php index 7f4ea47a..c6bf36a6 100644 --- a/app/Core/HttpClient.php +++ b/app/Core/Http/Client.php @@ -1,14 +1,16 @@ <?php -namespace Kanboard\Core; +namespace Kanboard\Core\Http; + +use Kanboard\Core\Base; /** * HTTP client * - * @package core + * @package http * @author Frederic Guillot */ -class HttpClient extends Base +class Client extends Base { /** * HTTP connection timeout in seconds @@ -99,6 +101,36 @@ class HttpClient extends Base return ''; } + $stream = @fopen(trim($url), 'r', false, stream_context_create($this->getContext($method, $content, $headers))); + $response = ''; + + if (is_resource($stream)) { + $response = stream_get_contents($stream); + } else { + $this->logger->error('HttpClient: request failed'); + } + + if (DEBUG) { + $this->logger->debug('HttpClient: url='.$url); + $this->logger->debug('HttpClient: payload='.$content); + $this->logger->debug('HttpClient: metadata='.var_export(@stream_get_meta_data($stream), true)); + $this->logger->debug('HttpClient: response='.$response); + } + + return $response; + } + + /** + * Get stream context + * + * @access private + * @param string $method + * @param string $content + * @param string[] $headers + * @return array + */ + private function getContext($method, $content, array $headers) + { $default_headers = array( 'User-Agent: '.self::HTTP_USER_AGENT, 'Connection: close', @@ -126,22 +158,6 @@ class HttpClient extends Base $context['http']['request_fulluri'] = true; } - $stream = @fopen(trim($url), 'r', false, stream_context_create($context)); - $response = ''; - - if (is_resource($stream)) { - $response = stream_get_contents($stream); - } else { - $this->container['logger']->error('HttpClient: request failed'); - } - - if (DEBUG) { - $this->container['logger']->debug('HttpClient: url='.$url); - $this->container['logger']->debug('HttpClient: payload='.$content); - $this->container['logger']->debug('HttpClient: metadata='.var_export(@stream_get_meta_data($stream), true)); - $this->container['logger']->debug('HttpClient: response='.$response); - } - - return $response; + return $context; } } diff --git a/app/Core/Request.php b/app/Core/Http/Request.php index 5eda2d02..9f89a6e2 100644 --- a/app/Core/Request.php +++ b/app/Core/Http/Request.php @@ -1,14 +1,16 @@ <?php -namespace Kanboard\Core; +namespace Kanboard\Core\Http; + +use Kanboard\Core\Base; /** * Request class * - * @package core + * @package http * @author Frederic Guillot */ -class Request +class Request extends Base { /** * Get URL string parameter @@ -57,7 +59,8 @@ class Request */ public function getValues() { - if (! empty($_POST) && Security::validateCSRFFormToken($_POST)) { + if (! empty($_POST) && ! empty($_POST['csrf_token']) && $this->token->validateCSRFToken($_POST['csrf_token'])) { + unset($_POST['csrf_token']); return $_POST; } diff --git a/app/Core/Response.php b/app/Core/Http/Response.php index 528a6302..a793e58b 100644 --- a/app/Core/Response.php +++ b/app/Core/Http/Response.php @@ -1,14 +1,16 @@ <?php -namespace Kanboard\Core; +namespace Kanboard\Core\Http; + +use Kanboard\Core\Base; /** * Response class * - * @package core + * @package http * @author Frederic Guillot */ -class Response +class Response extends Base { /** * Send no cache headers diff --git a/app/Core/Router.php b/app/Core/Http/Router.php index 843f5139..0080b23a 100644 --- a/app/Core/Router.php +++ b/app/Core/Http/Router.php @@ -1,13 +1,14 @@ <?php -namespace Kanboard\Core; +namespace Kanboard\Core\Http; use RuntimeException; +use Kanboard\Core\Base; /** * Router class * - * @package core + * @package http * @author Frederic Guillot */ class Router extends Base diff --git a/app/Core/Security.php b/app/Core/Security/Token.php index 54207ee1..7aca08af 100644 --- a/app/Core/Security.php +++ b/app/Core/Security/Token.php @@ -1,14 +1,16 @@ <?php -namespace Kanboard\Core; +namespace Kanboard\Core\Security; + +use Kanboard\Core\Base; /** - * Security class + * Token Handler * - * @package core + * @package security * @author Frederic Guillot */ -class Security +class Token extends Base { /** * Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid() @@ -17,7 +19,7 @@ class Security * @access public * @return string Random token */ - public static function generateToken() + public static function getToken() { if (function_exists('openssl_random_pseudo_bytes')) { return bin2hex(\openssl_random_pseudo_bytes(30)); @@ -31,18 +33,16 @@ class Security /** * Generate and store a CSRF token in the current session * - * @static * @access public * @return string Random token */ - public static function getCSRFToken() + public function getCSRFToken() { - $nonce = self::generateToken(); - - if (empty($_SESSION['csrf_tokens'])) { + if (! isset($_SESSION['csrf_tokens'])) { $_SESSION['csrf_tokens'] = array(); } + $nonce = self::getToken(); $_SESSION['csrf_tokens'][$nonce] = true; return $nonce; @@ -51,12 +51,11 @@ class Security /** * Check if the token exists for the current session (a token can be used only one time) * - * @static * @access public * @param string $token CSRF token * @return bool */ - public static function validateCSRFToken($token) + public function validateCSRFToken($token) { if (isset($_SESSION['csrf_tokens'][$token])) { unset($_SESSION['csrf_tokens'][$token]); @@ -65,22 +64,4 @@ class Security return false; } - - /** - * Check if the token used in a form is correct and then remove the value - * - * @static - * @access public - * @param array $values Form values - * @return bool - */ - public static function validateCSRFFormToken(array &$values) - { - if (! empty($values['csrf_token']) && self::validateCSRFToken($values['csrf_token'])) { - unset($values['csrf_token']); - return true; - } - - return false; - } } diff --git a/app/Core/Session.php b/app/Core/Session.php index a93131c7..dd1e760e 100644 --- a/app/Core/Session.php +++ b/app/Core/Session.php @@ -3,6 +3,7 @@ namespace Kanboard\Core; use ArrayAccess; +use Kanboard\Core\Http\Request; /** * Session class diff --git a/app/Helper/Form.php b/app/Helper/Form.php index 5f19f2a8..bfd75ee3 100644 --- a/app/Helper/Form.php +++ b/app/Helper/Form.php @@ -2,7 +2,7 @@ namespace Kanboard\Helper; -use Kanboard\Core\Security; +use Kanboard\Core\Base; /** * Form helpers @@ -10,7 +10,7 @@ use Kanboard\Core\Security; * @package helper * @author Frederic Guillot */ -class Form extends \Kanboard\Core\Base +class Form extends Base { /** * Hidden CSRF token field @@ -20,7 +20,7 @@ class Form extends \Kanboard\Core\Base */ public function csrf() { - return '<input type="hidden" name="csrf_token" value="'.Security::getCSRFToken().'"/>'; + return '<input type="hidden" name="csrf_token" value="'.$this->token->getCSRFToken().'"/>'; } /** diff --git a/app/Helper/Url.php b/app/Helper/Url.php index f120252d..edb26841 100644 --- a/app/Helper/Url.php +++ b/app/Helper/Url.php @@ -2,8 +2,8 @@ namespace Kanboard\Helper; -use Kanboard\Core\Request; -use Kanboard\Core\Security; +use Kanboard\Core\Http\Request; +use Kanboard\Core\Base; /** * Url helpers @@ -11,7 +11,7 @@ use Kanboard\Core\Security; * @package helper * @author Frederic Guillot */ -class Url extends \Kanboard\Core\Base +class Url extends Base { private $base = ''; private $directory = ''; @@ -158,7 +158,7 @@ class Url extends \Kanboard\Core\Base } if ($csrf) { - $qs['csrf_token'] = Security::getCSRFToken(); + $qs['csrf_token'] = $this->token->getCSRFToken(); } if (! empty($qs)) { diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 4159da28..17554a3e 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -984,84 +984,84 @@ return array( 'Table of contents' => 'Tabla de contenido', 'Gantt' => 'Gantt', 'Help with project permissions' => 'Ayuda con permisos del proyecto', - // 'Author' => '', - // 'Version' => '', - // 'Plugins' => '', - // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', - // 'My notifications' => '', - // 'Custom filters' => '', - // 'Your custom filter have been created successfully.' => '', - // 'Unable to create your custom filter.' => '', - // 'Custom filter removed successfully.' => '', - // 'Unable to remove this custom filter.' => '', - // 'Edit custom filter' => '', - // 'Your custom filter have been updated successfully.' => '', - // 'Unable to update custom filter.' => '', - // 'Web' => '', - // 'New attachment on task #%d: %s' => '', - // 'New comment on task #%d' => '', - // 'Comment updated on task #%d' => '', - // 'New subtask on task #%d' => '', - // 'Subtask updated on task #%d' => '', - // 'New task #%d: %s' => '', - // 'Task updated #%d' => '', - // 'Task #%d closed' => '', - // 'Task #%d opened' => '', - // 'Column changed for task #%d' => '', - // 'New position for task #%d' => '', - // 'Swimlane changed for task #%d' => '', - // 'Assignee changed on task #%d' => '', - // '%d overdue tasks' => '', - // 'Task #%d is overdue' => '', - // 'No new notifications.' => '', - // 'Mark all as read' => '', - // 'Mark as read' => '', - // 'Total number of tasks in this column across all swimlanes' => '', - // 'Collapse swimlane' => '', - // 'Expand swimlane' => '', - // 'Add a new filter' => '', - // 'Share with all project members' => '', - // 'Shared' => '', - // 'Owner' => '', - // 'Unread notifications' => '', - // 'My filters' => '', - // 'Notification methods:' => '', - // 'Import tasks from CSV file' => '', - // 'Unable to read your file' => '', - // '%d task(s) have been imported successfully.' => '', - // 'Nothing have been imported!' => '', - // 'Import users from CSV file' => '', - // '%d user(s) have been imported successfully.' => '', - // 'Comma' => '', - // 'Semi-colon' => '', - // 'Tab' => '', - // 'Vertical bar' => '', - // 'Double Quote' => '', - // 'Single Quote' => '', - // '%s attached a file to the task #%d' => '', - // 'There is no column or swimlane activated in your project!' => '', - // 'Append filter (instead of replacement)' => '', - // 'Append/Replace' => '', - // 'Append' => '', - // 'Replace' => '', - // 'There is no notification method registered.' => '', - // 'Import' => '', - // 'change sorting' => '', - // 'Tasks Importation' => '', - // 'Delimiter' => '', + 'Author' => 'Autor', + 'Version' => 'Versión', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'No hay ningún plugin cargado', + 'Set maximum column height' => 'Establecer altura máxima de la columna', + 'Remove maximum column height' => 'Eliminar altura máxima de la columna', + 'My notifications' => 'Mis notificaciones', + 'Custom filters' => 'Filtros personalizados', + 'Your custom filter have been created successfully.' => 'Tus filtros personalizados han sido creados exitosamente', + 'Unable to create your custom filter.' => 'No se ha podido crear tu filtro personalizado', + 'Custom filter removed successfully.' => 'Filtro personalizado ha sido eliminado exitosamente', + 'Unable to remove this custom filter.' => 'No se ha podido eliminar tu filtro personalizado', + 'Edit custom filter' => 'Modificar filtro personalizado', + 'Your custom filter have been updated successfully.' => 'Tu filtro personalizado ha sido actualizado exitosamente', + 'Unable to update custom filter.' => 'No se ha podido actualizar tu filtro personalizado', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nuevo adjunto en la tarea #%d: %s', + 'New comment on task #%d' => 'Nuevo comentario en la tarea #%d', + 'Comment updated on task #%d' => 'Comentario actualizado en la tarea #%d', + 'New subtask on task #%d' => 'Nueva subtarea en la tarea #%d', + 'Subtask updated on task #%d' => 'La subtarea en la tarea #%d ha sido actualizada', + 'New task #%d: %s' => 'Nueva tarea #%d: %s', + 'Task updated #%d' => 'Tarea actualizada #%d', + 'Task #%d closed' => 'Tarea #%d ha sido cerrada', + 'Task #%d opened' => 'Tarea #%d ha sido abierta', + 'Column changed for task #%d' => 'Columna para tarea #%d ha sido cambiada', + 'New position for task #%d' => 'Nueva posición para tarea #%d', + 'Swimlane changed for task #%d' => 'Se cambió el swimlane de la tarea #%d', + 'Assignee changed on task #%d' => 'Se cambió el asignado de la tarea #%d', + '%d overdue tasks' => '%d tareas atrasadas', + 'Task #%d is overdue' => 'La tarea #%d está atrasada', + 'No new notifications.' => 'No hay nuevas notificaciones', + 'Mark all as read' => 'Marcar todo como leído', + 'Mark as read' => 'Marcar como leído', + 'Total number of tasks in this column across all swimlanes' => 'Número total de tareas en esta columna por todas las swimlanes', + 'Collapse swimlane' => 'Contraer swimlane', + 'Expand swimlane' => 'Ampliar swimlane', + 'Add a new filter' => 'Añadir nuevo filtro', + 'Share with all project members' => 'Compartir con todos los miembros del proyecto', + 'Shared' => 'Compartido', + 'Owner' => 'Dueño', + 'Unread notifications' => 'Notificaciones sin leer', + 'My filters' => 'Mis filtros', + 'Notification methods:' => 'Métodos de notificación', + 'Import tasks from CSV file' => 'Importar tareas desde archivo CSV', + 'Unable to read your file' => 'No es posible leer el archivo', + '%d task(s) have been imported successfully.' => '%d tarea(s) han sido importadas exitosamente', + 'Nothing have been imported!' => 'No se ha importado nada!', + 'Import users from CSV file' => 'Importar usuarios desde archivo CSV', + '%d user(s) have been imported successfully.' => '%d usuario(s) se han importado exitosamente', + 'Comma' => 'Coma', + 'Semi-colon' => 'Punto y coma', + 'Tab' => 'Tabulación', + 'Vertical bar' => 'Pleca', + 'Double Quote' => 'Comilla doble', + 'Single Quote' => 'Comilla sencilla', + '%s attached a file to the task #%d' => '%s adjuntó un archivo a la tarea #%d', + 'There is no column or swimlane activated in your project!' => 'No hay ninguna columna o swimlane activada en su proyecto!', + 'Append filter (instead of replacement)' => 'Añadir filtro (en vez de reemplazar)', + 'Append/Replace' => 'Añadir/Reemplazar', + 'Append' => 'Añadir', + 'Replace' => 'Reemplazar', + 'There is no notification method registered.' => 'No hay método de notificación registrado', + 'Import' => 'Importar', + 'change sorting' => 'Cambiar orden', + 'Tasks Importation' => 'Importación de tareas', + 'Delimiter' => 'Delimitador', // 'Enclosure' => '', - // 'CSV File' => '', - // 'Instructions' => '', - // 'Your file must use the predefined CSV format' => '', - // 'Your file must be encoded in UTF-8' => '', - // 'The first row must be the header' => '', - // 'Duplicates are not verified for you' => '', - // 'The due date must use the ISO format: YYYY-MM-DD' => '', - // 'Download CSV template' => '', - // 'No external integration registered.' => '', - // 'Duplicates are not imported' => '', - // 'Usernames must be lowercase and unique' => '', - // 'Passwords will be encrypted if present' => '', + 'CSV File' => 'Archivo CSV', + 'Instructions' => 'Indicaciones', + 'Your file must use the predefined CSV format' => 'Su archivo debe utilizar el formato CSV predeterminado', + 'Your file must be encoded in UTF-8' => 'Su archivo debe ser codificado en UTF-8', + 'The first row must be the header' => 'La primera fila debe ser el encabezado', + 'Duplicates are not verified for you' => 'Los duplicados no serán verificados', + 'The due date must use the ISO format: YYYY-MM-DD' => 'La fecha de entrega debe utilizar el formato ISO: AAAA-MM-DD', + 'Download CSV template' => 'Descargar plantilla CSV', + 'No external integration registered.' => 'No se ha registrado integración externa', + 'Duplicates are not imported' => 'Los duplicados no son importados', + 'Usernames must be lowercase and unique' => 'Los nombres de usuario deben ser únicos y contener sólo minúsculas', + 'Passwords will be encrypted if present' => 'Las contraseñas serán cifradas si es que existen', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 6ee5f2dd..e2bcecbd 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -68,7 +68,7 @@ return array( 'Disable' => 'Desativar', 'Enable' => 'Ativar', 'New project' => 'Novo projeto', - 'Do you really want to remove this project: "%s"?' => 'Você realmente deseja remover este projeto: "%s" ?', + 'Do you really want to remove this project: "%s"?' => 'Você realmente deseja remover este projeto: "%s"?', 'Remove project' => 'Remover projeto', 'Edit the board for "%s"' => 'Editar o board para "%s"', 'All projects' => 'Todos os projetos', @@ -167,8 +167,8 @@ return array( '%d closed tasks' => '%d tarefas finalizadas', 'No task for this project' => 'Não há tarefa para este projeto', 'Public link' => 'Link público', - 'Change assignee' => 'Mudar a designação', - 'Change assignee for the task "%s"' => 'Modificar designação para a tarefa "%s"', + 'Change assignee' => 'Alterar designação', + 'Change assignee for the task "%s"' => 'Alterar designação para a tarefa "%s"', 'Timezone' => 'Fuso horário', 'Sorry, I didn\'t find this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!', 'Page not found' => 'Página não encontrada', @@ -217,7 +217,7 @@ return array( 'Remove an automatic action' => 'Remover uma ação automática', 'Assign the task to a specific user' => 'Designar a tarefa para um usuário específico', 'Assign the task to the person who does the action' => 'Designar a tarefa para a pessoa que executa a ação', - 'Duplicate the task to another project' => 'Duplicar a tarefa para um outro projeto', + 'Duplicate the task to another project' => 'Duplicar a tarefa para outro projeto', 'Move a task to another column' => 'Mover a tarefa para outra coluna', 'Task modification' => 'Modificação de tarefa', 'Task creation' => 'Criação de tarefa', @@ -274,7 +274,7 @@ return array( 'Task removed successfully.' => 'Tarefa removida com sucesso.', 'Unable to remove this task.' => 'Não foi possível remover esta tarefa.', 'Remove a task' => 'Remover uma tarefa', - 'Do you really want to remove this task: "%s"?' => 'Você realmente deseja remover esta tarefa: "%s"', + 'Do you really want to remove this task: "%s"?' => 'Você realmente deseja remover esta tarefa: "%s"?', 'Assign automatically a color based on a category' => 'Atribuir automaticamente uma cor com base em uma categoria', 'Assign automatically a category based on a color' => 'Atribuir automaticamente uma categoria com base em uma cor', 'Task creation or modification' => 'Criação ou modificação de tarefa', @@ -287,12 +287,12 @@ return array( 'Your category have been updated successfully.' => 'A sua categoria foi atualizada com sucesso.', 'Unable to update your category.' => 'Não foi possível atualizar a sua categoria.', 'Remove a category' => 'Remover uma categoria', - 'Category removed successfully.' => 'Categoria removido com sucesso.', + 'Category removed successfully.' => 'Categoria removida com sucesso.', 'Unable to remove this category.' => 'Não foi possível remover esta categoria.', 'Category modification for the project "%s"' => 'Modificação de categoria para o projeto "%s"', - 'Category Name' => 'Nome da Categoria', + 'Category Name' => 'Nome da categoria', 'Add a new category' => 'Adicionar uma nova categoria', - 'Do you really want to remove this category: "%s"?' => 'Você realmente deseja remover esta categoria: "%s"', + 'Do you really want to remove this category: "%s"?' => 'Você realmente deseja remover esta categoria: "%s"?', 'All categories' => 'Todas as categorias', 'No category' => 'Nenhum categoria', 'The name is required' => 'O nome é obrigatório', @@ -300,7 +300,7 @@ return array( 'Unable to remove this file.' => 'Não foi possível remover este arquivo.', 'File removed successfully.' => 'Arquivo removido com sucesso.', 'Attach a document' => 'Anexar um documento', - 'Do you really want to remove this file: "%s"?' => 'Você realmente deseja remover este arquivo: "%s"', + 'Do you really want to remove this file: "%s"?' => 'Você realmente deseja remover este arquivo: "%s"?', 'Attachments' => 'Anexos', 'Edit the task' => 'Editar a tarefa', 'Edit the description' => 'Editar a descrição', @@ -331,7 +331,7 @@ return array( 'Unable to update your sub-task.' => 'Não foi possível atualizar a sua subtarefa.', 'Unable to create your sub-task.' => 'Não é possível criar a sua subtarefa.', 'Sub-task added successfully.' => 'Subtarefa adicionada com sucesso.', - 'Maximum size: ' => 'Tamanho máximo:', + 'Maximum size: ' => 'Tamanho máximo: ', 'Unable to upload the file.' => 'Não foi possível carregar o arquivo.', 'Display another project' => 'Exibir outro projeto', 'Login with my Github Account' => 'Entrar com minha Conta do Github', @@ -461,7 +461,7 @@ return array( 'Database' => 'Banco de dados', 'About' => 'Sobre', 'Database driver:' => 'Driver do banco de dados:', - 'Board settings' => 'Configurações do Board', + 'Board settings' => 'Configurações do board', 'URL and token' => 'URL e token', 'Webhook settings' => 'Configurações do Webhook', 'URL for task creation:' => 'URL para a criação da tarefa:', @@ -475,7 +475,7 @@ return array( 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequência em segundos (0 para desativar este recurso, 10 segundos por padrão)', 'Application URL' => 'URL da Aplicação', 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Exemplo: http://example.kanboard.net/ (utilizado nas notificações por e-mail)', - 'Token regenerated.' => 'Token ', + 'Token regenerated.' => 'Novo token gerado.', 'Date format' => 'Formato de data', 'ISO format is always accepted, example: "%s" and "%s"' => 'O formato ISO é sempre aceito, exemplo: "%s" e "%s"', 'New private project' => 'Novo projeto privado', @@ -562,7 +562,7 @@ return array( 'Unable to remove this swimlane.' => 'Não foi possível remover esta swimlane.', 'Unable to update this swimlane.' => 'Não foi possível atualizar esta swimlane.', 'Your swimlane have been created successfully.' => 'Sua swimlane foi criada com sucesso.', - 'Example: "Bug, Feature Request, Improvement"' => 'Exemplo: "Bug, Feature Request, Improvement"', + 'Example: "Bug, Feature Request, Improvement"' => 'Exemplo: "Bug, Solicitação de Recurso, Melhoria"', 'Default categories for new projects (Comma-separated)' => 'Categorias padrões para novos projetos (separadas por vírgula)', 'Gitlab commit received' => 'Gitlab commit received', 'Gitlab issue opened' => 'Gitlab issue opened', @@ -574,7 +574,7 @@ return array( 'Role for this project' => 'Função para este projeto', 'Project manager' => 'Gerente do projeto', 'Project member' => 'Membro do projeto', - 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Um gerente do projeto pode alterar as configurações do projeto e ter mais privilégios que um usuário padrão.', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Um gerente de projeto pode alterar as configurações do projeto e ter mais privilégios que um usuário padrão.', 'Gitlab Issue' => 'Gitlab Issue', 'Subtask Id' => 'ID da subtarefa', 'Subtasks' => 'Subtarefas', @@ -582,13 +582,13 @@ return array( 'Subtasks exportation for "%s"' => 'Subtarefas exportadas para "%s"', 'Task Title' => 'Título da Tarefa', 'Untitled' => 'Sem título', - 'Application default' => 'Aplicação padrão', + 'Application default' => 'Padrão da aplicação', 'Language:' => 'Idioma', 'Timezone:' => 'Fuso horário', 'All columns' => 'Todas as colunas', 'Calendar' => 'Calendário', 'Next' => 'Próximo', - // '#%d' => '', + '#%d' => '#%d', 'All swimlanes' => 'Todas as swimlanes', 'All colors' => 'Todas as cores', 'Moved to column %s' => 'Mover para a coluna %s', @@ -598,7 +598,7 @@ return array( 'Edit column "%s"' => 'Editar a coluna "%s"', 'Select the new status of the subtask: "%s"' => 'Selecionar um novo status para a subtarefa: "%s"', 'Subtask timesheet' => 'Gestão de tempo das subtarefas', - 'There is nothing to show.' => 'Não há nada para mostrar', + 'There is nothing to show.' => 'Não há nada para mostrar.', 'Time Tracking' => 'Gestão de tempo', 'You already have one subtask in progress' => 'Você já tem um subtarefa em andamento', 'Which parts of the project do you want to duplicate?' => 'Quais partes do projeto você deseja duplicar?', @@ -610,11 +610,11 @@ return array( 'End' => 'Fim', 'Task age in days' => 'Idade da tarefa em dias', 'Days in this column' => 'Dias nesta coluna', - // '%dd' => '', + '%dd' => '%dd', 'Add a link' => 'Adicionar uma associação', 'Add a new link' => 'Adicionar uma nova associação', 'Do you really want to remove this link: "%s"?' => 'Você realmente deseja remover esta associação: "%s"?', - 'Do you really want to remove this link with task #%d?' => 'Você realmente deseja remover esta associação com a tarefa n°%d?', + 'Do you really want to remove this link with task #%d?' => 'Você realmente deseja remover esta associação com a tarefa #%d?', 'Field required' => 'Campo requerido', 'Link added successfully.' => 'Associação criada com sucesso.', 'Link updated successfully.' => 'Associação atualizada com sucesso.', @@ -637,8 +637,8 @@ return array( 'is blocked by' => 'está bloqueada por', 'duplicates' => 'duplica', 'is duplicated by' => 'é duplicada por', - 'is a child of' => 'é um filho de', - 'is a parent of' => 'é um parente do', + 'is a child of' => 'é filha de', + 'is a parent of' => 'é mãe de', 'targets milestone' => 'visa um milestone', 'is a milestone of' => 'é um milestone de', 'fixes' => 'corrige', @@ -698,12 +698,12 @@ return array( '%s remove the assignee of the task %s' => '%s removeu a pessoa designada para a tarefa %s', 'Enable Gravatar images' => 'Ativar imagens do Gravatar', 'Information' => 'Informações', - 'Check two factor authentication code' => 'Verificação do código de autenticação à fator duplo', - 'The two factor authentication code is not valid.' => 'O código de autenticação à fator duplo não é válido', - 'The two factor authentication code is valid.' => 'O código de autenticação à fator duplo é válido', + 'Check two factor authentication code' => 'Verifique o código de autenticação em duas etapas', + 'The two factor authentication code is not valid.' => 'O código de autenticação em duas etapas não é válido.', + 'The two factor authentication code is valid.' => 'O código de autenticação em duas etapas é válido.', 'Code' => 'Código', - 'Two factor authentication' => 'Autenticação à fator duplo', - 'Enable/disable two factor authentication' => 'Ativar/Desativar autenticação à fator duplo', + 'Two factor authentication' => 'Autenticação em duas etapas', + 'Enable/disable two factor authentication' => 'Ativar/desativar autenticação em duas etapas', 'This QR code contains the key URI: ' => 'Este Código QR contém a chave URI:', 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Salve esta chave secreta no seu software TOTP (por exemplo Google Authenticator ou FreeOTP).', 'Check my code' => 'Verifique o meu código', @@ -717,19 +717,19 @@ return array( 'Burndown chart for "%s"' => 'Gráfico de Burndown para "%s"', 'Burndown chart' => 'Gráfico de Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico mostra a complexidade da tarefa ao longo do tempo (Trabalho Restante).', - 'Screenshot taken %s' => 'Screenshot tomada em %s', - 'Add a screenshot' => 'Adicionar uma Screenshot', - 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Tomar um screenshot e pressione CTRL + V ou ⌘ + V para colar aqui.', - 'Screenshot uploaded successfully.' => 'Screenshot enviada com sucesso.', + 'Screenshot taken %s' => 'Captura de tela tirada em %s', + 'Add a screenshot' => 'Adicionar uma captura de tela', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Tire uma captura de tela e pressione CTRL + V ou ⌘ + V para colar aqui.', + 'Screenshot uploaded successfully.' => 'Captura de tela enviada com sucesso.', 'SEK - Swedish Krona' => 'SEK - Coroa sueca', 'The project identifier is an optional alphanumeric code used to identify your project.' => 'O identificador de projeto é um código alfanumérico opcional utilizado para identificar o seu projeto.', 'Identifier' => 'Identificador', - 'Disable two factor authentication' => 'Desativar autenticação à dois fatores', - 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Você deseja realmente desativar a autenticação à dois fatores para esse usuário: "%s"?', + 'Disable two factor authentication' => 'Desativar autenticação em duas etapas', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Você realmente deseja desativar a autenticação em duas etapas para este usuário: "%s"?', 'Edit link' => 'Editar um link', 'Start to type task title...' => 'Digite o título do trabalho...', - 'A task cannot be linked to itself' => 'Uma tarefa não pode ser ligada a si própria', - 'The exact same link already exists' => 'Um link idêntico jà existe', + 'A task cannot be linked to itself' => 'Uma tarefa não pode ser vinculada a si própria', + 'The exact same link already exists' => 'Um link idêntico já existe', 'Recurrent task is scheduled to be generated' => 'A tarefa recorrente está programada para ser criada', 'Recurring information' => 'Informação sobre a recorrência', 'Score' => 'Complexidade', @@ -739,7 +739,7 @@ return array( 'Edit recurrence' => 'Modificar a recorrência', 'Generate recurrent task' => 'Gerar uma tarefa recorrente', 'Trigger to generate recurrent task' => 'Trigger para gerar tarefa recorrente', - 'Factor to calculate new due date' => 'Fator para o cálculo do nova data limite', + 'Factor to calculate new due date' => 'Fator para o cálculo da nova data limite', 'Timeframe to calculate new due date' => 'Escala de tempo para o cálculo da nova data limite', 'Base date to calculate new due date' => 'Data a ser utilizada para calcular a nova data limite', 'Action date' => 'Data da ação', @@ -770,8 +770,8 @@ return array( 'iCal feed' => 'Subscrição iCal', 'Preferences' => 'Preferências', 'Security' => 'Segurança', - 'Two factor authentication disabled' => 'Autenticação à fator duplo desativado', - 'Two factor authentication enabled' => 'Autenticação à fator duplo activado', + 'Two factor authentication disabled' => 'Autenticação em duas etapas desativada', + 'Two factor authentication enabled' => 'Autenticação em duas etapas ativada', 'Unable to update this user.' => 'Impossível de atualizar esse usuário.', 'There is no user management for private projects.' => 'Não há gerenciamento de usuários para projetos privados.', 'User that will receive the email' => 'O usuário que vai receber o e-mail', @@ -795,7 +795,7 @@ return array( 'Column change' => 'Mudança de coluna', 'Position change' => 'Mudança de posição', 'Swimlane change' => 'Mudança de swimlane', - 'Assignee change' => 'Mudança do designado', + 'Assignee change' => 'Mudança de designação', '[%s] Overdue tasks' => '[%s] Tarefas atrasadas', 'Notification' => 'Notificação', '%s moved the task #%d to the first swimlane' => '%s moveu a tarefa #%d para a primeira swimlane', @@ -804,7 +804,7 @@ return array( 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s para a primeira swimlane', '%s moved the task %s to the swimlane "%s"' => '%s moveu a tarefa %s para a swimlane "%s"', - 'This report contains all subtasks information for the given date range.' => 'Este relatório contém informações de todas as sub-tarefas para o período selecionado.', + 'This report contains all subtasks information for the given date range.' => 'Este relatório contém informações de todas as subtarefas para o período selecionado.', 'This report contains all tasks information for the given date range.' => 'Este relatório contém informações de todas as tarefas para o período selecionado.', 'Project activities for %s' => 'Atividade do projeto "%s"', 'view the board on Kanboard' => 'ver o painel no Kanboard', @@ -825,7 +825,7 @@ return array( 'Time estimated changed: %sh' => 'O tempo estimado foi mudado/ %sh', 'The field "%s" have been updated' => 'O campo "%s" foi atualizada', 'The description have been modified' => 'A descrição foi modificada', - 'Do you really want to close the task "%s" as well as all subtasks?' => 'Você realmente quer fechar a tarefa "%s" e todas as suas sub-tarefas?', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Você realmente deseja finalizar a tarefa "%s" e todas as suas subtarefas?', 'Swimlane: %s' => 'Swimlane: %s', 'I want to receive notifications for:' => 'Eu quero receber as notificações para:', 'All tasks' => 'Todas as tarefas', @@ -844,7 +844,7 @@ return array( '<30m' => '<30m', 'Stop timer' => 'Stop timer', 'Start timer' => 'Start timer', - 'Add project member' => 'Adicionar um membro ao projeto', + 'Add project member' => 'Adicionar membro ao projeto', 'Enable notifications' => 'Ativar as notificações', 'My activity stream' => 'Meu feed de atividades', 'My calendar' => 'Minha agenda', @@ -868,7 +868,7 @@ return array( 'Switch to the list view' => 'Mudar par o modo Lista', 'Go to the search/filter box' => 'Ir para o campo de pesquisa', 'There is no activity yet.' => 'Não há nenhuma atividade ainda.', - 'No tasks found.' => 'Nenhuma tarefa encontrada', + 'No tasks found.' => 'Nenhuma tarefa encontrada.', 'Keyboard shortcut: "%s"' => 'Tecla de atalho: "%s"', 'List' => 'Lista', 'Filter' => 'Filtro', @@ -918,7 +918,7 @@ return array( 'Default task color' => 'Cor padrão para as tarefas', 'Hide sidebar' => 'Esconder a barra lateral', 'Expand sidebar' => 'Expandir a barra lateral', - 'This feature does not work with all browsers.' => 'Esta funcionalidade não é compatível com todos os navegadores', + 'This feature does not work with all browsers.' => 'Esta funcionalidade não é compatível com todos os navegadores.', 'There is no destination project available.' => 'Não há nenhum projeto de destino disponível.', 'Trigger automatically subtask time tracking' => 'Ativar automaticamente o monitoramento do tempo para as subtarefas', 'Include closed tasks in the cumulative flow diagram' => 'Incluir as tarefas fechadas no diagrama de fluxo acumulado', @@ -932,7 +932,7 @@ return array( 'contributors' => 'contribuidores', 'License:' => 'Licença:', 'License' => 'Licença', - 'Project Administrator' => 'Administrador de Projeto', + 'Project Administrator' => 'Administrador de projeto', 'Enter the text below' => 'Entre o texto abaixo', 'Gantt chart for %s' => 'Gráfico de Gantt para %s', 'Sort by position' => 'Ordenar por posição', @@ -959,7 +959,7 @@ return array( 'Project members' => 'Membros de projeto', 'Gantt chart for all projects' => 'Gráfico de Gantt para todos os projetos', 'Projects list' => 'Lista dos projetos', - 'Gantt chart for this project' => 'Gráfico de Gantt para este projecto', + 'Gantt chart for this project' => 'Gráfico de Gantt para este projeto', 'Project board' => 'Painel do projeto', 'End date:' => 'Data de término:', 'There is no start date or end date for this project.' => 'Não há data de início ou data de término para este projeto.', @@ -1028,40 +1028,40 @@ return array( 'Unread notifications' => 'Notificações não lidas', 'My filters' => 'Meus filtros', 'Notification methods:' => 'Métodos de notificação:', - // 'Import tasks from CSV file' => '', - // 'Unable to read your file' => '', - // '%d task(s) have been imported successfully.' => '', - // 'Nothing have been imported!' => '', - // 'Import users from CSV file' => '', - // '%d user(s) have been imported successfully.' => '', - // 'Comma' => '', - // 'Semi-colon' => '', - // 'Tab' => '', - // 'Vertical bar' => '', - // 'Double Quote' => '', - // 'Single Quote' => '', - // '%s attached a file to the task #%d' => '', - // 'There is no column or swimlane activated in your project!' => '', - // 'Append filter (instead of replacement)' => '', - // 'Append/Replace' => '', - // 'Append' => '', - // 'Replace' => '', - // 'There is no notification method registered.' => '', - // 'Import' => '', - // 'change sorting' => '', - // 'Tasks Importation' => '', - // 'Delimiter' => '', - // 'Enclosure' => '', - // 'CSV File' => '', - // 'Instructions' => '', - // 'Your file must use the predefined CSV format' => '', - // 'Your file must be encoded in UTF-8' => '', - // 'The first row must be the header' => '', - // 'Duplicates are not verified for you' => '', - // 'The due date must use the ISO format: YYYY-MM-DD' => '', - // 'Download CSV template' => '', - // 'No external integration registered.' => '', - // 'Duplicates are not imported' => '', - // 'Usernames must be lowercase and unique' => '', - // 'Passwords will be encrypted if present' => '', + 'Import tasks from CSV file' => 'Importar tarefas a partir de arquivo CSV', + 'Unable to read your file' => 'Não foi possível ler seu arquivo', + '%d task(s) have been imported successfully.' => '%d tarefa(s) importada(s) com sucesso.', + 'Nothing have been imported!' => 'Nada foi importado!', + 'Import users from CSV file' => 'Importar usuários a partir de arquivo CSV', + '%d user(s) have been imported successfully.' => '%d usuário(s) importado(s) com sucesso.', + 'Comma' => 'Vírgula', + 'Semi-colon' => 'Ponto e vírgula', + 'Tab' => 'Tab', + 'Vertical bar' => 'Barra vertical', + 'Double Quote' => 'Aspas duplas', + 'Single Quote' => 'Aspas simples', + '%s attached a file to the task #%d' => '%s anexou um arquivo à tarefa #%d', + 'There is no column or swimlane activated in your project!' => 'Não há coluna ou swimlane ativa em seu projeto!', + 'Append filter (instead of replacement)' => 'Adicionar filtro (em vez de substituir)', + 'Append/Replace' => 'Adicionar/Substituir', + 'Append' => 'Adicionar', + 'Replace' => 'Substituir', + 'There is no notification method registered.' => 'Não há metodo de notificação registrado.', + 'Import' => 'Importar', + 'change sorting' => 'alterar ordenação', + 'Tasks Importation' => 'Importação de Tarefas', + 'Delimiter' => 'Separador', + 'Enclosure' => 'Delimitador de campos', + 'CSV File' => 'Arquivo CSV', + 'Instructions' => 'Instruções', + 'Your file must use the predefined CSV format' => 'Seu arquivo deve utilizar o formato CSV pré-definido', + 'Your file must be encoded in UTF-8' => 'Seu arquivo deve estar codificado em UTF-8', + 'The first row must be the header' => 'A primeira linha deve ser o cabeçalho', + 'Duplicates are not verified for you' => 'Registros duplicados não são verificados', + 'The due date must use the ISO format: YYYY-MM-DD' => 'A data de vencimento deve utilizar o formato ISO: YYYY-MM-DD', + 'Download CSV template' => 'Baixar modelo de arquivo CSV', + 'No external integration registered.' => 'Nenhuma integração externa registrada.', + 'Duplicates are not imported' => 'Registros duplicados não são importados', + 'Usernames must be lowercase and unique' => 'Nomes de usuário devem ser únicos e em letras minúsculas', + 'Passwords will be encrypted if present' => 'Senhas serão encriptadas, se presentes', ); diff --git a/app/Model/Authentication.php b/app/Model/Authentication.php index 580c1e14..11e32313 100644 --- a/app/Model/Authentication.php +++ b/app/Model/Authentication.php @@ -2,7 +2,7 @@ namespace Kanboard\Model; -use Kanboard\Core\Request; +use Kanboard\Core\Http\Request; use SimpleValidator\Validator; use SimpleValidator\Validators; use Gregwar\Captcha\CaptchaBuilder; diff --git a/app/Model/Config.php b/app/Model/Config.php index cf634f80..3b90f58d 100644 --- a/app/Model/Config.php +++ b/app/Model/Config.php @@ -3,7 +3,7 @@ namespace Kanboard\Model; use Kanboard\Core\Translator; -use Kanboard\Core\Security; +use Kanboard\Core\Security\Token; use Kanboard\Core\Session; /** @@ -265,7 +265,7 @@ class Config extends Setting */ public function regenerateToken($option) { - $this->save(array($option => Security::generateToken())); + $this->save(array($option => Token::getToken())); } /** diff --git a/app/Model/Project.php b/app/Model/Project.php index b767af26..9e30a9b8 100644 --- a/app/Model/Project.php +++ b/app/Model/Project.php @@ -4,7 +4,7 @@ namespace Kanboard\Model; use SimpleValidator\Validator; use SimpleValidator\Validators; -use Kanboard\Core\Security; +use Kanboard\Core\Security\Token; /** * Project model @@ -491,7 +491,7 @@ class Project extends Base $this->db ->table(self::TABLE) ->eq('id', $project_id) - ->save(array('is_public' => 1, 'token' => Security::generateToken())); + ->save(array('is_public' => 1, 'token' => Token::getToken())); } /** diff --git a/app/Model/User.php b/app/Model/User.php index 6e7e94e0..dc00c0c5 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -6,7 +6,7 @@ use PicoDb\Database; use SimpleValidator\Validator; use SimpleValidator\Validators; use Kanboard\Core\Session; -use Kanboard\Core\Security; +use Kanboard\Core\Security\Token; /** * User model @@ -383,7 +383,7 @@ class User extends Base return $this->db ->table(self::TABLE) ->eq('id', $user_id) - ->save(array('token' => Security::generateToken())); + ->save(array('token' => Token::getToken())); } /** diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index a021c1cc..54d58592 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -3,7 +3,7 @@ namespace Schema; use PDO; -use Kanboard\Core\Security; +use Kanboard\Core\Security\Token; const VERSION = 93; @@ -869,7 +869,7 @@ function version_20(PDO $pdo) function version_19(PDO $pdo) { $pdo->exec("ALTER TABLE config ADD COLUMN api_token VARCHAR(255) DEFAULT ''"); - $pdo->exec("UPDATE config SET api_token='".Security::generateToken()."'"); + $pdo->exec("UPDATE config SET api_token='".Token::getToken()."'"); } function version_18(PDO $pdo) @@ -1091,6 +1091,6 @@ function version_1(PDO $pdo) $pdo->exec(" INSERT INTO config (webhooks_token) - VALUES ('".Security::generateToken()."') + VALUES ('".Token::getToken()."') "); } diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index a3fb6d49..6f7efed0 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -3,7 +3,7 @@ namespace Schema; use PDO; -use Kanboard\Core\Security; +use Kanboard\Core\Security\Token; const VERSION = 73; @@ -994,6 +994,6 @@ function version_1(PDO $pdo) $pdo->exec(" INSERT INTO config (webhooks_token, api_token) - VALUES ('".Security::generateToken()."', '".Security::generateToken()."') + VALUES ('".Token::getToken()."', '".Token::getToken()."') "); } diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index b9ab86f8..d27f11ec 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -2,7 +2,7 @@ namespace Schema; -use Kanboard\Core\Security; +use Kanboard\Core\Security\Token; use PDO; const VERSION = 88; @@ -799,7 +799,7 @@ function version_20(PDO $pdo) function version_19(PDO $pdo) { $pdo->exec("ALTER TABLE config ADD COLUMN api_token TEXT DEFAULT ''"); - $pdo->exec("UPDATE config SET api_token='".Security::generateToken()."'"); + $pdo->exec("UPDATE config SET api_token='".Token::getToken()."'"); } function version_18(PDO $pdo) @@ -1068,6 +1068,6 @@ function version_1(PDO $pdo) $pdo->exec(" INSERT INTO config (webhooks_token) - VALUES ('".Security::generateToken()."') + VALUES ('".Token::getToken()."') "); } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index c103d639..79bb734f 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -11,6 +11,7 @@ use Kanboard\Core\ObjectStorage\FileStorage; use Kanboard\Core\Paginator; use Kanboard\Core\OAuth2; use Kanboard\Core\Tool; +use Kanboard\Core\Http\Client as HttpClient; use Kanboard\Model\UserNotificationType; use Kanboard\Model\ProjectNotificationType; @@ -81,19 +82,24 @@ class ClassProvider implements ServiceProviderInterface 'Core' => array( 'DateParser', 'Helper', - 'HttpClient', 'Lexer', - 'Request', - 'Router', 'Session', 'Template', ), + 'Core\Http' => array( + 'Request', + 'Response', + 'Router', + ), 'Core\Cache' => array( 'MemoryCache', ), 'Core\Plugin' => array( 'Hook', ), + 'Core\Security' => array( + 'Token', + ), 'Integration' => array( 'BitbucketWebhook', 'GithubWebhook', @@ -113,6 +119,10 @@ class ClassProvider implements ServiceProviderInterface return new OAuth2($c); }); + $container['httpClient'] = function ($c) { + return new HttpClient($c); + }; + $container['htmlConverter'] = function () { return new HtmlConverter(array('strip_tags' => true)); }; diff --git a/app/Subscriber/AuthSubscriber.php b/app/Subscriber/AuthSubscriber.php index 2461b52c..77a39942 100644 --- a/app/Subscriber/AuthSubscriber.php +++ b/app/Subscriber/AuthSubscriber.php @@ -2,7 +2,7 @@ namespace Kanboard\Subscriber; -use Kanboard\Core\Request; +use Kanboard\Core\Http\Request; use Kanboard\Event\AuthEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; diff --git a/tests/units/Core/RouterTest.php b/tests/units/Core/Http/RouterTest.php index 753e1204..c2380247 100644 --- a/tests/units/Core/RouterTest.php +++ b/tests/units/Core/Http/RouterTest.php @@ -1,8 +1,8 @@ <?php -require_once __DIR__.'/../Base.php'; +require_once __DIR__.'/../../Base.php'; -use Kanboard\Core\Router; +use Kanboard\Core\Http\Router; class RouterTest extends Base { diff --git a/tests/units/Core/Security/TokenTest.php b/tests/units/Core/Security/TokenTest.php new file mode 100644 index 00000000..dbb7bd1a --- /dev/null +++ b/tests/units/Core/Security/TokenTest.php @@ -0,0 +1,29 @@ +<?php + +require_once __DIR__.'/../../Base.php'; + +use Kanboard\Core\Security\Token; + +class TokenTest extends Base +{ + public function testGenerateToken() + { + $t1 = Token::getToken(); + $t2 = Token::getToken(); + + $this->assertNotEmpty($t1); + $this->assertNotEmpty($t2); + + $this->assertNotEquals($t1, $t2); + } + + public function testCSRFTokens() + { + $token = new Token($this->container); + $t1 = $token->getCSRFToken(); + + $this->assertNotEmpty($t1); + $this->assertTrue($token->validateCSRFToken($t1)); + $this->assertFalse($token->validateCSRFToken($t1)); + } +} |