Mr.Web
Mr.Web

Reputation: 7154

Get Lavavel (5) data without direct relationship

I have an app consisting of these tables

User model is defined with:

public function channels()
{
    return $this->hasMany('App\Channel');
}

Channel model is defined with:

public function playlists()
{
    return $this->hasMany('App\Playlist');
}

(and a $this->belongsTo('App\User'))

Playlist model is defined with:

public function tracks()
{
    return $this->hasMany('App\Track');
}

(and a $this->belongsTo('App\Channel'))

Track model is defined with:

public function playlist()
{
   return $this->belongsTo('App\Playlist');
}

Now when listing the tracks in the specific Playlist I do this:

function list($playlist) {
    $tracks = Track::with('playlist')->get();
    return response()->json($tracks);
}

This works correctly at the page http://.../tracks/2 (where 2 is the playlist id) but if the user changes 2 into 3 he can potentially see the tracks in the playlist #3 which may not be his. So How do I get the user id from the playlist to show only the tracks belonging to a playlist which the user actually has?

Upvotes: 0

Views: 36

Answers (1)

Jonathon
Jonathon

Reputation: 16283

In your given code, it looks like you're not actually loading the playlist as specified by the ID in your URL. $playlist here should be 2 with your example URL (http://.../tracks/2):

function list($playlist) {
    $tracks = Track::with('playlist')->get();
    return response()->json($tracks);
}

You're simply loading all tracks in your entire system, eager loading in their respective Playlist objects.

Assuming with that URL, you want to show the tracks for playlist 2, if and only if it belongs to the authenticated user, what you should do is find the playlist by its ID, authorise that the Playlist belongs to the logged in user and then load in its tracks using the relation:

public function list($id)
{
    // Find a playlist with the given ID, or 404 if none could be found.
    $playlist = Playlist::findOrFail($id);

    // If the playlist does not belong to the user, throw an AuthorizationException.
    if ($playlist->user_id != auth()->user()->id) {
        throw new AuthorizationException();
    }

    // Return a response, passing through the tracks belonging to the playlist.
    return response()->json($playlist->tracks);
}

A better way of handling the authorisation would be to use Laravel's built in authorisation features, most notably policies. This would allow you to separate your authorisation logic from your controller and keep all your authorisation logic in the same/similar place:

  • Create a policy, using php artisan make:policy PlaylistPolicy which would create a class App\Policies\PlaylistPolicy.
  • In that policy, simply define a method which denotes the action you're authorising against your playlists. In this example, you're authorising whether or not a user can view a given playlist, so view would be my suggestion of method name here.
  • That method would accept 2 parameters, the authenticated user, and the Playlist object that you're interested in:

    public function view(User $user, Playlist $playlist)
    {
        //
    }
    
  • Within that method all you need to do is add some logic that determines if the given user (the logged in user) has access to be able to view the given Playlist. The method should return true if the user has access or false if not.

    public function view(User $user, Playlist $playlist)
    {
        return $user->id === $playlist->user_id;
    }
    
  • Now you just need to register the policy in your App\Providers\AuthServiceProvider. You just need to map the Playlist model to the PlaylistPolicy by adding to the $policies property and you should be good to go.

    protected $policies = [
        Playlist::class => PlaylistPolicy::class
    ];
    
  • Now instead of that if statement in your controller you can simply use the authorize method, which should be present on your controller (as long as the AuthorizesRequests trait is used).

    public function list($id)
    {
        $playlist = Playlist::findOrFail($id);
    
        $this->authorize('view', $playlist);
    
        return response()->json($playlist->tracks);
    }
    

Upvotes: 2

Related Questions