From d392e6eef5308ede89ce9596ee8fa31e2d5a087b Mon Sep 17 00:00:00 2001 From: JustInVTime Date: Fri, 15 Mar 2013 22:11:42 +0100 Subject: [PATCH] Refactored #3490. Split DbTable into 2 different (CallbackCheck and CredentialTreatment) adapters --- .../Zend/Authentication/Adapter/DbTable.php | 486 +----------------- .../Adapter/DbTable/AbstractAdapter.php | 383 ++++++++++++++ .../Adapter/DbTable/CallbackCheckAdapter.php | 121 +++++ .../DbTable/CredentialTreatmentAdapter.php | 123 +++++ .../DbTable/Exception/ExceptionInterface.php | 15 + .../Exception/InvalidArgumentException.php | 18 + .../DbTable/Exception/RuntimeException.php | 17 + .../DbTable/CallbackCheckAdapterTest.php | 385 ++++++++++++++ .../CredentialTreatmentAdapterTest.php | 372 ++++++++++++++ .../Authentication/Adapter/DbTableTest.php | 369 +------------ 10 files changed, 1439 insertions(+), 850 deletions(-) create mode 100644 library/Zend/Authentication/Adapter/DbTable/AbstractAdapter.php create mode 100644 library/Zend/Authentication/Adapter/DbTable/CallbackCheckAdapter.php create mode 100644 library/Zend/Authentication/Adapter/DbTable/CredentialTreatmentAdapter.php create mode 100644 library/Zend/Authentication/Adapter/DbTable/Exception/ExceptionInterface.php create mode 100644 library/Zend/Authentication/Adapter/DbTable/Exception/InvalidArgumentException.php create mode 100644 library/Zend/Authentication/Adapter/DbTable/Exception/RuntimeException.php create mode 100644 tests/ZendTest/Authentication/Adapter/DbTable/CallbackCheckAdapterTest.php create mode 100644 tests/ZendTest/Authentication/Adapter/DbTable/CredentialTreatmentAdapterTest.php diff --git a/library/Zend/Authentication/Adapter/DbTable.php b/library/Zend/Authentication/Adapter/DbTable.php index ec4e3d613f3..a9a54b2bb4e 100644 --- a/library/Zend/Authentication/Adapter/DbTable.php +++ b/library/Zend/Authentication/Adapter/DbTable.php @@ -9,487 +9,9 @@ namespace Zend\Authentication\Adapter; -use stdClass; -use Zend\Authentication\Result as AuthenticationResult; -use Zend\Db\Adapter\Adapter as DbAdapter; -use Zend\Db\ResultSet\ResultSet; -use Zend\Db\Sql; -use Zend\Db\Sql\Expression as SqlExpr; -use Zend\Db\Sql\Predicate\Operator as SqlOp; - -class DbTable extends AbstractAdapter +/** + * @deprecated + */ +class DbTable extends DbTable\CredentialTreatmentAdapter { - - /** - * Database Connection - * - * @var DbAdapter - */ - protected $zendDb = null; - - /** - * @var Sql\Select - */ - protected $dbSelect = null; - - /** - * $tableName - the table name to check - * - * @var string - */ - protected $tableName = null; - - /** - * $identityColumn - the column to use as the identity - * - * @var string - */ - protected $identityColumn = null; - - /** - * $credentialColumns - columns to be used as the credentials - * - * @var string - */ - protected $credentialColumn = null; - - /** - * $credentialTreatment - Treatment applied to the credential, such as MD5() or PASSWORD() - * - * @var string - */ - protected $credentialTreatment = null; - - /** - * $credentialValidationCallback - This overrides the Treatment usage to provide a callback - * that allows for validation to happen in code - * - * @var callable - */ - protected $credentialValidationCallback = null; - - /** - * $authenticateResultInfo - * - * @var array - */ - protected $authenticateResultInfo = null; - - /** - * $resultRow - Results of database authentication query - * - * @var array - */ - protected $resultRow = null; - - /** - * $ambiguityIdentity - Flag to indicate same Identity can be used with - * different credentials. Default is FALSE and need to be set to true to - * allow ambiguity usage. - * - * @var bool - */ - protected $ambiguityIdentity = false; - - /** - * __construct() - Sets configuration options - * - * @param DbAdapter $zendDb - * @param string $tableName Optional - * @param string $identityColumn Optional - * @param string $credentialColumn Optional - * @param string $credentialTreatment Optional - * @param callable $credentialValidationCallback Optional - * @return \Zend\Authentication\Adapter\DbTable - */ - public function __construct(DbAdapter $zendDb, $tableName = null, $identityColumn = null, - $credentialColumn = null, $credentialTreatment = null, $credentialValidationCallback = null) - { - $this->zendDb = $zendDb; - - if (null !== $tableName) { - $this->setTableName($tableName); - } - - if (null !== $identityColumn) { - $this->setIdentityColumn($identityColumn); - } - - if (null !== $credentialColumn) { - $this->setCredentialColumn($credentialColumn); - } - - if (null !== $credentialTreatment) { - $this->setCredentialTreatment($credentialTreatment); - } - - if (null !== $credentialValidationCallback) { - $this->setCredentialValidationCallback($credentialValidationCallback); - } - } - - /** - * setTableName() - set the table name to be used in the select query - * - * @param string $tableName - * @return DbTable Provides a fluent interface - */ - public function setTableName($tableName) - { - $this->tableName = $tableName; - return $this; - } - - /** - * setIdentityColumn() - set the column name to be used as the identity column - * - * @param string $identityColumn - * @return DbTable Provides a fluent interface - */ - public function setIdentityColumn($identityColumn) - { - $this->identityColumn = $identityColumn; - return $this; - } - - /** - * setCredentialColumn() - set the column name to be used as the credential column - * - * @param string $credentialColumn - * @return DbTable Provides a fluent interface - */ - public function setCredentialColumn($credentialColumn) - { - $this->credentialColumn = $credentialColumn; - return $this; - } - - /** - * setCredentialTreatment() - allows the developer to pass a parametrized string that is - * used to transform or treat the input credential data. - * - * In many cases, passwords and other sensitive data are encrypted, hashed, encoded, - * obscured, or otherwise treated through some function or algorithm. By specifying a - * parametrized treatment string with this method, a developer may apply arbitrary SQL - * upon input credential data. - * - * Examples: - * - * 'PASSWORD(?)' - * 'MD5(?)' - * - * @param string $treatment - * @return DbTable Provides a fluent interface - */ - public function setCredentialTreatment($treatment) - { - $this->credentialTreatment = $treatment; - return $this; - } - - /** - * setCredentialValidationCallback() - allows the developer to use a callback as a way of checking the - * credential. - * - * @param callable $validationCallback - * @return DbTable - * @throws Exception\InvalidArgumentException - */ - public function setCredentialValidationCallback($validationCallback) - { - if (!is_callable($validationCallback)) { - throw new Exception\InvalidArgumentException('Invalid callback provided'); - } - $this->credentialValidationCallback = $validationCallback; - return $this; - } - - /** - * setAmbiguityIdentity() - sets a flag for usage of identical identities - * with unique credentials. It accepts integers (0, 1) or boolean (true, - * false) parameters. Default is false. - * - * @param int|bool $flag - * @return DbTable Provides a fluent interface - */ - public function setAmbiguityIdentity($flag) - { - if (is_integer($flag)) { - $this->ambiguityIdentity = (1 === $flag ? true : false); - } elseif (is_bool($flag)) { - $this->ambiguityIdentity = $flag; - } - return $this; - } - - /** - * getAmbiguityIdentity() - returns TRUE for usage of multiple identical - * identities with different credentials, FALSE if not used. - * - * @return bool - */ - public function getAmbiguityIdentity() - { - return $this->ambiguityIdentity; - } - - /** - * getDbSelect() - Return the preauthentication Db Select object for userland select query modification - * - * @return Sql\Select - */ - public function getDbSelect() - { - if ($this->dbSelect == null) { - $this->dbSelect = new Sql\Select(); - } - return $this->dbSelect; - } - - /** - * getResultRowObject() - Returns the result row as a stdClass object - * - * @param string|array $returnColumns - * @param string|array $omitColumns - * @return stdClass|bool - */ - public function getResultRowObject($returnColumns = null, $omitColumns = null) - { - if (!$this->resultRow) { - return false; - } - - $returnObject = new stdClass(); - - if (null !== $returnColumns) { - - $availableColumns = array_keys($this->resultRow); - foreach ((array) $returnColumns as $returnColumn) { - if (in_array($returnColumn, $availableColumns)) { - $returnObject->{$returnColumn} = $this->resultRow[$returnColumn]; - } - } - return $returnObject; - - } elseif (null !== $omitColumns) { - - $omitColumns = (array) $omitColumns; - foreach ($this->resultRow as $resultColumn => $resultValue) { - if (!in_array($resultColumn, $omitColumns)) { - $returnObject->{$resultColumn} = $resultValue; - } - } - return $returnObject; - - } - - foreach ($this->resultRow as $resultColumn => $resultValue) { - $returnObject->{$resultColumn} = $resultValue; - } - return $returnObject; - } - - /** - * This method is called to attempt an authentication. Previous to this - * call, this adapter would have already been configured with all - * necessary information to successfully connect to a database table and - * attempt to find a record matching the provided identity. - * - * @throws Exception\RuntimeException if answering the authentication query is impossible - * @return AuthenticationResult - */ - public function authenticate() - { - $this->_authenticateSetup(); - $dbSelect = $this->_authenticateCreateSelect(); - $resultIdentities = $this->_authenticateQuerySelect($dbSelect); - - if (($authResult = $this->_authenticateValidateResultSet($resultIdentities)) instanceof AuthenticationResult) { - return $authResult; - } - - // At this point, ambiguity is already done. Loop, check and break on success. - foreach ($resultIdentities as $identity) { - $authResult = $this->_authenticateValidateResult($identity); - if ($authResult->isValid()) { - break; - } - } - - return $authResult; - } - - /** - * _authenticateSetup() - This method abstracts the steps involved with - * making sure that this adapter was indeed setup properly with all - * required pieces of information. - * - * @throws Exception\RuntimeException in the event that setup was not done properly - * @return bool - */ - protected function _authenticateSetup() - { - $exception = null; - - if ($this->tableName == '') { - $exception = 'A table must be supplied for the DbTable authentication adapter.'; - } elseif ($this->identityColumn == '') { - $exception = 'An identity column must be supplied for the DbTable authentication adapter.'; - } elseif ($this->credentialColumn == '') { - $exception = 'A credential column must be supplied for the DbTable authentication adapter.'; - } elseif ($this->identity == '') { - $exception = 'A value for the identity was not provided prior to authentication with DbTable.'; - } elseif ($this->credential === null) { - $exception = 'A credential value was not provided prior to authentication with DbTable.'; - } - - if (null !== $exception) { - throw new Exception\RuntimeException($exception); - } - - $this->authenticateResultInfo = array( - 'code' => AuthenticationResult::FAILURE, - 'identity' => $this->identity, - 'messages' => array() - ); - - return true; - } - - /** - * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that - * is completely configured to be queried against the database. - * - * @return DbSelect - */ - protected function _authenticateCreateSelect() - { - $tableColumns = array('*'); - if (!is_callable($this->credentialValidationCallback)) { - // build credential expression - if (empty($this->credentialTreatment) || (strpos($this->credentialTreatment, '?') === false)) { - $this->credentialTreatment = '?'; - } - - $credentialExpression = new SqlExpr( - '(CASE WHEN ?' . ' = ' . $this->credentialTreatment . ' THEN 1 ELSE 0 END) AS ?', - array($this->credentialColumn, $this->credential, 'zend_auth_credential_match'), - array(SqlExpr::TYPE_IDENTIFIER, SqlExpr::TYPE_VALUE, SqlExpr::TYPE_IDENTIFIER) - ); - $tableColumns[] = $credentialExpression; - } - - // get select - $dbSelect = clone $this->getDbSelect(); - $dbSelect->from($this->tableName) - ->columns($tableColumns) - ->where(new SqlOp($this->identityColumn, '=', $this->identity)); - - return $dbSelect; - } - - /** - * _authenticateQuerySelect() - This method accepts a Zend\Db\Sql\Select object and - * performs a query against the database with that object. - * - * @param DbSelect $dbSelect - * @throws Exception\RuntimeException when an invalid select object is encountered - * @return array - */ - protected function _authenticateQuerySelect(Sql\Select $dbSelect) - { - $sql = new Sql\Sql($this->zendDb); - $statement = $sql->prepareStatementForSqlObject($dbSelect); - try { - $result = $statement->execute(); - $resultIdentities = array(); - // iterate result, most cross platform way - foreach ($result as $row) { - $resultIdentities[] = $row; - } - } catch (\Exception $e) { - throw new Exception\RuntimeException( - 'The supplied parameters to DbTable failed to ' - . 'produce a valid sql statement, please check table and column names ' - . 'for validity.', 0, $e - ); - } - return $resultIdentities; - } - - /** - * _authenticateValidateResultSet() - This method attempts to make - * certain that only one record was returned in the resultset - * - * @param array $resultIdentities - * @return bool|\Zend\Authentication\Result - */ - protected function _authenticateValidateResultSet(array $resultIdentities) - { - - if (count($resultIdentities) < 1) { - $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND; - $this->authenticateResultInfo['messages'][] = 'A record with the supplied identity could not be found.'; - return $this->_authenticateCreateAuthResult(); - } elseif (count($resultIdentities) > 1 && false === $this->getAmbiguityIdentity()) { - $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_IDENTITY_AMBIGUOUS; - $this->authenticateResultInfo['messages'][] = 'More than one record matches the supplied identity.'; - return $this->_authenticateCreateAuthResult(); - } - - return true; - } - - /** - * _authenticateValidateResult() - This method attempts to validate that - * the record in the resultset is indeed a record that matched the - * identity provided to this adapter. - * - * @param array $resultIdentity - * @return AuthenticationResult - */ - protected function _authenticateValidateResult($resultIdentity) - { - if (is_callable($this->credentialValidationCallback)) { - // since we are not aware of - try { - $callbackResult = call_user_func($this->credentialValidationCallback, $resultIdentity[$this->credentialColumn], $this->credential); - } catch (Exception $e) { - $callbackResult = false; - } - if ($callbackResult !== true) { - $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_CREDENTIAL_INVALID; - $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.'; - return $this->_authenticateCreateAuthResult(); - } - } else { - if ($resultIdentity['zend_auth_credential_match'] != '1') { - $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_CREDENTIAL_INVALID; - $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.'; - return $this->_authenticateCreateAuthResult(); - } - - unset($resultIdentity['zend_auth_credential_match']); - } - $this->resultRow = $resultIdentity; - - $this->authenticateResultInfo['code'] = AuthenticationResult::SUCCESS; - $this->authenticateResultInfo['messages'][] = 'Authentication successful.'; - return $this->_authenticateCreateAuthResult(); - } - - /** - * Creates a Zend\Authentication\Result object from the information that - * has been collected during the authenticate() attempt. - * - * @return AuthenticationResult - */ - protected function _authenticateCreateAuthResult() - { - return new AuthenticationResult( - $this->authenticateResultInfo['code'], - $this->authenticateResultInfo['identity'], - $this->authenticateResultInfo['messages'] - ); - } } diff --git a/library/Zend/Authentication/Adapter/DbTable/AbstractAdapter.php b/library/Zend/Authentication/Adapter/DbTable/AbstractAdapter.php new file mode 100644 index 00000000000..be85df1380b --- /dev/null +++ b/library/Zend/Authentication/Adapter/DbTable/AbstractAdapter.php @@ -0,0 +1,383 @@ +zendDb = $zendDb; + + if (null !== $tableName) { + $this->setTableName($tableName); + } + + if (null !== $identityColumn) { + $this->setIdentityColumn($identityColumn); + } + + if (null !== $credentialColumn) { + $this->setCredentialColumn($credentialColumn); + } + } + + + /** + * setTableName() - set the table name to be used in the select query + * + * @param string $tableName + * @return DbTable Provides a fluent interface + */ + public function setTableName($tableName) + { + $this->tableName = $tableName; + return $this; + } + + /** + * setIdentityColumn() - set the column name to be used as the identity column + * + * @param string $identityColumn + * @return DbTable Provides a fluent interface + */ + public function setIdentityColumn($identityColumn) + { + $this->identityColumn = $identityColumn; + return $this; + } + + /** + * setCredentialColumn() - set the column name to be used as the credential column + * + * @param string $credentialColumn + * @return DbTable Provides a fluent interface + */ + public function setCredentialColumn($credentialColumn) + { + $this->credentialColumn = $credentialColumn; + return $this; + } + + /** + * setAmbiguityIdentity() - sets a flag for usage of identical identities + * with unique credentials. It accepts integers (0, 1) or boolean (true, + * false) parameters. Default is false. + * + * @param int|bool $flag + * @return DbTable Provides a fluent interface + */ + public function setAmbiguityIdentity($flag) + { + if (is_integer($flag)) { + $this->ambiguityIdentity = (1 === $flag ? true : false); + } elseif (is_bool($flag)) { + $this->ambiguityIdentity = $flag; + } + return $this; + } + + /** + * getAmbiguityIdentity() - returns TRUE for usage of multiple identical + * identities with different credentials, FALSE if not used. + * + * @return bool + */ + public function getAmbiguityIdentity() + { + return $this->ambiguityIdentity; + } + + /** + * getDbSelect() - Return the preauthentication Db Select object for userland select query modification + * + * @return Sql\Select + */ + public function getDbSelect() + { + if ($this->dbSelect == null) { + $this->dbSelect = new Sql\Select(); + } + return $this->dbSelect; + } + + /** + * getResultRowObject() - Returns the result row as a stdClass object + * + * @param string|array $returnColumns + * @param string|array $omitColumns + * @return stdClass|bool + */ + public function getResultRowObject($returnColumns = null, $omitColumns = null) + { + if (!$this->resultRow) { + return false; + } + + $returnObject = new stdClass(); + + if (null !== $returnColumns) { + + $availableColumns = array_keys($this->resultRow); + foreach ((array) $returnColumns as $returnColumn) { + if (in_array($returnColumn, $availableColumns)) { + $returnObject->{$returnColumn} = $this->resultRow[$returnColumn]; + } + } + return $returnObject; + + } elseif (null !== $omitColumns) { + + $omitColumns = (array) $omitColumns; + foreach ($this->resultRow as $resultColumn => $resultValue) { + if (!in_array($resultColumn, $omitColumns)) { + $returnObject->{$resultColumn} = $resultValue; + } + } + return $returnObject; + + } + + foreach ($this->resultRow as $resultColumn => $resultValue) { + $returnObject->{$resultColumn} = $resultValue; + } + return $returnObject; + } + + + /** + * This method is called to attempt an authentication. Previous to this + * call, this adapter would have already been configured with all + * necessary information to successfully connect to a database table and + * attempt to find a record matching the provided identity. + * + * @throws Exception\RuntimeException if answering the authentication query is impossible + * @return AuthenticationResult + */ + public function authenticate() + { + $this->_authenticateSetup(); + $dbSelect = $this->_authenticateCreateSelect(); + $resultIdentities = $this->_authenticateQuerySelect($dbSelect); + + if (($authResult = $this->_authenticateValidateResultSet($resultIdentities)) instanceof AuthenticationResult) { + return $authResult; + } + + // At this point, ambiguity is already done. Loop, check and break on success. + foreach ($resultIdentities as $identity) { + $authResult = $this->_authenticateValidateResult($identity); + if ($authResult->isValid()) { + break; + } + } + + return $authResult; + } + + /** + * _authenticateValidateResult() - This method attempts to validate that + * the record in the resultset is indeed a record that matched the + * identity provided to this adapter. + * + * @param array $resultIdentity + * @return AuthenticationResult + */ + abstract protected function _authenticateValidateResult($resultIdentity); + + /** + * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that + * is completely configured to be queried against the database. + * + * @return Sql\Select + */ + abstract protected function _authenticateCreateSelect(); + + /** + * _authenticateSetup() - This method abstracts the steps involved with + * making sure that this adapter was indeed setup properly with all + * required pieces of information. + * + * @throws Exception\RuntimeException in the event that setup was not done properly + * @return bool + */ + protected function _authenticateSetup() + { + $exception = null; + + if ($this->tableName == '') { + $exception = 'A table must be supplied for the DbTable authentication adapter.'; + } elseif ($this->identityColumn == '') { + $exception = 'An identity column must be supplied for the DbTable authentication adapter.'; + } elseif ($this->credentialColumn == '') { + $exception = 'A credential column must be supplied for the DbTable authentication adapter.'; + } elseif ($this->identity == '') { + $exception = 'A value for the identity was not provided prior to authentication with DbTable.'; + } elseif ($this->credential === null) { + $exception = 'A credential value was not provided prior to authentication with DbTable.'; + } + + if (null !== $exception) { + throw new Exception\RuntimeException($exception); + } + + $this->authenticateResultInfo = array( + 'code' => AuthenticationResult::FAILURE, + 'identity' => $this->identity, + 'messages' => array() + ); + + return true; + } + + /** + * _authenticateQuerySelect() - This method accepts a Zend\Db\Sql\Select object and + * performs a query against the database with that object. + * + * @param Sql\Select $dbSelect + * @throws Exception\RuntimeException when an invalid select object is encountered + * @return array + */ + protected function _authenticateQuerySelect(Sql\Select $dbSelect) + { + $sql = new Sql\Sql($this->zendDb); + $statement = $sql->prepareStatementForSqlObject($dbSelect); + try { + $result = $statement->execute(); + $resultIdentities = array(); + // iterate result, most cross platform way + foreach ($result as $row) { + $resultIdentities[] = $row; + } + } catch (\Exception $e) { + throw new Exception\RuntimeException( + 'The supplied parameters to DbTable failed to ' + . 'produce a valid sql statement, please check table and column names ' + . 'for validity.', 0, $e + ); + } + return $resultIdentities; + } + + /** + * _authenticateValidateResultSet() - This method attempts to make + * certain that only one record was returned in the resultset + * + * @param array $resultIdentities + * @return bool|\Zend\Authentication\Result + */ + protected function _authenticateValidateResultSet(array $resultIdentities) + { + + if (count($resultIdentities) < 1) { + $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND; + $this->authenticateResultInfo['messages'][] = 'A record with the supplied identity could not be found.'; + return $this->_authenticateCreateAuthResult(); + } elseif (count($resultIdentities) > 1 && false === $this->getAmbiguityIdentity()) { + $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_IDENTITY_AMBIGUOUS; + $this->authenticateResultInfo['messages'][] = 'More than one record matches the supplied identity.'; + return $this->_authenticateCreateAuthResult(); + } + + return true; + } + + /** + * Creates a Zend\Authentication\Result object from the information that + * has been collected during the authenticate() attempt. + * + * @return AuthenticationResult + */ + protected function _authenticateCreateAuthResult() + { + return new AuthenticationResult( + $this->authenticateResultInfo['code'], + $this->authenticateResultInfo['identity'], + $this->authenticateResultInfo['messages'] + ); + } + + + +} diff --git a/library/Zend/Authentication/Adapter/DbTable/CallbackCheckAdapter.php b/library/Zend/Authentication/Adapter/DbTable/CallbackCheckAdapter.php new file mode 100644 index 00000000000..1ed643e9264 --- /dev/null +++ b/library/Zend/Authentication/Adapter/DbTable/CallbackCheckAdapter.php @@ -0,0 +1,121 @@ +setCredentialValidationCallback($credentialValidationCallback); + } else { + $this->setCredentialValidationCallback(function($a, $b){ + return $a === $b; + }); + } + } + + /** + * setCredentialValidationCallback() - allows the developer to use a callback as a way of checking the + * credential. + * + * @param callable $validationCallback + * @return DbTable + * @throws Exception\InvalidArgumentException + */ + public function setCredentialValidationCallback($validationCallback) + { + if (!is_callable($validationCallback)) { + throw new Exception\InvalidArgumentException('Invalid callback provided'); + } + $this->credentialValidationCallback = $validationCallback; + return $this; + } + + + + /** + * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that + * is completely configured to be queried against the database. + * + * @return Sql\Select + */ + protected function _authenticateCreateSelect() + { + // get select + $dbSelect = clone $this->getDbSelect(); + $dbSelect->from($this->tableName) + ->columns(array(Sql\Select::SQL_STAR)) + ->where(new SqlOp($this->identityColumn, '=', $this->identity)); + + return $dbSelect; + } + + + + /** + * _authenticateValidateResult() - This method attempts to validate that + * the record in the resultset is indeed a record that matched the + * identity provided to this adapter. + * + * @param array $resultIdentity + * @return AuthenticationResult + */ + protected function _authenticateValidateResult($resultIdentity) + { + try { + $callbackResult = call_user_func($this->credentialValidationCallback, $resultIdentity[$this->credentialColumn], $this->credential); + } catch (\Exception $e) { + $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_UNCATEGORIZED; + $this->authenticateResultInfo['messages'][] = $e->getMessage(); + return $this->_authenticateCreateAuthResult(); + } + if ($callbackResult !== true) { + $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_CREDENTIAL_INVALID; + $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.'; + return $this->_authenticateCreateAuthResult(); + } + + $this->resultRow = $resultIdentity; + + $this->authenticateResultInfo['code'] = AuthenticationResult::SUCCESS; + $this->authenticateResultInfo['messages'][] = 'Authentication successful.'; + return $this->_authenticateCreateAuthResult(); + } +} diff --git a/library/Zend/Authentication/Adapter/DbTable/CredentialTreatmentAdapter.php b/library/Zend/Authentication/Adapter/DbTable/CredentialTreatmentAdapter.php new file mode 100644 index 00000000000..3ff8b867916 --- /dev/null +++ b/library/Zend/Authentication/Adapter/DbTable/CredentialTreatmentAdapter.php @@ -0,0 +1,123 @@ +setCredentialTreatment($credentialTreatment); + } + } + + + /** + * setCredentialTreatment() - allows the developer to pass a parametrized string that is + * used to transform or treat the input credential data. + * + * In many cases, passwords and other sensitive data are encrypted, hashed, encoded, + * obscured, or otherwise treated through some function or algorithm. By specifying a + * parametrized treatment string with this method, a developer may apply arbitrary SQL + * upon input credential data. + * + * Examples: + * + * 'PASSWORD(?)' + * 'MD5(?)' + * + * @param string $treatment + * @return DbTable Provides a fluent interface + */ + public function setCredentialTreatment($treatment) + { + $this->credentialTreatment = $treatment; + return $this; + } + + /** + * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that + * is completely configured to be queried against the database. + * + * @return Sql\Select + */ + protected function _authenticateCreateSelect() + { + // build credential expression + if (empty($this->credentialTreatment) || (strpos($this->credentialTreatment, '?') === false)) { + $this->credentialTreatment = '?'; + } + + $credentialExpression = new SqlExpr( + '(CASE WHEN ?' . ' = ' . $this->credentialTreatment . ' THEN 1 ELSE 0 END) AS ?', + array($this->credentialColumn, $this->credential, 'zend_auth_credential_match'), + array(SqlExpr::TYPE_IDENTIFIER, SqlExpr::TYPE_VALUE, SqlExpr::TYPE_IDENTIFIER) + ); + + // get select + $dbSelect = clone $this->getDbSelect(); + $dbSelect->from($this->tableName) + ->columns(array('*', $credentialExpression)) + ->where(new SqlOp($this->identityColumn, '=', $this->identity)); + + return $dbSelect; + } + + /** + * _authenticateValidateResult() - This method attempts to validate that + * the record in the resultset is indeed a record that matched the + * identity provided to this adapter. + * + * @param array $resultIdentity + * @return AuthenticationResult + */ + protected function _authenticateValidateResult($resultIdentity) + { + if ($resultIdentity['zend_auth_credential_match'] != '1') { + $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_CREDENTIAL_INVALID; + $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.'; + return $this->_authenticateCreateAuthResult(); + } + + unset($resultIdentity['zend_auth_credential_match']); + $this->resultRow = $resultIdentity; + + $this->authenticateResultInfo['code'] = AuthenticationResult::SUCCESS; + $this->authenticateResultInfo['messages'][] = 'Authentication successful.'; + return $this->_authenticateCreateAuthResult(); + } +} diff --git a/library/Zend/Authentication/Adapter/DbTable/Exception/ExceptionInterface.php b/library/Zend/Authentication/Adapter/DbTable/Exception/ExceptionInterface.php new file mode 100644 index 00000000000..c205de32f8b --- /dev/null +++ b/library/Zend/Authentication/Adapter/DbTable/Exception/ExceptionInterface.php @@ -0,0 +1,15 @@ +markTestSkipped('Tests are not enabled in TestConfiguration.php'); + return; + } elseif (!extension_loaded('pdo')) { + $this->markTestSkipped('PDO extension is not loaded'); + return; + } elseif (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('SQLite PDO driver is not available'); + return; + } + + $this->_setupDbAdapter(); + $this->_setupAuthAdapter(); + } + + public function tearDown() + { + $this->_adapter = null; + if ($this->_db instanceof DbAdapter) { + $this->_db->query('DROP TABLE [users]'); + } + $this->_db = null; + } + + /** + * Ensures expected behavior for authentication success + */ + public function testAuthenticateSuccess() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $result = $this->_adapter->authenticate(); + $this->assertTrue($result->isValid()); + } + + + /** + * Ensures expected behavior for authentication success + */ + public function testAuthenticateSuccessWithCallback() + { + $this->_adapter = new Adapter\DbTable($this->_db, 'users', 'username', 'password', null, function($a, $b){return $a === $b;}); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $result = $this->_adapter->authenticate(); + $this->assertTrue($result->isValid()); + } + + + /** + * Ensures expected behavior for an invalid callback + */ + public function testAuthenticateCallbackThrowsException() + { + $this->setExpectedException( + 'Zend\Authentication\Adapter\Dbtable\Exception\InvalidArgumentException', + 'Invalid callback provided' + ); + $this->_adapter->setCredentialValidationCallback('This is not a valid callback'); + } + + /** + * Ensures expected behavior for for authentication failure + * reason: Identity not found. + */ + public function testAuthenticateFailureIdentityNotFound() + { + $this->_adapter->setIdentity('non_existent_username'); + $this->_adapter->setCredential('my_password'); + + $result = $this->_adapter->authenticate(); + $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_NOT_FOUND, $result->getCode()); + } + + /** + * Ensures expected behavior for for authentication failure + * reason: Identity not found. + */ + public function testAuthenticateFailureIdentityAmbiguous() + { + $sqlInsert = 'INSERT INTO users (username, password, real_name) VALUES ("my_username", "my_password", "My Real Name")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + + $result = $this->_adapter->authenticate(); + $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_AMBIGUOUS, $result->getCode()); + } + + /** + * Ensures expected behavior for authentication failure because of a bad password + */ + public function testAuthenticateFailureInvalidCredential() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password_bad'); + $result = $this->_adapter->authenticate(); + $this->assertFalse($result->isValid()); + } + + /** + * Ensures that getResultRowObject() works for successful authentication + */ + public function testGetResultRow() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $resultRow = $this->_adapter->getResultRowObject(); + $this->assertEquals($resultRow->username, 'my_username'); + } + + /** + * Ensure that ResultRowObject returns only what told to be included + */ + public function testGetSpecificResultRow() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $resultRow = $this->_adapter->getResultRowObject(array('username', 'real_name')); + $this->assertEquals('O:8:"stdClass":2:{s:8:"username";s:11:"my_username";s:9:"real_name";s:12:"My Real Name";}', + serialize($resultRow)); + } + + /** + * Ensure that ResultRowObject returns an object has specific omissions + */ + public function testGetOmittedResultRow() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $resultRow = $this->_adapter->getResultRowObject(null, 'password'); + $this->assertEquals('O:8:"stdClass":3:{s:2:"id";s:1:"1";s:8:"username";s:11:"my_username";s:9:"real_name";s:12:"My Real Name";}', + serialize($resultRow)); + } + + /** + * @group ZF-5957 + */ + public function testAdapterCanReturnDbSelectObject() + { + $this->assertTrue($this->_adapter->getDbSelect() instanceof DBSelect); + } + + /** + * @group ZF-5957 + */ + public function testAdapterCanUseModifiedDbSelectObject() + { + $select = $this->_adapter->getDbSelect(); + $select->where('1 = 0'); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + + $result = $this->_adapter->authenticate(); + $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_NOT_FOUND, $result->getCode()); + } + + /** + * @group ZF-5957 + */ + public function testAdapterReturnsASelectObjectWithoutAuthTimeModificationsAfterAuth() + { + $select = $this->_adapter->getDbSelect(); + $select->where('1 = 1'); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $selectAfterAuth = $this->_adapter->getDbSelect(); + $whereParts = $selectAfterAuth->where->getPredicates(); + $this->assertEquals(1, count($whereParts)); + + $lastWherePart = array_pop($whereParts); + $expressionData = $lastWherePart[1]->getExpressionData(); + $this->assertEquals('1 = 1', $expressionData[0][0]); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoTable() + { + $this->setExpectedException('Zend\Authentication\Adapter\Dbtable\Exception\RuntimeException', + 'A table must be supplied for'); + $adapter = new Adapter\DbTable($this->_db); + $adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoIdentityColumn() + { + $this->setExpectedException('Zend\Authentication\Adapter\Dbtable\Exception\RuntimeException', + 'An identity column must be supplied for the'); + $adapter = new Adapter\DbTable($this->_db, 'users'); + $adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoCredentialColumn() + { + $this->setExpectedException('Zend\Authentication\Adapter\Dbtable\Exception\RuntimeException', + 'A credential column must be supplied'); + $adapter = new Adapter\DbTable($this->_db, 'users', 'username'); + $adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoIdentity() + { + $this->setExpectedException('Zend\Authentication\Adapter\Dbtable\Exception\RuntimeException', + 'A value for the identity was not provided prior'); + $this->_adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoCredential() + { + $this->setExpectedException('Zend\Authentication\Adapter\Dbtable\Exception\RuntimeException', + 'A credential value was not provided prior'); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionBadSql() + { + $this->setExpectedException('Zend\Authentication\Adapter\Dbtable\Exception\RuntimeException', + 'The supplied parameters to'); + $this->_adapter->setTableName('bad_table_name'); + $this->_adapter->setIdentity('value'); + $this->_adapter->setCredential('value'); + $this->_adapter->authenticate(); + } + + /** + * Test to see same usernames with different passwords can not authenticate + * when flag is not set. This is the current state of + * Zend_Auth_Adapter_DbTable (up to ZF 1.10.6) + * + * @group ZF-7289 + */ + public function testEqualUsernamesDifferentPasswordShouldNotAuthenticateWhenFlagIsNotSet() + { + $sqlInsert = 'INSERT INTO users (username, password, real_name) ' + . 'VALUES ("my_username", "my_otherpass", "Test user 2")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + + // test if user 1 can authenticate + $this->_adapter->setIdentity('my_username') + ->setCredential('my_password'); + $result = $this->_adapter->authenticate(); + $this->assertTrue(in_array('More than one record matches the supplied identity.', + $result->getMessages())); + $this->assertFalse($result->isValid()); + } + + /** + * Test to see same usernames with different passwords can authenticate when + * a flag is set + * + * @group ZF-7289 + */ + public function testEqualUsernamesDifferentPasswordShouldAuthenticateWhenFlagIsSet() + { + $sqlInsert = 'INSERT INTO users (username, password, real_name) ' + . 'VALUES ("my_username", "my_otherpass", "Test user 2")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + + // test if user 1 can authenticate + $this->_adapter->setIdentity('my_username') + ->setCredential('my_password') + ->setAmbiguityIdentity(true); + $result = $this->_adapter->authenticate(); + $this->assertFalse(in_array('More than one record matches the supplied identity.', + $result->getMessages())); + $this->assertTrue($result->isValid()); + $this->assertEquals('my_username', $result->getIdentity()); + + $this->_adapter = null; + $this->_setupAuthAdapter(); + + // test if user 2 can authenticate + $this->_adapter->setIdentity('my_username') + ->setCredential('my_otherpass') + ->setAmbiguityIdentity(true); + $result2 = $this->_adapter->authenticate(); + $this->assertFalse(in_array('More than one record matches the supplied identity.', + $result->getMessages())); + $this->assertTrue($result2->isValid()); + $this->assertEquals('my_username', $result2->getIdentity()); + } + + + protected function _setupDbAdapter($optionalParams = array()) + { + $params = array('driver' => 'pdo_sqlite', + 'dbname' => TESTS_ZEND_AUTH_ADAPTER_DBTABLE_PDO_SQLITE_DATABASE); + + if (!empty($optionalParams)) { + $params['options'] = $optionalParams; + } + + $this->_db = new DbAdapter($params); + + $sqlCreate = 'CREATE TABLE IF NOT EXISTS [users] ( ' + . '[id] INTEGER NOT NULL PRIMARY KEY, ' + . '[username] VARCHAR(50) NOT NULL, ' + . '[password] VARCHAR(32) NULL, ' + . '[real_name] VARCHAR(150) NULL)'; + $this->_db->query($sqlCreate, DbAdapter::QUERY_MODE_EXECUTE); + + $sqlDelete = 'DELETE FROM users'; + $this->_db->query($sqlDelete, DbAdapter::QUERY_MODE_EXECUTE); + + $sqlInsert = 'INSERT INTO users (username, password, real_name) ' + . 'VALUES ("my_username", "my_password", "My Real Name")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + } + + protected function _setupAuthAdapter() + { + $this->_adapter = new Adapter\DbTable\CallbackCheckAdapter($this->_db, 'users', 'username', 'password'); + } + +} diff --git a/tests/ZendTest/Authentication/Adapter/DbTable/CredentialTreatmentAdapterTest.php b/tests/ZendTest/Authentication/Adapter/DbTable/CredentialTreatmentAdapterTest.php new file mode 100644 index 00000000000..f6a28baa489 --- /dev/null +++ b/tests/ZendTest/Authentication/Adapter/DbTable/CredentialTreatmentAdapterTest.php @@ -0,0 +1,372 @@ +markTestSkipped('Tests are not enabled in TestConfiguration.php'); + return; + } elseif (!extension_loaded('pdo')) { + $this->markTestSkipped('PDO extension is not loaded'); + return; + } elseif (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('SQLite PDO driver is not available'); + return; + } + + $this->_setupDbAdapter(); + $this->_setupAuthAdapter(); + } + + public function tearDown() + { + $this->_adapter = null; + if ($this->_db instanceof DbAdapter) { + $this->_db->query('DROP TABLE [users]'); + } + $this->_db = null; + } + + /** + * Ensures expected behavior for authentication success + */ + public function testAuthenticateSuccess() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $result = $this->_adapter->authenticate(); + $this->assertTrue($result->isValid()); + } + + /** + * Ensures expected behavior for authentication success + */ + public function testAuthenticateSuccessWithTreatment() + { + $this->_adapter = new Adapter\DbTable($this->_db, 'users', 'username', 'password', '?'); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $result = $this->_adapter->authenticate(); + $this->assertTrue($result->isValid()); + } + + + /** + * Ensures expected behavior for for authentication failure + * reason: Identity not found. + */ + public function testAuthenticateFailureIdentityNotFound() + { + $this->_adapter->setIdentity('non_existent_username'); + $this->_adapter->setCredential('my_password'); + + $result = $this->_adapter->authenticate(); + $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_NOT_FOUND, $result->getCode()); + } + + /** + * Ensures expected behavior for for authentication failure + * reason: Identity not found. + */ + public function testAuthenticateFailureIdentityAmbiguous() + { + $sqlInsert = 'INSERT INTO users (username, password, real_name) VALUES ("my_username", "my_password", "My Real Name")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + + $result = $this->_adapter->authenticate(); + $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_AMBIGUOUS, $result->getCode()); + } + + /** + * Ensures expected behavior for authentication failure because of a bad password + */ + public function testAuthenticateFailureInvalidCredential() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password_bad'); + $result = $this->_adapter->authenticate(); + $this->assertFalse($result->isValid()); + } + + /** + * Ensures that getResultRowObject() works for successful authentication + */ + public function testGetResultRow() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $resultRow = $this->_adapter->getResultRowObject(); + $this->assertEquals($resultRow->username, 'my_username'); + } + + /** + * Ensure that ResultRowObject returns only what told to be included + */ + public function testGetSpecificResultRow() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $resultRow = $this->_adapter->getResultRowObject(array('username', 'real_name')); + $this->assertEquals('O:8:"stdClass":2:{s:8:"username";s:11:"my_username";s:9:"real_name";s:12:"My Real Name";}', + serialize($resultRow)); + } + + /** + * Ensure that ResultRowObject returns an object has specific omissions + */ + public function testGetOmittedResultRow() + { + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $resultRow = $this->_adapter->getResultRowObject(null, 'password'); + $this->assertEquals('O:8:"stdClass":3:{s:2:"id";s:1:"1";s:8:"username";s:11:"my_username";s:9:"real_name";s:12:"My Real Name";}', + serialize($resultRow)); + } + + /** + * @group ZF-5957 + */ + public function testAdapterCanReturnDbSelectObject() + { + $this->assertTrue($this->_adapter->getDbSelect() instanceof DBSelect); + } + + /** + * @group ZF-5957 + */ + public function testAdapterCanUseModifiedDbSelectObject() + { + $select = $this->_adapter->getDbSelect(); + $select->where('1 = 0'); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + + $result = $this->_adapter->authenticate(); + $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_NOT_FOUND, $result->getCode()); + } + + /** + * @group ZF-5957 + */ + public function testAdapterReturnsASelectObjectWithoutAuthTimeModificationsAfterAuth() + { + $select = $this->_adapter->getDbSelect(); + $select->where('1 = 1'); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->setCredential('my_password'); + $this->_adapter->authenticate(); + $selectAfterAuth = $this->_adapter->getDbSelect(); + $whereParts = $selectAfterAuth->where->getPredicates(); + $this->assertEquals(1, count($whereParts)); + + $lastWherePart = array_pop($whereParts); + $expressionData = $lastWherePart[1]->getExpressionData(); + $this->assertEquals('1 = 1', $expressionData[0][0]); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoTable() + { + $this->setExpectedException('Zend\Authentication\Adapter\DbTable\Exception\RuntimeException', + 'A table must be supplied for'); + $adapter = new Adapter\DbTable($this->_db); + $adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoIdentityColumn() + { + $this->setExpectedException('Zend\Authentication\Adapter\DbTable\Exception\RuntimeException', + 'An identity column must be supplied for the'); + $adapter = new Adapter\DbTable($this->_db, 'users'); + $adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoCredentialColumn() + { + $this->setExpectedException('Zend\Authentication\Adapter\DbTable\Exception\RuntimeException', + 'A credential column must be supplied'); + $adapter = new Adapter\DbTable($this->_db, 'users', 'username'); + $adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoIdentity() + { + $this->setExpectedException('Zend\Authentication\Adapter\DbTable\Exception\RuntimeException', + 'A value for the identity was not provided prior'); + $this->_adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionNoCredential() + { + $this->setExpectedException('Zend\Authentication\Adapter\DbTable\Exception\RuntimeException', + 'A credential value was not provided prior'); + $this->_adapter->setIdentity('my_username'); + $this->_adapter->authenticate(); + } + + /** + * Ensure that exceptions are caught + */ + public function testCatchExceptionBadSql() + { + $this->setExpectedException('Zend\Authentication\Adapter\DbTable\Exception\RuntimeException', + 'The supplied parameters to'); + $this->_adapter->setTableName('bad_table_name'); + $this->_adapter->setIdentity('value'); + $this->_adapter->setCredential('value'); + $this->_adapter->authenticate(); + } + + /** + * Test to see same usernames with different passwords can not authenticate + * when flag is not set. This is the current state of + * Zend_Auth_Adapter_DbTable (up to ZF 1.10.6) + * + * @group ZF-7289 + */ + public function testEqualUsernamesDifferentPasswordShouldNotAuthenticateWhenFlagIsNotSet() + { + $sqlInsert = 'INSERT INTO users (username, password, real_name) ' + . 'VALUES ("my_username", "my_otherpass", "Test user 2")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + + // test if user 1 can authenticate + $this->_adapter->setIdentity('my_username') + ->setCredential('my_password'); + $result = $this->_adapter->authenticate(); + $this->assertTrue(in_array('More than one record matches the supplied identity.', + $result->getMessages())); + $this->assertFalse($result->isValid()); + } + + /** + * Test to see same usernames with different passwords can authenticate when + * a flag is set + * + * @group ZF-7289 + */ + public function testEqualUsernamesDifferentPasswordShouldAuthenticateWhenFlagIsSet() + { + $sqlInsert = 'INSERT INTO users (username, password, real_name) ' + . 'VALUES ("my_username", "my_otherpass", "Test user 2")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + + // test if user 1 can authenticate + $this->_adapter->setIdentity('my_username') + ->setCredential('my_password') + ->setAmbiguityIdentity(true); + $result = $this->_adapter->authenticate(); + $this->assertFalse(in_array('More than one record matches the supplied identity.', + $result->getMessages())); + $this->assertTrue($result->isValid()); + $this->assertEquals('my_username', $result->getIdentity()); + + $this->_adapter = null; + $this->_setupAuthAdapter(); + + // test if user 2 can authenticate + $this->_adapter->setIdentity('my_username') + ->setCredential('my_otherpass') + ->setAmbiguityIdentity(true); + $result2 = $this->_adapter->authenticate(); + $this->assertFalse(in_array('More than one record matches the supplied identity.', + $result->getMessages())); + $this->assertTrue($result2->isValid()); + $this->assertEquals('my_username', $result2->getIdentity()); + } + + + protected function _setupDbAdapter($optionalParams = array()) + { + $params = array('driver' => 'pdo_sqlite', + 'dbname' => TESTS_ZEND_AUTH_ADAPTER_DBTABLE_PDO_SQLITE_DATABASE); + + if (!empty($optionalParams)) { + $params['options'] = $optionalParams; + } + + $this->_db = new DbAdapter($params); + + $sqlCreate = 'CREATE TABLE IF NOT EXISTS [users] ( ' + . '[id] INTEGER NOT NULL PRIMARY KEY, ' + . '[username] VARCHAR(50) NOT NULL, ' + . '[password] VARCHAR(32) NULL, ' + . '[real_name] VARCHAR(150) NULL)'; + $this->_db->query($sqlCreate, DbAdapter::QUERY_MODE_EXECUTE); + + $sqlDelete = 'DELETE FROM users'; + $this->_db->query($sqlDelete, DbAdapter::QUERY_MODE_EXECUTE); + + $sqlInsert = 'INSERT INTO users (username, password, real_name) ' + . 'VALUES ("my_username", "my_password", "My Real Name")'; + $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); + } + + protected function _setupAuthAdapter() + { + $this->_adapter = new Adapter\DbTable\CredentialTreatmentAdapter($this->_db, 'users', 'username', 'password'); + } + +} diff --git a/tests/ZendTest/Authentication/Adapter/DbTableTest.php b/tests/ZendTest/Authentication/Adapter/DbTableTest.php index 3734923e173..1e0ec3de7d1 100644 --- a/tests/ZendTest/Authentication/Adapter/DbTableTest.php +++ b/tests/ZendTest/Authentication/Adapter/DbTableTest.php @@ -10,10 +10,7 @@ namespace ZendTest\Authentication\Adapter; -use Zend\Authentication; use Zend\Authentication\Adapter; -use Zend\Db\Adapter\Adapter as DbAdapter; -use Zend\Db\Sql\Select as DBSelect; /** * @category Zend @@ -22,372 +19,8 @@ * @group Zend_Auth * @group Zend_Db_Table */ -class DbTableTest extends \PHPUnit_Framework_TestCase +class DbTableTest extends DbTable\CredentialTreatmentAdapterTest { - /** - * SQLite database connection - * - * @var \Zend\Db\Adapter\Adapter - */ - protected $_db = null; - - /** - * Database table authentication adapter - * - * @var \Zend\Authentication\Adapter\DbTable - */ - protected $_adapter = null; - - /** - * Set up test configuration - */ - public function setUp() - { - if (!defined('TESTS_ZEND_AUTH_ADAPTER_DBTABLE_PDO_SQLITE_ENABLED') || - constant('TESTS_ZEND_AUTH_ADAPTER_DBTABLE_PDO_SQLITE_ENABLED') === false - ) { - $this->markTestSkipped('Tests are not enabled in TestConfiguration.php'); - return; - } elseif (!extension_loaded('pdo')) { - $this->markTestSkipped('PDO extension is not loaded'); - return; - } elseif (!in_array('sqlite', \PDO::getAvailableDrivers())) { - $this->markTestSkipped('SQLite PDO driver is not available'); - return; - } - - $this->_setupDbAdapter(); - $this->_setupAuthAdapter(); - } - - public function tearDown() - { - $this->_adapter = null; - if ($this->_db instanceof DbAdapter) { - $this->_db->query('DROP TABLE [users]'); - } - $this->_db = null; - } - - /** - * Ensures expected behavior for authentication success - */ - public function testAuthenticateSuccess() - { - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - $result = $this->_adapter->authenticate(); - $this->assertTrue($result->isValid()); - } - - /** - * Ensures expected behavior for authentication success - */ - public function testAuthenticateSuccessWithTreatment() - { - $this->_adapter = new Adapter\DbTable($this->_db, 'users', 'username', 'password', '?'); - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - $result = $this->_adapter->authenticate(); - $this->assertTrue($result->isValid()); - } - - - /** - * Ensures expected behavior for authentication success - */ - public function testAuthenticateSuccessWithCallback() - { - $this->_adapter = new Adapter\DbTable($this->_db, 'users', 'username', 'password', null, function($a, $b){return $a === $b;}); - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - $result = $this->_adapter->authenticate(); - $this->assertTrue($result->isValid()); - } - - - /** - * Ensures expected behavior for an invalid callback - */ - public function testAuthenticateCallbackThrowsException() - { - $this->setExpectedException( - 'Zend\Authentication\Adapter\Exception\InvalidArgumentException', - 'Invalid callback provided' - ); - $this->_adapter->setCredentialValidationCallback('This is not a valid callback'); - } - - /** - * Ensures expected behavior for for authentication failure - * reason: Identity not found. - */ - public function testAuthenticateFailureIdentityNotFound() - { - $this->_adapter->setIdentity('non_existent_username'); - $this->_adapter->setCredential('my_password'); - - $result = $this->_adapter->authenticate(); - $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_NOT_FOUND, $result->getCode()); - } - - /** - * Ensures expected behavior for for authentication failure - * reason: Identity not found. - */ - public function testAuthenticateFailureIdentityAmbiguous() - { - $sqlInsert = 'INSERT INTO users (username, password, real_name) VALUES ("my_username", "my_password", "My Real Name")'; - $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); - - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - - $result = $this->_adapter->authenticate(); - $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_AMBIGUOUS, $result->getCode()); - } - - /** - * Ensures expected behavior for authentication failure because of a bad password - */ - public function testAuthenticateFailureInvalidCredential() - { - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password_bad'); - $result = $this->_adapter->authenticate(); - $this->assertFalse($result->isValid()); - } - - /** - * Ensures that getResultRowObject() works for successful authentication - */ - public function testGetResultRow() - { - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - $this->_adapter->authenticate(); - $resultRow = $this->_adapter->getResultRowObject(); - $this->assertEquals($resultRow->username, 'my_username'); - } - - /** - * Ensure that ResultRowObject returns only what told to be included - */ - public function testGetSpecificResultRow() - { - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - $this->_adapter->authenticate(); - $resultRow = $this->_adapter->getResultRowObject(array('username', 'real_name')); - $this->assertEquals('O:8:"stdClass":2:{s:8:"username";s:11:"my_username";s:9:"real_name";s:12:"My Real Name";}', - serialize($resultRow)); - } - - /** - * Ensure that ResultRowObject returns an object has specific omissions - */ - public function testGetOmittedResultRow() - { - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - $this->_adapter->authenticate(); - $resultRow = $this->_adapter->getResultRowObject(null, 'password'); - $this->assertEquals('O:8:"stdClass":3:{s:2:"id";s:1:"1";s:8:"username";s:11:"my_username";s:9:"real_name";s:12:"My Real Name";}', - serialize($resultRow)); - } - - /** - * @group ZF-5957 - */ - public function testAdapterCanReturnDbSelectObject() - { - $this->assertTrue($this->_adapter->getDbSelect() instanceof DBSelect); - } - - /** - * @group ZF-5957 - */ - public function testAdapterCanUseModifiedDbSelectObject() - { - $select = $this->_adapter->getDbSelect(); - $select->where('1 = 0'); - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - - $result = $this->_adapter->authenticate(); - $this->assertEquals(Authentication\Result::FAILURE_IDENTITY_NOT_FOUND, $result->getCode()); - } - - /** - * @group ZF-5957 - */ - public function testAdapterReturnsASelectObjectWithoutAuthTimeModificationsAfterAuth() - { - $select = $this->_adapter->getDbSelect(); - $select->where('1 = 1'); - $this->_adapter->setIdentity('my_username'); - $this->_adapter->setCredential('my_password'); - $this->_adapter->authenticate(); - $selectAfterAuth = $this->_adapter->getDbSelect(); - $whereParts = $selectAfterAuth->where->getPredicates(); - $this->assertEquals(1, count($whereParts)); - - $lastWherePart = array_pop($whereParts); - $expressionData = $lastWherePart[1]->getExpressionData(); - $this->assertEquals('1 = 1', $expressionData[0][0]); - } - - /** - * Ensure that exceptions are caught - */ - public function testCatchExceptionNoTable() - { - $this->setExpectedException('Zend\Authentication\Adapter\Exception\RuntimeException', - 'A table must be supplied for'); - $adapter = new Adapter\DbTable($this->_db); - $adapter->authenticate(); - } - - /** - * Ensure that exceptions are caught - */ - public function testCatchExceptionNoIdentityColumn() - { - $this->setExpectedException('Zend\Authentication\Adapter\Exception\RuntimeException', - 'An identity column must be supplied for the'); - $adapter = new Adapter\DbTable($this->_db, 'users'); - $adapter->authenticate(); - } - - /** - * Ensure that exceptions are caught - */ - public function testCatchExceptionNoCredentialColumn() - { - $this->setExpectedException('Zend\Authentication\Adapter\Exception\RuntimeException', - 'A credential column must be supplied'); - $adapter = new Adapter\DbTable($this->_db, 'users', 'username'); - $adapter->authenticate(); - } - - /** - * Ensure that exceptions are caught - */ - public function testCatchExceptionNoIdentity() - { - $this->setExpectedException('Zend\Authentication\Adapter\Exception\RuntimeException', - 'A value for the identity was not provided prior'); - $this->_adapter->authenticate(); - } - - /** - * Ensure that exceptions are caught - */ - public function testCatchExceptionNoCredential() - { - $this->setExpectedException('Zend\Authentication\Adapter\Exception\RuntimeException', - 'A credential value was not provided prior'); - $this->_adapter->setIdentity('my_username'); - $this->_adapter->authenticate(); - } - - /** - * Ensure that exceptions are caught - */ - public function testCatchExceptionBadSql() - { - $this->setExpectedException('Zend\Authentication\Adapter\Exception\RuntimeException', - 'The supplied parameters to'); - $this->_adapter->setTableName('bad_table_name'); - $this->_adapter->setIdentity('value'); - $this->_adapter->setCredential('value'); - $this->_adapter->authenticate(); - } - - /** - * Test to see same usernames with different passwords can not authenticate - * when flag is not set. This is the current state of - * Zend_Auth_Adapter_DbTable (up to ZF 1.10.6) - * - * @group ZF-7289 - */ - public function testEqualUsernamesDifferentPasswordShouldNotAuthenticateWhenFlagIsNotSet() - { - $sqlInsert = 'INSERT INTO users (username, password, real_name) ' - . 'VALUES ("my_username", "my_otherpass", "Test user 2")'; - $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); - - // test if user 1 can authenticate - $this->_adapter->setIdentity('my_username') - ->setCredential('my_password'); - $result = $this->_adapter->authenticate(); - $this->assertTrue(in_array('More than one record matches the supplied identity.', - $result->getMessages())); - $this->assertFalse($result->isValid()); - } - - /** - * Test to see same usernames with different passwords can authenticate when - * a flag is set - * - * @group ZF-7289 - */ - public function testEqualUsernamesDifferentPasswordShouldAuthenticateWhenFlagIsSet() - { - $sqlInsert = 'INSERT INTO users (username, password, real_name) ' - . 'VALUES ("my_username", "my_otherpass", "Test user 2")'; - $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); - - // test if user 1 can authenticate - $this->_adapter->setIdentity('my_username') - ->setCredential('my_password') - ->setAmbiguityIdentity(true); - $result = $this->_adapter->authenticate(); - $this->assertFalse(in_array('More than one record matches the supplied identity.', - $result->getMessages())); - $this->assertTrue($result->isValid()); - $this->assertEquals('my_username', $result->getIdentity()); - - $this->_adapter = null; - $this->_setupAuthAdapter(); - - // test if user 2 can authenticate - $this->_adapter->setIdentity('my_username') - ->setCredential('my_otherpass') - ->setAmbiguityIdentity(true); - $result2 = $this->_adapter->authenticate(); - $this->assertFalse(in_array('More than one record matches the supplied identity.', - $result->getMessages())); - $this->assertTrue($result2->isValid()); - $this->assertEquals('my_username', $result2->getIdentity()); - } - - - protected function _setupDbAdapter($optionalParams = array()) - { - $params = array('driver' => 'pdo_sqlite', - 'dbname' => TESTS_ZEND_AUTH_ADAPTER_DBTABLE_PDO_SQLITE_DATABASE); - - if (!empty($optionalParams)) { - $params['options'] = $optionalParams; - } - - $this->_db = new DbAdapter($params); - - $sqlCreate = 'CREATE TABLE IF NOT EXISTS [users] ( ' - . '[id] INTEGER NOT NULL PRIMARY KEY, ' - . '[username] VARCHAR(50) NOT NULL, ' - . '[password] VARCHAR(32) NULL, ' - . '[real_name] VARCHAR(150) NULL)'; - $this->_db->query($sqlCreate, DbAdapter::QUERY_MODE_EXECUTE); - - $sqlDelete = 'DELETE FROM users'; - $this->_db->query($sqlDelete, DbAdapter::QUERY_MODE_EXECUTE); - - $sqlInsert = 'INSERT INTO users (username, password, real_name) ' - . 'VALUES ("my_username", "my_password", "My Real Name")'; - $this->_db->query($sqlInsert, DbAdapter::QUERY_MODE_EXECUTE); - } protected function _setupAuthAdapter() {