Skip to content

Commit d0b01b1

Browse files
authored
Merge pull request #686 from code16/fix-wizard-command-authorization
Add authorizeForStep() method for wizard commands
2 parents 7ef0939 + 3a9794b commit d0b01b1

8 files changed

Lines changed: 232 additions & 10 deletions

src/EntityList/Commands/Wizards/InstanceWizardCommand.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ protected function initialDataForStep(string $step, mixed $instanceId): array
5757
return [];
5858
}
5959

60+
public function authorizeForStep(string $step, mixed $instanceId): bool
61+
{
62+
return true;
63+
}
64+
6065
abstract protected function executeFirstStep(mixed $instanceId, array $data): array;
6166

6267
abstract protected function buildFormFieldsForFirstStep(FieldsContainer $formFields): void;

src/EntityList/Commands/Wizards/IsEntityWizardCommand.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ protected function initialDataForStep(string $step): array
5454
{
5555
return [];
5656
}
57+
58+
public function authorizeForStep(string $step): bool
59+
{
60+
return true;
61+
}
5762
}

src/EntityList/Commands/Wizards/IsWizardCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ protected function toStep(string $step): array
3838
];
3939
}
4040

41-
protected function extractStepFromRequest(): ?string
41+
public function extractStepFromRequest(): ?string
4242
{
43-
if ($step = request()->get('command_step')) {
43+
if ($step = request()->input('command_step')) {
4444
[$step, $this->key] = explode(':', $step);
4545

4646
return $step;

src/Http/Controllers/Api/Commands/ApiDashboardCommandController.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Code16\Sharp\Http\Controllers\Api\Commands;
44

5+
use Code16\Sharp\Dashboard\Commands\DashboardWizardCommand;
56
use Code16\Sharp\Dashboard\SharpDashboard;
67
use Code16\Sharp\Data\Commands\CommandFormData;
78
use Code16\Sharp\Exceptions\Auth\SharpAuthorizationException;
@@ -51,14 +52,17 @@ public function update(string $globalFilter, string $entityKey, string $commandK
5152

5253
protected function getDashboardCommandHandler(SharpDashboard $dashboard, string $commandKey)
5354
{
54-
if ($handler = $dashboard->findDashboardCommandHandler($commandKey)) {
55-
$handler->buildCommandConfig();
55+
$commandHandler = $dashboard->findDashboardCommandHandler($commandKey);
56+
$commandHandler->buildCommandConfig();
5657

57-
if (! $handler->authorize()) {
58-
throw new SharpAuthorizationException();
59-
}
58+
$authorized = $commandHandler instanceof DashboardWizardCommand && ($step = $commandHandler->extractStepFromRequest())
59+
? $commandHandler->authorizeForStep($step)
60+
: $commandHandler->authorize();
61+
62+
if (! $authorized) {
63+
throw new SharpAuthorizationException();
6064
}
6165

62-
return $handler;
66+
return $commandHandler;
6367
}
6468
}

src/Http/Controllers/Api/Commands/HandlesEntityCommand.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Code16\Sharp\Http\Controllers\Api\Commands;
44

55
use Code16\Sharp\EntityList\Commands\EntityCommand;
6+
use Code16\Sharp\EntityList\Commands\Wizards\EntityWizardCommand;
67
use Code16\Sharp\EntityList\SharpEntityList;
78
use Code16\Sharp\Exceptions\Auth\SharpAuthorizationException;
89

@@ -13,7 +14,11 @@ protected function getEntityCommandHandler(SharpEntityList $list, string $comman
1314
$commandHandler = $list->findEntityCommandHandler($commandKey);
1415
$commandHandler->buildCommandConfig();
1516

16-
if (! $commandHandler->authorize()) {
17+
$authorized = $commandHandler instanceof EntityWizardCommand && ($step = $commandHandler->extractStepFromRequest())
18+
? $commandHandler->authorizeForStep($step)
19+
: $commandHandler->authorize();
20+
21+
if (! $authorized) {
1722
throw new SharpAuthorizationException();
1823
}
1924

src/Http/Controllers/Api/Commands/HandlesInstanceCommand.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Code16\Sharp\Http\Controllers\Api\Commands;
44

55
use Code16\Sharp\EntityList\Commands\InstanceCommand;
6+
use Code16\Sharp\EntityList\Commands\Wizards\InstanceWizardCommand;
67
use Code16\Sharp\EntityList\SharpEntityList;
78
use Code16\Sharp\Exceptions\Auth\SharpAuthorizationException;
89
use Code16\Sharp\Show\SharpShow;
@@ -17,7 +18,11 @@ protected function getInstanceCommandHandler(
1718
$commandHandler = $commandContainer->findInstanceCommandHandler($commandKey);
1819
$commandHandler->buildCommandConfig();
1920

20-
if (! $commandHandler->authorize() || ! $commandHandler->authorizeFor($instanceId)) {
21+
$authorized = $commandHandler instanceof InstanceWizardCommand && ($step = $commandHandler->extractStepFromRequest())
22+
? $commandHandler->authorizeForStep($step, $instanceId)
23+
: $commandHandler->authorize() && $commandHandler->authorizeFor($instanceId);
24+
25+
if (! $authorized) {
2126
throw new SharpAuthorizationException();
2227
}
2328

tests/Http/Api/Commands/ApiEntityListEntityWizardCommandControllerTest.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,104 @@ protected function executeStepNextStep(array $data): array
297297
]);
298298
});
299299

300+
it('authorize() is only called for firstStep', function () {
301+
$authorizeForStep = true;
302+
303+
fakeListFor('person', new class($authorizeForStep) extends PersonList
304+
{
305+
public function __construct(public bool &$authorizeForStep) {}
306+
307+
protected function getEntityCommands(): ?array
308+
{
309+
return [
310+
'wizard' => new class($this->authorizeForStep) extends EntityWizardCommand
311+
{
312+
public function __construct(public bool &$authorizeForStep) {}
313+
314+
protected function getKey(): string
315+
{
316+
return 'test-key';
317+
}
318+
319+
public function label(): ?string
320+
{
321+
return 'my command';
322+
}
323+
324+
public function authorize(): bool
325+
{
326+
return false;
327+
}
328+
329+
public function authorizeForStep(string $step): bool
330+
{
331+
return $this->authorizeForStep;
332+
}
333+
334+
public function buildFormFieldsForFirstStep(FieldsContainer $formFields): void
335+
{
336+
$formFields->addField(SharpFormTextField::make('name'));
337+
}
338+
339+
protected function executeFirstStep(array $data): array
340+
{
341+
$this->validate($data, ['name' => 'required']);
342+
343+
return $this->toStep('next-step');
344+
}
345+
346+
public function buildFormFieldsForStepNextStep(FieldsContainer $formFields): void
347+
{
348+
$formFields->addField(SharpFormTextField::make('age'));
349+
}
350+
351+
protected function executeStepNextStep(array $data): array
352+
{
353+
return $this->reload();
354+
}
355+
},
356+
];
357+
}
358+
});
359+
360+
// First post step 1...
361+
$this
362+
->postJson(
363+
route('code16.sharp.api.list.command.entity', [
364+
'globalFilter' => 'root',
365+
'entityKey' => 'person',
366+
'commandKey' => 'wizard',
367+
]),
368+
['data' => ['name' => 'test']],
369+
)
370+
->assertForbidden();
371+
372+
// Then post step 2 but authorized (default)
373+
$this
374+
->postJson(
375+
route('code16.sharp.api.list.command.entity', [
376+
'entityKey' => 'person',
377+
'commandKey' => 'wizard',
378+
'command_step' => 'next-step:test-key',
379+
]),
380+
['data' => ['age' => '22']],
381+
)
382+
->assertOk();
383+
384+
// Post 2 but disallowed
385+
$authorizeForStep = false;
386+
$this
387+
->postJson(
388+
route('code16.sharp.api.list.command.entity', [
389+
'entityKey' => 'person',
390+
'commandKey' => 'wizard',
391+
'command_step' => 'next-step:test-key',
392+
]),
393+
['data' => ['age' => '22']],
394+
)
395+
->assertForbidden();
396+
});
397+
300398
it('allows to define a global method for step execution', function () {
301399
fakeListFor('person', new class() extends PersonList
302400
{

tests/Http/Api/Commands/ApiEntityListInstanceWizardCommandControllerTest.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,106 @@ protected function executeStepNextStep($instanceId, array $data): array
302302
]);
303303
});
304304

305+
it('authorize() is only called for firstStep', function () {
306+
$authorizeForStep = true;
307+
308+
fakeListFor('person', new class($authorizeForStep) extends PersonList
309+
{
310+
public function __construct(public bool &$authorizeForStep) {}
311+
312+
protected function getInstanceCommands(): ?array
313+
{
314+
return [
315+
'wizard' => new class($this->authorizeForStep) extends InstanceWizardCommand
316+
{
317+
public function __construct(public bool &$authorizeForStep) {}
318+
319+
protected function getKey(): string
320+
{
321+
return 'test-key';
322+
}
323+
324+
public function label(): ?string
325+
{
326+
return 'my command';
327+
}
328+
329+
public function authorizeFor(mixed $instanceId): bool
330+
{
331+
return false;
332+
}
333+
334+
public function authorizeForStep(string $step, mixed $instanceId): bool
335+
{
336+
return $this->authorizeForStep;
337+
}
338+
339+
public function buildFormFieldsForFirstStep(FieldsContainer $formFields): void
340+
{
341+
$formFields->addField(SharpFormTextField::make('name'));
342+
}
343+
344+
protected function executeFirstStep($instanceId, array $data): array
345+
{
346+
$this->validate($data, ['name' => 'required']);
347+
348+
return $this->toStep('next-step');
349+
}
350+
351+
public function buildFormFieldsForStepNextStep(FieldsContainer $formFields): void
352+
{
353+
$formFields->addField(SharpFormTextField::make('age'));
354+
}
355+
356+
protected function executeStepNextStep($instanceId, array $data): array
357+
{
358+
return $this->reload();
359+
}
360+
},
361+
];
362+
}
363+
});
364+
365+
// First post step 1...
366+
$this
367+
->postJson(
368+
route('code16.sharp.api.list.command.instance', [
369+
'globalFilter' => 'root',
370+
'entityKey' => 'person',
371+
'commandKey' => 'wizard',
372+
'instanceId' => 1]),
373+
['data' => ['name' => 'test']],
374+
)
375+
->assertForbidden();
376+
377+
// Then post step 2 should be authorized (default)...
378+
$this
379+
->postJson(
380+
route('code16.sharp.api.list.command.instance', [
381+
'entityKey' => 'person',
382+
'commandKey' => 'wizard',
383+
'instanceId' => 1,
384+
'command_step' => 'next-step:test-key',
385+
]),
386+
['data' => ['age' => '22']],
387+
)
388+
->assertOk();
389+
390+
// Post 2 but disallowed
391+
$authorizeForStep = false;
392+
$this
393+
->postJson(
394+
route('code16.sharp.api.list.command.instance', [
395+
'entityKey' => 'person',
396+
'commandKey' => 'wizard',
397+
'instanceId' => 1,
398+
'command_step' => 'next-step:test-key',
399+
]),
400+
['data' => ['age' => '22']],
401+
)
402+
->assertForbidden();
403+
});
404+
305405
it('allows to define a global method for step execution', function () {
306406
fakeListFor('person', new class() extends PersonList
307407
{

0 commit comments

Comments
 (0)