diff --git a/lib/SimpleSAML/Error/Error.php b/lib/SimpleSAML/Error/Error.php index a276d8f3059db3addc6224a66091d1d1298d6acd..fd99b80b2509427d8f3067bfc209d17cc1e18f94 100644 --- a/lib/SimpleSAML/Error/Error.php +++ b/lib/SimpleSAML/Error/Error.php @@ -1,297 +1,303 @@ <?php + /** - * Class which wraps simpleSAMLphp errors in exceptions. + * Class that wraps SimpleSAMLphp errors in exceptions. * * @author Olav Morken, UNINETT AS. - * @package simpleSAMLphp + * @package SimpleSAMLphp */ -class SimpleSAML_Error_Error extends SimpleSAML_Error_Exception { - - - /** - * The error code. - * - * @var string - */ - private $errorCode; - - - /** - * The http code. - * - * @var integer - */ - protected $httpCode = 500; - - - /** - * The error title tag in dictionary. - * - * @var string - */ - private $dictTitle; - - - /** - * The error description tag in dictionary. - * - * @var string - */ - private $dictDescr; - - - /** - * The name of module which throw error. - * - * @var string|NULL - */ - private $module = NULL; - - - /** - * The parameters for the error. - * - * @var array - */ - private $parameters; - - - /** - * Name of custom include template for the error. - * - * @var string|NULL - */ - protected $includeTemplate = NULL; - - - /** - * Constructor for this error. - * - * The error can either be given as a string, or as an array. If it is an array, the - * first element in the array (with index 0), is the error code, while the other elements - * are replacements for the error text. - * - * @param mixed $errorCode One of the error codes defined in the errors dictionary. - * @param Exception $cause The exception which caused this fatal error (if any). - */ - public function __construct($errorCode, Exception $cause = NULL, $httpCode = NULL) { - assert('is_string($errorCode) || is_array($errorCode)'); - - if (is_array($errorCode)) { - $this->parameters = $errorCode; - unset($this->parameters[0]); - $this->errorCode = $errorCode[0]; - } else { - $this->parameters = array(); - $this->errorCode = $errorCode; - } - - if (isset($httpCode)) { - $this->httpCode = $httpCode; - } - - $moduleCode = explode(':', $this->errorCode, 2); - if (count($moduleCode) === 2) { - $this->module = $moduleCode[0]; - $this->dictTitle = '{' . $this->module . ':errors:title_' . $moduleCode[1] . '}'; - $this->dictDescr = '{' . $this->module . ':errors:descr_' . $moduleCode[1] . '}'; - } else { - $this->dictTitle = '{errors:title_' . $this->errorCode . '}'; - $this->dictDescr = '{errors:descr_' . $this->errorCode . '}'; - } - - if (!empty($this->parameters)) { - $msg = $this->errorCode . '('; - foreach ($this->parameters as $k => $v) { - if ($k === 0) { - continue; - } - - $msg .= var_export($k, TRUE) . ' => ' . var_export($v, TRUE) . ', '; - } - $msg = substr($msg, 0, -2) . ')'; - } else { - $msg = $this->errorCode; - } - parent::__construct($msg, -1, $cause); - } - - - /** - * Retrieve the error code given when throwing this error. - * - * @return string The error code. - */ - public function getErrorCode() { - return $this->errorCode; - } - - - /** - * Retrieve the error parameters given when throwing this error. - * - * @return array The parameters. - */ - public function getParameters() { - return $this->parameters; - } - - - /** - * Retrieve the error title tag in dictionary. - * - * @return string The error title tag. - */ - public function getDictTitle() { - return $this->dictTitle; - } - - - /** - * Retrieve the error description tag in dictionary. - * - * @return string The error description tag. - */ - public function getDictDescr() { - return $this->dictDescr; - } - - - /** - * Set the HTTP return code for this error. - * - * This should be overridden by subclasses who want a different return code than 500 Internal Server Error. - */ - protected function setHTTPCode() { - // Some mostly used HTTP codes. - $httpCodesMap = array( - 400 => 'HTTP/1.0 400 Bad Request', - 403 => 'HTTP/1.0 403 Forbidden', - 404 => 'HTTP/1.0 404 Not Found', - 405 => 'HTTP/1.0 405 Method Not Allowed', - 500 => 'HTTP/1.0 500 Internal Server Error', - 501 => 'HTTP/1.0 501 Method Not Implemented', - 503 => 'HTTP/1.0 503 Service Temporarily Unavailable', - ); - - $httpCode = $this->httpCode; - - if (function_exists('http_response_code')) { - http_response_code($httpCode); - return; - } - - if (!array_key_exists($this->httpCode, $httpCodesMap)) { - $httpCode = 500; - SimpleSAML_Logger::warning('HTTP response code not defined: ' . var_export($this->httpCode, TRUE)); - } - - header($httpCodesMap[$httpCode]); - } - - - /** - * Save an error report. - * - * @return array The array with the error report data. - */ - protected function saveError() { - - $data = $this->format(); - $emsg = array_shift($data); - $etrace = implode("\n", $data); - - $reportId = bin2hex(openssl_random_pseudo_bytes(4)); - SimpleSAML_Logger::error('Error report with id ' . $reportId . ' generated.'); - - $config = SimpleSAML_Configuration::getInstance(); - $session = SimpleSAML_Session::getSessionFromRequest(); - - if (isset($_SERVER['HTTP_REFERER'])) { - $referer = $_SERVER['HTTP_REFERER']; - /* - * Remove anything after the first '?' or ';', just - * in case it contains any sensitive data. - */ - $referer = explode('?', $referer, 2); - $referer = $referer[0]; - $referer = explode(';', $referer, 2); - $referer = $referer[0]; - } else { - $referer = 'unknown'; - } - $errorData = array( - 'exceptionMsg' => $emsg, - 'exceptionTrace' => $etrace, - 'reportId' => $reportId, - 'trackId' => $session->getTrackID(), - 'url' => \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(), - 'version' => $config->getVersion(), - 'referer' => $referer, - ); - $session->setData('core:errorreport', $reportId, $errorData); - - return $errorData; - } - - - /** - * Display this error. - * - * This method displays a standard simpleSAMLphp error page and exits. - */ - public function show() { - - $this->setHTTPCode(); - - /* Log the error message. */ - $this->logError(); - - $errorData = $this->saveError(); - - $config = SimpleSAML_Configuration::getInstance(); - - $data['showerrors'] = $config->getBoolean('showerrors', true); - $data['error'] = $errorData; - $data['errorCode'] = $this->errorCode; - $data['parameters'] = $this->parameters; - $data['module'] = $this->module; - $data['dictTitle'] = $this->dictTitle; - $data['dictDescr'] = $this->dictDescr; - $data['includeTemplate'] = $this->includeTemplate; - - /* Check if there is a valid technical contact email address. */ - if($config->getBoolean('errorreporting', TRUE) && - $config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org') { - /* Enable error reporting. */ - $baseurl = \SimpleSAML\Utils\HTTP::getBaseURL(); - $data['errorReportAddress'] = $baseurl . 'errorreport.php'; - } - - $data['email'] = ''; - $session = SimpleSAML_Session::getSessionFromRequest(); - $authorities = $session->getAuthorities(); - foreach ($authorities as $authority) { - $attributes = $session->getAuthData($authority, 'Attributes'); - if ($attributes !== NULL && array_key_exists('mail', $attributes) && count($attributes['mail']) > 0) { - $data['email'] = $attributes['mail'][0]; - break; // enough, don't need to get all available mails, if more than one - } - } - - $show_function = $config->getArray('errors.show_function', NULL); - if (isset($show_function)) { - assert('is_callable($show_function)'); - call_user_func($show_function, $config, $data); - assert('FALSE'); - } else { - $t = new SimpleSAML_XHTML_Template($config, 'error.php', 'errors'); - $t->data = array_merge($t->data, $data); - $t->show(); - } - - exit; - } - +class SimpleSAML_Error_Error extends SimpleSAML_Error_Exception +{ + + + /** + * The error code. + * + * @var string + */ + private $errorCode; + + + /** + * The http code. + * + * @var integer + */ + protected $httpCode = 500; + + + /** + * The error title tag in dictionary. + * + * @var string + */ + private $dictTitle; + + + /** + * The error description tag in dictionary. + * + * @var string + */ + private $dictDescr; + + + /** + * The name of module which throw error. + * + * @var string|NULL + */ + private $module = null; + + + /** + * The parameters for the error. + * + * @var array + */ + private $parameters; + + + /** + * Name of custom include template for the error. + * + * @var string|NULL + */ + protected $includeTemplate = null; + + + /** + * Constructor for this error. + * + * The error can either be given as a string, or as an array. If it is an array, the first element in the array + * (with index 0), is the error code, while the other elements are replacements for the error text. + * + * @param mixed $errorCode One of the error codes defined in the errors dictionary. + * @param Exception $cause The exception which caused this fatal error (if any). Optional. + * @param int|null $httpCode The HTTP response code to use. Optional. + */ + public function __construct($errorCode, Exception $cause = null, $httpCode = null) + { + assert('is_string($errorCode) || is_array($errorCode)'); + + if (is_array($errorCode)) { + $this->parameters = $errorCode; + unset($this->parameters[0]); + $this->errorCode = $errorCode[0]; + } else { + $this->parameters = array(); + $this->errorCode = $errorCode; + } + + if (isset($httpCode)) { + $this->httpCode = $httpCode; + } + + $moduleCode = explode(':', $this->errorCode, 2); + if (count($moduleCode) === 2) { + $this->module = $moduleCode[0]; + $this->dictTitle = '{'.$this->module.':errors:title_'.$moduleCode[1].'}'; + $this->dictDescr = '{'.$this->module.':errors:descr_'.$moduleCode[1].'}'; + } else { + $this->dictTitle = '{errors:title_'.$this->errorCode.'}'; + $this->dictDescr = '{errors:descr_'.$this->errorCode.'}'; + } + + if (!empty($this->parameters)) { + $msg = $this->errorCode.'('; + foreach ($this->parameters as $k => $v) { + if ($k === 0) { + continue; + } + + $msg .= var_export($k, true).' => '.var_export($v, true).', '; + } + $msg = substr($msg, 0, -2).')'; + } else { + $msg = $this->errorCode; + } + parent::__construct($msg, -1, $cause); + } + + + /** + * Retrieve the error code given when throwing this error. + * + * @return string The error code. + */ + public function getErrorCode() + { + return $this->errorCode; + } + + + /** + * Retrieve the error parameters given when throwing this error. + * + * @return array The parameters. + */ + public function getParameters() + { + return $this->parameters; + } + + + /** + * Retrieve the error title tag in dictionary. + * + * @return string The error title tag. + */ + public function getDictTitle() + { + return $this->dictTitle; + } + + + /** + * Retrieve the error description tag in dictionary. + * + * @return string The error description tag. + */ + public function getDictDescr() + { + return $this->dictDescr; + } + + + /** + * Set the HTTP return code for this error. + * + * This should be overridden by subclasses who want a different return code than 500 Internal Server Error. + */ + protected function setHTTPCode() + { + // Some mostly used HTTP codes. + $httpCodesMap = array( + 400 => 'HTTP/1.0 400 Bad Request', + 403 => 'HTTP/1.0 403 Forbidden', + 404 => 'HTTP/1.0 404 Not Found', + 405 => 'HTTP/1.0 405 Method Not Allowed', + 500 => 'HTTP/1.0 500 Internal Server Error', + 501 => 'HTTP/1.0 501 Method Not Implemented', + 503 => 'HTTP/1.0 503 Service Temporarily Unavailable', + ); + + $httpCode = $this->httpCode; + + if (function_exists('http_response_code')) { + http_response_code($httpCode); + return; + } + + if (!array_key_exists($this->httpCode, $httpCodesMap)) { + $httpCode = 500; + SimpleSAML_Logger::warning('HTTP response code not defined: '.var_export($this->httpCode, true)); + } + + header($httpCodesMap[$httpCode]); + } + + + /** + * Save an error report. + * + * @return array The array with the error report data. + */ + protected function saveError() + { + $data = $this->format(); + $emsg = array_shift($data); + $etrace = implode("\n", $data); + + $reportId = bin2hex(openssl_random_pseudo_bytes(4)); + SimpleSAML_Logger::error('Error report with id '.$reportId.' generated.'); + + $config = SimpleSAML_Configuration::getInstance(); + $session = SimpleSAML_Session::getSessionFromRequest(); + + if (isset($_SERVER['HTTP_REFERER'])) { + $referer = $_SERVER['HTTP_REFERER']; + // remove anything after the first '?' or ';', just in case it contains any sensitive data + $referer = explode('?', $referer, 2); + $referer = $referer[0]; + $referer = explode(';', $referer, 2); + $referer = $referer[0]; + } else { + $referer = 'unknown'; + } + $errorData = array( + 'exceptionMsg' => $emsg, + 'exceptionTrace' => $etrace, + 'reportId' => $reportId, + 'trackId' => $session->getTrackID(), + 'url' => \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(), + 'version' => $config->getVersion(), + 'referer' => $referer, + ); + $session->setData('core:errorreport', $reportId, $errorData); + + return $errorData; + } + + + /** + * Display this error. + * + * This method displays a standard simpleSAMLphp error page and exits. + */ + public function show() + { + $this->setHTTPCode(); + + // log the error message + $this->logError(); + + $errorData = $this->saveError(); + + $config = SimpleSAML_Configuration::getInstance(); + + $data['showerrors'] = $config->getBoolean('showerrors', true); + $data['error'] = $errorData; + $data['errorCode'] = $this->errorCode; + $data['parameters'] = $this->parameters; + $data['module'] = $this->module; + $data['dictTitle'] = $this->dictTitle; + $data['dictDescr'] = $this->dictDescr; + $data['includeTemplate'] = $this->includeTemplate; + $data['clipboard.js'] = true; + + // check if there is a valid technical contact email address + if ($config->getBoolean('errorreporting', true) && + $config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org' + ) { + // enable error reporting + $baseurl = \SimpleSAML\Utils\HTTP::getBaseURL(); + $data['errorReportAddress'] = $baseurl.'errorreport.php'; + } + + $data['email'] = ''; + $session = SimpleSAML_Session::getSessionFromRequest(); + $authorities = $session->getAuthorities(); + foreach ($authorities as $authority) { + $attributes = $session->getAuthData($authority, 'Attributes'); + if ($attributes !== null && array_key_exists('mail', $attributes) && count($attributes['mail']) > 0) { + $data['email'] = $attributes['mail'][0]; + break; // enough, don't need to get all available mails, if more than one + } + } + + $show_function = $config->getArray('errors.show_function', null); + if (isset($show_function)) { + assert('is_callable($show_function)'); + call_user_func($show_function, $config, $data); + assert('FALSE'); + } else { + $t = new SimpleSAML_XHTML_Template($config, 'error.php', 'errors'); + $t->data = array_merge($t->data, $data); + $t->show(); + } + + exit; + } } diff --git a/templates/error.php b/templates/error.php index d8e7b38cba4c52eda8e816ff49a6877f78338fd9..e1aa2f29b52d425fca6f116350ee3828a7c2f486 100644 --- a/templates/error.php +++ b/templates/error.php @@ -1,72 +1,74 @@ -<?php - $this->data['header'] = $this->t($this->data['dictTitle']); - - $this->data['head'] = ' -<meta name="robots" content="noindex, nofollow" /> -<meta name="googlebot" content="noarchive, nofollow" />'; - - $this->includeAtTemplateBase('includes/header.php'); -?> +<?php +$this->data['header'] = $this->t($this->data['dictTitle']); - <h2><?php echo $this->t($this->data['dictTitle']); ?></h2> +$this->data['head'] = <<<EOF +<meta name="robots" content="noindex, nofollow" /> +<meta name="googlebot" content="noarchive, nofollow" /> +EOF; +$this->includeAtTemplateBase('includes/header.php'); +?> + <h2><?php echo $this->t($this->data['dictTitle']); ?></h2> <?php echo htmlspecialchars($this->t($this->data['dictDescr'], $this->data['parameters'])); -/* Include optional information for error. */ +// include optional information for error if (isset($this->data['includeTemplate'])) { - $this->includeAtTemplateBase($this->data['includeTemplate']); + $this->includeAtTemplateBase($this->data['includeTemplate']); } ?> - - <div class="trackidtext"> - <?php echo $this->t('report_trackid'); ?> - <span class="trackid"><?php echo $this->data['error']['trackId']; ?></span> - </div> - - + <div class="trackidtext"> + <p><?php echo $this->t('report_trackid'); ?></p> + <div class="input-group" style="width: 1em;"> + <pre id="trackid" class="input-left"><?php echo $this->data['error']['trackId']; ?></pre> + <button data-clipboard-target="#trackid" id="btntrackid" class="btnaddonright"> + <img src="<?php echo $this->data['baseurlpath'].'/resources/icons/clipboard.svg'; ?>" + alt="Copy to clipboard"> + </button> + </div> + </div> <?php -/* Print out exception only if the exception is available. */ +// print out exception only if the exception is available if ($this->data['showerrors']) { ?> - <h2><?php echo $this->t('debuginfo_header'); ?></h2> - <p><?php echo $this->t('debuginfo_text'); ?></p> - - <div style="border: 1px solid #eee; padding: 1em; font-size: x-small"> - <p style="margin: 1px"><?php echo htmlspecialchars($this->data['error']['exceptionMsg']); ?></p> - <pre style=" padding: 1em; font-family: monospace; "><?php echo htmlspecialchars($this->data['error']['exceptionTrace']); ?></pre> - </div> + <h2><?php echo $this->t('debuginfo_header'); ?></h2> + <p><?php echo $this->t('debuginfo_text'); ?></p> + + <div style="border: 1px solid #eee; padding: 1em; font-size: x-small"> + <p style="margin: 1px"><?php echo htmlspecialchars($this->data['error']['exceptionMsg']); ?></p> + <pre style="padding: 1em; font-family: monospace;"><?php + echo htmlspecialchars($this->data['error']['exceptionTrace']); ?></pre> + </div> <?php } -?> -<?php /* Add error report submit section if we have a valid technical contact. 'errorreportaddress' will only be set if * the technical contact email address has been set. */ if (isset($this->data['errorReportAddress'])) { ?> - - <h2><?php echo $this->t('report_header'); ?></h2> - <form action="<?php echo htmlspecialchars($this->data['errorReportAddress']); ?>" method="post"> - - <p><?php echo $this->t('report_text'); ?></p> - <p><?php echo $this->t('report_email'); ?> <input type="text" size="25" name="email" value="<?php echo htmlspecialchars($this->data['email']); ?>" /></p> - - <p> - <textarea name="text" rows="6" cols="43"><?php echo $this->t('report_explain'); ?></textarea> - </p><p> - <input type="hidden" name="reportId" value="<?php echo $this->data['error']['reportId']; ?>" /> - <input type="submit" name="send" value="<?php echo $this->t('report_submit'); ?>" /> - </p> - </form> -<?php + <h2><?php echo $this->t('report_header'); ?></h2> + <form action="<?php echo htmlspecialchars($this->data['errorReportAddress']); ?>" method="post"> + <p><?php echo $this->t('report_text'); ?></p> + <p><?php echo $this->t('report_email'); ?> + <input type="text" size="25" name="email" value="<?php echo htmlspecialchars($this->data['email']); ?>" /> + </p> + <p> + <textarea class="metadatabox" name="text" rows="6" style="width: 100%; padding: 0.5em;"><?php + echo $this->t('report_explain'); ?></textarea> + </p> + <p> + <input type="hidden" name="reportId" value="<?php echo $this->data['error']['reportId']; ?>"/> + <button type="submit" name="send" class="btn"><?php echo $this->t('report_submit'); ?></button> + </p> + </form> + <?php } ?> - -<h2 style="clear: both"><?php echo $this->t('howto_header'); ?></h2> - -<p><?php echo $this->t('howto_text'); ?></p> - - -<?php $this->includeAtTemplateBase('includes/footer.php'); + <h2 style="clear: both"><?php echo $this->t('howto_header'); ?></h2> + <p><?php echo $this->t('howto_text'); ?></p> + <script type="text/javascript"> + var clipboard = new Clipboard('#btntrackid'); + </script> +<?php +$this->includeAtTemplateBase('includes/footer.php');