From 06c80dd91136ef41c3a913f46d92cfa1c3ac92eb Mon Sep 17 00:00:00 2001 From: Tim van Dijen <tim.dijen@minbzk.nl> Date: Mon, 8 Jul 2019 21:25:13 +0200 Subject: [PATCH] Fix Psalm-issues batch #1 (#1157) --- composer.json | 2 +- composer.lock | 31 ++++++++-------- lib/SimpleSAML/Auth/ProcessingChain.php | 4 ++- lib/SimpleSAML/Auth/Source.php | 5 +++ lib/SimpleSAML/Auth/State.php | 1 + lib/SimpleSAML/Bindings/Shib13/Artifact.php | 1 + lib/SimpleSAML/Bindings/Shib13/HTTPPost.php | 8 ++--- lib/SimpleSAML/Configuration.php | 4 +-- .../Error/UnserializableException.php | 2 +- lib/SimpleSAML/HTTP/Router.php | 25 ++++++++----- lib/SimpleSAML/IdP/IFrameLogoutHandler.php | 2 +- lib/SimpleSAML/Locale/Localization.php | 8 +++-- lib/SimpleSAML/Locale/Translate.php | 12 ++++--- .../MetaDataStorageHandlerFlatFile.php | 9 +++-- .../MetaDataStorageHandlerSerialize.php | 12 +++++-- .../Metadata/MetaDataStorageSource.php | 2 ++ lib/SimpleSAML/Metadata/SAMLParser.php | 28 +++++++-------- lib/SimpleSAML/Metadata/Signer.php | 1 + lib/SimpleSAML/Metadata/Sources/MDQ.php | 11 ++++++ lib/SimpleSAML/Utils/EMail.php | 2 +- lib/SimpleSAML/XHTML/Template.php | 36 +++++++++++-------- lib/SimpleSAML/XHTML/TemplateLoader.php | 6 ++++ lib/SimpleSAML/XML/Parser.php | 15 +++++--- lib/SimpleSAML/XML/Shib13/AuthnRequest.php | 6 ++-- lib/SimpleSAML/XML/Shib13/AuthnResponse.php | 10 ++++-- lib/SimpleSAML/XML/Signer.php | 9 +++-- lib/SimpleSAML/XML/Validator.php | 24 +++++++------ psalm.xml | 30 ++++++++++++++++ 28 files changed, 203 insertions(+), 103 deletions(-) diff --git a/composer.json b/composer.json index 007e580d3..08f7cd886 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "ext-json": "*", "ext-mbstring": "*", "gettext/gettext": "^4.6", - "jaimeperez/twig-configurable-i18n": "^2.0", + "jaimeperez/twig-configurable-i18n": "^2.1", "phpmailer/phpmailer": "^6.0", "robrichards/xmlseclibs": "^3.0", "simplesamlphp/saml2": "^3.3", diff --git a/composer.lock b/composer.lock index 01c6c2c08..7eb7a5cc6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b809e29b2bfe75e501cfdc5efe9a7c9e", + "content-hash": "11faaddab3dbdd175af8c5a20e8f5d0f", "packages": [ { "name": "gettext/gettext", @@ -131,20 +131,24 @@ }, { "name": "jaimeperez/twig-configurable-i18n", - "version": "v2.0", + "version": "v2.1", "source": { "type": "git", "url": "https://github.com/jaimeperez/twig-configurable-i18n.git", - "reference": "4ccf150ba9f28d2e31d0622ecc9b81cdc6a2638b" + "reference": "38a22aaa6b31efdc0d76d58f5934dea3ebac8556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jaimeperez/twig-configurable-i18n/zipball/4ccf150ba9f28d2e31d0622ecc9b81cdc6a2638b", - "reference": "4ccf150ba9f28d2e31d0622ecc9b81cdc6a2638b", + "url": "https://api.github.com/repos/jaimeperez/twig-configurable-i18n/zipball/38a22aaa6b31efdc0d76d58f5934dea3ebac8556", + "reference": "38a22aaa6b31efdc0d76d58f5934dea3ebac8556", "shasum": "" }, "require": { - "twig/extensions": "^1.3" + "twig/extensions": "^1.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.36", + "twig/twig": "^1.37 || ^2.7" }, "type": "project", "autoload": { @@ -171,7 +175,7 @@ "translation", "twig" ], - "time": "2018-10-10T09:12:46+00:00" + "time": "2019-06-07T11:03:28+00:00" }, { "name": "paragonie/random_compat", @@ -2714,16 +2718,16 @@ }, { "name": "twig/twig", - "version": "v1.42.0", + "version": "v1.42.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "2983fcf2e20a4afe95f07e61d89a7590b3c8df2e" + "reference": "21707d6ebd05476854805e4f91b836531941bcd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/2983fcf2e20a4afe95f07e61d89a7590b3c8df2e", - "reference": "2983fcf2e20a4afe95f07e61d89a7590b3c8df2e", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/21707d6ebd05476854805e4f91b836531941bcd4", + "reference": "21707d6ebd05476854805e4f91b836531941bcd4", "shasum": "" }, "require": { @@ -2776,7 +2780,7 @@ "keywords": [ "templating" ], - "time": "2019-05-31T06:20:05+00:00" + "time": "2019-06-18T15:35:16+00:00" }, { "name": "webmozart/assert", @@ -4153,8 +4157,5 @@ }, "platform-dev": { "ext-curl": "*" - }, - "platform-overrides": { - "php": "5.6.40" } } diff --git a/lib/SimpleSAML/Auth/ProcessingChain.php b/lib/SimpleSAML/Auth/ProcessingChain.php index e07fc2534..3ff70888c 100644 --- a/lib/SimpleSAML/Auth/ProcessingChain.php +++ b/lib/SimpleSAML/Auth/ProcessingChain.php @@ -149,7 +149,7 @@ class ProcessingChain * @param array $config Array with the authentication processing filter configuration. * @param int $priority The priority of the current filter, (not included in the filter * definition.) - * @return ProcessingFilter The parsed filter. + * @return \SimpleSAML\Auth\ProcessingFilter The parsed filter. */ private static function parseFilter($config, $priority) { @@ -166,6 +166,8 @@ class ProcessingChain ); $config['%priority'] = $priority; unset($config['class']); + + /** @var \SimpleSAML\Auth\ProcessingFilter */ return new $className($config, null); } diff --git a/lib/SimpleSAML/Auth/Source.php b/lib/SimpleSAML/Auth/Source.php index 3327103d6..71469744c 100644 --- a/lib/SimpleSAML/Auth/Source.php +++ b/lib/SimpleSAML/Auth/Source.php @@ -126,6 +126,10 @@ abstract class Source // the default implementation just copies over the previous authentication data $session = Session::getSessionFromRequest(); $data = $session->getAuthState($this->authId); + if ($data === null) { + throw new Error\NoState(); + } + foreach ($data as $k => $v) { $state[$k] = $v; } @@ -333,6 +337,7 @@ abstract class Source $authSource = new $className($info, $config); } + /** @var \SimpleSAML\Auth\Source */ return $authSource; } diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php index 8e5030ddf..09daaa9a1 100644 --- a/lib/SimpleSAML/Auth/State.php +++ b/lib/SimpleSAML/Auth/State.php @@ -400,6 +400,7 @@ class State $id = $_REQUEST[self::EXCEPTION_PARAM]; } + /** @var array $state */ $state = self::loadState($id, self::EXCEPTION_STAGE); assert(array_key_exists(self::EXCEPTION_DATA, $state)); diff --git a/lib/SimpleSAML/Bindings/Shib13/Artifact.php b/lib/SimpleSAML/Bindings/Shib13/Artifact.php index 702c59f82..839271f62 100644 --- a/lib/SimpleSAML/Bindings/Shib13/Artifact.php +++ b/lib/SimpleSAML/Bindings/Shib13/Artifact.php @@ -136,6 +136,7 @@ class Artifact Utils\XML::debugSAMLMessage($request, 'out'); + /** @var array $url */ $url = $idpMetadata->getDefaultEndpoint( 'ArtifactResolutionService', ['urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding'] diff --git a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php index 89c3640df..b98f34c3e 100644 --- a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php +++ b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php @@ -20,14 +20,14 @@ use SimpleSAML\XML\Signer; class HTTPPost { /** - * @var \SimpleSAML\Configuration|null + * @var \SimpleSAML\Configuration */ - private $configuration = null; + private $configuration; /** - * @var \SimpleSAML\Metadata\MetaDataStorageHandler|null + * @var \SimpleSAML\Metadata\MetaDataStorageHandler */ - private $metadata = null; + private $metadata; /** diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php index 95077aed9..dbc37ebc2 100644 --- a/lib/SimpleSAML/Configuration.php +++ b/lib/SimpleSAML/Configuration.php @@ -1243,9 +1243,9 @@ class Configuration implements Utils\ClearableState * @param mixed $default The default value to return if no matching endpoint is found. If no default is provided, * an exception will be thrown. * - * @return array|null The default endpoint, or null if no acceptable endpoints are used. + * @return mixed The default endpoint, or the $default parameter if no acceptable endpoints are used. * - * @throws \Exception If no supported endpoint is found. + * @throws \Exception If no supported endpoint is found and no $default parameter is specified. */ public function getDefaultEndpoint($endpointType, array $bindings = null, $default = self::REQUIRED_OPTION) { diff --git a/lib/SimpleSAML/Error/UnserializableException.php b/lib/SimpleSAML/Error/UnserializableException.php index 54f4eb34f..6ac5b39ff 100644 --- a/lib/SimpleSAML/Error/UnserializableException.php +++ b/lib/SimpleSAML/Error/UnserializableException.php @@ -37,7 +37,7 @@ class UnserializableException extends Exception $msg = $original->getMessage(); $code = $original->getCode(); - if (!is_int($code)) { + if ($original instanceof \PDOException) { // PDOException uses a string as the code. Filter it out here. $code = -1; } diff --git a/lib/SimpleSAML/HTTP/Router.php b/lib/SimpleSAML/HTTP/Router.php index cb49e8057..7adb8ed80 100644 --- a/lib/SimpleSAML/HTTP/Router.php +++ b/lib/SimpleSAML/HTTP/Router.php @@ -21,19 +21,19 @@ use Symfony\Component\Routing\RequestContext; */ class Router { - /** @var ArgumentResolver */ + /** @var \Symfony\Component\HttpKernel\Controller\ArgumentResolver */ protected $arguments; /** @var \SimpleSAML\Configuration|null */ protected $config = null; - /** @var RequestContext */ + /** @var \Symfony\Component\Routing\RequestContext */ protected $context; - /** @var EventDispatcher */ + /** @var \Symfony\Component\EventDispatcher\EventDispatcher */ protected $dispatcher; - /** @var Request|null */ + /** @var \Symfony\Component\HttpFoundation\Request|null */ protected $request = null; /** @var \SimpleSAML\Module\ControllerResolver */ @@ -42,7 +42,7 @@ class Router /** @var \SimpleSAML\Session|null */ protected $session = null; - /** @var RequestStack|null */ + /** @var \Symfony\Component\HttpFoundation\RequestStack|null */ protected $stack = null; @@ -65,9 +65,10 @@ class Router * * If no specific arguments are given, the default instances will be used (configuration, session, etc). * - * @param Request|null $request The request to process. Defaults to the current one. + * @param \Symfony\Component\HttpFoundation\Request|null $request + * The request to process. Defaults to the current one. * - * @return Response A response suitable for the given request. + * @return \Symfony\Component\HttpFoundation\Response A response suitable for the given request. * * @throws \Exception If an error occurs. */ @@ -79,10 +80,13 @@ class Router if ($this->session === null) { $this->setSession(Session::getSessionFromRequest()); } - $this->request = $request; + if ($request === null) { $this->request = Request::createFromGlobals(); + } else { + $this->request = $request; } + $stack = new RequestStack(); $stack->push($this->request); $this->context->fromRequest($this->request); @@ -94,11 +98,14 @@ class Router /** * Send a given response to the browser. * - * @param Response $response The response to send. + * @param \Symfony\Component\HttpFoundation\Response $response The response to send. * @return void */ public function send(Response $response) { + if ($this->request === null) { + throw new \Exception("No request found to respond to"); + } $response->prepare($this->request); $response->send(); } diff --git a/lib/SimpleSAML/IdP/IFrameLogoutHandler.php b/lib/SimpleSAML/IdP/IFrameLogoutHandler.php index 6a6aa90d5..58374cf69 100644 --- a/lib/SimpleSAML/IdP/IFrameLogoutHandler.php +++ b/lib/SimpleSAML/IdP/IFrameLogoutHandler.php @@ -88,7 +88,7 @@ class IFrameLogoutHandler implements LogoutHandlerInterface * This function will never return. * * @param string $assocId The association that is terminated. - * @param string $relayState The RelayState from the start of the logout. + * @param string|null $relayState The RelayState from the start of the logout. * @param \SimpleSAML\Error\Exception|null $error The error that occurred during session termination (if any). * @return void */ diff --git a/lib/SimpleSAML/Locale/Localization.php b/lib/SimpleSAML/Locale/Localization.php index dd25eb131..8a9346aac 100644 --- a/lib/SimpleSAML/Locale/Localization.php +++ b/lib/SimpleSAML/Locale/Localization.php @@ -95,7 +95,9 @@ class Localization public function __construct(Configuration $configuration) { $this->configuration = $configuration; - $this->localeDir = $this->configuration->resolvePath('locales'); + /** @var string $locales */ + $locales = $this->configuration->resolvePath('locales'); + $this->localeDir = $locales; $this->language = new Language($configuration); $this->langcode = $this->language->getPosixLanguage($this->language->getLanguage()); $this->i18nBackend = ($this->configuration->getBoolean('usenewui', false) ? self::GETTEXT_I18N_BACKEND : self::SSP_I18N_BACKEND); @@ -123,7 +125,9 @@ class Localization */ public function getDomainLocaleDir($domain) { - $localeDir = $this->configuration->resolvePath('modules').'/'.$domain.'/locales'; + /** @var string $base */ + $base = $this->configuration->resolvePath('modules'); + $localeDir = $base.'/'.$domain.'/locales'; return $localeDir; } diff --git a/lib/SimpleSAML/Locale/Translate.php b/lib/SimpleSAML/Locale/Translate.php index 7d7dfd177..a92ca7ee8 100644 --- a/lib/SimpleSAML/Locale/Translate.php +++ b/lib/SimpleSAML/Locale/Translate.php @@ -108,7 +108,7 @@ class Translate $fileName = substr($name, $sepPos + 1); $dictDir = Module::getModuleDir($module).'/dictionaries/'; } else { - $dictDir = $this->configuration->getPathValue('dictionarydir', 'dictionaries/'); + $dictDir = $this->configuration->getPathValue('dictionarydir', 'dictionaries/') ?: 'dictionaries/'; $fileName = $name; } @@ -261,7 +261,7 @@ class Translate * @param bool $striptags * @deprecated Not used in twig, gettext * - * @return string The translated tag, or a placeholder value if the tag wasn't found. + * @return string|null The translated tag, or a placeholder value if the tag wasn't found. */ public function t( $tag, @@ -292,6 +292,7 @@ class Translate ); // for backwards compatibility + /** @psalm-suppress PossiblyInvalidArgument */ if (!$replacements && ($this->getTag($tag) === null)) { Logger::warning( 'Code which uses $fallbackdefault === FALSE should be updated to use the getTag() method instead.' @@ -387,6 +388,7 @@ class Translate } else { $filebase = $this->configuration->getPathValue('dictionarydir', 'dictionaries/'); } + $filebase = $filebase ?: 'dictionaries/'; $lang = $this->readDictionaryFile($filebase.$file); Logger::debug('Translate: Merging language array. Loading ['.$file.']'); @@ -520,9 +522,9 @@ class Translate /** * Pick a translation from a given array of translations for the current language. * - * @param array $context An array of options. The current language must be specified as an ISO 639 code accessible + * @param array|null $context An array of options. The current language must be specified as an ISO 639 code accessible * with the key "currentLanguage" in the array. - * @param array $translations An array of translations. Each translation has an ISO 639 code as its key, identifying + * @param array|null $translations An array of translations. Each translation has an ISO 639 code as its key, identifying * the language it corresponds to. * * @return null|string The translation appropriate for the current language, or null if none found. If the @@ -530,7 +532,7 @@ class Translate */ public static function translateFromArray($context, $translations) { - if (!is_array($translations) || $translations === null) { + if (!is_array($translations)) { return null; } diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php index f18ac585a..03c7c1fc9 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php @@ -21,7 +21,7 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource * * @var string */ - private $directory; + private $directory = '/'; /** @@ -50,7 +50,7 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource // find the path to the directory we should search for metadata in if (array_key_exists('directory', $config)) { - $this->directory = $config['directory']; + $this->directory = $config['directory'] ?: 'metadata/'; } else { $this->directory = $globalConfig->getString('metadatadir', 'metadata/'); } @@ -58,7 +58,10 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource /* Resolve this directory relative to the SimpleSAMLphp directory (unless it is * an absolute path). */ - $this->directory = $globalConfig->resolvePath($this->directory).'/'; + + /** @var string $base */ + $base = $globalConfig->resolvePath($this->directory); + $this->directory = $base.'/'; } diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php index a41663fcc..db1b086be 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Metadata; use SimpleSAML\Configuration; use SimpleSAML\Logger; +use SimpleSAML\Utils; /** * Class for handling metadata files in serialized format. @@ -24,9 +25,9 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource /** * The base directory where metadata is stored. * - * @var string|null + * @var string */ - private $directory; + private $directory = '/'; /** @@ -49,7 +50,7 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource /* Resolve this directory relative to the SimpleSAMLphp directory (unless it is * an absolute path). */ - $this->directory = $globalConfig->resolvePath($this->directory); + $this->directory = Utils\System::resolvePath($this->directory, $globalConfig->getBaseDir()); } @@ -187,6 +188,7 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource $data = @file_get_contents($filePath); if ($data === false) { + /** @var array $error */ $error = error_get_last(); Logger::warning( 'Error reading file '.$filePath.': '.$error['message'] @@ -231,6 +233,7 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource Logger::info('Creating directory: '.$dir); $res = @mkdir($dir, 0777, true); if ($res === false) { + /** @var array $error */ $error = error_get_last(); Logger::error('Failed to create directory '.$dir.': '.$error['message']); return false; @@ -243,6 +246,7 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource $res = file_put_contents($newPath, $data); if ($res === false) { + /** @var array $error */ $error = error_get_last(); Logger::error('Error saving file '.$newPath.': '.$error['message']); return false; @@ -250,6 +254,7 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource $res = rename($newPath, $filePath); if ($res === false) { + /** @var array $error */ $error = error_get_last(); Logger::error('Error renaming '.$newPath.' to '.$filePath.': '.$error['message']); return false; @@ -283,6 +288,7 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource $res = unlink($filePath); if ($res === false) { + /** @var array $error */ $error = error_get_last(); Logger::error( 'Failed to delete file '.$filePath. diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageSource.php b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php index 3ec6c0b55..45fac75df 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageSource.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php @@ -97,6 +97,8 @@ abstract class MetaDataStorageSource null ); } + + /** @var \SimpleSAML\Metadata\MetaDataStorageSource */ return new $className($sourceConfig); } } diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php index d18c6d176..6c9f7fed0 100644 --- a/lib/SimpleSAML/Metadata/SAMLParser.php +++ b/lib/SimpleSAML/Metadata/SAMLParser.php @@ -220,8 +220,9 @@ class SAMLParser } } - if ($entityElement->getOrganization() !== null) { - $this->processOrganization($entityElement->getOrganization()); + $organization = $entityElement->getOrganization(); + if ($organization !== null) { + $this->processOrganization($organization); } if ($entityElement->getContactPerson() !== []) { @@ -333,10 +334,6 @@ class SAMLParser throw new \Exception('Failed to read XML from file: '.$file); } - if ($doc->documentElement === null) { - throw new \Exception('Opened file is not an XML document: '.$file); - } - return self::parseDescriptorsElement($doc->documentElement); } @@ -1118,13 +1115,12 @@ class SAMLParser $ret['UIInfo']['PrivacyStatementURL'] = $e->getPrivacyStatementURL(); foreach ($e->getKeywords() as $uiItem) { - if (!($uiItem instanceof \SAML2\XML\mdui\Keywords) - || ($uiItem->getKeywords() === []) - || ($uiItem->getLanguage() === null) - ) { + $keywords = $uiItem->getKeywords(); + $language = $uiItem->getLanguage(); + if (($keywords === []) || ($language === null)) { continue; } - $ret['UIInfo']['Keywords'][$uiItem->getLanguage()] = $uiItem->getKeywords(); + $ret['UIInfo']['Keywords'][$language] = $keywords; } foreach ($e->getLogo() as $uiItem) { if (!($uiItem instanceof Logo) @@ -1444,10 +1440,6 @@ class SAMLParser // find the EntityDescriptor DOMElement. This should be the first (and only) child of the DOMDocument $ed = $doc->documentElement; - if ($ed === null) { - throw new \Exception('Failed to load SAML metadata from empty XML document.'); - } - if (Utils\XML::isDOMNodeOfType($ed, 'EntityDescriptor', '@md') === false) { throw new \Exception('Expected first element in the metadata document to be an EntityDescriptor element.'); } @@ -1494,6 +1486,12 @@ class SAMLParser } + /** + * @param string $algorithm + * @param string $data + * @throws \UnexpectedValueException + * @return string + */ private function computeFingerprint($algorithm, $data) { switch ($algorithm) { diff --git a/lib/SimpleSAML/Metadata/Signer.php b/lib/SimpleSAML/Metadata/Signer.php index b149fdd9e..28824c059 100644 --- a/lib/SimpleSAML/Metadata/Signer.php +++ b/lib/SimpleSAML/Metadata/Signer.php @@ -265,6 +265,7 @@ class Signer $objKey->loadKey($keyData, false); // get the EntityDescriptor node we should sign + /** @var \DOMElement $rootNode */ $rootNode = $xml->firstChild; $rootNode->setAttribute('ID', '_'.hash('sha256', $metadataString)); diff --git a/lib/SimpleSAML/Metadata/Sources/MDQ.php b/lib/SimpleSAML/Metadata/Sources/MDQ.php index 399975d5d..60d4c01f2 100644 --- a/lib/SimpleSAML/Metadata/Sources/MDQ.php +++ b/lib/SimpleSAML/Metadata/Sources/MDQ.php @@ -4,6 +4,7 @@ namespace SimpleSAML\Metadata\Sources; use RobRichards\XMLSecLibs\XMLSecurityDSig; use SimpleSAML\Configuration; +use SimpleSAML\Error; use SimpleSAML\Logger; use SimpleSAML\Metadata\SAMLParser; use SimpleSAML\Utils; @@ -34,6 +35,11 @@ class MDQ extends \SimpleSAML\Metadata\MetaDataStorageSource */ private $validateFingerprint; + /** + * @var string + */ + private $validateFingerprintAlgorithm; + /** * The cache directory, or null if no cache directory is configured. * @@ -129,6 +135,10 @@ class MDQ extends \SimpleSAML\Metadata\MetaDataStorageSource assert(is_string($set)); assert(is_string($entityId)); + if ($this->cacheDir === null) { + throw new Error\ConfigurationError("Missing cache directory configuration."); + } + $cachekey = sha1($entityId); return $this->cacheDir.'/'.$set.'-'.$cachekey.'.cached.xml'; } @@ -174,6 +184,7 @@ class MDQ extends \SimpleSAML\Metadata\MetaDataStorageSource $rawData = file_get_contents($cachefilename); if (empty($rawData)) { + /** @var array $error */ $error = error_get_last(); throw new \Exception( __CLASS__.': error reading metadata from cache file "'.$cachefilename.'": '.$error['message'] diff --git a/lib/SimpleSAML/Utils/EMail.php b/lib/SimpleSAML/Utils/EMail.php index add2e6503..5e41d79c7 100644 --- a/lib/SimpleSAML/Utils/EMail.php +++ b/lib/SimpleSAML/Utils/EMail.php @@ -260,7 +260,7 @@ class EMail ]); $t = new Template($config, $template); $twig = $t->getTwig(); - if (is_bool($twig)) { + if (!isset($twig)) { throw new \Exception('Even though we explicitly configure that we want Twig, the Template class does not give us Twig. This is a bug.'); } $result = $twig->render($template, [ diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php index b1c3cca00..9ba61d9e6 100644 --- a/lib/SimpleSAML/XHTML/Template.php +++ b/lib/SimpleSAML/XHTML/Template.php @@ -65,9 +65,9 @@ class Template extends Response /** * The twig environment. * - * @var false|\JaimePerez\TwigConfigurableI18n\Twig\Environment + * @var \Twig\Environment */ - private $twig = false; + private $twig; /** * The template name. @@ -78,6 +78,8 @@ class Template extends Response /** * Current module, if any. + * + * @var string */ private $module; @@ -108,9 +110,9 @@ class Template extends Response * of the module and the name of the theme, respectively. If we are using the default theme, the variable has * the 'default' string in the "name" key, and 'null' in the "module" key. * - * @var array|null + * @var array */ - private $theme = null; + private $theme = ['module' => null, 'name' => 'default']; /** * Constructor @@ -147,6 +149,7 @@ class Template extends Response if ($controller && class_exists($controller) && in_array(TemplateControllerInterface::class, class_implements($controller)) ) { + /** @var \SimpleSAML\XHTML\TemplateControllerInterface $this->controller */ $this->controller = new $controller(); } @@ -173,7 +176,7 @@ class Template extends Response $tag = $this->configuration->getVersion(); if ($tag === 'master') { - $tag = filemtime($file); + $tag = strval(filemtime($file)); } $tag = substr(hash('md5', $tag), 0, 5); return $this->configuration->getBasePath().'assets/'.$asset.'?tag='.$tag; @@ -261,17 +264,20 @@ class Template extends Response /** * Setup twig. - * @return Twig_Environment|false + * @return \Twig\Environment + * @throws \Exception if the template does not exist */ private function setupTwig() { $auto_reload = $this->configuration->getBoolean('template.auto_reload', true); $cache = $this->configuration->getString('template.cache', false); + // set up template paths $loader = $this->setupTwigTemplatepaths(); + // abort if twig template does not exist if (!$loader->exists($this->twig_template)) { - return false; + throw new \Exception('Template-file \"'.$this->template.'\" does not exist.'); } // load extra i18n domains @@ -337,7 +343,7 @@ class Template extends Response */ private function findThemeTemplateDirs() { - if ($this->theme['module'] === null) { + if (!isset($this->theme['module'])) { // no module involved return []; } @@ -517,7 +523,7 @@ class Template extends Response */ public function show() { - if ($this->twig !== false) { + if ($this->useNewUI) { echo $this->getContents(); } else { require($this->findTemplatePath($this->template)); @@ -573,7 +579,8 @@ class Template extends Response $filename = Module::getModuleDir($templateModule).'/templates/'.$templateName; } else { // .../templates/<theme>/<templateName> - $filename = $this->configuration->getPathValue('templatedir', 'templates/').$templateName; + $base = $this->configuration->getPathValue('templatedir', 'templates/') ?: 'templates/'; + $filename = $base.$templateName; } if (file_exists($filename)) { @@ -592,7 +599,8 @@ class Template extends Response $filename = Module::getModuleDir($templateModule).'/templates/'.$templateName; } else { // .../templates/<templateName> - $filename = $this->configuration->getPathValue('templatedir', 'templates/').'/'.$templateName; + $base = $this->configuration->getPathValue('templatedir', 'templates/') ?: 'templates/'; + $filename = $base.'/'.$templateName; } if (file_exists($filename)) { @@ -638,7 +646,7 @@ class Template extends Response /** * Get the current instance of Twig in use. * - * @return false|Twig_Environment The Twig instance in use, or false if Twig is not used. + * @return \Twig\Environment The Twig instance in use, or null if Twig is not used. */ public function getTwig() { @@ -727,7 +735,7 @@ class Template extends Response /** * @param string $tag * - * @return array + * @return array|null * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Translate::getTag() instead. */ public function getTag($tag) @@ -856,7 +864,7 @@ class Template extends Response * @param bool $fallbackdefault * @param array $oldreplacements * @param bool $striptags - * @return string + * @return string|null */ public function t( $tag, diff --git a/lib/SimpleSAML/XHTML/TemplateLoader.php b/lib/SimpleSAML/XHTML/TemplateLoader.php index a2c657147..14231bb9f 100644 --- a/lib/SimpleSAML/XHTML/TemplateLoader.php +++ b/lib/SimpleSAML/XHTML/TemplateLoader.php @@ -9,6 +9,8 @@ use SimpleSAML\Module; * when the main template is not part of a module (or the same one). * * @package simplesamlphp/simplesamlphp + * + * @psalm-suppress DeprecatedInterface This suppress may be removed when Twig 3.0 becomes the default */ class TemplateLoader extends \Twig\Loader\FilesystemLoader { @@ -16,6 +18,10 @@ class TemplateLoader extends \Twig\Loader\FilesystemLoader * This method adds a namespace dynamically so that we can load templates from modules whenever we want. * * {@inheritdoc} + * + * @param string $name + * @param bool $throw + * @return string|false|null */ protected function findTemplate($name, $throw = true) { diff --git a/lib/SimpleSAML/XML/Parser.php b/lib/SimpleSAML/XML/Parser.php index 234aaf3ca..2e5713d15 100644 --- a/lib/SimpleSAML/XML/Parser.php +++ b/lib/SimpleSAML/XML/Parser.php @@ -11,8 +11,8 @@ namespace SimpleSAML\XML; class Parser { - /** @var \SimpleXMLElement|null */ - public $simplexml = null; + /** @var \SimpleXMLElement */ + public $simplexml; /** * @param string $xml @@ -29,6 +29,7 @@ class Parser /** * @param \SimpleXMLElement $element * @return \SimpleSAML\XML\Parser + * @psalm-return \SimpleSAML\XML\Parser */ public static function fromSimpleXMLElement(\SimpleXMLElement $element) { @@ -41,8 +42,11 @@ class Parser /* Create a new parser with the xml document where the namespace definitions * are added. */ - $parser = new Parser($element->asXML()); - return $parser; + $xml = $element->asXML(); + if ($xml === false) { + throw new \Exception('Error converting SimpleXMLElement to well-formed XML string.'); + } + return new Parser($xml); } @@ -55,6 +59,7 @@ class Parser public function getValueDefault($xpath, $defvalue) { try { + /** @var string */ return $this->getValue($xpath, true); } catch (\Exception $e) { return $defvalue; @@ -66,7 +71,7 @@ class Parser * @param string $xpath * @param bool $required * @throws \Exception - * @return string + * @return string|null */ public function getValue($xpath, $required = false) { diff --git a/lib/SimpleSAML/XML/Shib13/AuthnRequest.php b/lib/SimpleSAML/XML/Shib13/AuthnRequest.php index 8277e51bb..942dabb38 100644 --- a/lib/SimpleSAML/XML/Shib13/AuthnRequest.php +++ b/lib/SimpleSAML/XML/Shib13/AuthnRequest.php @@ -76,9 +76,11 @@ class AuthnRequest $desturl = $desturl['Location']; $target = $this->getRelayState(); - + $issuer = $this->getIssuer(); + assert($issuer !== null); + $url = $desturl.'?'. - 'providerId='.urlencode($this->getIssuer()). + 'providerId='.urlencode($issuer). '&shire='.urlencode($shire). (isset($target) ? '&target='.urlencode($target) : ''); return $url; diff --git a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php index d04983b31..9148c3869 100644 --- a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php +++ b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php @@ -269,10 +269,14 @@ class AuthnResponse 'shib:AttributeStatement/shib:Attribute/shib:AttributeValue', $assertion ); - /** @var \DOMElement $attribute */ + foreach ($attribute_nodes as $attribute) { + /** @var \DOMElement $attribute */ + $value = $attribute->textContent; - $name = $attribute->parentNode->getAttribute('AttributeName'); + /** @var \DOMElement $parentNode */ + $parentNode = $attribute->parentNode; + $name = $parentNode->getAttribute('AttributeName'); if ($attribute->hasAttribute('Scope')) { $scopePart = '@'.$attribute->getAttribute('Scope'); @@ -280,7 +284,7 @@ class AuthnResponse $scopePart = ''; } - if (!is_string($name)) { + if (empty($name)) { throw new \Exception('Shib13 Attribute node without an AttributeName.'); } diff --git a/lib/SimpleSAML/XML/Signer.php b/lib/SimpleSAML/XML/Signer.php index 235335b64..09144e196 100644 --- a/lib/SimpleSAML/XML/Signer.php +++ b/lib/SimpleSAML/XML/Signer.php @@ -26,7 +26,7 @@ class Signer private $idAttrName = ''; /** - * @var XMLSecurityKey|bool The private key (as an XMLSecurityKey). + * @var XMLSecurityKey|false The private key (as an XMLSecurityKey). */ private $privateKey = false; @@ -283,7 +283,8 @@ class Signer assert($insertBefore === null || $insertBefore instanceof DOMElement || $insertBefore instanceof DOMComment || $insertBefore instanceof DOMText); - if ($this->privateKey === false) { + $privateKey = $this->privateKey; + if ($privateKey === false) { throw new \Exception('Private key not set.'); } @@ -303,9 +304,7 @@ class Signer $options ); - /** @var \RobRichards\XMLSecLibs\XMLSecurityKey $this->privateKey */ - $objXMLSecDSig->sign($this->privateKey); - + $objXMLSecDSig->sign($privateKey); // Add the certificate to the signature $objXMLSecDSig->add509Cert($this->certificate, true); diff --git a/lib/SimpleSAML/XML/Validator.php b/lib/SimpleSAML/XML/Validator.php index 848445ac0..2d1184525 100644 --- a/lib/SimpleSAML/XML/Validator.php +++ b/lib/SimpleSAML/XML/Validator.php @@ -16,7 +16,7 @@ use SimpleSAML\Logger; class Validator { /** - * @var string This variable contains the X509 certificate the XML document + * @var string|null This variable contains the X509 certificate the XML document * was signed with, or NULL if it wasn't signed with an X509 certificate. */ private $x509Certificate = null; @@ -37,7 +37,7 @@ class Validator * - A string: Assumed to be a PEM-encoded certificate / public key. * - An array: Assumed to be an array returned by \SimpleSAML\Utils\Crypto::loadPublicKey. * - * @param \DOMNode $xmlNode The XML node which contains the Signature element. + * @param \DOMDocument $xmlNode The XML node which contains the Signature element. * @param string|array $idAttribute The ID attribute which is used in node references. If * this attribute is NULL (the default), then we will use whatever is the default * ID. Can be eigther a string with one value, or an array with multiple ID @@ -47,7 +47,7 @@ class Validator */ public function __construct($xmlNode, $idAttribute = null, $publickey = false) { - assert($xmlNode instanceof \DOMNode); + assert($xmlNode instanceof \DOMDocument); if ($publickey === null) { $publickey = false; @@ -143,7 +143,7 @@ class Validator * This function will return the certificate as a PEM-encoded string. If the XML * wasn't signed by an X509 certificate, NULL will be returned. * - * @return string The certificate as a PEM-encoded string, or NULL if not signed with an X509 certificate. + * @return string|null The certificate as a PEM-encoded string, or NULL if not signed with an X509 certificate. */ public function getX509Certificate() { @@ -277,12 +277,14 @@ class Validator { assert($node instanceof \DOMNode); - while ($node !== null) { - if (in_array($node, $this->validNodes, true)) { - return true; - } + if ($this->validNodes !== null) { + while ($node !== null) { + if (in_array($node, $this->validNodes, true)) { + return true; + } - $node = $node->parentNode; + $node = $node->parentNode; + } } /* Neither this node nor any of the parent nodes could be found in the list of @@ -438,8 +440,8 @@ class Validator if ($resExternal !== true) { Logger::debug('Failed to validate with external function: '.var_export($resExternal, true)); throw new \Exception('Could not verify certificate against CA file "'. - $caFile.'". Internal result:'.$resBuiltin. - ' External result:'.$resExternal); + $caFile.'". Internal result:'.var_export($resBuiltin, true). + ' External result:'.var_export($resExternal, true)); } } diff --git a/psalm.xml b/psalm.xml index 5e1a94690..c80d81a44 100644 --- a/psalm.xml +++ b/psalm.xml @@ -3,15 +3,36 @@ name="SimpleSAMLphp" useDocblockTypes="true" totallyTyped="false" + hideExternalErrors="true" > <projectFiles> + <directory name="lib/SimpleSAML/Auth" /> + <directory name="lib/SimpleSAML/Bindings" /> + <directory name="lib/SimpleSAML/Error" /> + <directory name="lib/SimpleSAML/HTTP" /> + <directory name="lib/SimpleSAML/IdP" /> + <directory name="lib/SimpleSAML/Locale" /> + <directory name="lib/SimpleSAML/Logger" /> + <directory name="lib/SimpleSAML/Metadata" /> + <directory name="lib/SimpleSAML/Module" /> + <directory name="lib/SimpleSAML/Stats" /> + <directory name="lib/SimpleSAML/Store" /> <directory name="lib/SimpleSAML/Utils" /> + <directory name="lib/SimpleSAML/XHTML" /> + <directory name="lib/SimpleSAML/XML" /> + + <!-- Ignore deprecated classes --> + <ignoreFiles> + <file name="lib/SimpleSAML/Auth/DefaultAuth.php" /> + <file name="lib/SimpleSAML/Utilities.php" /> + </ignoreFiles> </projectFiles> <issueHandlers> <LessSpecificReturnType errorLevel="info" /> <!-- level 3 issues - slightly lazy code writing, but probably low false-negatives --> + <DeprecatedClass errorLevel="info" /> <DeprecatedMethod errorLevel="info" /> <MissingClosureReturnType errorLevel="info" /> @@ -30,6 +51,15 @@ <!-- Ignore these errors until we are fully typehinted --> <DocblockTypeContradiction errorLevel="suppress" /> <RedundantConditionGivenDocblockType errorLevel="suppress" /> + + <!-- Suppress false-positive --> + <UnresolvableInclude errorLevel="suppress" /> + <!-- See #1141 --> + <MissingDependency> + <errorLevel type="suppress"> + <file name="lib/SimpleSAML/HTTP/Router.php" /> + </errorLevel> + </MissingDependency> </issueHandlers> <stubs> -- GitLab