mirror of
https://github.com/YunoHost-Apps/kanboard_ynh.git
synced 2024-09-03 19:36:17 +02:00
Update sources to kanboard v1.0.22
This commit is contained in:
parent
21e59f0725
commit
9b8806775a
452 changed files with 17917 additions and 10094 deletions
|
@ -1,11 +1,41 @@
|
|||
Version 1.0.22
|
||||
--------------
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* LDAP configuration parameters changes (See documentation)
|
||||
* SQL table changes:
|
||||
- "users" table: added new column "role" and removed columns "is_admin" and "is_project_admin"
|
||||
- "project_has_users" table: replaced column "is_owner" with column "role"
|
||||
- Sqlite does not support alter table, old columns still there but unused
|
||||
* API procedure changes:
|
||||
- createUser
|
||||
- createLdapUser
|
||||
- updateUser
|
||||
- updateTask
|
||||
* Event removed: "session.bootstrap", use "app.boostrap" instead
|
||||
|
||||
New features:
|
||||
|
||||
* Add pluggable authentication and authorization system (complete rewrite)
|
||||
* Add groups (teams/organization)
|
||||
* Add LDAP groups synchronization
|
||||
* Add project group permissions
|
||||
* Add new project role Viewer
|
||||
* Add generic LDAP client library
|
||||
* Add search query attribute for task link
|
||||
* Add the possibility to define API token in config file
|
||||
* Add capability to reopen Gitlab issues
|
||||
* Try to load config.php from /data if not available
|
||||
|
||||
Version 1.0.21
|
||||
--------------
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* Projects with duplicate name are now allowed:
|
||||
For Postgres and Mysql the unique constraint is removed by database migration
|
||||
However Sqlite does not support alter table, only new databases will have the unique constraint removed
|
||||
- For Postgres and Mysql the unique constraint is removed by database migration
|
||||
- However Sqlite does not support alter table, only new databases will have the unique constraint removed
|
||||
|
||||
New features:
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Kanboard\Action;
|
||||
|
||||
use Kanboard\Integration\GithubWebhook;
|
||||
use Kanboard\Integration\GitlabWebhook;
|
||||
use Kanboard\Integration\BitbucketWebhook;
|
||||
|
||||
/**
|
||||
|
@ -23,6 +24,7 @@ class TaskOpen extends Base
|
|||
{
|
||||
return array(
|
||||
GithubWebhook::EVENT_ISSUE_REOPENED,
|
||||
GitlabWebhook::EVENT_ISSUE_REOPENED,
|
||||
BitbucketWebhook::EVENT_ISSUE_REOPENED,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Kanboard\Api;
|
||||
|
||||
use JsonRPC\AuthenticationFailure;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Base class
|
||||
|
@ -24,15 +23,58 @@ class Auth extends Base
|
|||
*/
|
||||
public function checkCredentials($username, $password, $class, $method)
|
||||
{
|
||||
$this->container['dispatcher']->dispatch('api.bootstrap', new Event);
|
||||
$this->container['dispatcher']->dispatch('app.bootstrap');
|
||||
|
||||
if ($username !== 'jsonrpc' && ! $this->authentication->hasCaptcha($username) && $this->authentication->authenticate($username, $password)) {
|
||||
if ($this->isUserAuthenticated($username, $password)) {
|
||||
$this->checkProcedurePermission(true, $method);
|
||||
$this->userSession->initialize($this->user->getByUsername($username));
|
||||
} elseif ($username === 'jsonrpc' && $password === $this->config->get('api_token')) {
|
||||
} elseif ($this->isAppAuthenticated($username, $password)) {
|
||||
$this->checkProcedurePermission(false, $method);
|
||||
} else {
|
||||
throw new AuthenticationFailure('Wrong credentials');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user credentials
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
private function isUserAuthenticated($username, $password)
|
||||
{
|
||||
return $username !== 'jsonrpc' &&
|
||||
! $this->userLocking->isLocked($username) &&
|
||||
$this->authenticationManager->passwordAuthentication($username, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check administrative credentials
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
private function isAppAuthenticated($username, $password)
|
||||
{
|
||||
return $username === 'jsonrpc' && $password === $this->getApiToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API Token
|
||||
*
|
||||
* @access private
|
||||
* @return string
|
||||
*/
|
||||
private function getApiToken()
|
||||
{
|
||||
if (defined('API_AUTHENTICATION_TOKEN')) {
|
||||
return API_AUTHENTICATION_TOKEN;
|
||||
}
|
||||
|
||||
return $this->config->get('api_token');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class Me extends Base
|
|||
public function getMyDashboard()
|
||||
{
|
||||
$user_id = $this->userSession->getId();
|
||||
$projects = $this->project->getQueryColumnStats($this->projectPermission->getActiveMemberProjectIds($user_id))->findAll();
|
||||
$projects = $this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id))->findAll();
|
||||
$tasks = $this->taskFinder->getUserQuery($user_id)->findAll();
|
||||
|
||||
return array(
|
||||
|
@ -32,7 +32,7 @@ class Me extends Base
|
|||
|
||||
public function getMyActivityStream()
|
||||
{
|
||||
$project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
return $this->projectActivity->getProjects($project_ids, 100);
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ class Me extends Base
|
|||
|
||||
public function getMyProjectsList()
|
||||
{
|
||||
return $this->projectPermission->getMemberProjects($this->userSession->getId());
|
||||
return $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
}
|
||||
|
||||
public function getMyOverdueTasks()
|
||||
|
@ -60,7 +60,7 @@ class Me extends Base
|
|||
|
||||
public function getMyProjects()
|
||||
{
|
||||
$project_ids = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$projects = $this->project->getAllByIds($project_ids);
|
||||
|
||||
return $this->formatProjects($projects);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* ProjectPermission API controller
|
||||
*
|
||||
|
@ -12,16 +14,16 @@ class ProjectPermission extends \Kanboard\Core\Base
|
|||
{
|
||||
public function getMembers($project_id)
|
||||
{
|
||||
return $this->projectPermission->getMembers($project_id);
|
||||
return $this->projectUserRole->getAllUsers($project_id);
|
||||
}
|
||||
|
||||
public function revokeUser($project_id, $user_id)
|
||||
{
|
||||
return $this->projectPermission->revokeMember($project_id, $user_id);
|
||||
return $this->projectUserRole->removeUser($project_id, $user_id);
|
||||
}
|
||||
|
||||
public function allowUser($project_id, $user_id)
|
||||
{
|
||||
return $this->projectPermission->addMember($project_id, $user_id);
|
||||
return $this->projectUserRole->addUser($project_id, $user_id, Role::PROJECT_MEMBER);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,14 @@ class Task extends Base
|
|||
{
|
||||
$this->checkProjectPermission($project_id);
|
||||
|
||||
if ($owner_id !== 0 && ! $this->projectPermission->isMember($project_id, $owner_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->userSession->isLogged()) {
|
||||
$creator_id = $this->userSession->getId();
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'title' => $title,
|
||||
'project_id' => $project_id,
|
||||
|
@ -96,20 +104,28 @@ class Task extends Base
|
|||
return $valid ? $this->taskCreation->create($values) : false;
|
||||
}
|
||||
|
||||
public function updateTask($id, $title = null, $project_id = null, $color_id = null, $owner_id = null,
|
||||
$creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null,
|
||||
public function updateTask($id, $title = null, $color_id = null, $owner_id = null,
|
||||
$date_due = null, $description = null, $category_id = null, $score = null,
|
||||
$recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null,
|
||||
$recurrence_timeframe = null, $recurrence_basedate = null, $reference = null)
|
||||
{
|
||||
$this->checkTaskPermission($id);
|
||||
|
||||
$project_id = $this->taskFinder->getProjectId($id);
|
||||
|
||||
if ($project_id === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($owner_id !== null && ! $this->projectPermission->isMember($project_id, $owner_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'id' => $id,
|
||||
'title' => $title,
|
||||
'project_id' => $project_id,
|
||||
'color_id' => $color_id,
|
||||
'owner_id' => $owner_id,
|
||||
'creator_id' => $creator_id,
|
||||
'date_due' => $date_due,
|
||||
'description' => $description,
|
||||
'category_id' => $category_id,
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
use Kanboard\Auth\Ldap;
|
||||
use LogicException;
|
||||
use Kanboard\Core\Security\Role;
|
||||
use Kanboard\Core\Ldap\Client as LdapClient;
|
||||
use Kanboard\Core\Ldap\ClientException as LdapException;
|
||||
use Kanboard\Core\Ldap\User as LdapUser;
|
||||
|
||||
/**
|
||||
* User API controller
|
||||
|
@ -27,7 +31,7 @@ class User extends \Kanboard\Core\Base
|
|||
return $this->user->remove($user_id);
|
||||
}
|
||||
|
||||
public function createUser($username, $password, $name = '', $email = '', $is_admin = 0, $is_project_admin = 0)
|
||||
public function createUser($username, $password, $name = '', $email = '', $role = Role::APP_USER)
|
||||
{
|
||||
$values = array(
|
||||
'username' => $username,
|
||||
|
@ -35,44 +39,53 @@ class User extends \Kanboard\Core\Base
|
|||
'confirmation' => $password,
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'is_admin' => $is_admin,
|
||||
'is_project_admin' => $is_project_admin,
|
||||
'role' => $role,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->user->validateCreation($values);
|
||||
return $valid ? $this->user->create($values) : false;
|
||||
}
|
||||
|
||||
public function createLdapUser($username = '', $email = '', $is_admin = 0, $is_project_admin = 0)
|
||||
public function createLdapUser($username)
|
||||
{
|
||||
$ldap = new Ldap($this->container);
|
||||
$user = $ldap->lookup($username, $email);
|
||||
try {
|
||||
|
||||
if (! $user) {
|
||||
$ldap = LdapClient::connect();
|
||||
$user = LdapUser::getUser($ldap, sprintf(LDAP_USER_FILTER, $username));
|
||||
|
||||
if ($user === null) {
|
||||
$this->logger->info('User not found in LDAP server');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->getUsername() === '') {
|
||||
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'username' => $user->getUsername(),
|
||||
'name' => $user->getName(),
|
||||
'email' => $user->getEmail(),
|
||||
'role' => $user->getRole(),
|
||||
'is_ldap_user' => 1,
|
||||
);
|
||||
|
||||
return $this->user->create($values);
|
||||
|
||||
} catch (LdapException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'username' => $user['username'],
|
||||
'name' => $user['name'],
|
||||
'email' => $user['email'],
|
||||
'is_ldap_user' => 1,
|
||||
'is_admin' => $is_admin,
|
||||
'is_project_admin' => $is_project_admin,
|
||||
);
|
||||
|
||||
return $this->user->create($values);
|
||||
}
|
||||
|
||||
public function updateUser($id, $username = null, $name = null, $email = null, $is_admin = null, $is_project_admin = null)
|
||||
public function updateUser($id, $username = null, $name = null, $email = null, $role = null)
|
||||
{
|
||||
$values = array(
|
||||
'id' => $id,
|
||||
'username' => $username,
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'is_admin' => $is_admin,
|
||||
'is_project_admin' => $is_project_admin,
|
||||
'role' => $role,
|
||||
);
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\User;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Database authentication
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Database extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Database';
|
||||
|
||||
/**
|
||||
* Authenticate a user
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
$user = $this->db
|
||||
->table(User::TABLE)
|
||||
->eq('username', $username)
|
||||
->eq('disable_login_form', 0)
|
||||
->eq('is_ldap_user', 0)
|
||||
->findOne();
|
||||
|
||||
if (is_array($user) && password_verify($password, $user['password'])) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
125
sources/app/Auth/DatabaseAuth.php
Normal file
125
sources/app/Auth/DatabaseAuth.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
|
||||
use Kanboard\Core\Security\SessionCheckProviderInterface;
|
||||
use Kanboard\Model\User;
|
||||
use Kanboard\User\DatabaseUserProvider;
|
||||
|
||||
/**
|
||||
* Database Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterface, SessionCheckProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $userInfo = array();
|
||||
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $username = '';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $password = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Database';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$user = $this->db
|
||||
->table(User::TABLE)
|
||||
->columns('id', 'password')
|
||||
->eq('username', $this->username)
|
||||
->eq('disable_login_form', 0)
|
||||
->eq('is_ldap_user', 0)
|
||||
->findOne();
|
||||
|
||||
if (! empty($user) && password_verify($this->password, $user['password'])) {
|
||||
$this->userInfo = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user session is valid
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidSession()
|
||||
{
|
||||
return $this->user->exists($this->userSession->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\User\DatabaseUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
if (empty($this->userInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DatabaseUserProvider($this->userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
*/
|
||||
public function setUsername($username)
|
||||
{
|
||||
$this->username = $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*
|
||||
* @access public
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Github backend
|
||||
*
|
||||
* @package auth
|
||||
*/
|
||||
class Github extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Github';
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* Authenticate a Github user
|
||||
*
|
||||
* @access public
|
||||
* @param string $github_id Github user id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($github_id)
|
||||
{
|
||||
$user = $this->user->getByGithubId($github_id);
|
||||
|
||||
if (! empty($user)) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a Github account for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unlink($user_id)
|
||||
{
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'github_id' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user table based on the Github profile information
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param array $profile Github profile
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'github_id' => $profile['id'],
|
||||
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
|
||||
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 configured service
|
||||
*
|
||||
* @access public
|
||||
* @return Kanboard\Core\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'github', array(), '', true),
|
||||
GITHUB_OAUTH_AUTHORIZE_URL,
|
||||
GITHUB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Github profile
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile($code)
|
||||
{
|
||||
$this->getService()->getAccessToken($code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITHUB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
}
|
143
sources/app/Auth/GithubAuth.php
Normal file
143
sources/app/Auth/GithubAuth.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
|
||||
use Kanboard\User\GithubUserProvider;
|
||||
|
||||
/**
|
||||
* Github Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GithubAuth extends Base implements OAuthAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\User\GithubUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Github';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
|
||||
if (! empty($profile)) {
|
||||
$this->userInfo = new GithubUserProvider($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return GithubAuth
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return GithubUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured OAuth2 service
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'github', array(), '', true),
|
||||
GITHUB_OAUTH_AUTHORIZE_URL,
|
||||
GITHUB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Github profile
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
$this->getService()->getAccessToken($this->code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITHUB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($userId)
|
||||
{
|
||||
return $this->user->update(array('id' => $userId, 'github_id' => ''));
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Gitlab backend
|
||||
*
|
||||
* @package auth
|
||||
*/
|
||||
class Gitlab extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Gitlab';
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* Authenticate a Gitlab user
|
||||
*
|
||||
* @access public
|
||||
* @param string $gitlab_id Gitlab user id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($gitlab_id)
|
||||
{
|
||||
$user = $this->user->getByGitlabId($gitlab_id);
|
||||
|
||||
if (! empty($user)) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a Gitlab account for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unlink($user_id)
|
||||
{
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'gitlab_id' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user table based on the Gitlab profile information
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param array $profile Gitlab profile
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'gitlab_id' => $profile['id'],
|
||||
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
|
||||
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 configured service
|
||||
*
|
||||
* @access public
|
||||
* @return Kanboard\Core\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITLAB_CLIENT_ID,
|
||||
GITLAB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'gitlab', array(), '', true),
|
||||
GITLAB_OAUTH_AUTHORIZE_URL,
|
||||
GITLAB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Gitlab profile
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile($code)
|
||||
{
|
||||
$this->getService()->getAccessToken($code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITLAB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
}
|
143
sources/app/Auth/GitlabAuth.php
Normal file
143
sources/app/Auth/GitlabAuth.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
|
||||
use Kanboard\User\GitlabUserProvider;
|
||||
|
||||
/**
|
||||
* Gitlab Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GitlabAuth extends Base implements OAuthAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\User\GitlabUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Gitlab';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
|
||||
if (! empty($profile)) {
|
||||
$this->userInfo = new GitlabUserProvider($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return GitlabAuth
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return GitlabUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured OAuth2 service
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GITLAB_CLIENT_ID,
|
||||
GITLAB_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'gitlab', array(), '', true),
|
||||
GITLAB_OAUTH_AUTHORIZE_URL,
|
||||
GITLAB_OAUTH_TOKEN_URL,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Gitlab profile
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
$this->getService()->getAccessToken($this->code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
GITLAB_API_URL.'user',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($userId)
|
||||
{
|
||||
return $this->user->update(array('id' => $userId, 'gitlab_id' => ''));
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* Google backend
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Google extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'Google';
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* Authenticate a Google user
|
||||
*
|
||||
* @access public
|
||||
* @param string $google_id Google unique id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($google_id)
|
||||
{
|
||||
$user = $this->user->getByGoogleId($google_id);
|
||||
|
||||
if (! empty($user)) {
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a Google account for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unlink($user_id)
|
||||
{
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'google_id' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user table based on the Google profile information
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param array $profile Google profile
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
return $this->user->update(array(
|
||||
'id' => $user_id,
|
||||
'google_id' => $profile['id'],
|
||||
'email' => empty($user['email']) ? $profile['email'] : $user['email'],
|
||||
'name' => empty($user['name']) ? $profile['name'] : $user['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 configured service
|
||||
*
|
||||
* @access public
|
||||
* @return KanboardCore\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'google', array(), '', true),
|
||||
'https://accounts.google.com/o/oauth2/auth',
|
||||
'https://accounts.google.com/o/oauth2/token',
|
||||
array('https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile')
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Google profile
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile($code)
|
||||
{
|
||||
$this->getService()->getAccessToken($code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
'https://www.googleapis.com/oauth2/v1/userinfo',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
}
|
143
sources/app/Auth/GoogleAuth.php
Normal file
143
sources/app/Auth/GoogleAuth.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
|
||||
use Kanboard\User\GoogleUserProvider;
|
||||
|
||||
/**
|
||||
* Google Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GoogleAuth extends Base implements OAuthAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\User\GoogleUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
|
||||
/**
|
||||
* OAuth2 instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* OAuth2 code
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Google';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
|
||||
if (! empty($profile)) {
|
||||
$this->userInfo = new GoogleUserProvider($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return GoogleAuth
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return GoogleUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured OAuth2 service
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
if (empty($this->service)) {
|
||||
$this->service = $this->oauth->createService(
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
$this->helper->url->to('oauth', 'google', array(), '', true),
|
||||
'https://accounts.google.com/o/oauth2/auth',
|
||||
'https://accounts.google.com/o/oauth2/token',
|
||||
array('https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile')
|
||||
);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Google profile
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
$this->getService()->getAccessToken($this->code);
|
||||
|
||||
return $this->httpClient->getJson(
|
||||
'https://www.googleapis.com/oauth2/v1/userinfo',
|
||||
array($this->getService()->getAuthorizationHeader())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($userId)
|
||||
{
|
||||
return $this->user->update(array('id' => $userId, 'google_id' => ''));
|
||||
}
|
||||
}
|
|
@ -1,521 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* LDAP model
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Ldap extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'LDAP';
|
||||
|
||||
/**
|
||||
* Get LDAP server name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapServer()
|
||||
{
|
||||
return LDAP_SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP bind type
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getLdapBindType()
|
||||
{
|
||||
return LDAP_BIND_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP server port
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getLdapPort()
|
||||
{
|
||||
return LDAP_PORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP username (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUsername()
|
||||
{
|
||||
return LDAP_USERNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP password (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapPassword()
|
||||
{
|
||||
return LDAP_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP Base DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapBaseDn()
|
||||
{
|
||||
return LDAP_ACCOUNT_BASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account id attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountId()
|
||||
{
|
||||
return LDAP_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account email attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountEmail()
|
||||
{
|
||||
return LDAP_ACCOUNT_EMAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account name attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountName()
|
||||
{
|
||||
return LDAP_ACCOUNT_FULLNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account memberof attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapAccountMemberOf()
|
||||
{
|
||||
return LDAP_ACCOUNT_MEMBEROF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP admin group DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapGroupAdmin()
|
||||
{
|
||||
return LDAP_GROUP_ADMIN_DN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP project admin group DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapGroupProjectAdmin()
|
||||
{
|
||||
return LDAP_GROUP_PROJECT_ADMIN_DN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP username pattern
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUserPattern($username)
|
||||
{
|
||||
return sprintf(LDAP_USER_PATTERN, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the LDAP username is case sensitive
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLdapAccountCaseSensitive()
|
||||
{
|
||||
return LDAP_USERNAME_CASE_SENSITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the automatic account creation is enabled
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLdapAccountCreationEnabled()
|
||||
{
|
||||
return LDAP_ACCOUNT_CREATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ge the list of attributes to fetch when reading the LDAP user entry
|
||||
*
|
||||
* Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProfileAttributes()
|
||||
{
|
||||
return array_values(array_filter(array(
|
||||
$this->getLdapAccountId(),
|
||||
$this->getLdapAccountName(),
|
||||
$this->getLdapAccountEmail(),
|
||||
$this->getLdapAccountMemberOf()
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
$username = $this->isLdapAccountCaseSensitive() ? $username : strtolower($username);
|
||||
$result = $this->findUser($username, $password);
|
||||
|
||||
if (is_array($result)) {
|
||||
$user = $this->user->getByUsername($username);
|
||||
|
||||
if (! empty($user)) {
|
||||
|
||||
// There is already a local user with that name
|
||||
if ($user['is_ldap_user'] == 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
// We create automatically a new user
|
||||
if ($this->isLdapAccountCreationEnabled() && $this->user->create($result) !== false) {
|
||||
$user = $this->user->getByUsername($username);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We open the session
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the user from the LDAP server
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function findUser($username, $password)
|
||||
{
|
||||
$ldap = $this->connect();
|
||||
|
||||
if ($ldap !== false && $this->bind($ldap, $username, $password)) {
|
||||
return $this->getProfile($ldap, $username, $password);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP connection
|
||||
*
|
||||
* @access public
|
||||
* @return resource|boolean
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if (! function_exists('ldap_connect')) {
|
||||
$this->logger->error('LDAP: The PHP LDAP extension is required');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip SSL certificate verification
|
||||
if (! LDAP_SSL_VERIFY) {
|
||||
putenv('LDAPTLS_REQCERT=never');
|
||||
}
|
||||
|
||||
$ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort());
|
||||
|
||||
if ($ldap === false) {
|
||||
$this->logger->error('LDAP: Unable to connect to the LDAP server');
|
||||
return false;
|
||||
}
|
||||
|
||||
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
|
||||
ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 1);
|
||||
ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 1);
|
||||
|
||||
if (LDAP_START_TLS && ! @ldap_start_tls($ldap)) {
|
||||
$this->logger->error('LDAP: Unable to use ldap_start_tls()');
|
||||
return false;
|
||||
}
|
||||
|
||||
return $ldap;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP authentication
|
||||
*
|
||||
* @access public
|
||||
* @param resource $ldap
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function bind($ldap, $username, $password)
|
||||
{
|
||||
if ($this->getLdapBindType() === 'user') {
|
||||
$ldap_username = sprintf($this->getLdapUsername(), $username);
|
||||
$ldap_password = $password;
|
||||
} elseif ($this->getLdapBindType() === 'proxy') {
|
||||
$ldap_username = $this->getLdapUsername();
|
||||
$ldap_password = $this->getLdapPassword();
|
||||
} else {
|
||||
$ldap_username = null;
|
||||
$ldap_password = null;
|
||||
}
|
||||
|
||||
if (! @ldap_bind($ldap, $ldap_username, $ldap_password)) {
|
||||
$this->logger->error('LDAP: Unable to bind to server with: '.$ldap_username);
|
||||
$this->logger->error('LDAP: bind type='.$this->getLdapBindType());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP user profile
|
||||
*
|
||||
* @access public
|
||||
* @param resource $ldap
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function getProfile($ldap, $username, $password)
|
||||
{
|
||||
$user_pattern = $this->getLdapUserPattern($username);
|
||||
$entries = $this->executeQuery($ldap, $user_pattern);
|
||||
|
||||
if ($entries === false) {
|
||||
$this->logger->error('LDAP: Unable to get user profile: '.$user_pattern);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (@ldap_bind($ldap, $entries[0]['dn'], $password)) {
|
||||
return $this->prepareProfile($ldap, $entries, $username);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
$this->logger->debug('LDAP: wrong password for '.$entries[0]['dn']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build user profile from LDAP information
|
||||
*
|
||||
* @access public
|
||||
* @param resource $ldap
|
||||
* @param array $entries
|
||||
* @param string $username
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function prepareProfile($ldap, array $entries, $username)
|
||||
{
|
||||
if ($this->getLdapAccountId() !== '') {
|
||||
$username = $this->getEntry($entries, $this->getLdapAccountId(), $username);
|
||||
}
|
||||
|
||||
return array(
|
||||
'username' => $username,
|
||||
'name' => $this->getEntry($entries, $this->getLdapAccountName()),
|
||||
'email' => $this->getEntry($entries, $this->getLdapAccountEmail()),
|
||||
'is_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupAdmin()),
|
||||
'is_project_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupProjectAdmin()),
|
||||
'is_ldap_user' => 1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check group membership
|
||||
*
|
||||
* @access public
|
||||
* @param array $group_entries
|
||||
* @param string $group_dn
|
||||
* @return boolean
|
||||
*/
|
||||
public function isMemberOf(array $group_entries, $group_dn)
|
||||
{
|
||||
if (! isset($group_entries['count']) || empty($group_dn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $group_entries['count']; $i++) {
|
||||
if ($group_entries[$i] === $group_dn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve info on LDAP user by username or email
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $email
|
||||
* @return boolean|array
|
||||
*/
|
||||
public function lookup($username = null, $email = null)
|
||||
{
|
||||
$query = $this->getLookupQuery($username, $email);
|
||||
if ($query === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connect and attempt anonymous or proxy binding
|
||||
$ldap = $this->connect();
|
||||
if ($ldap === false || ! $this->bind($ldap, null, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to find user
|
||||
$entries = $this->executeQuery($ldap, $query);
|
||||
if ($entries === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// User id not retrieved: LDAP_ACCOUNT_ID not properly configured
|
||||
if (empty($username) && ! isset($entries[0][$this->getLdapAccountId()][0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->prepareProfile($ldap, $entries, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute LDAP query
|
||||
*
|
||||
* @access private
|
||||
* @param resource $ldap
|
||||
* @param string $query
|
||||
* @return boolean|array
|
||||
*/
|
||||
private function executeQuery($ldap, $query)
|
||||
{
|
||||
$sr = @ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes());
|
||||
if ($sr === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = ldap_get_entries($ldap, $sr);
|
||||
if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LDAP query to find a user
|
||||
*
|
||||
* @access private
|
||||
* @param string $username
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
private function getLookupQuery($username, $email)
|
||||
{
|
||||
if (! empty($username) && ! empty($email)) {
|
||||
return '(&('.$this->getLdapUserPattern($username).')('.$this->getLdapAccountEmail().'='.$email.'))';
|
||||
} elseif (! empty($username)) {
|
||||
return $this->getLdapUserPattern($username);
|
||||
} elseif (! empty($email)) {
|
||||
return '('.$this->getLdapAccountEmail().'='.$email.')';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return one entry from a list of entries
|
||||
*
|
||||
* @access private
|
||||
* @param array $entries LDAP entries
|
||||
* @param string $key Key
|
||||
* @param string $default Default value if key not set in entry
|
||||
* @return string
|
||||
*/
|
||||
private function getEntry(array $entries, $key, $default = '')
|
||||
{
|
||||
return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return subset of entries
|
||||
*
|
||||
* @access private
|
||||
* @param array $entries
|
||||
* @param string $key
|
||||
* @param array $default
|
||||
* @return array
|
||||
*/
|
||||
private function getEntries(array $entries, $key, $default = array())
|
||||
{
|
||||
return isset($entries[0][$key]) ? $entries[0][$key] : $default;
|
||||
}
|
||||
}
|
172
sources/app/Auth/LdapAuth.php
Normal file
172
sources/app/Auth/LdapAuth.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use LogicException;
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Ldap\Client as LdapClient;
|
||||
use Kanboard\Core\Ldap\ClientException as LdapException;
|
||||
use Kanboard\Core\Ldap\User as LdapUser;
|
||||
use Kanboard\Core\Security\PasswordAuthenticationProviderInterface;
|
||||
|
||||
/**
|
||||
* LDAP Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @var \Kanboard\User\LdapUserProvider
|
||||
*/
|
||||
private $userInfo = null;
|
||||
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $username = '';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $password = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'LDAP';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
try {
|
||||
|
||||
$client = LdapClient::connect($this->getLdapUsername(), $this->getLdapPassword());
|
||||
$user = LdapUser::getUser($client, $this->username);
|
||||
|
||||
if ($user === null) {
|
||||
$this->logger->info('User not found in LDAP server');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->getUsername() === '') {
|
||||
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
|
||||
}
|
||||
|
||||
if ($client->authenticate($user->getDn(), $this->password)) {
|
||||
$this->userInfo = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (LdapException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return \Kanboard\User\LdapUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
*/
|
||||
public function setUsername($username)
|
||||
{
|
||||
$this->username = $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*
|
||||
* @access public
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP username (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUsername()
|
||||
{
|
||||
switch ($this->getLdapBindType()) {
|
||||
case 'proxy':
|
||||
return LDAP_USERNAME;
|
||||
case 'user':
|
||||
return sprintf(LDAP_USERNAME, $this->username);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP password (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapPassword()
|
||||
{
|
||||
switch ($this->getLdapBindType()) {
|
||||
case 'proxy':
|
||||
return LDAP_PASSWORD;
|
||||
case 'user':
|
||||
return $this->password;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP bind type
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getLdapBindType()
|
||||
{
|
||||
if (LDAP_BIND_TYPE !== 'user' && LDAP_BIND_TYPE !== 'proxy' && LDAP_BIND_TYPE !== 'anonymous') {
|
||||
throw new LogicException('Wrong value for the parameter LDAP_BIND_TYPE');
|
||||
}
|
||||
|
||||
return LDAP_BIND_TYPE;
|
||||
}
|
||||
}
|
|
@ -1,323 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Http\Request;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
use Kanboard\Core\Security\Token;
|
||||
|
||||
/**
|
||||
* RememberMe model
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RememberMe extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'RememberMe';
|
||||
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'remember_me';
|
||||
|
||||
/**
|
||||
* Cookie name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COOKIE_NAME = '__R';
|
||||
|
||||
/**
|
||||
* Expiration (60 days)
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const EXPIRATION = 5184000;
|
||||
|
||||
/**
|
||||
* Get a remember me record
|
||||
*
|
||||
* @access public
|
||||
* @param $token
|
||||
* @param $sequence
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($token, $sequence)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('token', $token)
|
||||
->eq('sequence', $sequence)
|
||||
->gt('expiration', time())
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sessions for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->desc('date_creation')
|
||||
->columns('id', 'ip', 'user_agent', 'date_creation', 'expiration')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user with the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$credentials = $this->readCookie();
|
||||
|
||||
if ($credentials !== false) {
|
||||
$record = $this->find($credentials['token'], $credentials['sequence']);
|
||||
|
||||
if ($record) {
|
||||
|
||||
// Update the sequence
|
||||
$this->writeCookie(
|
||||
$record['token'],
|
||||
$this->update($record['token']),
|
||||
$record['expiration']
|
||||
);
|
||||
|
||||
// Create the session
|
||||
$this->userSession->initialize($this->user->getById($record['user_id']));
|
||||
|
||||
// Do not ask 2FA for remember me session
|
||||
$this->sessionStorage->postAuth['validated'] = true;
|
||||
|
||||
$this->container['dispatcher']->dispatch(
|
||||
'auth.success',
|
||||
new AuthEvent(self::AUTH_NAME, $this->userSession->getId())
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a session record
|
||||
*
|
||||
* @access public
|
||||
* @param integer $session_id Session id
|
||||
* @return mixed
|
||||
*/
|
||||
public function remove($session_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $session_id)
|
||||
->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the current RememberMe session and the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
*/
|
||||
public function destroy($user_id)
|
||||
{
|
||||
$credentials = $this->readCookie();
|
||||
|
||||
if ($credentials !== false) {
|
||||
$this->deleteCookie();
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->eq('token', $credentials['token'])
|
||||
->remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RememberMe session
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param string $ip IP Address
|
||||
* @param string $user_agent User Agent
|
||||
* @return array
|
||||
*/
|
||||
public function create($user_id, $ip, $user_agent)
|
||||
{
|
||||
$token = hash('sha256', $user_id.$user_agent.$ip.Token::getToken());
|
||||
$sequence = Token::getToken();
|
||||
$expiration = time() + self::EXPIRATION;
|
||||
|
||||
$this->cleanup($user_id);
|
||||
|
||||
$this
|
||||
->db
|
||||
->table(self::TABLE)
|
||||
->insert(array(
|
||||
'user_id' => $user_id,
|
||||
'ip' => $ip,
|
||||
'user_agent' => $user_agent,
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
'expiration' => $expiration,
|
||||
'date_creation' => time(),
|
||||
));
|
||||
|
||||
return array(
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
'expiration' => $expiration,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old sessions for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return bool
|
||||
*/
|
||||
public function cleanup($user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->lt('expiration', time())
|
||||
->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new sequence token and update the database
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @return string
|
||||
*/
|
||||
public function update($token)
|
||||
{
|
||||
$new_sequence = Token::getToken();
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
->eq('token', $token)
|
||||
->update(array('sequence' => $new_sequence));
|
||||
|
||||
return $new_sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @return string
|
||||
*/
|
||||
public function encodeCookie($token, $sequence)
|
||||
{
|
||||
return implode('|', array($token, $sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the value of a cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Raw cookie data
|
||||
* @return array
|
||||
*/
|
||||
public function decodeCookie($value)
|
||||
{
|
||||
list($token, $sequence) = explode('|', $value);
|
||||
|
||||
return array(
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the current user has a RememberMe cookie
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCookie()
|
||||
{
|
||||
return ! empty($_COOKIE[self::COOKIE_NAME]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write and encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @param string $expiration Cookie expiration
|
||||
*/
|
||||
public function writeCookie($token, $sequence, $expiration)
|
||||
{
|
||||
setcookie(
|
||||
self::COOKIE_NAME,
|
||||
$this->encodeCookie($token, $sequence),
|
||||
$expiration,
|
||||
$this->helper->url->dir(),
|
||||
null,
|
||||
Request::isHTTPS(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and decode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function readCookie()
|
||||
{
|
||||
if (empty($_COOKIE[self::COOKIE_NAME])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->decodeCookie($_COOKIE[self::COOKIE_NAME]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the cookie
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function deleteCookie()
|
||||
{
|
||||
setcookie(
|
||||
self::COOKIE_NAME,
|
||||
'',
|
||||
time() - 3600,
|
||||
$this->helper->url->dir(),
|
||||
null,
|
||||
Request::isHTTPS(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
79
sources/app/Auth/RememberMeAuth.php
Normal file
79
sources/app/Auth/RememberMeAuth.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
|
||||
use Kanboard\User\DatabaseUserProvider;
|
||||
|
||||
/**
|
||||
* Rember Me Cookie Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RememberMeAuth extends Base implements PreAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $userInfo = array();
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'RememberMe';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$credentials = $this->rememberMeCookie->read();
|
||||
|
||||
if ($credentials !== false) {
|
||||
$session = $this->rememberMeSession->find($credentials['token'], $credentials['sequence']);
|
||||
|
||||
if (! empty($session)) {
|
||||
$this->rememberMeCookie->write(
|
||||
$session['token'],
|
||||
$this->rememberMeSession->updateSequence($session['token']),
|
||||
$session['expiration']
|
||||
);
|
||||
|
||||
$this->userInfo = $this->user->getById($session['user_id']);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return DatabaseUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
if (empty($this->userInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DatabaseUserProvider($this->userInfo);
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthEvent;
|
||||
|
||||
/**
|
||||
* ReverseProxy backend
|
||||
*
|
||||
* @package auth
|
||||
* @author Sylvain Veyrié
|
||||
*/
|
||||
class ReverseProxy extends Base
|
||||
{
|
||||
/**
|
||||
* Backend name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_NAME = 'ReverseProxy';
|
||||
|
||||
/**
|
||||
* Get username from the reverse proxy
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername()
|
||||
{
|
||||
return isset($_SERVER[REVERSE_PROXY_USER_HEADER]) ? $_SERVER[REVERSE_PROXY_USER_HEADER] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user with the HTTP header
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
if (isset($_SERVER[REVERSE_PROXY_USER_HEADER])) {
|
||||
$login = $_SERVER[REVERSE_PROXY_USER_HEADER];
|
||||
$user = $this->user->getByUsername($login);
|
||||
|
||||
if (empty($user)) {
|
||||
$this->createUser($login);
|
||||
$user = $this->user->getByUsername($login);
|
||||
}
|
||||
|
||||
$this->userSession->initialize($user);
|
||||
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create automatically a new local user after the authentication
|
||||
*
|
||||
* @access private
|
||||
* @param string $login Username
|
||||
* @return bool
|
||||
*/
|
||||
private function createUser($login)
|
||||
{
|
||||
$email = strpos($login, '@') !== false ? $login : '';
|
||||
|
||||
if (REVERSE_PROXY_DEFAULT_DOMAIN !== '' && empty($email)) {
|
||||
$email = $login.'@'.REVERSE_PROXY_DEFAULT_DOMAIN;
|
||||
}
|
||||
|
||||
return $this->user->create(array(
|
||||
'email' => $email,
|
||||
'username' => $login,
|
||||
'is_admin' => REVERSE_PROXY_DEFAULT_ADMIN === $login,
|
||||
'is_ldap_user' => 1,
|
||||
'disable_login_form' => 1,
|
||||
));
|
||||
}
|
||||
}
|
76
sources/app/Auth/ReverseProxyAuth.php
Normal file
76
sources/app/Auth/ReverseProxyAuth.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PreAuthenticationProviderInterface;
|
||||
use Kanboard\Core\Security\SessionCheckProviderInterface;
|
||||
use Kanboard\User\ReverseProxyUserProvider;
|
||||
|
||||
/**
|
||||
* Reverse-Proxy Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface
|
||||
{
|
||||
/**
|
||||
* User properties
|
||||
*
|
||||
* @access protected
|
||||
* @var \Kanboard\User\ReverseProxyUserProvider
|
||||
*/
|
||||
protected $userInfo = null;
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'ReverseProxy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$username = $this->request->getRemoteUser();
|
||||
|
||||
if (! empty($username)) {
|
||||
$this->userInfo = new ReverseProxyUserProvider($username);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user session is valid
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidSession()
|
||||
{
|
||||
return $this->request->getRemoteUser() === $this->userSession->getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return ReverseProxyUserProvider
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
}
|
126
sources/app/Auth/TotpAuth.php
Normal file
126
sources/app/Auth/TotpAuth.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Auth;
|
||||
|
||||
use Otp\Otp;
|
||||
use Otp\GoogleAuthenticator;
|
||||
use Base32\Base32;
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\PostAuthenticationProviderInterface;
|
||||
|
||||
/**
|
||||
* TOTP Authentication Provider
|
||||
*
|
||||
* @package auth
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* User pin code
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $code = '';
|
||||
|
||||
/**
|
||||
* Private key
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $secret = '';
|
||||
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Time-based One-time Password Algorithm';
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$otp = new Otp;
|
||||
return $otp->checkTotp(Base32::decode($this->secret), $this->code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set validation code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
*/
|
||||
public function setCode($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set secret token
|
||||
*
|
||||
* @access public
|
||||
* @param string $secret
|
||||
*/
|
||||
public function setSecret($secret)
|
||||
{
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret token
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSecret()
|
||||
{
|
||||
if (empty($this->secret)) {
|
||||
$this->secret = GoogleAuthenticator::generateRandom();
|
||||
}
|
||||
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get QR code url
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @return string
|
||||
*/
|
||||
public function getQrCodeUrl($label)
|
||||
{
|
||||
if (empty($this->secret)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return GoogleAuthenticator::getQrCodeUrl('totp', $label, $this->secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key url (empty if no url can be provided)
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyUrl($label)
|
||||
{
|
||||
if (empty($this->secret)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return GoogleAuthenticator::getKeyUri('totp', $label, $this->secret);
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ class Action extends Base
|
|||
'available_events' => $this->action->getAvailableEvents(),
|
||||
'available_params' => $this->action->getAllActionParameters(),
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
|
||||
'projects_list' => $this->project->getList(false),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
|
@ -86,7 +86,7 @@ class Action extends Base
|
|||
'values' => $values,
|
||||
'action_params' => $action_params,
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
|
||||
'projects_list' => $projects_list,
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
|
|
|
@ -20,7 +20,7 @@ class Activity extends Base
|
|||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->template->layout('activity/project', array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'events' => $this->projectActivity->getProject($project['id']),
|
||||
'project' => $project,
|
||||
'title' => t('%s\'s activity', $project['name'])
|
||||
|
|
|
@ -20,7 +20,7 @@ class Analytic extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['content_for_sublayout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('analytic/layout', $params);
|
||||
|
@ -132,6 +132,9 @@ class Analytic extends Base
|
|||
* Common method for CFD and Burdown chart
|
||||
*
|
||||
* @access private
|
||||
* @param string $template
|
||||
* @param string $column
|
||||
* @param string $title
|
||||
*/
|
||||
private function commonAggregateMetrics($template, $column, $title)
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ class App extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['content_for_sublayout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('app/layout', $params);
|
||||
|
@ -42,7 +42,7 @@ class App extends Base
|
|||
->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id))
|
||||
->setMax($max)
|
||||
->setOrder('name')
|
||||
->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveMemberProjectIds($user_id)))
|
||||
->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id)))
|
||||
->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects');
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ class App extends Base
|
|||
|
||||
$this->response->html($this->layout('app/activity', array(
|
||||
'title' => t('My activity stream'),
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveMemberProjectIds($user['id']), 100),
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id']), 100),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
@ -202,49 +202,4 @@ class App extends Base
|
|||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Markdown text and reply with the HTML Code
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function preview()
|
||||
{
|
||||
$payload = $this->request->getJson();
|
||||
|
||||
if (empty($payload['text'])) {
|
||||
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
|
||||
}
|
||||
|
||||
$this->response->html($this->helper->text->markdown($payload['text']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Task autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$projects = $this->projectPermission->getActiveMemberProjectIds($this->userSession->getId());
|
||||
|
||||
if (empty($projects)) {
|
||||
$this->response->json(array());
|
||||
}
|
||||
|
||||
$filter = $this->taskFilterAutoCompleteFormatter
|
||||
->create()
|
||||
->filterByProjects($projects)
|
||||
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
|
||||
|
||||
// Search by task id or by title
|
||||
if (ctype_digit($search)) {
|
||||
$filter->filterById($search);
|
||||
} else {
|
||||
$filter->filterByTitle($search);
|
||||
}
|
||||
|
||||
$this->response->json($filter->format());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class Auth extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('auth/index', array(
|
||||
'captcha' => isset($values['username']) && $this->authentication->hasCaptcha($values['username']),
|
||||
'captcha' => ! empty($values['username']) && $this->userLocking->hasCaptcha($values['username']),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'no_layout' => true,
|
||||
|
@ -40,18 +40,11 @@ class Auth extends Base
|
|||
public function check()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
$this->sessionStorage->hasRememberMe = ! empty($values['remember_me']);
|
||||
list($valid, $errors) = $this->authentication->validateForm($values);
|
||||
|
||||
if ($valid) {
|
||||
if (isset($this->sessionStorage->redirectAfterLogin)
|
||||
&& ! empty($this->sessionStorage->redirectAfterLogin)
|
||||
&& ! filter_var($this->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
|
||||
$redirect = $this->sessionStorage->redirectAfterLogin;
|
||||
unset($this->sessionStorage->redirectAfterLogin);
|
||||
$this->response->redirect($redirect);
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
$this->redirectAfterLogin();
|
||||
}
|
||||
|
||||
$this->login($values, $errors);
|
||||
|
@ -64,7 +57,6 @@ class Auth extends Base
|
|||
*/
|
||||
public function logout()
|
||||
{
|
||||
$this->authentication->backend('rememberMe')->destroy($this->userSession->getId());
|
||||
$this->sessionManager->close();
|
||||
$this->response->redirect($this->helper->url->to('auth', 'login'));
|
||||
}
|
||||
|
@ -83,4 +75,20 @@ class Auth extends Base
|
|||
$this->sessionStorage->captcha = $builder->getPhrase();
|
||||
$builder->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user after the authentication
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function redirectAfterLogin()
|
||||
{
|
||||
if (isset($this->sessionStorage->redirectAfterLogin) && ! empty($this->sessionStorage->redirectAfterLogin) && ! filter_var($this->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) {
|
||||
$redirect = $this->sessionStorage->redirectAfterLogin;
|
||||
unset($this->sessionStorage->redirectAfterLogin);
|
||||
$this->response->redirect($redirect);
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Pimple\Container;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Base controller
|
||||
|
@ -14,36 +13,22 @@ use Symfony\Component\EventDispatcher\Event;
|
|||
abstract class Base extends \Kanboard\Core\Base
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Pimple\Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
if (DEBUG) {
|
||||
$this->logger->debug('START_REQUEST='.$_SERVER['REQUEST_URI']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
* Method executed before each action
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __destruct()
|
||||
public function beforeAction($controller, $action)
|
||||
{
|
||||
if (DEBUG) {
|
||||
foreach ($this->db->getLogMessages() as $message) {
|
||||
$this->logger->debug($message);
|
||||
}
|
||||
$this->sessionManager->open();
|
||||
$this->dispatcher->dispatch('app.bootstrap');
|
||||
$this->sendHeaders($action);
|
||||
$this->authenticationManager->checkCurrentSession();
|
||||
|
||||
$this->logger->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nbQueries));
|
||||
$this->logger->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT']));
|
||||
$this->logger->debug('MEMORY='.$this->helper->text->bytes(memory_get_usage()));
|
||||
$this->logger->debug('END_REQUEST='.$_SERVER['REQUEST_URI']);
|
||||
if (! $this->applicationAuthorization->isAllowed($controller, $action, Role::APP_PUBLIC)) {
|
||||
$this->handleAuthentication();
|
||||
$this->handlePostAuthentication($controller, $action);
|
||||
$this->checkApplicationAuthorization($controller, $action);
|
||||
$this->checkProjectAuthorization($controller, $action);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,34 +54,14 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed before each action
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeAction($controller, $action)
|
||||
{
|
||||
$this->sessionManager->open();
|
||||
$this->sendHeaders($action);
|
||||
$this->container['dispatcher']->dispatch('session.bootstrap', new Event);
|
||||
|
||||
if (! $this->acl->isPublicAction($controller, $action)) {
|
||||
$this->handleAuthentication();
|
||||
$this->handle2FA($controller, $action);
|
||||
$this->handleAuthorization($controller, $action);
|
||||
|
||||
$this->sessionStorage->hasSubtaskInProgress = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check authentication
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
*/
|
||||
public function handleAuthentication()
|
||||
private function handleAuthentication()
|
||||
{
|
||||
if (! $this->authentication->isAuthenticated()) {
|
||||
if (! $this->userSession->isLogged() && ! $this->authenticationManager->preAuthentication()) {
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
@ -107,15 +72,15 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
}
|
||||
|
||||
/**
|
||||
* Check 2FA
|
||||
* Handle Post-Authentication (2FA)
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
*/
|
||||
public function handle2FA($controller, $action)
|
||||
private function handlePostAuthentication($controller, $action)
|
||||
{
|
||||
$ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout');
|
||||
|
||||
if ($ignore === false && $this->userSession->has2FA() && ! $this->userSession->check2FA()) {
|
||||
if ($ignore === false && $this->userSession->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) {
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
@ -125,11 +90,23 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
}
|
||||
|
||||
/**
|
||||
* Check page access and authorization
|
||||
* Check application authorization
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
*/
|
||||
public function handleAuthorization($controller, $action)
|
||||
private function checkApplicationAuthorization($controller, $action)
|
||||
{
|
||||
if (! $this->helper->user->hasAccess($controller, $action)) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check project authorization
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function checkProjectAuthorization($controller, $action)
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$task_id = $this->request->getIntegerParam('task_id');
|
||||
|
@ -139,7 +116,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$project_id = $this->taskFinder->getProjectId($task_id);
|
||||
}
|
||||
|
||||
if (! $this->acl->isAllowed($controller, $action, $project_id)) {
|
||||
if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($controller, $action, $project_id)) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
@ -147,10 +124,10 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
/**
|
||||
* Application not found page (404 error)
|
||||
*
|
||||
* @access public
|
||||
* @access protected
|
||||
* @param boolean $no_layout Display the layout or not
|
||||
*/
|
||||
public function notfound($no_layout = false)
|
||||
protected function notfound($no_layout = false)
|
||||
{
|
||||
$this->response->html($this->template->layout('app/notfound', array(
|
||||
'title' => t('Page not found'),
|
||||
|
@ -161,11 +138,15 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
/**
|
||||
* Application forbidden page
|
||||
*
|
||||
* @access public
|
||||
* @access protected
|
||||
* @param boolean $no_layout Display the layout or not
|
||||
*/
|
||||
public function forbidden($no_layout = false)
|
||||
protected function forbidden($no_layout = false)
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('app/forbidden', array(
|
||||
'title' => t('Access Forbidden'),
|
||||
'no_layout' => $no_layout,
|
||||
|
@ -209,7 +190,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$content = $this->template->render($template, $params);
|
||||
$params['task_content_for_layout'] = $content;
|
||||
$params['title'] = $params['task']['project_name'].' > '.$params['task']['title'];
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
|
||||
return $this->template->layout('task/layout', $params);
|
||||
}
|
||||
|
@ -227,7 +208,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
$content = $this->template->render($template, $params);
|
||||
$params['project_content_for_layout'] = $content;
|
||||
$params['title'] = $params['project']['name'] === $params['title'] ? $params['title'] : $params['project']['name'].' > '.$params['title'];
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['sidebar_template'] = $sidebar_template;
|
||||
|
||||
return $this->template->layout('project/layout', $params);
|
||||
|
@ -300,12 +281,15 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
* Common method to get project filters
|
||||
*
|
||||
* @access protected
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @return array
|
||||
*/
|
||||
protected function getProjectFilters($controller, $action)
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
|
||||
$board_selector = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$board_selector = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
unset($board_selector[$project['id']]);
|
||||
|
||||
$filters = array(
|
||||
|
|
|
@ -51,7 +51,7 @@ class Board extends Base
|
|||
|
||||
$this->response->html($this->template->layout('board/view_private', array(
|
||||
'categories_list' => $this->category->getList($params['project']['id'], false),
|
||||
'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
|
||||
'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()),
|
||||
'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['project']['id']),
|
||||
'description' => $params['project']['description'],
|
||||
|
@ -142,195 +142,6 @@ class Board extends Base
|
|||
$this->response->html($this->renderBoard($project_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get links on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function tasklinks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_tasklinks', array(
|
||||
'links' => $this->taskLink->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subtasks on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function subtasks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_subtasks', array(
|
||||
'subtasks' => $this->subtask->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all attachments during the task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_files', array(
|
||||
'files' => $this->file->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display comments during a task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function comments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_comments', array(
|
||||
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting())
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display task description
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function description()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_description', array(
|
||||
'task' => $task
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a task assignee directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeAssignee()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_assignee', array(
|
||||
'values' => $task,
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an assignee modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateAssignee()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateAssigneeModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a task category directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeCategory()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_category', array(
|
||||
'values' => $task,
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a category modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateCategory()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateCategoryModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Screenshot popover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function screenshot()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('file/screenshot', array(
|
||||
'task' => $task,
|
||||
'redirect' => 'board',
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recurrence information on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function recurrence()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('task/recurring_info', array(
|
||||
'task' => $task,
|
||||
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
|
||||
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
|
||||
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display swimlane description in tooltip
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function swimlane()
|
||||
{
|
||||
$this->getProject();
|
||||
$swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
|
||||
$this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable collapsed mode
|
||||
*
|
||||
|
@ -355,6 +166,7 @@ class Board extends Base
|
|||
* Change display mode
|
||||
*
|
||||
* @access private
|
||||
* @param boolean $mode
|
||||
*/
|
||||
private function changeDisplayMode($mode)
|
||||
{
|
||||
|
@ -372,6 +184,7 @@ class Board extends Base
|
|||
* Render board
|
||||
*
|
||||
* @access private
|
||||
* @param integer $project_id
|
||||
*/
|
||||
private function renderBoard($project_id)
|
||||
{
|
||||
|
|
101
sources/app/Controller/BoardPopover.php
Normal file
101
sources/app/Controller/BoardPopover.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Board Popover
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class BoardPopover extends Base
|
||||
{
|
||||
/**
|
||||
* Change a task assignee directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeAssignee()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_assignee', array(
|
||||
'values' => $task,
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an assignee modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateAssignee()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateAssigneeModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a task category directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeCategory()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
|
||||
$this->response->html($this->template->render('board/popover_category', array(
|
||||
'values' => $task,
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a category modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function updateCategory()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateCategoryModification($values);
|
||||
|
||||
if ($valid && $this->taskModification->update($values)) {
|
||||
$this->flash->success(t('Task updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Screenshot popover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function screenshot()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('file/screenshot', array(
|
||||
'task' => $task,
|
||||
'redirect' => 'board',
|
||||
)));
|
||||
}
|
||||
}
|
112
sources/app/Controller/BoardTooltip.php
Normal file
112
sources/app/Controller/BoardTooltip.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Board Tooltip
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class BoardTooltip extends Base
|
||||
{
|
||||
/**
|
||||
* Get links on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function tasklinks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_tasklinks', array(
|
||||
'links' => $this->taskLink->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subtasks on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function subtasks()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->response->html($this->template->render('board/tooltip_subtasks', array(
|
||||
'subtasks' => $this->subtask->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all attachments during the task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_files', array(
|
||||
'files' => $this->file->getAll($task['id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display comments during a task mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function comments()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_comments', array(
|
||||
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting())
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display task description
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function description()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('board/tooltip_description', array(
|
||||
'task' => $task
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recurrence information on mouseover
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function recurrence()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->template->render('task/recurring_info', array(
|
||||
'task' => $task,
|
||||
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
|
||||
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
|
||||
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display swimlane description in tooltip
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function swimlane()
|
||||
{
|
||||
$this->getProject();
|
||||
$swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
|
||||
$this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class Config extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['values'] = $this->config->getAll();
|
||||
$params['errors'] = array();
|
||||
$params['config_content_for_layout'] = $this->template->render($template, $params);
|
||||
|
|
|
@ -20,7 +20,7 @@ class Currency extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['config_content_for_layout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('config/layout', $params);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Custom Filter management
|
||||
*
|
||||
|
@ -137,7 +139,7 @@ class Customfilter extends Base
|
|||
{
|
||||
$user_id = $this->userSession->getId();
|
||||
|
||||
if ($filter['user_id'] != $user_id && (! $this->projectPermission->isManager($project['id'], $user_id) || ! $this->userSession->isAdmin())) {
|
||||
if ($filter['user_id'] != $user_id && ($this->projectUserRole->getUserRole($project['id'], $user_id) === Role::PROJECT_MANAGER || ! $this->userSession->isAdmin())) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class Doc extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,8 @@ class Feed extends Base
|
|||
$this->forbidden(true);
|
||||
}
|
||||
|
||||
$projects = $this->projectPermission->getActiveMemberProjects($user['id']);
|
||||
|
||||
$this->response->xml($this->template->render('feed/user', array(
|
||||
'events' => $this->projectActivity->getProjects(array_keys($projects)),
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id'])),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ class Gantt extends Base
|
|||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('gantt/projects', array(
|
||||
'projects' => $this->projectGanttFormatter->filter($project_ids)->format(),
|
||||
'title' => t('Gantt chart for all projects'),
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ class Gantt extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->template->layout('gantt/project', $params + array(
|
||||
'users_list' => $this->projectPermission->getMemberList($params['project']['id'], false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
|
||||
'sorting' => $sorting,
|
||||
'tasks' => $filter->format(),
|
||||
)));
|
||||
|
@ -109,7 +109,7 @@ class Gantt extends Base
|
|||
'column_id' => $this->board->getFirstColumn($project['id']),
|
||||
'position' => 1
|
||||
),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id'], true, false, true),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'swimlanes_list' => $this->swimlane->getList($project['id'], false, true),
|
||||
|
|
255
sources/app/Controller/Group.php
Normal file
255
sources/app/Controller/Group.php
Normal file
|
@ -0,0 +1,255 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Group Controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Group extends Base
|
||||
{
|
||||
/**
|
||||
* List all groups
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$paginator = $this->paginator
|
||||
->setUrl('group', 'index')
|
||||
->setMax(30)
|
||||
->setOrder('name')
|
||||
->setQuery($this->group->getQuery())
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('group/index', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Groups').' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* List all users
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$group = $this->group->getById($group_id);
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('group', 'users')
|
||||
->setMax(30)
|
||||
->setOrder('username')
|
||||
->setQuery($this->groupMember->getQuery($group_id))
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('group/users', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Members of %s', $group['name']).' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
'group' => $group,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a new group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create(array $values = array(), array $errors = array())
|
||||
{
|
||||
$this->response->html($this->template->layout('group/create', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('New group')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->group->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->group->create($values['name']) !== false) {
|
||||
$this->flash->success(t('Group created successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('group', 'index'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to create your group.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->create($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to update a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit(array $values = array(), array $errors = array())
|
||||
{
|
||||
if (empty($values)) {
|
||||
$values = $this->group->getById($this->request->getIntegerParam('group_id'));
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('group/edit', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('Edit group')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->group->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->group->update($values) !== false) {
|
||||
$this->flash->success(t('Group updated successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('group', 'index'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update your group.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->edit($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to associate a user to a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function associate(array $values = array(), array $errors = array())
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$group = $this->group->getbyId($group_id);
|
||||
|
||||
if (empty($values)) {
|
||||
$values['group_id'] = $group_id;
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('group/associate', array(
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'users' => $this->user->prepareList($this->groupMember->getNotMembers($group_id)),
|
||||
'group' => $group,
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'title' => t('Add group member to "%s"', $group['name']),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function addUser()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if (isset($values['group_id']) && isset($values['user_id'])) {
|
||||
if ($this->groupMember->addUser($values['group_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Group member added successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $values['group_id'])));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to add group member.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->associate($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog to remove a user from a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function dissociate()
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
$group = $this->group->getById($group_id);
|
||||
$user = $this->user->getById($user_id);
|
||||
|
||||
$this->response->html($this->template->layout('group/dissociate', array(
|
||||
'group' => $group,
|
||||
'user' => $user,
|
||||
'title' => t('Remove user from group "%s"', $group['name']),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user from a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function removeUser()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
|
||||
if ($this->groupMember->removeUser($group_id, $user_id)) {
|
||||
$this->flash->success(t('User removed successfully from this group.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to remove this user from the group.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $group_id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog to remove a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
$group = $this->group->getById($group_id);
|
||||
|
||||
$this->response->html($this->template->layout('group/remove', array(
|
||||
'group' => $group,
|
||||
'title' => t('Remove group'),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a group
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$group_id = $this->request->getIntegerParam('group_id');
|
||||
|
||||
if ($this->group->remove($group_id)) {
|
||||
$this->flash->success(t('Group removed successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to remove this group.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('group', 'index'));
|
||||
}
|
||||
}
|
24
sources/app/Controller/GroupHelper.php
Normal file
24
sources/app/Controller/GroupHelper.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Group Helper
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GroupHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Group autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$groups = $this->groupManager->find($search);
|
||||
$this->response->json($this->groupAutoCompleteFormatter->setGroups($groups)->format());
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ class Link extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['config_content_for_layout'] = $this->template->render($template, $params);
|
||||
|
||||
return $this->template->layout('config/layout', $params);
|
||||
|
|
|
@ -17,7 +17,7 @@ class Oauth extends Base
|
|||
*/
|
||||
public function google()
|
||||
{
|
||||
$this->step1('google');
|
||||
$this->step1('Google');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,7 @@ class Oauth extends Base
|
|||
*/
|
||||
public function github()
|
||||
{
|
||||
$this->step1('github');
|
||||
$this->step1('Github');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ class Oauth extends Base
|
|||
*/
|
||||
public function gitlab()
|
||||
{
|
||||
$this->step1('gitlab');
|
||||
$this->step1('Gitlab');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,12 +45,12 @@ class Oauth extends Base
|
|||
*
|
||||
* @access public
|
||||
*/
|
||||
public function unlink($backend = '')
|
||||
public function unlink()
|
||||
{
|
||||
$backend = $this->request->getStringParam('backend', $backend);
|
||||
$backend = $this->request->getStringParam('backend');
|
||||
$this->checkCSRFParam();
|
||||
|
||||
if ($this->authentication->backend($backend)->unlink($this->userSession->getId())) {
|
||||
if ($this->authenticationManager->getProvider($backend)->unlink($this->userSession->getId())) {
|
||||
$this->flash->success(t('Your external account is not linked anymore to your profile.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to unlink your external account.'));
|
||||
|
@ -63,15 +63,16 @@ class Oauth extends Base
|
|||
* Redirect to the provider if no code received
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
private function step1($backend)
|
||||
private function step1($provider)
|
||||
{
|
||||
$code = $this->request->getStringParam('code');
|
||||
|
||||
if (! empty($code)) {
|
||||
$this->step2($backend, $code);
|
||||
$this->step2($provider, $code);
|
||||
} else {
|
||||
$this->response->redirect($this->authentication->backend($backend)->getService()->getAuthorizationUrl());
|
||||
$this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,30 +80,35 @@ class Oauth extends Base
|
|||
* Link or authenticate the user
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
* @param string $code
|
||||
*/
|
||||
private function step2($backend, $code)
|
||||
private function step2($provider, $code)
|
||||
{
|
||||
$profile = $this->authentication->backend($backend)->getProfile($code);
|
||||
$this->authenticationManager->getProvider($provider)->setCode($code);
|
||||
|
||||
if ($this->userSession->isLogged()) {
|
||||
$this->link($backend, $profile);
|
||||
$this->link($provider);
|
||||
}
|
||||
|
||||
$this->authenticate($backend, $profile);
|
||||
$this->authenticate($provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link the account
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
private function link($backend, $profile)
|
||||
private function link($provider)
|
||||
{
|
||||
if (empty($profile)) {
|
||||
$authProvider = $this->authenticationManager->getProvider($provider);
|
||||
|
||||
if (! $authProvider->authenticate()) {
|
||||
$this->flash->failure(t('External authentication failed'));
|
||||
} else {
|
||||
$this->userProfile->assign($this->userSession->getId(), $authProvider->getUser());
|
||||
$this->flash->success(t('Your external account is linked to your profile successfully.'));
|
||||
$this->authentication->backend($backend)->updateUser($this->userSession->getId(), $profile);
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
|
||||
|
@ -112,10 +118,11 @@ class Oauth extends Base
|
|||
* Authenticate the account
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
private function authenticate($backend, $profile)
|
||||
private function authenticate($provider)
|
||||
{
|
||||
if (! empty($profile) && $this->authentication->backend($backend)->authenticate($profile['id'])) {
|
||||
if ($this->authenticationManager->oauthAuthentication($provider)) {
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
} else {
|
||||
$this->response->html($this->template->layout('auth/index', array(
|
||||
|
|
|
@ -20,7 +20,7 @@ class Project extends Base
|
|||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
|
||||
$nb_projects = count($project_ids);
|
||||
|
@ -33,7 +33,7 @@ class Project extends Base
|
|||
->calculate();
|
||||
|
||||
$this->response->html($this->template->layout('project/index', array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'paginator' => $paginator,
|
||||
'nb_projects' => $nb_projects,
|
||||
'title' => t('Projects').' ('.$nb_projects.')'
|
||||
|
@ -160,11 +160,11 @@ class Project extends Base
|
|||
$values = $this->request->getValues();
|
||||
|
||||
if (isset($values['is_private'])) {
|
||||
if (! $this->helper->user->isProjectAdministrationAllowed($project['id'])) {
|
||||
if (! $this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
|
||||
unset($values['is_private']);
|
||||
}
|
||||
} elseif ($project['is_private'] == 1 && ! isset($values['is_private'])) {
|
||||
if ($this->helper->user->isProjectAdministrationAllowed($project['id'])) {
|
||||
if ($this->helper->user->hasProjectAccess('project', 'create', $project['id'])) {
|
||||
$values += array('is_private' => 0);
|
||||
}
|
||||
}
|
||||
|
@ -183,120 +183,6 @@ class Project extends Base
|
|||
$this->edit($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Users list for the selected project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->projectLayout('project/users', array(
|
||||
'project' => $project,
|
||||
'users' => $this->projectPermission->getAllUsers($project['id']),
|
||||
'title' => t('Edit project access list')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow everybody
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function allowEverybody()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
|
||||
list($valid, ) = $this->projectPermission->validateProjectModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->project->update($values)) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $project['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow a specific user (admin only)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function allow()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, ) = $this->projectPermission->validateUserModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->projectPermission->addMember($values['project_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the role of a project member
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function role()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
'is_owner' => $this->request->getIntegerParam('is_owner'),
|
||||
);
|
||||
|
||||
list($valid, ) = $this->projectPermission->validateUserModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->projectPermission->changeRole($values['project_id'], $values['user_id'], $values['is_owner'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke user access (admin only)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function revoke()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
);
|
||||
|
||||
list($valid, ) = $this->projectPermission->validateUserModification($values);
|
||||
|
||||
if ($valid) {
|
||||
if ($this->projectPermission->revokeMember($values['project_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a project
|
||||
*
|
||||
|
@ -413,17 +299,28 @@ class Project extends Base
|
|||
*/
|
||||
public function create(array $values = array(), array $errors = array())
|
||||
{
|
||||
$is_private = $this->request->getIntegerParam('private', $this->userSession->isAdmin() || $this->userSession->isProjectAdmin() ? 0 : 1);
|
||||
$is_private = isset($values['is_private']) && $values['is_private'] == 1;
|
||||
|
||||
$this->response->html($this->template->layout('project/new', array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'values' => empty($values) ? array('is_private' => $is_private) : $values,
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'is_private' => $is_private,
|
||||
'title' => $is_private ? t('New private project') : t('New project'),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a private project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function createPrivate(array $values = array(), array $errors = array())
|
||||
{
|
||||
$values['is_private'] = 1;
|
||||
$this->create($values, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new project
|
||||
*
|
||||
|
|
177
sources/app/Controller/ProjectPermission.php
Normal file
177
sources/app/Controller/ProjectPermission.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Project Permission
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectPermission extends Base
|
||||
{
|
||||
/**
|
||||
* Show all permissions
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index(array $values = array(), array $errors = array())
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
if (empty($values)) {
|
||||
$values['role'] = Role::PROJECT_MEMBER;
|
||||
}
|
||||
|
||||
$this->response->html($this->projectLayout('project_permission/index', array(
|
||||
'project' => $project,
|
||||
'users' => $this->projectUserRole->getUsers($project['id']),
|
||||
'groups' => $this->projectGroupRole->getGroups($project['id']),
|
||||
'roles' => $this->role->getProjectRoles(),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'title' => t('Project Permissions'),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow everybody
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function allowEverybody()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
|
||||
|
||||
if ($this->project->update($values)) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $project['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to the project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function addUser()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke user access
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function removeUser()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
);
|
||||
|
||||
if ($this->projectUserRole->removeUser($values['project_id'], $values['user_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change user role
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeUserRole()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getJson();
|
||||
|
||||
if (! empty($project_id) && ! empty($values) && $this->projectUserRole->changeUserRole($project_id, $values['id'], $values['role'])) {
|
||||
$this->response->json(array('status' => 'ok'));
|
||||
} else {
|
||||
$this->response->json(array('status' => 'error'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add group to the project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function addGroup()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if (empty($values['group_id']) && ! empty($values['external_id'])) {
|
||||
$values['group_id'] = $this->group->create($values['name'], $values['external_id']);
|
||||
}
|
||||
|
||||
if ($this->projectGroupRole->addGroup($values['project_id'], $values['group_id'], $values['role'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke group access
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function removeGroup()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'group_id' => $this->request->getIntegerParam('group_id'),
|
||||
);
|
||||
|
||||
if ($this->projectGroupRole->removeGroup($values['project_id'], $values['group_id'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('ProjectPermission', 'index', array('project_id' => $values['project_id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change group role
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function changeGroupRole()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getJson();
|
||||
|
||||
if (! empty($project_id) && ! empty($values) && $this->projectGroupRole->changeGroupRole($project_id, $values['id'], $values['role'])) {
|
||||
$this->response->json(array('status' => 'ok'));
|
||||
} else {
|
||||
$this->response->json(array('status' => 'error'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace Kanboard\Controller;
|
|||
|
||||
use Kanboard\Model\User as UserModel;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* Project User overview
|
||||
|
@ -23,7 +24,7 @@ class Projectuser extends Base
|
|||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$params['content_for_sublayout'] = $this->template->render($template, $params);
|
||||
$params['filter'] = array('user_id' => $params['user_id']);
|
||||
|
||||
|
@ -37,17 +38,17 @@ class Projectuser extends Base
|
|||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
|
||||
return array($user_id, $project_ids, $this->user->getList(true));
|
||||
}
|
||||
|
||||
private function role($is_owner, $action, $title, $title_user)
|
||||
private function role($role, $action, $title, $title_user)
|
||||
{
|
||||
list($user_id, $project_ids, $users) = $this->common();
|
||||
|
||||
$query = $this->projectPermission->getQueryByRole($project_ids, $is_owner)->callback(array($this->project, 'applyColumnStats'));
|
||||
$query = $this->projectPermission->getQueryByRole($project_ids, $role)->callback(array($this->project, 'applyColumnStats'));
|
||||
|
||||
if ($user_id !== UserModel::EVERYBODY_ID) {
|
||||
$query->eq(UserModel::TABLE.'.id', $user_id);
|
||||
|
@ -101,7 +102,7 @@ class Projectuser extends Base
|
|||
*/
|
||||
public function managers()
|
||||
{
|
||||
$this->role(1, 'managers', t('People who are project managers'), 'Projects where "%s" is manager');
|
||||
$this->role(Role::PROJECT_MANAGER, 'managers', t('People who are project managers'), 'Projects where "%s" is manager');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,7 +111,7 @@ class Projectuser extends Base
|
|||
*/
|
||||
public function members()
|
||||
{
|
||||
$this->role(0, 'members', t('People who are project members'), 'Projects where "%s" is member');
|
||||
$this->role(ROLE::PROJECT_MEMBER, 'members', t('People who are project members'), 'Projects where "%s" is member');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,7 +12,7 @@ class Search extends Base
|
|||
{
|
||||
public function index()
|
||||
{
|
||||
$projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$projects = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
$search = urldecode($this->request->getStringParam('search'));
|
||||
$nb_tasks = 0;
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class Subtask extends Base
|
|||
$this->response->html($this->taskLayout('subtask/create', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
|
||||
'task' => $task,
|
||||
)));
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ class Subtask extends Base
|
|||
$this->response->html($this->taskLayout('subtask/edit', array(
|
||||
'values' => empty($values) ? $subtask : $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
|
||||
'status_list' => $this->subtask->getStatusList(),
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
|
|
|
@ -76,7 +76,7 @@ class Task extends Base
|
|||
'link_label_list' => $this->link->getList(0, false),
|
||||
'columns_list' => $this->board->getColumnsList($task['project_id']),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'users_list' => $this->projectPermission->getMemberList($task['project_id'], true, false, false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id'], true, false, false),
|
||||
'date_format' => $this->config->get('application_date_format'),
|
||||
'date_formats' => $this->dateParser->getAvailableFormats(),
|
||||
'title' => $task['project_name'].' > '.$task['title'],
|
||||
|
|
57
sources/app/Controller/TaskHelper.php
Normal file
57
sources/app/Controller/TaskHelper.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* Task Ajax Helper
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Render Markdown text and reply with the HTML Code
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function preview()
|
||||
{
|
||||
$payload = $this->request->getJson();
|
||||
|
||||
if (empty($payload['text'])) {
|
||||
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
|
||||
}
|
||||
|
||||
$this->response->html($this->helper->text->markdown($payload['text']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Task autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$projects = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
|
||||
if (empty($projects)) {
|
||||
$this->response->json(array());
|
||||
}
|
||||
|
||||
$filter = $this->taskFilterAutoCompleteFormatter
|
||||
->create()
|
||||
->filterByProjects($projects)
|
||||
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
|
||||
|
||||
// Search by task id or by title
|
||||
if (ctype_digit($search)) {
|
||||
$filter->filterById($search);
|
||||
} else {
|
||||
$filter->filterByTitle($search);
|
||||
}
|
||||
|
||||
$this->response->json($filter->format());
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ class Taskcreation extends Base
|
|||
'errors' => $errors,
|
||||
'values' => $values + array('project_id' => $project['id']),
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id'], true, false, true),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'swimlanes_list' => $swimlanes_list,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Model\Project as ProjectModel;
|
||||
|
||||
/**
|
||||
* Task Duplication controller
|
||||
*
|
||||
|
@ -107,7 +109,7 @@ class Taskduplication extends Base
|
|||
private function chooseDestination(array $task, $template)
|
||||
{
|
||||
$values = array();
|
||||
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
|
||||
$projects_list = $this->projectUserRole->getProjectsByUser($this->userSession->getId(), array(ProjectModel::ACTIVE));
|
||||
|
||||
unset($projects_list[$task['project_id']]);
|
||||
|
||||
|
@ -117,7 +119,7 @@ class Taskduplication extends Base
|
|||
$swimlanes_list = $this->swimlane->getList($dst_project_id, false, true);
|
||||
$columns_list = $this->board->getColumnsList($dst_project_id);
|
||||
$categories_list = $this->category->getList($dst_project_id);
|
||||
$users_list = $this->projectPermission->getMemberList($dst_project_id);
|
||||
$users_list = $this->projectUserRole->getAssignableUsersList($dst_project_id);
|
||||
|
||||
$values = $this->taskDuplication->checkDestinationProjectValues($task);
|
||||
$values['project_id'] = $dst_project_id;
|
||||
|
|
|
@ -110,7 +110,7 @@ class Taskmodification extends Base
|
|||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'task' => $task,
|
||||
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
|
||||
'colors_list' => $this->color->getList(),
|
||||
'categories_list' => $this->category->getList($task['project_id']),
|
||||
'date_format' => $this->config->get('application_date_format'),
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Otp\Otp;
|
||||
use Otp\GoogleAuthenticator;
|
||||
use Base32\Base32;
|
||||
|
||||
/**
|
||||
* Two Factor Auth controller
|
||||
*
|
||||
|
@ -36,12 +32,15 @@ class Twofactor extends User
|
|||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$label = $user['email'] ?: $user['username'];
|
||||
|
||||
$provider->setSecret($user['twofactor_secret']);
|
||||
|
||||
$this->response->html($this->layout('twofactor/index', array(
|
||||
'user' => $user,
|
||||
'qrcode_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getQrCodeUrl('totp', $label, $user['twofactor_secret']) : '',
|
||||
'key_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getKeyUri('totp', $label, $user['twofactor_secret']) : '',
|
||||
'qrcode_url' => $user['twofactor_activated'] == 1 ? $provider->getQrCodeUrl($label) : '',
|
||||
'key_url' => $user['twofactor_activated'] == 1 ? $provider->getKeyUrl($label) : '',
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -61,7 +60,7 @@ class Twofactor extends User
|
|||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 1,
|
||||
'twofactor_secret' => GoogleAuthenticator::generateRandom(),
|
||||
'twofactor_secret' => $this->authenticationManager->getPostAuthenticationProvider()->getSecret(),
|
||||
));
|
||||
} else {
|
||||
$this->user->update(array(
|
||||
|
@ -72,14 +71,14 @@ class Twofactor extends User
|
|||
}
|
||||
|
||||
// Allow the user to test or disable the feature
|
||||
$this->userSession->disable2FA();
|
||||
$this->userSession->disablePostAuthentication();
|
||||
|
||||
$this->flash->success(t('User updated successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 2FA
|
||||
* Test code
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
|
@ -88,10 +87,13 @@ class Twofactor extends User
|
|||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$otp = new Otp;
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) {
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$provider->setCode(empty($values['code']) ? '' : $values['code']);
|
||||
$provider->setSecret($user['twofactor_secret']);
|
||||
|
||||
if ($provider->authenticate()) {
|
||||
$this->flash->success(t('The two factor authentication code is valid.'));
|
||||
} else {
|
||||
$this->flash->failure(t('The two factor authentication code is not valid.'));
|
||||
|
@ -110,11 +112,14 @@ class Twofactor extends User
|
|||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$otp = new Otp;
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) {
|
||||
$this->sessionStorage->postAuth['validated'] = true;
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$provider->setCode(empty($values['code']) ? '' : $values['code']);
|
||||
$provider->setSecret($user['twofactor_secret']);
|
||||
|
||||
if ($provider->authenticate()) {
|
||||
$this->userSession->validatePostAuthentication();
|
||||
$this->flash->success(t('The two factor authentication code is valid.'));
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
} else {
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Notification\Mail as MailNotification;
|
||||
use Kanboard\Model\Project as ProjectModel;
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* User controller
|
||||
|
@ -24,7 +26,7 @@ class User extends Base
|
|||
{
|
||||
$content = $this->template->render($template, $params);
|
||||
$params['user_content_for_layout'] = $content;
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
|
||||
$params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
|
||||
|
||||
if (isset($params['user'])) {
|
||||
$params['title'] = ($params['user']['name'] ?: $params['user']['username']).' (#'.$params['user']['id'].')';
|
||||
|
@ -49,7 +51,7 @@ class User extends Base
|
|||
|
||||
$this->response->html(
|
||||
$this->template->layout('user/index', array(
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'title' => t('Users').' ('.$paginator->getTotal().')',
|
||||
'paginator' => $paginator,
|
||||
)));
|
||||
|
@ -67,10 +69,11 @@ class User extends Base
|
|||
$this->response->html($this->template->layout($is_remote ? 'user/create_remote' : 'user/create_local', array(
|
||||
'timezones' => $this->config->getTimezones(true),
|
||||
'languages' => $this->config->getLanguages(true),
|
||||
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
|
||||
'roles' => $this->role->getApplicationRoles(),
|
||||
'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()),
|
||||
'projects' => $this->project->getList(),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'values' => $values + array('role' => Role::APP_USER),
|
||||
'title' => t('New user')
|
||||
)));
|
||||
}
|
||||
|
@ -92,7 +95,7 @@ class User extends Base
|
|||
$user_id = $this->user->create($values);
|
||||
|
||||
if ($user_id !== false) {
|
||||
$this->projectPermission->addMember($project_id, $user_id);
|
||||
$this->projectUserRole->addUser($project_id, $user_id, Role::PROJECT_MEMBER);
|
||||
|
||||
if (! empty($values['notifications_enabled'])) {
|
||||
$this->userNotificationType->saveSelectedTypes($user_id, array(MailNotification::TYPE));
|
||||
|
@ -170,7 +173,7 @@ class User extends Base
|
|||
{
|
||||
$user = $this->getUser();
|
||||
$this->response->html($this->layout('user/sessions', array(
|
||||
'sessions' => $this->authentication->backend('rememberMe')->getAll($user['id']),
|
||||
'sessions' => $this->rememberMeSession->getAll($user['id']),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
@ -184,8 +187,8 @@ class User extends Base
|
|||
{
|
||||
$this->checkCSRFParam();
|
||||
$user = $this->getUser();
|
||||
$this->authentication->backend('rememberMe')->remove($this->request->getIntegerParam('id'));
|
||||
$this->response->redirect($this->helper->url->to('user', 'session', array('user_id' => $user['id'])));
|
||||
$this->rememberMeSession->remove($this->request->getIntegerParam('id'));
|
||||
$this->response->redirect($this->helper->url->to('user', 'sessions', array('user_id' => $user['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,7 +208,7 @@ class User extends Base
|
|||
}
|
||||
|
||||
$this->response->html($this->layout('user/notifications', array(
|
||||
'projects' => $this->projectPermission->getMemberProjects($user['id']),
|
||||
'projects' => $this->projectUserRole->getProjectsByUser($user['id'], array(ProjectModel::ACTIVE)),
|
||||
'notifications' => $this->userNotification->readSettings($user['id']),
|
||||
'types' => $this->userNotificationType->getTypes(),
|
||||
'filters' => $this->userNotificationFilter->getFilters(),
|
||||
|
@ -326,16 +329,9 @@ class User extends Base
|
|||
if ($this->request->isPost()) {
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($this->userSession->isAdmin()) {
|
||||
$values += array('is_admin' => 0, 'is_project_admin' => 0);
|
||||
} else {
|
||||
// Regular users can't be admin
|
||||
if (isset($values['is_admin'])) {
|
||||
unset($values['is_admin']);
|
||||
}
|
||||
|
||||
if (isset($values['is_project_admin'])) {
|
||||
unset($values['is_project_admin']);
|
||||
if (! $this->userSession->isAdmin()) {
|
||||
if (isset($values['role'])) {
|
||||
unset($values['role']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,6 +354,7 @@ class User extends Base
|
|||
'user' => $user,
|
||||
'timezones' => $this->config->getTimezones(true),
|
||||
'languages' => $this->config->getLanguages(true),
|
||||
'roles' => $this->role->getApplicationRoles(),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
24
sources/app/Controller/UserHelper.php
Normal file
24
sources/app/Controller/UserHelper.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
/**
|
||||
* User Helper
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserHelper extends Base
|
||||
{
|
||||
/**
|
||||
* User autocompletion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$users = $this->userFilterAutoCompleteFormatter->create($search)->filterByUsernameOrByName()->format();
|
||||
$this->response->json($users);
|
||||
}
|
||||
}
|
|
@ -5,29 +5,43 @@ namespace Kanboard\Core;
|
|||
use Pimple\Container;
|
||||
|
||||
/**
|
||||
* Base class
|
||||
* Base Class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @property \Kanboard\Core\Session\SessionManager $sessionManager
|
||||
* @property \Kanboard\Core\Session\SessionStorage $sessionStorage
|
||||
* @property \Kanboard\Core\Session\FlashMessage $flash
|
||||
* @property \Kanboard\Core\Helper $helper
|
||||
* @property \Kanboard\Core\Mail\Client $emailClient
|
||||
* @property \Kanboard\Core\Paginator $paginator
|
||||
* @property \Kanboard\Core\Cache\MemoryCache $memoryCache
|
||||
* @property \Kanboard\Core\Group\GroupManager $groupManager
|
||||
* @property \Kanboard\Core\Http\Client $httpClient
|
||||
* @property \Kanboard\Core\Http\OAuth2 $oauth
|
||||
* @property \Kanboard\Core\Http\RememberMeCookie $rememberMeCookie
|
||||
* @property \Kanboard\Core\Http\Request $request
|
||||
* @property \Kanboard\Core\Http\Router $router
|
||||
* @property \Kanboard\Core\Http\Response $response
|
||||
* @property \Kanboard\Core\Template $template
|
||||
* @property \Kanboard\Core\OAuth2 $oauth
|
||||
* @property \Kanboard\Core\Lexer $lexer
|
||||
* @property \Kanboard\Core\Http\Router $router
|
||||
* @property \Kanboard\Core\Mail\Client $emailClient
|
||||
* @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\AccessMap $projectAccessMap
|
||||
* @property \Kanboard\Core\Security\AuthenticationManager $authenticationManager
|
||||
* @property \Kanboard\Core\Security\AccessMap $applicationAccessMap
|
||||
* @property \Kanboard\Core\Security\AccessMap $projectAccessMap
|
||||
* @property \Kanboard\Core\Security\Authorization $applicationAuthorization
|
||||
* @property \Kanboard\Core\Security\Authorization $projectAuthorization
|
||||
* @property \Kanboard\Core\Security\Role $role
|
||||
* @property \Kanboard\Core\Security\Token $token
|
||||
* @property \Kanboard\Core\Session\FlashMessage $flash
|
||||
* @property \Kanboard\Core\Session\SessionManager $sessionManager
|
||||
* @property \Kanboard\Core\Session\SessionStorage $sessionStorage
|
||||
* @property \Kanboard\Core\User\GroupSync $groupSync
|
||||
* @property \Kanboard\Core\User\UserProfile $userProfile
|
||||
* @property \Kanboard\Core\User\UserSync $userSync
|
||||
* @property \Kanboard\Core\User\UserSession $userSession
|
||||
* @property \Kanboard\Core\DateParser $dateParser
|
||||
* @property \Kanboard\Core\Helper $helper
|
||||
* @property \Kanboard\Core\Lexer $lexer
|
||||
* @property \Kanboard\Core\Paginator $paginator
|
||||
* @property \Kanboard\Core\Template $template
|
||||
* @property \Kanboard\Integration\BitbucketWebhook $bitbucketWebhook
|
||||
* @property \Kanboard\Integration\GithubWebhook $githubWebhook
|
||||
* @property \Kanboard\Integration\GitlabWebhook $gitlabWebhook
|
||||
|
@ -36,7 +50,8 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Formatter\TaskFilterAutoCompleteFormatter $taskFilterAutoCompleteFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterCalendarFormatter $taskFilterCalendarFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterICalendarFormatter $taskFilterICalendarFormatter
|
||||
* @property \Kanboard\Model\Acl $acl
|
||||
* @property \Kanboard\Formatter\UserFilterAutoCompleteFormatter $userFilterAutoCompleteFormatter
|
||||
* @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter
|
||||
* @property \Kanboard\Model\Action $action
|
||||
* @property \Kanboard\Model\Authentication $authentication
|
||||
* @property \Kanboard\Model\Board $board
|
||||
|
@ -46,8 +61,9 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\Config $config
|
||||
* @property \Kanboard\Model\Currency $currency
|
||||
* @property \Kanboard\Model\CustomFilter $customFilter
|
||||
* @property \Kanboard\Model\DateParser $dateParser
|
||||
* @property \Kanboard\Model\File $file
|
||||
* @property \Kanboard\Model\Group $group
|
||||
* @property \Kanboard\Model\GroupMember $groupMember
|
||||
* @property \Kanboard\Model\LastLogin $lastLogin
|
||||
* @property \Kanboard\Model\Link $link
|
||||
* @property \Kanboard\Model\Notification $notification
|
||||
|
@ -60,8 +76,11 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\ProjectDailyStats $projectDailyStats
|
||||
* @property \Kanboard\Model\ProjectMetadata $projectMetadata
|
||||
* @property \Kanboard\Model\ProjectPermission $projectPermission
|
||||
* @property \Kanboard\Model\ProjectUserRole $projectUserRole
|
||||
* @property \Kanboard\Model\ProjectGroupRole $projectGroupRole
|
||||
* @property \Kanboard\Model\ProjectNotification $projectNotification
|
||||
* @property \Kanboard\Model\ProjectNotificationType $projectNotificationType
|
||||
* @property \Kanboard\Model\RememberMeSession $rememberMeSession
|
||||
* @property \Kanboard\Model\Subtask $subtask
|
||||
* @property \Kanboard\Model\SubtaskExport $subtaskExport
|
||||
* @property \Kanboard\Model\SubtaskTimeTracking $subtaskTimeTracking
|
||||
|
@ -84,16 +103,17 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\Transition $transition
|
||||
* @property \Kanboard\Model\User $user
|
||||
* @property \Kanboard\Model\UserImport $userImport
|
||||
* @property \Kanboard\Model\UserLocking $userLocking
|
||||
* @property \Kanboard\Model\UserNotification $userNotification
|
||||
* @property \Kanboard\Model\UserNotificationType $userNotificationType
|
||||
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
|
||||
* @property \Kanboard\Model\UserUnreadNotification $userUnreadNotification
|
||||
* @property \Kanboard\Model\UserSession $userSession
|
||||
* @property \Kanboard\Model\UserMetadata $userMetadata
|
||||
* @property \Kanboard\Model\Webhook $webhook
|
||||
* @property \Psr\Log\LoggerInterface $logger
|
||||
* @property \League\HTMLToMarkdown\HtmlConverter $htmlConverter
|
||||
* @property \PicoDb\Database $db
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
*/
|
||||
abstract class Base
|
||||
{
|
||||
|
|
|
@ -23,7 +23,7 @@ class MemoryCache extends Base implements CacheInterface
|
|||
*
|
||||
* @access public
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
|
|
|
@ -93,7 +93,7 @@ class Csv
|
|||
{
|
||||
if (! empty($value)) {
|
||||
$value = trim(strtolower($value));
|
||||
return $value === '1' || $value{0} === 't' ? 1 : 0;
|
||||
return $value === '1' || $value{0} === 't' || $value{0} === 'y' ? 1 : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
21
sources/app/Core/Group/GroupBackendProviderInterface.php
Normal file
21
sources/app/Core/Group/GroupBackendProviderInterface.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Group;
|
||||
|
||||
/**
|
||||
* Group Backend Provider Interface
|
||||
*
|
||||
* @package group
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface GroupBackendProviderInterface
|
||||
{
|
||||
/**
|
||||
* Find a group from a search query
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return GroupProviderInterface[]
|
||||
*/
|
||||
public function find($input);
|
||||
}
|
71
sources/app/Core/Group/GroupManager.php
Normal file
71
sources/app/Core/Group/GroupManager.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Group;
|
||||
|
||||
/**
|
||||
* Group Manager
|
||||
*
|
||||
* @package group
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GroupManager
|
||||
{
|
||||
/**
|
||||
* List of backend providers
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $providers = array();
|
||||
|
||||
/**
|
||||
* Register a new group backend provider
|
||||
*
|
||||
* @access public
|
||||
* @param GroupBackendProviderInterface $provider
|
||||
* @return GroupManager
|
||||
*/
|
||||
public function register(GroupBackendProviderInterface $provider)
|
||||
{
|
||||
$this->providers[] = $provider;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a group from a search query
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return GroupProviderInterface[]
|
||||
*/
|
||||
public function find($input)
|
||||
{
|
||||
$groups = array();
|
||||
|
||||
foreach ($this->providers as $provider) {
|
||||
$groups = array_merge($groups, $provider->find($input));
|
||||
}
|
||||
|
||||
return $this->removeDuplicates($groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove duplicated groups
|
||||
*
|
||||
* @access private
|
||||
* @param array $groups
|
||||
* @return GroupProviderInterface[]
|
||||
*/
|
||||
private function removeDuplicates(array $groups)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ($groups as $group) {
|
||||
if (! isset($result[$group->getName()])) {
|
||||
$result[$group->getName()] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
}
|
40
sources/app/Core/Group/GroupProviderInterface.php
Normal file
40
sources/app/Core/Group/GroupProviderInterface.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Group;
|
||||
|
||||
/**
|
||||
* Group Provider Interface
|
||||
*
|
||||
* @package group
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface GroupProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get internal id
|
||||
*
|
||||
* You must return 0 if the group come from an external backend
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getInternalId();
|
||||
|
||||
/**
|
||||
* Get external id
|
||||
*
|
||||
* You must return a unique id if the group come from an external provider
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getExternalId();
|
||||
|
||||
/**
|
||||
* Get group name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core;
|
||||
namespace Kanboard\Core\Http;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* OAuth2 client
|
||||
* OAuth2 Client
|
||||
*
|
||||
* @package core
|
||||
* @package http
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class OAuth2 extends Base
|
120
sources/app/Core/Http/RememberMeCookie.php
Normal file
120
sources/app/Core/Http/RememberMeCookie.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Http;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Remember Me Cookie
|
||||
*
|
||||
* @package http
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RememberMeCookie extends Base
|
||||
{
|
||||
/**
|
||||
* Cookie name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COOKIE_NAME = 'KB_RM';
|
||||
|
||||
/**
|
||||
* Encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @return string
|
||||
*/
|
||||
public function encode($token, $sequence)
|
||||
{
|
||||
return implode('|', array($token, $sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the value of a cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Raw cookie data
|
||||
* @return array
|
||||
*/
|
||||
public function decode($value)
|
||||
{
|
||||
list($token, $sequence) = explode('|', $value);
|
||||
|
||||
return array(
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the current user has a RememberMe cookie
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCookie()
|
||||
{
|
||||
return $this->request->getCookie(self::COOKIE_NAME) !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Write and encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @param string $expiration Cookie expiration
|
||||
* @return boolean
|
||||
*/
|
||||
public function write($token, $sequence, $expiration)
|
||||
{
|
||||
return setcookie(
|
||||
self::COOKIE_NAME,
|
||||
$this->encode($token, $sequence),
|
||||
$expiration,
|
||||
$this->helper->url->dir(),
|
||||
null,
|
||||
$this->request->isHTTPS(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and decode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function read()
|
||||
{
|
||||
$cookie = $this->request->getCookie(self::COOKIE_NAME);
|
||||
|
||||
if (empty($cookie)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->decode($cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
return setcookie(
|
||||
self::COOKIE_NAME,
|
||||
'',
|
||||
time() - 3600,
|
||||
$this->helper->url->dir(),
|
||||
null,
|
||||
$this->request->isHTTPS(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kanboard\Core\Http;
|
||||
|
||||
use Pimple\Container;
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
|
@ -13,7 +14,35 @@ use Kanboard\Core\Base;
|
|||
class Request extends Base
|
||||
{
|
||||
/**
|
||||
* Get URL string parameter
|
||||
* Pointer to PHP environment variables
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $server;
|
||||
private $get;
|
||||
private $post;
|
||||
private $files;
|
||||
private $cookies;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Pimple\Container $container
|
||||
*/
|
||||
public function __construct(Container $container, array $server = array(), array $get = array(), array $post = array(), array $files = array(), array $cookies = array())
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->server = empty($server) ? $_SERVER : $server;
|
||||
$this->get = empty($get) ? $_GET : $get;
|
||||
$this->post = empty($post) ? $_POST : $post;
|
||||
$this->files = empty($files) ? $_FILES : $files;
|
||||
$this->cookies = empty($cookies) ? $_COOKIE : $cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query string string parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Parameter name
|
||||
|
@ -22,11 +51,11 @@ class Request extends Base
|
|||
*/
|
||||
public function getStringParam($name, $default_value = '')
|
||||
{
|
||||
return isset($_GET[$name]) ? $_GET[$name] : $default_value;
|
||||
return isset($this->get[$name]) ? $this->get[$name] : $default_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL integer parameter
|
||||
* Get query string integer parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Parameter name
|
||||
|
@ -35,7 +64,7 @@ class Request extends Base
|
|||
*/
|
||||
public function getIntegerParam($name, $default_value = 0)
|
||||
{
|
||||
return isset($_GET[$name]) && ctype_digit($_GET[$name]) ? (int) $_GET[$name] : $default_value;
|
||||
return isset($this->get[$name]) && ctype_digit($this->get[$name]) ? (int) $this->get[$name] : $default_value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,9 +88,9 @@ class Request extends Base
|
|||
*/
|
||||
public function getValues()
|
||||
{
|
||||
if (! empty($_POST) && ! empty($_POST['csrf_token']) && $this->token->validateCSRFToken($_POST['csrf_token'])) {
|
||||
unset($_POST['csrf_token']);
|
||||
return $_POST;
|
||||
if (! empty($this->post) && ! empty($this->post['csrf_token']) && $this->token->validateCSRFToken($this->post['csrf_token'])) {
|
||||
unset($this->post['csrf_token']);
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
return array();
|
||||
|
@ -98,8 +127,8 @@ class Request extends Base
|
|||
*/
|
||||
public function getFileContent($name)
|
||||
{
|
||||
if (isset($_FILES[$name])) {
|
||||
return file_get_contents($_FILES[$name]['tmp_name']);
|
||||
if (isset($this->files[$name]['tmp_name'])) {
|
||||
return file_get_contents($this->files[$name]['tmp_name']);
|
||||
}
|
||||
|
||||
return '';
|
||||
|
@ -114,7 +143,7 @@ class Request extends Base
|
|||
*/
|
||||
public function getFilePath($name)
|
||||
{
|
||||
return isset($_FILES[$name]['tmp_name']) ? $_FILES[$name]['tmp_name'] : '';
|
||||
return isset($this->files[$name]['tmp_name']) ? $this->files[$name]['tmp_name'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,7 +154,7 @@ class Request extends Base
|
|||
*/
|
||||
public function isPost()
|
||||
{
|
||||
return isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
return isset($this->server['REQUEST_METHOD']) && $this->server['REQUEST_METHOD'] === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,13 +173,24 @@ class Request extends Base
|
|||
*
|
||||
* Note: IIS return the value 'off' and other web servers an empty value when it's not HTTPS
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isHTTPS()
|
||||
public function isHTTPS()
|
||||
{
|
||||
return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== '' && $_SERVER['HTTPS'] !== 'off';
|
||||
return isset($this->server['HTTPS']) && $this->server['HTTPS'] !== '' && $this->server['HTTPS'] !== 'off';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie value
|
||||
*
|
||||
* @access public
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public function getCookie($name)
|
||||
{
|
||||
return isset($this->cookies[$name]) ? $this->cookies[$name] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,7 +203,18 @@ class Request extends Base
|
|||
public function getHeader($name)
|
||||
{
|
||||
$name = 'HTTP_'.str_replace('-', '_', strtoupper($name));
|
||||
return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
|
||||
return isset($this->server[$name]) ? $this->server[$name] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote user
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteUser()
|
||||
{
|
||||
return isset($this->server[REVERSE_PROXY_USER_HEADER]) ? $this->server[REVERSE_PROXY_USER_HEADER] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,41 +225,38 @@ class Request extends Base
|
|||
*/
|
||||
public function getQueryString()
|
||||
{
|
||||
return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
|
||||
return isset($this->server['QUERY_STRING']) ? $this->server['QUERY_STRING'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns uri
|
||||
* Return URI
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getUri()
|
||||
{
|
||||
return isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
|
||||
return isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user agent
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function getUserAgent()
|
||||
public function getUserAgent()
|
||||
{
|
||||
return empty($_SERVER['HTTP_USER_AGENT']) ? t('Unknown') : $_SERVER['HTTP_USER_AGENT'];
|
||||
return empty($this->server['HTTP_USER_AGENT']) ? t('Unknown') : $this->server['HTTP_USER_AGENT'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the real IP address of the user
|
||||
* Get the IP address of the user
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param bool $only_public Return only public IP address
|
||||
* @return string
|
||||
*/
|
||||
public static function getIpAddress($only_public = false)
|
||||
public function getIpAddress()
|
||||
{
|
||||
$keys = array(
|
||||
'HTTP_CLIENT_IP',
|
||||
|
@ -221,23 +269,24 @@ class Request extends Base
|
|||
);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (isset($_SERVER[$key])) {
|
||||
foreach (explode(',', $_SERVER[$key]) as $ip_address) {
|
||||
$ip_address = trim($ip_address);
|
||||
|
||||
if ($only_public) {
|
||||
|
||||
// Return only public IP address
|
||||
if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
|
||||
return $ip_address;
|
||||
}
|
||||
} else {
|
||||
return $ip_address;
|
||||
}
|
||||
if (! empty($this->server[$key])) {
|
||||
foreach (explode(',', $this->server[$key]) as $ipAddress) {
|
||||
return trim($ipAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t('Unknown');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get start time
|
||||
*
|
||||
* @access public
|
||||
* @return float
|
||||
*/
|
||||
public function getStartTime()
|
||||
{
|
||||
return isset($this->server['REQUEST_TIME_FLOAT']) ? $this->server['REQUEST_TIME_FLOAT'] : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -257,7 +257,7 @@ class Response extends Base
|
|||
*/
|
||||
public function hsts()
|
||||
{
|
||||
if (Request::isHTTPS()) {
|
||||
if ($this->request->isHTTPS()) {
|
||||
header('Strict-Transport-Security: max-age=31536000');
|
||||
}
|
||||
}
|
||||
|
|
165
sources/app/Core/Ldap/Client.php
Normal file
165
sources/app/Core/Ldap/Client.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* LDAP Client
|
||||
*
|
||||
* @package ldap
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/**
|
||||
* LDAP resource
|
||||
*
|
||||
* @access private
|
||||
* @var resource
|
||||
*/
|
||||
private $ldap;
|
||||
|
||||
/**
|
||||
* Establish LDAP connection
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return Client
|
||||
*/
|
||||
public static function connect($username = null, $password = null)
|
||||
{
|
||||
$client = new self;
|
||||
$client->open($client->getLdapServer());
|
||||
$username = $username ?: $client->getLdapUsername();
|
||||
$password = $password ?: $client->getLdapPassword();
|
||||
|
||||
if (empty($username) && empty($password)) {
|
||||
$client->useAnonymousAuthentication();
|
||||
} else {
|
||||
$client->authenticate($username, $password);
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server connection
|
||||
*
|
||||
* @access public
|
||||
* @return resource
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->ldap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish server connection
|
||||
*
|
||||
* @access public
|
||||
* @param string $server LDAP server hostname or IP
|
||||
* @param integer $port LDAP port
|
||||
* @param boolean $tls Start TLS
|
||||
* @param boolean $verify Skip SSL certificate verification
|
||||
* @return Client
|
||||
*/
|
||||
public function open($server, $port = LDAP_PORT, $tls = LDAP_START_TLS, $verify = LDAP_SSL_VERIFY)
|
||||
{
|
||||
if (! function_exists('ldap_connect')) {
|
||||
throw new ClientException('LDAP: The PHP LDAP extension is required');
|
||||
}
|
||||
|
||||
if (! $verify) {
|
||||
putenv('LDAPTLS_REQCERT=never');
|
||||
}
|
||||
|
||||
$this->ldap = ldap_connect($server, $port);
|
||||
|
||||
if ($this->ldap === false) {
|
||||
throw new ClientException('LDAP: Unable to connect to the LDAP server');
|
||||
}
|
||||
|
||||
ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
ldap_set_option($this->ldap, LDAP_OPT_REFERRALS, 0);
|
||||
ldap_set_option($this->ldap, LDAP_OPT_NETWORK_TIMEOUT, 1);
|
||||
ldap_set_option($this->ldap, LDAP_OPT_TIMELIMIT, 1);
|
||||
|
||||
if ($tls && ! @ldap_start_tls($this->ldap)) {
|
||||
throw new ClientException('LDAP: Unable to start TLS');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymous authentication
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function useAnonymousAuthentication()
|
||||
{
|
||||
if (! @ldap_bind($this->ldap)) {
|
||||
throw new ClientException('Unable to perform anonymous binding');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication with username/password
|
||||
*
|
||||
* @access public
|
||||
* @param string $bind_rdn
|
||||
* @param string $bind_password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($bind_rdn, $bind_password)
|
||||
{
|
||||
if (! @ldap_bind($this->ldap, $bind_rdn, $bind_password)) {
|
||||
throw new ClientException('LDAP authentication failure for "'.$bind_rdn.'"');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP server name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapServer()
|
||||
{
|
||||
if (! LDAP_SERVER) {
|
||||
throw new LogicException('LDAP server not configured, check the parameter LDAP_SERVER');
|
||||
}
|
||||
|
||||
return LDAP_SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP username (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUsername()
|
||||
{
|
||||
return LDAP_USERNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP password (proxy auth)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapPassword()
|
||||
{
|
||||
return LDAP_PASSWORD;
|
||||
}
|
||||
}
|
15
sources/app/Core/Ldap/ClientException.php
Normal file
15
sources/app/Core/Ldap/ClientException.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* LDAP Client Exception
|
||||
*
|
||||
* @package ldap
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ClientException extends Exception
|
||||
{
|
||||
}
|
63
sources/app/Core/Ldap/Entries.php
Normal file
63
sources/app/Core/Ldap/Entries.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
/**
|
||||
* LDAP Entries
|
||||
*
|
||||
* @package ldap
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Entries
|
||||
{
|
||||
/**
|
||||
* LDAP entries
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $entries = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param array $entries
|
||||
*/
|
||||
public function __construct(array $entries)
|
||||
{
|
||||
$this->entries = $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entries
|
||||
*
|
||||
* @access public
|
||||
* @return Entry[]
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$entities = array();
|
||||
|
||||
if (! isset($this->entries['count'])) {
|
||||
return $entities;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $this->entries['count']; $i++) {
|
||||
$entities[] = new Entry($this->entries[$i]);
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first entry
|
||||
*
|
||||
* @access public
|
||||
* @return Entry
|
||||
*/
|
||||
public function getFirstEntry()
|
||||
{
|
||||
return new Entry(isset($this->entries[0]) ? $this->entries[0] : array());
|
||||
}
|
||||
}
|
91
sources/app/Core/Ldap/Entry.php
Normal file
91
sources/app/Core/Ldap/Entry.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
/**
|
||||
* LDAP Entry
|
||||
*
|
||||
* @package ldap
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Entry
|
||||
{
|
||||
/**
|
||||
* LDAP entry
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $entry = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param array $entry
|
||||
*/
|
||||
public function __construct(array $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all attribute values
|
||||
*
|
||||
* @access public
|
||||
* @param string $attribute
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAll($attribute)
|
||||
{
|
||||
$attributes = array();
|
||||
|
||||
if (! isset($this->entry[$attribute]['count'])) {
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $this->entry[$attribute]['count']; $i++) {
|
||||
$attributes[] = $this->entry[$attribute][$i];
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first attribute value
|
||||
*
|
||||
* @access public
|
||||
* @param string $attribute
|
||||
* @param string $default
|
||||
* @return string
|
||||
*/
|
||||
public function getFirstValue($attribute, $default = '')
|
||||
{
|
||||
return isset($this->entry[$attribute][0]) ? $this->entry[$attribute][0] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry distinguished name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDn()
|
||||
{
|
||||
return isset($this->entry['dn']) ? $this->entry['dn'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given value exists in attribute list
|
||||
*
|
||||
* @access public
|
||||
* @param string $attribute
|
||||
* @param string $value
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasValue($attribute, $value)
|
||||
{
|
||||
$attributes = $this->getAll($attribute);
|
||||
return in_array($value, $attributes);
|
||||
}
|
||||
}
|
130
sources/app/Core/Ldap/Group.php
Normal file
130
sources/app/Core/Ldap/Group.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
use LogicException;
|
||||
use Kanboard\Group\LdapGroupProvider;
|
||||
|
||||
/**
|
||||
* LDAP Group Finder
|
||||
*
|
||||
* @package ldap
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Group
|
||||
{
|
||||
/**
|
||||
* Query
|
||||
*
|
||||
* @access private
|
||||
* @var Query
|
||||
*/
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param Query $query
|
||||
*/
|
||||
public function __construct(Query $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param Client $client
|
||||
* @param string $query
|
||||
* @return array
|
||||
*/
|
||||
public static function getGroups(Client $client, $query)
|
||||
{
|
||||
$self = new self(new Query($client));
|
||||
return $self->find($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find groups
|
||||
*
|
||||
* @access public
|
||||
* @param string $query
|
||||
* @return array
|
||||
*/
|
||||
public function find($query)
|
||||
{
|
||||
$this->query->execute($this->getBasDn(), $query, $this->getAttributes());
|
||||
$groups = array();
|
||||
|
||||
if ($this->query->hasResult()) {
|
||||
$groups = $this->build();
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build groups list
|
||||
*
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function build()
|
||||
{
|
||||
$groups = array();
|
||||
|
||||
foreach ($this->query->getEntries()->getAll() as $entry) {
|
||||
$groups[] = new LdapGroupProvider($entry->getDn(), $entry->getFirstValue($this->getAttributeName()));
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ge the list of attributes to fetch when reading the LDAP group entry
|
||||
*
|
||||
* Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array_values(array_filter(array(
|
||||
$this->getAttributeName(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP group name attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributeName()
|
||||
{
|
||||
if (! LDAP_GROUP_ATTRIBUTE_NAME) {
|
||||
throw new LogicException('LDAP full name attribute empty, check the parameter LDAP_GROUP_ATTRIBUTE_NAME');
|
||||
}
|
||||
|
||||
return LDAP_GROUP_ATTRIBUTE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP group base DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getBasDn()
|
||||
{
|
||||
if (! LDAP_GROUP_BASE_DN) {
|
||||
throw new LogicException('LDAP group base DN empty, check the parameter LDAP_GROUP_BASE_DN');
|
||||
}
|
||||
|
||||
return LDAP_GROUP_BASE_DN;
|
||||
}
|
||||
}
|
87
sources/app/Core/Ldap/Query.php
Normal file
87
sources/app/Core/Ldap/Query.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
/**
|
||||
* LDAP Query
|
||||
*
|
||||
* @package ldap
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Query
|
||||
{
|
||||
/**
|
||||
* LDAP client
|
||||
*
|
||||
* @access private
|
||||
* @var Client
|
||||
*/
|
||||
private $client = null;
|
||||
|
||||
/**
|
||||
* Query result
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $entries = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param Client $client
|
||||
*/
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query
|
||||
*
|
||||
* @access public
|
||||
* @param string $baseDn
|
||||
* @param string $filter
|
||||
* @param array $attributes
|
||||
* @return Query
|
||||
*/
|
||||
public function execute($baseDn, $filter, array $attributes)
|
||||
{
|
||||
$sr = ldap_search($this->client->getConnection(), $baseDn, $filter, $attributes);
|
||||
if ($sr === false) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$entries = ldap_get_entries($this->client->getConnection(), $sr);
|
||||
if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->entries = $entries;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query returned a result
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasResult()
|
||||
{
|
||||
return ! empty($this->entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP Entries
|
||||
*
|
||||
* @access public
|
||||
* @return Entities
|
||||
*/
|
||||
public function getEntries()
|
||||
{
|
||||
return new Entries($this->entries);
|
||||
}
|
||||
}
|
223
sources/app/Core/Ldap/User.php
Normal file
223
sources/app/Core/Ldap/User.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
use LogicException;
|
||||
use Kanboard\Core\Security\Role;
|
||||
use Kanboard\User\LdapUserProvider;
|
||||
|
||||
/**
|
||||
* LDAP User Finder
|
||||
*
|
||||
* @package ldap
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* Query
|
||||
*
|
||||
* @access private
|
||||
* @var Query
|
||||
*/
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param Query $query
|
||||
*/
|
||||
public function __construct(Query $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param Client $client
|
||||
* @param string $username
|
||||
* @return array
|
||||
*/
|
||||
public static function getUser(Client $client, $username)
|
||||
{
|
||||
$self = new self(new Query($client));
|
||||
return $self->find($self->getLdapUserPattern($username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user
|
||||
*
|
||||
* @access public
|
||||
* @param string $query
|
||||
* @return null|LdapUserProvider
|
||||
*/
|
||||
public function find($query)
|
||||
{
|
||||
$this->query->execute($this->getBasDn(), $query, $this->getAttributes());
|
||||
$user = null;
|
||||
|
||||
if ($this->query->hasResult()) {
|
||||
$user = $this->build();
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build user profile
|
||||
*
|
||||
* @access protected
|
||||
* @return LdapUserProvider
|
||||
*/
|
||||
protected function build()
|
||||
{
|
||||
$entry = $this->query->getEntries()->getFirstEntry();
|
||||
$role = Role::APP_USER;
|
||||
|
||||
if ($entry->hasValue($this->getAttributeGroup(), $this->getGroupAdminDn())) {
|
||||
$role = Role::APP_ADMIN;
|
||||
} elseif ($entry->hasValue($this->getAttributeGroup(), $this->getGroupManagerDn())) {
|
||||
$role = Role::APP_MANAGER;
|
||||
}
|
||||
|
||||
return new LdapUserProvider(
|
||||
$entry->getDn(),
|
||||
$entry->getFirstValue($this->getAttributeUsername()),
|
||||
$entry->getFirstValue($this->getAttributeName()),
|
||||
$entry->getFirstValue($this->getAttributeEmail()),
|
||||
$role,
|
||||
$entry->getAll($this->getAttributeGroup())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ge the list of attributes to fetch when reading the LDAP user entry
|
||||
*
|
||||
* Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array_values(array_filter(array(
|
||||
$this->getAttributeUsername(),
|
||||
$this->getAttributeName(),
|
||||
$this->getAttributeEmail(),
|
||||
$this->getAttributeGroup(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account id attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributeUsername()
|
||||
{
|
||||
if (! LDAP_USER_ATTRIBUTE_USERNAME) {
|
||||
throw new LogicException('LDAP username attribute empty, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
|
||||
}
|
||||
|
||||
return LDAP_USER_ATTRIBUTE_USERNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP user name attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributeName()
|
||||
{
|
||||
if (! LDAP_USER_ATTRIBUTE_FULLNAME) {
|
||||
throw new LogicException('LDAP full name attribute empty, check the parameter LDAP_USER_ATTRIBUTE_FULLNAME');
|
||||
}
|
||||
|
||||
return LDAP_USER_ATTRIBUTE_FULLNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account email attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributeEmail()
|
||||
{
|
||||
if (! LDAP_USER_ATTRIBUTE_EMAIL) {
|
||||
throw new LogicException('LDAP email attribute empty, check the parameter LDAP_USER_ATTRIBUTE_EMAIL');
|
||||
}
|
||||
|
||||
return LDAP_USER_ATTRIBUTE_EMAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP account memberof attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributeGroup()
|
||||
{
|
||||
return LDAP_USER_ATTRIBUTE_GROUPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP admin group DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupAdminDn()
|
||||
{
|
||||
return LDAP_GROUP_ADMIN_DN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP application manager group DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupManagerDn()
|
||||
{
|
||||
return LDAP_GROUP_MANAGER_DN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP user base DN
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getBasDn()
|
||||
{
|
||||
if (! LDAP_USER_BASE_DN) {
|
||||
throw new LogicException('LDAP user base DN empty, check the parameter LDAP_USER_BASE_DN');
|
||||
}
|
||||
|
||||
return LDAP_USER_BASE_DN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LDAP user pattern
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @return string
|
||||
*/
|
||||
public function getLdapUserPattern($username)
|
||||
{
|
||||
if (! LDAP_USER_FILTER) {
|
||||
throw new LogicException('LDAP user filter empty, check the parameter LDAP_USER_FILTER');
|
||||
}
|
||||
|
||||
return sprintf(LDAP_USER_FILTER, $username);
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ class Lexer
|
|||
"/^(swimlane:)/" => 'T_SWIMLANE',
|
||||
"/^(ref:)/" => 'T_REFERENCE',
|
||||
"/^(reference:)/" => 'T_REFERENCE',
|
||||
"/^(link:)/" => 'T_LINK',
|
||||
"/^(\s+)/" => 'T_WHITESPACE',
|
||||
'/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE',
|
||||
'/^(yesterday|tomorrow|today)/' => 'T_DATE',
|
||||
|
@ -118,6 +119,7 @@ class Lexer
|
|||
case 'T_COLUMN':
|
||||
case 'T_PROJECT':
|
||||
case 'T_SWIMLANE':
|
||||
case 'T_LINK':
|
||||
$next = next($tokens);
|
||||
|
||||
if ($next !== false && $next['token'] === 'T_STRING') {
|
||||
|
|
|
@ -46,7 +46,7 @@ class Mail extends Base implements ClientInterface
|
|||
* Get SwiftMailer transport
|
||||
*
|
||||
* @access protected
|
||||
* @return \Swift_Transport
|
||||
* @return \Swift_Transport|\Swift_MailTransport|\Swift_SmtpTransport|\Swift_SendmailTransport
|
||||
*/
|
||||
protected function getTransport()
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ class Sendmail extends Mail
|
|||
* Get SwiftMailer transport
|
||||
*
|
||||
* @access protected
|
||||
* @return \Swift_Transport
|
||||
* @return \Swift_Transport|\Swift_MailTransport|\Swift_SmtpTransport|\Swift_SendmailTransport
|
||||
*/
|
||||
protected function getTransport()
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ class Smtp extends Mail
|
|||
* Get SwiftMailer transport
|
||||
*
|
||||
* @access protected
|
||||
* @return \Swift_Transport
|
||||
* @return \Swift_Transport|\Swift_MailTransport|\Swift_SmtpTransport|\Swift_SendmailTransport
|
||||
*/
|
||||
protected function getTransport()
|
||||
{
|
||||
|
|
155
sources/app/Core/Security/AccessMap.php
Normal file
155
sources/app/Core/Security/AccessMap.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Access Map Definition
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AccessMap
|
||||
{
|
||||
/**
|
||||
* Default role
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $defaultRole = '';
|
||||
|
||||
/**
|
||||
* Role hierarchy
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $hierarchy = array();
|
||||
|
||||
/**
|
||||
* Access map
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $map = array();
|
||||
|
||||
/**
|
||||
* Define the default role when nothing match
|
||||
*
|
||||
* @access public
|
||||
* @param string $role
|
||||
* @return Acl
|
||||
*/
|
||||
public function setDefaultRole($role)
|
||||
{
|
||||
$this->defaultRole = $role;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define role hierarchy
|
||||
*
|
||||
* @access public
|
||||
* @param string $role
|
||||
* @param array $subroles
|
||||
* @return Acl
|
||||
*/
|
||||
public function setRoleHierarchy($role, array $subroles)
|
||||
{
|
||||
foreach ($subroles as $subrole) {
|
||||
if (isset($this->hierarchy[$subrole])) {
|
||||
$this->hierarchy[$subrole][] = $role;
|
||||
} else {
|
||||
$this->hierarchy[$subrole] = array($role);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get computed role hierarchy
|
||||
*
|
||||
* @access public
|
||||
* @param string $role
|
||||
* @return array
|
||||
*/
|
||||
public function getRoleHierarchy($role)
|
||||
{
|
||||
$roles = array($role);
|
||||
|
||||
if (isset($this->hierarchy[$role])) {
|
||||
$roles = array_merge($roles, $this->hierarchy[$role]);
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new access rules
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller Controller class name
|
||||
* @param mixed $methods List of method name or just one method
|
||||
* @param string $role Lowest role required
|
||||
* @return Acl
|
||||
*/
|
||||
public function add($controller, $methods, $role)
|
||||
{
|
||||
if (is_array($methods)) {
|
||||
foreach ($methods as $method) {
|
||||
$this->addRule($controller, $method, $role);
|
||||
}
|
||||
} else {
|
||||
$this->addRule($controller, $methods, $role);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new access rule
|
||||
*
|
||||
* @access private
|
||||
* @param string $controller
|
||||
* @param string $method
|
||||
* @param string $role
|
||||
* @return Acl
|
||||
*/
|
||||
private function addRule($controller, $method, $role)
|
||||
{
|
||||
$controller = strtolower($controller);
|
||||
$method = strtolower($method);
|
||||
|
||||
if (! isset($this->map[$controller])) {
|
||||
$this->map[$controller] = array();
|
||||
}
|
||||
|
||||
$this->map[$controller][$method] = $role;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get roles that match the given controller/method
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller
|
||||
* @param string $method
|
||||
* @return boolean
|
||||
*/
|
||||
public function getRoles($controller, $method)
|
||||
{
|
||||
$controller = strtolower($controller);
|
||||
$method = strtolower($method);
|
||||
|
||||
foreach (array($method, '*') as $key) {
|
||||
if (isset($this->map[$controller][$key])) {
|
||||
return $this->getRoleHierarchy($this->map[$controller][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getRoleHierarchy($this->defaultRole);
|
||||
}
|
||||
}
|
187
sources/app/Core/Security/AuthenticationManager.php
Normal file
187
sources/app/Core/Security/AuthenticationManager.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
use LogicException;
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Event\AuthFailureEvent;
|
||||
use Kanboard\Event\AuthSuccessEvent;
|
||||
|
||||
/**
|
||||
* Authentication Manager
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AuthenticationManager extends Base
|
||||
{
|
||||
/**
|
||||
* Event names
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_SUCCESS = 'auth.success';
|
||||
const EVENT_FAILURE = 'auth.failure';
|
||||
|
||||
/**
|
||||
* List of authentication providers
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $providers = array();
|
||||
|
||||
/**
|
||||
* Register a new authentication provider
|
||||
*
|
||||
* @access public
|
||||
* @param AuthenticationProviderInterface $provider
|
||||
* @return AuthenticationManager
|
||||
*/
|
||||
public function register(AuthenticationProviderInterface $provider)
|
||||
{
|
||||
$this->providers[$provider->getName()] = $provider;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new authentication provider
|
||||
*
|
||||
* @access public
|
||||
* @param string $name
|
||||
* @return AuthenticationProviderInterface|OAuthAuthenticationProviderInterface|PasswordAuthenticationProviderInterface|PreAuthenticationProviderInterface|OAuthAuthenticationProviderInterface
|
||||
*/
|
||||
public function getProvider($name)
|
||||
{
|
||||
if (! isset($this->providers[$name])) {
|
||||
throw new LogicException('Authentication provider not found: '.$name);
|
||||
}
|
||||
|
||||
return $this->providers[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute providers that are able to validate the current session
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function checkCurrentSession()
|
||||
{
|
||||
if ($this->userSession->isLogged()) {
|
||||
foreach ($this->filterProviders('SessionCheckProviderInterface') as $provider) {
|
||||
if (! $provider->isValidSession()) {
|
||||
$this->logger->debug('Invalidate session for '.$this->userSession->getUsername());
|
||||
$this->sessionStorage->flush();
|
||||
$this->preAuthentication();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute pre-authentication providers
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function preAuthentication()
|
||||
{
|
||||
foreach ($this->filterProviders('PreAuthenticationProviderInterface') as $provider) {
|
||||
if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) {
|
||||
$this->dispatcher->dispatch(self::EVENT_SUCCESS, new AuthSuccessEvent($provider->getName()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute username/password authentication providers
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param boolean $fireEvent
|
||||
* @return boolean
|
||||
*/
|
||||
public function passwordAuthentication($username, $password, $fireEvent = true)
|
||||
{
|
||||
foreach ($this->filterProviders('PasswordAuthenticationProviderInterface') as $provider) {
|
||||
$provider->setUsername($username);
|
||||
$provider->setPassword($password);
|
||||
|
||||
if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) {
|
||||
if ($fireEvent) {
|
||||
$this->dispatcher->dispatch(self::EVENT_SUCCESS, new AuthSuccessEvent($provider->getName()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fireEvent) {
|
||||
$this->dispatcher->dispatch(self::EVENT_FAILURE, new AuthFailureEvent($username));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform OAuth2 authentication
|
||||
*
|
||||
* @access public
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public function oauthAuthentication($name)
|
||||
{
|
||||
$provider = $this->getProvider($name);
|
||||
|
||||
if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) {
|
||||
$this->dispatcher->dispatch(self::EVENT_SUCCESS, new AuthSuccessEvent($provider->getName()));
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(self::EVENT_FAILURE, new AuthFailureEvent);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last Post-Authentication provider
|
||||
*
|
||||
* @access public
|
||||
* @return PostAuthenticationProviderInterface
|
||||
*/
|
||||
public function getPostAuthenticationProvider()
|
||||
{
|
||||
$providers = $this->filterProviders('PostAuthenticationProviderInterface');
|
||||
|
||||
if (empty($providers)) {
|
||||
throw new LogicException('You must have at least one Post-Authentication Provider configured');
|
||||
}
|
||||
|
||||
return array_pop($providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter registered providers by interface type
|
||||
*
|
||||
* @access private
|
||||
* @param string $interface
|
||||
* @return array
|
||||
*/
|
||||
private function filterProviders($interface)
|
||||
{
|
||||
$interface = '\Kanboard\Core\Security\\'.$interface;
|
||||
|
||||
return array_filter($this->providers, function(AuthenticationProviderInterface $provider) use ($interface) {
|
||||
return is_a($provider, $interface);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Authentication Provider Interface
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get authentication provider name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Authenticate the user
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate();
|
||||
}
|
46
sources/app/Core/Security/Authorization.php
Normal file
46
sources/app/Core/Security/Authorization.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Authorization Handler
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Authorization
|
||||
{
|
||||
/**
|
||||
* Access Map
|
||||
*
|
||||
* @access private
|
||||
* @var AccessMap
|
||||
*/
|
||||
private $accessMap;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param AccessMap $accessMap
|
||||
*/
|
||||
public function __construct(AccessMap $accessMap)
|
||||
{
|
||||
$this->accessMap = $accessMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given role is allowed to access to the specified resource
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller
|
||||
* @param string $method
|
||||
* @param string $role
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAllowed($controller, $method, $role)
|
||||
{
|
||||
$roles = $this->accessMap->getRoles($controller, $method);
|
||||
return in_array($role, $roles);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* OAuth2 Authentication Provider Interface
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface OAuthAuthenticationProviderInterface extends AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return UserProviderInterface
|
||||
*/
|
||||
public function getUser();
|
||||
|
||||
/**
|
||||
* Unlink user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($userId);
|
||||
|
||||
/**
|
||||
* Get configured OAuth2 service
|
||||
*
|
||||
* @access public
|
||||
* @return Kanboard\Core\Http\OAuth2
|
||||
*/
|
||||
public function getService();
|
||||
|
||||
/**
|
||||
* Set OAuth2 code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
* @return OAuthAuthenticationProviderInterface
|
||||
*/
|
||||
public function setCode($code);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Password Authentication Provider Interface
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface PasswordAuthenticationProviderInterface extends AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return UserProviderInterface
|
||||
*/
|
||||
public function getUser();
|
||||
|
||||
/**
|
||||
* Set username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
*/
|
||||
public function setUsername($username);
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*
|
||||
* @access public
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword($password);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Post Authentication Provider Interface
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface PostAuthenticationProviderInterface extends AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Set user pin-code
|
||||
*
|
||||
* @access public
|
||||
* @param string $code
|
||||
*/
|
||||
public function setCode($code);
|
||||
|
||||
/**
|
||||
* Set secret token (fetched from user profile)
|
||||
*
|
||||
* @access public
|
||||
* @param string $secret
|
||||
*/
|
||||
public function setSecret($secret);
|
||||
|
||||
/**
|
||||
* Get secret token (will be saved in user profile)
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSecret();
|
||||
|
||||
/**
|
||||
* Get QR code url (empty if no QR can be provided)
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @return string
|
||||
*/
|
||||
public function getQrCodeUrl($label);
|
||||
|
||||
/**
|
||||
* Get key url (empty if no url can be provided)
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyUrl($label);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Pre-Authentication Provider Interface
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface PreAuthenticationProviderInterface extends AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get user object
|
||||
*
|
||||
* @access public
|
||||
* @return UserProviderInterface
|
||||
*/
|
||||
public function getUser();
|
||||
}
|
64
sources/app/Core/Security/Role.php
Normal file
64
sources/app/Core/Security/Role.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Role Definitions
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Role
|
||||
{
|
||||
const APP_ADMIN = 'app-admin';
|
||||
const APP_MANAGER = 'app-manager';
|
||||
const APP_USER = 'app-user';
|
||||
const APP_PUBLIC = 'app-public';
|
||||
|
||||
const PROJECT_MANAGER = 'project-manager';
|
||||
const PROJECT_MEMBER = 'project-member';
|
||||
const PROJECT_VIEWER = 'project-viewer';
|
||||
|
||||
/**
|
||||
* Get application roles
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getApplicationRoles()
|
||||
{
|
||||
return array(
|
||||
self::APP_ADMIN => t('Administrator'),
|
||||
self::APP_MANAGER => t('Manager'),
|
||||
self::APP_USER => t('User'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project roles
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProjectRoles()
|
||||
{
|
||||
return array(
|
||||
self::PROJECT_MANAGER => t('Project Manager'),
|
||||
self::PROJECT_MEMBER => t('Project Member'),
|
||||
self::PROJECT_VIEWER => t('Project Viewer'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get application roles
|
||||
*
|
||||
* @access public
|
||||
* @param string $role
|
||||
* @return string
|
||||
*/
|
||||
public function getRoleName($role)
|
||||
{
|
||||
$roles = $this->getApplicationRoles() + $this->getProjectRoles();
|
||||
return isset($roles[$role]) ? $roles[$role] : t('Unknown');
|
||||
}
|
||||
}
|
20
sources/app/Core/Security/SessionCheckProviderInterface.php
Normal file
20
sources/app/Core/Security/SessionCheckProviderInterface.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Security;
|
||||
|
||||
/**
|
||||
* Session Check Provider Interface
|
||||
*
|
||||
* @package security
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface SessionCheckProviderInterface
|
||||
{
|
||||
/**
|
||||
* Check if the user session is valid
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidSession();
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
namespace Kanboard\Core\Session;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Http\Request;
|
||||
|
||||
/**
|
||||
* Session Manager
|
||||
|
@ -13,6 +12,13 @@ use Kanboard\Core\Http\Request;
|
|||
*/
|
||||
class SessionManager extends Base
|
||||
{
|
||||
/**
|
||||
* Event names
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_DESTROY = 'session.destroy';
|
||||
|
||||
/**
|
||||
* Return true if the session is open
|
||||
*
|
||||
|
@ -41,7 +47,7 @@ class SessionManager extends Base
|
|||
session_name('KB_SID');
|
||||
session_start();
|
||||
|
||||
$this->container['sessionStorage']->setStorage($_SESSION);
|
||||
$this->sessionStorage->setStorage($_SESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,6 +57,8 @@ class SessionManager extends Base
|
|||
*/
|
||||
public function close()
|
||||
{
|
||||
$this->dispatcher->dispatch(self::EVENT_DESTROY);
|
||||
|
||||
// Destroy the session cookie
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
|
@ -80,7 +88,7 @@ class SessionManager extends Base
|
|||
SESSION_DURATION,
|
||||
$this->helper->url->dir() ?: '/',
|
||||
null,
|
||||
Request::isHTTPS(),
|
||||
$this->request->isHTTPS(),
|
||||
true
|
||||
);
|
||||
|
||||
|
|
|
@ -12,12 +12,13 @@ namespace Kanboard\Core\Session;
|
|||
* @property array $user
|
||||
* @property array $flash
|
||||
* @property array $csrf
|
||||
* @property array $postAuth
|
||||
* @property array $postAuthenticationValidated
|
||||
* @property array $filters
|
||||
* @property string $redirectAfterLogin
|
||||
* @property string $captcha
|
||||
* @property string $commentSorting
|
||||
* @property bool $hasSubtaskInProgress
|
||||
* @property bool $hasRememberMe
|
||||
* @property bool $boardCollapsed
|
||||
*/
|
||||
class SessionStorage
|
||||
|
@ -60,6 +61,21 @@ class SessionStorage
|
|||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush session data
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
$session = get_object_vars($this);
|
||||
unset($session['storage']);
|
||||
|
||||
foreach (array_keys($session) as $property) {
|
||||
unset($this->$property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy class properties to external storage
|
||||
*
|
||||
|
|
32
sources/app/Core/User/GroupSync.php
Normal file
32
sources/app/Core/User/GroupSync.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\User;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Group Synchronization
|
||||
*
|
||||
* @package user
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GroupSync extends Base
|
||||
{
|
||||
/**
|
||||
* Synchronize group membership
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @param array $groupIds
|
||||
*/
|
||||
public function synchronize($userId, array $groupIds)
|
||||
{
|
||||
foreach ($groupIds as $groupId) {
|
||||
$group = $this->group->getByExternalId($groupId);
|
||||
|
||||
if (! empty($group) && ! $this->groupMember->isMember($group['id'], $userId)) {
|
||||
$this->groupMember->addUser($group['id'], $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
sources/app/Core/User/UserProfile.php
Normal file
62
sources/app/Core/User/UserProfile.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\User;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* User Profile
|
||||
*
|
||||
* @package user
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserProfile extends Base
|
||||
{
|
||||
/**
|
||||
* Assign provider data to the local user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @param UserProviderInterface $user
|
||||
* @return boolean
|
||||
*/
|
||||
public function assign($userId, UserProviderInterface $user)
|
||||
{
|
||||
$profile = $this->user->getById($userId);
|
||||
|
||||
$values = UserProperty::filterProperties($profile, UserProperty::getProperties($user));
|
||||
$values['id'] = $userId;
|
||||
|
||||
if ($this->user->update($values)) {
|
||||
$profile = array_merge($profile, $values);
|
||||
$this->userSession->initialize($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize user properties with the local database and create the user session
|
||||
*
|
||||
* @access public
|
||||
* @param UserProviderInterface $user
|
||||
* @return boolean
|
||||
*/
|
||||
public function initialize(UserProviderInterface $user)
|
||||
{
|
||||
if ($user->getInternalId()) {
|
||||
$profile = $this->user->getById($user->getInternalId());
|
||||
} elseif ($user->getExternalIdColumn() && $user->getExternalId()) {
|
||||
$profile = $this->userSync->synchronize($user);
|
||||
$this->groupSync->synchronize($profile['id'], $user->getExternalGroupIds());
|
||||
}
|
||||
|
||||
if (! empty($profile)) {
|
||||
$this->userSession->initialize($profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
70
sources/app/Core/User/UserProperty.php
Normal file
70
sources/app/Core/User/UserProperty.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\User;
|
||||
|
||||
/**
|
||||
* User Property
|
||||
*
|
||||
* @package user
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserProperty
|
||||
{
|
||||
/**
|
||||
* Get filtered user properties from user provider
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param UserProviderInterface $user
|
||||
* @return array
|
||||
*/
|
||||
public static function getProperties(UserProviderInterface $user)
|
||||
{
|
||||
$properties = array(
|
||||
'username' => $user->getUsername(),
|
||||
'name' => $user->getName(),
|
||||
'email' => $user->getEmail(),
|
||||
'role' => $user->getRole(),
|
||||
$user->getExternalIdColumn() => $user->getExternalId(),
|
||||
);
|
||||
|
||||
$properties = array_merge($properties, $user->getExtraAttributes());
|
||||
|
||||
return array_filter($properties, array(__NAMESPACE__.'\UserProperty', 'isNotEmptyValue'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter user properties compared to existing user profile
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param array $profile
|
||||
* @param array $properties
|
||||
* @return array
|
||||
*/
|
||||
public static function filterProperties(array $profile, array $properties)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($properties as $property => $value) {
|
||||
if (array_key_exists($property, $profile) && ! self::isNotEmptyValue($profile[$property])) {
|
||||
$values[$property] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is not empty
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isNotEmptyValue($value)
|
||||
{
|
||||
return $value !== null && $value !== '';
|
||||
}
|
||||
}
|
103
sources/app/Core/User/UserProviderInterface.php
Normal file
103
sources/app/Core/User/UserProviderInterface.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\User;
|
||||
|
||||
/**
|
||||
* User Provider Interface
|
||||
*
|
||||
* @package user
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface UserProviderInterface
|
||||
{
|
||||
/**
|
||||
* Return true to allow automatic user creation
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isUserCreationAllowed();
|
||||
|
||||
/**
|
||||
* Get external id column name
|
||||
*
|
||||
* Example: google_id, github_id, gitlab_id...
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getExternalIdColumn();
|
||||
|
||||
/**
|
||||
* Get internal id
|
||||
*
|
||||
* If a value is returned the user properties won't be updated in the local database
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getInternalId();
|
||||
|
||||
/**
|
||||
* Get external id
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getExternalId();
|
||||
|
||||
/**
|
||||
* Get user role
|
||||
*
|
||||
* Return an empty string to not override role stored in the database
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getRole();
|
||||
|
||||
/**
|
||||
* Get username
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername();
|
||||
|
||||
/**
|
||||
* Get user full name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Get user email
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getEmail();
|
||||
|
||||
/**
|
||||
* Get external group ids
|
||||
*
|
||||
* A synchronization is done at login time,
|
||||
* the user will be member of those groups if they exists in the database
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getExternalGroupIds();
|
||||
|
||||
/**
|
||||
* Get extra user attributes
|
||||
*
|
||||
* Example: is_ldap_user, disable_login_form, notifications_enabled...
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getExtraAttributes();
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Model;
|
||||
namespace Kanboard\Core\User;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\Role;
|
||||
|
||||
/**
|
||||
* User Session
|
||||
*
|
||||
* @package model
|
||||
* @package user
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserSession extends Base
|
||||
|
@ -18,22 +21,29 @@ class UserSession extends Base
|
|||
*/
|
||||
public function initialize(array $user)
|
||||
{
|
||||
if (isset($user['password'])) {
|
||||
unset($user['password']);
|
||||
}
|
||||
|
||||
if (isset($user['twofactor_secret'])) {
|
||||
unset($user['twofactor_secret']);
|
||||
foreach (array('password', 'is_admin', 'is_project_admin', 'twofactor_secret') as $column) {
|
||||
if (isset($user[$column])) {
|
||||
unset($user[$column]);
|
||||
}
|
||||
}
|
||||
|
||||
$user['id'] = (int) $user['id'];
|
||||
$user['is_admin'] = isset($user['is_admin']) ? (bool) $user['is_admin'] : false;
|
||||
$user['is_project_admin'] = isset($user['is_project_admin']) ? (bool) $user['is_project_admin'] : false;
|
||||
$user['is_ldap_user'] = isset($user['is_ldap_user']) ? (bool) $user['is_ldap_user'] : false;
|
||||
$user['twofactor_activated'] = isset($user['twofactor_activated']) ? (bool) $user['twofactor_activated'] : false;
|
||||
|
||||
$this->sessionStorage->user = $user;
|
||||
$this->sessionStorage->postAuth = array('validated' => false);
|
||||
$this->sessionStorage->postAuthenticationValidated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user application role
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getRole()
|
||||
{
|
||||
return $this->sessionStorage->user['role'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,9 +52,19 @@ class UserSession extends Base
|
|||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function check2FA()
|
||||
public function isPostAuthenticationValidated()
|
||||
{
|
||||
return isset($this->sessionStorage->postAuth['validated']) && $this->sessionStorage->postAuth['validated'] === true;
|
||||
return isset($this->sessionStorage->postAuthenticationValidated) && $this->sessionStorage->postAuthenticationValidated === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate 2FA for the current session
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function validatePostAuthentication()
|
||||
{
|
||||
$this->sessionStorage->postAuthenticationValidated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,7 +73,7 @@ class UserSession extends Base
|
|||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function has2FA()
|
||||
public function hasPostAuthentication()
|
||||
{
|
||||
return isset($this->sessionStorage->user['twofactor_activated']) && $this->sessionStorage->user['twofactor_activated'] === true;
|
||||
}
|
||||
|
@ -63,7 +83,7 @@ class UserSession extends Base
|
|||
*
|
||||
* @access public
|
||||
*/
|
||||
public function disable2FA()
|
||||
public function disablePostAuthentication()
|
||||
{
|
||||
$this->sessionStorage->user['twofactor_activated'] = false;
|
||||
}
|
||||
|
@ -76,18 +96,7 @@ class UserSession extends Base
|
|||
*/
|
||||
public function isAdmin()
|
||||
{
|
||||
return isset($this->sessionStorage->user['is_admin']) && $this->sessionStorage->user['is_admin'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the logged user is project admin
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isProjectAdmin()
|
||||
{
|
||||
return isset($this->sessionStorage->user['is_project_admin']) && $this->sessionStorage->user['is_project_admin'] === true;
|
||||
return isset($this->sessionStorage->user['role']) && $this->sessionStorage->user['role'] === Role::APP_ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,7 +114,7 @@ class UserSession extends Base
|
|||
* Get username
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername()
|
||||
{
|
76
sources/app/Core/User/UserSync.php
Normal file
76
sources/app/Core/User/UserSync.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\User;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* User Synchronization
|
||||
*
|
||||
* @package user
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserSync extends Base
|
||||
{
|
||||
/**
|
||||
* Synchronize user profile
|
||||
*
|
||||
* @access public
|
||||
* @param UserProviderInterface $user
|
||||
* @return array
|
||||
*/
|
||||
public function synchronize(UserProviderInterface $user)
|
||||
{
|
||||
$profile = $this->user->getByExternalId($user->getExternalIdColumn(), $user->getExternalId());
|
||||
$properties = UserProperty::getProperties($user);
|
||||
|
||||
if (! empty($profile)) {
|
||||
$profile = $this->updateUser($profile, $properties);
|
||||
} elseif ($user->isUserCreationAllowed()) {
|
||||
$profile = $this->createUser($user, $properties);
|
||||
}
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
*
|
||||
* @access public
|
||||
* @param array $profile
|
||||
* @param array $properties
|
||||
* @return array
|
||||
*/
|
||||
private function updateUser(array $profile, array $properties)
|
||||
{
|
||||
$values = UserProperty::filterProperties($profile, $properties);
|
||||
|
||||
if (! empty($values)) {
|
||||
$values['id'] = $profile['id'];
|
||||
$result = $this->user->update($values);
|
||||
return $result ? array_merge($profile, $properties) : $profile;
|
||||
}
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user
|
||||
*
|
||||
* @access public
|
||||
* @param UserProviderInterface $user
|
||||
* @param array $properties
|
||||
* @return array
|
||||
*/
|
||||
private function createUser(UserProviderInterface $user, array $properties)
|
||||
{
|
||||
$id = $this->user->create($properties);
|
||||
|
||||
if ($id === false) {
|
||||
$this->logger->error('Unable to create user profile: '.$user->getExternalId());
|
||||
return array();
|
||||
}
|
||||
|
||||
return $this->user->getById($id);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Event;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event as BaseEvent;
|
||||
|
||||
class AuthEvent extends BaseEvent
|
||||
{
|
||||
private $auth_name;
|
||||
private $user_id;
|
||||
|
||||
public function __construct($auth_name, $user_id)
|
||||
{
|
||||
$this->auth_name = $auth_name;
|
||||
$this->user_id = $user_id;
|
||||
}
|
||||
|
||||
public function getUserId()
|
||||
{
|
||||
return $this->user_id;
|
||||
}
|
||||
|
||||
public function getAuthType()
|
||||
{
|
||||
return $this->auth_name;
|
||||
}
|
||||
}
|
44
sources/app/Event/AuthFailureEvent.php
Normal file
44
sources/app/Event/AuthFailureEvent.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Event;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event as BaseEvent;
|
||||
|
||||
/**
|
||||
* Authentication Failure Event
|
||||
*
|
||||
* @package event
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AuthFailureEvent extends BaseEvent
|
||||
{
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $username = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
*/
|
||||
public function __construct($username = '')
|
||||
{
|
||||
$this->username = $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get username
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername()
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
}
|
43
sources/app/Event/AuthSuccessEvent.php
Normal file
43
sources/app/Event/AuthSuccessEvent.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Event;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event as BaseEvent;
|
||||
|
||||
/**
|
||||
* Authentication Success Event
|
||||
*
|
||||
* @package event
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AuthSuccessEvent extends BaseEvent
|
||||
{
|
||||
/**
|
||||
* Authentication provider name
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $authType;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param string $authType
|
||||
*/
|
||||
public function __construct($authType)
|
||||
{
|
||||
$this->authType = $authType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthType()
|
||||
{
|
||||
return $this->authType;
|
||||
}
|
||||
}
|
55
sources/app/Formatter/GroupAutoCompleteFormatter.php
Normal file
55
sources/app/Formatter/GroupAutoCompleteFormatter.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
/**
|
||||
* Autocomplete formatter for groups
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class GroupAutoCompleteFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Groups found
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
/**
|
||||
* Format groups for the ajax autocompletion
|
||||
*
|
||||
* @access public
|
||||
* @param array $groups
|
||||
* @return GroupAutoCompleteFormatter
|
||||
*/
|
||||
public function setGroups(array $groups)
|
||||
{
|
||||
$this->groups = $groups;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format groups for the ajax autocompletion
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function format()
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ($this->groups as $group) {
|
||||
$result[] = array(
|
||||
'id' => $group->getInternalId(),
|
||||
'external_id' => $group->getExternalId(),
|
||||
'value' => $group->getName(),
|
||||
'label' => $group->getName(),
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -79,7 +79,7 @@ class ProjectGanttFormatter extends Project implements FormatterInterface
|
|||
'gantt_link' => $this->helper->url->href('gantt', 'project', array('project_id' => $project['id'])),
|
||||
'color' => $color,
|
||||
'not_defined' => empty($project['start_date']) || empty($project['end_date']),
|
||||
'users' => $this->projectPermission->getProjectUsers($project['id']),
|
||||
'users' => $this->projectUserRole->getAllUsersGroupedByRole($project['id']),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
38
sources/app/Formatter/UserFilterAutoCompleteFormatter.php
Normal file
38
sources/app/Formatter/UserFilterAutoCompleteFormatter.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Formatter;
|
||||
|
||||
use Kanboard\Model\User;
|
||||
use Kanboard\Model\UserFilter;
|
||||
|
||||
/**
|
||||
* Autocomplete formatter for user filter
|
||||
*
|
||||
* @package formatter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserFilterAutoCompleteFormatter extends UserFilter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Format the tasks for the ajax autocompletion
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function format()
|
||||
{
|
||||
$users = $this->query->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')->findAll();
|
||||
|
||||
foreach ($users as &$user) {
|
||||
$user['value'] = $user['username'].' (#'.$user['id'].')';
|
||||
|
||||
if (empty($user['name'])) {
|
||||
$user['label'] = $user['username'];
|
||||
} else {
|
||||
$user['label'] = $user['name'].' ('.$user['username'].')';
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
}
|
34
sources/app/Group/DatabaseBackendGroupProvider.php
Normal file
34
sources/app/Group/DatabaseBackendGroupProvider.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Group;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Group\GroupBackendProviderInterface;
|
||||
|
||||
/**
|
||||
* Database Backend Group Provider
|
||||
*
|
||||
* @package group
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class DatabaseBackendGroupProvider extends Base implements GroupBackendProviderInterface
|
||||
{
|
||||
/**
|
||||
* Find a group from a search query
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return DatabaseGroupProvider[]
|
||||
*/
|
||||
public function find($input)
|
||||
{
|
||||
$result = array();
|
||||
$groups = $this->group->search($input);
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$result[] = new DatabaseGroupProvider($group);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue