Skip to content

Commit b766044

Browse files
authored
Policies (#261)
* Adding policy for attach/detach. * Apply fixes from StyleCI (#260) * Formatting.
1 parent 1cd6345 commit b766044

File tree

11 files changed

+313
-5
lines changed

11 files changed

+313
-5
lines changed

src/Fields/Field.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ public function fillAttribute(RestifyRequest $request, $model, int $bulkRow = nu
226226
}
227227

228228
if ($request->isStoreRequest() && is_callable($this->storeCallback)) {
229-
return $model->{$this->attribute} = call_user_func(
229+
return call_user_func(
230230
$this->storeCallback, $request, $model, $this->attribute, $bulkRow
231231
);
232232
}
@@ -238,7 +238,7 @@ public function fillAttribute(RestifyRequest $request, $model, int $bulkRow = nu
238238
}
239239

240240
if ($request->isUpdateRequest() && is_callable($this->updateCallback)) {
241-
return $model->{$this->attribute} = call_user_func(
241+
return call_user_func(
242242
$this->updateCallback, $request, $model, $this->attribute, $bulkRow
243243
);
244244
}

src/Http/Controllers/RepositoryAttachController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class RepositoryAttachController extends RepositoryController
1414
public function __invoke(RepositoryAttachRequest $request)
1515
{
1616
$model = $request->findModelOrFail();
17-
$repository = $request->repository()->allowToUpdate($request);
17+
$repository = $request->repository();
1818

1919
if (is_callable($method = $this->guessMethodName($request, $repository))) {
2020
return call_user_func($method, $request, $repository, $model);
@@ -23,6 +23,7 @@ public function __invoke(RepositoryAttachRequest $request)
2323
return $repository->attach(
2424
$request, $request->repositoryId,
2525
collect(Arr::wrap($request->input($request->relatedRepository)))
26+
->filter(fn ($relatedRepositoryId) => $request->repository()->allowToAttach($request, $request->attachRelatedModels()))
2627
->map(fn ($relatedRepositoryId) => $this->initializePivot(
2728
$request, $model->{$request->viaRelationship ?? $request->relatedRepository}(), $relatedRepositoryId
2829
))

src/Http/Controllers/RepositoryDetachController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ public function __invoke(RepositoryDetachRequest $request)
1717
return $repository->detach(
1818
$request, $request->repositoryId,
1919
collect(Arr::wrap($request->input($request->relatedRepository)))
20-
->map(fn ($relatedRepositoryId) => $this->initializePivot(
20+
->filter(fn ($relatedRepositoryId) => $request->repository()->allowToDetach($request, $request->detachRelatedModels()))
21+
->map(fn ($relatedRepositoryId) => $this->initializePivot(
2122
$request, $model->{$request->viaRelationship ?? $request->relatedRepository}(), $relatedRepositoryId
2223
))
2324
);

src/Http/Requests/RepositoryAttachRequest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
namespace Binaryk\LaravelRestify\Http\Requests;
44

5+
use Binaryk\LaravelRestify\Restify;
6+
use Illuminate\Support\Arr;
7+
use Illuminate\Support\Collection;
8+
59
class RepositoryAttachRequest extends RestifyRequest
610
{
11+
public function attachRelatedModels(): Collection
12+
{
13+
$relatedRepository = $this->repository(
14+
Restify::repositoryForTable($table = $this->relatedRepository)::uriKey()
15+
);
16+
17+
if (is_null($relatedRepository)) {
18+
abort(400, "Missing repository for the [$table] table");
19+
}
20+
21+
return collect(Arr::wrap($this->input($this->relatedRepository)))
22+
->map(fn ($id) => $relatedRepository->model()->newModelQuery()->whereKey($id)->first());
23+
}
724
}

src/Http/Requests/RepositoryDetachRequest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
namespace Binaryk\LaravelRestify\Http\Requests;
44

5+
use Binaryk\LaravelRestify\Restify;
6+
use Illuminate\Support\Arr;
7+
use Illuminate\Support\Collection;
8+
59
class RepositoryDetachRequest extends RestifyRequest
610
{
11+
public function detachRelatedModels(): Collection
12+
{
13+
$relatedRepository = $this->repository(
14+
Restify::repositoryForTable($table = $this->relatedRepository)::uriKey()
15+
);
16+
17+
if (is_null($relatedRepository)) {
18+
abort(400, "Missing repository for the [$table] table");
19+
}
20+
21+
return collect(Arr::wrap($this->input($this->relatedRepository)))
22+
->map(fn ($id) => $relatedRepository->model()->newModelQuery()->whereKey($id)->first());
23+
}
724
}

src/Repositories/Repository.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,8 @@ public function update(RestifyRequest $request, $repositoryId)
690690
return $this->resource;
691691
});
692692

693+
$this->updateFields($request)->each(fn (Field $field) => $field->invokeAfter($request, $this->resource));
694+
693695
return $this->response()
694696
->data($this->serializeForShow($request))
695697
->success();
@@ -754,6 +756,24 @@ public function allowToUpdate(RestifyRequest $request, $payload = null): self
754756
return $this;
755757
}
756758

759+
public function allowToAttach(RestifyRequest $request, Collection $attachers): self
760+
{
761+
$methodGuesser = 'attach'.Str::studly($request->relatedRepository);
762+
763+
$attachers->each(fn ($model) => $this->authorizeToAttach($request, $methodGuesser, $model));
764+
765+
return $this;
766+
}
767+
768+
public function allowToDetach(RestifyRequest $request, Collection $attachers): self
769+
{
770+
$methodGuesser = 'detach'.Str::studly($request->relatedRepository);
771+
772+
$attachers->each(fn ($model) => $this->authorizeToDetach($request, $methodGuesser, $model));
773+
774+
return $this;
775+
}
776+
757777
public function allowToUpdateBulk(RestifyRequest $request, $payload = null): self
758778
{
759779
$this->authorizeToUpdateBulk($request);

src/Repositories/ValidatingTrait.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,26 @@ public static function validatorForUpdate(RestifyRequest $request, $resource = n
9898
});
9999
}
100100

101+
public static function validatorForAttach(RestifyRequest $request, $resource = null, array $plainPayload = null)
102+
{
103+
/** * @var Repository $on */
104+
$on = $resource ?? static::resolveWith(static::newModel());
105+
106+
$messages = $on->collectFields($request)->flatMap(function ($k) {
107+
$messages = [];
108+
foreach ($k->messages as $ruleFor => $message) {
109+
$messages[$k->attribute.'.'.$ruleFor] = $message;
110+
}
111+
112+
return $messages;
113+
})->toArray();
114+
115+
return Validator::make($plainPayload ?? $request->all(), $on->getUpdatingRules($request), $messages)->after(function ($validator) use ($request) {
116+
static::afterValidation($request, $validator);
117+
static::afterUpdatingValidation($request, $validator);
118+
});
119+
}
120+
101121
public static function validatorForUpdateBulk(RestifyRequest $request, $resource = null, array $plainPayload = null)
102122
{
103123
/** * @var Repository $on */

src/Restify.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static function repositoryForKey($key)
5252
}
5353

5454
/**
55-
* Get the repository class name for a given key.
55+
* Get the repository class name for a given model.
5656
*
5757
* @param string $model
5858
* @return string
@@ -68,6 +68,19 @@ public static function repositoryForModel($model)
6868
});
6969
}
7070

71+
/**
72+
* Get the repository class name for a given table name.
73+
*
74+
* @param string $table
75+
* @return string
76+
*/
77+
public static function repositoryForTable($table)
78+
{
79+
return collect(static::$repositories)->first(function ($value) use ($table) {
80+
return app($value::$model)->getTable() === $table;
81+
});
82+
}
83+
7184
/**
7285
* Register the given repositories.
7386
*

src/Traits/AuthorizableModels.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,36 @@ public function authorizeToUpdate(Request $request)
142142
$this->authorizeTo($request, 'update');
143143
}
144144

145+
public function authorizeToAttach(Request $request, $method, $model)
146+
{
147+
if (! static::authorizable()) {
148+
return true;
149+
}
150+
151+
$authorized = method_exists(Gate::getPolicyFor($this->model()), $method)
152+
? Gate::check($method, [$this->model(), $model])
153+
: true;
154+
155+
if (false === $authorized) {
156+
throw new AuthorizationException();
157+
}
158+
}
159+
160+
public function authorizeToDetach(Request $request, $method, $model)
161+
{
162+
if (! static::authorizable()) {
163+
return true;
164+
}
165+
166+
$authorized = method_exists(Gate::getPolicyFor($this->model()), $method)
167+
? Gate::check($method, [$this->model(), $model])
168+
: true;
169+
170+
if (false === $authorized) {
171+
throw new AuthorizationException();
172+
}
173+
}
174+
145175
public function authorizeToUpdateBulk(Request $request)
146176
{
147177
$this->authorizeTo($request, 'updateBulk');

tests/Controllers/RepositoryAttachControllerTest.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
namespace Binaryk\LaravelRestify\Tests\Controllers;
44

55
use Binaryk\LaravelRestify\Tests\Fixtures\Company\Company;
6+
use Binaryk\LaravelRestify\Tests\Fixtures\Company\CompanyPolicy;
7+
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
68
use Binaryk\LaravelRestify\Tests\IntegrationTest;
9+
use Illuminate\Support\Facades\Gate;
710

811
class RepositoryAttachControllerTest extends IntegrationTest
912
{
@@ -63,4 +66,64 @@ public function test_after_attach_a_user_to_company_number_of_users_increased()
6366
$usersFromCompany = $this->getJson('/restify-api/users?viaRepository=companies&viaRepositoryId=1&viaRelationship=users');
6467
$this->assertCount(1, $usersFromCompany->json('data'));
6568
}
69+
70+
public function test_policy_to_attach_a_user_to_a_company()
71+
{
72+
Gate::policy(Company::class, CompanyPolicy::class);
73+
74+
$user = $this->mockUsers(2)->first();
75+
$company = factory(Company::class)->create();
76+
$this->authenticate(
77+
factory(User::class)->create()
78+
);
79+
80+
$_SERVER['allow_attach_users'] = false;
81+
82+
$this->postJson('restify-api/companies/'.$company->id.'/attach/users', [
83+
'users' => $user->id,
84+
'is_admin' => true,
85+
])
86+
->assertForbidden();
87+
88+
$_SERVER['allow_attach_users'] = true;
89+
90+
$this->postJson('restify-api/companies/'.$company->id.'/attach/users', [
91+
'users' => $user->id,
92+
'is_admin' => true,
93+
])
94+
->assertCreated();
95+
}
96+
97+
public function test_policy_to_detach_a_user_to_a_company()
98+
{
99+
Gate::policy(Company::class, CompanyPolicy::class);
100+
101+
$user = $this->mockUsers(2)->first();
102+
$company = factory(Company::class)->create();
103+
$this->authenticate(
104+
factory(User::class)->create()
105+
);
106+
107+
$this->postJson('restify-api/companies/'.$company->id.'/attach/users', [
108+
'users' => $user->id,
109+
'is_admin' => true,
110+
])
111+
->assertCreated();
112+
113+
$_SERVER['allow_detach_users'] = false;
114+
115+
$this->postJson('restify-api/companies/'.$company->id.'/detach/users', [
116+
'users' => $user->id,
117+
'is_admin' => true,
118+
])
119+
->assertForbidden();
120+
121+
$_SERVER['allow_detach_users'] = true;
122+
123+
$this->postJson('restify-api/companies/'.$company->id.'/detach/users', [
124+
'users' => $user->id,
125+
'is_admin' => true,
126+
])
127+
->assertNoContent();
128+
}
66129
}

0 commit comments

Comments
 (0)