diff --git a/security/OpenIDAuthenticator.php b/security/OpenIDAuthenticator.php index 336b3efe0..000405d52 100644 --- a/security/OpenIDAuthenticator.php +++ b/security/OpenIDAuthenticator.php @@ -40,8 +40,12 @@ class OpenIDAuthenticator extends Authenticator { * @param Form $form Optional: If passed, better error messages can be * produced by using * {@link Form::sessionMessage()} - * @return bool|Member Returns FALSE if authentication fails, otherwise - * the member object + * @return bool Returns FALSE if authentication fails, otherwise the + * method will not return at all because the browser will be + * redirected to some other server. + * + * @todo Check if we can send the POST request for OpenID 2 directly + * (without rendering a form and using javascript) */ public function authenticate(array $RAW_data, Form $form = null) { $openid = $RAW_data['OpenIDURL']; @@ -49,26 +53,12 @@ class OpenIDAuthenticator extends Authenticator { $trust_root = Director::absoluteBaseURL(); $return_to_url = $trust_root . 'OpenIDAuthenticator_Controller'; - /** - * @todo Change the store to use the database! - */ - // FIXXXME - $store_path = TEMP_FOLDER; - - if(!file_exists($store_path) && !mkdir($store_path)) { - print "Could not create the FileStore directory '$store_path'. ". - " Please check the effective permissions."; - exit(0); - } - $store = new Auth_OpenID_FileStore($store_path); - // END FIXXXME - $consumer = new Auth_OpenID_Consumer($store, new SessionWrapper()); + $consumer = new Auth_OpenID_Consumer(new OpenIDStorage(), + new SessionWrapper()); - // Begin the OpenID authentication process. + // No auth request means we can't begin OpenID $auth_request = $consumer->begin($openid); - - // No auth request means we can't begin OpenID. if(!$auth_request) { if(!is_null($form)) { $form->sessionMessage("That doesn't seem to be a valid OpenID " . @@ -91,32 +81,23 @@ class OpenIDAuthenticator extends Authenticator { } - - /** - * @todo Check if the POST request should be send directly (without rendering a form) - */ - // For OpenID 1, send a redirect. For OpenID 2, use a Javascript - // form to send a POST request to the server. if($auth_request->shouldSendRedirect()) { + // For OpenID 1, send a redirect. $redirect_url = $auth_request->redirectURL($trust_root, $return_to_url); - // If the redirect URL can't be built, display an error - // message. if(Auth_OpenID::isFailure($redirect_url)) { displayError("Could not redirect to server: " . $redirect_url->message); } else { Director::redirect($redirect_url); -// header("Location: ".$redirect_url); - } + } else { - // Generate form markup and render it. + // For OpenID 2, use a javascript form to send a POST request to the + // server. $form_id = 'openid_message'; $form_html = $auth_request->formMarkup($trust_root, $return_to_url, false, array('id' => $form_id)); - // Display an error if the form markup couldn't be generated; - // otherwise, render the HTML. if(Auth_OpenID::isFailure($form_html)) { displayError("Could not redirect to server: " . $form_html->message); } else { @@ -126,12 +107,15 @@ class OpenIDAuthenticator extends Authenticator { "", "", $form_html, - "

Click "Continue" to login. You are only seeing this because you appear to have JavaScript disabled.

", + "

Click "Continue" to login. You are only seeing " . + "this because you appear to have JavaScript disabled.

", ""); print implode("\n", $page_contents); } } + + // Stop the script execution! This method should return only on error exit(); } @@ -173,6 +157,8 @@ class OpenIDAuthenticator_Controller extends Controller { /** * Run the controller + * + * @param array $requestParams Passed request parameters */ function run($requestParams) { parent::init(); @@ -181,23 +167,8 @@ class OpenIDAuthenticator_Controller extends Controller { Profiler::mark("OpenIDAuthenticator_Controller"); - /** - * This is where the example will store its OpenID information. - * You should change this path if you want the example store to be - * created elsewhere. After you're done playing with the example - * script, you'll have to remove this directory manually. - */ - $store_path = TEMP_FOLDER; - - if(!file_exists($store_path) && !mkdir($store_path)) { - print "Could not create the FileStore directory '$store_path'. ". - " Please check the effective permissions."; - exit(0); - } - $store = new Auth_OpenID_FileStore($store_path); - - $consumer = new Auth_OpenID_Consumer($store, new SessionWrapper()); - + $consumer = new Auth_OpenID_Consumer(new OpenIDStorage(), + new SessionWrapper()); // Complete the authentication process using the server's response. @@ -277,8 +248,10 @@ class OpenIDAuthenticator_Controller extends Controller { * @param string $type Message type (e.g. "good" or "bad") */ function sessionMessage($message, $type) { - Session::set("FormInfo.OpenIDLoginForm_LoginForm.formError.message", $message); - Session::set("'FormInfo.OpenIDLoginForm_LoginForm.formError.type", $type); + Session::set("FormInfo.OpenIDLoginForm_LoginForm.formError.message", + $message); + Session::set("'FormInfo.OpenIDLoginForm_LoginForm.formError.type", + $type); } } diff --git a/security/OpenIDStorage.php b/security/OpenIDStorage.php new file mode 100644 index 000000000..18632f121 --- /dev/null +++ b/security/OpenIDStorage.php @@ -0,0 +1,400 @@ + + */ + + +/** + * Require the {@link Auth_OpenID_MySQLStore MySQL storage class} + */ +require_once "Auth/OpenID/MySQLStore.php"; + + +/** + * Require the + * {@link Auth_OpenID_DatabaseConnection database connection class} + */ +require_once "Auth/OpenID/DatabaseConnection.php"; + + + +/** + * OpenID storage class + * + * This is the interface for the store objects the OpenID library uses. + * It is a single class that provides all of the persistence mechanisms that + * the OpenID library needs, for both servers and consumers. + * + * @author Markus Lanthaler + */ +class OpenIDStorage extends Auth_OpenID_MySQLStore { + + /** + * This static variable is used to decrease the number of table existence + * checks + * + * @todo Create the tables during installation, so we can reduce the + * number of needed SQL queries to a minimum and we don't need this + * variable anymore + */ + private static $S_checkedTableExistence = false; + + + /** + * Constructor + * + * The constructor will check (once per script execution; with help of a + * static variable) if the needed tables exist, otherwise it will create + * them. + * + * @param string $associations_table This is an optional parameter to + * specify the name of the table used + * for storing associations. + * The default value is + * 'authentication_openid_associations'. + * @param string $nonces_table This is an optional parameter to specify + * the name of the table used for storing + * nonces. + * The default value is + * 'authentication_openid_nonces'. + * + * + * @todo Should the max. nonce age be configurable? + * @todo Create the tables during installation, so we can reduce the + * number of needed SQL queries. + */ + function __construct($associations_table = null, $nonces_table = null) + { + if(is_null($associations_table)) + $associations_table = 'authentication_openid_associations'; + + if(is_null($nonces_table)) + $nonces_table = 'authentication_openid_nonces'; + + $connection = new OpenIDDatabaseConnection(); + + parent::__construct($connection, $associations_table, $nonces_table); + + + if(self::$S_checkedTableExistence == false) { + $table_list = (!isset(DB::getConn()->tableList)) + ? $table_list = DB::tableList() + : DB::getConn()->tableList; + + $this->connection->autoCommit(true); + + if(!isset($table_list[strtolower($this->associations_table_name)])) + $this->create_assoc_table(); + + if(!isset($table_list[strtolower($this->nonces_table_name)])) + $this->create_nonce_table(); + + $this->connection->autoCommit(false); + DB::tableList(); + + self::$S_checkedTableExistence = true; + } + } + + + /** + * This method is called by the constructor to set values in $this->sql, + * which is an array keyed on sql name. + * + * @access private + */ + function setSQL() + { + parent::setSQL(); + + $this->sql['nonce_table'] = + "CREATE TABLE %s (\n". + " server_url VARCHAR(2047),\n". + " timestamp INTEGER,\n". + " salt CHAR(40),\n". + " UNIQUE (server_url(255), timestamp, salt)\n". + ")"; + + $this->sql['assoc_table'] = + "CREATE TABLE %s (\n". + " server_url BLOB,\n". + " handle VARCHAR(255),\n". + " secret BLOB,\n". + " issued INTEGER,\n". + " lifetime INTEGER,\n". + " assoc_type VARCHAR(64),\n". + " PRIMARY KEY (server_url(255), handle)\n". + ")"; + } + + + /** + * Constitutes the passed value a database error? + * + * @return Returns TRUE if $value constitutes a database error; returns + * FALSE otherwise. + * @access private + */ + function isError($value) + { + return ($value === false); + } + + + /** + * Create the nonce table + * + * @return bool Returns TRUE on success, FALSE on failure. + */ + function create_nonce_table() + { + return $this->resultToBool( + $this->connection->query($this->sql['nonce_table'])); + } + + + /** + * Create the associations table + * + * @return bool Returns TRUE on success, FALSE on failure. + */ + function create_assoc_table() + { + return $this->resultToBool( + $this->connection->query($this->sql['assoc_table'])); + } +} + + + +/** + * Wrapper that emulates PEAR connection functionality which is needed for + * the {@link OpenIDStorage} class. + * + * @author Markus Lanthaler + * + * @todo If the new database abstraction adds support for transactions and + * prepared statements (placeholders) use that code without emulating + * it here. + */ +class OpenIDDatabaseConnection extends Auth_OpenID_DatabaseConnection { + + /** + * Run an SQL query with the specified parameters, if any. + * + * @param string $sql An SQL string with placeholders. A '?' is used for + * values to put into quotes, and a '!' for values that + * should not be quoted. + * @param array $params An array of parameters to insert into the SQL + * string using this connection's escaping mechanism. + * @return mixed $result The result of calling this connection's internal + * query function. + * The type of result depends on the underlying + * database engine. This method is usually used when + * the result of a query is not important, like a + * DDL query. + */ + public function query($sql, $params = array()) + { + if(($sql = $this->generateQuery($sql, $params)) === false) + return false; + + return DB::query($sql); + } + + + /** + * Run an SQL query and return the first column of the first row + * of the result set, if any. + * + * @param string $sql An SQL string with placeholders. A '?' is used for + * values to put into quotes, and a '!' for values that + * should not be quoted. + * @param array $params An array of parameters to insert into the SQL + * string using this connection's escaping mechanism. + * @return mixed $result The value of the first column of the first row of + * the result set. + * FALSE if no such result was found. + */ + public function getOne($sql, $params = array()) + { + if(($sql = $this->generateQuery($sql, $params)) === false) + return false; + + if(($result = DB::query($sql)) === false) + return false; + + return $result->value(); + } + + + /** + * Run an SQL query and return the first row of the result set, if + * any. + * + * @param string $sql An SQL string with placeholders. A '?' is used for + * values to put into quotes, and a '!' for values that + * should not be quoted. + * @param array $params An array of parameters to insert into the SQL + * string using this connection's escaping mechanism. + * @return array $result The first row of the result set, if any, keyed on + * column name. + * FALSE if no such result was found. + */ + public function getRow($sql, $params = array()) + { + if(($sql = $this->generateQuery($sql, $params)) === false) + return false; + + if(($result = DB::query($sql)) === false) + return false; + + return $result->record(); + } + + + /** + * Run an SQL query with the specified parameters, if any. + * + * @param string $sql An SQL string with placeholders. A '?' is used for + * values to put into quotes, and a '!' for values that + * should not be quoted. + * @param array $params An array of parameters to insert into the SQL + * string using this connection's escaping mechanism. + * @return array $result An array of arrays representing the result of the + * query; each array is keyed on column name. + */ + public function getAll($sql, $params = array()) + { + if(($sql = $this->generateQuery($sql, $params)) === false) + return false; + + if(($result = DB::query($sql)) === false) + return false; + + for($result_array = array(); $result->valid(); $result->next()) { + array_push($result_array, $result->current()); + } + + return $result_array; + } + + + /** + * Sets auto-commit mode on this database connection. + * + * @param bool $mode TRUE if auto-commit is to be used; FALSE if not. + */ + public function autoCommit($mode) + { + } + + + /** + * Starts a transaction on this connection, if supported. + */ + public function begin() + { + } + + + /** + * Commits a transaction on this connection, if supported. + */ + public function commit() + { + } + + + /** + * Performs a rollback on this connection, if supported. + */ + public function rollback() + { + } + + + /** + * Formats input so it can be safely used in a query + * + * @param string $sql An SQL string with placeholders. A '?' is used for + * values to put into quotes, and a '!' for values that + * should not be quoted. + * @param array $params An array of parameters to insert into the SQL + * string using this connection's escaping mechanism. + * @return bool|string $result A valid SQL string with all parameters + * properly escaped or FALSE if an invalid SQL + * string or an invalid number of parameters + * was passed. + */ + private function generateQuery($sql, $params = array()) + { + $tokens = preg_split('/((?quote($value); + } else { + $realquery .= $value; + } + + $realquery .= $newtokens[++$i]; + } + + return $realquery; + } + + + /** + * Formats input so it can be safely used in a query + * + * @param mixed $in The data to be formatted + * @return mixed The formatted data. The format depends on the input's + * PHP type- + */ + private function quote($in) + { + if(is_int($in)) { + return $in; + } elseif(is_float($in)) { + return "'" . Convert::raw2sql( + str_replace(',', '.', strval(floatval($in)))) . "'"; + } elseif(is_bool($in)) { + return ($in) ? '1' : '0'; + } elseif(is_null($in)) { + return 'NULL'; + } else { + return "'" . Convert::raw2sql($in) . "'"; + } + } +} + + +?> \ No newline at end of file