<?php
+/*
+ * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
+ * 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 the ESUP-Portail consortium & the JA-SIG
+ * Collaborative 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.
+ */
+
/**
* @file CAS/client.php
* Main class of the phpCAS library
return $this->_server['login_url'] = $url;
}
+
+ /**
+ * This method sets the serviceValidate URL of the CAS server.
+ * @param $url the serviceValidate URL
+ * @private
+ * @since 1.1.0 by Joachim Fritschi
+ */
+ function setServerServiceValidateURL($url)
+ {
+ return $this->_server['service_validate_url'] = $url;
+ }
+
+
+ /**
+ * This method sets the proxyValidate URL of the CAS server.
+ * @param $url the proxyValidate URL
+ * @private
+ * @since 1.1.0 by Joachim Fritschi
+ */
+ function setServerProxyValidateURL($url)
+ {
+ return $this->_server['proxy_validate_url'] = $url;
+ }
+
+
+ /**
+ * This method sets the samlValidate URL of the CAS server.
+ * @param $url the samlValidate URL
+ * @private
+ * @since 1.1.0 by Joachim Fritschi
+ */
+ function setServerSamlValidateURL($url)
+ {
+ return $this->_server['saml_validate_url'] = $url;
+ }
+
+
/**
* This method is used to retrieve the service validating URL of the CAS server.
* @return a URL.
// return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
return $this->_server['service_validate_url'].'?service='.urlencode($this->getURL());
}
-
+ /**
+ * This method is used to retrieve the SAML validating URL of the CAS server.
+ * @return a URL.
+ * @private
+ */
+ function getServerSamlValidateURL()
+ {
+ phpCAS::traceBegin();
+ // the URL is build only when needed
+ if ( empty($this->_server['saml_validate_url']) ) {
+ switch ($this->getServerVersion()) {
+ case SAML_VERSION_1_1:
+ $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
+ break;
+ }
+ }
+ phpCAS::traceEnd($this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL()));
+ return $this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL());
+ }
/**
* This method is used to retrieve the proxy validating URL of the CAS server.
* @return a URL.
{
return $this->_server['logout_url'] = $url;
}
-
+
/**
* An array to store extra curl options.
*/
var $_curl_options = array();
-
+
/**
* This method is used to set additional user curl options.
*/
function setExtraCurlOption($key, $value)
- {
+ {
$this->_curl_options[$key] = $value;
- }
-
+ }
+
/**
* This method checks to see if the request is secured via HTTPS
* @return true if https, false otherwise
phpCAS::traceBegin();
- if (!$this->isLogoutRequest() && !empty($_GET['ticket']) && $start_session) {
- // copy old session vars and destroy the current session
- if (!isset($_SESSION)) {
- session_start();
- }
- $old_session = $_SESSION;
- session_destroy();
- // set up a new session, of name based on the ticket
- $session_id = preg_replace('/[^\w]/','',$_GET['ticket']);
- phpCAS::LOG("Session ID: " . $session_id);
- session_id($session_id);
- if (!isset($_SESSION)) {
- session_start();
- }
- // restore old session vars
- $_SESSION = $old_session;
- // Redirect to location without ticket.
- header('Location: '.$this->getURL());
- }
-
- //activate session mechanism if desired
- if (!$this->isLogoutRequest() && $start_session) {
+ // the redirect header() call and DOM parsing code from domxml-php4-php5.php won't work in PHP4 compatibility mode
+ if (version_compare(PHP_VERSION,'5','>=') && ini_get('zend.ze1_compatibility_mode')) {
+ phpCAS::error('phpCAS cannot support zend.ze1_compatibility_mode. Sorry.');
+ }
+ $this->_start_session = $start_session;
+
+ if ($this->_start_session && session_id())
+ {
+ phpCAS :: error("Another session was started before phpcas. Either disable the session" .
+ " handling for phpcas in the client() call or modify your application to leave" .
+ " session handling to phpcas");
+ }
+ // skip Session Handling for logout requests and if don't want it'
+ if ($start_session && !$this->isLogoutRequest())
+ {
+ phpCAS :: trace("Starting a new session");
session_start();
}
+
+ // are we in proxy mode ?
$this->_proxy = $proxy;
//check version
break;
case CAS_VERSION_2_0:
break;
+ case SAML_VERSION_1_1:
+ break;
default:
phpCAS::error('this version of CAS (`'
.$server_version
}
$this->_server['version'] = $server_version;
- //check hostname
+ // check hostname
if ( empty($server_hostname)
|| !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
}
$this->_server['hostname'] = $server_hostname;
- //check port
+ // check port
if ( $server_port == 0
|| !is_int($server_port) ) {
phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
}
$this->_server['port'] = $server_port;
- //check URI
+ // check URI
if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
}
- //add leading and trailing `/' and remove doubles
+ // add leading and trailing `/' and remove doubles
$server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
$this->_server['uri'] = $server_uri;
- //set to callback mode if PgtIou and PgtId CGI GET parameters are provided
+ // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
if ( $this->isProxy() ) {
$this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
}
phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
}
break;
+ case SAML_VERSION_1_1: // SAML just does Service Tickets
+ if( preg_match('/^[SP]T-/',$ticket) ) {
+ phpCAS::trace('SA \''.$ticket.'\' found');
+ $this->setSA($ticket);
+ unset($_GET['ticket']);
+ } else if ( !empty($ticket) ) {
+ //ill-formed ticket, halt
+ phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
+ }
+ break;
}
}
phpCAS::traceEnd();
/** @} */
+ // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ // XX XX
+ // XX Session Handling XX
+ // XX XX
+ // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+
+ /**
+ * A variable to whether phpcas will use its own session handling. Default = true
+ * @hideinitializer
+ * @private
+ */
+ var $_start_session = true;
+
+ function setStartSession($session)
+ {
+ $this->_start_session = session;
+ }
+
+ function getStartSession($session)
+ {
+ $this->_start_session = session;
+ }
+
+ /**
+ * Renaming the session
+ */
+ function renameSession($ticket)
+ {
+ phpCAS::traceBegin();
+ if($this->_start_session){
+ if (!empty ($this->_user))
+ {
+ $old_session = $_SESSION;
+ session_destroy();
+ // set up a new session, of name based on the ticket
+ $session_id = preg_replace('/[^\w]/', '', $ticket);
+ phpCAS :: trace("Session ID: ".$session_id);
+ session_id($session_id);
+ session_start();
+ phpCAS :: trace("Restoring old session vars");
+ $_SESSION = $old_session;
+ } else
+ {
+ phpCAS :: error('Session should only be renamed after successfull authentication');
+ }
+ }else{
+ phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
+ }
+ phpCAS::traceEnd();
+ }
+
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// XX XX
// XX AUTHENTICATION XX
return $this->_user;
}
+
+
+ /***********************************************************************************************************************
+ * Atrributes section
+ *
+ * @author Matthias Crauwels <matthias.crauwels@ugent.be>, Ghent University, Belgium
+ *
+ ***********************************************************************************************************************/
+ /**
+ * The Authenticated users attributes. Written by CASClient::setAttributes(), read by CASClient::getAttributes().
+ * @attention client applications should use phpCAS::getAttributes().
+ *
+ * @hideinitializer
+ * @private
+ */
+ var $_attributes = array();
+
+ function setAttributes($attributes)
+ { $this->_attributes = $attributes; }
+
+ function getAttributes() {
+ if ( empty($this->_user) ) { // if no user is set, there shouldn't be any attributes also...
+ phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
+ }
+ return $this->_attributes;
+ }
+
+ function hasAttributes()
+ { return !empty($this->_attributes); }
+
+ function hasAttribute($key)
+ { return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); }
+
+ function getAttribute($key) {
+ if($this->hasAttribute($key)) {
+ return $this->_attributes[$key];
+ }
+ }
+
/**
* This method is called to renew the authentication of the user
* If the user is authenticated, renew the connection
}
phpCAS::traceEnd();
}
-
+
/**
* This method is called to be sure that the user is authenticated. When not
* authenticated, halt by redirecting to the CAS server; otherwise return TRUE.
* This method is called to check if the user is authenticated (previously or by
* tickets given in the URL).
*
- * @return TRUE when the user is authenticated.
+ * @return TRUE when the user is authenticated. Also may redirect to the same URL without the ticket.
*
* @public
*/
$validate_url = '';
if ( $this->wasPreviouslyAuthenticated() ) {
- // the user has already (previously during the session) been
- // authenticated, nothing to be done.
- phpCAS::trace('user was already authenticated, no need to look for tickets');
- $res = TRUE;
- }
- elseif ( $this->hasST() ) {
- // if a Service Ticket was given, validate it
- phpCAS::trace('ST `'.$this->getST().'\' is present');
- $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
- phpCAS::trace('ST `'.$this->getST().'\' was validated');
- if ( $this->isProxy() ) {
- $this->validatePGT($validate_url,$text_response,$tree_response); // idem
- phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
- $_SESSION['phpCAS']['pgt'] = $this->getPGT();
+ if($this->hasST() || $this->hasPT() || $this->hasSA()){
+ // User has a additional ticket but was already authenticated
+ phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
+ header('Location: '.$this->getURL());
+ phpCAS::log( "Prepare redirect to remove ticket: ".$this->getURL() );
+ }else{
+ // the user has already (previously during the session) been
+ // authenticated, nothing to be done.
+ phpCAS::trace('user was already authenticated, no need to look for tickets');
}
- $_SESSION['phpCAS']['user'] = $this->getUser();
$res = TRUE;
}
- elseif ( $this->hasPT() ) {
- // if a Proxy Ticket was given, validate it
- phpCAS::trace('PT `'.$this->getPT().'\' is present');
- $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
- phpCAS::trace('PT `'.$this->getPT().'\' was validated');
- if ( $this->isProxy() ) {
- $this->validatePGT($validate_url,$text_response,$tree_response); // idem
- phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
- $_SESSION['phpCAS']['pgt'] = $this->getPGT();
- }
- $_SESSION['phpCAS']['user'] = $this->getUser();
- $res = TRUE;
- }
else {
- // no ticket given, not authenticated
- phpCAS::trace('no ticket found');
+ if ( $this->hasST() ) {
+ // if a Service Ticket was given, validate it
+ phpCAS::trace('ST `'.$this->getST().'\' is present');
+ $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
+ phpCAS::trace('ST `'.$this->getST().'\' was validated');
+ if ( $this->isProxy() ) {
+ $this->validatePGT($validate_url,$text_response,$tree_response); // idem
+ phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
+ $_SESSION['phpCAS']['pgt'] = $this->getPGT();
+ }
+ $_SESSION['phpCAS']['user'] = $this->getUser();
+ $res = TRUE;
+ }
+ elseif ( $this->hasPT() ) {
+ // if a Proxy Ticket was given, validate it
+ phpCAS::trace('PT `'.$this->getPT().'\' is present');
+ $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
+ phpCAS::trace('PT `'.$this->getPT().'\' was validated');
+ if ( $this->isProxy() ) {
+ $this->validatePGT($validate_url,$text_response,$tree_response); // idem
+ phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
+ $_SESSION['phpCAS']['pgt'] = $this->getPGT();
+ }
+ $_SESSION['phpCAS']['user'] = $this->getUser();
+ $res = TRUE;
+ }
+ elseif ( $this->hasSA() ) {
+ // if we have a SAML ticket, validate it.
+ phpCAS::trace('SA `'.$this->getSA().'\' is present');
+ $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
+ phpCAS::trace('SA `'.$this->getSA().'\' was validated');
+ $_SESSION['phpCAS']['user'] = $this->getUser();
+ $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
+ $res = TRUE;
+ }
+ else {
+ // no ticket given, not authenticated
+ phpCAS::trace('no ticket found');
+ }
+ if ($res) {
+ // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
+ // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
+ header('Location: '.$this->getURL());
+ phpCAS::log( "Prepare redirect to : ".$this->getURL() );
+ }
}
phpCAS::traceEnd($res);
if ( $this->isSessionAuthenticated() ) {
// authentication already done
$this->setUser($_SESSION['phpCAS']['user']);
+ if(isset($_SESSION['phpCAS']['attributes'])){
+ $this->setAttributes($_SESSION['phpCAS']['attributes']);
+ }
phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
$auth = TRUE;
} else {
printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
$this->printHTMLFooter();
+
phpCAS::traceExit();
exit();
}
-
-// /**
-// * This method is used to logout from CAS.
-// * @param $url a URL that will be transmitted to the CAS server (to come back to when logged out)
-// * @public
-// */
-// function logout($url = "") {
-// phpCAS::traceBegin();
-// $cas_url = $this->getServerLogoutURL();
-// // v0.4.14 sebastien.gougeon at univ-rennes1.fr
-// // header('Location: '.$cas_url);
-// if ( $url != "" ) {
-// // Adam Moore 1.0.0RC2
-// $url = '?service=' . $url . '&url=' . $url;
-// }
-// header('Location: '.$cas_url . $url);
-// session_unset();
-// session_destroy();
-// $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
-// printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
-// $this->printHTMLFooter();
-// phpCAS::traceExit();
-// exit();
-// }
+
/**
* This method is used to logout from CAS.
$cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']);
}
header('Location: '.$cas_url);
+ phpCAS::log( "Prepare redirect to : ".$cas_url );
+
session_unset();
session_destroy();
+
$this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
$this->printHTMLFooter();
+
phpCAS::traceExit();
exit();
}
phpCAS::traceEnd();
return;
}
+ if(!$this->_start_session){
+ phpCAS::log("phpCAS can't handle logout requests if it does not manage the session.");
+ }
phpCAS::log("Logout requested");
phpCAS::log("SAML REQUEST: ".$_POST['logoutRequest']);
if ($check_client) {
}
$client_ip = $_SERVER['REMOTE_ADDR'];
$client = gethostbyaddr($client_ip);
- phpCAS::log("Client: ".$client);
+ phpCAS::log("Client: ".$client."/".$client_ip);
$allowed = false;
foreach ($allowed_clients as $allowed_client) {
- if ($client == $allowed_client) {
+ if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
phpCAS::log("Allowed client '".$allowed_client."' matches, logout request is allowed");
$allowed = true;
break;
}
if (!$allowed) {
phpCAS::error("Unauthorized logout request from client '".$client."'");
- printf("Unauthorized!");
+ printf("Unauthorized!");
phpCAS::traceExit();
exit();
}
phpCAS::log("Ticket to logout: ".$ticket2logout);
$session_id = preg_replace('/[^\w]/','',$ticket2logout);
phpCAS::log("Session id: ".$session_id);
-
- // fix New session ID
+
+ // destroy a possible application session created before phpcas
+ if(session_id()){
+ session_unset();
+ session_destroy();
+ }
+ // fix session ID
session_id($session_id);
$_COOKIE[session_name()]=$session_id;
$_GET[session_name()]=$session_id;
// Overwrite session
session_start();
session_unset();
- session_destroy();
- printf("Disconnected!");
+ session_destroy();
+ printf("Disconnected!");
phpCAS::traceExit();
exit();
}
* This method is used to validate a ST; halt on failure, and sets $validate_url,
* $text_reponse and $tree_response on success. These parameters are used later
* by CASClient::validatePGT() for CAS proxies.
- *
+ * Used for all CAS 1.0 validations
* @param $validate_url the URL of the request to the CAS server.
* @param $text_response the response of the CAS server, as is (XML text).
* @param $tree_response the response of the CAS server, as a DOM XML tree.
$validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
if ( $this->isProxy() ) {
// pass the callback url for CAS proxies
- $validate_url .= '&pgtUrl='.$this->getCallbackURL();
+ $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
}
// open and read the URL
}
break;
}
+ $this->renameSession($this->getST());
+ // at this step, ST has been validated and $this->_user has been set,
+ phpCAS::traceEnd(TRUE);
+ return TRUE;
+ }
+
+ // ########################################################################
+ // SAML VALIDATION
+ // ########################################################################
+ /**
+ * @addtogroup internalBasic
+ * @{
+ */
+
+ /**
+ * This method is used to validate a SAML TICKET; halt on failure, and sets $validate_url,
+ * $text_reponse and $tree_response on success. These parameters are used later
+ * by CASClient::validatePGT() for CAS proxies.
+ *
+ * @param $validate_url the URL of the request to the CAS server.
+ * @param $text_response the response of the CAS server, as is (XML text).
+ * @param $tree_response the response of the CAS server, as a DOM XML tree.
+ *
+ * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
+ *
+ * @private
+ */
+ function validateSA($validate_url,&$text_response,&$tree_response)
+ {
+ phpCAS::traceBegin();
+
+ // build the URL to validate the ticket
+ $validate_url = $this->getServerSamlValidateURL();
+
+ // open and read the URL
+ if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
+ phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
+ $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
+ }
+
+ phpCAS::trace('server version: '.$this->getServerVersion());
+ // analyze the result depending on the version
+ switch ($this->getServerVersion()) {
+ case SAML_VERSION_1_1:
+
+ // read the response of the CAS server into a DOM object
+ if ( !($dom = domxml_open_mem($text_response))) {
+ phpCAS::trace('domxml_open_mem() failed');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ // read the root node of the XML tree
+ if ( !($tree_response = $dom->document_element()) ) {
+ phpCAS::trace('document_element() failed');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ // insure that tag name is 'Envelope'
+ if ( $tree_response->node_name() != 'Envelope' ) {
+ phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->node_name().'\'');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ // check for the NameIdentifier tag in the SAML response
+ if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("NameIdentifier")) != 0) {
+ phpCAS::trace('NameIdentifier found');
+ $user = trim($success_elements[0]->get_content());
+ phpCAS::trace('user = `'.$user.'`');
+ $this->setUser($user);
+ $this->setSessionAttributes($text_response);
+ } else {
+ phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
+ $this->authError('SA not validated',
+ $validate_url,
+ FALSE/*$no_response*/,
+ TRUE/*$bad_response*/,
+ $text_response);
+ }
+ break;
+ }
+ $this->renameSession($this->getSA());
// at this step, ST has been validated and $this->_user has been set,
phpCAS::traceEnd(TRUE);
return TRUE;
}
+ /**
+ * This method will parse the DOM and pull out the attributes from the SAML
+ * payload and put them into an array, then put the array into the session.
+ *
+ * @param $text_response the SAML payload.
+ * @return bool TRUE when successfull and FALSE if no attributes a found
+ *
+ * @private
+ */
+ function setSessionAttributes($text_response)
+ {
+ phpCAS::traceBegin();
+
+ $result = FALSE;
+
+ if (isset($_SESSION[SAML_ATTRIBUTES])) {
+ phpCAS::trace("session attrs already set."); //testbml - do we care?
+ }
+
+ $attr_array = array();
+
+ if (($dom = domxml_open_mem($text_response))) {
+ $xPath = $dom->xpath_new_context();
+ $xPath->xpath_register_ns('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
+ $xPath->xpath_register_ns('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
+ $nodelist = $xPath->xpath_eval("//saml:Attribute");
+ if($nodelist){
+ $attrs = $nodelist->nodeset;
+ foreach($attrs as $attr){
+ $xres = $xPath->xpath_eval("saml:AttributeValue", $attr);
+ $name = $attr->get_attribute("AttributeName");
+ $value_array = array();
+ foreach($xres->nodeset as $node){
+ $value_array[] = $node->get_content();
+ }
+ $attr_array[$name] = $value_array;
+ }
+ $_SESSION[SAML_ATTRIBUTES] = $attr_array;
+ // UGent addition...
+ foreach($attr_array as $attr_key => $attr_value) {
+ if(count($attr_value) > 1) {
+ $this->_attributes[$attr_key] = $attr_value;
+ phpCAS::trace("* " . $attr_key . "=" . $attr_value);
+ }
+ else {
+ $this->_attributes[$attr_key] = $attr_value[0];
+ phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
+ }
+ }
+ $result = TRUE;
+ }else{
+ phpCAS::trace("SAML Attributes are empty");
+ $result = FALSE;
+ }
+ }
+ phpCAS::traceEnd($result);
+ return $result;
+ }
+
/** @} */
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$this->storePGT($pgt,$pgt_iou);
$this->printHTMLFooter();
phpCAS::traceExit();
+ exit();
}
/** @} */
}
// create the storage object
- $this->_pgt_storage = &new PGTStorageFile($this,$format,$path);
+ $this->_pgt_storage = new PGTStorageFile($this,$format,$path);
}
/**
trigger_error('PGT storage into database is an experimental feature, use at your own risk',E_USER_WARNING);
// create the storage object
- $this->_pgt_storage = & new PGTStorageDB($this,$user,$password,$database_type,$hostname,$port,$database,$table);
+ $this->_pgt_storage = new PGTStorageDB($this,$user,$password,$database_type,$hostname,$port,$database,$table);
}
// ########################################################################
*/
function validatePGT(&$validate_url,$text_response,$tree_response)
{
- phpCAS::traceBegin();
+ // here cannot use phpCAS::traceBegin(); alongside domxml-php4-to-php5.php
+ phpCAS::log('start validatePGT()');
if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) {
phpCAS::trace('<proxyGrantingTicket> not found');
// authentication succeded, but no PGT Iou was transmitted
}
$this->setPGT($pgt);
}
- phpCAS::traceEnd(TRUE);
+ // here, cannot use phpCAS::traceEnd(TRUE); alongside domxml-php4-to-php5.php
+ phpCAS::log('end validatePGT()');
return TRUE;
}
curl_setopt($ch, $key, $value);
}
}
-
+
if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) {
phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
}
- if ($this->_cas_server_cert != '' ) {
+ if ($this->_cas_server_cert != '' && $this->_cas_server_ca_cert != '') {
+ // This branch added by IDMS. Seems phpCAS implementor got a bit confused about the curl options CURLOPT_SSLCERT and CURLOPT_CAINFO
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
+ curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
+ curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
+ curl_setopt($ch, CURLOPT_VERBOSE, '1');
+ phpCAS::trace('CURL: Set all required opts for mutual authentication ------');
+ } else if ($this->_cas_server_cert != '' ) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
} else if ($this->_cas_server_ca_cert != '') {
if ( is_array($cookies) ) {
curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies));
}
+ // add extra stuff if SAML
+ if ($this->hasSA()) {
+ $more_headers = array ("soapaction: http://www.oasis-open.org/committees/security",
+ "cache-control: no-cache",
+ "pragma: no-cache",
+ "accept: text/xml",
+ "connection: keep-alive",
+ "content-type: text/xml");
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $more_headers);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ $data = $this->buildSAMLPayload();
+ //phpCAS::trace('SAML Payload: '.print_r($data, TRUE));
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+ }
// perform the query
$buf = curl_exec ($ch);
+ //phpCAS::trace('CURL: Call completed. Response body is: \''.$buf.'\'');
if ( $buf === FALSE ) {
phpCAS::trace('curl_exec() failed');
$err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch);
+ //phpCAS::trace('curl error: '.$err_msg);
// close the CURL session
curl_close ($ch);
$res = FALSE;
phpCAS::traceEnd($res);
return $res;
- }
+ }
+
+ /**
+ * This method is used to build the SAML POST body sent to /samlValidate URL.
+ *
+ * @return the SOAP-encased SAMLP artifact (the ticket).
+ *
+ * @private
+ */
+ function buildSAMLPayload()
+ {
+ phpCAS::traceBegin();
+
+ //get the ticket
+ $sa = $this->getSA();
+ //phpCAS::trace("SA: ".$sa);
+
+ $body=SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST.SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE.SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
+
+ phpCAS::traceEnd($body);
+ return ($body);
+ }
/**
* This method is the callback used by readURL method to request HTTP headers.
*/
var $_curl_headers = array();
function _curl_read_headers($ch, $header)
- {
+ {
$this->_curl_headers[] = $header;
return strlen($header);
- }
-
+ }
+
/**
* This method is used to access an HTTP[S] service.
*
function serviceWeb($url,&$err_code,&$output)
{
phpCAS::traceBegin();
+ $cookies = array();
// at first retrieve a PT
$pt = $this->retrievePT($url,$err_code,$output);
$res = FALSE;
} else {
// add cookies if necessary
- if ( is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
+ if ( isset($_SESSION['phpCAS']['services'][$url]['cookies']) &&
+ is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) {
$cookies[] = $name.'='.$val;
}
*
* @param $url a string giving the URL of the service, including the mailing box
* for IMAP URLs, as accepted by imap_open().
+ * @param $service a string giving for CAS retrieve Proxy ticket
* @param $flags options given to imap_open().
* @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
* success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
*
* @public
*/
- function serviceMail($url,$flags,&$err_code,&$err_msg,&$pt)
+ function serviceMail($url,$service,$flags,&$err_code,&$err_msg,&$pt)
{
phpCAS::traceBegin();
// at first retrieve a PT
- $pt = $this->retrievePT($target_service,$err_code,$output);
+ $pt = $this->retrievePT($service,$err_code,$output);
$stream = FALSE;
*/
function hasPT()
{ return !empty($this->_pt); }
+ /**
+ * This method returns the SAML Ticket provided in the URL of the request.
+ * @return The SAML ticket.
+ * @private
+ */
+ function getSA()
+ { return 'ST'.substr($this->_sa, 2); }
+
+ /**
+ * This method stores the SAML Ticket.
+ * @param $sa The SAML Ticket.
+ * @private
+ */
+ function setSA($sa)
+ { $this->_sa = $sa; }
+
+ /**
+ * This method tells if a SAML Ticket was stored.
+ * @return TRUE if a SAML Ticket has been stored.
+ * @private
+ */
+ function hasSA()
+ { return !empty($this->_sa); }
/** @} */
// ########################################################################
*/
/**
- * This method is used to validate a PT; halt on failure
- *
+ * This method is used to validate a ST or PT; halt on failure
+ * Used for all CAS 2.0 validations
* @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
*
* @private
if ( $this->isProxy() ) {
// pass the callback url for CAS proxies
- $validate_url .= '&pgtUrl='.$this->getCallbackURL();
+ $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
}
// open and read the URL
$text_response);
}
+ $this->renameSession($this->getPT());
// at this step, PT has been validated and $this->_user has been set,
phpCAS::traceEnd(TRUE);
}
}
- $final_uri .= strtok($_SERVER['REQUEST_URI'],"?");
- $cgi_params = '?'.strtok("?");
- // remove the ticket if present in the CGI parameters
- $cgi_params = preg_replace('/&ticket=[^&]*/','',$cgi_params);
- $cgi_params = preg_replace('/\?ticket=[^&;]*/','?',$cgi_params);
- $cgi_params = preg_replace('/\?%26/','?',$cgi_params);
- $cgi_params = preg_replace('/\?&/','?',$cgi_params);
- $cgi_params = preg_replace('/\?$/','',$cgi_params);
- $final_uri .= $cgi_params;
+ $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $final_uri .= $request_uri[0];
+
+ if (isset($request_uri[1]) && $request_uri[1])
+ {
+ $query_string = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
+
+ // If the query string still has anything left, append it to the final URI
+ if ($query_string !== '')
+ $final_uri .= "?$query_string";
+
+ }
+
+ phpCAS::trace("Final URI: $final_uri");
$this->setURL($final_uri);
}
phpCAS::traceEnd($this->_url);
return $this->_url;
- }
+ }
+
+
+
+ /**
+ * Removes a parameter from a query string
+ *
+ * @param string $parameterName
+ * @param string $queryString
+ * @return string
+ *
+ * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
+ */
+ function removeParameterFromQueryString($parameterName, $queryString)
+ {
+ $parameterName = preg_quote($parameterName);
+ return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
+ }
+
/**
* This method sets the URL of the current request
phpCAS::traceBegin();
$this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
- printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),$this->getURL(),$_SERVER['SERVER_ADMIN']);
+ printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),htmlentities($this->getURL()),$_SERVER['SERVER_ADMIN']);
phpCAS::trace('CAS URL: '.$cas_url);
phpCAS::trace('Authentication failure: '.$failure);
if ( $no_response ) {
/** @} */
}
-?>
\ No newline at end of file
+?>