From e73e73464e0bcf0e73bc8bdb54667d6af95c5dd9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaime=20Pe=CC=81rez=20Crespo?= <jaime.perez@uninett.no>
Date: Thu, 20 Dec 2018 17:32:34 +0100
Subject: [PATCH] Do not store the username and password obtained via ECP in
 the state array.

---
 modules/core/lib/Auth/UserPassBase.php        | 19 +++++--
 modules/saml/lib/IdP/SAML2.php                | 17 ------
 .../core/lib/Auth/UserPassBaseTest.php        | 55 +++++++++++++++++--
 tests/modules/saml/lib/IdP/SAML2Test.php      | 37 -------------
 4 files changed, 63 insertions(+), 65 deletions(-)

diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php
index 5f0ee4ffa..847334466 100644
--- a/modules/core/lib/Auth/UserPassBase.php
+++ b/modules/core/lib/Auth/UserPassBase.php
@@ -192,11 +192,20 @@ abstract class UserPassBase extends \SimpleSAML\Auth\Source
             $state['forcedUsername'] = $this->forcedUsername;
         }
 
-        // ECP requests supply authentication credentials with the AUthnRequest
-        // so we validate them now rather than redirecting
-        if (isset($state['core:auth:username']) && isset($state['core:auth:password'])) {
-            $username = $state['core:auth:username'];
-            $password = $state['core:auth:password'];
+        // ECP requests supply authentication credentials with the AuthnRequest
+        // so we validate them now rather than redirecting. The SAML spec
+        // doesn't define how the credentials are transferred, but Office 365
+        // uses the Authorization header, so we will just use that in lieu of
+        // other use cases.
+        if (isset($state['saml:Binding']) && $state['saml:Binding'] === \SAML2\Constants::BINDING_PAOS) {
+            if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
+                \SimpleSAML\Logger::error("ECP AuthnRequest did not contain Basic Authentication header");
+                // TODO Return a SOAP fault instead of using the current binding?
+                throw new \SimpleSAML\Error\Error("WRONGUSERPASS");
+            }
+
+            $username = $_SERVER['PHP_AUTH_USER'];
+            $password = $_SERVER['PHP_AUTH_PW'];
 
             if (isset($state['forcedUsername'])) {
                 $username = $state['forcedUsername'];
diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index f530451e0..1c787412d 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -455,26 +455,9 @@ class SAML2
             'saml:RequestedAuthnContext'  => $authnContext,
         ];
 
-        // ECP AuthnRequests need to supply credentials
-        if ($binding instanceof SOAP) {
-            self::processSOAPAuthnRequest($state);
-        }
-
         $idp->handleAuthenticationRequest($state);
     }
 
-    public static function processSOAPAuthnRequest(array &$state)
-    {
-        if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
-            Logger::error("ECP AuthnRequest did not contain Basic Authentication header");
-            // TODO Throw some sort of ECP-specific exception / convert this to SOAP fault
-            throw new \SimpleSAML\Error\Error("WRONGUSERPASS");
-        }
-
-        $state['core:auth:username'] = $_SERVER['PHP_AUTH_USER'];
-        $state['core:auth:password'] = $_SERVER['PHP_AUTH_PW'];
-    }
-
     /**
      * Send a logout request to a given association.
      *
diff --git a/tests/modules/core/lib/Auth/UserPassBaseTest.php b/tests/modules/core/lib/Auth/UserPassBaseTest.php
index cd5a328ac..f477ad2ab 100644
--- a/tests/modules/core/lib/Auth/UserPassBaseTest.php
+++ b/tests/modules/core/lib/Auth/UserPassBaseTest.php
@@ -6,11 +6,13 @@ class UserPassBaseTest extends \PHPUnit_Framework_TestCase
 {
     public function testAuthenticateECPCallsLoginAndSetsAttributes()
     {
-        $state = [];
+        $state = [
+            'saml:Binding' => \SAML2\Constants::BINDING_PAOS,
+        ];
         $attributes = ['attrib' => 'val'];
 
-        $username = $state['core:auth:username'] = 'username';
-        $password = $state['core:auth:password'] = 'password';
+        $username = $_SERVER['PHP_AUTH_USER'] = 'username';
+        $password = $_SERVER['PHP_AUTH_PW'] = 'password';
 
         $stub = $this->getMockBuilder('\SimpleSAML\Module\core\Auth\UserPassBase')
             ->disableOriginalConstructor()
@@ -27,15 +29,56 @@ class UserPassBaseTest extends \PHPUnit_Framework_TestCase
         $this->assertSame($attributes, $state['Attributes']);
     }
 
+
+    public function testAuthenticateECPMissingUsername()
+    {
+        $this->setExpectedException('\SimpleSAML\Error\Error', 'WRONGUSERPASS');
+
+        $state = [
+            'saml:Binding' => \SAML2\Constants::BINDING_PAOS,
+        ];
+
+        unset($_SERVER['PHP_AUTH_USER']);
+        $_SERVER['PHP_AUTH_PW'] = 'password';
+
+        $stub = $this->getMockBuilder('\SimpleSAML\Module\core\Auth\UserPassBase')
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $stub->authenticate($state);
+    }
+
+
+    public function testAuthenticateECPMissingPassword()
+    {
+        $this->setExpectedException('\SimpleSAML\Error\Error', 'WRONGUSERPASS');
+
+        $state = [
+            'saml:Binding' => \SAML2\Constants::BINDING_PAOS,
+        ];
+
+        $_SERVER['PHP_AUTH_USER'] = 'username';
+        unset($_SERVER['PHP_AUTH_PW']);
+
+        $stub = $this->getMockBuilder('\SimpleSAML\Module\core\Auth\UserPassBase')
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $stub->authenticate($state);
+    }
+
+
     public function testAuthenticateECPCallsLoginWithForcedUsername()
     {
-        $state = [];
+        $state = [
+            'saml:Binding' => \SAML2\Constants::BINDING_PAOS,
+        ];
         $attributes = [];
 
         $forcedUsername = 'forcedUsername';
 
-        $state['core:auth:username'] = 'username';
-        $password = $state['core:auth:password'] = 'password';
+        $_SERVER['PHP_AUTH_USER'] = 'username';
+        $password = $_SERVER['PHP_AUTH_PW'] = 'password';
 
         $stub = $this->getMockBuilder('\SimpleSAML\Module\core\Auth\UserPassBase')
             ->disableOriginalConstructor()
diff --git a/tests/modules/saml/lib/IdP/SAML2Test.php b/tests/modules/saml/lib/IdP/SAML2Test.php
index dbd026097..607234a17 100644
--- a/tests/modules/saml/lib/IdP/SAML2Test.php
+++ b/tests/modules/saml/lib/IdP/SAML2Test.php
@@ -9,43 +9,6 @@ use SimpleSAML\Test\Utils\ClearStateTestCase;
 
 class SAML2Test extends ClearStateTestCase
 {
-    public function testProcessSOAPAuthnRequest()
-    {
-        $username = $_SERVER['PHP_AUTH_USER'] = 'username';
-        $password = $_SERVER['PHP_AUTH_PW'] = 'password';
-        $state = [];
-
-        \SimpleSAML\Module\saml\IdP\SAML2::processSOAPAuthnRequest($state);
-
-        $this->assertEquals($username, $state['core:auth:username']);
-        $this->assertEquals($password, $state['core:auth:password']);
-    }
-
-    public function testProcessSOAPAuthnRequestMissingUsername()
-    {
-        $this->setExpectedException('\SimpleSAML\Error\Error', 'WRONGUSERPASS');
-
-        $_SERVER['PHP_AUTH_PW'] = 'password';
-        unset($_SERVER['PHP_AUTH_USER']);
-        $state = [];
-        Configuration::loadFromArray([
-            'baseurlpath' => 'https://idp.example.com/',
-        ], '', 'simplesaml');
-
-        \SimpleSAML\Module\saml\IdP\SAML2::processSOAPAuthnRequest($state);
-    }
-
-    public function testProcessSOAPAuthnRequestMissingPassword()
-    {
-        $this->setExpectedException('\SimpleSAML\Error\Error', 'WRONGUSERPASS');
-
-        $_SERVER['PHP_AUTH_USER'] = 'username';
-        unset($_SERVER['PHP_AUTH_PW']);
-        $state = [];
-
-        \SimpleSAML\Module\saml\IdP\SAML2::processSOAPAuthnRequest($state);
-    }
-
     /**
      * Default values for the state array expected to be generated at the start of logins
      * @var array
-- 
GitLab