Add personal API access token

This commit is contained in:
Frederic Guillot 2016-12-03 15:43:36 -05:00
parent 23d862aef8
commit b8f7532e5c
No known key found for this signature in database
GPG Key ID: 92D77191BA7FBC99
45 changed files with 526 additions and 15 deletions

View File

@ -3,6 +3,7 @@ Version 1.0.35
New features: New features:
* Add personal API access token for users
* Rewrite of Markdown editor (remove CodeMirror) * Rewrite of Markdown editor (remove CodeMirror)
* Suggest menu for task ID and user mentions in Markdown editor * Suggest menu for task ID and user mentions in Markdown editor

View File

@ -28,6 +28,7 @@ class AuthenticationMiddleware extends Base implements MiddlewareInterface
public function execute($username, $password, $procedureName) public function execute($username, $password, $procedureName)
{ {
$this->dispatcher->dispatch('app.bootstrap'); $this->dispatcher->dispatch('app.bootstrap');
$this->sessionStorage->scope = 'API';
if ($this->isUserAuthenticated($username, $password)) { if ($this->isUserAuthenticated($username, $password)) {
$this->userSession->initialize($this->userModel->getByUsername($username)); $this->userSession->initialize($this->userModel->getByUsername($username));

View File

@ -0,0 +1,119 @@
<?php
namespace Kanboard\Auth;
use Kanboard\Core\Base;
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
use Kanboard\Model\UserModel;
use Kanboard\User\DatabaseUserProvider;
/**
* API Access Token Authentication Provider
*
* @package Kanboard\Auth
* @author Frederic Guillot
*/
class ApiAccessTokenAuth extends Base implements PasswordAuthenticationProviderInterface
{
/**
* 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 'API Access Token';
}
/**
* Authenticate the user
*
* @access public
* @return boolean
*/
public function authenticate()
{
if (! isset($this->sessionStorage->scope) || $this->sessionStorage->scope !== 'API') {
$this->logger->debug(__METHOD__.': Authentication provider skipped because invalid scope');
return false;
}
$user = $this->db
->table(UserModel::TABLE)
->columns('id', 'password')
->eq('username', $this->username)
->eq('api_access_token', $this->password)
->notNull('api_access_token')
->eq('is_active', 1)
->findOne();
if (! empty($user)) {
$this->userInfo = $user;
return true;
}
return false;
}
/**
* 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

@ -11,7 +11,7 @@ use Kanboard\User\DatabaseUserProvider;
/** /**
* Database Authentication Provider * Database Authentication Provider
* *
* @package auth * @package Kanboard\Auth
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterface, SessionCheckProviderInterface class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterface, SessionCheckProviderInterface

View File

@ -12,7 +12,7 @@ use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
/** /**
* LDAP Authentication Provider * LDAP Authentication Provider
* *
* @package auth * @package Kanboard\Auth
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class LdapAuth extends Base implements PasswordAuthenticationProviderInterface class LdapAuth extends Base implements PasswordAuthenticationProviderInterface

View File

@ -7,9 +7,9 @@ use Kanboard\Core\Security\PreAuthenticationProviderInterface;
use Kanboard\User\DatabaseUserProvider; use Kanboard\User\DatabaseUserProvider;
/** /**
* Rember Me Cookie Authentication Provider * RememberMe Cookie Authentication Provider
* *
* @package auth * @package Kanboard\Auth
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class RememberMeAuth extends Base implements PreAuthenticationProviderInterface class RememberMeAuth extends Base implements PreAuthenticationProviderInterface

View File

@ -10,7 +10,7 @@ use Kanboard\User\ReverseProxyUserProvider;
/** /**
* Reverse-Proxy Authentication Provider * Reverse-Proxy Authentication Provider
* *
* @package auth * @package Kanboard\Auth
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface

View File

@ -11,7 +11,7 @@ use Kanboard\Core\Security\PostAuthenticationProviderInterface;
/** /**
* TOTP Authentication Provider * TOTP Authentication Provider
* *
* @package auth * @package Kanboard\Auth
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class TotpAuth extends Base implements PostAuthenticationProviderInterface class TotpAuth extends Base implements PostAuthenticationProviderInterface

View File

@ -0,0 +1,50 @@
<?php
namespace Kanboard\Controller;
use Kanboard\Core\Security\Token;
/**
* Class UserApiAccessController
*
* @package Kanboard\Controller
* @author Frederic Guillot
*/
class UserApiAccessController extends BaseController
{
public function show()
{
$user = $this->getUser();
return $this->response->html($this->helper->layout->user('user_api_access/show', array(
'user' => $user,
'title' => t('API User Access'),
)));
}
public function generate()
{
$user = $this->getUser();
$this->checkCSRFParam();
$this->userModel->update(array(
'id' => $user['id'],
'api_access_token' => Token::getToken(),
));
$this->response->redirect($this->helper->url->to('UserApiAccessController', 'show', array('user_id' => $user['id'])));
}
public function remove()
{
$user = $this->getUser();
$this->checkCSRFParam();
$this->userModel->update(array(
'id' => $user['id'],
'api_access_token' => null,
));
$this->response->redirect($this->helper->url->to('UserApiAccessController', 'show', array('user_id' => $user['id'])));
}
}

View File

@ -19,6 +19,7 @@ namespace Kanboard\Core\Session;
* @property bool $hasSubtaskInProgress * @property bool $hasSubtaskInProgress
* @property bool $hasRememberMe * @property bool $hasRememberMe
* @property bool $boardCollapsed * @property bool $boardCollapsed
* @property string $scope
* @property bool $twoFactorBeforeCodeCalled * @property bool $twoFactorBeforeCodeCalled
* @property string $twoFactorSecret * @property string $twoFactorSecret
* @property string $oauthState * @property string $oauthState

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => 'Verschieben einer Aufgabe ist nicht erlaubt', 'Moving a task is not permitted' => 'Verschieben einer Aufgabe ist nicht erlaubt',
'This value must be in the range %d to %d' => 'Dieser Wert muss im Bereich %d bis %d sein', 'This value must be in the range %d to %d' => 'Dieser Wert muss im Bereich %d bis %d sein',
'You are not allowed to move this task.' => 'Sie haben nicht die Berechtigung, diese Aufgabe zu verschieben.', 'You are not allowed to move this task.' => 'Sie haben nicht die Berechtigung, diese Aufgabe zu verschieben.',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1279,4 +1279,13 @@ return array(
'Moving a task is not permitted' => 'Déplaçer une tâche n\'est pas autorisé', 'Moving a task is not permitted' => 'Déplaçer une tâche n\'est pas autorisé',
'This value must be in the range %d to %d' => 'Cette valeur doit être définie entre %d et %d', 'This value must be in the range %d to %d' => 'Cette valeur doit être définie entre %d et %d',
'You are not allowed to move this task.' => 'Vous n\'êtes pas autorisé à déplacer cette tâche.', 'You are not allowed to move this task.' => 'Vous n\'êtes pas autorisé à déplacer cette tâche.',
'API User Access' => 'Accès utilisateur de l\'API',
'Preview' => 'Aperçu',
'Write' => 'Écrire',
'Write your text in Markdown' => 'Écrivez votre texte en Markdown',
'New External Task: %s' => 'Nouvelle tâche externe : %s',
'No personal API access token registered.' => 'Aucun jeton d\'accès personnel à l\'API enregistré.',
'Your personal API access token is "%s"' => 'Votre jeton d\'accès personnel à l\'API est « %s »',
'Remove your token' => 'Supprimer votre jeton',
'Generate a new token' => 'Générer un nouveau jeton',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => 'Spostare task non è permesso', 'Moving a task is not permitted' => 'Spostare task non è permesso',
'This value must be in the range %d to %d' => 'Questo valore deve essere compreso tra %d e %d', 'This value must be in the range %d to %d' => 'Questo valore deve essere compreso tra %d e %d',
'You are not allowed to move this task.' => 'Non ti è permesso spostare questo task.', 'You are not allowed to move this task.' => 'Non ti è permesso spostare questo task.',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => '할일 이동이 거부되었습니다.', 'Moving a task is not permitted' => '할일 이동이 거부되었습니다.',
'This value must be in the range %d to %d' => '값의 범위는 %d 부터 %d 까지 입니다.', 'This value must be in the range %d to %d' => '값의 범위는 %d 부터 %d 까지 입니다.',
'You are not allowed to move this task.' => '당신은 할일 이동이 거부되었습니다.', 'You are not allowed to move this task.' => '당신은 할일 이동이 거부되었습니다.',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => 'Mover uma tarefa não é permitido', 'Moving a task is not permitted' => 'Mover uma tarefa não é permitido',
'This value must be in the range %d to %d' => 'Este valor precisa estar no intervalo %d até %d', 'This value must be in the range %d to %d' => 'Este valor precisa estar no intervalo %d até %d',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => 'Não é permitido mover uma tarefa', 'Moving a task is not permitted' => 'Não é permitido mover uma tarefa',
'This value must be in the range %d to %d' => 'Este valor deve estar entre %d e %d', 'This value must be in the range %d to %d' => 'Este valor deve estar entre %d e %d',
'You are not allowed to move this task.' => 'Não lhe é permitido mover esta tarefa.', 'You are not allowed to move this task.' => 'Não lhe é permitido mover esta tarefa.',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => 'Перемещение задачи не разрешено', 'Moving a task is not permitted' => 'Перемещение задачи не разрешено',
'This value must be in the range %d to %d' => 'Значение должно находиться в диапазоне от %d до %d', 'This value must be in the range %d to %d' => 'Значение должно находиться в диапазоне от %d до %d',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
// 'Moving a task is not permitted' => '', // 'Moving a task is not permitted' => '',
// 'This value must be in the range %d to %d' => '', // 'This value must be in the range %d to %d' => '',
// 'You are not allowed to move this task.' => '', // 'You are not allowed to move this task.' => '',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => 'Görev taşımaya izin verilmemiş', 'Moving a task is not permitted' => 'Görev taşımaya izin verilmemiş',
'This value must be in the range %d to %d' => 'Bu değer şu aralıkta olmalı: "%d" "%d"', 'This value must be in the range %d to %d' => 'Bu değer şu aralıkta olmalı: "%d" "%d"',
'You are not allowed to move this task.' => 'Bu görevi taşımaya izniniz yok.', 'You are not allowed to move this task.' => 'Bu görevi taşımaya izniniz yok.',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -1278,4 +1278,13 @@ return array(
'Moving a task is not permitted' => '禁止移动任务', 'Moving a task is not permitted' => '禁止移动任务',
'This value must be in the range %d to %d' => '输入值必须在%d到%d之间', 'This value must be in the range %d to %d' => '输入值必须在%d到%d之间',
'You are not allowed to move this task.' => '你不能移动此任务', 'You are not allowed to move this task.' => '你不能移动此任务',
// 'API User Access' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Write your text in Markdown' => '',
// 'New External Task: %s' => '',
// 'No personal API access token registered.' => '',
// 'Your personal API access token is "%s"' => '',
// 'Remove your token' => '',
// 'Generate a new token' => '',
); );

View File

@ -6,7 +6,12 @@ use PDO;
use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role; use Kanboard\Core\Security\Role;
const VERSION = 117; const VERSION = 118;
function version_118(PDO $pdo)
{
$pdo->exec('ALTER TABLE `users` ADD COLUMN `api_access_token` VARCHAR(255) DEFAULT NULL');
}
function version_117(PDO $pdo) function version_117(PDO $pdo)
{ {

View File

@ -6,7 +6,12 @@ use PDO;
use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role; use Kanboard\Core\Security\Role;
const VERSION = 96; const VERSION = 97;
function version_97(PDO $pdo)
{
$pdo->exec('ALTER TABLE "users" ADD COLUMN api_access_token VARCHAR(255) DEFAULT NULL');
}
function version_96(PDO $pdo) function version_96(PDO $pdo)
{ {

View File

@ -715,6 +715,7 @@ CREATE TABLE `users` (
`role` varchar(25) NOT NULL DEFAULT 'app-user', `role` varchar(25) NOT NULL DEFAULT 'app-user',
`is_active` tinyint(1) DEFAULT '1', `is_active` tinyint(1) DEFAULT '1',
`avatar_path` varchar(255) DEFAULT NULL, `avatar_path` varchar(255) DEFAULT NULL,
`api_access_token` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `users_username_idx` (`username`) UNIQUE KEY `users_username_idx` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ -738,7 +739,7 @@ CREATE TABLE `users` (
LOCK TABLES `settings` WRITE; LOCK TABLES `settings` WRITE;
/*!40000 ALTER TABLE `settings` DISABLE KEYS */; /*!40000 ALTER TABLE `settings` DISABLE KEYS */;
INSERT INTO `settings` VALUES ('api_token','f9ae1d30899c88091642f4e996ff97b1e5db516b5edd256793246b18b637',0,0),('application_currency','USD',0,0),('application_datetime_format','m/d/Y H:i',1,1480728474),('application_date_format','m/d/Y',1,1480728474),('application_language','fr_FR',1,1480728474),('application_stylesheet','',1,1480728474),('application_timezone','UTC',1,1480728474),('application_time_format','H:i',1,1480728474),('application_url','',1,1480728474),('board_columns','',0,0),('board_highlight_period','172800',0,0),('board_private_refresh_interval','10',0,0),('board_public_refresh_interval','60',0,0),('calendar_project_tasks','date_started',0,0),('calendar_user_subtasks_time_tracking','0',0,0),('calendar_user_tasks','date_started',0,0),('cfd_include_closed_tasks','1',0,0),('default_color','yellow',0,0),('integration_gravatar','0',0,0),('password_reset','checked',1,1480728474),('project_categories','',0,0),('subtask_restriction','0',0,0),('subtask_time_tracking','1',0,0),('webhook_token','382dae506e2bd5a4e45709c275827b0bbc7bee9f683b2320c78299deb36e',0,0),('webhook_url','',0,0); INSERT INTO `settings` VALUES ('api_token','f149956cb60c88d01123a28964fc035b1ce4513be454f2a85fe6b4ca3758',0,0),('application_currency','USD',0,0),('application_date_format','m/d/Y',0,0),('application_language','en_US',0,0),('application_stylesheet','',0,0),('application_timezone','UTC',0,0),('application_url','',0,0),('board_columns','',0,0),('board_highlight_period','172800',0,0),('board_private_refresh_interval','10',0,0),('board_public_refresh_interval','60',0,0),('calendar_project_tasks','date_started',0,0),('calendar_user_subtasks_time_tracking','0',0,0),('calendar_user_tasks','date_started',0,0),('cfd_include_closed_tasks','1',0,0),('default_color','yellow',0,0),('integration_gravatar','0',0,0),('password_reset','1',0,0),('project_categories','',0,0),('subtask_restriction','0',0,0),('subtask_time_tracking','1',0,0),('webhook_token','47d1d896b6612234c7543eb3f3a09a0a669f77a079d13ad3d810ccb79896',0,0),('webhook_url','',0,0);
/*!40000 ALTER TABLE `settings` ENABLE KEYS */; /*!40000 ALTER TABLE `settings` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
@ -767,4 +768,4 @@ UNLOCK TABLES;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$ZYX2JNGPsH/SMc3UBk0rYu2LYCLYRVEqokhOjIULKXs6RjvaV2RBu', 'app-admin');INSERT INTO schema_version VALUES ('117'); INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$R1zYk04d96KcHRpd9.r5I.5I6mgKIgUdsaISZYmaDLPIJCUO0FFJG', 'app-admin');INSERT INTO schema_version VALUES ('118');

View File

@ -1245,7 +1245,8 @@ CREATE TABLE "users" (
"gitlab_id" integer, "gitlab_id" integer,
"role" character varying(25) DEFAULT 'app-user'::character varying NOT NULL, "role" character varying(25) DEFAULT 'app-user'::character varying NOT NULL,
"is_active" boolean DEFAULT true, "is_active" boolean DEFAULT true,
"avatar_path" character varying(255) "avatar_path" character varying(255),
"api_access_token" character varying(255) DEFAULT NULL::character varying
); );
@ -2544,8 +2545,8 @@ INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_high
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_public_refresh_interval', '60', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_public_refresh_interval', '60', 0, 0);
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_private_refresh_interval', '10', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_private_refresh_interval', '10', 0, 0);
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_columns', '', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_columns', '', 0, 0);
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('webhook_token', 'c12181512f9cfd66a6b2af4edf199390b922bbb8a259dc5397c2329ed47c', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('webhook_token', '8687190194e06d34c2cd84a57b36f67696c971c2f8e453f96e59eccb8e73', 0, 0);
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('api_token', '4e05046a5cb5b907da712ab02af98e9752da48bfacc0a625c4c08493bc8f', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('api_token', '8381164131e3995ca17c754a5b0cf7039d66b9f389b80250978de9fcf2f5', 0, 0);
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_language', 'en_US', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_language', 'en_US', 0, 0);
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_timezone', 'UTC', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_timezone', 'UTC', 0, 0);
INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_url', '', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_url', '', 0, 0);
@ -2615,4 +2616,4 @@ SELECT pg_catalog.setval('links_id_seq', 11, true);
-- PostgreSQL database dump complete -- PostgreSQL database dump complete
-- --
INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$ZYX2JNGPsH/SMc3UBk0rYu2LYCLYRVEqokhOjIULKXs6RjvaV2RBu', 'app-admin');INSERT INTO schema_version VALUES ('96'); INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$R1zYk04d96KcHRpd9.r5I.5I6mgKIgUdsaISZYmaDLPIJCUO0FFJG', 'app-admin');INSERT INTO schema_version VALUES ('97');

View File

@ -6,7 +6,12 @@ use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role; use Kanboard\Core\Security\Role;
use PDO; use PDO;
const VERSION = 107; const VERSION = 108;
function version_108(PDO $pdo)
{
$pdo->exec('ALTER TABLE users ADD COLUMN api_access_token VARCHAR(255) DEFAULT NULL');
}
function version_107(PDO $pdo) function version_107(PDO $pdo)
{ {

View File

@ -2,6 +2,7 @@
namespace Kanboard\ServiceProvider; namespace Kanboard\ServiceProvider;
use Kanboard\Auth\ApiAccessTokenAuth;
use Pimple\Container; use Pimple\Container;
use Pimple\ServiceProviderInterface; use Pimple\ServiceProviderInterface;
use Kanboard\Core\Security\AuthenticationManager; use Kanboard\Core\Security\AuthenticationManager;
@ -44,6 +45,8 @@ class AuthenticationProvider implements ServiceProviderInterface
$container['authenticationManager']->register(new LdapAuth($container)); $container['authenticationManager']->register(new LdapAuth($container));
} }
$container['authenticationManager']->register(new ApiAccessTokenAuth($container));
$container['projectAccessMap'] = $this->getProjectAccessMap(); $container['projectAccessMap'] = $this->getProjectAccessMap();
$container['applicationAccessMap'] = $this->getApplicationAccessMap(); $container['applicationAccessMap'] = $this->getApplicationAccessMap();
$container['apiAccessMap'] = $this->getApiAccessMap(); $container['apiAccessMap'] = $this->getApiAccessMap();

View File

@ -158,6 +158,7 @@ class RouteProvider implements ServiceProviderInterface
$container['route']->addRoute('user/:user_id/authentication', 'UserCredentialController', 'changeAuthentication'); $container['route']->addRoute('user/:user_id/authentication', 'UserCredentialController', 'changeAuthentication');
$container['route']->addRoute('user/:user_id/2fa', 'TwoFactorController', 'index'); $container['route']->addRoute('user/:user_id/2fa', 'TwoFactorController', 'index');
$container['route']->addRoute('user/:user_id/avatar', 'AvatarFileController', 'show'); $container['route']->addRoute('user/:user_id/avatar', 'AvatarFileController', 'show');
$container['route']->addRoute('user/:user_id/api', 'UserApiAccessController', 'show');
// Groups // Groups
$container['route']->addRoute('groups', 'GroupListController', 'index'); $container['route']->addRoute('groups', 'GroupListController', 'index');

View File

@ -0,0 +1,17 @@
<div class="page-header">
<h2><?= t('API User Access') ?></h2>
</div>
<p class="alert">
<?php if (empty($user['api_access_token'])): ?>
<?= t('No personal API access token registered.') ?>
<?php else: ?>
<?= t('Your personal API access token is "%s"', $user['api_access_token']) ?>
<?php endif ?>
</p>
<?php if (! empty($user['api_access_token'])): ?>
<?= $this->url->link(t('Remove your token'), 'UserApiAccessController', 'remove', array('user_id' => $user['id']), true, 'btn btn-red') ?>
<?php endif ?>
<?= $this->url->link(t('Generate a new token'), 'UserApiAccessController', 'generate', array('user_id' => $user['id']), true, 'btn btn-blue') ?>

View File

@ -90,6 +90,11 @@
<?= $this->url->link(t('Integrations'), 'UserViewController', 'integrations', array('user_id' => $user['id'])) ?> <?= $this->url->link(t('Integrations'), 'UserViewController', 'integrations', array('user_id' => $user['id'])) ?>
</li> </li>
<?php endif ?> <?php endif ?>
<?php if ($this->user->hasAccess('UserApiAccessController', 'show')): ?>
<li <?= $this->app->checkMenuSelection('UserApiAccessController', 'show') ?>>
<?= $this->url->link(t('API'), 'UserApiAccessController', 'show', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
<?php endif ?> <?php endif ?>
<?php if ($this->user->hasAccess('UserCredentialController', 'changeAuthentication')): ?> <?php if ($this->user->hasAccess('UserCredentialController', 'changeAuthentication')): ?>

View File

@ -0,0 +1,71 @@
<?php
use Kanboard\Auth\ApiAccessTokenAuth;
use Kanboard\Model\UserModel;
require_once __DIR__.'/../Base.php';
class ApiAccessTokenAuthTest extends Base
{
public function testGetName()
{
$provider = new ApiAccessTokenAuth($this->container);
$this->assertEquals('API Access Token', $provider->getName());
}
public function testAuthenticateWithoutToken()
{
$provider = new ApiAccessTokenAuth($this->container);
$provider->setUsername('admin');
$provider->setPassword('admin');
$this->assertFalse($provider->authenticate());
$this->assertNull($provider->getUser());
}
public function testAuthenticateWithEmptyPassword()
{
$provider = new ApiAccessTokenAuth($this->container);
$provider->setUsername('admin');
$provider->setPassword('');
$this->assertFalse($provider->authenticate());
}
public function testAuthenticateWithTokenAndNoScope()
{
$provider = new ApiAccessTokenAuth($this->container);
$userModel = new UserModel($this->container);
$userModel->update(array(
'id' => 1,
'api_access_token' => 'test',
));
$provider->setUsername('admin');
$provider->setPassword('test');
$this->assertFalse($provider->authenticate());
}
public function testAuthenticateWithToken()
{
$this->container['sessionStorage']->scope = 'API';
$provider = new ApiAccessTokenAuth($this->container);
$userModel = new UserModel($this->container);
$userModel->update(array(
'id' => 1,
'api_access_token' => 'test',
));
$provider->setUsername('admin');
$provider->setPassword('test');
$this->assertTrue($provider->authenticate());
$this->assertInstanceOf('Kanboard\User\DatabaseUserProvider', $provider->getUser());
$provider->setUsername('admin');
$provider->setPassword('something else');
$this->assertFalse($provider->authenticate());
}
}