parameters = $errorCode; unset($this->parameters[0]); $this->errorCode = $errorCode[0]; } else { $this->parameters = []; $this->errorCode = $errorCode; } if (isset($httpCode)) { $this->httpCode = $httpCode; } $errorCodes = $errorCodes ?? $this->getErrorCodes(); $this->dictTitle = $errorCodes->getTitle($this->errorCode); $this->dictDescr = $errorCodes->getDescription($this->errorCode); if (!empty($this->parameters)) { $msgData = ['errorCode' => $this->errorCode] + $this->parameters; $msg = json_encode($msgData); } else { $msg = $this->errorCode; } parent::__construct($msg, -1, $cause); } /** * Retrieve the ErrorCodes instance to use for resolving dictionary title and description tags. * * Extend this to use custom ErrorCodes instance (with custom error codes and their title / description tags). * * This has to be public to allow Login to get an object * containing custom error codes if they in use. * * @return \SimpleSAML\Erorr\ErrorCodes */ public function getErrorCodes(): ErrorCodes { return new ErrorCodes(); } /** * 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. */ protected function setHTTPCode(): void { http_response_code($this->httpCode); } /** * Save an error report. * * @return array The array with the error report data. * @throws \Exception * @throws \Throwable */ protected function saveError(): array { $data = $this->format(true); $emsg = array_shift($data); $etrace = implode("\n", $data); $reportId = bin2hex(openssl_random_pseudo_bytes(4)); $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'; } $showerrors = $config->getOptionalBoolean('showerrors', false); $whitelist = Configuration::getInstance()->getOptionalArray('showerrors.whitelist', ['*' => true]); if (count($whitelist) == 1 && array_key_exists('*', $whitelist)) { // no change to filtering // everything is shown by default. } else { // explicitly handle showing erorrs // if not listed, do not show backtrace. $showRealError = false; if (array_key_exists($this->errorCode, $whitelist)) { $showRealError = ($whitelist[$this->errorCode] == true); } if (!$showRealError) { // they didn't select to show this message $emsg = "secret"; $etrace = "trace"; $showerrors = false; } } $httpUtils = new Utils\HTTP(); $errorData = [ 'exceptionMsg' => $emsg, 'exceptionTrace' => $etrace, 'reportId' => $reportId, 'trackId' => $session->getTrackID(), 'url' => $httpUtils->getSelfURLNoQuery(), 'version' => $config->getVersion(), 'referer' => $referer, 'showerrors' => $showerrors, ]; $session->setData('core:errorreport', $reportId, $errorData); return $errorData; } /** * Display this error. * * This method displays a standard SimpleSAMLphp error page and exits. * * @param int $logLevel The log-level for this exception * @param bool $suppressReport Whether or not sending an error report is an option * @throws \Exception * @throws \SimpleSAML\Error\ConfigurationError * @throws \Throwable */ public function show(int $logLevel = Logger::ERR, bool $suppressReport = false): void { // log the error message $this->log($logLevel); $errorData = $this->saveError(); $config = Configuration::getInstance(); $data = []; $data['showerrors'] = $errorData['showerrors']; $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 ( $suppressReport === false && $config->getOptionalBoolean('errorreporting', true) && $config->getOptionalString('technicalcontact_email', 'na@example.org') !== 'na@example.org' ) { // enable error reporting $data['errorReportAddress'] = Module::getModuleURL('core/errorReport'); Logger::error('Error report with id ' . $errorData['reportId'] . ' generated.'); } $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->getOptionalArray('errors.show_function', null); Assert::nullOrIsCallable($show_function); if ($show_function !== null) { $this->setHTTPCode(); $response = call_user_func($show_function, $config, $data); $response->send(); } else { $t = new Template($config, 'error.twig'); // Include translations for the module that holds the included template if ($this->includeTemplate !== null) { $module = explode(':', $this->includeTemplate, 2); if (count($module) === 2 && Module::isModuleEnabled($module[0])) { $t->getLocalization()->addModuleDomain($module[0]); } } $t->setStatusCode($this->httpCode); $t->data = array_merge($t->data, $data); $t->send(); } } }