From: <gem...@li...> - 2011-11-15 09:37:23
|
Revision: 215 http://gemstracker.svn.sourceforge.net/gemstracker/?rev=215&view=rev Author: mennodekker Date: 2011-11-15 09:37:16 +0000 (Tue, 15 Nov 2011) Log Message: ----------- Almost done putting Auth back in, checkpassword still to be removed Modified Paths: -------------- trunk/library/classes/Gems/Auth.php trunk/library/classes/Gems/Default/IndexAction.php trunk/library/classes/Gems/User/NoLoginDefinition.php trunk/library/classes/Gems/User/OldStaffUserDefinition.php trunk/library/classes/Gems/User/ProjectUserDefinition.php trunk/library/classes/Gems/User/StaffUserDefinition.php trunk/library/classes/Gems/User/User.php trunk/library/classes/Gems/User/UserDefinitionInterface.php Added Paths: ----------- trunk/library/classes/Gems/Auth/ trunk/library/classes/Gems/Auth/Adapter/ trunk/library/classes/Gems/Auth/Adapter/Callback.php Added: trunk/library/classes/Gems/Auth/Adapter/Callback.php =================================================================== --- trunk/library/classes/Gems/Auth/Adapter/Callback.php (rev 0) +++ trunk/library/classes/Gems/Auth/Adapter/Callback.php 2011-11-15 09:37:16 UTC (rev 215) @@ -0,0 +1,90 @@ +<?php +/** + * Copyright (c) 2011, Erasmus MC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Erasmus MC nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Short description of file + * + * @package Gems + * @subpackage Auth + * @copyright Copyright (c) 2011 Erasmus MC + * @license New BSD License + * @version $Id: Sample.php 215 2011-07-12 08:52:54Z michiel $ + */ + +/** + * Short description for Callback + * + * Long description for class Callback (if any)... + * + * @package Gems + * @subpackage Auth + * @copyright Copyright (c) 2011 Erasmus MC + * @license New BSD License + * @since Class available since version 1.0 + * @deprecated Class deprecated since version 2.0 + */ +class Gems_Auth_Adapter_Callback implements Zend_Auth_Adapter_Interface +{ + private $_callback; + private $_identity; + private $_params; + + /** + * Create an auth adapter from a callback + * + * Ideally the callback should return a Zend_Auth_Result, when not it should + * return true or false and in that case this adapter will wrap the result + * in a Zend_Auth_Result + * + * @param type $callback A valid callback + * @param type $identity The identity to use + * @param type $params Array of parameters needed for the callback + */ + public function __construct($callback, $identity, $params) + { + $this->_callback = $callback; + $this->_identity = $identity; + $this->_params = $params; + } + + /** + * Perform the authenticate attempt + * + * @return Zend_Auth_Result + */ + public function authenticate() + { + $result = call_user_func_array($this->_callback, $this->_params); + if ( !($result instanceof Zend_Auth_Result)) { + if ($result === true) { + $result = new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $this->_identity); + } else { + $result = new Zend_Auth_Result(Zend_Auth_Result::FAILURE, $this->_identity); + } + } + return $result; + } +} \ No newline at end of file Modified: trunk/library/classes/Gems/Auth.php =================================================================== --- trunk/library/classes/Gems/Auth.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/Auth.php 2011-11-15 09:37:16 UTC (rev 215) @@ -57,7 +57,7 @@ */ protected $_messageTemplates = array( self::ERROR_DATABASE_NOT_INSTALLED => 'Installation not complete! Login is not yet possible!', - self::ERROR_PASSWORD_DELAY => 'Your account is temporarily blocked, please wait %s minutes' + self::ERROR_PASSWORD_DELAY => 'Your account is temporarily blocked, please wait %s seconds' ); /** @@ -92,37 +92,76 @@ return new Zend_Auth_Result($code, null, (array) $messages); } - public function authenticate(Zend_Auth_Adapter_Interface $adapter, $username = '') { + public function authenticate(Zend_Auth_Adapter_Interface $adapter, $formValues) { try { - /** - * Lookup last failed login and number of failed logins - */ - try { - $sql = "SELECT gul_failed_logins, UNIX_TIMESTAMP(gul_last_failed) AS gul_last_failed - FROM gems__user_logins WHERE gul_login = ?"; - $results = $this->db->fetchRow($sql, array($username)); - } catch (Zend_Db_Exception $zde) { - //If we need to apply a db patch, just use a default value - $results = 0; - MUtil_Echo::r(GemsEscort::getInstance()->translate->_('Please update the database')); - } + $login_name = $formValues['userlogin']; + $organization = $formValues['organization']; + $sql = "SELECT gula_failed_logins, gula_last_failed FROM gems__user_login_attemps WHERE gula_login = ? AND gula_id_organization = ?"; + $values = $this->db->fetchRow($sql, array($login_name, $organization)); - $delay = pow($results['gul_failed_logins'], $this->_delayFactor); - $remaining = ($results['gul_last_failed'] + $delay) - time(); + if (! $values) { + $values = array(); + $values['gula_login'] = $login_name; + $values['gula_id_organization'] = $organization; + $values['gula_failed_logins'] = 0; + $values['gula_last_failed'] = null; + } elseif ($values['gula_failed_logins'] > 0) { + // Get the datetime + $last = new MUtil_Date($values['gula_last_failed'], Zend_Date::ISO_8601); - if ($results['gul_failed_logins'] > 0 && $remaining > 0) { - //$this->_obscureValue = false; - $result = $this->_error(self::ERROR_PASSWORD_DELAY, ceil($remaining / 60)); + // How long to wait until we can ignore the previous failed attempt + $delay = pow($values['gula_failed_logins'], GemsEscort::getInstance()->project->getAccountDelayFactor()); + + if (abs($last->diffSeconds()) <= $delay) { + // Response gets slowly slower + $sleepTime = min($values['gula_failed_logins'], 10); + sleep($sleepTime); + $remaining = $delay - abs($last->diffSeconds()) - $sleepTime; + if ($remaining>0) { + $result = $this->_error(self::ERROR_PASSWORD_DELAY, $remaining); + } + } } - } catch (Zend_Db_Exception $zde) { - $result = $this->_error(self::ERROR_DATABASE_NOT_INSTALLED); + } catch (Zend_Db_Exception $e) { + // Fall through as this does not work if the database upgrade did not run + // MUtil_Echo::r($e); } - if (!isset($result)) { - //Ok we are done without errors, now delegate to the Zend_Auth_Adapter + // We only forward to auth adapter when we have no timeout to prevent hammering the auth system + if (! isset($result) ) { $result = parent::authenticate($adapter); } + if ($result->isValid()) { + $values['gula_failed_logins'] = 0; + $values['gula_last_failed'] = null; + } else { + if ($values['gula_failed_logins']) { + // Only increment when we have no password delay + if ($result->getCode() <> self::ERROR_PASSWORD_DELAY) { + $values['gula_failed_logins'] += 1; + $values['gula_last_failed'] = new Zend_Db_Expr('CURRENT_TIMESTAMP'); + } + } else { + $values['gula_failed_logins'] = 1; + $values['gula_last_failed'] = new Zend_Db_Expr('CURRENT_TIMESTAMP'); + } + $values['gula_failed_logins'] = max($values['gula_failed_logins'], 1); + } + + try { + if (isset($values['gula_login'])) { + $this->db->insert('gems__user_login_attemps', $values); + } else { + $where = $this->db->quoteInto('gula_login = ? AND ', $login_name); + $where .= $this->db->quoteInto('gula_id_organization = ?', $organization); + $this->db->update('gems__user_login_attemps', $values, $where); + } + } catch (Zend_Db_Exception $e) { + // Fall through as this does not work if the database upgrade did not run + // MUtil_Echo::r($e); + } + //Now localize $result = $this->localize($result); Modified: trunk/library/classes/Gems/Default/IndexAction.php =================================================================== --- trunk/library/classes/Gems/Default/IndexAction.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/Default/IndexAction.php 2011-11-15 09:37:16 UTC (rev 215) @@ -184,7 +184,7 @@ $element->setAttrib('size', 10); $element->setAttrib('maxlength', 20); $element->setRequired(true); - $element->addValidator(new Gems_User_LoginPasswordValidator($this->loader->getUserLoader(), 'userlogin', 'organization', $this->translate)); + //$element->addValidator(new Gems_User_LoginPasswordValidator($this->loader->getUserLoader(), 'userlogin', 'organization', $this->translate)); return $element; } @@ -284,31 +284,42 @@ $user = $this->loader->getUser($request->getParam('userlogin'), $request->getParam('organization')); if ($user->isActive()) { - $user->setAsCurrentUser(); + $formValues = $form->getValues(); + $authResult = $user->authenticate($formValues); - /** - * Fix current locale / organization in cookies - */ - Gems_Cookies::setLocale($user->getLocale(), $this->basepath->getBasePath()); - Gems_Cookies::setOrganization($user->getOrganizationId(), $this->basepath->getBasePath()); + if ($authResult->isValid()) { - /** - * Ready - */ - $this->addMessage(sprintf($this->_('Login successful, welcome %s.'), $user->getFullName())); + $user->setAsCurrentUser(); - /** - * Log the login - */ - Gems_AccessLog::getLog($this->db)->log("index.login", $this->getRequest(), null, $user->getUserId(), true); + $user->afterLogin($form->getValues()); - if ($previousRequestParameters = $this->session->previousRequestParameters) { - $this->_reroute(array('controller' => $previousRequestParameters['controller'], 'action' => $previousRequestParameters['action']), false); + /** + * Fix current locale / organization in cookies + */ + Gems_Cookies::setLocale($user->getLocale(), $this->basepath->getBasePath()); + Gems_Cookies::setOrganization($user->getOrganizationId(), $this->basepath->getBasePath()); + + /** + * Ready + */ + $this->addMessage(sprintf($this->_('Login successful, welcome %s.'), $user->getFullName())); + + /** + * Log the login + */ + Gems_AccessLog::getLog($this->db)->log("index.login", $this->getRequest(), null, $user->getUserId(), true); + + if ($previousRequestParameters = $this->session->previousRequestParameters) { + $this->_reroute(array('controller' => $previousRequestParameters['controller'], 'action' => $previousRequestParameters['action']), false); + } else { + // This reroutes to the first available menu page after login + $this->_reroute(array('controller' => null, 'action' => null), true); + } + return; } else { - // This reroutes to the first available menu page after login - $this->_reroute(array('controller' => null, 'action' => null), true); + $errors = $authResult->getMessages(); + $this->addMessage($errors); } - return; } } else { $errors = $form->getErrors(); Modified: trunk/library/classes/Gems/User/NoLoginDefinition.php =================================================================== --- trunk/library/classes/Gems/User/NoLoginDefinition.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/User/NoLoginDefinition.php 2011-11-15 09:37:16 UTC (rev 215) @@ -74,4 +74,15 @@ //'user_organization_id' => 0, //REMOVED AS IT BREAKS STORING LAST ORGANIZATION ); } + + public function getAuthAdapter($formValues) + { + $adapter = new Gems_Auth_Adapter_Callback(array(get_class(),'alwaysFalse'), $formValues['userlogin'], $formValues); + return $adapter; + } + + private function alwaysFalse($params) { + $result = new Zend_Auth_Result(Zend_Auth_Result::FAILURE, $params['userlogin']); + return false; + } } Modified: trunk/library/classes/Gems/User/OldStaffUserDefinition.php =================================================================== --- trunk/library/classes/Gems/User/OldStaffUserDefinition.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/User/OldStaffUserDefinition.php 2011-11-15 09:37:16 UTC (rev 215) @@ -178,4 +178,75 @@ { return md5($password); } + + public function getAuthAdapter($formValues) + { + $adapter = new Zend_Auth_Adapter_DbTable(null, 'gems__staff', 'gsf_login', 'gsf_password'); + + $pwd_hash = $this->hashPassword($formValues['password']); + + $select = $adapter->getDbSelect(); + $select->where('gsf_active = 1') + ->where('gsf_id_organization = ?', $formValues['organization']); + + $adapter->setIdentity($formValues['userlogin']) + ->setCredential($pwd_hash); + + return $adapter; + } + + /** + * Perform UserDefinition specific post-login logic + * + * @param Zend_Auth_Result $authResult + * @return void + */ + public function afterLogin($authResult, $formValues) + { + if ($authResult->isValid()) { + $userData = $this->getUserData($formValues['userlogin'], $formValues['organization']); + $staff_id = $userData['user_id']; + + $sql = 'SELECT gul_id_user FROM gems__user_logins WHERE gul_can_login = 1 AND gul_login = ? AND gul_id_organization = ?'; + + try { + $user_id = $this->db->fetchOne($sql, array($login_name, $organization)); + + $currentTimestamp = new Zend_Db_Expr('CURRENT_TIMESTAMP'); + + // Move to USER_STAFF + $values['gup_id_user'] = $user_id; + $values['gup_password'] = $this->project->getValueHash($password); + $values['gup_reset_key'] = null; + $values['gup_reset_requested'] = null; + $values['gup_reset_required'] = 0; + $values['gup_changed'] = $currentTimestamp ; + $values['gup_changed_by'] = $staff_id; + $values['gup_created'] = $currentTimestamp ; + $values['gup_created_by'] = $staff_id; + + $this->db->insert('gems__user_passwords', $values); + + // Update user class + $values = array(); + $values['gul_user_class'] = Gems_User_UserLoader::USER_STAFF; + $values['gul_changed'] = $currentTimestamp ; + $values['gul_changed_by'] = $staff_id; + $this->db->update('gems__user_logins', $values, $this->db->quoteInto('gul_id_user = ?', $user_id)); + + // Remove old password + $values = array(); + $values['gsf_password'] = null; + $values['gsf_changed'] = $currentTimestamp ; + $values['gsf_changed_by'] = $user_id; + + $this->db->update('gems__staff', $values, $this->db->quoteInto('gsf_id_user = ?', $staff_id)); + + } catch (Zend_Db_Exception $e) { + // Fall through as this does not work if the database upgrade did not run + // MUtil_Echo::r($e); + + } + } + } } Modified: trunk/library/classes/Gems/User/ProjectUserDefinition.php =================================================================== --- trunk/library/classes/Gems/User/ProjectUserDefinition.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/User/ProjectUserDefinition.php 2011-11-15 09:37:16 UTC (rev 215) @@ -86,4 +86,10 @@ 'allowedOrgs' => array($organization => 'SUPER ADMIN') ); } + + public function getAuthAdapter($formValues) + { + $adapter = new Gems_Auth_Adapter_Callback(array($this->project,'checkSuperAdminPassword'), $formValues['userlogin'], $formValues['password']); + return $adapter; + } } Modified: trunk/library/classes/Gems/User/StaffUserDefinition.php =================================================================== --- trunk/library/classes/Gems/User/StaffUserDefinition.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/User/StaffUserDefinition.php 2011-11-15 09:37:16 UTC (rev 215) @@ -252,4 +252,21 @@ return $this; } + + public function getAuthAdapter($formValues) + { + $adapter = new Zend_Auth_Adapter_DbTable($this->db, 'gems__user_passwords', 'gul_login', 'gup_password'); + + $pwd_hash = $this->hashPassword($formValues['password']); + + $select = $adapter->getDbSelect(); + $select->join('gems__user_logins', 'gup_id_user = gul_id_user', array()) + ->where('gul_can_login = 1') + ->where('gul_id_organization = ?', $formValues['organization']); + + $adapter->setIdentity($formValues['userlogin']) + ->setCredential($pwd_hash); + + return $adapter; + } } Modified: trunk/library/classes/Gems/User/User.php =================================================================== --- trunk/library/classes/Gems/User/User.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/User/User.php 2011-11-15 09:37:16 UTC (rev 215) @@ -48,6 +48,12 @@ { /** * + * @var Zend_Auth_Result + */ + private $_authResult; + + /** + * * @var ArrayObject or Zend_Session_Namespace */ private $_vars; @@ -173,6 +179,34 @@ } /** + * Perform project specific after login logic here, can also delegate to the user definition + * + * @return void + */ + public function afterLogin($formValues) { + if (is_callable(array($this->definition, 'afterLogin'))) { + $this->definition->afterLogin($this->_authResult, $formValues); + } + } + + /** + * Authenticate a users credentials using the submitted form + * + * @param array $formValues the array containing all formvalues from the login form + * @return boolean + */ + public function authenticate($formValues) + { + $auth = Gems_Auth::getInstance(); + $adapter = $this->definition->getAuthAdapter($formValues); + $authResult = $auth->authenticate($adapter, $formValues); + + $this->_authResult = $authResult; + + return $authResult; + } + + /** * Return true if a password reset key can be created. * * @return boolean Modified: trunk/library/classes/Gems/User/UserDefinitionInterface.php =================================================================== --- trunk/library/classes/Gems/User/UserDefinitionInterface.php 2011-11-15 08:18:52 UTC (rev 214) +++ trunk/library/classes/Gems/User/UserDefinitionInterface.php 2011-11-15 09:37:16 UTC (rev 215) @@ -88,6 +88,13 @@ public function checkPasswordResetKey(Gems_User_User $user, $key); /** + * Returns an initialized Zend_Auth_Adapter_Interface + * + * @return Zend_Auth_Adapter_Interface + */ + public function getAuthAdapter($formValues); + + /** * Return a password reset key * * @param Gems_User_User $user The user to create a key for. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |