R
R
Ruslan2020-04-21 12:30:16
Laravel
Ruslan, 2020-04-21 12:30:16

Laravel. How to properly authorize actions in the service?

There is the essence of projects. The project has a property with a list of users who work with this project.
Accordingly, there are such operations (permissions):
1. projects.view - viewing the list of projects in which you participate.
2. projects.users.view - viewing the list of project participants
3. projects.users.edit - editing the list of project participants

It turns out that users who have permission projects.view see a list of projects in which they participate.
Some of the users who have permission projects.users.view can also see all the members of this project.

Here is the route:

$options = ['only' => ['index', 'store', 'destroy']];
Route::resource('/projects/{projectId}/users', 'ProjectUsersController', $options)->names('projects.users');

We can view the list of users, add a user to the project and remove from the project.

This is what the controller looks like now
class ProjectUsersController extends Controller
{
    public function index(ProfileRepository $profileRepository, $projectId)
    {
        $this->authorize('projects.users.view');

        $project = Project::find($projectId);

        $this->authorize('access', $project);

        $userIds = $project->users()->select('id')->pluck('id')->toArray();
        $users = $profileRepository->getByIds($userIds);

        return $users;
    }
}


Just in case, I'll throw off the "policy" for the access operation
class ProjectPolicy
{
    use HandlesAuthorization;

    public function access(User $user, Project $project)
    {
        if ($user->can('projects.view') == false) {
            return false;
        }

        if ($user->id == $project->created_by) {
            return true;
        }

        if ($project->users()->pluck('id')->contains($user->id)) {
            return true;
        }

        return false;
    }
}


Here's the actual question . I want to move "obtaining a list of project users" into a separate service. And call this service in the controller, for example ProjectUsersService::getUsers($projectId). Operation projects.users.view I can check directly in the controller. And I can check the access operation only after receiving the project object. I just would not like to receive a project outside the service:
$this->authorize('projects.users.view');
$project = Project::find($projectId);
$this->authorize('access', $project);
$projectUsersService->getUsers($project);

// далее еще будут добавление и удаление участников
$this->authorize('projects.users.edit');
$project = Project::find($projectId);
$this->authorize('access', $project);
$projectUsersService->addUser($project, $userId);


And authorization within the service is not always needed. It turns out that you need to add a flag to the getUsers/addUser/removeUser method, indicating whether to check or not. Or is it possible somehow else?

UPD
Here is the code of the service, which is obtained if the check of the access operation is added to the service
class ProjectUsersService
{
    public function getUsers(ProfileRepository $profileRepository, int $projectId)
    {
        $project = Project::find($projectId);
        
        if (\Auth::user()->can('access', $project) == false) {
            throw new \Exception();
        }
        
        $userIds = $project->users()->select('id')->pluck('id')->toArray();
        $users = $profileRepository->getByIds($userIds);

        return $users;
    }
    
    public function addUserInProject(int $projectId, int $userId)
    {
        $project = Project::find($projectId);
        
        if (\Auth::user()->can('access', $project) == false) {
            throw new \Exception();
        }
        
        $project->users()->attach($userId);
    }

    public function deleteUserFromProject(int $projectId, int $userId)
    {
        $project = Project::find($projectId);
        
        if (\Auth::user()->can('access', $project) == false) {
            throw new \Exception();
        }
        
        $project->users()->detach($userId);
    }
}

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question