mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2025-01-09 09:22:40 +00:00
Authentication tests
More tests are needed
This commit is contained in:
parent
26fa9461eb
commit
94bf37c388
2 changed files with 52 additions and 13 deletions
|
@ -125,7 +125,7 @@ class Auth extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$me['scheme'] = $me['scheme'] ?? "";
|
$me['scheme'] = $me['scheme'] ?? "";
|
||||||
$me['path'] = explode("/", $me['path'] ?? "");
|
$me['path'] = explode("/", $me['path'] ?? "");
|
||||||
$me['id'] = rawurldecode(array_pop($me['path']) ?? "");
|
$me['id'] = rawurldecode(array_pop($me['path']) ?? "");
|
||||||
$me['port'] == (($me['scheme'] === "http" && $me['port'] == 80) || ($me['scheme'] === "https" && $me['port'] == 443)) ? 0 : $me['port'];
|
$me['port'] = (($me['scheme'] === "http" && ($me['port'] ?? 80) == 80) || ($me['scheme'] === "https" && ($me['port'] ?? 443) == 443)) ? 0 : $me['port'] ?? 0;
|
||||||
$c = parse_url($canonical);
|
$c = parse_url($canonical);
|
||||||
$c['path'] = explode("/", $c['path']);
|
$c['path'] = explode("/", $c['path']);
|
||||||
$c['id'] = rawurldecode(array_pop($c['path']));
|
$c['id'] = rawurldecode(array_pop($c['path']));
|
||||||
|
@ -187,12 +187,13 @@ class Auth extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
} else {
|
} else {
|
||||||
// user has logged in
|
// user has logged in
|
||||||
$query = $req->getQueryParams();
|
$query = $req->getQueryParams();
|
||||||
$redir = URL::normalize(rawurldecode($query['redirect_uri']));
|
$redir = URL::normalize($query['redirect_uri']);
|
||||||
// check that the redirect URL is an absolute one
|
// check that the redirect URL is an absolute one
|
||||||
if (!URL::absolute($redir)) {
|
if (!URL::absolute($redir)) {
|
||||||
return new EmptyResponse(400);
|
return new EmptyResponse(400);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
$state = $query['state'] ?? "";
|
||||||
// ensure the logged-in user matches the IndieAuth identifier URL
|
// ensure the logged-in user matches the IndieAuth identifier URL
|
||||||
$user = $req->getAttribute("authenticatedUser");
|
$user = $req->getAttribute("authenticatedUser");
|
||||||
if (!$this->matchIdentifier($this->buildIdentifier($req, $user), $query['me'])) {
|
if (!$this->matchIdentifier($this->buildIdentifier($req, $user), $query['me'])) {
|
||||||
|
@ -202,21 +203,20 @@ class Auth extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
if (!in_array($type, ["code", "id"])) {
|
if (!in_array($type, ["code", "id"])) {
|
||||||
throw new ExceptionAuth("unsupported_response_type");
|
throw new ExceptionAuth("unsupported_response_type");
|
||||||
}
|
}
|
||||||
$state = $query['state'] ?? "";
|
|
||||||
// store the identity URL, client ID, redirect URL, and response type
|
// store the identity URL, client ID, redirect URL, and response type
|
||||||
$data = json_encode([
|
$data = json_encode([
|
||||||
'me' => $query['me'],
|
'me' => $query['me'],
|
||||||
'client' => $query['client_id'],
|
'client_id' => $query['client_id'],
|
||||||
'redir' => $query['redirect_uri'],
|
'redirect_uri' => $query['redirect_uri'],
|
||||||
'type' => $type,
|
'response_type' => $type,
|
||||||
], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
|
], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
|
||||||
// issue an authorization code and build the redirect URL
|
// issue an authorization code and build the redirect URL
|
||||||
$code = Arsse::$db->tokenCreate($user, "microsub.auth", null, Date::add("PT2M"), $data);
|
$code = Arsse::$db->tokenCreate($user, "microsub.auth", null, Date::add("PT2M"), $data);
|
||||||
$next = URL::queryAppend($redir, "code=$code&state=$state");
|
$next = URL::queryAppend($redir, "code=$code&state=$state");
|
||||||
return new EmptyResponse(302, ["Location: $next"]);
|
return new EmptyResponse(302, ['Location' => $next]);
|
||||||
} catch (ExceptionAuth $e) {
|
} catch (ExceptionAuth $e) {
|
||||||
$next = URL::queryAppend($redir, "state=$state&error=".$e->getMessage());
|
$next = URL::queryAppend($redir, "state=$state&error=".$e->getMessage());
|
||||||
return new EmptyResponse(302, ["Location: $next"]);
|
return new EmptyResponse(302, ['Location' => $next]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,13 +297,13 @@ class Auth extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// validate the auth code
|
// validate the auth code
|
||||||
if (!is_array($data)) {
|
if (!is_array($data)) {
|
||||||
throw new ExceptionAuth("invalid_grant");
|
throw new ExceptionAuth("invalid_grant");
|
||||||
} elseif ($data['client'] !== $clientId || $data['redir'] !== $redirUrl) {
|
} elseif ($data['client_id'] !== $clientId || $data['redirect_uri'] !== $redirUrl) {
|
||||||
throw new ExceptionAuth("invalid_client");
|
throw new ExceptionAuth("invalid_client");
|
||||||
} elseif (isset($me) && $me !== $data['me']) {
|
} elseif (isset($me) && $me !== $data['me']) {
|
||||||
throw new ExceptionAuth("invalid_grant");
|
throw new ExceptionAuth("invalid_grant");
|
||||||
}
|
}
|
||||||
// return the associated user name and the auth-code type
|
// return the associated user name and the auth-code type
|
||||||
return [$token['user'], $data['type'] ?? "id"];
|
return [$token['user'], $data['response_type'] ?? "id"];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handles token verification as an API call
|
/** Handles token verification as an API call
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\TestCase\REST\Microsub;
|
namespace JKingWeb\Arsse\TestCase\REST\Microsub;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Database;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\Response\JsonResponse as Response;
|
use Zend\Diactoros\Response\JsonResponse as Response;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
|
@ -13,15 +15,20 @@ use Zend\Diactoros\Response\HtmlResponse;
|
||||||
|
|
||||||
/** @covers \JKingWeb\Arsse\REST\Microsub\Auth<extended> */
|
/** @covers \JKingWeb\Arsse\REST\Microsub\Auth<extended> */
|
||||||
class TestAuth extends \JKingWeb\Arsse\Test\AbstractTest {
|
class TestAuth extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
public function req(string $url, string $method = "GET", array $data = [], array $headers = [], string $type = "application/x-www-form-urlencoded", string $body = null): ResponseInterface {
|
public function setUp() {
|
||||||
|
self::clearData();
|
||||||
|
Arsse::$db = \Phake::mock(Database::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function req(string $url, string $method = "GET", array $params = [], array $headers = [], array $data = [], string $type = "application/x-www-form-urlencoded", string $body = null, string $user = null): ResponseInterface {
|
||||||
$type = (strtoupper($method) === "GET") ? "" : $type;
|
$type = (strtoupper($method) === "GET") ? "" : $type;
|
||||||
$req = $this->serverRequest($method, $url, "/u/", $headers, [], $body ?? $data, $type);
|
$req = $this->serverRequest($method, $url, "/u/", $headers, [], $body ?? $data, $type, $params, $user);
|
||||||
return (new \JKingWeb\Arsse\REST\Microsub\Auth)->dispatch($req);
|
return (new \JKingWeb\Arsse\REST\Microsub\Auth)->dispatch($req);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideInvalidRequests */
|
/** @dataProvider provideInvalidRequests */
|
||||||
public function testHandleInvalidRequests(ResponseInterface $exp, string $method, string $url, string $type = null) {
|
public function testHandleInvalidRequests(ResponseInterface $exp, string $method, string $url, string $type = null) {
|
||||||
$act = $this->req("http://example.com".$url, $method, [], [], $type ?? "");
|
$act = $this->req("http://example.com".$url, $method, [], [], [], $type ?? "");
|
||||||
$this->assertMessage($exp, $act);
|
$this->assertMessage($exp, $act);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,4 +90,36 @@ class TestAuth extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
["https://example.com:80/u/john.doe", "https://example.com:80"],
|
["https://example.com:80/u/john.doe", "https://example.com:80"],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideLoginData */
|
||||||
|
public function testLogInAUser(array $params, string $authenticatedUser = null, ResponseInterface $exp) {
|
||||||
|
\Phake::when(Arsse::$db)->tokenCreate->thenReturn("authCode");
|
||||||
|
$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([
|
||||||
|
'me' => $params['me'],
|
||||||
|
'client_id' => $params['client_id'],
|
||||||
|
'redirect_uri' => $params['redirect_uri'],
|
||||||
|
'response_type' => strlen($params['response_type'] ?? "") ? $params['response_type'] : "id",
|
||||||
|
], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
|
||||||
|
} else {
|
||||||
|
\Phake::verify(Arsse::$db, \Phake::times(0))->tokenCreate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"])],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue