diff --git a/app/Auth/Base.php b/app/Auth/Base.php new file mode 100644 index 000000000..f9c1c3298 --- /dev/null +++ b/app/Auth/Base.php @@ -0,0 +1,60 @@ +registry = $registry; + $this->db = $this->registry->shared('db'); + } + + /** + * Load automatically models + * + * @access public + * @param string $name Model name + * @return mixed + */ + public function __get($name) + { + return Tool::loadModel($this->registry, $name); + } +} diff --git a/app/Auth/Database.php b/app/Auth/Database.php new file mode 100644 index 000000000..67881593b --- /dev/null +++ b/app/Auth/Database.php @@ -0,0 +1,52 @@ +db->table(User::TABLE)->eq('username', $username)->eq('is_ldap_user', 0)->findOne(); + + if ($user && password_verify($password, $user['password'])) { + + // Update user session + $this->user->updateSession($user); + + // Update login history + $this->lastLogin->create( + self::AUTH_NAME, + $user['id'], + $this->user->getIpAddress(), + $this->user->getUserAgent() + ); + + return true; + } + + return false; + } +} diff --git a/app/Model/GitHub.php b/app/Auth/GitHub.php similarity index 94% rename from app/Model/GitHub.php rename to app/Auth/GitHub.php index bf4f4c51c..1e99df410 100644 --- a/app/Model/GitHub.php +++ b/app/Auth/GitHub.php @@ -1,171 +1,178 @@ -user->getByGitHubId($github_id); - - if ($user) { - - // Create the user session - $this->user->updateSession($user); - - // Update login history - $this->lastLogin->create( - LastLogin::AUTH_GITHUB, - $user['id'], - $this->user->getIpAddress(), - $this->user->getUserAgent() - ); - - 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 - * @todo Don't overwrite existing email/name with empty GitHub data - */ - public function updateUser($user_id, array $profile) - { - return $this->user->update(array( - 'id' => $user_id, - 'github_id' => $profile['id'], - 'email' => $profile['email'], - 'name' => $profile['name'], - )); - } - - /** - * Get the GitHub service instance - * - * @access public - * @return \OAuth\OAuth2\Service\GitHub - */ - public function getService() - { - $uriFactory = new UriFactory(); - $currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER); - $currentUri->setQuery('controller=user&action=gitHub'); - - $storage = new Session(false); - - $credentials = new Credentials( - GITHUB_CLIENT_ID, - GITHUB_CLIENT_SECRET, - $currentUri->getAbsoluteUri() - ); - - $serviceFactory = new ServiceFactory(); - - return $serviceFactory->createService( - 'gitHub', - $credentials, - $storage, - array('') - ); - } - - /** - * Get the authorization URL - * - * @access public - * @return \OAuth\Common\Http\Uri\Uri - */ - public function getAuthorizationUrl() - { - return $this->getService()->getAuthorizationUri(); - } - - /** - * Get GitHub profile information from the API - * - * @access public - * @param string $code GitHub authorization code - * @return bool|array - */ - public function getGitHubProfile($code) - { - try { - $gitHubService = $this->getService(); - $gitHubService->requestAccessToken($code); - - return json_decode($gitHubService->request('user'), true); - } - catch (TokenResponseException $e) { - return false; - } - - return false; - } - - /** - * Revokes this user's GitHub tokens for Kanboard - * - * @access public - * @return bool|array - * @todo Currently this simply removes all our tokens for this user, ideally it should - * restrict itself to the one in question - */ - public function revokeGitHubAccess() - { - try { - $gitHubService = $this->getService(); - - $basicAuthHeader = array('Authorization' => 'Basic ' . - base64_encode(GITHUB_CLIENT_ID.':'.GITHUB_CLIENT_SECRET)); - - return json_decode($gitHubService->request('/applications/'.GITHUB_CLIENT_ID.'/tokens', 'DELETE', null, $basicAuthHeader), true); - } - catch (TokenResponseException $e) { - return false; - } - - return false; - } -} +user->getByGitHubId($github_id); + + if ($user) { + + // Create the user session + $this->user->updateSession($user); + + // Update login history + $this->lastLogin->create( + self::AUTH_NAME, + $user['id'], + $this->user->getIpAddress(), + $this->user->getUserAgent() + ); + + 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 + * @todo Don't overwrite existing email/name with empty GitHub data + */ + public function updateUser($user_id, array $profile) + { + return $this->user->update(array( + 'id' => $user_id, + 'github_id' => $profile['id'], + 'email' => $profile['email'], + 'name' => $profile['name'], + )); + } + + /** + * Get the GitHub service instance + * + * @access public + * @return \OAuth\OAuth2\Service\GitHub + */ + public function getService() + { + $uriFactory = new UriFactory(); + $currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER); + $currentUri->setQuery('controller=user&action=gitHub'); + + $storage = new Session(false); + + $credentials = new Credentials( + GITHUB_CLIENT_ID, + GITHUB_CLIENT_SECRET, + $currentUri->getAbsoluteUri() + ); + + $serviceFactory = new ServiceFactory(); + + return $serviceFactory->createService( + 'gitHub', + $credentials, + $storage, + array('') + ); + } + + /** + * Get the authorization URL + * + * @access public + * @return \OAuth\Common\Http\Uri\Uri + */ + public function getAuthorizationUrl() + { + return $this->getService()->getAuthorizationUri(); + } + + /** + * Get GitHub profile information from the API + * + * @access public + * @param string $code GitHub authorization code + * @return bool|array + */ + public function getGitHubProfile($code) + { + try { + $gitHubService = $this->getService(); + $gitHubService->requestAccessToken($code); + + return json_decode($gitHubService->request('user'), true); + } + catch (TokenResponseException $e) { + return false; + } + + return false; + } + + /** + * Revokes this user's GitHub tokens for Kanboard + * + * @access public + * @return bool|array + * @todo Currently this simply removes all our tokens for this user, ideally it should + * restrict itself to the one in question + */ + public function revokeGitHubAccess() + { + try { + $gitHubService = $this->getService(); + + $basicAuthHeader = array('Authorization' => 'Basic ' . + base64_encode(GITHUB_CLIENT_ID.':'.GITHUB_CLIENT_SECRET)); + + return json_decode($gitHubService->request('/applications/'.GITHUB_CLIENT_ID.'/tokens', 'DELETE', null, $basicAuthHeader), true); + } + catch (TokenResponseException $e) { + return false; + } + + return false; + } +} diff --git a/app/Model/Google.php b/app/Auth/Google.php similarity index 94% rename from app/Model/Google.php rename to app/Auth/Google.php index cca4f6686..3dca96be2 100644 --- a/app/Model/Google.php +++ b/app/Auth/Google.php @@ -1,6 +1,6 @@ lastLogin->create( - LastLogin::AUTH_GOOGLE, + self::AUTH_NAME, $user['id'], $this->user->getIpAddress(), $this->user->getUserAgent() diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php new file mode 100644 index 000000000..bb17653d4 --- /dev/null +++ b/app/Auth/Ldap.php @@ -0,0 +1,150 @@ +findUser($username, $password); + + if (is_array($result)) { + + $user = $this->user->getByUsername($username); + + if ($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->createUser($username, $result['name'], $result['email'])) { + $user = $this->user->getByUsername($username); + } + else { + return false; + } + } + + // We open the session + $this->user->updateSession($user); + + // Update login history + $this->lastLogin->create( + self::AUTH_NAME, + $user['id'], + $this->user->getIpAddress(), + $this->user->getUserAgent() + ); + + 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) + { + if (! function_exists('ldap_connect')) { + die('The PHP LDAP extension is required'); + } + + // Skip SSL certificate verification + if (! LDAP_SSL_VERIFY) { + putenv('LDAPTLS_REQCERT=never'); + } + + $ldap = ldap_connect(LDAP_SERVER, LDAP_PORT); + + if (! is_resource($ldap)) { + die('Unable to connect to the LDAP server: "'.LDAP_SERVER.'"'); + } + + ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + + if (! @ldap_bind($ldap, LDAP_USERNAME, LDAP_PASSWORD)) { + die('Unable to bind to the LDAP server: "'.LDAP_SERVER.'"'); + } + + $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, sprintf(LDAP_USER_PATTERN, $username), array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL)); + + if ($sr === false) { + return false; + } + + $info = ldap_get_entries($ldap, $sr); + + // User not found + if (count($info) == 0 || $info['count'] == 0) { + return false; + } + + // We got our user + if (@ldap_bind($ldap, $info[0]['dn'], $password)) { + + return array( + 'username' => $username, + 'name' => isset($info[0][LDAP_ACCOUNT_FULLNAME][0]) ? $info[0][LDAP_ACCOUNT_FULLNAME][0] : '', + 'email' => isset($info[0][LDAP_ACCOUNT_EMAIL][0]) ? $info[0][LDAP_ACCOUNT_EMAIL][0] : '', + ); + } + + return false; + } + + /** + * Create a new local user after the LDAP authentication + * + * @access public + * @param string $username Username + * @param string $name Name of the user + * @param string $email Email address + * @return bool + */ + public function createUser($username, $name, $email) + { + $values = array( + 'username' => $username, + 'name' => $name, + 'email' => $email, + 'is_admin' => 0, + 'is_ldap_user' => 1, + ); + + return $this->user->create($values); + } +} diff --git a/app/Model/RememberMe.php b/app/Auth/RememberMe.php similarity index 94% rename from app/Model/RememberMe.php rename to app/Auth/RememberMe.php index e23ed8870..3cf6fc868 100644 --- a/app/Model/RememberMe.php +++ b/app/Auth/RememberMe.php @@ -1,17 +1,24 @@ user->updateSession($this->user->getById($record['user_id'])); $this->acl->isRememberMe(true); + // Update last login infos + $this->lastLogin->create( + self::AUTH_NAME, + $this->acl->getUserId(), + $this->user->getIpAddress(), + $this->user->getUserAgent() + ); + return true; } } diff --git a/app/Model/ReverseProxyAuth.php b/app/Auth/ReverseProxy.php similarity index 86% rename from app/Model/ReverseProxyAuth.php rename to app/Auth/ReverseProxy.php index 14d18ba34..e23ee24fb 100644 --- a/app/Model/ReverseProxyAuth.php +++ b/app/Auth/ReverseProxy.php @@ -1,17 +1,24 @@ lastLogin->create( - LastLogin::AUTH_REVERSE_PROXY, + self::AUTH_NAME, $user['id'], $this->user->getIpAddress(), $this->user->getUserAgent() diff --git a/app/Controller/Base.php b/app/Controller/Base.php index 11841e09b..ed8a6b3ba 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -15,20 +15,16 @@ use Model\LastLogin; * @author Frederic Guillot * * @property \Model\Acl $acl + * @property \Model\Authentication $authentication * @property \Model\Action $action * @property \Model\Board $board * @property \Model\Category $category * @property \Model\Comment $comment * @property \Model\Config $config * @property \Model\File $file - * @property \Model\Google $google - * @property \Model\GitHub $gitHub * @property \Model\LastLogin $lastLogin - * @property \Model\Ldap $ldap * @property \Model\Notification $notification * @property \Model\Project $project - * @property \Model\RememberMe $rememberMe - * @property \Model\ReverseProxyAuth $reverseProxyAuth * @property \Model\SubTask $subTask * @property \Model\Task $task * @property \Model\User $user @@ -123,29 +119,8 @@ abstract class Base date_default_timezone_set($this->config->get('timezone', 'UTC')); // Authentication - if (! $this->acl->isLogged() && ! $this->acl->isPublicAction($controller, $action)) { - - // Try the "remember me" authentication first - if (! $this->rememberMe->authenticate()) { - - // Automatic reverse proxy header authentication - if(! (REVERSE_PROXY_AUTH && $this->reverseProxyAuth->authenticate()) ) { - // Redirect to the login form if not authenticated - $this->response->redirect('?controller=user&action=login'); - } - } - else { - - $this->lastLogin->create( - LastLogin::AUTH_REMEMBER_ME, - $this->acl->getUserId(), - $this->user->getIpAddress(), - $this->user->getUserAgent() - ); - } - } - else if ($this->rememberMe->hasCookie()) { - $this->rememberMe->refresh(); + if (! $this->authentication->isAuthenticated($controller, $action)) { + $this->response->redirect('?controller=user&action=login'); } // Check if the user is allowed to see this page diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 498f32149..48bfb9cf3 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -28,7 +28,7 @@ class Config extends Base 'menu' => 'config', 'title' => t('Settings'), 'timezones' => $this->config->getTimezones(), - 'remember_me_sessions' => $this->rememberMe->getAll($this->acl->getUserId()), + 'remember_me_sessions' => $this->authentication->backend('rememberMe')->getAll($this->acl->getUserId()), 'last_logins' => $this->lastLogin->getAll($this->acl->getUserId()), ))); } @@ -73,7 +73,7 @@ class Config extends Base 'menu' => 'config', 'title' => t('Settings'), 'timezones' => $this->config->getTimezones(), - 'remember_me_sessions' => $this->rememberMe->getAll($this->acl->getUserId()), + 'remember_me_sessions' => $this->authentication->backend('rememberMe')->getAll($this->acl->getUserId()), 'last_logins' => $this->lastLogin->getAll($this->acl->getUserId()), ))); } @@ -124,7 +124,7 @@ class Config extends Base public function removeRememberMeToken() { $this->checkCSRFParam(); - $this->rememberMe->remove($this->request->getIntegerParam('id')); + $this->authentication->backend('rememberMe')->remove($this->request->getIntegerParam('id')); $this->response->redirect('?controller=config&action=index#remember-me'); } } diff --git a/app/Controller/User.php b/app/Controller/User.php index d30c6fd2e..0bb7aec15 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -18,7 +18,7 @@ class User extends Base public function logout() { $this->checkCSRFParam(); - $this->rememberMe->destroy($this->acl->getUserId()); + $this->authentication->backend('rememberMe')->destroy($this->acl->getUserId()); $this->session->close(); $this->response->redirect('?controller=user&action=login'); } @@ -30,7 +30,7 @@ class User extends Base */ public function login() { - if (isset($_SESSION['user'])) { + if ($this->acl->isLogged()) { $this->response->redirect('?controller=app'); } @@ -50,7 +50,7 @@ class User extends Base public function check() { $values = $this->request->getValues(); - list($valid, $errors) = $this->user->validateLogin($values); + list($valid, $errors) = $this->authentication->validateForm($values); if ($valid) { $this->response->redirect('?controller=app'); @@ -249,14 +249,14 @@ class User extends Base if ($code) { - $profile = $this->google->getGoogleProfile($code); + $profile = $this->authentication->backend('google')->getGoogleProfile($code); if (is_array($profile)) { // If the user is already logged, link the account otherwise authenticate if ($this->acl->isLogged()) { - if ($this->google->updateUser($this->acl->getUserId(), $profile)) { + if ($this->authentication->backend('google')->updateUser($this->acl->getUserId(), $profile)) { $this->session->flash(t('Your Google Account is linked to your profile successfully.')); } else { @@ -265,7 +265,7 @@ class User extends Base $this->response->redirect('?controller=user'); } - else if ($this->google->authenticate($profile['id'])) { + else if ($this->authentication->backend('google')->authenticate($profile['id'])) { $this->response->redirect('?controller=app'); } else { @@ -279,7 +279,7 @@ class User extends Base } } - $this->response->redirect($this->google->getAuthorizationUrl()); + $this->response->redirect($this->authentication->backend('google')->getAuthorizationUrl()); } /** @@ -290,7 +290,7 @@ class User extends Base public function unlinkGoogle() { $this->checkCSRFParam(); - if ($this->google->unlink($this->acl->getUserId())) { + if ($this->authentication->backend('google')->unlink($this->acl->getUserId())) { $this->session->flash(t('Your Google Account is not linked anymore to your profile.')); } else { @@ -310,14 +310,14 @@ class User extends Base $code = $this->request->getStringParam('code'); if ($code) { - $profile = $this->gitHub->getGitHubProfile($code); + $profile = $this->authentication->backend('gitHub')->getGitHubProfile($code); if (is_array($profile)) { // If the user is already logged, link the account otherwise authenticate if ($this->acl->isLogged()) { - if ($this->gitHub->updateUser($this->acl->getUserId(), $profile)) { + if ($this->authentication->backend('gitHub')->updateUser($this->acl->getUserId(), $profile)) { $this->session->flash(t('Your GitHub account was successfully linked to your profile.')); } else { @@ -326,7 +326,7 @@ class User extends Base $this->response->redirect('?controller=user'); } - else if ($this->gitHub->authenticate($profile['id'])) { + else if ($this->authentication->backend('gitHub')->authenticate($profile['id'])) { $this->response->redirect('?controller=app'); } else { @@ -340,7 +340,7 @@ class User extends Base } } - $this->response->redirect($this->gitHub->getAuthorizationUrl()); + $this->response->redirect($this->authentication->backend('gitHub')->getAuthorizationUrl()); } /** @@ -352,9 +352,9 @@ class User extends Base { $this->checkCSRFParam(); - $this->gitHub->revokeGitHubAccess(); + $this->authentication->backend('gitHub')->revokeGitHubAccess(); - if ($this->gitHub->unlink($this->acl->getUserId())) { + if ($this->authentication->backend('gitHub')->unlink($this->acl->getUserId())) { $this->session->flash(t('Your GitHub account is no longer linked to your profile.')); } else { diff --git a/app/Core/Tool.php b/app/Core/Tool.php index 1a2e99044..85b684e28 100644 --- a/app/Core/Tool.php +++ b/app/Core/Tool.php @@ -34,8 +34,11 @@ class Tool public static function loadModel(Registry $registry, $name) { - $class = '\Model\\'.ucfirst($name); - $registry->$name = new $class($registry); + if (! isset($registry->$name)) { + $class = '\Model\\'.ucfirst($name); + $registry->$name = new $class($registry); + } + return $registry->shared($name); } } diff --git a/app/Model/Authentication.php b/app/Model/Authentication.php new file mode 100644 index 000000000..4c8aad82a --- /dev/null +++ b/app/Model/Authentication.php @@ -0,0 +1,125 @@ +registry->$name)) { + $class = '\Auth\\'.ucfirst($name); + $this->registry->$name = new $class($this->registry); + } + + return $this->registry->shared($name); + } + + /** + * Check if the current user is authenticated + * + * @access public + * @param string $controller Controller + * @param string $action Action name + * @return bool + */ + public function isAuthenticated($controller, $action) + { + // If the action is public we don't need to do any checks + if ($this->acl->isPublicAction($controller, $action)) { + return true; + } + + // If the user is already logged it's ok + if ($this->acl->isLogged()) { + + // We update each time the RememberMe cookie tokens + if ($this->backend('rememberMe')->hasCookie()) { + $this->backend('rememberMe')->refresh(); + } + + return true; + } + + // We try first with the RememberMe cookie + if ($this->backend('rememberMe')->authenticate()) { + return true; + } + + // Then with the ReverseProxy authentication + if (REVERSE_PROXY_AUTH && $this->backend('reverseProxy')->authenticate()) { + return true; + } + + return false; + } + + /** + * Validate user login form + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateForm(array $values) + { + $v = new Validator($values, array( + new Validators\Required('username', t('The username is required')), + new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), + new Validators\Required('password', t('The password is required')), + )); + + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result) { + + $authenticated = false; + + // Try first the database auth and then LDAP if activated + if ($this->backend('database')->authenticate($values['username'], $values['password'])) { + $authenticated = true; + } + else if (LDAP_AUTH && $this->backend('ldap')->authenticate($values['username'], $values['password'])) { + $authenticated = true; + } + + if ($authenticated) { + + // Setup the remember me feature + if (! empty($values['remember_me'])) { + + $credentials = $this->backend('rememberMe') + ->create($this->acl->getUserId(), $this->user->getIpAddress(), $this->user->getUserAgent()); + + $this->backend('rememberMe')->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']); + } + } + else { + $result = false; + $errors['login'] = t('Bad username or password'); + } + } + + return array( + $result, + $errors + ); + } +} diff --git a/app/Model/Base.php b/app/Model/Base.php index 92578ffc4..1439a36eb 100644 --- a/app/Model/Base.php +++ b/app/Model/Base.php @@ -2,20 +2,6 @@ namespace Model; -require __DIR__.'/../../vendor/SimpleValidator/Validator.php'; -require __DIR__.'/../../vendor/SimpleValidator/Base.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/Required.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/Unique.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/MaxLength.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/MinLength.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/Integer.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/Equals.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/AlphaNumeric.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/GreaterThan.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/Date.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/Email.php'; -require __DIR__.'/../../vendor/SimpleValidator/Validators/Numeric.php'; - use Core\Event; use Core\Tool; use Core\Registry; @@ -35,7 +21,6 @@ use PicoDb\Database; * @property \Model\Config $config * @property \Model\File $file * @property \Model\LastLogin $lastLogin - * @property \Model\Ldap $ldap * @property \Model\Notification $notification * @property \Model\Project $project * @property \Model\SubTask $subTask diff --git a/app/Model/LastLogin.php b/app/Model/LastLogin.php index e2ea63e13..3391db508 100644 --- a/app/Model/LastLogin.php +++ b/app/Model/LastLogin.php @@ -24,18 +24,6 @@ class LastLogin extends Base */ const NB_LOGINS = 10; - /** - * Authentication methods - * - * @var string - */ - const AUTH_DATABASE = 'database'; - const AUTH_REMEMBER_ME = 'remember_me'; - const AUTH_LDAP = 'ldap'; - const AUTH_GOOGLE = 'google'; - const AUTH_GITHUB = 'github'; - const AUTH_REVERSE_PROXY = 'reverse_proxy'; - /** * Create a new record * diff --git a/app/Model/Ldap.php b/app/Model/Ldap.php deleted file mode 100644 index 007f71710..000000000 --- a/app/Model/Ldap.php +++ /dev/null @@ -1,104 +0,0 @@ -create($username, $info[0][LDAP_ACCOUNT_FULLNAME][0], $info[0][LDAP_ACCOUNT_EMAIL][0]); - } - - return false; - } - - /** - * Create automatically a new local user after the LDAP authentication - * - * @access public - * @param string $username Username - * @param string $name Name of the user - * @param string $email Email address - * @return bool - */ - public function create($username, $name, $email) - { - $user = $this->user->getByUsername($username); - - // There is an existing user account - if ($user) { - - if ($user['is_ldap_user'] == 1) { - - // LDAP user already created - return true; - } - else { - - // There is already a local user with that username - return false; - } - } - - // Create a LDAP user - $values = array( - 'username' => $username, - 'name' => $name, - 'email' => $email, - 'is_admin' => 0, - 'is_ldap_user' => 1, - ); - - return $userModel->create($values); - } -} diff --git a/app/Model/User.php b/app/Model/User.php index d0e33fd0a..5f6b8a3a2 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -311,84 +311,6 @@ class User extends Base return array(false, $v->getErrors()); } - /** - * Validate user login - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateLogin(array $values) - { - $v = new Validator($values, array( - new Validators\Required('username', t('The username is required')), - new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), - new Validators\Required('password', t('The password is required')), - )); - - $result = $v->execute(); - $errors = $v->getErrors(); - - if ($result) { - - list($authenticated, $method) = $this->authenticate($values['username'], $values['password']); - - if ($authenticated === true) { - - // Create the user session - $user = $this->getByUsername($values['username']); - $this->updateSession($user); - - // Update login history - $this->lastLogin->create( - $method, - $user['id'], - $this->getIpAddress(), - $this->getUserAgent() - ); - - // Setup the remember me feature - if (! empty($values['remember_me'])) { - $credentials = $this->rememberMe->create($user['id'], $this->getIpAddress(), $this->getUserAgent()); - $this->rememberMe->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']); - } - } - else { - $result = false; - $errors['login'] = t('Bad username or password'); - } - } - - return array( - $result, - $errors - ); - } - - /** - * Authenticate a user - * - * @access public - * @param string $username Username - * @param string $password Password - * @return array - */ - public function authenticate($username, $password) - { - // Database authentication - $user = $this->db->table(self::TABLE)->eq('username', $username)->eq('is_ldap_user', 0)->findOne(); - $authenticated = $user && \password_verify($password, $user['password']); - $method = LastLogin::AUTH_DATABASE; - - // LDAP authentication - if (! $authenticated && LDAP_AUTH) { - $authenticated = $this->ldap->authenticate($username, $password); - $method = LastLogin::AUTH_LDAP; - } - - return array($authenticated, $method); - } - /** * Get the user agent of the connected user * diff --git a/app/common.php b/app/common.php index 9ce0016ab..55ecd8945 100644 --- a/app/common.php +++ b/app/common.php @@ -4,7 +4,19 @@ require __DIR__.'/Core/Loader.php'; require __DIR__.'/helpers.php'; require __DIR__.'/translator.php'; -require 'vendor/swiftmailer/swift_required.php'; +require __DIR__.'/../vendor/SimpleValidator/Validator.php'; +require __DIR__.'/../vendor/SimpleValidator/Base.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Required.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Unique.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/MaxLength.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/MinLength.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Integer.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Equals.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/AlphaNumeric.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/GreaterThan.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Date.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Email.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Numeric.php'; use Core\Event; use Core\Loader; @@ -47,6 +59,10 @@ defined('LDAP_AUTH') or define('LDAP_AUTH', false); defined('LDAP_SERVER') or define('LDAP_SERVER', ''); defined('LDAP_PORT') or define('LDAP_PORT', 389); defined('LDAP_SSL_VERIFY') or define('LDAP_SSL_VERIFY', true); +defined('LDAP_USERNAME') or define('LDAP_USERNAME', null); +defined('LDAP_PASSWORD') or define('LDAP_PASSWORD', null); +defined('LDAP_ACCOUNT_BASE') or define('LDAP_ACCOUNT_BASE', ''); +defined('LDAP_USER_PATTERN') or define('LDAP_USER_PATTERN', ''); defined('LDAP_ACCOUNT_FULLNAME') or define('LDAP_ACCOUNT_FULLNAME', 'displayname'); defined('LDAP_ACCOUNT_EMAIL') or define('LDAP_ACCOUNT_EMAIL', 'mail'); diff --git a/config.default.php b/config.default.php index 5f8313a90..b79bad941 100644 --- a/config.default.php +++ b/config.default.php @@ -55,7 +55,7 @@ define('LDAP_USERNAME', null); define('LDAP_PASSWORD', null); // LDAP account base, i.e. root of all user account -// Example: ou=people,dc=example,dc=com +// Example: ou=People,dc=example,dc=com define('LDAP_ACCOUNT_BASE', ''); // LDAP query pattern to use when searching for a user account diff --git a/docs/ldap-authentication.markdown b/docs/ldap-authentication.markdown index 65abbbb3d..989ee24d8 100644 --- a/docs/ldap-authentication.markdown +++ b/docs/ldap-authentication.markdown @@ -23,17 +23,54 @@ Differences between a local user and a LDAP user are the following: - By default, all LDAP users have no admin privileges - To become administrator, a LDAP user must be promoted by another administrator +The full name and the email address are automatically fetched from the LDAP server. + Configuration ------------- -The first step is to create a custom config file named `config.php`. -This file must be stored in the root directory. +You have to create a custom config file named `config.php` (you can also use the template `config.default.php`). +This file must be stored in the root directory of Kanboard. -To do that, you can create an empty PHP file or copy/rename the sample file `config.default.php`. +### Available configuration parameters + +```php +// Enable LDAP authentication (false by default) +define('LDAP_AUTH', false); + +// LDAP server hostname +define('LDAP_SERVER', ''); + +// LDAP server port (389 by default) +define('LDAP_PORT', 389); + +// By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification. +define('LDAP_SSL_VERIFY', true); + +// LDAP username to connect with. NULL for anonymous bind (by default). +define('LDAP_USERNAME', null); + +// LDAP password to connect with. NULL for anonymous bind (by default). +define('LDAP_PASSWORD', null); + +// LDAP account base, i.e. root of all user account +// Example: ou=People,dc=example,dc=com +define('LDAP_ACCOUNT_BASE', ''); + +// LDAP query pattern to use when searching for a user account +// Example for ActiveDirectory: '(&(objectClass=user)(sAMAccountName=%s))' +// Example for OpenLDAP: 'uid=%s' +define('LDAP_USER_PATTERN', ''); + +// Name of an attribute of the user account object which should be used as the full name of the user. +define('LDAP_ACCOUNT_FULLNAME', 'displayname'); + +// Name of an attribute of the user account object which should be used as the email of the user. +define('LDAP_ACCOUNT_EMAIL', 'mail'); +``` ### Example for Microsoft Active Directory -Let's say we have a domain `MYDOMAIN` (mydomain.local) and the primary controller is `myserver.mydomain.local`. +Let's say we have a domain `KANBOARD` (kanboard.local) and the primary controller is `myserver.kanboard.local`. ```php