Source for file PAPIAuthnEngine.php
Documentation is available at PAPIAuthnEngine.php
* @copyright Copyright 2005-2010 RedIRIS, http://www.rediris.es/
* This file is part of phpPoA2.
* phpPoA2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* phpPoA2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with phpPoA2. If not, see <http://www.gnu.org/licenses/>.
* @license http://www.gnu.org/licenses/gpl.html GNU General Public License
* @author Jaime Perez <jaime.perez@rediris.es>
* Default assertion delimiters for standard PAPI 1.5 protocol.
define("ATTR_SEPARATOR", ",");
define("VALUE_SEPARATOR", "|");
define("NAMEVALUE_SEPARATOR", "=");
* Supported database types.
define('PAPI_DBA', 'PAPIDBADB');
define('PAPI_MYSQL', 'PAPIMySQLDB');
define('PAPI_SESSION', 'PAPISessionDB');
* Supported namespaces for attributes.
define('NS_PAPI_PROTOCOL', 'urn:mace:rediris.es:papi:protocol');
define('NS_PAPI_ATTRIBUTES', 'urn:mace:rediris.es:papi:attributes');
* Prefix for operational attributes inside the protocol and special
define('PROTO_ATTR_PREFIX', '_papi_');
define('PROTO_ATTR_AS_ID', '__asid');
define('PROTO_ATTR_KEY', '__key');
define('PROTO_ATTR_EXPIRE_TIME', '__expiretime');
* Default timeout for stored requests.
define('REQUEST_LIFETIME', 300); // 5 minutes
* This hook is executed at the end of the method that returns the URL where to redirect a user.
* It can be used to alter parameters in the URL. The hook receives an array of parameters which
* should be directly modified. Functions for this hook must be defined like this:
* function redirectURLFinishHook(&$params);
* Please bear in mind that hooks must return TRUE or they'll keep other hooks from executing.
define("PAPI_REDIRECT_URL_FINISH", "PAPI_REDIRECT_URL_FINISH");
* This hook is executed when a valid response is found from the AS/GPoA and the original request
* of the user is about to be restored. It receives an array with the main PHP global variables
* of the original context. Functions for this hook must be defined like this:
* function restoreOriginalRequestHook(&$env);
* Please bear in mind that hooks must return TRUE or they'll keep other hooks from executing.
define("PAPI_RESTORE_ORIGINAL_REQUEST", "PAPI_RESTORE_ORIGINAL_REQUEST");
* This hook is executed when a valid response is found from the AS/GPoA and the engine is about
* to end the authentication result. It receives a boolean value that determines if the URL should
* be cleaned by means of a redirection to the initial URL. Functions for this hook must be
* function cleanURLHook(&$clean);
* Please bear in mind that hooks must return TRUE or they'll keep other hooks from executing.
define("PAPI_CLEAN_URL", "PAPI_CLEAN_URL");
* This hook is executed when returning the attributes found for a user with getAttributes()
* method. It receives a string with the attributes and the array that results of proccessing
* the string. Functions for this hook must be defined like this:
* function attributeParser($assertion, &$attributes);
* Please bear in mind that hooks must return TRUE or they'll keep other hooks from executing.
define("PAPI_ATTRIBUTE_PARSER", "PAPI_ATTRIBUTE_PARSER");
* Authentication engine for the PAPI 1.5 protocol.
* PLEASE NOTE THAT THIS ENGINE WORKS ONLY FOR WEB-BASED APPLICATIONS.
* @subpackage PAPIAuthenticationEngine
protected $opoa = "http";
PAPI_RESTORE_ORIGINAL_REQUEST,
parent::configure($file, $section);
// check mcrypt extension
// initialize cryptographic engine
if (isset ($_SERVER['HTTPS'])) $this->opoa .= "s";
$this->opoa .= "://". $_SERVER['SERVER_NAME']. $this->cfg->getLocation();
$db_t = $this->cfg->getDBType();
$this->db = new $db_t($this->cfg);
// PAPI authentication protocol v1.0
// check if we have a cookie or coming back from AS/GPoA
if ($action === "CHECKED" && !$auth) { // GPoA/AS response
$data = $_REQUEST['DATA'];
if ($key) { // the request was validated
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// build HTML with an input element for each element in the query string
parse_str($_SERVER["QUERY_STRING"], $params);
foreach ($params as $name => $value) {
$inputs .= "<input name='". $name. "' type='hidden' value='". $value. "' />";
$inputs .= "<input type='submit' value='". PoAUtils::msg('continue', array()). "' />";
// print a HTML form to continue
<title>phpPoA2 transaction in progress...</title>
<body onload="document.forms[0].submit();">
<form action=" <?php echo $_SERVER['SCRIPT_NAME'];?>" method="post">
$protocol = (!empty($_SERVER['HTTPS'])) ? "https://" : "http://";
$url = $protocol. $_SERVER['SERVER_NAME']. ":". $_SERVER['SERVER_PORT'];
$url .= substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], "ACTION=CHECKED") - 1);
} else { // can't set the cookie
} else { // the request is invalid!
} else if (!$auth) { // first time browser access (w/o cookie)
} else { // valid access with cookie, update it!
// if no request found, assume this is a visit to a previously stored URL
// (reloaded by the user or in the browser favorites/history).
// Rebuild the request without the ACTION and DATA parameters
$protocol = (!empty($_SERVER['HTTPS'])) ? "https://" : "http://";
$re[] = "/ACTION=[^&]*&?/";
$re[] = "/DATA=[^&]*&?/";
$location = $protocol. $_SERVER["SERVER_NAME"]. ":". $_SERVER["SERVER_PORT"].
} else { // set cookie failed!
// avoid parsing the assertion again and again
foreach ($attrs as $attr) {
// discard operational attributes
if (count($values) > 1) {
public function getAttribute($name, $namespace = NS_PAPI_ATTRIBUTES) {
public function logout($slo = false) {
// first check if we really need to logout!
$rtype = $this->cfg->getRedirectType();
// configuration error, redirection type must be GPOA_T and the
throw new PoAException('slo-conf-error', E_USER_ERROR, array());
if ($action === "PAPILOGOUT") { // single logout
} else { // logout is triggered from the application
// attribute query protocol
//TODO: define attr query protocol and implement!
* Check if a cookie is valid.
* @param cookie The cookie.
* @return boolean true if the cookie is valid, false otherwise.
$cookie = $_COOKIE[$name];
// extract the contents from the cookie
$newsource = $this->crypto->decryptAES($cookie);
if ($location != $this->cfg->getLocation()) {
* Delete the current cookie, if any.
* This is a hack to make cookie deletion work with firefox browers.
* Firefox won't delete a cookie unless it is set with exactly the same
* way it was originally set, except the expiration time. So we have to
* set the contents, location and domain, and then set an expiration date
$this->cfg->getLocation(),
$this->cfg->getCookieDomain(), 0);
* Check the response from the AS/GPoA.
* @param data The data received.
* @param key The public key of the AS/GPoA.
* @return boolean true if valid, false else.
$newsource = $this->crypto->decrypt($data);
if ($newsource === false) {
if (empty($newsource)) { // empty assertion
$response = explode(":", $newsource);
if ($this->assertion === "ERROR") { // AS/GPoA error response, authentication failed!
if ($current_time + $this->cfg->getCookieTimeout() < time()) { // expired
* Redirect user browser to the appropriate URL for authentication.
* WARNING: This method ends execution.
* @param location If set, the location where to redirect the user. If not, defaults are used.
* @return void This method does not return!
protected function redirect($location = "") {
throw new PoAException('cannot-redirect', E_USER_ERROR, array());
* Retrieve the URL where to redirect a user to perform a single logout.
* @return string The appropriate URL where to redirect the browser, false if error.
$protocol = (!empty($_SERVER['HTTPS'])) ? "https://" : "http://";
$url = $protocol. $_SERVER['SERVER_NAME']. ":". $_SERVER['SERVER_PORT']. $_SERVER['REQUEST_URI'];
$c_url = $this->cfg->getLogoutURL();
$params = array('ACTION' => 'PAPISIGNOFFREQ',
'POA' => $this->cfg->getID(), // TODO
$sep = (strstr("?", $this->cfg->getRedirectURL())) ? "&" : "?";
* Retrieve the URL where to redirect a user once he has successfully logged out.
* @return string The appropriate URL where to redirect the browser, false if error.
$protocol = (!empty($_SERVER['HTTPS'])) ? "https://" : "http://";
$params = array('ACTION' => 'PAPILOGGEDOUT',
'POA' => $this->cfg->getID()); // TODO
$sep = (strstr("?", $this->cfg->getRedirectURL())) ? "&" : "?";
* Retrieve the URL where to redirect a user and store his request.
* @return string The appropriate URL where to redirect the browser, false if error.
// initialize key identifier
$protocol = (!empty($_SERVER['HTTPS'])) ? "https://" : "http://";
$url = $protocol. $_SERVER['SERVER_NAME']. ":". $_SERVER['SERVER_PORT']. $_SERVER['REQUEST_URI'];
// check if the request hast to be sent to a GPoA or an AS
if ($this->cfg->getRedirectType() === AS_T) { // AS
$params = array('ATTREQ' => 'poaid',
$params = array('ACTION' => 'CHECK',
$hli = $this->cfg->getHomeLocatorID();
$params['PAPIHLI'] = $hli;
$params['POA'] = $this->opoa; // TODO
$params['PAPIOPOA'] = $this->opoa;
$id = $this->cfg->getID();
$params['POA'] = $id; // TODO
// check friendly name TODO
$fname = $this->cfg->getFriendlyName();
$params['POADISPLAYNAME'] = $fname;
$logout = $this->cfg->getLogoutURL();
$hli = @$params['PAPIHLI'];
// save the current request
$sep = (strstr("?", $this->cfg->getRedirectURL())) ? "&" : "?";
* Save a request to the request database. The request includes: $_REQUEST, $_GET, $_POST,
* $_SERVER['QUERY_STRING'], $_SERVER['REQUEST_METHOD'] and php://input.
* @param key The key identifier for this request.
* @param hli The home locator identifier that should be used for this request.
* @return string|booleanThe key to retrieve later this request from the database, false if error.
// perform db maintenance
$purged = $this->db->purge($this->cfg->getRequestLifetime());
// create/replace entry for random key
// marcoscm: Some clients don't rely *only* on $_REQUEST (2), but on $_GET (0), $_POST (1),
// $_SERVER["QUERY_STRING"] (3), $_SERVER["REQUEST_METHOD"] (4) or and php://input (5)
$ok = $this->db->replaceContents($key, $_GET, $_POST, $_REQUEST, $_SERVER["QUERY_STRING"],
return (!$ok) ? false : $key;
* Load a request from the request database.
* @param key The key that identifies the request.
* @return hash The request associated with that key, false if error.
global $HTTP_RAW_POST_DATA;
$request = $this->db->fetch($key);
// check if HLI matches with AS ID
if (!empty($request['HLI']) && $this->as_id != $request['HLI']) {
// reload original context
$_POST = $request["POST"];
$_REQUEST = $request["REQUEST"];
$_SERVER["QUERY_STRING"] = $request["QUERY_STRING"];
$_SERVER["REQUEST_METHOD"] = $request["REQUEST_METHOD"];
$HTTP_RAW_POST_DATA = $request["PHP_INPUT"];
* Delete a request from the request database.
* @param key The key that identifies the request.
* @return boolean true if success, false in any other case.
if ($this->db->check($key)) {
$request = $this->db->delete($key);
* Generate a new cookie for the current user.
* @return string The cookie conveniently encrypted with our own key.
$content = time(). ":". $expiration. ":". $this->cfg->getLocation(). ":". $this->id. ":". $this->as_id. ":". $this->assertion;
return $this->crypto->encryptAES($content);
* Determines if it's safe to assume the user as authenticated.
* @return boolean true if the user still has a valid session, false otherwise.
|