From 5445772da4fc6a1f3b63ca733bc1c348dc56d4ce Mon Sep 17 00:00:00 2001 From: rfair404 Date: Tue, 13 Dec 2022 14:06:30 -0700 Subject: [PATCH 1/3] bump onelogin/php-saml to ^4.0 for php8 compat --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b7aef1a..e8a8f2c 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "onelogin/php-saml": "^3.0" + "onelogin/php-saml": "^4.0" }, "require-dev": { "humanmade/coding-standards": "dev-master" From 51d3a4711791ac68ddb6c8081a67d8475ae2346e Mon Sep 17 00:00:00 2001 From: rfair404 Date: Tue, 13 Dec 2022 14:07:39 -0700 Subject: [PATCH 2/3] update composer lock with dependencies for onelogin/php-saml --- composer.lock | 55 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/composer.lock b/composer.lock index 36d1780..d29b7c8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,38 +4,39 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a686dbbc7aa978d8659d6c1805435145", + "content-hash": "86d84d9b6fce14a25fc65096b0bf6334", "packages": [ { "name": "onelogin/php-saml", - "version": "v3.0.0", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/onelogin/php-saml.git", - "reference": "920c2240e48c9a74aad4129720f48fbf3d5fee47" + "reference": "b22a57ebd13e838b90df5d3346090bc37056409d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/onelogin/php-saml/zipball/920c2240e48c9a74aad4129720f48fbf3d5fee47", - "reference": "920c2240e48c9a74aad4129720f48fbf3d5fee47", + "url": "https://api.github.com/repos/onelogin/php-saml/zipball/b22a57ebd13e838b90df5d3346090bc37056409d", + "reference": "b22a57ebd13e838b90df5d3346090bc37056409d", "shasum": "" }, "require": { - "php": ">=5.4", - "robrichards/xmlseclibs": "^3.0" + "php": ">=7.3", + "robrichards/xmlseclibs": ">=3.1.1" }, "require-dev": { - "pdepend/pdepend": "^2.5.0", - "php-coveralls/php-coveralls": "^1.0.2 || ^2.0", - "phploc/phploc": "^2.1 || ^3.0 || ^4.0", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1", - "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", - "squizlabs/php_codesniffer": "^3.1.1" + "pdepend/pdepend": "^2.8.0", + "php-coveralls/php-coveralls": "^2.0", + "phploc/phploc": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "phpunit/phpunit": "^9.5", + "sebastian/phpcpd": "^4.0 || ^5.0 || ^6.0 ", + "squizlabs/php_codesniffer": "^3.5.8" }, "suggest": { "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", - "ext-gettext": "Install gettext and php5-gettext libs to handle translations", - "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)" + "ext-dom": "Install xml lib", + "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)", + "ext-zlib": "Install zlib" }, "type": "library", "autoload": { @@ -54,20 +55,25 @@ "onelogin", "saml" ], - "time": "2018-10-02T16:02:37+00:00" + "support": { + "email": "sixto.garcia@onelogin.com", + "issues": "https://github.com/onelogin/php-saml/issues", + "source": "https://github.com/onelogin/php-saml/" + }, + "time": "2022-07-15T20:44:36+00:00" }, { "name": "robrichards/xmlseclibs", - "version": "3.0.4", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/robrichards/xmlseclibs.git", - "reference": "0a53d3c3aa87564910cae4ed01416441d3ae0db5" + "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/0a53d3c3aa87564910cae4ed01416441d3ae0db5", - "reference": "0a53d3c3aa87564910cae4ed01416441d3ae0db5", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/f8f19e58f26cdb42c54b214ff8a820760292f8df", + "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df", "shasum": "" }, "require": { @@ -92,7 +98,11 @@ "xml", "xmldsig" ], - "time": "2019-11-05T11:44:22+00:00" + "support": { + "issues": "https://github.com/robrichards/xmlseclibs/issues", + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.1" + }, + "time": "2020-09-05T13:00:25+00:00" } ], "packages-dev": [ @@ -272,5 +282,6 @@ "prefer-stable": false, "prefer-lowest": false, "platform": [], - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.3.0" } From 2f587ac7e311084f7c4b36b95ccdca25049c437a Mon Sep 17 00:00:00 2001 From: rfair404 Date: Tue, 10 Jan 2023 15:16:58 -0700 Subject: [PATCH 3/3] manually update vendor/php-saml library to 4.1.0 --- .../.github/workflows/php-package.yml | 54 ++++++ vendor/onelogin/php-saml/CHANGELOG | 109 +++++++++++- vendor/onelogin/php-saml/README.md | 127 +++++++++++--- .../php-saml/advanced_settings_example.php | 37 ++++- vendor/onelogin/php-saml/composer.json | 26 ++- vendor/onelogin/php-saml/phpunit.xml | 20 +++ vendor/onelogin/php-saml/settings_example.php | 11 +- vendor/onelogin/php-saml/src/Saml2/Auth.php | 74 ++++++--- .../php-saml/src/Saml2/AuthnRequest.php | 43 +++-- .../onelogin/php-saml/src/Saml2/Constants.php | 2 + .../php-saml/src/Saml2/IdPMetadataParser.php | 13 ++ .../php-saml/src/Saml2/LogoutRequest.php | 44 +++-- .../php-saml/src/Saml2/LogoutResponse.php | 37 +++-- .../onelogin/php-saml/src/Saml2/Metadata.php | 4 +- .../onelogin/php-saml/src/Saml2/Response.php | 84 +++++++--- .../onelogin/php-saml/src/Saml2/Settings.php | 156 ++++++++++++++---- vendor/onelogin/php-saml/src/Saml2/Utils.php | 107 +++++++++--- .../onelogin/php-saml/src/Saml2/version.json | 5 +- 18 files changed, 775 insertions(+), 178 deletions(-) create mode 100644 vendor/onelogin/php-saml/.github/workflows/php-package.yml create mode 100644 vendor/onelogin/php-saml/phpunit.xml diff --git a/vendor/onelogin/php-saml/.github/workflows/php-package.yml b/vendor/onelogin/php-saml/.github/workflows/php-package.yml new file mode 100644 index 0000000..3ba3d15 --- /dev/null +++ b/vendor/onelogin/php-saml/.github/workflows/php-package.yml @@ -0,0 +1,54 @@ +# This workflow will install PHP dependencies, run tests and lint with a variety of PHP versions +# For more information see: https://github.com/marketplace/actions/setup-php-action + +name: php-saml 4.x package + +on: + push: + branches: [ 4.* ] + pull_request: + branches: [ 4.* ] + +jobs: + test: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: ['ubuntu-latest'] + php-versions: [7.3, 7.4, 8.0, 8.1] + steps: + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, intl, mcrypt, xml + tools: composer:v2 + ini-values: post_max_size=256M, max_execution_time=180 + coverage: xdebug + + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Install Composer dependencies + run: | + composer self-update + composer install --prefer-source --no-interaction + - name: Syntax check PHP + run: | + php vendor/bin/phpcpd --exclude tests --exclude vendor . + php vendor/bin/phploc src/. + mkdir -p tests/build/dependences + php vendor/bin/pdepend --summary-xml=tests/build/logs/dependence-summary.xml --jdepend-chart=tests/build/dependences/jdepend.svg --overview-pyramid=tests/build/dependences/pyramid.svg src/. + + - name: PHP Code Sniffer + run: php vendor/bin/phpcs --standard=tests/ZendModStandard src/Saml2 demo1 demo2 endpoints tests/src + + - name: Run unit tests + run: vendor/bin/phpunit --verbose --debug diff --git a/vendor/onelogin/php-saml/CHANGELOG b/vendor/onelogin/php-saml/CHANGELOG index 31fe880..27a4b2b 100644 --- a/vendor/onelogin/php-saml/CHANGELOG +++ b/vendor/onelogin/php-saml/CHANGELOG @@ -1,10 +1,117 @@ CHANGELOG ========= +v4.0.0 +* Supports PHP 8.X -v.3.0.0 (pending) +v3.6.1 +* [#467](https://github.com/onelogin/php-saml/issues/467) Fix bug on getSelfRoutedURLNoQuery method + +v3.6.0 +* Add AES128_GCM encryption on generateNameId method. New setting parameter encryption_algorithm. If you set a encryption method different than AES128_CBC then the algorithm RSA_OAEP_MGF1P will be used as well instead RSA_1_5 +* PHP 8.0 support + +v3.5.1 +* 3.5.0 packagist/github release due a confusion were using the master (2.X branch). I'm releasing 3.5.1 to fix this issue and go back to 3.X branch + +v3.5.0 +* [#412](https://github.com/onelogin/php-saml/pull/412) Empty instead of unset the $_SESSION variable +* [#433](https://github.com/onelogin/php-saml/issues/443) Fix Incorrect Destination in LogoutResponse when using responseUrl #443 +* Update xmlseclibs to 3.1.1 +* Add support for SMARTCARD_PKI and RSA_TOKEN Auth Contexts +* Get lib path dinamically +* Check for x509Cert of the IdP when loading settings, even if the security index was not provided +* Support Statements with Attribute elements with the same name enabling the allowRepeatAttributeName setting + +v3.4.1 +* Add setSchemasPath to Auth class and fix backward compatibility + +v.3.4.0 +* Support rejecting unsolicited SAMLResponses. +* Support stric destination matching. +* Reject SAMLResponse if requestID was provided to the validotr but the InResponseTo attributeof the SAMLResponse is missing +* Check destination against the getSelfURLNoQuery as well on LogoutRequest and LogoutResponse as we do on Response +* Improve getSelfRoutedURLNoQuery method +* Only add responseUrl to the settings if ResponseLocation present in the IdPMetadataParser +* Remove use of $_GET on static method validateBinarySign +* Fix error message when Assertion and NameId are both encrypted (not supported) + +v.3.3.1 +* Update xmlseclibs to 3.0.4 +* Remove Comparison atribute from RequestedAuthnContext when setting has empty value + +v.3.3.0 +* Set true as the default value for strict setting +* Relax comparision of false on SignMetadata +* Fix CI + +v.3.2.1 +* Add missed nameIdValueReq parameter to buildAuthnRequest method + +v.3.2.0 +* Add support for Subjects on AuthNRequests by the new parameter nameIdValueReq +* Support SLO ResponseLocation +* [#344](https://github.com/onelogin/php-saml/issues/344) Raise errors on IdPMetadataParser::parseRemoteXML and IdPMetadataParser::parseFileXML +* [#356](https://github.com/onelogin/php-saml/issues/356) Support 'x509cert' and 'privateKey' on signMetadata security setting + +v.3.1.1 +* Force to use at least xmlseclibs 3.0.3 for security reasons +* [#367](https://github.com/onelogin/php-saml/pull/367) Move the creation of the AuthnRequest to separate function +* Set strict=true on config examples +* Move phpunit.xml + +v.3.1.0 +* Security improvement suggested by Nils Engelbertz to prevent DDOS by expansion of internally defined entities (XEE) +* Fix setting_example.php servicename parameter + +v.3.0.0 * Remove mcrypt dependency. Compatible with PHP 7.2 * xmlseclibs now is not part of the toolkit and need to be installed from original source +v.2.18.0 +* Support rejecting unsolicited SAMLResponses. +* Support stric destination matching. +* Reject SAMLResponse if requestID was provided to the validotr but the InResponseTo attributeof the SAMLResponse is missing +* Check destination against the getSelfURLNoQuery as well on LogoutRequest and LogoutResponse as we do on Response +* Improve getSelfRoutedURLNoQuery method +* Only add responseUrl to the settings if ResponseLocation present in the IdPMetadataParser +* Remove use of $_GET on static method validateBinarySign +* Fix error message when Assertion and NameId are both encrypted (not supported) + +v.2.17.1 +* Update xmlseclibs to 3.0.4 +* Remove Comparison atribute from RequestedAuthnContext when setting has empty value + +v.2.17.0 +* Set true as the default value for strict setting +* Support 'x509cert' and 'privateKey' on signMetadata security settings +* Relax comparision of false on SignMetadata +* Fix CI + +v.2.16.0 +* Support SLO ResponseLocation +* [#344](https://github.com/onelogin/php-saml/issues/344) Raise errors on IdPMetadataParser::parseRemoteXML and IdPMetadataParser::parseFileXML +* Adjusted acs endpoint to extract NameQualifier and SPNameQualifier from SAMLResponse. Adjusted single logout service to provide NameQualifier and SPNameQualifier to logout method. Add getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from Auth and LogoutRequest constructor to support SPNameQualifier parameter. Align LogoutRequest constructor with SAML specs +* Add support for Subjects on AuthNRequests by the new parameter +* Set strict=true on config examples + +v.2.15.0 +* Security improvement suggested by Nils Engelbertz to prevent DDOS by expansion of internally defined entities (XEE) +* Fix bug on settings_example.php + +v.2.14.0 +* Add parameter to the decryptElement method to make optional the formatting +* [#283](https://github.com/onelogin/php-saml/pull/283) New method of importing a decrypted assertion into the XML document to replace the EncryptedAssertion. Fix signature issues on Signed Encrypted Assertions with default namespace +* Allow the getSPMetadata() method to always include the encryption Key Descriptor +* Change some Fatal Error to Exceptions +* [#265](https://github.com/onelogin/php-saml/issues/265) Support parameters at getSPMetadata method +* Avoid calling static method using this + +v.2.13.0 +* Update xmlseclibs with some fixes. +* Add extra protection verifying the Signature algorithm used on SignedInfo element, not only rely on the xmlseclibs verify / verifySignature methods. +* Add getAttributesWithFriendlyName method which returns the set of SAML attributes indexed by FriendlyName +* Fix bug on parseRemoteXML and parseFileXML. Internal calls to parseXML missed the desiredNameIdFormat parameter + v.2.12.0 * Improve Time management. Use DateTime/DateTimeZone classes. * Escape error messages in debug mode diff --git a/vendor/onelogin/php-saml/README.md b/vendor/onelogin/php-saml/README.md index 15402b6..99c24c4 100644 --- a/vendor/onelogin/php-saml/README.md +++ b/vendor/onelogin/php-saml/README.md @@ -1,4 +1,4 @@ -# OneLogin's SAML PHP Toolkit Compatible with PHP 5.X & 7.X +# OneLogin's SAML PHP Toolkit Compatible with PHP 7.X & 8.X [![Build Status](https://api.travis-ci.org/onelogin/php-saml.png?branch=master)](http://travis-ci.org/onelogin/php-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/php-saml/badge.png)](https://coveralls.io/r/onelogin/php-saml) [![License](https://poser.pugx.org/onelogin/php-saml/license.png)](https://packagist.org/packages/onelogin/php-saml) @@ -10,7 +10,7 @@ and supported by OneLogin Inc. Warning ------- -This version is compatible with PHP 7.X and does not include xmlseclibs (you will need to install it via composer, dependency described in composer.json) +This version is compatible with PHP >=7.3 and 8.X and does not include xmlseclibs (you will need to install it via composer, dependency described in composer.json) Security Guidelines ------------------- @@ -82,7 +82,13 @@ Installation ### Code ### -#### Option 1. Download from github #### +#### Option 1. clone the repository from github #### + +git clone git@github.com:onelogin/php-saml.git + +Then pull the 3.X.X branch/tag + +#### Option 2. Download from github #### The toolkit is hosted on github. You can download it from: @@ -94,7 +100,10 @@ Copy the core of the library inside the php application. (each application has i structure so take your time to locate the PHP SAML toolkit in the best place). See the "Guide to add SAML support to my app" to know how. -#### Option 2. Composer #### +Take in mind that the compressed file only contains the main files. +If you plan to play with the demos, use the Option 1. + +#### Option 3. Composer #### The toolkit supports [composer](https://getcomposer.org/). You can find the `onelogin/php-saml` package at https://packagist.org/packages/onelogin/php-saml @@ -115,7 +124,9 @@ Your settings are at risk of being deleted when updating packages using `compose Compatibility ------------- -This 3.X.X supports PHP 7.X. but can be used with PHP >=5.4 as well (5.6.24+ recommended for security reasons). +This 4.X.X supports PHP >=7.3 . + +It is not compatible with PHP5.6 or PHP7.0. Namespaces ---------- @@ -137,6 +148,37 @@ environment is not secure and will be exposed to attacks. In production also we highly recommended to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment. + +### Avoiding Open Redirect attacks ### + +Some implementations uses the RelayState parameter as a way to control the flow when SSO and SLO succeeded. So basically the +user is redirected to the value of the RelayState. + +If you are using Signature Validation on the HTTP-Redirect binding, you will have the RelayState value integrity covered, otherwise, and +on HTTP-POST binding, you can't trust the RelayState so before +executing the validation, you need to verify that its value belong +a trusted and expected URL. + +Read more about Open Redirect [CWE-601](https://cwe.mitre.org/data/definitions/601.html). + + +### Avoiding Reply attacks ### + +A reply attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO). + +SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that +make harder this kind of attacks, but they are still possible. + +In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs alredy valdidated and processed. Those values only need +to be stored the amount of time of the SAML Message life time, so +we don't need to store all processed message/assertion Ids, but the most recent ones. + +The OneLogin_Saml2_Auth class contains the [getLastRequestID](https://github.com/onelogin/php-saml/blob/b8214b74dd72960fa6aa88ab454667c64cea935c/src/Saml2/Auth.php#L657), [getLastMessageId](https://github.com/onelogin/php-saml/blob/b8214b74dd72960fa6aa88ab454667c64cea935c/src/Saml2/Auth.php#L762) and [getLastAssertionId](https://github.com/onelogin/php-saml/blob/b8214b74dd72960fa6aa88ab454667c64cea935c/src/Saml2/Auth.php#L770) methods to retrieve the IDs + +Checking that the ID of the current Message/Assertion does not exists in the list of the ones already processed will prevent reply +attacks. + + Getting started --------------- @@ -247,7 +289,7 @@ $settings = array( // or unencrypted messages if it expects them to be signed or encrypted. // Also it will reject the messages if the SAML standard is not strictly // followed: Destination, NameId, Conditions ... are validated too. - 'strict' => false, + 'strict' => true, // Enable debug mode (to print errors). 'debug' => false, @@ -335,6 +377,9 @@ $settings = array( 'singleLogoutService' => array( // URL Location of the IdP where SLO Request will be sent. 'url' => '', + // URL location of the IdP where SLO Response will be sent (ResponseLocation) + // if not set, url for the SLO Request will be used + 'responseUrl' => '', // SAML protocol binding to be used when returning the // message. OneLogin Toolkit supports the HTTP-Redirect binding // only for this endpoint. @@ -415,10 +460,14 @@ $advancedSettings = array( 'logoutResponseSigned' => false, /* Sign the Metadata - False || True (use sp certs) || array( - keyFileName => 'metadata.key', - certFileName => 'metadata.crt' - ) + False || True (use sp certs) || array ( + 'keyFileName' => 'metadata.key', + 'certFileName' => 'metadata.crt' + ) + || array ( + 'x509cert' => '', + 'privateKey' => '' + ) */ 'signMetadata' => false, @@ -448,7 +497,7 @@ $advancedSettings = array( // Set to false and no AuthContext will be sent in the AuthNRequest. // Set true or don't present this parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'. // Set an array with the possible auth context values: array('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'). - 'requestedAuthnContext' => true, + 'requestedAuthnContext' => false, // Indicates if the SP will validate all received xmls. // (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true). @@ -458,6 +507,20 @@ $advancedSettings = array( // attribute will not be rejected for this fact. 'relaxDestinationValidation' => false, + // If true, Destination URL should strictly match to the address to + // which the response has been sent. + // Notice that if 'relaxDestinationValidation' is true an empty Destintation + // will be accepted. + 'destinationStrictlyMatches' => false, + + // If true, the toolkit will not raised an error when the Statement Element + // contain atribute elements with name duplicated + 'allowRepeatAttributeName' => false, + + // If true, SAMLResponses with an InResponseTo value will be rejectd if not + // AuthNRequest ID provided to the validation method. + 'rejectUnsolicitedResponsesWithInResponseTo' => false, + // Algorithm that the toolkit will use on signing process. Options: // 'http://www.w3.org/2000/09/xmldsig#rsa-sha1' // 'http://www.w3.org/2000/09/xmldsig#dsa-sha1' @@ -475,6 +538,17 @@ $advancedSettings = array( // Notice that sha1 is a deprecated algorithm and should not be used 'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256', + // Algorithm that the toolkit will use for encryption process. Options: + // 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' + // 'http://www.w3.org/2009/xmlenc11#aes128-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes192-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes256-gcm'; + // Notice that aes-cbc are not consider secure anymore so should not be used + 'encryption_algorithm' => 'http://www.w3.org/2009/xmlenc11#aes128-gcm', + // ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses // uppercase. Turn it True for ADFS compatibility on signature verification 'lowercaseUrlencoding' => false, @@ -600,13 +674,14 @@ $auth = new OneLogin\Saml2\Auth(); $auth->login($newTargetUrl); ``` -The login method can receive other five optional parameters: +The login method can receive other six optional parameters: * `$parameters` - An array of parameters that will be added to the `GET` in the HTTP-Redirect. * `$forceAuthn` - When true the `AuthNRequest` will set the `ForceAuthn='true'` * `$isPassive` - When true the `AuthNRequest` will set the `Ispassive='true'` * `$strict` - True if we want to stay (returns the url string) False to redirect * `$setNameIdPolicy` - When true the AuthNRequest will set a nameIdPolicy element. +* `$nameIdValueReq` - Indicates to the IdP the subject that should be authenticated. If a match on the future SAMLResponse ID and the AuthNRequest ID to be sent is required, that AuthNRequest ID must to be extracted and saved. @@ -705,11 +780,13 @@ if (!$auth->isAuthenticated()) { $_SESSION['samlUserdata'] = $auth->getAttributes(); $_SESSION['samlNameId'] = $auth->getNameId(); $_SESSION['samlNameIdFormat'] = $auth->getNameIdFormat(); -$_SESSION['samlNameidNameQualifier' = $auth->getNameIdNameQualifier(); -$_SESSION['samlNameidSPNameQualifier' = $auth->getNameIdSPNameQualifier(); +$_SESSION['samlNameidNameQualifier'] = $auth->getNameIdNameQualifier(); +$_SESSION['samlNameidSPNameQualifier'] = $auth->getNameIdSPNameQualifier(); $_SESSION['samlSessionIndex'] = $auth->getSessionIndex(); if (isset($_POST['RelayState']) && OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) { + // To avoid 'Open Redirect' attacks, before execute the + // redirection confirm the value of $_POST['RelayState'] is a // trusted URL. $auth->redirectTo($_POST['RelayState']); } @@ -966,7 +1043,7 @@ A more complex logout with all the parameters: ``` $auth = new OneLogin\Saml2\Auth(); $returnTo = null; -$paramters = array(); +$parameters = array(); $nameId = null; $sessionIndex = null; $nameIdFormat = null; @@ -988,13 +1065,13 @@ if (isset($_SESSION['samlNameIdNameQualifier'])) { if (isset($_SESSION['samlNameIdSPNameQualifier'])) { $nameIdSPNameQualifier = $_SESSION['samlNameIdSPNameQualifier']; } -$auth->logout($returnTo, $paramters, $nameId, $sessionIndex, false, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); +$auth->logout($returnTo, $parameters, $nameId, $sessionIndex, false, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); ``` If a match on the future LogoutResponse ID and the LogoutRequest ID to be sent is required, that LogoutRequest ID must to be extracted and stored. ```php -$sloBuiltUrl = $auth->logout(null, $paramters, $nameId, $sessionIndex, true); +$sloBuiltUrl = $auth->logout(null, $parameters, $nameId, $sessionIndex, true); $_SESSION['LogoutRequestID'] = $auth->getLastRequestID(); header('Pragma: no-cache'); header('Cache-Control: no-cache, must-revalidate'); @@ -1048,6 +1125,8 @@ if (isset($_GET['sso'])) { // SSO action. Will send an AuthNRequest to the I $_SESSION['samlUserdata'] = $auth->getAttributes(); // Retrieves user data if (isset($_POST['RelayState']) && OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) { + // To avoid 'Open Redirect' attacks, before execute the + // redirection confirm the value of $_POST['RelayState'] is a // trusted URL. $auth->redirectTo($_POST['RelayState']); // Redirect if there is a } // relayState set } else if (isset($_GET['sls'])) { // Single Logout Service @@ -1056,7 +1135,7 @@ if (isset($_GET['sso'])) { // SSO action. Will send an AuthNRequest to the I if (empty($errors)) { echo '

Sucessfully logged out

'; } else { - echo '

' . implode(', ', $errors) . '

'; + echo '

' . htmlentities(implode(', ', $errors)) . '

'; } } @@ -1096,7 +1175,7 @@ php-saml toolkit uses a bunch of methods in OneLogin\Saml2\Utils that try to gue * `getSelfURLNoQuery` Returns the URL of the current host + current view. * `getSelfRoutedURLNoQuery` Returns the routed URL of the current host + current view. -getSelfURLNoQuery and getSelfRoutedURLNoQuery are used to calculate the currentURL in order to valdate SAML elements like Destination or Recipient. +getSelfURLNoQuery and getSelfRoutedURLNoQuery are used to calculate the currentURL in order to validate SAML elements like Destination or Recipient. When the PHP application is behind a proxy or a load balancer we can execute `setProxyVars(true)` and `setSelfPort` and `isHTTPS` will take care of the `$_SERVER["HTTP_X_FORWARDED_PORT"]` and `$_SERVER['HTTP_X_FORWARDED_PROTO']` vars (otherwise they are ignored). @@ -1167,7 +1246,7 @@ Main class of OneLogin PHP Toolkit * `getNameId` - Returns the nameID * `getNameIdFormat` - Gets the NameID Format provided by the SAML response from the IdP. * `getNameIdNameQualifier` - Gets the NameID NameQualifier provided from the SAML Response String. - * `getNameIdNameSPQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. + * `getNameIdSPNameQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. * `getSessionIndex` - Gets the SessionIndex from the AuthnStatement. * `getErrors` - Returns if there were any error * `getSSOurl` - Gets the SSO url. @@ -1205,7 +1284,7 @@ SAML 2 Authentication Response class * `getNameId` - Gets the NameID provided by the SAML response from the IdP. * `getNameIdFormat` - Gets the NameID Format provided by the SAML response from the IdP. * `getNameIdNameQualifier` - Gets the NameID NameQualifier provided from the SAML Response String. - * `getNameIdNameSPQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. + * `getNameIdSPNameQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. * `getSessionNotOnOrAfter` - Gets the SessionNotOnOrAfter from the AuthnStatement * `getSessionIndex` - Gets the SessionIndex from the AuthnStatement. @@ -1340,6 +1419,8 @@ Auxiliary class that contains several methods to retrieve and process IdP metada * `parseXML` - Get IdP Metadata Info from XML. * `injectIntoSettings` - Inject metadata info into php-saml settings array. +The class does not validate in any way the URL that is introduced on methods like parseRemoteXML in order to retrieve the remove XML. Usually is the same administrator that handles the Service Provider the ones that set the URL that should belong to a trusted third-party IdP. +But there are other scenarios, like a SAAS app where the administrator of the app delegates on other administrators. In such case, extra protection should be taken in order to validate such URL inputs and avoid attacks like SSRF. For more info, look at the source code; each method is documented and details about what it does and how to use it are provided. Make sure to also check the doc folder where @@ -1398,7 +1479,7 @@ Once the SP is configured, the metadata of the SP is published at the process, the `index.php` view. 2.2 in the second link we access to (`attrs.php`) have the same process - described at 2.1 with the diference that as `RelayState` is set the `attrs.php`. + described at 2.1 with the difference that as `RelayState` is set the `attrs.php`. 3. The SAML Response is processed in the ACS (`index.php?acs`), if the Response is not valid, the process stops here and a message is shown. Otherwise we @@ -1425,7 +1506,7 @@ Once the SP is configured, the metadata of the SP is published at the session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP. Notice that all the SAML Requests and Responses are handled by a unique file, -the `index.php` file and how `GET` paramters are used to know the action that +the `index.php` file and how `GET` parameters are used to know the action that must be done. diff --git a/vendor/onelogin/php-saml/advanced_settings_example.php b/vendor/onelogin/php-saml/advanced_settings_example.php index 7da4bb3..d9c16e2 100644 --- a/vendor/onelogin/php-saml/advanced_settings_example.php +++ b/vendor/onelogin/php-saml/advanced_settings_example.php @@ -33,10 +33,14 @@ 'logoutResponseSigned' => false, /* Sign the Metadata - False || True (use sp certs) || array( - keyFileName => 'metadata.key', - certFileName => 'metadata.crt' - ) + False || True (use sp certs) || array ( + 'keyFileName' => 'metadata.key', + 'certFileName' => 'metadata.crt' + ) + || array ( + 'x509cert' => '', + 'privateKey' => '' + ) */ 'signMetadata' => false, @@ -81,6 +85,20 @@ // attribute will not be rejected for this fact. 'relaxDestinationValidation' => false, + // If true, Destination URL should strictly match to the address to + // which the response has been sent. + // Notice that if 'relaxDestinationValidation' is true an empty Destintation + // will be accepted. + 'destinationStrictlyMatches' => false, + + // If true, the toolkit will not raised an error when the Statement Element + // contain atribute elements with name duplicated + 'allowRepeatAttributeName' => false, + + // If true, SAMLResponses with an InResponseTo value will be rejectd if not + // AuthNRequest ID provided to the validation method. + 'rejectUnsolicitedResponsesWithInResponseTo' => false, + // Algorithm that the toolkit will use on signing process. Options: // 'http://www.w3.org/2000/09/xmldsig#rsa-sha1' // 'http://www.w3.org/2000/09/xmldsig#dsa-sha1' @@ -98,6 +116,17 @@ // Notice that sha1 is a deprecated algorithm and should not be used 'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256', + // Algorithm that the toolkit will use for encryption process. Options: + // 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' + // 'http://www.w3.org/2009/xmlenc11#aes128-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes192-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes256-gcm'; + // Notice that aes-cbc are not consider secure anymore so should not be used + 'encryption_algorithm' => 'http://www.w3.org/2009/xmlenc11#aes128-gcm', + // ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses // uppercase. Turn it True for ADFS compatibility on signature verification 'lowercaseUrlencoding' => false, diff --git a/vendor/onelogin/php-saml/composer.json b/vendor/onelogin/php-saml/composer.json index bab318e..42290e8 100644 --- a/vendor/onelogin/php-saml/composer.json +++ b/vendor/onelogin/php-saml/composer.json @@ -15,20 +15,28 @@ "source": "https://github.com/onelogin/php-saml/" }, "require": { - "php": ">=5.4", - "robrichards/xmlseclibs": "^3.0" + "php": ">=7.3", + "robrichards/xmlseclibs": ">=3.1.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1", - "php-coveralls/php-coveralls": "^1.0.2 || ^2.0", - "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", - "phploc/phploc": "^2.1 || ^3.0 || ^4.0", - "pdepend/pdepend": "^2.5.0", - "squizlabs/php_codesniffer": "^3.1.1" + "phpunit/phpunit": "^9.5", + "php-coveralls/php-coveralls": "^2.0", + "sebastian/phpcpd": "^4.0 || ^5.0 || ^6.0 ", + "phploc/phploc": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "pdepend/pdepend": "^2.8.0", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true }, "suggest": { "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)", "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", - "ext-gettext": "Install gettext and php5-gettext libs to handle translations" + "ext-dom": "Install xml lib", + "ext-zlib": "Install zlib" } } diff --git a/vendor/onelogin/php-saml/phpunit.xml b/vendor/onelogin/php-saml/phpunit.xml new file mode 100644 index 0000000..600c3ba --- /dev/null +++ b/vendor/onelogin/php-saml/phpunit.xml @@ -0,0 +1,20 @@ + + + + + ./src + + + + + + + + + + + ./tests/src + + + + diff --git a/vendor/onelogin/php-saml/settings_example.php b/vendor/onelogin/php-saml/settings_example.php index c9f4108..981a21a 100644 --- a/vendor/onelogin/php-saml/settings_example.php +++ b/vendor/onelogin/php-saml/settings_example.php @@ -5,7 +5,7 @@ // or unencrypted messages if it expects them signed or encrypted // Also will reject the messages if not strictly follow the SAML // standard: Destination, NameId, Conditions ... are validated too. - 'strict' => false, + 'strict' => true, // Enable debug mode (to print errors) 'debug' => false, @@ -27,14 +27,14 @@ 'url' => '', // SAML protocol binding to be used when returning the // message. Onelogin Toolkit supports for this endpoint the - // HTTP-Redirect binding only + // HTTP-POST binding only 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', ), // If you need to specify requested attributes, set a // attributeConsumingService. nameFormat, attributeValue and // friendlyName can be omitted. Otherwise remove this section. "attributeConsumingService"=> array( - "ServiceName" => "SP test", + "serviceName" => "SP test", "serviceDescription" => "Test Service", "requestedAttributes" => array( array( @@ -86,13 +86,16 @@ 'url' => '', // SAML protocol binding to be used when returning the // message. Onelogin Toolkit supports for this endpoint the - // HTTP-POST binding only + // HTTP-Redirect binding only 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', ), // SLO endpoint info of the IdP. 'singleLogoutService' => array( // URL Location of the IdP where the SP will send the SLO Request 'url' => '', + // URL location of the IdP where the SP SLO Response will be sent (ResponseLocation) + // if not set, url for the SLO Request will be used + 'responseUrl' => '', // SAML protocol binding to be used when returning the // message. Onelogin Toolkit supports for this endpoint the // HTTP-Redirect binding only diff --git a/vendor/onelogin/php-saml/src/Saml2/Auth.php b/vendor/onelogin/php-saml/src/Saml2/Auth.php index 636c034..e5c4d54 100644 --- a/vendor/onelogin/php-saml/src/Saml2/Auth.php +++ b/vendor/onelogin/php-saml/src/Saml2/Auth.php @@ -168,13 +168,14 @@ class Auth * Initializes the SP SAML instance. * * @param array|null $settings Setting data + * @param bool $spValidationOnly if true, The library will only validate the SAML SP settings, * * @throws Exception * @throws Error */ - public function __construct(array $settings = null) + public function __construct(array $settings = null, bool $spValidationOnly = false) { - $this->_settings = new Settings($settings); + $this->_settings = new Settings($settings, $spValidationOnly); } /** @@ -206,6 +207,17 @@ public function setStrict($value) $this->_settings->setStrict($value); } + /** + * Set schemas path + * + * @param string $path + * @return $this + */ + public function setSchemasPath($path) + { + $this->_paths['schemas'] = $path; + } + /** * Process the SAML Response sent by the IdP. * @@ -322,7 +334,7 @@ public function processSLO($keepLocalSession = false, $requestId = null, $retrie $parameters['Signature'] = $signature; } - return $this->redirectTo($this->getSLOurl(), $parameters, $stay); + return $this->redirectTo($this->getSLOResponseUrl(), $parameters, $stay); } } else { $this->_errors[] = 'invalid_binding'; @@ -520,14 +532,15 @@ public function getAttributeWithFriendlyName($friendlyName) * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true' * @param bool $stay True if we want to stay (returns the url string) False to redirect * @param bool $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element + * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated * * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters - * + * * @throws Error */ - public function login($returnTo = null, array $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true) + public function login($returnTo = null, array $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true, $nameIdValueReq = null) { - $authnRequest = new AuthnRequest($this->_settings, $forceAuthn, $isPassive, $setNameIdPolicy); + $authnRequest = $this->buildAuthnRequest($this->_settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq); $this->_lastRequest = $authnRequest->getXML(); $this->_lastRequestID = $authnRequest->getId(); @@ -606,32 +619,37 @@ public function logout($returnTo = null, array $parameters = array(), $nameId = return $this->redirectTo($sloUrl, $parameters, $stay); } - /** - * Gets the SSO url. + /** + * Gets the IdP SSO url. * - * @return string The url of the Single Sign On Service + * @return string The url of the IdP Single Sign On Service */ public function getSSOurl() { - $idpData = $this->_settings->getIdPData(); - return $idpData['singleSignOnService']['url']; + return $this->_settings->getIdPSSOUrl(); } /** - * Gets the SLO url. + * Gets the IdP SLO url. * - * @return string|null The url of the Single Logout Service + * @return string|null The url of the IdP Single Logout Service */ public function getSLOurl() { - $url = null; - $idpData = $this->_settings->getIdPData(); - if (isset($idpData['singleLogoutService']) && isset($idpData['singleLogoutService']['url'])) { - $url = $idpData['singleLogoutService']['url']; - } - return $url; + return $this->_settings->getIdPSLOUrl(); } + /** + * Gets the IdP SLO response url. + * + * @return string|null The response url of the IdP Single Logout Service + */ + public function getSLOResponseUrl() + { + return $this->_settings->getIdPSLOResponseUrl(); + } + + /** * Gets the ID of the last AuthNRequest or LogoutRequest generated by the Service Provider. * @@ -642,6 +660,22 @@ public function getLastRequestID() return $this->_lastRequestID; } + /** + * Creates an AuthnRequest + * + * @param Settings $settings Setting data + * @param bool $forceAuthn When true the AuthNRequest will set the ForceAuthn='true' + * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true' + * @param bool $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element + * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated + * + * @return AuthnRequest The AuthnRequest object + */ + public function buildAuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq = null) + { + return new AuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq); + } + /** * Generates the Signature for a SAML Request * @@ -689,7 +723,7 @@ public function buildResponseSignature($samlResponse, $relayState, $signAlgorith * @throws Exception * @throws Error */ - private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type="SAMLRequest") + private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type = "SAMLRequest") { $key = $this->_settings->getSPkey(); if (empty($key)) { diff --git a/vendor/onelogin/php-saml/src/Saml2/AuthnRequest.php b/vendor/onelogin/php-saml/src/Saml2/AuthnRequest.php index 2dd6bd2..fd9afb5 100644 --- a/vendor/onelogin/php-saml/src/Saml2/AuthnRequest.php +++ b/vendor/onelogin/php-saml/src/Saml2/AuthnRequest.php @@ -44,22 +44,33 @@ class AuthnRequest /** * Constructs the AuthnRequest object. * - * @param Settings $settings SAML Toolkit Settings - * @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true' - * @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true' - * @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy + * @param Settings $settings SAML Toolkit Settings + * @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true' + * @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true' + * @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy + * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated */ - public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = false, $isPassive = false, $setNameIdPolicy = true) + public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = false, $isPassive = false, $setNameIdPolicy = true, $nameIdValueReq = null) { $this->_settings = $settings; $spData = $this->_settings->getSPData(); - $idpData = $this->_settings->getIdPData(); $security = $this->_settings->getSecurityData(); $id = Utils::generateUniqueID(); $issueInstant = Utils::parseTime2SAML(time()); + $subjectStr = ""; + if (isset($nameIdValueReq)) { + $subjectStr = << + {$nameIdValueReq} + + +SUBJECT; + } + $nameIdPolicyStr = ''; if ($setNameIdPolicy) { $nameIDPolicyFormat = $spData['NameIDFormat']; @@ -68,6 +79,7 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = fa } $nameIdPolicyStr = << @@ -114,14 +126,20 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = fa $authnComparison = $security['requestedAuthnContextComparison']; } + $authnComparisonAttr = ''; + if (!empty($authnComparison)) { + $authnComparisonAttr = sprintf('Comparison="%s"', $authnComparison); + } + if ($security['requestedAuthnContext'] === true) { $requestedAuthnStr = << + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport REQUESTEDAUTHN; } else { - $requestedAuthnStr .= " \n"; + $requestedAuthnStr .= " \n"; foreach ($security['requestedAuthnContext'] as $contextValue) { $requestedAuthnStr .= " ".$contextValue."\n"; } @@ -131,6 +149,7 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = fa $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES); $acsUrl = htmlspecialchars($spData['assertionConsumerService']['url'], ENT_QUOTES); + $destination = $this->_settings->getIdPSSOUrl(); $request = << - {$spEntityId} -{$nameIdPolicyStr} -{$requestedAuthnStr} + {$spEntityId}{$subjectStr}{$nameIdPolicyStr}{$requestedAuthnStr} AUTHNREQUEST; diff --git a/vendor/onelogin/php-saml/src/Saml2/Constants.php b/vendor/onelogin/php-saml/src/Saml2/Constants.php index 21261fb..1b467dd 100644 --- a/vendor/onelogin/php-saml/src/Saml2/Constants.php +++ b/vendor/onelogin/php-saml/src/Saml2/Constants.php @@ -64,9 +64,11 @@ class Constants const AC_PASSWORD_PROTECTED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; const AC_X509 = 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'; const AC_SMARTCARD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard'; + const AC_SMARTCARD_PKI = 'urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI'; const AC_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos'; const AC_WINDOWS = 'urn:federation:authentication:windows'; const AC_TLS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient'; + const AC_RSATOKEN = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken'; // Subject Confirmation const CM_BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer'; diff --git a/vendor/onelogin/php-saml/src/Saml2/IdPMetadataParser.php b/vendor/onelogin/php-saml/src/Saml2/IdPMetadataParser.php index e305125..a4fcc30 100644 --- a/vendor/onelogin/php-saml/src/Saml2/IdPMetadataParser.php +++ b/vendor/onelogin/php-saml/src/Saml2/IdPMetadataParser.php @@ -26,6 +26,10 @@ class IdPMetadataParser /** * Get IdP Metadata Info from URL * + * This class does not validate in any way the URL that is introduced, + * make sure to validate it properly before use it in the parseRemoteXML + * method in order to avoid security issues like SSRF attacks. + * * @param string $url URL where the IdP metadata is published * @param string $entityId Entity Id of the desired IdP, if no * entity Id is provided and the XML @@ -43,6 +47,9 @@ public static function parseRemoteXML($url, $entityId = null, $desiredNameIdForm try { $ch = curl_init($url); + curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP); + curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP); + curl_setopt($ch, CURLOPT_MAXREDIRS, 5); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); @@ -56,6 +63,7 @@ public static function parseRemoteXML($url, $entityId = null, $desiredNameIdForm throw new Exception(curl_error($ch), curl_errno($ch)); } } catch (Exception $e) { + throw new Exception('Error on parseRemoteXML. '.$e->getMessage()); } return $metadataInfo; } @@ -84,6 +92,7 @@ public static function parseFileXML($filepath, $entityId = null, $desiredNameIdF $metadataInfo = self::parseXML($data, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding); } } catch (Exception $e) { + throw new Exception('Error on parseFileXML. '.$e->getMessage()); } return $metadataInfo; } @@ -158,6 +167,10 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n 'url' => $sloNodes->item(0)->getAttribute('Location'), 'binding' => $sloNodes->item(0)->getAttribute('Binding') ); + + if ($sloNodes->item(0)->hasAttribute('ResponseLocation')) { + $metadataInfo['idp']['singleLogoutService']['responseUrl'] = $sloNodes->item(0)->getAttribute('ResponseLocation'); + } } $keyDescriptorCertSigningNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "encryption"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); diff --git a/vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php b/vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php index 2e9258c..108c49b 100644 --- a/vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php +++ b/vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php @@ -104,7 +104,7 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $request = null, $nameIdFormat = Constants::NAMEID_ENTITY; } - /* From saml-core-2.0-os 8.3.6, when the entity Format is used: + /* From saml-core-2.0-os 8.3.6, when the entity Format is used: "The NameQualifier, SPNameQualifier, and SPProvidedID attributes MUST be omitted. */ if (!empty($nameIdFormat) && $nameIdFormat == Constants::NAMEID_ENTITY) { @@ -122,12 +122,14 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $request = null, $nameIdSPNameQualifier, $nameIdFormat, $cert, - $nameIdNameQualifier + $nameIdNameQualifier, + $security['encryption_algorithm'] ); $sessionIndexStr = isset($sessionIndex) ? "{$sessionIndex}" : ""; $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES); + $destination = $this->_settings->getIdPSLOUrl(); $logoutRequest = << + Destination="{$destination}"> {$spEntityId} {$nameIdObj} {$sessionIndexStr} @@ -184,7 +186,7 @@ public function getRequest($deflate = null) * * @return string ID * - * @throws OneLogin_Saml2_Error + * @throws Error */ public static function getID($request) { @@ -278,7 +280,7 @@ public static function getNameIdData($request, $key = null) * @param string|null $key The SP key * * @return string Name ID Value - * + * * @throws Error * @throws Exception * @throws ValidationError @@ -295,7 +297,7 @@ public static function getNameId($request, $key = null) * @param string|DOMDocument $request Logout Request Message * * @return string|null $issuer The Issuer - * + * * @throws Exception */ public static function getIssuer($request) @@ -324,7 +326,7 @@ public static function getIssuer($request) * @param string|DOMDocument $request Logout Request Message * * @return array The SessionIndex value - * + * * @throws Exception */ public static function getSessionIndexes($request) @@ -350,7 +352,7 @@ public static function getSessionIndexes($request) * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature * * @return bool If the Logout Request is or not valid - * + * * @throws Exception * @throws ValidationError */ @@ -368,7 +370,7 @@ public function isValid($retrieveParametersFromServer = false) $security = $this->_settings->getSecurityData(); if ($security['wantXMLValidation']) { - $res = Utils::validateXML($dom, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($dom, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); if (!$res instanceof DOMDocument) { throw new ValidationError( "Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd", @@ -393,11 +395,25 @@ public function isValid($retrieveParametersFromServer = false) // Check destination if ($dom->documentElement->hasAttribute('Destination')) { $destination = $dom->documentElement->getAttribute('Destination'); - if (!empty($destination) && strpos($destination, $currentURL) === false) { - throw new ValidationError( - "The LogoutRequest was received at $currentURL instead of $destination", - ValidationError::WRONG_DESTINATION - ); + if (empty($destination)) { + if (!$security['relaxDestinationValidation']) { + throw new ValidationError( + "The LogoutRequest has an empty Destination value", + ValidationError::EMPTY_DESTINATION + ); + } + } else { + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURL); + if (strncmp($destination, $currentURL, $urlComparisonLength) !== 0) { + $currentURLNoRouted = Utils::getSelfURLNoQuery(); + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURLNoRouted); + if (strncmp($destination, $currentURLNoRouted, $urlComparisonLength) !== 0) { + throw new ValidationError( + "The LogoutRequest was received at $currentURL instead of $destination", + ValidationError::WRONG_DESTINATION + ); + } + } } } diff --git a/vendor/onelogin/php-saml/src/Saml2/LogoutResponse.php b/vendor/onelogin/php-saml/src/Saml2/LogoutResponse.php index 2f376bb..9c3f020 100644 --- a/vendor/onelogin/php-saml/src/Saml2/LogoutResponse.php +++ b/vendor/onelogin/php-saml/src/Saml2/LogoutResponse.php @@ -65,10 +65,9 @@ class LogoutResponse * * @param Settings $settings Settings. * @param string|null $response An UUEncoded SAML Logout response from the IdP. - * + * * @throws Error * @throws Exception - * */ public function __construct(\OneLogin\Saml2\Settings $settings, $response = null) { @@ -140,7 +139,7 @@ public function getStatus() * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature * * @return bool Returns if the SAML LogoutResponse is or not valid - * + * * @throws ValidationError */ public function isValid($requestId = null, $retrieveParametersFromServer = false) @@ -154,7 +153,7 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false $security = $this->_settings->getSecurityData(); if ($security['wantXMLValidation']) { - $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); if (!$res instanceof DOMDocument) { throw new ValidationError( "Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd", @@ -185,14 +184,27 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false $currentURL = Utils::getSelfRoutedURLNoQuery(); - // Check destination if ($this->document->documentElement->hasAttribute('Destination')) { $destination = $this->document->documentElement->getAttribute('Destination'); - if (!empty($destination) && strpos($destination, $currentURL) === false) { - throw new ValidationError( - "The LogoutResponse was received at $currentURL instead of $destination", - ValidationError::WRONG_DESTINATION - ); + if (empty($destination)) { + if (!$security['relaxDestinationValidation']) { + throw new ValidationError( + "The LogoutResponse has an empty Destination value", + ValidationError::EMPTY_DESTINATION + ); + } + } else { + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURL); + if (strncmp($destination, $currentURL, $urlComparisonLength) !== 0) { + $currentURLNoRouted = Utils::getSelfURLNoQuery(); + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURLNoRouted); + if (strncmp($destination, $currentURLNoRouted, $urlComparisonLength) !== 0) { + throw new ValidationError( + "The LogoutResponse was received at $currentURL instead of $destination", + ValidationError::WRONG_DESTINATION + ); + } + } } } @@ -246,19 +258,18 @@ public function build($inResponseTo) { $spData = $this->_settings->getSPData(); - $idpData = $this->_settings->getIdPData(); $this->id = Utils::generateUniqueID(); $issueInstant = Utils::parseTime2SAML(time()); - $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES); + $destination = $this->_settings->getIdPSLOResponseUrl(); $logoutResponse = << {$spEntityId} diff --git a/vendor/onelogin/php-saml/src/Saml2/Metadata.php b/vendor/onelogin/php-saml/src/Saml2/Metadata.php index 2efd113..922ad60 100644 --- a/vendor/onelogin/php-saml/src/Saml2/Metadata.php +++ b/vendor/onelogin/php-saml/src/Saml2/Metadata.php @@ -200,7 +200,7 @@ public static function builder($sp, $authnsign = false, $wsign = false, $validUn * @param string $digestAlgorithm Digest algorithm method * * @return string Signed Metadata - * + * * @throws Exception */ public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $digestAlgorithm = XMLSecurityDSig::SHA256) @@ -217,7 +217,7 @@ public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XML * @param bool $wantsEncrypted Whether to include the KeyDescriptor for encryption * * @return string Metadata with KeyDescriptors - * + * * @throws Exception */ public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = true) diff --git a/vendor/onelogin/php-saml/src/Saml2/Response.php b/vendor/onelogin/php-saml/src/Saml2/Response.php index 7ad31b0..813aa66 100644 --- a/vendor/onelogin/php-saml/src/Saml2/Response.php +++ b/vendor/onelogin/php-saml/src/Saml2/Response.php @@ -144,7 +144,7 @@ public function isValid($requestId = null) ); } - $status = $this->checkStatus(); + $this->checkStatus(); $singleAssertion = $this->validateNumAssertions(); if (!$singleAssertion) { @@ -172,7 +172,7 @@ public function isValid($requestId = null) if ($security['wantXMLValidation']) { $errorXmlMsg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"; - $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); if (!$res instanceof DOMDocument) { throw new ValidationError( $errorXmlMsg, @@ -182,7 +182,7 @@ public function isValid($requestId = null) // If encrypted, check also the decrypted document if ($this->encrypted) { - $res = Utils::validateXML($this->decryptedDocument, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($this->decryptedDocument, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); if (!$res instanceof DOMDocument) { throw new ValidationError( $errorXmlMsg, @@ -195,18 +195,33 @@ public function isValid($requestId = null) $currentURL = Utils::getSelfRoutedURLNoQuery(); + $responseInResponseTo = null; if ($this->document->documentElement->hasAttribute('InResponseTo')) { $responseInResponseTo = $this->document->documentElement->getAttribute('InResponseTo'); } - // Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided - if (isset($requestId) && isset($responseInResponseTo) && $requestId != $responseInResponseTo) { + if (!isset($requestId) && isset($responseInResponseTo) && $security['rejectUnsolicitedResponsesWithInResponseTo']) { throw new ValidationError( - "The InResponseTo of the Response: $responseInResponseTo, does not match the ID of the AuthNRequest sent by the SP: $requestId", + "The Response has an InResponseTo attribute: " . $responseInResponseTo . " while no InResponseTo was expected", ValidationError::WRONG_INRESPONSETO ); } + // Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided + if (isset($requestId) && $requestId != $responseInResponseTo) { + if ($responseInResponseTo == null) { + throw new ValidationError( + "No InResponseTo at the Response, but it was provided the requestId related to the AuthNRequest sent by the SP: $requestId", + ValidationError::WRONG_INRESPONSETO + ); + } else { + throw new ValidationError( + "The InResponseTo of the Response: $responseInResponseTo, does not match the ID of the AuthNRequest sent by the SP: $requestId", + ValidationError::WRONG_INRESPONSETO + ); + } + } + if (!$this->encrypted && $security['wantAssertionsEncrypted']) { throw new ValidationError( "The assertion of the Response is not encrypted and the SP requires it", @@ -254,7 +269,10 @@ public function isValid($requestId = null) // Check destination if ($this->document->documentElement->hasAttribute('Destination')) { - $destination = trim($this->document->documentElement->getAttribute('Destination')); + $destination = $this->document->documentElement->getAttribute('Destination'); + if (isset($destination)) { + $destination = trim($destination); + } if (empty($destination)) { if (!$security['relaxDestinationValidation']) { throw new ValidationError( @@ -263,10 +281,11 @@ public function isValid($requestId = null) ); } } else { - if (strpos($destination, $currentURL) !== 0) { + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURL); + if (strncmp($destination, $currentURL, $urlComparisonLength) !== 0) { $currentURLNoRouted = Utils::getSelfURLNoQuery(); - - if (strpos($destination, $currentURLNoRouted) !== 0) { + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURLNoRouted); + if (strncmp($destination, $currentURLNoRouted, $urlComparisonLength) !== 0) { throw new ValidationError( "The response was received at $currentURL instead of $destination", ValidationError::WRONG_DESTINATION @@ -292,12 +311,14 @@ public function isValid($requestId = null) // Check the issuers $issuers = $this->getIssuers(); foreach ($issuers as $issuer) { - $trimmedIssuer = trim($issuer); - if (empty($trimmedIssuer) || $trimmedIssuer !== $idPEntityId) { - throw new ValidationError( - "Invalid issuer in the Assertion/Response (expected '$idPEntityId', got '$trimmedIssuer')", - ValidationError::WRONG_ISSUER - ); + if (isset($issuer)) { + $trimmedIssuer = trim($issuer); + if (empty($trimmedIssuer) || $trimmedIssuer !== $idPEntityId) { + throw new ValidationError( + "Invalid issuer in the Assertion/Response (expected '$idPEntityId', got '$trimmedIssuer')", + ValidationError::WRONG_ISSUER + ); + } } } @@ -383,7 +404,7 @@ public function isValid($requestId = null) $encryptedIDNodes = Utils::query($this->decryptedDocument, '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID'); if ($encryptedIDNodes->length > 0) { throw new ValidationError( - 'Unsigned SAML Response that contains a signed and encrypted Assertion with encrypted nameId is not supported.', + 'SAML Response that contains an encrypted Assertion with encrypted nameId is not supported.', ValidationError::NOT_SUPPORTED ); } @@ -448,7 +469,7 @@ public function getId() /** * @return string|null the ID of the assertion in the Response - * + * * @throws ValidationError */ public function getAssertionId() @@ -538,7 +559,10 @@ public function getAudiences() $entries = $this->_queryAssertion('/saml:Conditions/saml:AudienceRestriction/saml:Audience'); foreach ($entries as $entry) { - $value = trim($entry->textContent); + $value = $entry->textContent; + if (isset($value)) { + $value = trim($value); + } if (!empty($value)) { $audiences[] = $value; } @@ -788,6 +812,9 @@ private function _getAttributesByKeyName($keyName = "Name") { $attributes = array(); $entries = $this->_queryAssertion('/saml:AttributeStatement/saml:Attribute'); + + $security = $this->_settings->getSecurityData(); + $allowRepeatAttributeName = $security['allowRepeatAttributeName']; /** @var $entry DOMNode */ foreach ($entries as $entry) { $attributeKeyNode = $entry->attributes->getNamedItem($keyName); @@ -795,11 +822,13 @@ private function _getAttributesByKeyName($keyName = "Name") continue; } $attributeKeyName = $attributeKeyNode->nodeValue; - if (in_array($attributeKeyName, array_keys($attributes))) { - throw new ValidationError( - "Found an Attribute element with duplicated ".$keyName, - ValidationError::DUPLICATED_ATTRIBUTE_NAME_FOUND - ); + if (in_array($attributeKeyName, array_keys($attributes), true)) { + if (!$allowRepeatAttributeName) { + throw new ValidationError( + "Found an Attribute element with duplicated ".$keyName, + ValidationError::DUPLICATED_ATTRIBUTE_NAME_FOUND + ); + } } $attributeValues = array(); foreach ($entry->childNodes as $childNode) { @@ -808,7 +837,12 @@ private function _getAttributesByKeyName($keyName = "Name") $attributeValues[] = $childNode->nodeValue; } } - $attributes[$attributeKeyName] = $attributeValues; + + if (in_array($attributeKeyName, array_keys($attributes), true)) { + $attributes[$attributeKeyName] = array_merge($attributes[$attributeKeyName], $attributeValues); + } else { + $attributes[$attributeKeyName] = $attributeValues; + } } return $attributes; } diff --git a/vendor/onelogin/php-saml/src/Saml2/Settings.php b/vendor/onelogin/php-saml/src/Saml2/Settings.php index a6a41b7..4d9b333 100644 --- a/vendor/onelogin/php-saml/src/Saml2/Settings.php +++ b/vendor/onelogin/php-saml/src/Saml2/Settings.php @@ -45,7 +45,7 @@ class Settings * * @var bool */ - private $_strict = false; + private $_strict = true; /** * Activate debug mode @@ -122,7 +122,7 @@ class Settings * @throws Error If any settings parameter is invalid * @throws Exception If Settings is incorrectly supplied */ - public function __construct(array $settings = null, $spValidationOnly = false) + public function __construct(array $settings = null,bool $spValidationOnly = false) { $this->_spValidationOnly = $spValidationOnly; $this->_loadPaths(); @@ -164,7 +164,7 @@ private function _loadPaths() 'base' => $basePath, 'config' => $basePath, 'cert' => $basePath.'certs/', - 'lib' => $basePath.'src/' + 'lib' => __DIR__ . '/', ); if (defined('ONELOGIN_CUSTOMPATH')) { @@ -220,7 +220,21 @@ public function getLibPath() */ public function getSchemasPath() { - return $this->_paths['lib'].'schemas/'; + if (isset($this->_paths['schemas'])) { + return $this->_paths['schemas']; + } + return __DIR__ . '/schemas/'; + } + + /** + * Set schemas path + * + * @param string $path + * @return $this + */ + public function setSchemasPath($path) + { + $this->_paths['schemas'] = $path; } /** @@ -378,6 +392,21 @@ private function _addDefaultValues() $this->_security['relaxDestinationValidation'] = false; } + // Strict Destination match validation + if (!isset($this->_security['destinationStrictlyMatches'])) { + $this->_security['destinationStrictlyMatches'] = false; + } + + // Allow duplicated Attribute Names + if (!isset($this->_security['allowRepeatAttributeName'])) { + $this->_security['allowRepeatAttributeName'] = false; + } + + // InResponseTo + if (!isset($this->_security['rejectUnsolicitedResponsesWithInResponseTo'])) { + $this->_security['rejectUnsolicitedResponsesWithInResponseTo'] = false; + } + // encrypt expected if (!isset($this->_security['wantAssertionsEncrypted'])) { $this->_security['wantAssertionsEncrypted'] = false; @@ -401,6 +430,11 @@ private function _addDefaultValues() $this->_security['digestAlgorithm'] = XMLSecurityDSig::SHA256; } + // EncryptionAlgorithm + if (!isset($this->_security['encryption_algorithm'])) { + $this->_security['encryption_algorithm'] = XMLSecurityKey::AES128_CBC; + } + if (!isset($this->_security['lowercaseUrlencoding'])) { $this->_security['lowercaseUrlencoding'] = false; } @@ -520,19 +554,26 @@ public function checkIdPSettings(array $settings) $errors[] = 'idp_slo_url_invalid'; } - if (isset($settings['security'])) { - $security = $settings['security']; + if (isset($idp['singleLogoutService']) + && isset($idp['singleLogoutService']['responseUrl']) + && !empty($idp['singleLogoutService']['responseUrl']) + && !filter_var($idp['singleLogoutService']['responseUrl'], FILTER_VALIDATE_URL) + ) { + $errors[] = 'idp_slo_response_url_invalid'; + } - $existsX509 = isset($idp['x509cert']) && !empty($idp['x509cert']); - $existsMultiX509Sign = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['signing']) && !empty($idp['x509certMulti']['signing']); + $existsX509 = isset($idp['x509cert']) && !empty($idp['x509cert']); + $existsMultiX509Sign = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['signing']) && !empty($idp['x509certMulti']['signing']); + $existsFingerprint = isset($idp['certFingerprint']) && !empty($idp['certFingerprint']); + if (!($existsX509 || $existsFingerprint || $existsMultiX509Sign) + ) { + $errors[] = 'idp_cert_or_fingerprint_not_found_and_required'; + } + + if (isset($settings['security'])) { $existsMultiX509Enc = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['encryption']) && !empty($idp['x509certMulti']['encryption']); - $existsFingerprint = isset($idp['certFingerprint']) && !empty($idp['certFingerprint']); - if (!($existsX509 || $existsFingerprint || $existsMultiX509Sign) - ) { - $errors[] = 'idp_cert_or_fingerprint_not_found_and_required'; - } - if ((isset($security['nameIdEncrypted']) && $security['nameIdEncrypted'] == true) + if ((isset($settings['security']['nameIdEncrypted']) && $settings['security']['nameIdEncrypted'] == true) && !($existsX509 || $existsMultiX509Enc) ) { $errors[] = 'idp_cert_not_found_and_required'; @@ -588,8 +629,10 @@ public function checkSPSettings(array $settings) } if (isset($security['signMetadata']) && is_array($security['signMetadata'])) { - if (!isset($security['signMetadata']['keyFileName']) - || !isset($security['signMetadata']['certFileName']) + if ((!isset($security['signMetadata']['keyFileName']) + || !isset($security['signMetadata']['certFileName'])) && + (!isset($security['signMetadata']['privateKey']) + || !isset($security['signMetadata']['x509cert'])) ) { $errors[] = 'sp_signMetadata_invalid'; } @@ -787,12 +830,53 @@ public function shouldCompressResponses() return $this->_compress['responses']; } + /** + * Gets the IdP SSO url. + * + * @return string|null The url of the IdP Single Sign On Service + */ + public function getIdPSSOUrl() + { + $ssoUrl = null; + if (isset($this->_idp['singleSignOnService']) && isset($this->_idp['singleSignOnService']['url'])) { + $ssoUrl = $this->_idp['singleSignOnService']['url']; + } + return $ssoUrl; + } + + /** + * Gets the IdP SLO url. + * + * @return string|null The request url of the IdP Single Logout Service + */ + public function getIdPSLOUrl() + { + $sloUrl = null; + if (isset($this->_idp['singleLogoutService']) && isset($this->_idp['singleLogoutService']['url'])) { + $sloUrl = $this->_idp['singleLogoutService']['url']; + } + return $sloUrl; + } + + /** + * Gets the IdP SLO response url. + * + * @return string|null The response url of the IdP Single Logout Service + */ + public function getIdPSLOResponseUrl() + { + if (isset($this->_idp['singleLogoutService']) && isset($this->_idp['singleLogoutService']['responseUrl'])) { + return $this->_idp['singleLogoutService']['responseUrl']; + } + return $this->getIdPSLOUrl(); + } + /** * Gets the SP metadata. The XML representation. * * @param bool $alwaysPublishEncryptionCert When 'true', the returned * metadata will always include an 'encryption' KeyDescriptor. Otherwise, - * the 'encryption' KeyDescriptor will only be included if + * the 'encryption' KeyDescriptor will only be included if * $advancedSettings['security']['wantNameIdEncrypted'] or * $advancedSettings['security']['wantAssertionsEncrypted'] are enabled. * @param int|null $validUntil Metadata's valid time @@ -825,7 +909,7 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil } //Sign Metadata - if (isset($this->_security['signMetadata']) && $this->_security['signMetadata'] !== false) { + if (isset($this->_security['signMetadata']) && $this->_security['signMetadata'] != false) { if ($this->_security['signMetadata'] === true) { $keyMetadata = $this->getSPkey(); $certMetadata = $cert; @@ -843,15 +927,8 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil Error::PUBLIC_CERT_FILE_NOT_FOUND ); } - } else { - if (!isset($this->_security['signMetadata']['keyFileName']) - || !isset($this->_security['signMetadata']['certFileName']) - ) { - throw new Error( - 'Invalid Setting: signMetadata value of the sp is not valid', - Error::SETTINGS_INVALID_SYNTAX - ); - } + } else if (isset($this->_security['signMetadata']['keyFileName']) && + isset($this->_security['signMetadata']['certFileName'])) { $keyFileName = $this->_security['signMetadata']['keyFileName']; $certFileName = $this->_security['signMetadata']['certFileName']; @@ -875,6 +952,29 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil } $keyMetadata = file_get_contents($keyMetadataFile); $certMetadata = file_get_contents($certMetadataFile); + } else if (isset($this->_security['signMetadata']['privateKey']) && + isset($this->_security['signMetadata']['x509cert'])) { + $keyMetadata = Utils::formatPrivateKey($this->_security['signMetadata']['privateKey']); + $certMetadata = Utils::formatCert($this->_security['signMetadata']['x509cert']); + if (!$keyMetadata) { + throw new Error( + 'Private key not found.', + Error::PRIVATE_KEY_FILE_NOT_FOUND + ); + } + + if (!$certMetadata) { + throw new Error( + 'Public cert not found.', + Error::PUBLIC_CERT_FILE_NOT_FOUND + ); + } + } else { + throw new Error( + 'Invalid Setting: signMetadata value of the sp is not valid', + Error::SETTINGS_INVALID_SYNTAX + ); + } $signatureAlgorithm = $this->_security['signatureAlgorithm']; @@ -898,7 +998,7 @@ public function validateMetadata($xml) assert(is_string($xml)); $errors = array(); - $res = Utils::validateXML($xml, 'saml-schema-metadata-2.0.xsd', $this->_debug); + $res = Utils::validateXML($xml, 'saml-schema-metadata-2.0.xsd', $this->_debug, $this->getSchemasPath()); if (!$res instanceof DOMDocument) { $errors[] = $res; } else { diff --git a/vendor/onelogin/php-saml/src/Saml2/Utils.php b/vendor/onelogin/php-saml/src/Saml2/Utils.php index f88298a..c6e912c 100644 --- a/vendor/onelogin/php-saml/src/Saml2/Utils.php +++ b/vendor/onelogin/php-saml/src/Saml2/Utils.php @@ -82,13 +82,24 @@ public static function loadXML(DOMDocument $dom, $xml) assert($dom instanceof DOMDocument); assert(is_string($xml)); - if (strpos($xml, 'loadXML($xml); - libxml_disable_entity_loader($oldEntityLoader); + + if (PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($oldEntityLoader); + } + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new Exception( + 'Detected use of DOCTYPE/ENTITY in XML, disabled to prevent XXE/XEE attacks' + ); + } + } if (!$res) { return false; @@ -105,12 +116,13 @@ public static function loadXML(DOMDocument $dom, $xml) * @param string|DOMDocument $xml The XML string or document which should be validated. * @param string $schema The schema filename which should be used. * @param bool $debug To disable/enable the debug mode + * @param string $schemaPath Change schema path * * @return string|DOMDocument $dom string that explains the problem or the DOMDocument * * @throws Exception */ - public static function validateXML($xml, $schema, $debug = false) + public static function validateXML($xml, $schema, $debug = false, $schemaPath = null) { assert(is_string($xml) || $xml instanceof DOMDocument); assert(is_string($schema)); @@ -128,10 +140,20 @@ public static function validateXML($xml, $schema, $debug = false) } } - $schemaFile = __DIR__ . '/schemas/' . $schema; - $oldEntityLoader = libxml_disable_entity_loader(false); + if (isset($schemaPath)) { + $schemaFile = $schemaPath . $schema; + } else { + $schemaFile = __DIR__ . '/schemas/' . $schema; + } + + $oldEntityLoader = null; + if (PHP_VERSION_ID < 80000) { + $oldEntityLoader = libxml_disable_entity_loader(false); + } $res = $dom->schemaValidate($schemaFile); - libxml_disable_entity_loader($oldEntityLoader); + if (PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($oldEntityLoader); + } if (!$res) { $xmlErrors = libxml_get_errors(); syslog(LOG_INFO, 'Error validating the metadata: '.var_export($xmlErrors, true)); @@ -199,6 +221,10 @@ public static function treeCopyReplace(DomNode $targetNode, DomNode $sourceNode, */ public static function formatCert($cert, $heads = true) { + if (is_null($cert)) { + return; + } + $x509cert = str_replace(array("\x0D", "\r", "\n"), "", $cert); if (!empty($x509cert)) { $x509cert = str_replace('-----BEGIN CERTIFICATE-----', "", $x509cert); @@ -223,6 +249,10 @@ public static function formatCert($cert, $heads = true) */ public static function formatPrivateKey($key, $heads = true) { + if (is_null($key)) { + return; + } + $key = str_replace(array("\x0D", "\r", "\n"), "", $key); if (!empty($key)) { if (strpos($key, '-----BEGIN PRIVATE KEY-----') !== false) { @@ -297,7 +327,12 @@ public static function redirect($url, array $parameters = array(), $stay = false * Verify that the URL matches the regex for the protocol. * By default this will check for http and https */ - $wrongProtocol = !preg_match(self::$_protocolRegex, $url); + if (isset(self::$_protocolRegex)) { + $protocol = self::$_protocolRegex; + } else { + $protocol = ""; + } + $wrongProtocol = !preg_match($protocol, $url); $url = filter_var($url, FILTER_VALIDATE_URL); if ($wrongProtocol || empty($url)) { throw new Error( @@ -616,7 +651,7 @@ public static function getSelfRoutedURLNoQuery() if (!empty($_SERVER['REQUEST_URI'])) { $route = $_SERVER['REQUEST_URI']; if (!empty($_SERVER['QUERY_STRING'])) { - $route = str_replace($_SERVER['QUERY_STRING'], '', $route); + $route = self::strLreplace($_SERVER['QUERY_STRING'], '', $route); if (substr($route, -1) == '?') { $route = substr($route, 0, -1); } @@ -629,9 +664,26 @@ public static function getSelfRoutedURLNoQuery() } $selfRoutedURLNoQuery = $selfURLhost . $route; + + $pos = strpos($selfRoutedURLNoQuery, "?"); + if ($pos !== false) { + $selfRoutedURLNoQuery = substr($selfRoutedURLNoQuery, 0, $pos); + } + return $selfRoutedURLNoQuery; } + public static function strLreplace($search, $replace, $subject) + { + $pos = strrpos($subject, $search); + + if ($pos !== false) { + $subject = substr_replace($subject, $replace, $pos, strlen($search)); + } + + return $subject; + } + /** * Returns the URL of the current host + current view + query. * @@ -704,7 +756,7 @@ public static function extractOriginalQueryParam($name) */ public static function generateUniqueID() { - return 'ONELOGIN_' . sha1(uniqid((string)mt_rand(), true)); + return 'ONELOGIN_' . sha1(random_bytes(20)); } /** @@ -734,6 +786,10 @@ public static function parseTime2SAML($time) */ public static function parseSAML2Time($time) { + if (empty($time)) { + return null; + } + $matches = array(); /* We use a very strict regex to parse the timestamp. */ @@ -935,12 +991,12 @@ public static function isSessionStarted() */ public static function deleteLocalSession() { - if (Utils::isSessionStarted()) { + session_unset(); session_destroy(); + } else { + $_SESSION = array(); } - - unset($_SESSION); } /** @@ -971,7 +1027,10 @@ public static function calculateX509Fingerprint($x509cert, $alg = 'sha1') if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { break; } - $data .= trim($curData); + if (isset($curData)) { + $curData = trim($curData); + } + $data .= $curData; } } @@ -1004,6 +1063,9 @@ public static function calculateX509Fingerprint($x509cert, $alg = 'sha1') */ public static function formatFingerPrint($fingerprint) { + if (is_null($fingerprint)) { + return; + } $formatedFingerprint = str_replace(':', '', $fingerprint); $formatedFingerprint = strtolower($formatedFingerprint); return $formatedFingerprint; @@ -1017,12 +1079,13 @@ public static function formatFingerPrint($fingerprint) * @param string|null $format SP Format * @param string|null $cert IdP Public cert to encrypt the nameID * @param string|null $nq IdP Name Qualifier + * @param string|null $encAlg Encryption algorithm * * @return string $nameIDElement DOMElement | XMLSec nameID * * @throws Exception */ - public static function generateNameId($value, $spnq, $format = null, $cert = null, $nq = null) + public static function generateNameId($value, $spnq, $format = null, $cert = null, $nq = null, $encAlg = XMLSecurityKey::AES128_CBC) { $doc = new DOMDocument(); @@ -1042,14 +1105,18 @@ public static function generateNameId($value, $spnq, $format = null, $cert = nul $doc->appendChild($nameId); if (!empty($cert)) { - $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'public')); + if ($encAlg == XMLSecurityKey::AES128_CBC) { + $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'public')); + } else { + $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type'=>'public')); + } $seckey->loadKey($cert); $enc = new XMLSecEnc(); $enc->setNode($nameId); $enc->type = XMLSecEnc::Element; - $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES128_CBC); + $symmetricKey = new XMLSecurityKey($encAlg); $symmetricKey->generateSessionKey(); $enc->encryptKey($seckey, $symmetricKey); @@ -1361,7 +1428,7 @@ public static function addSign($xml, $key, $cert, $signAlgorithm = XMLSecurityKe * Validates a signature (Message or Assertion). * * @param string|\DomNode $xml The element we should validate - * @param string|null $cert The pubic cert + * @param string|null $cert The public cert * @param string|null $fingerprint The fingerprint of the public cert * @param string|null $fingerprintalg The algorithm used to get the fingerprint * @param string|null $xpath The xpath of the signed element @@ -1525,7 +1592,7 @@ public static function validateBinarySign($messageType, $getData, $idpData, $ret } } - if ($objKey->verifySignature($signedQuery, base64_decode($_GET['Signature'])) === 1) { + if ($objKey->verifySignature($signedQuery, base64_decode($getData['Signature'])) === 1) { $signatureValid = true; break; } diff --git a/vendor/onelogin/php-saml/src/Saml2/version.json b/vendor/onelogin/php-saml/src/Saml2/version.json index ebdf4ec..2c41bd6 100644 --- a/vendor/onelogin/php-saml/src/Saml2/version.json +++ b/vendor/onelogin/php-saml/src/Saml2/version.json @@ -1,6 +1,7 @@ { "php-saml": { - "version": "3.0.0", - "released": "02/11/2018" + "version": "4.1.0", + "released": "07/15/2022" } } +