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 | } |