In addition to allowing or restricting access to a complete operation, you may wish to only restrict the scope of the operation. For example instead of listing all users on the index page, only list the current user.
To implement a policy scope:
Create Your Scope Method
Unlike add, edit, or delete, where we are working on a single record, index works with all records in the table, so we need to create a Users Table Policy to accompany the User Policy. In /src/Policy/UsersTablePolicy.php
file we can add the scopeIndex
method to restrict the index
query to only the logged in user.
<?php
declare(strict_types=1);
namespace App\Policy;
use App\Model\Entity\UsersTable;
use Authorization\IdentityInterface;
class UsersTablePolicy
{
public function scopeIndex(IdentityInterface $user, $query) {
return $query->where(['Users.id' => $user->id]);
}
}
Apply the Scope Policy
Now in our UsersController.php
in the index
action implement the scope method.
public function index()
{
$this->Authorization->skipAuthorization();
$query = $this->Users->find();
$users = $this->paginate($this->Authorization->applyScope($query));
$this->set(compact('users'));
}
And now our index list only shows the currently logged in user. We can make the scopeIndex
more detailed if we'd like. For example, if we want Admin users to see all users, but other users to only see themselves:
public function scopeIndex($user, $query) {
if ($user->getOriginalData()->is_admin) {
return;
}
return $query->where(['Users.id' => $user->getIdentifier()]);
}
Preconditions
In addition to policy methods related directly to the standard CRUD actions (canView(), canEdit(), etc.) you can apply common precondition checks covering all operations in a policy. This is useful to quickly allow or deny all actions to a resource.
To implement a precondition include and implement the BeforePolicyInterface
in your entity policy so you can add the before()
method. Here is the code for our /src/Policy/UsersPolicy.php
...
use Authorization\Policy\BeforePolicyInterface;
use Authorization\Policy\ResultInterface;
class UserPolicy implements BeforePolicyInterface
{
...
Add the before()
Method
The interface requires the before()
method so add it to your policy class.
public function before(?IdentityInterface $user, mixed $resource, string $action): ResultInterface|bool|null
if ($user->getOriginalData()->is_admin) {
// Admin user role gets access to everything
return true;
} elseif ($user->getOriginalData()->is_disabled) {
// Disabled users get access to nothing except unauthenticated and skipped actions
return false;
}
// returning null indicates the before hook didn't make a decision so the
// authorization methods below will be invoked
return;
}
And now, since our precondition authorizes Admins, we don't need the canAdd()
and canDelete()
methods and can clean up canEdit()
.
public function canEdit(IdentityInterface $user, User $resource)
{
// Users can only edit themselves
return $user->id === $resource->id;
}
Documents and Phone Numbers
Now take what you've learned and apply Preconditions allowing Admin's full access to Documents and Phone Numbers.