diff --git a/composer.json b/composer.json index 53b0e96f971a327b439a2c416651f37fb68484d9..7d1cb25ab4e9301b99745b32fe0ec99f3bd1c07e 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.3", "curl/curl": "*", "ext-json": "*", "ext-curl": "*" diff --git a/src/PrivacyIDEA.php b/src/PrivacyIDEA.php index 1ef7d2be9535d9f659b107769cbae5c664c45a6e..a82a3ab4d9be6b90f62af0a265060fb9d65c57fa 100644 --- a/src/PrivacyIDEA.php +++ b/src/PrivacyIDEA.php @@ -69,7 +69,7 @@ class PrivacyIDEA * @return PIResponse|null null if response was empty or malformed, or parameter missing * @throws PIBadRequestException */ - public function validateCheck($username, $pass, $transactionID = null, $headers=null) + public function validateCheck($username, $pass, $transactionID = null, $headers = null) { assert('string' === gettype($username)); assert('string' === gettype($pass)); @@ -401,13 +401,49 @@ class PrivacyIDEA if (!empty($response['result']['value'])) { - return @$response['result']['value']['token'] ?: ""; + // Ensure an admin account + if (!empty($response['result']['value']['token'])) + { + if ($this->findRecursive($response, 'role') != 'admin') + { + $this->debugLog("Auth token was of a user without admin role."); + return ""; + } + return $response['result']['value']['token']; + } } - - $this->debugLog("/auth response did not contain a auth token."); + $this->debugLog("/auth response did not contain the auth token."); return ""; } + /** + * Find a key in array recursively. + * + * @param array $haystack The array which will be searched. + * @param string $needle Search string. + * @return mixed Result of key search. + */ + public function findRecursive($haystack, $needle) + { + assert(is_array($haystack)); + assert(is_string($needle)); + + $iterator = new RecursiveArrayIterator($haystack); + $recursive = new RecursiveIteratorIterator( + $iterator, + RecursiveIteratorIterator::SELF_FIRST + ); + + foreach ($recursive as $key => $value) + { + if ($key === $needle) + { + return $value; + } + } + return false; + } + /** * Send a request to an endpoint with the specified parameters and headers. * @@ -440,7 +476,7 @@ class PrivacyIDEA } } } - + $this->debugLog("Sending " . http_build_query($params, '', ', ') . " to " . $endpoint); $completeUrl = $this->serverURL . $endpoint; diff --git a/test/ErrorMissingAuthorizationHeaderTest.php b/test/ErrorMissingAuthorizationHeaderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8c1fc8e46b5031ff255a4a3e8180b49e07e81b60 --- /dev/null +++ b/test/ErrorMissingAuthorizationHeaderTest.php @@ -0,0 +1,84 @@ +<?php + +//require_once(__DIR__ . '/../src/Client-Autoloader.php'); +require_once(__DIR__ . '/../vendor/autoload.php'); +require_once('utils/Utils.php'); + +use InterNations\Component\HttpMock\PHPUnit\HttpMockTrait; +use PHPUnit\Framework\TestCase; +use utils\Utils; + +class ErrorMissingAuthorizationHeaderTest extends TestCase implements PILog +{ + private $pi; + + use HttpMockTrait; + + public static function setUpBeforeClass(): void + { + static::setUpHttpMockBeforeClass('8082', 'localhost'); + } + + public static function tearDownAfterClass(): void + { + static::tearDownHttpMockAfterClass(); + } + + public function setUp(): void + { + $this->setUpHttpMock(); + $this->pi = new PrivacyIDEA('testUserAgent', "localhost:8082"); + $this->pi->logger = $this; + $this->pi->realm = "testRealm"; + } + + public function tearDown(): void + { + $this->tearDownHttpMock(); + } + + /** + * @throws PIBadRequestException + */ + public function testErrorMissingAuthorizationHeader() + { + $this->http->mock + ->when() + ->methodIs('POST') + ->pathIs('/validate/triggerchallenge') + ->then() + ->body(Utils::errorMissingAuthorizationHeaderResponseBody()) + ->end(); + $this->http->setUp(); + + $this->http->mock + ->when() + ->methodIs('POST') + ->pathIs('/auth') + ->then() + ->body(Utils::postAuthNoRoleAdminResponseBody()) + ->end(); + $this->http->setUp(); + + $this->pi->serviceAccountName = "testServiceAccount"; + $this->pi->serviceAccountPass = "testServicePass"; + $this->pi->serviceAccountRealm = "testServiceRealm"; + + $response = $this->pi->triggerchallenge("testUser"); + + $this->assertEquals("4033", $response->errorCode); + $this->assertEquals("Authentication failure. Missing Authorization header.", $response->errorMessage); + $this->assertFalse($response->status); + $this->assertEquals("", $response->otpMessage()); + } + + public function piDebug($message) + { + echo $message . "\n"; + } + + public function piError($message) + { + echo "error: " . $message . "\n"; + } +} \ No newline at end of file diff --git a/test/utils/Utils.php b/test/utils/Utils.php index a5d0e194aed7b049a6314865443a4c5ae2dcb652..08da8bc1bff299f35b9d0bebe4603dee0dad5664 100644 --- a/test/utils/Utils.php +++ b/test/utils/Utils.php @@ -109,6 +109,31 @@ class Utils " \"signature\": \"rsa_sha256_pss:\"\n" . "}"; } + /** + * @return string + */ + public static function postAuthNoRoleAdminResponseBody() + { + return "{\n" . " \"id\": 1,\n" . " \"jsonrpc\": \"2.0\",\n" . + " \"result\": {\n" . " \"status\": true,\n" . + " \"value\": {\n" . " \"log_level\": 20,\n" . + " \"menus\": [\n" . " \"components\",\n" . + " \"machines\"\n" . " ],\n" . + " \"realm\": \"\",\n" . " \"rights\": [\n" . + " \"policydelete\",\n" . + " \"resync\"\n" . " ],\n" . + " \"role\": \"user\",\n" . " \"token\": \"" . + self::authToken() . "\",\n" . " \"username\": \"admin\",\n" . + " \"logout_time\": 120,\n" . + " \"default_tokentype\": \"hotp\",\n" . + " \"user_details\": false,\n" . + " \"subscription_status\": 0\n" . " }\n" . + " },\n" . " \"time\": 1589446794.8502703,\n" . + " \"version\": \"privacyIDEA 3.2.1\",\n" . + " \"versionnumber\": \"3.2.1\",\n" . + " \"signature\": \"rsa_sha256_pss:\"\n" . "}"; + } + /** * @return string */ @@ -234,6 +259,17 @@ class Utils "\"signature\":\"rsa_sha256_pss:1c64db29cad0dc127d6...5ec143ee52a7804ea1dc8e23ab2fc90ac0ac147c0\"}"; } + /** + * @return string + */ + public static function errorMissingAuthorizationHeaderResponseBody() + { + return "{" . "\"detail\":null," . "\"id\":1," . "\"jsonrpc\":\"2.0\"," . "\"result\":{" . "\"error\":{" . + "\"code\":4033," . "\"message\":\"Authentication failure. Missing Authorization header.\"}," . + "\"status\":false}," . "\"time\":1649752303.65651," . "\"version\":\"privacyIDEA 3.6.3\"," . + "\"signature\":\"rsa_sha256_pss:1c64db29cad0dc127d6...5ec143ee52a7804ea1dc8e23ab2fc90ac0ac147c0\"}"; + } + /** * @return string */