2019-03-10 03:44:59 +00:00
|
|
|
<?php
|
|
|
|
/** @license MIT
|
|
|
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
|
|
|
* See LICENSE and AUTHORS files for details */
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace JKingWeb\Arsse\REST\Fever;
|
|
|
|
|
|
|
|
use JKingWeb\Arsse\Arsse;
|
|
|
|
use JKingWeb\Arsse\Database;
|
|
|
|
use JKingWeb\Arsse\User;
|
|
|
|
use JKingWeb\Arsse\Service;
|
|
|
|
use JKingWeb\Arsse\Context\Context;
|
|
|
|
use JKingWeb\Arsse\Misc\ValueInfo;
|
|
|
|
use JKingWeb\Arsse\AbstractException;
|
|
|
|
use JKingWeb\Arsse\Db\ExceptionInput;
|
|
|
|
use JKingWeb\Arsse\Feed\Exception as FeedException;
|
|
|
|
use JKingWeb\Arsse\REST\Target;
|
|
|
|
use JKingWeb\Arsse\REST\Exception404;
|
|
|
|
use JKingWeb\Arsse\REST\Exception405;
|
|
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
|
|
use Psr\Http\Message\ResponseInterface;
|
2019-03-19 02:49:47 +00:00
|
|
|
use Zend\Diactoros\Response\JsonResponse;
|
2019-03-10 03:44:59 +00:00
|
|
|
use Zend\Diactoros\Response\EmptyResponse;
|
|
|
|
|
|
|
|
class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|
|
|
const LEVEL = 3;
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public function dispatch(ServerRequestInterface $req): ResponseInterface {
|
|
|
|
$inR = $req->getQueryParams();
|
2019-03-19 02:49:47 +00:00
|
|
|
$inW = $req->getParsedBody();
|
|
|
|
if (!array_key_exists("api", $inR)) {
|
2019-03-10 03:44:59 +00:00
|
|
|
// the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404
|
|
|
|
return new EmptyResponse(404);
|
|
|
|
}
|
|
|
|
$xml = $inR['api'] === "xml";
|
|
|
|
switch ($req->getMethod()) {
|
|
|
|
case "OPTIONS":
|
|
|
|
// do stuff
|
|
|
|
break;
|
|
|
|
case "POST":
|
|
|
|
if (strlen($req->getHeaderLine("Content-Type")) && $req->getHeaderLine("Content-Type") !== "application/x-www-form-urlencoded") {
|
|
|
|
return new EmptyResponse(415, ['Accept' => "application/x-www-form-urlencoded"]);
|
|
|
|
}
|
|
|
|
$out = [
|
|
|
|
'api_version' => self::LEVEL,
|
|
|
|
'auth' => 0,
|
|
|
|
];
|
2019-03-20 03:37:08 +00:00
|
|
|
if ($req->getAttribute("authenticated", false)) {
|
|
|
|
// if HTTP authentication was successfully used, set the expected user ID
|
|
|
|
Arsse::$user->id = $req->getAttribute("authenticatedUser");
|
|
|
|
$out['auth'] = 1;
|
|
|
|
} elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) {
|
|
|
|
// otherwise if HTTP authentication failed or is required, deny access at the HTTP level
|
|
|
|
return new EmptyResponse(401);
|
|
|
|
}
|
2019-03-10 03:44:59 +00:00
|
|
|
// check that the user specified credentials
|
|
|
|
if ($this->logIn(strtolower($inW['api_key'] ?? ""))) {
|
|
|
|
$out['auth'] = 1;
|
|
|
|
} else {
|
2019-03-20 03:37:08 +00:00
|
|
|
$out['auth'] = 0;
|
2019-03-10 03:44:59 +00:00
|
|
|
return $this->formatResponse($out, $xml);
|
|
|
|
}
|
|
|
|
// handle each possible parameter
|
|
|
|
# do stuff
|
|
|
|
// return the result
|
|
|
|
return $this->formatResponse($out, $xml);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return new EmptyResponse(405, ['Allow' => "OPTIONS,POST"]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function formatResponse(array $data, bool $xml): ResponseInterface {
|
|
|
|
if ($xml) {
|
|
|
|
throw \Exception("Not implemented yet");
|
|
|
|
} else {
|
|
|
|
return new JsonResponse($data, 200, [], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function logIn(string $hash): bool {
|
|
|
|
// if HTTP authentication was successful and sessions are not enforced, proceed unconditionally
|
|
|
|
if (isset(Arsse::$user->id) && !Arsse::$conf->userSessionEnforced) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
// verify the supplied hash is valid
|
2019-03-19 02:49:47 +00:00
|
|
|
$s = Arsse::$db->TokenLookup("fever.login", $hash);
|
2019-03-10 03:44:59 +00:00
|
|
|
} catch (\JKingWeb\Arsse\Db\ExceptionInput $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// set the user name
|
|
|
|
Arsse::$user->id = $s['user'];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-21 14:19:30 +00:00
|
|
|
public static function userRegister(string $user, string $password = null): string {
|
2019-03-10 03:44:59 +00:00
|
|
|
$password = $password ?? Arsse::$user->generatePassword();
|
|
|
|
$hash = md5("$user:$password");
|
2019-03-21 02:24:35 +00:00
|
|
|
$tr = Arsse::$db->begin();
|
|
|
|
Arsse::$db->tokenRevoke($user, "fever.login");
|
2019-03-10 03:44:59 +00:00
|
|
|
Arsse::$db->tokenCreate($user, "fever.login", $hash);
|
2019-03-21 02:24:35 +00:00
|
|
|
$tr->commit();
|
2019-03-10 03:44:59 +00:00
|
|
|
return $password;
|
|
|
|
}
|
2019-03-20 14:42:04 +00:00
|
|
|
|
2019-03-21 14:19:30 +00:00
|
|
|
public static function userUnregister(string $user): bool {
|
2019-03-20 14:42:04 +00:00
|
|
|
return (bool) Arsse::$db->tokenRevoke($user, "fever.login");
|
|
|
|
}
|
2019-03-24 19:05:21 +00:00
|
|
|
|
|
|
|
public static function userAuthenticate(string $user, string $password): bool {
|
|
|
|
try {
|
|
|
|
return (bool) Arsse::$db->tokenLookup("fever.login", md5("$user:$password"));
|
|
|
|
} catch (ExceptionInput $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-03-10 03:44:59 +00:00
|
|
|
}
|