<?php

declare(strict_types=1);

/*
 * Copyright (c) 2017-2023 François Kooman <fkooman@tuxed.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

require_once dirname(__DIR__) . '/vendor/autoload.php';
require_once __DIR__ . '/MyBearerValidator.php';
require_once __DIR__ . '/MyClientDb.php';
require_once __DIR__ . '/MyOAuthServer.php';

use fkooman\OAuth\Server\Exception\OAuthException;
use fkooman\OAuth\Server\Http\JsonResponse;
use fkooman\OAuth\Server\Http\Request;
use fkooman\OAuth\Server\Http\Response;

/**
 * Function to "escape" strings to prevent XSS. Typically your template
 * engine will take care of this, but in this example there is no template
 * engine.
 */
function escapeString(string $inputStr): string
{
    return htmlentities($inputStr, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

$issuerIdentity = 'http://localhost:8080';
$request = Request::fromServerVariables();

try {
    $oauthServer = new MyOAuthServer($issuerIdentity);
    $bearerValidator = new MyBearerValidator();

    // user authentication MUST take place, here we ignore this for simplicity,
    // and assume the user_id is "foo"
    $userId = 'foo';

    switch ($request->requestMethod() . ':' . $request->pathInfo()) {
        case 'HEAD:/authorize':
        case 'GET:/authorize':
            // optional "shortcut" to avoid "Approval" dialog if the client has
            // "requireApproval" set to false
            if ($authorizeResponse = $oauthServer->getAuthorizeResponse($userId)) {
                $authorizeResponse->send();

                break;
            }

            // this is an authorization request, parse all parameters and
            // return an array with data that you can use to ask the user
            // for authorization, this is a very minimal HTML form example
            $authorizeVariables = $oauthServer->getAuthorize();
            $httpResponse = new Response(
                sprintf('<html><head><title>Authorize</title></head><body><h1>Authorize</h1><pre>%s</pre><form method="post"><button type="submit" name="approve" value="yes">Approve</button><button type="submit" name="approve" value="no">Reject</button></form></body></html>', var_export($authorizeVariables, true)),
                ['Content-Type' => 'text/html']
            );
            // the Response object is a simple HTTP wrapper that has the
            // statusCode, responseHeaders and responseBody, here we send it
            // directly, if you use a framework you can extract those and pass
            // them along...
            $httpResponse->send();

            break;
        case 'POST:/authorize':
            // you MUST implement CSRF protection!
            $httpResponse = $oauthServer->postAuthorize($userId);
            $httpResponse->send();

            break;
        case 'POST:/token':
            $jsonResponse = $oauthServer->postToken();

            // we print the HTTP response to the "error_log" for easy debugging
            error_log(var_export($jsonResponse, true));
            $jsonResponse->send();

            break;
        case 'HEAD:/authorizations':
        case 'GET:/authorizations':
            $storage = $oauthServer->getStorage();
            echo '<html><head><title>Authorizations</title></head><body><h1>Authorizations</h1><ul>';
            foreach ($storage->getAuthorizations($userId) as $authorization) {
                echo sprintf(
                    '<li>User: "%s", Client "%s", Scope "%s", Last Used "%s"</li>',
                    $userId,
                    $authorization->clientId(),
                    (string) $authorization->scope(),
                    $authorization->lastUsed()->format(DateTimeImmutable::ATOM)
                );
            }
            echo '</ul></body></html>';

            break;
        case 'HEAD:/revoke':
        case 'GET:/revoke':
            $storage = $oauthServer->getStorage();
            echo '<html><head><title>Revoke</title></head><body><h1>Revoke</h1><ul>';
            foreach ($storage->getAuthorizations($userId) as $authorization) {
                echo sprintf(
                    '<li>Deleting Authorization (User: "%s", Client "%s", Scope "%s")</li>',
                    $userId,
                    $authorization->clientId(),
                    (string) $authorization->scope()
                );
                $storage->deleteAuthorization($authorization->authKey());
            }
            echo '</ul></body></html>';

            break;
        case 'HEAD:/api':
        case 'GET:/api':
            // obtain accessToken from the "Authorization", this contains
            // the user_id the authorization is bound to, as well as some other
            // information, e.g. approved "scope"
            $accessToken = $bearerValidator->validate();

            // require both the "foo" and "bar" scope
            $accessToken->scope()->requireAll(['foo', 'bar']);
            // require any of "foo" or "bar" scope
            // $accessToken->scope()->requireAny(['foo', 'bar']);

            // use "helper" JsonResponse here, typically your HTTP framework
            // will provide this...
            $jsonResponse = new JsonResponse(
                ['user_id' => $accessToken->userId()]
            );
            $jsonResponse->send();

            break;
        case 'HEAD:/.well-known/oauth-authorization-server':
        case 'GET:/.well-known/oauth-authorization-server':
            $jsonResponse = new JsonResponse(
                MyOAuthServer::metadata($issuerIdentity, $issuerIdentity . '/authorize', $issuerIdentity . '/token')
            );
            $jsonResponse->send();

            break;
        default:
            if ($request->isBrowser()) {
                $r = new Response('[404] Not Found');
                $r->send();
                exit(0);
            }

            $r = new JsonResponse(['error' => 'not_found'], [], 404);
            $r->send();
    }
} catch (OAuthException $e) {
    if ($request->isBrowser()) {
        $r = new Response(
            sprintf(
                '[%d] %s (%s)',
                $e->getStatusCode(),
                escapeString($e->getMessage()),
                escapeString($e->getDescription() ?? 'N/A')
            ),
            ['Content-Type' => 'text/html'],
            $e->getStatusCode()
        );
        $r->send();
        exit(0);
    }
    $e->getJsonResponse()->send();
} catch (Exception $e) {
    if ($request->isBrowser()) {
        $r = new Response(
            sprintf(
                '[500] %s',
                escapeString($e->getMessage())
            ),
            ['Content-Type' => 'text/html'],
            500
        );
        $r->send();
        exit(0);
    }

    $r = new JsonResponse(['error' => $e->getMessage()], [], 500);
    $r->send();
}
