diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-12-05 20:31:27 -0500 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-12-05 20:31:27 -0500 |
commit | e9fedf3e5cd63aea4da7a71f6647ee427c62fa49 (patch) | |
tree | abc2de5aebace4a2d7c94805552264dab6b10bc7 /app/Auth | |
parent | 346b8312e5ac877ce3192c2db3a26b500018bbb5 (diff) |
Rewrite of the authentication and authorization system
Diffstat (limited to 'app/Auth')
-rw-r--r-- | app/Auth/DatabaseAuth.php | 125 | ||||
-rw-r--r-- | app/Auth/Github.php | 123 | ||||
-rw-r--r-- | app/Auth/GithubAuth.php | 143 | ||||
-rw-r--r-- | app/Auth/Gitlab.php | 123 | ||||
-rw-r--r-- | app/Auth/GitlabAuth.php | 143 | ||||
-rw-r--r-- | app/Auth/Google.php | 124 | ||||
-rw-r--r-- | app/Auth/GoogleAuth.php | 143 | ||||
-rw-r--r-- | app/Auth/Ldap.php | 521 | ||||
-rw-r--r-- | app/Auth/LdapAuth.php | 187 | ||||
-rw-r--r-- | app/Auth/RememberMe.php | 323 | ||||
-rw-r--r-- | app/Auth/RememberMeAuth.php | 79 | ||||
-rw-r--r-- | app/Auth/ReverseProxy.php | 83 | ||||
-rw-r--r-- | app/Auth/ReverseProxyAuth.php | 76 | ||||
-rw-r--r-- | app/Auth/TotpAuth.php | 126 |
14 files changed, 1022 insertions, 1297 deletions
diff --git a/app/Auth/DatabaseAuth.php b/app/Auth/DatabaseAuth.php new file mode 100644 index 00000000..727afaf3 --- /dev/null +++ b/app/Auth/DatabaseAuth.php @@ -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; + } +} diff --git a/app/Auth/Github.php b/app/Auth/Github.php deleted file mode 100644 index 4777152a..00000000 --- a/app/Auth/Github.php +++ /dev/null @@ -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()) - ); - } -} diff --git a/app/Auth/GithubAuth.php b/app/Auth/GithubAuth.php new file mode 100644 index 00000000..47da0413 --- /dev/null +++ b/app/Auth/GithubAuth.php @@ -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' => '')); + } +} diff --git a/app/Auth/Gitlab.php b/app/Auth/Gitlab.php deleted file mode 100644 index 698b59c3..00000000 --- a/app/Auth/Gitlab.php +++ /dev/null @@ -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()) - ); - } -} diff --git a/app/Auth/GitlabAuth.php b/app/Auth/GitlabAuth.php new file mode 100644 index 00000000..df6e0176 --- /dev/null +++ b/app/Auth/GitlabAuth.php @@ -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' => '')); + } +} diff --git a/app/Auth/Google.php b/app/Auth/Google.php deleted file mode 100644 index 6c1bc3cd..00000000 --- a/app/Auth/Google.php +++ /dev/null @@ -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()) - ); - } -} diff --git a/app/Auth/GoogleAuth.php b/app/Auth/GoogleAuth.php new file mode 100644 index 00000000..0dc1c62f --- /dev/null +++ b/app/Auth/GoogleAuth.php @@ -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' => '')); + } +} diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php deleted file mode 100644 index 3d361aa7..00000000 --- a/app/Auth/Ldap.php +++ /dev/null @@ -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; - } -} diff --git a/app/Auth/LdapAuth.php b/app/Auth/LdapAuth.php new file mode 100644 index 00000000..eb66e54d --- /dev/null +++ b/app/Auth/LdapAuth.php @@ -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; + } +} diff --git a/app/Auth/RememberMe.php b/app/Auth/RememberMe.php deleted file mode 100644 index 0a567cbe..00000000 --- a/app/Auth/RememberMe.php +++ /dev/null @@ -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 - ); - } -} diff --git a/app/Auth/RememberMeAuth.php b/app/Auth/RememberMeAuth.php new file mode 100644 index 00000000..02b7b9f6 --- /dev/null +++ b/app/Auth/RememberMeAuth.php @@ -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); + } +} diff --git a/app/Auth/ReverseProxy.php b/app/Auth/ReverseProxy.php deleted file mode 100644 index d119ca98..00000000 --- a/app/Auth/ReverseProxy.php +++ /dev/null @@ -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, - )); - } -} diff --git a/app/Auth/ReverseProxyAuth.php b/app/Auth/ReverseProxyAuth.php new file mode 100644 index 00000000..8af7f0a2 --- /dev/null +++ b/app/Auth/ReverseProxyAuth.php @@ -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; + } +} diff --git a/app/Auth/TotpAuth.php b/app/Auth/TotpAuth.php new file mode 100644 index 00000000..f41fabd8 --- /dev/null +++ b/app/Auth/TotpAuth.php @@ -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); + } +} |