<?php declare(strict_types=1); namespace SimpleSAML\Error; use SimpleSAML\Configuration; use SimpleSAML\Logger; use SimpleSAML\Session; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; use Webmozart\Assert\Assert; /** * Class that wraps SimpleSAMLphp errors in exceptions. * * @author Olav Morken, UNINETT AS. * @package SimpleSAMLphp */ class Error extends 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 that threw the 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, ?int $httpCode = null) { Assert::true(is_string($errorCode) || is_array($errorCode)); if (is_array($errorCode)) { $this->parameters = $errorCode; unset($this->parameters[0]); $this->errorCode = $errorCode[0]; } else { $this->parameters = []; $this->errorCode = $errorCode; } if (isset($httpCode)) { $this->httpCode = $httpCode; } $this->dictTitle = ErrorCodes::getErrorCodeTitle($this->errorCode); $this->dictDescr = ErrorCodes::getErrorCodeDescription($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(): string { return $this->errorCode; } /** * Retrieve the error parameters given when throwing this error. * * @return array The parameters. */ public function getParameters(): array { return $this->parameters; } /** * Retrieve the error title tag in dictionary. * * @return string The error title tag. */ public function getDictTitle(): string { return $this->dictTitle; } /** * Retrieve the error description tag in dictionary. * * @return string The error description tag. */ public function getDictDescr(): string { 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. * @return void */ protected function setHTTPCode(): void { http_response_code($this->httpCode); } /** * Save an error report. * * @return array The array with the error report data. */ protected function saveError(): array { $data = $this->format(true); $emsg = array_shift($data); $etrace = implode("\n", $data); $reportId = bin2hex(openssl_random_pseudo_bytes(4)); Logger::error('Error report with id ' . $reportId . ' generated.'); $config = Configuration::getInstance(); $session = 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 = [ 'exceptionMsg' => $emsg, 'exceptionTrace' => $etrace, 'reportId' => $reportId, 'trackId' => $session->getTrackID(), 'url' => 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. * @return void */ public function show(): void { $this->setHTTPCode(); // log the error message $this->logError(); $errorData = $this->saveError(); $config = Configuration::getInstance(); $data = []; $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 = Utils\HTTP::getBaseURL(); $data['errorReportAddress'] = $baseurl . 'errorreport.php'; } $data['email'] = ''; $session = 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::isCallable($show_function); call_user_func($show_function, $config, $data); Assert::true(false); } else { $t = new Template($config, 'error.twig', 'errors'); $t->data = array_merge($t->data, $data); $t->send(); } exit; } }