Skip to content

Commit

Permalink
Fix authorize rule (#10)
Browse files Browse the repository at this point in the history
* fix

* Cleanup test
  • Loading branch information
freekmurze authored and AlexVanderbist committed Oct 12, 2018
1 parent b592081 commit effb8cc
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 63 deletions.
30 changes: 11 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,31 @@ The package will automatically register itself.

### `authorized`

**DO NOT USE THIS METHOD, CURRENTLY BROKEN, WILL BE FIXED SOON**
Determine if the user is authorized to perform an ability.
Determine if the user is authorized to perform an ability on an instance of the given model. The id of the model is the field under validation

Consider the following definition:
Consider the following policy:

```php
Gate::define('lowUserId', function (User $user) {
return $user->id < 100;
});
```

All users with id 100 or higher will receive a validation error using the following validation rules:

```php
// in a `FormRequest`

public function rules()
class ModelPolicy
{
return [
'field_under_validation' => [new Authorized('lowUserId')],
];
use HandlesAuthorization;

public function edit(User $user, Model $model): bool
{
return $model->user->id === $user->id;
}
}
```

The following example will validate if the logged in user can edit the `Model` with the given `model_id` as its primary key.
This validation rule will pass if the id of the logged in user matches the `user_id` on `TestModel` who's it is in the `model_id` key of the request.

```php
// in a `FormRequest`

public function rules()
{
return [
'model_id' => [new Authorized('edit', Model::class)],
'model_id' => [new Authorized('edit', TestModel::class)],
];
}
```
Expand Down
14 changes: 11 additions & 3 deletions src/Rules/Authorized.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@ class Authorized implements Rule
/** @var array */
protected $arguments;

public function __construct(string $ability, $arguments = [])
public function __construct(string $ability, string $className)
{
$this->ability = $ability;

$this->arguments = array_wrap($arguments);
$this->className = $className;
}

public function passes($attribute, $value)
{
return optional(Auth::user())->can($this->ability, $this->arguments) ?? false;
if (! $user = Auth::user()) {
return false;
}

if (! $model = $this->className::find($value)) {
return false;
}

return $user->can($this->ability, $model);
}

public function message()
Expand Down
76 changes: 35 additions & 41 deletions tests/Rules/AuthorizedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,73 @@
use Illuminate\Support\Facades\Gate;
use Spatie\ValidationRules\Tests\TestCase;
use Spatie\ValidationRules\Rules\Authorized;
use Spatie\ValidationRules\Tests\TestClasses\Models\TestModel;
use Spatie\ValidationRules\Tests\TestClasses\Policies\TestModelPolicy;

class AuthorizedTest extends TestCase
{
public function setUp()
{
parent::setUp();

$this->actingAs(factory(User::class)->create());
Gate::policy(TestModel::class, TestModelPolicy::class);
}

/** @test */
public function it_will_return_true_if_the_gate_returns_true_for_the_given_ability_name()
{
Gate::define('abilityName', function () {
return true;
});
$rule = new Authorized('edit', TestModel::class);

$rule = new Authorized('abilityName');
$user = factory(User::class)->create(['id' => 1]);
TestModel::create([
'id' => 1,
'user_id' => $user->id,
]);

$this->assertTrue($rule->passes('attribute', 'value'));
}

/** @test */
public function it_will_return_false_if_the_gate_returns_true_for_the_given_ability_name()
{
Gate::define('otherAbilityName', function () {
return false;
});

$rule = new Authorized('otherAbilityName');
$this->actingAs($user);

$this->assertFalse($rule->passes('attribute', 'value'));
$this->assertTrue($rule->passes('attribute', '1'));
}

/** @test */
public function it_will_return_false_if_there_is_noone_logged_in()
public function it_will_return_false_if_noone_is_logged_in()
{
Auth::logout();

Gate::define('noUserAbilityName', function () {
return true;
});
$rule = new Authorized('edit', TestModel::class);

$rule = new Authorized('noUserAbilityName');
$user = factory(User::class)->create(['id' => 1]);
TestModel::create([
'id' => 1,
'user_id' => $user->id,
]);

$this->assertFalse($rule->passes('attribute', 'value'));
$this->assertFalse($rule->passes('attribute', '1'));
}

/** @test */
public function it_can_accept_an_argument()
public function it_will_return_false_if_the_model_is_not_found()
{
Gate::define('argumentAbilityName', function (User $user, $argument) {
return $argument === true;
});
$rule = new Authorized('edit', TestModel::class);

$rule = new Authorized('argumentAbilityName', true);
$this->assertTrue($rule->passes('attribute', 'value'));
$user = factory(User::class)->create(['id' => 1]);
TestModel::create([
'id' => 1,
'user_id' => $user->id,
]);

$rule = new Authorized('argumentAbilityName', false);
$this->assertFalse($rule->passes('attribute', 'value'));
$this->assertFalse($rule->passes('attribute', '2'));
}

/** @test */
public function it_can_accept_multiple_arguments_as_an_array()
public function it_will_return_false_if_the_gate_returns_false()
{
Gate::define('arrayAbilityName', function (User $user, int $argumentA, int $argumentB) {
return $argumentA + $argumentB === 3;
});
$rule = new Authorized('edit', TestModel::class);

$rule = new Authorized('arrayAbilityName', [1, 2]);
$this->assertTrue($rule->passes('attribute', 'value'));
$user = factory(User::class)->create(['id' => 1]);
TestModel::create([
'id' => 1,
'user_id' => 2,
]);

$rule = new Authorized('arrayAbilityName', [2, 3]);
$this->assertFalse($rule->passes('attribute', 'value'));
$this->assertFalse($rule->passes('attribute', '1'));
}
}
7 changes: 7 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,12 @@ protected function setUpDatabase()
$table->string('remember_token');
$table->timestamps();
});

Schema::create('test_models', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->nullable();
$table->unsignedInteger('user_id');
$table->timestamps();
});
}
}
17 changes: 17 additions & 0 deletions tests/TestClasses/Models/TestModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Spatie\ValidationRules\Tests\TestClasses\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\User;

class TestModel extends Model
{
protected $guarded = [];

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
17 changes: 17 additions & 0 deletions tests/TestClasses/Policies/TestModelPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Spatie\ValidationRules\Tests\TestClasses\Policies;

use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Foundation\Auth\User;
use Spatie\ValidationRules\Tests\TestClasses\Models\TestModel;

class TestModelPolicy
{
use HandlesAuthorization;

public function edit(User $user, TestModel $testModel): bool
{
return $testModel->user->id === $user->id;
}
}

0 comments on commit effb8cc

Please sign in to comment.