Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
95.00% |
38 / 40 |
|
50.00% |
2 / 4 |
CRAP | |
0.00% |
0 / 1 |
| DefaultAuthorizationForm | |
95.00% |
38 / 40 |
|
50.00% |
2 / 4 |
13 | |
0.00% |
0 / 1 |
| __construct | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
4.01 | |||
| showForm | |
96.00% |
24 / 25 |
|
0.00% |
0 / 1 |
7 | |||
| transformAuthorizationCode | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| setLogger | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php declare(strict_types=1); |
| 2 | |
| 3 | namespace Taproot\IndieAuth\Callback; |
| 4 | |
| 5 | use BadMethodCallException; |
| 6 | use BarnabyWalters\Mf2 as M; |
| 7 | use Exception; |
| 8 | use Psr\Http\Message\ServerRequestInterface; |
| 9 | use GuzzleHttp\Psr7\Response; |
| 10 | use Psr\Http\Message\ResponseInterface; |
| 11 | use Psr\Log\LoggerAwareInterface; |
| 12 | use Psr\Log\LoggerInterface; |
| 13 | use Psr\Log\NullLogger; |
| 14 | |
| 15 | use function Taproot\IndieAuth\renderTemplate; |
| 16 | |
| 17 | /** |
| 18 | * Default Authorization Form |
| 19 | * |
| 20 | * This implementation of {@see AuthorizationFormInterface} is used by {@see \Taproot\IndieAuth\Server} if the user doesn’t |
| 21 | * provide one of their own. It presents the user with a simple consent screen, showing any |
| 22 | * available details about the client app, and allowing the user to grant any requested scopes. |
| 23 | * |
| 24 | * When the form is submitted, any granted scopes are then added to the authorization code data. |
| 25 | * |
| 26 | * You can customise the authorization template used by passing either path to your own template or |
| 27 | * a custom callable to the constructor. Refer to the default template `/templates/default_authorization_page.html.php` |
| 28 | * as a starting point. |
| 29 | * |
| 30 | * If you want to add additional form controls (e.g. configurable token lifetimes), as well as |
| 31 | * making a new template, you’ll need to make a subclass which overrides {@see DefaultAuthorizationForm::transformAuthorizationCode()} |
| 32 | * to additionally handle your new form data. |
| 33 | * |
| 34 | * For any more involved customisation, it may make sense to create your own implementation of {@see AuthorizationFormInterface}. |
| 35 | */ |
| 36 | class DefaultAuthorizationForm implements AuthorizationFormInterface, LoggerAwareInterface { |
| 37 | /** @var string $csrfKey */ |
| 38 | public $csrfKey; |
| 39 | |
| 40 | /** @var callable $formTemplateCallable */ |
| 41 | private $formTemplateCallable; |
| 42 | |
| 43 | /** @var LoggerInterface $logger */ |
| 44 | private $logger; |
| 45 | |
| 46 | /** |
| 47 | * Constructor |
| 48 | * |
| 49 | * @param string|callable|null $formTemplate The path to a custom template, or a template callable with the signature `function (array $context): string`. Uses the default if null. |
| 50 | * @param string|null $csrfKey The key used to retrieve a CSRF token from the request attributes, and as its form data name. Uses the default defined in Server if null. Only change this if you’re using a custom CSRF middleware. |
| 51 | * @param LoggerInterface|null $logger A logger. |
| 52 | */ |
| 53 | public function __construct($formTemplate=null, ?string $csrfKey=null, ?LoggerInterface $logger=null) { |
| 54 | $this->csrfKey = $csrfKey ?? \Taproot\IndieAuth\Server::DEFAULT_CSRF_KEY; |
| 55 | $this->logger = $logger ?? new NullLogger; |
| 56 | |
| 57 | $formTemplate = $formTemplate ?? __DIR__ . '/../../templates/default_authorization_page.html.php'; |
| 58 | if (is_string($formTemplate)) { |
| 59 | if (!file_exists($formTemplate)) { |
| 60 | $this->logger->warning("\$formTemplate string passed to DefaultAuthorizationForm was not a valid path.", ['formTemplate' => $formTemplate]); |
| 61 | } |
| 62 | $formTemplate = function (array $context) use ($formTemplate): string { |
| 63 | return renderTemplate($formTemplate, $context); |
| 64 | }; |
| 65 | } |
| 66 | |
| 67 | if (!is_callable($formTemplate)) { |
| 68 | throw new BadMethodCallException("\$formTemplate must be a string (path), callable, or null."); |
| 69 | } |
| 70 | |
| 71 | $this->formTemplateCallable = $formTemplate; |
| 72 | } |
| 73 | |
| 74 | public function showForm(ServerRequestInterface $request, array $authenticationResult, string $formAction, $clientHAppOrException): ResponseInterface { |
| 75 | // Show an authorization page. List all requested scopes, as this default |
| 76 | // function has no way of knowing which scopes are supported by the consumer. |
| 77 | $scopes = []; |
| 78 | foreach(explode(' ', $request->getQueryParams()['scope'] ?? '') as $s) { |
| 79 | $scopes[$s] = null; // Ideally there would be a description of the scope here, we don’t have one though. |
| 80 | } |
| 81 | |
| 82 | $exception = null; |
| 83 | $appData = null; |
| 84 | if ($clientHAppOrException instanceof Exception) { |
| 85 | $exception = $clientHAppOrException; |
| 86 | } elseif (M\isMicroformat($clientHAppOrException)) { |
| 87 | $appData = [ |
| 88 | 'name' => M\getPlaintext($clientHAppOrException, 'name'), |
| 89 | 'url' => M\getPlaintext($clientHAppOrException, 'url'), |
| 90 | 'photo' => M\getPlaintext($clientHAppOrException, 'photo'), |
| 91 | 'author' => null |
| 92 | ]; |
| 93 | |
| 94 | if (M\hasProp($clientHAppOrException, 'author')) { |
| 95 | $rawAuthor = $clientHAppOrException['properties']['author'][0]; |
| 96 | if (is_string($rawAuthor)) { |
| 97 | $appData['author'] = $rawAuthor; |
| 98 | } elseif (M\isMicroformat($rawAuthor)) { |
| 99 | $appData['author'] = [ |
| 100 | 'name' => M\getPlaintext($rawAuthor, 'name'), |
| 101 | 'url' => M\getPlaintext($rawAuthor, 'url'), |
| 102 | 'photo' => M\getPlaintext($rawAuthor, 'photo') |
| 103 | ]; |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | return new Response(200, ['content-type' => 'text/html'], call_user_func($this->formTemplateCallable, [ |
| 109 | 'scopes' => $scopes, |
| 110 | 'user' => $authenticationResult, |
| 111 | 'formAction' => $formAction, |
| 112 | 'request' => $request, |
| 113 | 'clientHApp' => $appData, |
| 114 | 'exception' => $exception, |
| 115 | 'clientId' => $request->getQueryParams()['client_id'], |
| 116 | 'clientRedirectUri' => $request->getQueryParams()['redirect_uri'], |
| 117 | 'csrfFormElement' => '<input type="hidden" name="' . htmlentities($this->csrfKey) . '" value="' . htmlentities($request->getAttribute($this->csrfKey)) . '" />' |
| 118 | ])); |
| 119 | } |
| 120 | |
| 121 | public function transformAuthorizationCode(ServerRequestInterface $request, array $code): array { |
| 122 | // Add any granted scopes from the form to the code. |
| 123 | $grantedScopes = $request->getParsedBody()['taproot_indieauth_server_scope'] ?? []; |
| 124 | |
| 125 | // This default implementation naievely accepts any scopes it receives from the form. |
| 126 | // You may wish to perform some sort of validation. |
| 127 | $code['scope'] = join(' ', $grantedScopes); |
| 128 | |
| 129 | // You may wish to additionally make any other necessary changes to the the code based on |
| 130 | // the form submission, e.g. if the user set a custom token lifetime, or wanted extra data |
| 131 | // stored on the token to affect how it behaves. |
| 132 | return $code; |
| 133 | } |
| 134 | |
| 135 | public function setLogger(LoggerInterface $logger): void { |
| 136 | $this->logger = $logger; |
| 137 | } |
| 138 | } |