From c0f42ac03142b942ac6dc547e0cfbe44ff6b1a62 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Sat, 28 Sep 2019 14:41:35 -0400 Subject: [PATCH] Second set of authentication tests --- lib/Misc/URL.php | 7 ++++++- lib/REST/Microsub/Auth.php | 7 +++++-- tests/cases/Misc/TestURL.php | 1 + tests/cases/REST/Microsub/TestAuth.php | 28 ++++++++++++++++---------- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/lib/Misc/URL.php b/lib/Misc/URL.php index c8261081..b0976575 100644 --- a/lib/Misc/URL.php +++ b/lib/Misc/URL.php @@ -39,7 +39,12 @@ class URL { * @param string $p Password to add to the URL, if a username is specified */ public static function normalize(string $url, string $u = null, string $p = null): string { - extract(parse_url($url)); + $parts = parse_url($url); + if (!$parts) { + // bail if there is no authority + return $url; + } + extract($parts); $out = ""; if (isset($scheme)) { $out .= strtolower($scheme).":"; diff --git a/lib/REST/Microsub/Auth.php b/lib/REST/Microsub/Auth.php index c4e2c110..eb21153e 100644 --- a/lib/REST/Microsub/Auth.php +++ b/lib/REST/Microsub/Auth.php @@ -120,6 +120,9 @@ class Auth extends \JKingWeb\Arsse\REST\AbstractHandler { */ protected function matchIdentifier(string $canonical, string $me): bool { $me = parse_url(URL::normalize($me)); + if (!$me) { + return false; + } $me['scheme'] = $me['scheme'] ?? ""; $me['path'] = explode("/", $me['path'] ?? ""); $me['id'] = rawurldecode(array_pop($me['path']) ?? ""); @@ -191,7 +194,7 @@ class Auth extends \JKingWeb\Arsse\REST\AbstractHandler { return new EmptyResponse(400); } try { - $state = $query['state'] ?? ""; + $state = rawurlencode($query['state'] ?? ""); // ensure the logged-in user matches the IndieAuth identifier URL $user = $req->getAttribute("authenticatedUser"); if (!$this->matchIdentifier($this->buildIdentifier($req, $user), $query['me'])) { @@ -210,7 +213,7 @@ class Auth extends \JKingWeb\Arsse\REST\AbstractHandler { ], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); // issue an authorization code and build the redirect URL $code = Arsse::$db->tokenCreate($user, "microsub.auth", null, Date::add("PT2M"), $data); - $next = URL::queryAppend($redir, "code=$code&state=$state"); + $next = URL::queryAppend($redir, "state=$state&code=$code"); return new EmptyResponse(302, ['Location' => $next]); } catch (ExceptionAuth $e) { $next = URL::queryAppend($redir, "state=$state&error=".$e->getMessage()); diff --git a/tests/cases/Misc/TestURL.php b/tests/cases/Misc/TestURL.php index 8260c0b0..18bf3bc6 100644 --- a/tests/cases/Misc/TestURL.php +++ b/tests/cases/Misc/TestURL.php @@ -73,6 +73,7 @@ class TestURL extends \JKingWeb\Arsse\Test\AbstractTest { ["EXAMPLE.COM/", "EXAMPLE.COM/"], ["EXAMPLE.COM", "EXAMPLE.COM"], [" ", "%20"], + ["http:///%G", "http:///%G"] ]; } diff --git a/tests/cases/REST/Microsub/TestAuth.php b/tests/cases/REST/Microsub/TestAuth.php index 7617fcb5..5ca43f22 100644 --- a/tests/cases/REST/Microsub/TestAuth.php +++ b/tests/cases/REST/Microsub/TestAuth.php @@ -8,6 +8,7 @@ namespace JKingWeb\Arsse\TestCase\REST\Microsub; use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Database; +use JKingWeb\Arsse\Misc\Date; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response\JsonResponse as Response; use Zend\Diactoros\Response\EmptyResponse; @@ -97,7 +98,7 @@ class TestAuth extends \JKingWeb\Arsse\Test\AbstractTest { $act = $this->req("http://example.com/u/?f=auth", "GET", $params, [], [], "", null, $authenticatedUser); $this->assertMessage($exp, $act); if ($act->getStatusCode() == 302 && !preg_match("/\berror=\w/", $act->getHeaderLine("Location") ?? "")) { - \Phake::verify(Arsse::$db)->tokenCreate($authenticatedUser, "microsub.auth", null, null, json_encode([ + \Phake::verify(Arsse::$db)->tokenCreate($authenticatedUser, "microsub.auth", null, $this->isInstanceOf(\DateTimeInterface::class), json_encode([ 'me' => $params['me'], 'client_id' => $params['client_id'], 'redirect_uri' => $params['redirect_uri'], @@ -110,16 +111,21 @@ class TestAuth extends \JKingWeb\Arsse\Test\AbstractTest { public function provideLoginData() { return [ - 'Challenge' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], null, new EmptyResponse(401)], - 'Failed challenge' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "", new EmptyResponse(401)], - 'Wrong user 1' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "jane.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], - 'Wrong user 2' => [['me' => "https://example.com/u/jane.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], - 'Wrong domain' => [['me' => "https://example.net/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], - 'Wrong port' => [['me' => "https://example.com:80/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], - 'Wrong scheme' => [['me' => "ftp://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], - 'Wrong path' => [['me' => "http://example.com/user/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], - 'Bad redirect' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "//example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(400)], - 'Bad response type' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "bad"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=unsupported_response_type"])], + 'Challenge' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], null, new EmptyResponse(401)], + 'Failed challenge' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "", new EmptyResponse(401)], + 'Wrong user 1' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "jane.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], + 'Wrong user 2' => [['me' => "https://example.com/u/jane.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], + 'Wrong domain 1' => [['me' => "https://example.net/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], + 'Wrong domain 2' => [['me' => "https:///u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], + 'Wrong port' => [['me' => "https://example.com:80/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], + 'Wrong scheme' => [['me' => "ftp://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], + 'Wrong path' => [['me' => "http://example.com/user/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=access_denied"])], + 'Bad redirect 1' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "//example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(400)], + 'Bad redirect 2' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "https:///redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(400)], + 'Bad response type' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "bad"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&error=unsupported_response_type"])], + 'Success 1' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=ABCDEF&code=authCode"])], + 'Success 2' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/redirect", 'state' => "R&R", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/redirect?state=R%26R&code=authCode"])], + 'Success 3' => [['me' => "https://example.com/u/john.doe", 'client_id' => "http://example.org/", 'redirect_uri' => "http://example.org/?p=redirect", 'state' => "ABCDEF", 'response_type' => "code"], "john.doe", new EmptyResponse(302, ['Location' => "http://example.org/?p=redirect&state=ABCDEF&code=authCode"])], ]; } }