diff --git a/lib/CAS.php b/lib/CAS.php index a446f34ea0..0e95c7a26d 100755 --- a/lib/CAS.php +++ b/lib/CAS.php @@ -40,10 +40,8 @@ // hack by Vangelis Haniotakis to handle the absence of $_SERVER['REQUEST_URI'] // in IIS // -if (php_sapi_name() != 'cli') { - if (!isset($_SERVER['REQUEST_URI'])) { - $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING']; - } +if (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SCRIPT_NAME']) && isset($_SERVER['QUERY_STRING'])) { + $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING']; } // Add a E_USER_DEPRECATED for php versions <= 5.2 @@ -63,7 +61,7 @@ /** * phpCAS version. accessible for the user by phpCAS::getVersion(). */ -define('PHPCAS_VERSION', '1.3.2'); +define('PHPCAS_VERSION', '1.3.5'); /** * @addtogroup public @@ -78,6 +76,10 @@ * CAS version 2.0 */ define("CAS_VERSION_2_0", '2.0'); +/** + * CAS version 3.0 + */ +define("CAS_VERSION_3_0", '3.0'); // ------------------------------------------------------------------------ // SAML defines @@ -134,6 +136,11 @@ */ define("SAML_ATTRIBUTES", 'SAMLATTRIBS'); +/** + * SAML Attributes + */ +define("DEFAULT_ERROR", 'Internal script failure'); + /** @} */ /** * @addtogroup publicPGTStorage @@ -212,6 +219,7 @@ define("PHPCAS_LANG_JAPANESE", 'CAS_Languages_Japanese'); define("PHPCAS_LANG_SPANISH", 'CAS_Languages_Spanish'); define("PHPCAS_LANG_CATALAN", 'CAS_Languages_Catalan'); +define("PHPCAS_LANG_CHINESE_SIMPLIFIED", 'CAS_Languages_ChineseSimplified'); /** @} */ @@ -237,7 +245,13 @@ /** * The default directory for the debug file under Unix. */ -define('DEFAULT_DEBUG_DIR', '/tmp/'); +function gettmpdir() { +if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); } +if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); } +if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); } +return "/tmp"; +} +define('DEFAULT_DEBUG_DIR', gettmpdir()."/"); /** @} */ @@ -267,6 +281,7 @@ class phpCAS /** * This variable is used by the interface class phpCAS. * + * @var CAS_Client * @hideinitializer */ private static $_PHPCAS_CLIENT; @@ -286,6 +301,15 @@ class phpCAS */ private static $_PHPCAS_DEBUG; + /** + * This variable is used to enable verbose mode + * This pevents debug info to be show to the user. Since it's a security + * feature the default is false + * + * @hideinitializer + */ + private static $_PHPCAS_VERBOSE = false; + // ######################################################################## // INITIALIZATION @@ -313,7 +337,7 @@ class phpCAS */ public static function client($server_version, $server_hostname, $server_port, $server_uri, $changeSessionID = true, - $service_address = null // Utilisé pour forcer l'URL de base + $service_address = null // Utilisé pour forcer l'URL de base ) { phpCAS :: traceBegin(); if (is_object(self::$_PHPCAS_CLIENT)) { @@ -342,11 +366,15 @@ public static function client($server_version, $server_hostname, ); // initialize the object $_PHPCAS_CLIENT + try { self::$_PHPCAS_CLIENT = new CAS_Client( $server_version, false, $server_hostname, $server_port, $server_uri, $changeSessionID, $service_address - ); + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } phpCAS :: traceEnd(); } @@ -395,13 +423,27 @@ public static function proxy($server_version, $server_hostname, ); // initialize the object $_PHPCAS_CLIENT - self::$_PHPCAS_CLIENT = new CAS_Client( - $server_version, true, $server_hostname, $server_port, $server_uri, - $changeSessionID - ); + try { + self::$_PHPCAS_CLIENT = new CAS_Client( + $server_version, true, $server_hostname, $server_port, $server_uri, + $changeSessionID + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } phpCAS :: traceEnd(); } + /** + * Answer whether or not the client or proxy has been initialized + * + * @return bool + */ + public static function isInitialized () + { + return (is_object(self::$_PHPCAS_CLIENT)); + } + /** @} */ // ######################################################################## // DEBUGGING @@ -449,11 +491,39 @@ public static function setDebug($filename = '') self::$_PHPCAS_DEBUG['filename'] = $filename; self::$_PHPCAS_DEBUG['indent'] = 0; - phpCAS :: trace('START phpCAS-' . PHPCAS_VERSION . ' ******************'); + phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************'); + } + } + + /** + * Enable verbose errors messages in the website output + * This is a security relevant since internal status info may leak an may + * help an attacker. Default is therefore false + * + * @param bool $verbose enable verbose output + * + * @return void + */ + public static function setVerbose($verbose) + { + if ($verbose === true) { + self::$_PHPCAS_VERBOSE = true; + } else { + self::$_PHPCAS_VERBOSE = false; } } + /** + * Show is verbose mode is on + * + * @return boot verbose + */ + public static function getVerbose() + { + return self::$_PHPCAS_VERBOSE; + } + /** * Logs a string in debug mode. * @@ -498,6 +568,7 @@ public static function log($str) */ public static function error($msg) { + phpCAS :: traceBegin(); $dbg = debug_backtrace(); $function = '?'; $file = '?'; @@ -513,8 +584,12 @@ public static function error($msg) } } } - echo "
\nphpCAS error: " . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . " in " . $file . " on line " . $line . "
\n"; - phpCAS :: trace($msg); + if (self::$_PHPCAS_VERBOSE) { + echo "
\nphpCAS error: " . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . " in " . $file . " on line " . $line . "
\n"; + } else { + echo "
\nError: ". DEFAULT_ERROR ."
\n"; + } + phpCAS :: trace($msg . ' in ' . $file . 'on line ' . $line ); phpCAS :: traceEnd(); throw new CAS_GracefullTerminationException(__CLASS__ . "::" . $function . '(): ' . $msg); @@ -534,7 +609,8 @@ public static function trace($str) } /** - * This method is used to indicate the start of the execution of a function in debug mode. + * This method is used to indicate the start of the execution of a function + * in debug mode. * * @return void */ @@ -638,13 +714,13 @@ public static function traceExit() */ public static function setLang($lang) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (gettype($lang) != 'string') { - phpCAS :: error('type mismatched for parameter $lang (should be `string\')'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setLang($lang); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setLang($lang); } /** @} */ @@ -684,13 +760,13 @@ public static function getVersion() */ public static function setHTMLHeader($header) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (gettype($header) != 'string') { - phpCAS :: error('type mismatched for parameter $header (should be `string\')'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setHTMLHeader($header); } /** @@ -702,13 +778,13 @@ public static function setHTMLHeader($header) */ public static function setHTMLFooter($footer) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (gettype($footer) != 'string') { - phpCAS :: error('type mismatched for parameter $footer (should be `string\')'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLFooter($footer); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setHTMLFooter($footer); } /** @} */ @@ -731,19 +807,13 @@ public static function setHTMLFooter($footer) public static function setPGTStorage($storage) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called before ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() (called at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ')'); - } - if ( !($storage instanceof CAS_PGTStorage) ) { - phpCAS :: error('type mismatched for parameter $storage (should be a CAS_PGTStorage `object\')'); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorage($storage); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setPGTStorage($storage); phpCAS :: traceEnd(); } @@ -768,25 +838,13 @@ public static function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table='', $driver_options=null ) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called before ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() (called at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ')'); - } - if (gettype($username) != 'string') { - phpCAS :: error('type mismatched for parameter $username (should be `string\')'); - } - if (gettype($password) != 'string') { - phpCAS :: error('type mismatched for parameter $password (should be `string\')'); - } - if (gettype($table) != 'string') { - phpCAS :: error('type mismatched for parameter $table (should be `string\')'); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options); phpCAS :: traceEnd(); } @@ -801,19 +859,13 @@ public static function setPGTStorageDb($dsn_or_pdo, $username='', public static function setPGTStorageFile($path = '') { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called before ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() (called at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ')'); - } - if (gettype($path) != 'string') { - phpCAS :: error('type mismatched for parameter $path (should be `string\')'); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageFile($path); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setPGTStorageFile($path); phpCAS :: traceEnd(); } /** @} */ @@ -838,23 +890,13 @@ public static function setPGTStorageFile($path = '') public static function getProxiedService ($type) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after the programmer is sure the user has been authenticated (by calling ' . __CLASS__ . '::checkAuthentication() or ' . __CLASS__ . '::forceAuthentication()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); - } - if (gettype($type) != 'string') { - phpCAS :: error('type mismatched for parameter $type (should be `string\')'); - } + phpCAS::_validateProxyExists(); - $res = self::$_PHPCAS_CLIENT->getProxiedService($type); + try { + $res = self::$_PHPCAS_CLIENT->getProxiedService($type); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } phpCAS :: traceEnd(); return $res; @@ -874,20 +916,13 @@ public static function getProxiedService ($type) */ public static function initializeProxiedService (CAS_ProxiedService $proxiedService) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after the programmer is sure the user has been authenticated (by calling ' . __CLASS__ . '::checkAuthentication() or ' . __CLASS__ . '::forceAuthentication()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); - } + phpCAS::_validateProxyExists(); - self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService); + try { + self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } } /** @@ -908,23 +943,13 @@ public static function initializeProxiedService (CAS_ProxiedService $proxiedServ public static function serviceWeb($url, & $err_code, & $output) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after the programmer is sure the user has been authenticated (by calling ' . __CLASS__ . '::checkAuthentication() or ' . __CLASS__ . '::forceAuthentication()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be `string\')'); - } + phpCAS::_validateProxyExists(); - $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output); + try { + $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } phpCAS :: traceEnd($res); return $res; @@ -952,28 +977,14 @@ public static function serviceWeb($url, & $err_code, & $output) public static function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after the programmer is sure the user has been authenticated (by calling ' . __CLASS__ . '::checkAuthentication() or ' . __CLASS__ . '::forceAuthentication()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be `string\')'); - } + phpCAS::_validateProxyExists(); - if (gettype($flags) != 'integer') { - phpCAS :: error('type mismatched for parameter $flags (should be `integer\')'); + try { + $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt); - phpCAS :: traceEnd($res); return $res; } @@ -1000,13 +1011,32 @@ public static function serviceMail($url, $service, $flags, & $err_code, & $err_m */ public static function setCacheTimesForAuthRecheck($n) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (gettype($n) != 'integer') { - phpCAS :: error('type mismatched for parameter $n (should be `integer\')'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); + } + + + /** + * Set a callback function to be run when receiving CAS attributes + * + * The callback function will be passed an $success_elements + * payload of the response (\DOMElement) as its first parameter. + * + * @param string $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setCasAttributeParserCallback($function, $additionalArgs); } /** @@ -1030,9 +1060,7 @@ public static function setCacheTimesForAuthRecheck($n) */ public static function setPostAuthenticateCallback ($function, array $additionalArgs = array()) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); self::$_PHPCAS_CLIENT->setPostAuthenticateCallback($function, $additionalArgs); } @@ -1053,9 +1081,7 @@ public static function setPostAuthenticateCallback ($function, array $additional */ public static function setSingleSignoutCallback ($function, array $additionalArgs = array()) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); self::$_PHPCAS_CLIENT->setSingleSignoutCallback($function, $additionalArgs); } @@ -1073,9 +1099,7 @@ public static function setSingleSignoutCallback ($function, array $additionalArg public static function checkAuthentication() { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); $auth = self::$_PHPCAS_CLIENT->checkAuthentication(); @@ -1096,16 +1120,13 @@ public static function checkAuthentication() public static function forceAuthentication() { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - + phpCAS::_validateClientExists(); $auth = self::$_PHPCAS_CLIENT->forceAuthentication(); // store where the authentication has been checked and the result self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); - /* if (!$auth) { + /* if (!$auth) { phpCAS :: trace('user is not authenticated, redirecting to the CAS server'); self::$_PHPCAS_CLIENT->forceAuthentication(); } else { @@ -1124,9 +1145,8 @@ public static function forceAuthentication() public static function renewAuthentication() { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + $auth = self::$_PHPCAS_CLIENT->renewAuthentication(); // store where the authentication has been checked and the result @@ -1145,9 +1165,7 @@ public static function renewAuthentication() public static function isAuthenticated() { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); // call the isAuthenticated method of the $_PHPCAS_CLIENT object $auth = self::$_PHPCAS_CLIENT->isAuthenticated(); @@ -1168,9 +1186,8 @@ public static function isAuthenticated() */ public static function isSessionAuthenticated() { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + return (self::$_PHPCAS_CLIENT->isSessionAuthenticated()); } @@ -1178,65 +1195,56 @@ public static function isSessionAuthenticated() * This method returns the CAS user's login name. * * @return string the login name of the authenticated user - * @warning should not be called only after phpCAS::forceAuthentication() + * @warning should only be called after phpCAS::forceAuthentication() * or phpCAS::checkAuthentication(). * */ public static function getUser() { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getUser(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - return self::$_PHPCAS_CLIENT->getUser(); } /** * Answer attributes about the authenticated user. * - * @warning should not be called only after phpCAS::forceAuthentication() + * @warning should only be called after phpCAS::forceAuthentication() * or phpCAS::checkAuthentication(). * * @return array */ public static function getAttributes() { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - return self::$_PHPCAS_CLIENT->getAttributes(); } /** * Answer true if there are attributes for the authenticated user. * - * @warning should not be called only after phpCAS::forceAuthentication() + * @warning should only be called after phpCAS::forceAuthentication() * or phpCAS::checkAuthentication(). * * @return bool */ public static function hasAttributes() { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - return self::$_PHPCAS_CLIENT->hasAttributes(); } /** @@ -1245,21 +1253,18 @@ public static function hasAttributes() * @param string $key attribute name * * @return bool - * @warning should not be called only after phpCAS::forceAuthentication() + * @warning should only be called after phpCAS::forceAuthentication() * or phpCAS::checkAuthentication(). */ public static function hasAttribute($key) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - return self::$_PHPCAS_CLIENT->hasAttribute($key); } /** @@ -1268,21 +1273,18 @@ public static function hasAttribute($key) * @param string $key attribute name * * @return mixed string for a single value or an array if multiple values exist. - * @warning should not be called only after phpCAS::forceAuthentication() + * @warning should only be called after phpCAS::forceAuthentication() * or phpCAS::checkAuthentication(). */ public static function getAttribute($key) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()'); - } - if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) { - phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false'); + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - return self::$_PHPCAS_CLIENT->getAttribute($key); } /** @@ -1295,9 +1297,8 @@ public static function getAttribute($key) */ public static function handleLogoutRequests($check_client = true, $allowed_clients = false) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + return (self::$_PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients)); } @@ -1309,9 +1310,8 @@ public static function handleLogoutRequests($check_client = true, $allowed_clien */ public static function getServerLoginURL() { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + return self::$_PHPCAS_CLIENT->getServerLoginURL(); } @@ -1326,19 +1326,24 @@ public static function getServerLoginURL() public static function setServerLoginURL($url = '') { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after' . __CLASS__ . '::client()'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be `string`)'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLoginURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setServerLoginURL($url); + phpCAS :: traceEnd(); } /** * Set the serviceValidate URL of the CAS server. - * Used only in CAS 1.0 validations + * Used for all CAS versions of URL validations. + * Examples: + * CAS 1.0 http://www.exemple.com/validate + * CAS 2.0 http://www.exemple.com/validateURL + * CAS 3.0 http://www.exemple.com/p3/serviceValidate * * @param string $url the serviceValidate URL * @@ -1347,19 +1352,24 @@ public static function setServerLoginURL($url = '') public static function setServerServiceValidateURL($url = '') { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after' . __CLASS__ . '::client()'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be `string`)'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url); + phpCAS :: traceEnd(); } /** * Set the proxyValidate URL of the CAS server. - * Used for all CAS 2.0 validations + * Used for all CAS versions of proxy URL validations + * Examples: + * CAS 1.0 http://www.exemple.com/ + * CAS 2.0 http://www.exemple.com/proxyValidate + * CAS 3.0 http://www.exemple.com/p3/proxyValidate * * @param string $url the proxyValidate URL * @@ -1368,13 +1378,14 @@ public static function setServerServiceValidateURL($url = '') public static function setServerProxyValidateURL($url = '') { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after' . __CLASS__ . '::client()'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be `string`)'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url); + phpCAS :: traceEnd(); } @@ -1388,13 +1399,14 @@ public static function setServerProxyValidateURL($url = '') public static function setServerSamlValidateURL($url = '') { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after' . __CLASS__ . '::client()'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be`string\')'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url); + phpCAS :: traceEnd(); } @@ -1406,9 +1418,8 @@ public static function setServerSamlValidateURL($url = '') */ public static function getServerLogoutURL() { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + return self::$_PHPCAS_CLIENT->getServerLogoutURL(); } @@ -1423,17 +1434,14 @@ public static function getServerLogoutURL() public static function setServerLogoutURL($url = '') { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error( - 'this method should only be called after' . __CLASS__ . '::client()' - ); - } - if (gettype($url) != 'string') { - phpCAS :: error( - 'type mismatched for parameter $url (should be `string`)' - ); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLogoutURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setServerLogoutURL($url); + phpCAS :: traceEnd(); } @@ -1448,9 +1456,8 @@ public static function setServerLogoutURL($url = '') public static function logout($params = "") { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + $parsedParams = array (); if ($params != "") { if (is_string($params)) { @@ -1475,16 +1482,15 @@ public static function logout($params = "") * This method is used to logout from CAS. Halts by redirecting to the CAS * server. * - * @param service $service a URL that will be transmitted to the CAS server + * @param string $service a URL that will be transmitted to the CAS server * * @return void */ public static function logoutWithRedirectService($service) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + if (!is_string($service)) { phpCAS :: error('type mismatched for parameter $service (should be `string\')'); } @@ -1534,9 +1540,8 @@ public static function logoutWithRedirectServiceAndUrl($service, $url) { trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + if (!is_string($service)) { phpCAS :: error('type mismatched for parameter $service (should be `string\')'); } @@ -1565,16 +1570,14 @@ public static function logoutWithRedirectServiceAndUrl($service, $url) public static function setFixedCallbackURL($url = '') { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (!self::$_PHPCAS_CLIENT->isProxy()) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setCallbackURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setCallbackURL($url); + phpCAS :: traceEnd(); } @@ -1589,13 +1592,14 @@ public static function setFixedCallbackURL($url = '') public static function setFixedServiceURL($url) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (gettype($url) != 'string') { - phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setURL($url); + phpCAS :: traceEnd(); } @@ -1606,9 +1610,7 @@ public static function setFixedServiceURL($url) */ public static function getServiceURL() { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateProxyExists(); return (self::$_PHPCAS_CLIENT->getURL()); } @@ -1623,13 +1625,13 @@ public static function getServiceURL() */ public static function retrievePT($target_service, & $err_code, & $err_msg) { - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()'); - } - if (gettype($target_service) != 'string') { - phpCAS :: error('type mismatched for parameter $target_service(should be `string\')'); + phpCAS::_validateProxyExists(); + + try { + return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg)); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg)); } /** @@ -1644,16 +1646,14 @@ public static function retrievePT($target_service, & $err_code, & $err_msg) public static function setCasServerCACert($cert, $validate_cn = true) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } - if (gettype($cert) != 'string') { - phpCAS :: error('type mismatched for parameter $cert (should be `string\')'); - } - if (gettype($validate_cn) != 'boolean') { - phpCAS :: error('type mismatched for parameter $validate_cn (should be `boolean\')'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn); + phpCAS :: traceEnd(); } @@ -1665,9 +1665,8 @@ public static function setCasServerCACert($cert, $validate_cn = true) public static function setNoCasServerValidation() { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + phpCAS :: trace('You have configured no validation of the legitimacy of the cas server. This is not recommended for production use.'); self::$_PHPCAS_CLIENT->setNoCasServerValidation(); phpCAS :: traceEnd(); @@ -1686,9 +1685,8 @@ public static function setNoCasServerValidation() public static function setNoClearTicketsFromUrl() { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + self::$_PHPCAS_CLIENT->setNoClearTicketsFromUrl(); phpCAS :: traceEnd(); } @@ -1707,9 +1705,8 @@ public static function setNoClearTicketsFromUrl() public static function setExtraCurlOption($key, $value) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } + phpCAS::_validateClientExists(); + self::$_PHPCAS_CLIENT->setExtraCurlOption($key, $value); phpCAS :: traceEnd(); } @@ -1753,11 +1750,12 @@ public static function setExtraCurlOption($key, $value) public static function allowProxyChain(CAS_ProxyChain_Interface $proxy_chain) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } - if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0) { - phpCAS :: error('this method can only be used with the cas 2.0 protool'); + phpCAS::_validateClientExists(); + + if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0 + && self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_3_0 + ) { + phpCAS :: error('this method can only be used with the cas 2.0/3.0 protocols'); } self::$_PHPCAS_CLIENT->getAllowedProxyChains()->allowProxyChain($proxy_chain); phpCAS :: traceEnd(); @@ -1774,9 +1772,7 @@ public static function allowProxyChain(CAS_ProxyChain_Interface $proxy_chain) */ public static function getProxies () { - if ( !is_object(self::$_PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client()'); - } + phpCAS::_validateProxyExists(); return(self::$_PHPCAS_CLIENT->getProxies()); } @@ -1797,13 +1793,14 @@ public static function addRebroadcastNode($rebroadcastNodeUrl) { phpCAS::traceBegin(); phpCAS::log('rebroadcastNodeUrl:'.$rebroadcastNodeUrl); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } - if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl)) { - phpCAS::error('type mismatched for parameter $rebroadcastNodeUrl (should be `url\')'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl); + phpCAS::traceEnd(); } @@ -1818,14 +1815,55 @@ public static function addRebroadcastNode($rebroadcastNodeUrl) public static function addRebroadcastHeader($header) { phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); } - self::$_PHPCAS_CLIENT->addRebroadcastHeader($header); + phpCAS :: traceEnd(); } -} + /** + * Checks if a client already exists + * + * @throws CAS_OutOfSequenceBeforeClientException + * + * @return void + */ + private static function _validateClientExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeClientException(); + } + } + + /** + * Checks of a proxy client aready exists + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + private static function _validateProxyExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + + /** + * For testing purposes, use this method to set the client to a test double + * + * @return void + */ + public static function setCasClient(\CAS_Client $client) + { + self::$_PHPCAS_CLIENT = $client; + } +} // ######################################################################## // DOCUMENTATION // ######################################################################## diff --git a/lib/CAS/AuthenticationException.php b/lib/CAS/AuthenticationException.php index 801156e881..1a98d753c3 100755 --- a/lib/CAS/AuthenticationException.php +++ b/lib/CAS/AuthenticationException.php @@ -68,39 +68,43 @@ class CAS_AuthenticationException public function __construct($client,$failure,$cas_url,$no_response, $bad_response='',$cas_response='',$err_code='',$err_msg='' ) { + $messages = array(); phpCAS::traceBegin(); $lang = $client->getLangObj(); $client->printHTMLHeader($lang->getAuthenticationFailed()); printf( $lang->getYouWereNotAuthenticated(), htmlentities($client->getURL()), - $_SERVER['SERVER_ADMIN'] + isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:'' ); - phpCAS::trace('CAS URL: '.$cas_url); - phpCAS::trace('Authentication failure: '.$failure); + phpCAS::trace($messages[] = 'CAS URL: '.$cas_url); + phpCAS::trace($messages[] = 'Authentication failure: '.$failure); if ( $no_response ) { - phpCAS::trace('Reason: no response from the CAS server'); + phpCAS::trace($messages[] = 'Reason: no response from the CAS server'); } else { if ( $bad_response ) { - phpCAS::trace('Reason: bad response from the CAS server'); + phpCAS::trace($messages[] = 'Reason: bad response from the CAS server'); } else { switch ($client->getServerVersion()) { case CAS_VERSION_1_0: - phpCAS::trace('Reason: CAS error'); + phpCAS::trace($messages[] = 'Reason: CAS error'); break; case CAS_VERSION_2_0: + case CAS_VERSION_3_0: if ( empty($err_code) ) { - phpCAS::trace('Reason: no CAS error'); + phpCAS::trace($messages[] = 'Reason: no CAS error'); } else { - phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg); + phpCAS::trace($messages[] = 'Reason: ['.$err_code.'] CAS error: '.$err_msg); } break; } } - phpCAS::trace('CAS response: '.$cas_response); + phpCAS::trace($messages[] = 'CAS response: '.$cas_response); } $client->printHTMLFooter(); phpCAS::traceExit(); + + parent::__construct(implode("\n", $messages)); } } diff --git a/lib/CAS/Autoload.php b/lib/CAS/Autoload.php index c7d436e199..e56dbdfa93 100755 --- a/lib/CAS/Autoload.php +++ b/lib/CAS/Autoload.php @@ -25,35 +25,39 @@ function CAS_autoload($class) { // Static to hold the Include Path to CAS static $include_path; - // Setup the include path if it's not already set from a previous call - if (!$include_path) { - $include_path = dirname(dirname(__FILE__)); - } + // Check only for CAS classes if (substr($class, 0, 4) !== 'CAS_') { return false; } + // Setup the include path if it's not already set from a previous call + if (empty($include_path)) { + $include_path = array(dirname(dirname(__FILE__)), dirname(dirname(__FILE__)) . '/../test/' ); + } + // Declare local variable to store the expected full path to the file - $file_path = $include_path . '/' . str_replace('_', '/', $class) . '.php'; - $fp = @fopen($file_path, 'r', true); - if ($fp) { - fclose($fp); - include $file_path; - if (!class_exists($class, false) && !interface_exists($class, false)) { - die( - new Exception( - 'Class ' . $class . ' was not present in ' . - $file_path . - ' [CAS_autoload]' - ) - ); + foreach ($include_path as $path) { + $file_path = $path . '/' . str_replace('_', '/', $class) . '.php'; + $fp = @fopen($file_path, 'r', true); + if ($fp) { + fclose($fp); + include $file_path; + if (!class_exists($class, false) && !interface_exists($class, false)) { + die( + new Exception( + 'Class ' . $class . ' was not present in ' . + $file_path . + ' [CAS_autoload]' + ) + ); + } + return true; } - return true; } $e = new Exception( 'Class ' . $class . ' could not be loaded from ' . $file_path . ', file does not exist (Path="' - . $include_path .'") [CAS_autoload]' + . implode(':', $include_path) .'") [CAS_autoload]' ); $trace = $e->getTrace(); if (isset($trace[2]) && isset($trace[2]['function']) @@ -71,9 +75,13 @@ function CAS_autoload($class) // set up __autoload if (function_exists('spl_autoload_register')) { - if (!(spl_autoload_functions()) || !in_array('CAS_autoload', spl_autoload_functions())) { + if (!(spl_autoload_functions()) + || !in_array('CAS_autoload', spl_autoload_functions()) + ) { spl_autoload_register('CAS_autoload'); - if (function_exists('__autoload') && !in_array('__autoload', spl_autoload_functions())) { + if (function_exists('__autoload') + && !in_array('__autoload', spl_autoload_functions()) + ) { // __autoload() was being used, but now would be ignored, add // it to the autoload stack spl_autoload_register('__autoload'); diff --git a/lib/CAS/Client.php b/lib/CAS/Client.php index d7bf1e11f1..662a0a111d 100755 --- a/lib/CAS/Client.php +++ b/lib/CAS/Client.php @@ -131,9 +131,11 @@ public function printHTMLFooter() $lang = $this->getLangObj(); $this->_htmlFilterOutput( empty($this->_output_footer)? - ('
phpCAS __PHPCAS_VERSION__ ' - .$lang->getUsingServer() - .' __SERVER_BASE_URL__ (CAS __CAS_VERSION__)
') + (phpcas::getVerbose())? + '
phpCAS __PHPCAS_VERSION__ ' + .$lang->getUsingServer() + .' __SERVER_BASE_URL__ (CAS __CAS_VERSION__)
' + :'' :$this->_output_footer ); } @@ -147,6 +149,10 @@ public function printHTMLFooter() */ public function setHTMLHeader($header) { + // Argument Validation + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + $this->_output_header = $header; } @@ -159,6 +165,10 @@ public function setHTMLHeader($header) */ public function setHTMLFooter($footer) { + // Argument Validation + if (gettype($footer) != 'string') + throw new CAS_TypeMismatchException($footer, '$footer', 'string'); + $this->_output_footer = $footer; } @@ -190,10 +200,16 @@ public function setHTMLFooter($footer) */ public function setLang($lang) { + // Argument Validation + if (gettype($lang) != 'string') + throw new CAS_TypeMismatchException($lang, '$lang', 'string'); + phpCAS::traceBegin(); $obj = new $lang(); if (!($obj instanceof CAS_Languages_LanguageInterface)) { - throw new CAS_InvalidArgumentException('$className must implement the CAS_Languages_LanguageInterface'); + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Languages_LanguageInterface' + ); } $this->_lang = $lang; phpCAS::traceEnd(); @@ -323,9 +339,7 @@ public function getServerLoginURL($gateway=false,$renew=false) phpCAS::traceBegin(); // the URL is build only when needed if ( empty($this->_server['login_url']) ) { - $this->_server['login_url'] = $this->_getServerBaseURL(); - $this->_server['login_url'] .= 'login?service='; - $this->_server['login_url'] .= urlencode($this->getURL()); + $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL())); } $url = $this->_server['login_url']; if ($renew) { @@ -350,6 +364,10 @@ public function getServerLoginURL($gateway=false,$renew=false) */ public function setServerLoginURL($url) { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + return $this->_server['login_url'] = $url; } @@ -363,6 +381,10 @@ public function setServerLoginURL($url) */ public function setServerServiceValidateURL($url) { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + return $this->_server['service_validate_url'] = $url; } @@ -376,6 +398,10 @@ public function setServerServiceValidateURL($url) */ public function setServerProxyValidateURL($url) { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + return $this->_server['proxy_validate_url'] = $url; } @@ -389,6 +415,10 @@ public function setServerProxyValidateURL($url) */ public function setServerSamlValidateURL($url) { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + return $this->_server['saml_validate_url'] = $url; } @@ -412,9 +442,16 @@ public function getServerServiceValidateURL() $this->_server['service_validate_url'] = $this->_getServerBaseURL() .'serviceValidate'; break; + case CAS_VERSION_3_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'p3/serviceValidate'; + break; } } - $url = $this->_buildQueryUrl($this->_server['service_validate_url'], 'service='.urlencode($this->getURL())); + $url = $this->_buildQueryUrl( + $this->_server['service_validate_url'], + 'service='.urlencode($this->getURL()) + ); phpCAS::traceEnd($url); return $url; } @@ -435,7 +472,10 @@ public function getServerSamlValidateURL() } } - $url = $this->_buildQueryUrl($this->_server['saml_validate_url'], 'TARGET='.urlencode($this->getURL())); + $url = $this->_buildQueryUrl( + $this->_server['saml_validate_url'], + 'TARGET='.urlencode($this->getURL()) + ); phpCAS::traceEnd($url); return $url; } @@ -457,9 +497,15 @@ public function getServerProxyValidateURL() case CAS_VERSION_2_0: $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate'; break; + case CAS_VERSION_3_0: + $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate'; + break; } } - $url = $this->_buildQueryUrl($this->_server['proxy_validate_url'], 'service='.urlencode($this->getURL())); + $url = $this->_buildQueryUrl( + $this->_server['proxy_validate_url'], + 'service='.urlencode($this->getURL()) + ); phpCAS::traceEnd($url); return $url; } @@ -479,6 +525,7 @@ public function getServerProxyURL() $this->_server['proxy_url'] = ''; break; case CAS_VERSION_2_0: + case CAS_VERSION_3_0: $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy'; break; } @@ -509,6 +556,10 @@ public function getServerLogoutURL() */ public function setServerLogoutURL($url) { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + return $this->_server['logout_url'] = $url; } @@ -561,7 +612,9 @@ public function setRequestImplementation ($className) { $obj = new $className; if (!($obj instanceof CAS_Request_RequestInterface)) { - throw new CAS_InvalidArgumentException('$className must implement the CAS_Request_RequestInterface'); + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Request_RequestInterface' + ); } $this->_requestImplementation = $className; } @@ -588,7 +641,33 @@ public function setNoClearTicketsFromUrl () } /** - * @var callback $_postAuthenticateCallbackFunction; + * @var callback $_attributeParserCallbackFunction; + */ + private $_casAttributeParserCallbackFunction = null; + + /** + * @var array $_attributeParserCallbackArgs; + */ + private $_casAttributeParserCallbackArgs = array(); + + /** + * Set a callback function to be run when parsing CAS attributes + * + * The callback function will be passed a XMLNode as its first parameter, + * followed by any $additionalArgs you pass. + * + * @param string $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + $this->_casAttributeParserCallbackFunction = $function; + $this->_casAttributeParserCallbackArgs = $additionalArgs; + } + + /** @var callback $_postAuthenticateCallbackFunction; */ private $_postAuthenticateCallbackFunction = null; @@ -656,6 +735,20 @@ public function setSingleSignoutCallback ($function, array $additionalArgs = arr // Methods for supplying code-flow feedback to integrators. // ######################################################################## + /** + * Ensure that this is actually a proxy object or fail with an exception + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + public function ensureIsProxy() + { + if (!$this->isProxy()) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + /** * Mark the caller of authentication. This will help client integraters determine * problems with their code flow if they call a function such as getUser() before @@ -688,6 +781,21 @@ public function wasAuthenticationCalled () return !empty($this->_authentication_caller); } + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceBeforeAuthenticationCallException + * + * @return void + */ + private function _ensureAuthenticationCalled() + { + if (!$this->wasAuthenticationCalled()) { + throw new CAS_OutOfSequenceBeforeAuthenticationCallException(); + } + } + /** * Answer the result of the authentication call. * @@ -698,12 +806,33 @@ public function wasAuthenticationCalled () */ public function wasAuthenticationCallSuccessful () { - if (empty($this->_authentication_caller)) { - throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.'); - } + $this->_ensureAuthenticationCalled(); return $this->_authentication_caller['result']; } + + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceBeforeAuthenticationCallException + * + * @return void + */ + public function ensureAuthenticationCallSuccessful() + { + $this->_ensureAuthenticationCalled(); + if (!$this->_authentication_caller['result']) { + throw new CAS_OutOfSequenceException( + 'authentication was checked (by ' + . $this->getAuthenticationCallerMethod() + . '() at ' . $this->getAuthenticationCallerFile() + . ':' . $this->getAuthenticationCallerLine() + . ') but the method returned false' + ); + } + } + /** * Answer information about the authentication caller. * @@ -714,9 +843,7 @@ public function wasAuthenticationCallSuccessful () */ public function getAuthenticationCallerFile () { - if (empty($this->_authentication_caller)) { - throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.'); - } + $this->_ensureAuthenticationCalled(); return $this->_authentication_caller['file']; } @@ -730,9 +857,7 @@ public function getAuthenticationCallerFile () */ public function getAuthenticationCallerLine () { - if (empty($this->_authentication_caller)) { - throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.'); - } + $this->_ensureAuthenticationCalled(); return $this->_authentication_caller['line']; } @@ -746,9 +871,7 @@ public function getAuthenticationCallerLine () */ public function getAuthenticationCallerMethod () { - if (empty($this->_authentication_caller)) { - throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.'); - } + $this->_ensureAuthenticationCalled(); return $this->_authentication_caller['method']; } @@ -770,32 +893,80 @@ public function getAuthenticationCallerMethod () * @param string $server_hostname the hostname of the CAS server * @param int $server_port the port the CAS server is running on * @param string $server_uri the URI the CAS server is responding on - * @param bool $changeSessionID Allow phpCAS to change the session_id (Single Sign Out/handleLogoutRequests is based on that change) + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) * * @return a newly created CAS_Client object */ - public function __construct( +/* public function __construct( $server_version, $proxy, $server_hostname, $server_port, $server_uri, - $changeSessionID = true, - $service_address = null + $changeSessionID = true ) { + // Argument validation + if (gettype($server_version) != 'string') + throw new CAS_TypeMismatchException($server_version, '$server_version', 'string'); + if (gettype($proxy) != 'boolean') + throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean'); + if (gettype($server_hostname) != 'string') + throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string'); + if (gettype($server_port) != 'integer') + throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer'); + if (gettype($server_uri) != 'string') + throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string'); + if (gettype($changeSessionID) != 'boolean') + throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean'); phpCAS::traceBegin(); - - $this->_server_base_url = $service_address; + */ + // le constructeur est patché selon la méthode suivie dans GEPI + public function __construct( + $server_version, + $proxy, + $server_hostname, + $server_port, + $server_uri, + $changeSessionID = true, + $service_address = null + ) { + // Argument validation + if (gettype($server_version) != 'string') + throw new CAS_TypeMismatchException($server_version, '$server_version', 'string'); + if (gettype($proxy) != 'boolean') + throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean'); + if (gettype($server_hostname) != 'string') + throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string'); + if (gettype($server_port) != 'integer') + throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer'); + if (gettype($server_uri) != 'string') + throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string'); + if (gettype($changeSessionID) != 'boolean') + throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean'); + // à voir : tester le type de $service_address - $this->_setChangeSessionID($changeSessionID); // true : allow to change the session_id(), false session_id won't be change and logout won't be handle because of that + phpCAS::traceBegin(); + + $this->_server_base_url = $service_address; // patch GEPI + + // true : allow to change the session_id(), false session_id won't be + // change and logout won't be handle because of that + $this->_setChangeSessionID($changeSessionID); // skip Session Handling for logout requests and if don't want it' if (session_id()=="" && !$this->_isLogoutRequest()) { - phpCAS :: trace("Starting a new session"); session_start(); + phpCAS :: trace("Starting a new session " . session_id()); + } + // Only for debug purposes + if ($this->isSessionAuthenticated()){ + phpCAS :: trace("Session is authenticated as: " . $_SESSION['phpCAS']['user']); + } else { + phpCAS :: trace("Session is not authenticated"); } - // are we in proxy mode ? $this->_proxy = $proxy; @@ -807,7 +978,9 @@ public function __construct( if (!isset($_SESSION['phpCAS']['service_cookies'])) { $_SESSION['phpCAS']['service_cookies'] = array(); } - $this->_serviceCookieJar = new CAS_CookieJar($_SESSION['phpCAS']['service_cookies']); + $this->_serviceCookieJar = new CAS_CookieJar( + $_SESSION['phpCAS']['service_cookies'] + ); } //check version @@ -820,6 +993,7 @@ public function __construct( } break; case CAS_VERSION_2_0: + case CAS_VERSION_3_0: break; case SAML_VERSION_1_1: break; @@ -852,7 +1026,8 @@ public function __construct( phpCAS::error('bad CAS server URI (`'.$server_uri.'\')'); } // add leading and trailing `/' and remove doubles - $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri.'/'); + if(strstr($server_uri, '?') === false) $server_uri .= '/'; + $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri); $this->_server['uri'] = $server_uri; // set to callback mode if PgtIou and PgtId CGI GET parameters are provided @@ -863,7 +1038,9 @@ public function __construct( if ( $this->_isCallbackMode() ) { //callback mode: check that phpCAS is secured if ( !$this->_isHttps() ) { - phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server'); + phpCAS::error( + 'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server' + ); } } else { //normal mode: get ticket and remove it from CGI parameters for @@ -875,7 +1052,10 @@ public function __construct( unset($_GET['ticket']); } else if ( !empty($ticket) ) { //ill-formed ticket, halt - phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')'); + phpCAS::error( + 'ill-formed ticket found in the URL (ticket=`' + .htmlentities($ticket).'\')' + ); } } @@ -967,6 +1147,23 @@ private function _setUser($user) */ public function getUser() { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_getUser(); + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * + * @warning should be called only after CAS_Client::forceAuthentication() or + * CAS_Client::isAuthenticated(), otherwise halt with an error. + */ + private function _getUser() + { + // This is likely a duplicate check that could be removed.... if ( empty($this->_user) ) { phpCAS::error( 'this method should be used only after '.__CLASS__ @@ -1004,6 +1201,9 @@ public function setAttributes($attributes) */ public function getAttributes() { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + // This is likely a duplicate check that could be removed.... if ( empty($this->_user) ) { // if no user is set, there shouldn't be any attributes also... phpCAS::error( @@ -1021,6 +1221,9 @@ public function getAttributes() */ public function hasAttributes() { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + return !empty($this->_attributes); } /** @@ -1031,6 +1234,21 @@ public function hasAttributes() * @return bool is attribute available */ public function hasAttribute($key) + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_hasAttribute($key); + } + + /** + * Check whether a specific attribute with a name is available + * + * @param string $key name of attribute + * + * @return bool is attribute available + */ + private function _hasAttribute($key) { return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); @@ -1045,7 +1263,10 @@ public function hasAttribute($key) */ public function getAttribute($key) { - if ($this->hasAttribute($key)) { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + if ($this->_hasAttribute($key)) { return $this->_attributes[$key]; } } @@ -1055,7 +1276,7 @@ public function getAttribute($key) * If the user is authenticated, renew the connection * If not, redirect to CAS * - * @return void + * @return true when the user is authenticated; otherwise halt. */ public function renewAuthentication() { @@ -1064,13 +1285,16 @@ public function renewAuthentication() if (isset( $_SESSION['phpCAS']['auth_checked'])) { unset($_SESSION['phpCAS']['auth_checked']); } - if ( $this->isAuthenticated() ) { - phpCAS::trace('user already authenticated; renew'); - $this->redirectToCas(false, true); + if ( $this->isAuthenticated(true) ) { + phpCAS::trace('user already authenticated'); + $res = true; } else { - $this->redirectToCas(); + $this->redirectToCas(false, true); + // never reached + $res = false; } phpCAS::traceEnd(); + return $res; } /** @@ -1117,6 +1341,9 @@ public function forceAuthentication() */ public function setCacheTimesForAuthRecheck($n) { + if (gettype($n) != 'integer') + throw new CAS_TypeMismatchException($n, '$n', 'string'); + $this->_cache_times_for_auth_recheck = $n; } @@ -1162,7 +1389,9 @@ public function checkAuthentication() .$this->_cache_times_for_auth_recheck.')' ); } else { - phpCAS::trace('user is not authenticated (cached for until login pressed)'); + phpCAS::trace( + 'user is not authenticated (cached for until login pressed)' + ); } } else { $_SESSION['phpCAS']['unauth_count'] = 0; @@ -1181,10 +1410,12 @@ public function checkAuthentication() * This method is called to check if the user is authenticated (previously or by * tickets given in the URL). * + * @param bool $renew true to force the authentication with the CAS server + * * @return true when the user is authenticated. Also may redirect to the * same URL without the ticket. */ - public function isAuthenticated() + public function isAuthenticated($renew=false) { phpCAS::traceBegin(); $res = false; @@ -1192,48 +1423,74 @@ public function isAuthenticated() if ( $this->_wasPreviouslyAuthenticated() ) { if ($this->hasTicket()) { // User has a additional ticket but was already authenticated - phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()'); + phpCAS::trace( + 'ticket was present and will be discarded, use renewAuthenticate()' + ); if ($this->_clearTicketsFromUrl) { phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); header('Location: '.$this->getURL()); flush(); phpCAS::traceExit(); throw new CAS_GracefullTerminationException(); } else { - phpCAS::trace('Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.'); + phpCAS::trace( + 'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.' + ); $res = true; } } 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'); + phpCAS::trace( + 'user was already authenticated, no need to look for tickets' + ); $res = true; } + + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); } else { if ($this->hasTicket()) { switch ($this->getServerVersion()) { case CAS_VERSION_1_0: // if a Service Ticket was given, validate it - phpCAS::trace('CAS 1.0 ticket `'.$this->getTicket().'\' is present'); - $this->validateCAS10($validate_url, $text_response, $tree_response); // if it fails, it halts - phpCAS::trace('CAS 1.0 ticket `'.$this->getTicket().'\' was validated'); - $_SESSION['phpCAS']['user'] = $this->getUser(); + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS10( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' was validated' + ); + $_SESSION['phpCAS']['user'] = $this->_getUser(); $res = true; $logoutTicket = $this->getTicket(); break; case CAS_VERSION_2_0: + case CAS_VERSION_3_0: // if a Proxy Ticket was given, validate it - phpCAS::trace('CAS 2.0 ticket `'.$this->getTicket().'\' is present'); - $this->validateCAS20($validate_url, $text_response, $tree_response); // note: if it fails, it halts - phpCAS::trace('CAS 2.0 ticket `'.$this->getTicket().'\' was validated'); + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS20( + $validate_url, $text_response, $tree_response, $renew + ); // note: if it fails, it halts + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated' + ); if ( $this->isProxy() ) { - $this->_validatePGT($validate_url, $text_response, $tree_response); // idem + $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(); - if ($this->hasAttributes()) { - $_SESSION['phpCAS']['attributes'] = $this->getAttributes(); + $_SESSION['phpCAS']['user'] = $this->_getUser(); + if (!empty($this->_attributes)) { + $_SESSION['phpCAS']['attributes'] = $this->_attributes; } $proxies = $this->getProxies(); if (!empty($proxies)) { @@ -1244,11 +1501,17 @@ public function isAuthenticated() break; case SAML_VERSION_1_1: // if we have a SAML ticket, validate it. - phpCAS::trace('SAML 1.1 ticket `'.$this->getTicket().'\' is present'); - $this->validateSA($validate_url, $text_response, $tree_response); // if it fails, it halts - phpCAS::trace('SAML 1.1 ticket `'.$this->getTicket().'\' was validated'); - $_SESSION['phpCAS']['user'] = $this->getUser(); - $_SESSION['phpCAS']['attributes'] = $this->getAttributes(); + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateSA( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' was validated' + ); + $_SESSION['phpCAS']['user'] = $this->_getUser(); + $_SESSION['phpCAS']['attributes'] = $this->_attributes; $res = true; $logoutTicket = $this->getTicket(); break; @@ -1260,16 +1523,19 @@ public function isAuthenticated() // no ticket given, not authenticated phpCAS::trace('no ticket found'); } - if ($res) { - // Mark the auth-check as complete to allow post-authentication - // callbacks to make use of phpCAS::getUser() and similar methods - $this->markAuthenticationCall($res); + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); + + if ($res) { // call the post-authenticate callback if registered. if ($this->_postAuthenticateCallbackFunction) { $args = $this->_postAuthenticateCallbackArgs; array_unshift($args, $logoutTicket); - call_user_func_array($this->_postAuthenticateCallbackFunction, $args); + call_user_func_array( + $this->_postAuthenticateCallbackFunction, $args + ); } // if called with a ticket parameter, we need to redirect to the @@ -1280,6 +1546,7 @@ public function isAuthenticated() // security precaution to prevent a ticket in the HTTP_REFERRER if ($this->_clearTicketsFromUrl) { phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); header('Location: '.$this->getURL()); flush(); phpCAS::traceExit(); @@ -1287,7 +1554,6 @@ public function isAuthenticated() } } } - phpCAS::traceEnd($res); return $res; } @@ -1326,31 +1592,49 @@ private function _wasPreviouslyAuthenticated() if ( $this->isProxy() ) { // CAS proxy: username and PGT must be present - if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) { + if ( $this->isSessionAuthenticated() + && !empty($_SESSION['phpCAS']['pgt']) + ) { // authentication already done $this->_setUser($_SESSION['phpCAS']['user']); if (isset($_SESSION['phpCAS']['attributes'])) { $this->setAttributes($_SESSION['phpCAS']['attributes']); } $this->_setPGT($_SESSION['phpCAS']['pgt']); - phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\''); + phpCAS::trace( + 'user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `' + .$_SESSION['phpCAS']['pgt'].'\'' + ); // Include the list of proxies if (isset($_SESSION['phpCAS']['proxies'])) { $this->_setProxies($_SESSION['phpCAS']['proxies']); - phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"'); + phpCAS::trace( + 'proxies = "' + .implode('", "', $_SESSION['phpCAS']['proxies']).'"' + ); } $auth = true; - } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) { + } elseif ( $this->isSessionAuthenticated() + && empty($_SESSION['phpCAS']['pgt']) + ) { // these two variables should be empty or not empty at the same time - phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty'); + phpCAS::trace( + 'username found (`'.$_SESSION['phpCAS']['user'] + .'\') but PGT is empty' + ); // unset all tickets to enforce authentication unset($_SESSION['phpCAS']); $this->setTicket(''); - } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) { + } elseif ( !$this->isSessionAuthenticated() + && !empty($_SESSION['phpCAS']['pgt']) + ) { // these two variables should be empty or not empty at the same time - phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty'); + phpCAS::trace( + 'PGT found (`'.$_SESSION['phpCAS']['pgt'] + .'\') but username is empty' + ); // unset all tickets to enforce authentication unset($_SESSION['phpCAS']); $this->setTicket(''); @@ -1370,7 +1654,10 @@ private function _wasPreviouslyAuthenticated() // Include the list of proxies if (isset($_SESSION['phpCAS']['proxies'])) { $this->_setProxies($_SESSION['phpCAS']['proxies']); - phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"'); + phpCAS::trace( + 'proxies = "' + .implode('", "', $_SESSION['phpCAS']['proxies']).'"' + ); } $auth = true; @@ -1397,6 +1684,7 @@ public function redirectToCas($gateway=false,$renew=false) { phpCAS::traceBegin(); $cas_url = $this->getServerLoginURL($gateway, $renew); + session_write_close(); if (php_sapi_name() === 'cli') { @header('Location: '.$cas_url); } else { @@ -1426,17 +1714,26 @@ public function logout($params) $cas_url = $this->getServerLogoutURL(); $paramSeparator = '?'; if (isset($params['url'])) { - $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']); + $cas_url = $cas_url . $paramSeparator . "url=" + . urlencode($params['url']); $paramSeparator = '&'; } if (isset($params['service'])) { - $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']); + $cas_url = $cas_url . $paramSeparator . "service=" + . urlencode($params['service']); } header('Location: '.$cas_url); phpCAS::trace("Prepare redirect to : ".$cas_url); + phpCAS::trace("Destroying session : ".session_id()); session_unset(); session_destroy(); + if (session_status() === PHP_SESSION_NONE) { + phpCAS::trace("Session terminated"); + } else { + phpCAS::error("Session was not terminated"); + phpCAS::trace("Session was not terminated"); + } $lang = $this->getLangObj(); $this->printHTMLHeader($lang->getLogout()); printf('

'.$lang->getShouldHaveBeenRedirected(). '

', $cas_url); @@ -1473,8 +1770,12 @@ public function handleLogoutRequests($check_client=true, $allowed_clients=false) phpCAS::traceEnd(); return; } - if (!$this->getChangeSessionID() && is_null($this->_signoutCallbackFunction)) { - phpCAS::trace("phpCAS can't handle logout requests if it is not allowed to change session_id."); + if (!$this->getChangeSessionID() + && is_null($this->_signoutCallbackFunction) + ) { + phpCAS::trace( + "phpCAS can't handle logout requests if it is not allowed to change session_id." + ); } phpCAS::trace("Logout requested"); $decoded_logout_rq = urldecode($_POST['logoutRequest']); @@ -1488,12 +1789,19 @@ public function handleLogoutRequests($check_client=true, $allowed_clients=false) $client = gethostbyaddr($client_ip); phpCAS::trace("Client: ".$client."/".$client_ip); foreach ($allowed_clients as $allowed_client) { - if (($client == $allowed_client) or ($client_ip == $allowed_client)) { - phpCAS::trace("Allowed client '".$allowed_client."' matches, logout request is allowed"); + if (($client == $allowed_client) + || ($client_ip == $allowed_client) + ) { + phpCAS::trace( + "Allowed client '".$allowed_client + ."' matches, logout request is allowed" + ); $allowed = true; break; } else { - phpCAS::trace("Allowed client '".$allowed_client."' does not match"); + phpCAS::trace( + "Allowed client '".$allowed_client."' does not match" + ); } } } else { @@ -1508,9 +1816,16 @@ public function handleLogoutRequests($check_client=true, $allowed_clients=false) $this->_rebroadcast(self::LOGOUT); } // Extract the ticket from the SAML Request - preg_match("|(.*)|", $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3); - $wrappedSamlSessionIndex = preg_replace('||', '', $tick[0][0]); - $ticket2logout = preg_replace('||', '', $wrappedSamlSessionIndex); + preg_match( + "|(.*)|", + $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3 + ); + $wrappedSamlSessionIndex = preg_replace( + '||', '', $tick[0][0] + ); + $ticket2logout = preg_replace( + '||', '', $wrappedSamlSessionIndex + ); phpCAS::trace("Ticket to logout: ".$ticket2logout); // call the post-authenticate callback if registered. @@ -1520,7 +1835,8 @@ public function handleLogoutRequests($check_client=true, $allowed_clients=false) call_user_func_array($this->_signoutCallbackFunction, $args); } - // If phpCAS is managing the session_id, destroy session thanks to session_id. + // If phpCAS is managing the session_id, destroy session thanks to + // session_id. if ($this->getChangeSessionID()) { $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket2logout); phpCAS::trace("Session id: ".$session_id); @@ -1626,11 +1942,16 @@ public function hasTicket() private $_cas_server_ca_cert = null; - /** - * validate CN of the CAS server certificate - * - * @hideinitializer - */ + /** + + * validate CN of the CAS server certificate + + * + + * @hideinitializer + + */ + private $_cas_server_cn_validate = true; /** @@ -1652,6 +1973,16 @@ public function hasTicket() */ public function setCasServerCACert($cert, $validate_cn) { + // Argument validation + if (gettype($cert) != 'string') { + throw new CAS_TypeMismatchException($cert, '$cert', 'string'); + } + if (gettype($validate_cn) != 'boolean') { + throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean'); + } + if ( !file_exists($cert) && $this->_requestImplementation !== 'CAS_TestHarness_DummyRequest'){ + throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation); + } $this->_cas_server_ca_cert = $cert; $this->_cas_server_cn_validate = $validate_cn; } @@ -1676,20 +2007,29 @@ public function setNoCasServerValidation() * server, as is (XML text). * @param string &$tree_response reference to the response of the CAS * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server * * @return bool true when successfull and issue a CAS_AuthenticationException * and false on an error */ - public function validateCAS10(&$validate_url,&$text_response,&$tree_response) + public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false) { phpCAS::traceBegin(); $result = false; // build the URL to validate the ticket - $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getTicket(); + $validate_url = $this->getServerServiceValidateURL() + .'&ticket='.urlencode($this->getTicket()); + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } // open and read the URL if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { - phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'); + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); throw new CAS_AuthenticationException( $this, 'CAS 1.0 ticket not validated', $validate_url, true/*$no_response*/ @@ -1747,21 +2087,31 @@ public function validateCAS10(&$validate_url,&$text_response,&$tree_response) * server, as is (XML text). * @param string &$tree_response reference to the response of the CAS * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server * * @return bool true when successfull and issue a CAS_AuthenticationException * and false on an error */ - public function validateSA(&$validate_url,&$text_response,&$tree_response) + public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false) { phpCAS::traceBegin(); $result = false; // build the URL to validate the ticket $validate_url = $this->getServerSamlValidateURL(); + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + // open and read the URL if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { - phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'); - throw new CAS_AuthenticationException($this, 'SA not validated', $validate_url, true/*$no_response*/); + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, true/*$no_response*/ + ); } phpCAS::trace('server version: '.$this->getServerVersion()); @@ -1794,7 +2144,10 @@ public function validateSA(&$validate_url,&$text_response,&$tree_response) $result = false; } else if ( $tree_response->localName != 'Envelope' ) { // insure that tag name is 'Envelope' - phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->localName.'\''); + phpCAS::trace( + 'bad XML root node (should be `Envelope\' instead of `' + .$tree_response->localName.'\'' + ); throw new CAS_AuthenticationException( $this, 'SA not validated', $validate_url, false/*$no_response*/, true/*$bad_response*/, @@ -1868,7 +2221,7 @@ private function _setSessionAttributes($text_response) 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); + phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true)); } else { $this->_attributes[$attr_key] = $attr_value[0]; phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]); @@ -1921,6 +2274,7 @@ public function isProxy() return $this->_proxy; } + /** @} */ // ######################################################################## // PGT @@ -2040,11 +2394,11 @@ private function _getCallbackURL() $final_uri = ''; // remove the ticket if present in the URL $final_uri = 'https://'; - $final_uri .= $this->_getServerUrl(); + $final_uri .= $this->_getClientUrl(); $request_uri = $_SERVER['REQUEST_URI']; $request_uri = preg_replace('/\?.*$/', '', $request_uri); $final_uri .= $request_uri; - $this->setCallbackURL($final_uri); + $this->_callback_url = $final_uri; } return $this->_callback_url; } @@ -2058,6 +2412,12 @@ private function _getCallbackURL() */ public function setCallbackURL($url) { + // Sequence validation + $this->ensureIsProxy(); + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + return $this->_callback_url = $url; } @@ -2175,15 +2535,17 @@ private function _loadPGT($pgt_iou) */ public function setPGTStorage($storage) { + // Sequence validation + $this->ensureIsProxy(); + // check that the storage has not already been set if ( is_object($this->_pgt_storage) ) { phpCAS::error('PGT storage already defined'); } // check to make sure a valid storage object was specified - if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) { - phpCAS::error('Invalid PGT storage object'); - } + if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) + throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object'); // store the PGTStorage object $this->_pgt_storage = $storage; @@ -2206,10 +2568,28 @@ public function setPGTStorage($storage) * * @return void */ - public function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table='', $driver_options=null) - { + public function setPGTStorageDb( + $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null + ) { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if ((is_object($dsn_or_pdo) && !($dsn_or_pdo instanceof PDO)) || gettype($dsn_or_pdo) != 'string') + throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object'); + if (gettype($username) != 'string') + throw new CAS_TypeMismatchException($username, '$username', 'string'); + if (gettype($password) != 'string') + throw new CAS_TypeMismatchException($password, '$password', 'string'); + if (gettype($table) != 'string') + throw new CAS_TypeMismatchException($table, '$password', 'string'); + // create the storage object - $this->setPGTStorage(new CAS_PGTStorage_Db($this, $dsn_or_pdo, $username, $password, $table, $driver_options)); + $this->setPGTStorage( + new CAS_PGTStorage_Db( + $this, $dsn_or_pdo, $username, $password, $table, $driver_options + ) + ); } /** @@ -2222,6 +2602,13 @@ public function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table= */ public function setPGTStorageFile($path='') { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if (gettype($path) != 'string') + throw new CAS_TypeMismatchException($path, '$path', 'string'); + // create the storage object $this->setPGTStorage(new CAS_PGTStorage_File($this, $path)); } @@ -2235,7 +2622,9 @@ public function setPGTStorageFile($path='') * * @param string &$validate_url the URL of the request to the CAS server. * @param string $text_response the response of the CAS server, as is - * (XML text); result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20(). + * (XML text); result of + * CAS_Client::validateCAS10() or + * CAS_Client::validateCAS20(). * @param string $tree_response the response of the CAS server, as a DOM XML * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20(). * @@ -2255,13 +2644,16 @@ private function _validatePGT(&$validate_url,$text_response,$tree_response) ); } else { // PGT Iou transmitted, extract it - $pgt_iou = trim($tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue); + $pgt_iou = trim( + $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue + ); if (preg_match('/PGTIOU-[\.\-\w]/', $pgt_iou)) { $pgt = $this->_loadPGT($pgt_iou); if ( $pgt == false ) { phpCAS::trace('could not load PGT'); throw new CAS_AuthenticationException( - $this, 'PGT Iou was transmitted but PGT could not be retrieved', + $this, + 'PGT Iou was transmitted but PGT could not be retrieved', $validate_url, false/*$no_response*/, false/*$bad_response*/, $text_response ); @@ -2295,6 +2687,10 @@ private function _validatePGT(&$validate_url,$text_response,$tree_response) */ public function retrievePT($target_service,&$err_code,&$err_msg) { + // Argument validation + if (gettype($target_service) != 'string') + throw new CAS_TypeMismatchException($target_service, '$target_service', 'string'); + phpCAS::traceBegin(); // by default, $err_msg is set empty and $pt to true. On error, $pt is @@ -2304,11 +2700,14 @@ public function retrievePT($target_service,&$err_code,&$err_msg) $err_msg = ''; // build the URL to retrieve the PT - $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->_getPGT(); + $cas_url = $this->getServerProxyURL().'?targetService=' + .urlencode($target_service).'&pgt='.$this->_getPGT(); // open and read the URL if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) { - phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')'); + phpCAS::trace( + 'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')' + ); $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE; $err_msg = 'could not retrieve PT (no response from the CAS server)'; phpCAS::traceEnd(false); @@ -2357,7 +2756,9 @@ public function retrievePT($target_service,&$err_code,&$err_msg) if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) { $err_code = PHPCAS_SERVICE_OK; $err_msg = ''; - $pt = trim($proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue); + $pt = trim( + $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue + ); phpCAS::trace('original PT: '.trim($pt)); phpCAS::traceEnd($pt); return $pt; @@ -2385,7 +2786,8 @@ public function retrievePT($target_service,&$err_code,&$err_msg) // at this step, we are sure that the response of the CAS server was // illformed $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE; - $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')'; + $err_msg = 'Invalid response from the CAS server (response=`' + .$cas_response.'\')'; phpCAS::traceEnd(false); return false; @@ -2428,10 +2830,14 @@ private function _readURL($url, &$headers, &$body, &$err_msg) $request->setUrl($url); if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) { - phpCAS::error('one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'); + phpCAS::error( + 'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.' + ); } if ($this->_cas_server_ca_cert != '') { - $request->setSslCaCert($this->_cas_server_ca_cert, $this->_cas_server_cn_validate); + $request->setSslCaCert( + $this->_cas_server_ca_cert, $this->_cas_server_cn_validate + ); } // add extra stuff if SAML @@ -2471,9 +2877,11 @@ private function _buildSAMLPayload() phpCAS::traceBegin(); //get the ticket - $sa = $this->getTicket(); + $sa = urlencode($this->getTicket()); - $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; + $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); @@ -2503,6 +2911,14 @@ private function _buildSAMLPayload() */ public function getProxiedService ($type) { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($type) != 'string') + throw new CAS_TypeMismatchException($type, '$type', 'string'); + switch ($type) { case PHPCAS_PROXIED_SERVICE_HTTP_GET: case PHPCAS_PROXIED_SERVICE_HTTP_POST: @@ -2517,13 +2933,15 @@ public function getProxiedService ($type) } return $proxiedService; case PHPCAS_PROXIED_SERVICE_IMAP; - $proxiedService = new CAS_ProxiedService_Imap($this->getUser()); + $proxiedService = new CAS_ProxiedService_Imap($this->_getUser()); if ($proxiedService instanceof CAS_ProxiedService_Testable) { $proxiedService->setCasClient($this); } return $proxiedService; default: - throw new CAS_InvalidArgumentException("Unknown proxied-service type, $type."); + throw new CAS_InvalidArgumentException( + "Unknown proxied-service type, $type." + ); } } @@ -2544,9 +2962,17 @@ public function getProxiedService ($type) */ public function initializeProxiedService (CAS_ProxiedService $proxiedService) { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + $url = $proxiedService->getServiceUrl(); if (!is_string($url)) { - throw new CAS_ProxiedService_Exception("Proxied Service ".get_class($proxiedService)."->getServiceUrl() should have returned a string, returned a ".gettype($url)." instead."); + throw new CAS_ProxiedService_Exception( + "Proxied Service ".get_class($proxiedService) + ."->getServiceUrl() should have returned a string, returned a " + .gettype($url)." instead." + ); } $pt = $this->retrievePT($url, $err_code, $err_msg); if (!$pt) { @@ -2571,6 +2997,14 @@ public function initializeProxiedService (CAS_ProxiedService $proxiedService) */ public function serviceWeb($url,&$err_code,&$output) { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + try { $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET); $service->setUrl($url); @@ -2584,7 +3018,9 @@ public function serviceWeb($url,&$err_code,&$output) return false; } catch (CAS_ProxiedService_Exception $e) { $lang = $this->getLangObj(); - $output = sprintf($lang->getServiceUnavailable(), $url, $e->getMessage()); + $output = sprintf( + $lang->getServiceUnavailable(), $url, $e->getMessage() + ); $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; return false; } @@ -2611,6 +3047,18 @@ public function serviceWeb($url,&$err_code,&$output) */ public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt) { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + if (gettype($serviceUrl) != 'string') + throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string'); + if (gettype($flags) != 'integer') + throw new CAS_TypeMismatchException($flags, '$flags', 'string'); + try { $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP); $service->setServiceUrl($serviceUrl); @@ -2740,20 +3188,23 @@ public function getAllowedProxyChains () * @param string &$validate_url the url of the reponse * @param string &$text_response the text of the repsones * @param string &$tree_response the domxml tree of the respones + * @param bool $renew true to force the authentication with the CAS server * * @return bool true when successfull and issue a CAS_AuthenticationException * and false on an error */ - public function validateCAS20(&$validate_url,&$text_response,&$tree_response) + public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false) { phpCAS::traceBegin(); phpCAS::trace($text_response); $result = false; // build the URL to validate the ticket if ($this->getAllowedProxyChains()->isProxyingAllowed()) { - $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getTicket(); + $validate_url = $this->getServerProxyValidateURL().'&ticket=' + .urlencode($this->getTicket()); } else { - $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getTicket(); + $validate_url = $this->getServerServiceValidateURL().'&ticket=' + .urlencode($this->getTicket()); } if ( $this->isProxy() ) { @@ -2761,9 +3212,16 @@ public function validateCAS20(&$validate_url,&$text_response,&$tree_response) $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL()); } + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + // open and read the URL if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { - phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'); + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); throw new CAS_AuthenticationException( $this, 'Ticket not validated', $validate_url, true/*$no_response*/ @@ -2801,9 +3259,22 @@ public function validateCAS20(&$validate_url,&$text_response,&$tree_response) false/*$no_response*/, true/*$bad_response*/, $text_response ); $result = false; + } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) { + // authentication failed, extract the error code and message and throw exception + $auth_fail_list = $tree_response + ->getElementsByTagName("authenticationFailure"); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, false/*$bad_response*/, + $text_response, + $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/, + trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/ + ); + $result = false; } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) { // authentication succeded, extract the user name - $success_elements = $tree_response->getElementsByTagName("authenticationSuccess"); + $success_elements = $tree_response + ->getElementsByTagName("authenticationSuccess"); if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) { // no user specified => error throw new CAS_AuthenticationException( @@ -2812,7 +3283,11 @@ public function validateCAS20(&$validate_url,&$text_response,&$tree_response) ); $result = false; } else { - $this->_setUser(trim($success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue)); + $this->_setUser( + trim( + $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue + ) + ); $this->_readExtraAttributesCas20($success_elements); // Store the proxies we are sitting behind for authorization checking $proxyList = array(); @@ -2836,17 +3311,6 @@ public function validateCAS20(&$validate_url,&$text_response,&$tree_response) $result = true; } } - } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) { - // authentication succeded, extract the error code and message - $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure"); - throw new CAS_AuthenticationException( - $this, 'Ticket not validated', $validate_url, - false/*$no_response*/, false/*$bad_response*/, - $text_response, - $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/, - trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/ - ); - $result = false; } else { throw new CAS_AuthenticationException( $this, 'Ticket not validated', $validate_url, @@ -2896,14 +3360,30 @@ private function _readExtraAttributesCas20($success_elements) // // // - if ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) { - $attr_nodes = $success_elements->item(0)->getElementsByTagName("attributes"); + if ($this->_casAttributeParserCallbackFunction !== null + && is_callable($this->_casAttributeParserCallbackFunction) + ) { + array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0)); + phpCas :: trace("Calling attritubeParser callback"); + $extra_attributes = call_user_func_array( + $this->_casAttributeParserCallbackFunction, + $this->_casAttributeParserCallbackArgs + ); + } elseif ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) { + $attr_nodes = $success_elements->item(0) + ->getElementsByTagName("attributes"); phpCas :: trace("Found nested jasig style attributes"); if ($attr_nodes->item(0)->hasChildNodes()) { // Nested Attributes foreach ($attr_nodes->item(0)->childNodes as $attr_child) { - phpCas :: trace("Attribute [".$attr_child->localName."] = ".$attr_child->nodeValue); - $this->_addAttributeToArray($extra_attributes, $attr_child->localName, $attr_child->nodeValue); + phpCas :: trace( + "Attribute [".$attr_child->localName."] = " + .$attr_child->nodeValue + ); + $this->_addAttributeToArray( + $extra_attributes, $attr_child->localName, + $attr_child->nodeValue + ); } } } else { @@ -2933,8 +3413,13 @@ private function _readExtraAttributesCas20($success_elements) continue; default: if (strlen(trim($attr_node->nodeValue))) { - phpCas :: trace("Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue); - $this->_addAttributeToArray($extra_attributes, $attr_node->localName, $attr_node->nodeValue); + phpCas :: trace( + "Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue + ); + $this->_addAttributeToArray( + $extra_attributes, $attr_node->localName, + $attr_node->nodeValue + ); } } } @@ -2960,16 +3445,30 @@ private function _readExtraAttributesCas20($success_elements) // // // - if (!count($extra_attributes) && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0) { - $attr_nodes = $success_elements->item(0)->getElementsByTagName("attribute"); + if (!count($extra_attributes) + && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0 + ) { + $attr_nodes = $success_elements->item(0) + ->getElementsByTagName("attribute"); $firstAttr = $attr_nodes->item(0); - if (!$firstAttr->hasChildNodes() && $firstAttr->hasAttribute('name') && $firstAttr->hasAttribute('value')) { + if (!$firstAttr->hasChildNodes() + && $firstAttr->hasAttribute('name') + && $firstAttr->hasAttribute('value') + ) { phpCas :: trace("Found Name-Value style attributes"); // Nested Attributes foreach ($attr_nodes as $attr_node) { - if ($attr_node->hasAttribute('name') && $attr_node->hasAttribute('value')) { - phpCas :: trace("Attribute [".$attr_node->getAttribute('name')."] = ".$attr_node->getAttribute('value')); - $this->_addAttributeToArray($extra_attributes, $attr_node->getAttribute('name'), $attr_node->getAttribute('value')); + if ($attr_node->hasAttribute('name') + && $attr_node->hasAttribute('value') + ) { + phpCas :: trace( + "Attribute [".$attr_node->getAttribute('name') + ."] = ".$attr_node->getAttribute('value') + ); + $this->_addAttributeToArray( + $extra_attributes, $attr_node->getAttribute('name'), + $attr_node->getAttribute('value') + ); } } } @@ -3039,6 +3538,10 @@ private function _addAttributeToArray(array &$attributeArray, $name, $value) */ public function setURL($url) { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + $this->_url = $url; } @@ -3054,24 +3557,19 @@ public function getURL() // the URL is built when needed only if ( empty($this->_url) ) { $final_uri = ''; - - if ($this->_server_base_url != null) { - $final_uri .= $this->_server_base_url; - } else { - $final_uri = ($this->_isHttps()) ? 'https' : 'http'; - $final_uri .= '://'; - - $final_uri .= $this->_getServerUrl(); - } - // remove the ticket if present in the URL + $final_uri = ($this->_isHttps()) ? 'https' : 'http'; + $final_uri .= '://'; + + $final_uri .= $this->_getClientUrl(); $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 the query string still has anything left, + // append it to the final URI if ($query_string !== '') { $final_uri .= "?$query_string"; } @@ -3084,19 +3582,36 @@ public function getURL() return $this->_url; } + /** + * This method sets the base URL of the CAS server. + * + * @param string $url the base URL + * + * @return string base url + */ + public function setBaseURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['base_url'] = $url; + } + /** - * Try to figure out the server URL with possible Proxys / Ports etc. + * Try to figure out the phpCas client URL with possible Proxys / Ports etc. * * @return string Server URL with domain:port */ - private function _getServerUrl() + private function _getClientUrl() { $server_url = ''; if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { // explode the host list separated by comma and use the first host $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); - $server_url = $hosts[0]; + // see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default + return $hosts[0]; } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) { $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER']; } else { @@ -3110,7 +3625,8 @@ private function _getServerUrl() if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { $server_port = $_SERVER['SERVER_PORT']; } else { - $server_port = $_SERVER['HTTP_X_FORWARDED_PORT']; + $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']); + $server_port = $ports[0]; } if ( ($this->_isHttps() && $server_port!=443) @@ -3130,11 +3646,18 @@ private function _getServerUrl() */ private function _isHttps() { - if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'); + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https'); + } elseif ( isset($_SERVER['HTTPS']) + && !empty($_SERVER['HTTPS']) + && strcasecmp($_SERVER['HTTPS'], 'off') !== 0 + ) { return true; - } else { - return false; } + return false; + } /** @@ -3150,7 +3673,10 @@ private function _isHttps() private function _removeParameterFromQueryString($parameterName, $queryString) { $parameterName = preg_quote($parameterName); - return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString); + return preg_replace( + "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", + '', $queryString + ); } /** @@ -3183,19 +3709,24 @@ private function _renameSession($ticket) if ($this->getChangeSessionID()) { if (!empty($this->_user)) { $old_session = $_SESSION; + phpCAS :: trace("Killing session: ". session_id()); session_destroy(); // set up a new session, of name based on the ticket $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket); - phpCAS :: trace("Session ID: ".$session_id); + phpCAS :: trace("Starting session: ". $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'); + phpCAS :: trace ( + 'Session should only be renamed after successfull authentication' + ); } } else { - phpCAS :: trace("Skipping session rename since phpCAS is not handling the session."); + phpCAS :: trace( + "Skipping session rename since phpCAS is not handling the session." + ); } phpCAS::traceEnd(); } @@ -3232,7 +3763,10 @@ private function _authError( phpCAS::traceBegin(); $lang = $this->getLangObj(); $this->printHTMLHeader($lang->getAuthenticationFailed()); - printf($lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()), $_SERVER['SERVER_ADMIN']); + printf( + $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()), + isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:'' + ); phpCAS::trace('CAS URL: '.$cas_url); phpCAS::trace('Authentication failure: '.$failure); if ( $no_response ) { @@ -3246,10 +3780,13 @@ private function _authError( phpCAS::trace('Reason: CAS error'); break; case CAS_VERSION_2_0: + case CAS_VERSION_3_0: if ( empty($err_code) ) { phpCAS::trace('Reason: no CAS error'); } else { - phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg); + phpCAS::trace( + 'Reason: ['.$err_code.'] CAS error: '.$err_msg + ); } break; } @@ -3307,6 +3844,10 @@ private function _getNodeType($nodeURL) */ public function addRebroadcastNode($rebroadcastNodeUrl) { + // Argument validation + if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl)) + throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url'); + // Store the rebroadcast node and set flag $this->_rebroadcast = true; $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl; @@ -3327,6 +3868,9 @@ public function addRebroadcastNode($rebroadcastNodeUrl) */ public function addRebroadcastHeader($header) { + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + $this->_rebroadcast_headers[] = $header; } @@ -3369,8 +3913,13 @@ private function _rebroadcast($type) $multiRequest = new $multiClassName(); for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) { - if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false)) || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))) { - phpCAS::trace('Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI']); + if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false)) + || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false)) + ) { + phpCAS::trace( + 'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i] + .$_SERVER['REQUEST_URI'] + ); $className = $this->_requestImplementation; $request = new $className(); @@ -3384,7 +3933,9 @@ private function _rebroadcast($type) $request->makePost(); if ($type == self::LOGOUT) { // Logout request - $request->setPostBody('rebroadcast=false&logoutRequest='.$_POST['logoutRequest']); + $request->setPostBody( + 'rebroadcast=false&logoutRequest='.$_POST['logoutRequest'] + ); } else if ($type == self::PGTIOU) { // pgtIou/pgtId rebroadcast $request->setPostBody('rebroadcast=false'); @@ -3394,7 +3945,11 @@ private function _rebroadcast($type) $multiRequest->addRequest($request); } else { - phpCAS::trace('Rebroadcast not sent to self: '.$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'').'/'.(!empty($dns)?$dns:'')); + phpCAS::trace( + 'Rebroadcast not sent to self: ' + .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'') + .'/'.(!empty($dns)?$dns:'') + ); } } // We need at least 1 request diff --git a/lib/CAS/OutOfSequenceBeforeAuthenticationCallException.php b/lib/CAS/OutOfSequenceBeforeAuthenticationCallException.php new file mode 100644 index 0000000000..ef83097958 --- /dev/null +++ b/lib/CAS/OutOfSequenceBeforeAuthenticationCallException.php @@ -0,0 +1,56 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when an + * authentication call has not yet happened. + * + * @class CAS_OutOfSequenceBeforeAuthenticationCallException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeAuthenticationCallException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + /** + * Return standard error meessage + * + * @return void + */ + public function __construct () + { + parent::__construct('An authentication call hasn\'t happened yet.'); + } +} diff --git a/lib/CAS/OutOfSequenceBeforeClientException.php b/lib/CAS/OutOfSequenceBeforeClientException.php new file mode 100644 index 0000000000..f1ea7e2447 --- /dev/null +++ b/lib/CAS/OutOfSequenceBeforeClientException.php @@ -0,0 +1,58 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when the client() or + * proxy() call has not yet happened and no client or proxy object exists. + * + * @class CAS_OutOfSequenceBeforeClientException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeClientException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + /** + * Return standard error message + * + * @return void + */ + public function __construct () + { + parent::__construct( + 'this method cannot be called before phpCAS::client() or phpCAS::proxy()' + ); + } +} diff --git a/lib/CAS/OutOfSequenceBeforeProxyException.php b/lib/CAS/OutOfSequenceBeforeProxyException.php new file mode 100644 index 0000000000..8038542ed1 --- /dev/null +++ b/lib/CAS/OutOfSequenceBeforeProxyException.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when the proxy() call + * has not yet happened and no proxy object exists. + * + * @class CAS_OutOfSequenceBeforeProxyException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeProxyException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + + /** + * Return standard error message + * + * @return void + */ + public function __construct () + { + parent::__construct( + 'this method cannot be called before phpCAS::proxy()' + ); + } +} diff --git a/lib/CAS/PGTStorage/File.php b/lib/CAS/PGTStorage/File.php index 80a1ea1fd7..d3bcf80948 100755 --- a/lib/CAS/PGTStorage/File.php +++ b/lib/CAS/PGTStorage/File.php @@ -180,8 +180,10 @@ function init() function getPGTIouFilename($pgt_iou) { phpCAS::traceBegin(); - $filename = $this->getPath().$pgt_iou.'.plain'; - phpCAS::traceEnd($filename); + $filename = $this->getPath()."phpcas-".hash("sha256", $pgt_iou); +// $filename = $this->getPath().$pgt_iou.'.plain'; + phpCAS::trace("Sha256 filename:" . $filename); + phpCAS::traceEnd(); return $filename; } diff --git a/lib/CAS/ProxiedService/Imap.php b/lib/CAS/ProxiedService/Imap.php index d240d94dd4..847da28c10 100755 --- a/lib/CAS/ProxiedService/Imap.php +++ b/lib/CAS/ProxiedService/Imap.php @@ -79,7 +79,9 @@ public function __construct ($username) public function getServiceUrl () { if (empty($this->_url)) { - throw new CAS_ProxiedService_Exception('No URL set via '.get_class($this).'->getServiceUrl($url).'); + throw new CAS_ProxiedService_Exception( + 'No URL set via '.get_class($this).'->getServiceUrl($url).' + ); } return $this->_url; @@ -100,7 +102,9 @@ public function getServiceUrl () public function setServiceUrl ($url) { if ($this->hasBeenOpened()) { - throw new CAS_OutOfSequenceException('Cannot set the URL, stream already opened.'); + throw new CAS_OutOfSequenceException( + 'Cannot set the URL, stream already opened.' + ); } if (!is_string($url) || !strlen($url)) { throw new CAS_InvalidArgumentException('Invalid url.'); @@ -127,7 +131,9 @@ public function setServiceUrl ($url) public function setMailbox ($mailbox) { if ($this->hasBeenOpened()) { - throw new CAS_OutOfSequenceException('Cannot set the mailbox, stream already opened.'); + throw new CAS_OutOfSequenceException( + 'Cannot set the mailbox, stream already opened.' + ); } if (!is_string($mailbox) || !strlen($mailbox)) { throw new CAS_InvalidArgumentException('Invalid mailbox.'); @@ -155,7 +161,9 @@ public function setMailbox ($mailbox) public function setOptions ($options) { if ($this->hasBeenOpened()) { - throw new CAS_OutOfSequenceException('Cannot set options, stream already opened.'); + throw new CAS_OutOfSequenceException( + 'Cannot set options, stream already opened.' + ); } if (!is_int($options)) { throw new CAS_InvalidArgumentException('Invalid options.'); @@ -178,14 +186,19 @@ public function setOptions ($options) * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE * PHPCAS_SERVICE_PT_FAILURE - * @throws CAS_ProxiedService_Exception If there is a failure sending the request to the target service. */ + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ public function open () { if ($this->hasBeenOpened()) { throw new CAS_OutOfSequenceException('Stream already opened.'); } if (empty($this->_mailbox)) { - throw new CAS_ProxiedService_Exception('You must specify a mailbox via '.get_class($this).'->setMailbox($mailbox)'); + throw new CAS_ProxiedService_Exception( + 'You must specify a mailbox via '.get_class($this) + .'->setMailbox($mailbox)' + ); } phpCAS::traceBegin(); @@ -193,13 +206,16 @@ public function open () // Get our proxy ticket and append it to our URL. $this->initializeProxyTicket(); phpCAS::trace('opening IMAP mailbox `'.$this->_mailbox.'\'...'); - $this->_stream = @imap_open($this->_mailbox, $this->_username, $this->getProxyTicket(), $this->_options); + $this->_stream = @imap_open( + $this->_mailbox, $this->_username, $this->getProxyTicket(), + $this->_options + ); if ($this->_stream) { phpCAS::trace('ok'); } else { phpCAS::trace('could not open mailbox'); // @todo add localization integration. - $message = 'IMAP Error: '.$url.' '. var_export(imap_errors(), true); + $message = 'IMAP Error: '.$this->_url.' '. var_export(imap_errors(), true); phpCAS::trace($message); throw new CAS_ProxiedService_Exception($message); } @@ -236,7 +252,9 @@ protected function hasBeenOpened () public function getStream () { if (!$this->hasBeenOpened()) { - throw new CAS_OutOfSequenceException('Cannot access stream, not opened yet.'); + throw new CAS_OutOfSequenceException( + 'Cannot access stream, not opened yet.' + ); } return $this->_stream; } @@ -252,7 +270,9 @@ public function getStream () public function getImapProxyTicket () { if (!$this->hasBeenOpened()) { - throw new CAS_OutOfSequenceException('Cannot access errors, stream not opened yet.'); + throw new CAS_OutOfSequenceException( + 'Cannot access errors, stream not opened yet.' + ); } return $this->getProxyTicket(); } diff --git a/lib/CAS/Request/CurlMultiRequest.php b/lib/CAS/Request/CurlMultiRequest.php index a046989209..7099608539 100755 --- a/lib/CAS/Request/CurlMultiRequest.php +++ b/lib/CAS/Request/CurlMultiRequest.php @@ -64,10 +64,14 @@ class CAS_Request_CurlMultiRequest public function addRequest (CAS_Request_RequestInterface $request) { if ($this->_sent) { - throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__); + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); } if (!$request instanceof CAS_Request_CurlRequest) { - throw new CAS_InvalidArgumentException('As a CAS_Request_CurlMultiRequest, I can only work with CAS_Request_CurlRequest objects.'); + throw new CAS_InvalidArgumentException( + 'As a CAS_Request_CurlMultiRequest, I can only work with CAS_Request_CurlRequest objects.' + ); } $this->_requests[] = $request; @@ -81,7 +85,9 @@ public function addRequest (CAS_Request_RequestInterface $request) public function getNumRequests() { if ($this->_sent) { - throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__); + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); } return count($this->_requests); } @@ -100,10 +106,14 @@ public function getNumRequests() public function send () { if ($this->_sent) { - throw new CAS_OutOfSequenceException('Request has already been sent cannot send again.'); + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot send again.' + ); } if (!count($this->_requests)) { - throw new CAS_OutOfSequenceException('At least one request must be added via addRequest() before the multi-request can be sent.'); + throw new CAS_OutOfSequenceException( + 'At least one request must be added via addRequest() before the multi-request can be sent.' + ); } $this->_sent = true; @@ -112,7 +122,7 @@ public function send () $handles = array(); $multiHandle = curl_multi_init(); foreach ($this->_requests as $i => $request) { - $handle = $request->_initAndConfigure(); + $handle = $request->initAndConfigure(); curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); $handles[$i] = $handle; curl_multi_add_handle($multiHandle, $handle); diff --git a/lib/CAS/Request/CurlRequest.php b/lib/CAS/Request/CurlRequest.php index e20914f345..86d2492c5b 100755 --- a/lib/CAS/Request/CurlRequest.php +++ b/lib/CAS/Request/CurlRequest.php @@ -67,7 +67,7 @@ protected function sendRequest () /********************************************************* * initialize the CURL session *********************************************************/ - $ch = $this->_initAndConfigure(); + $ch = $this->initAndConfigure(); /********************************************************* * Perform the query @@ -75,7 +75,9 @@ protected function sendRequest () $buf = curl_exec($ch); if ( $buf === false ) { phpCAS::trace('curl_exec() failed'); - $this->storeErrorMessage('CURL error #'.curl_errno($ch).': '.curl_error($ch)); + $this->storeErrorMessage( + 'CURL error #'.curl_errno($ch).': '.curl_error($ch) + ); $res = false; } else { $this->storeResponseBody($buf); @@ -97,7 +99,7 @@ protected function sendRequest () * * @return resource The cURL handle on success, false on failure */ - private function _initAndConfigure() + public function initAndConfigure() { /********************************************************* * initialize the CURL session @@ -120,13 +122,14 @@ private function _initAndConfigure() if ($this->validateCN) { curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); } else { - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); } curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($ch, CURLOPT_CAINFO, $this->caCertPath); phpCAS::trace('CURL: Set CURLOPT_CAINFO ' . $this->caCertPath); } else { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); } /********************************************************* diff --git a/lib/CAS/TypeMismatchException.php b/lib/CAS/TypeMismatchException.php new file mode 100644 index 0000000000..4a13c2df45 --- /dev/null +++ b/lib/CAS/TypeMismatchException.php @@ -0,0 +1,70 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Exception that denotes invalid arguments were passed. + * + * @class CAS_InvalidArgumentException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_TypeMismatchException +extends CAS_InvalidArgumentException +{ + /** + * Constructor, provides a nice message. + * + * @param mixed $argument Argument + * @param string $argumentName Argument Name + * @param string $type Type + * @param string $message Error Message + * @param integer $code Code + * + * @return void + */ + public function __construct ( + $argument, $argumentName, $type, $message = '', $code = 0 + ) { + if (is_object($argument)) { + $foundType = get_class($argument).' object'; + } else { + $foundType = gettype($argument); + } + + parent::__construct( + 'type mismatched for parameter ' + . $argumentName . ' (should be \'' . $type .' \'), ' + . $foundType . ' given. ' . $message, $code + ); + } +} +?>