Rewrite of the authentication and authorization system

This commit is contained in:
Frederic Guillot
2015-12-05 20:31:27 -05:00
parent 346b8312e5
commit e9fedf3e5c
255 changed files with 14114 additions and 9820 deletions

125
app/Auth/DatabaseAuth.php Normal file
View 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 null|\Kanboard\User\DatabaseUserProvider
*/
public function getUser()
{
if (empty($this->userInfo)) {
return null;
}
return new DatabaseUserProvider($this->userInfo);
}
/**
* Set username
*
* @access public
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* Set password
*
* @access public
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
}

View File

@@ -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
app/Auth/GithubAuth.php Normal file
View 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 null|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 private
* @return array
*/
private 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' => ''));
}
}

View File

@@ -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
app/Auth/GitlabAuth.php Normal file
View 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 null|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 private
* @return array
*/
private 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' => ''));
}
}

View File

@@ -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
app/Auth/GoogleAuth.php Normal file
View 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 null|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 private
* @return array
*/
private 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' => ''));
}
}

View File

@@ -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;
}
}

187
app/Auth/LdapAuth.php Normal file
View File

@@ -0,0 +1,187 @@
<?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 $user = 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 {
$ldap = LdapClient::connect($this->getLdapUsername(), $this->getLdapPassword());
$user = LdapUser::getUser($ldap, $this->getLdapUserPattern());
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 ($ldap->authenticate($user->getDn(), $this->password)) {
$this->user = $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->user;
}
/**
* 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 user pattern
*
* @access public
* @return string
*/
public function getLdapUserPattern()
{
if (! LDAP_USER_FILTER) {
throw new LogicException('LDAP user filter empty, check the parameter LDAP_USER_FILTER');
}
return sprintf(LDAP_USER_FILTER, $this->username);
}
/**
* 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;
}
}

View File

@@ -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
);
}
}

View 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 null|DatabaseUserProvider
*/
public function getUser()
{
if (empty($this->userInfo)) {
return null;
}
return new DatabaseUserProvider($this->userInfo);
}
}

View File

@@ -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,
));
}
}

View 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;
/**
* ReverseProxy Authentication Provider
*
* @package auth
* @author Frederic Guillot
*/
class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface
{
/**
* User properties
*
* @access private
* @var \Kanboard\User\ReverseProxyUserProvider
*/
private $user = 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->user = 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 null|ReverseProxyUserProvider
*/
public function getUser()
{
return $this->user;
}
}

126
app/Auth/TotpAuth.php Normal file
View 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);
}
}