Reputation: 7154
I have an app consisting of these tables
user_id
foreign key to user)user_id
foreign key to user and foreign channel_id
to channels)playlist_id
foreign key to playlists BUT NOT a user_id
foreign key)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
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:
php artisan make:policy PlaylistPolicy
which would create a class App\Policies\PlaylistPolicy
.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 use
d).
public function list($id)
{
$playlist = Playlist::findOrFail($id);
$this->authorize('view', $playlist);
return response()->json($playlist->tracks);
}
Upvotes: 2