I want a middleware on my website for: People can edit their own posts but others posts. I tried this:
I get all posts that have the same post->user_id and user_id
$matches = Post::where('user_id', auth()->user()->id);
This gives back an array of posts that match the condition
Now what I want is to check if you are on a post that matches this condition, if the post->user_id and user_id do not match abort.
This is what I have, but you still can get on posts where the condition is NOT met.
if (!$matches){
abort(403);
}
return $next($request);
Abort when the criteria is not met and return the request when it is met
Instead of using middleware why not use the Policy, and since you will edit a post you can also use the Form Request. I suggest you to use Form Request
then edit the authorize() and add the condition there.
Okay lets say you are using Route Model Binding
//route
Route::put('/post/{post}', ['PostController','update']);
//controller
public function update(Post $post, UpdatePostRequest $request) {...}
You can directly check if the user is the owner inside the authorize(). Assuming that you define the relationship between the post and user
// app\Models\User.php
public function posts() : HasMany {...}
// app\Models\Post.php
public function user() : BelongsTo {...}
//request
class UpdatePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
//true if the Auth::user() own the post. otherwise false.
return $this->post->user()->is(Auth::user());
}
/**
* Get the validation rules that apply to the request.
*
* #return array<string, mixed>
*/
public function rules()
{
return [
// Your validation rules
];
}
It works if you will update the post but if just want to prevent the user from accessing the post they do not own. put this in your middleware.
if(! $request->post->user()->is(Auth::user())) {
abort(403);
}
return $next($request);
If you're inside a post, I guess you will get the Post ID inside your request as well. Something like http://127.0.0.1:5500/posts/1
Then you can get both Post and User ID. Use both values to determine whether the user has authorized the post or not.
Example:
Assume you have added the post ID URL Param as post_id
$match = Post::where('user_id', auth()->user()->id)->where('id', $request->route('post_id'));
if (!$match){
abort(403);
}
return $next($request);
Related
In my Laravel project I want to authorize user via a Request like this:
<?php
namespace Domain\Contents\Http\Requests\Blog;
use Domain\Contents\Models\Blog\Post;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class ReadPostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
if (request('id') === null) {
abort(403);
}
$post = Post::whereId(request('id'))->first();
return Gate::allows('view-post', $this->user(), $post);
}
// ...
}
But I think here this part of my code is little bit messy:
if (request('id') === null) {
abort(403);
}
$post = Post::whereId(request('id'))->first();
Is there any simpler solution for accessing current Post model in the Request class?
The documentation for FormRequests suggests that the authorize() method supports type hinting.
If you are using route model binding you could therefore just type hint the post:
public function authorize(Post $post)
{
return Gate::allows('view-post', $this->user(), $post);
}
Alternative solution is that you can directly access your Models that are used with Model Binding.
return Gate::allows('view-post', $this->user(), $this->post);
For ease of use you can typehint it in the comments.
/**
* #property \App\Models\Post $post
*/
I have a laravel app using Policies to assign roles and permissions, i cant seem to access the show page and im not sure what im doing wrong?
If i set return true it still shows a 403 error as well, so im unsure where im going wrong here. The index page is accessable but the show page is not?
UserPolicy
public function viewAny(User $user)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
public function view(User $user, User $model)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
UserController
public function __construct()
{
$this->authorizeResource(User::class, 'user');
}
public function index()
{
$page_title = 'Users';
$page_description = 'User Profiles';
$users = User::all();
return view('pages.users.users.index', compact('page_title', 'page_description', 'users'));
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show($id)
{
$user = User::findOrFail($id);
$user_roles = $user->getRoleNames()->toArray();
return view('pages.users.users.show', compact('user', 'user_roles'));
}
Base on Authorize Resource and Resource Controller documentation.
You should run php artisan make:policy UserPolicy --model=User. This allows the policy to navigate within the model.
When you use the authorizeResource() function you should implement your condition in the middleware like:
// For Index
Route::get('/users', [UserController::class, 'index'])->middleware('can:viewAny,user');
// For View
Route::get('/users/{user}', [UserController::class, 'view'])->middleware('can:view,user');
or you can also use one policy for both view and index on your controller.
I had an issue with authorizeResource function.
I stuck on failed auth policy error:
This action is unauthorized.
The problem was that I named controller resource/request param with different name than its model class name.
F. ex. my model class name is Acknowledge , but I named param as timelineAcknowledge
Laravel writes in its documentation that
The authorizeResource method accepts the model's class name as its first argument, and the name of the route / request parameter that will contain the model's ID as its second argument
So the second argument had to be request parameter name.
// Here request param name is timelineAcknowledge
public function show(Acknowledge $timelineAcknowledge)
{
return $timelineAcknowledge->toArray();
}
// So I used this naming here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'timelineAcknowledge');
}
Solution was to name request param to the same name as its model class name.
Fixed code example
// I changed param name to the same as its model name
public function show(Acknowledge $acknowledge)
{
return $acknowledge->toArray();
}
// Changed here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'acknowledge');
}
I looked over Laravel policy auth code and I saw that the code actually expects the name to be as the model class name, but I couldn't find it anywhere mentioned in Laravel docs.
Of course in most of the cases request param name is the same as model class name, but I had a different case.
Hope it might help for someone.
What I am trying to do is apply a policy on a control method that lists a bunch of records instead of just one record like most of the examples I have seen.
Instead of checking against the ThoughtRecords I want to check the signed in user hashedId to the user that's being queried hashedId in the controller index() method.
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
AuthServiceProvider.php
protected $policies = [
'App\ThoughtRecord' => 'App\Policies\ThoughtRecordPolicy',
];
public function boot()
{
$this->registerPolicies();
}
ThoughtRecordPolicy.php
public function view(User $signedInUser, User $client)
{
//return true;
dd('Policy working');
//return $signedInUser->id === $client->id;
}
ThoughtRecordController.php
public function index($userHashedId)
{
$client = User::where('hashed_id', $userHashedId)->first();
$this->authorize('view', ThoughtRecord::class, $client);
$records = ThoughtRecord::where('user_id', $client->id)->latest()->paginate(1);
return ThoughtRecordResource::collection($records);
}
Error
Too few arguments to function App\Policies\ThoughtRecordPolicy::view()
I have also tried:
$this->authorize('view', $client);
This action is unauthorized.
As said:
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
You need pass both the ThoughtRecord::class and the $client into an array:
$this->authorize('view', [ThoughtRecord::class, $client]);
I am currently using Laravel 5.2.
When I call the destroy method on my Post controller I get the following message:
I have taken a look at this question because it seems very similar to mine, although the answers were not able to help: Exception being thrown when trying to delete model in laravel 5.2
The answers were not able to help because I am not able to find any folder called Entrust in my project, and editing the line in config/auth only gave me a different error about not being able to find "App\Models\User".
I am not really sure where else to look for this issue and would really appreciate any help or advice you can give.
Here is my destroy method and Post controller:
Post Controller
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $table = 'posts';
/**
* Define relationship between posts and categories.
*
* #return eloquent relationship
*/
public function category()
{
return $this->belongsTo('App\Category', 'category_id');
}
/**
* Define relationship between posts and tags.
*
* #return eloquent relationship
*/
public function tags()
{
return $this->belongsToMany('App\Tag', 'post_tag', 'post_id', 'tag_id');
}
}
Destroy Method
public function destroy($id)
{
// find the post and tags
$post = Post::find($id);
$tags = $post->tags();
// update post_tag relationship
if($tags != null)
{
$post->tags()->detach($tags);
}
// delete the post
$post->delete();
// redirect with flash data to posts.index
Session::flash('success', 'The blog post was deleted successfully!');
return redirect()->route('posts.index');
}
You are getting a query builder instance from $tags = $post->tags();, instead just do $tags = $post->tags; to get the collection.
Second, pass an array of ids to the detach method like so:
// find the post and tags
$post = Post::find($id);
$ids = $post->tags->pluck('id');
// update post_tag relationship
if(count($ids))
{
$post->tags()->detach($ids);
}
// delete the post
$post->delete();
So by default I do not require a user to create a username on registration. So their username won't be set, and instead their UID will be their id rather than a username like so:
/**
* Get the Username
*/
public function getUsernameAttribute()
{
if($this->attributes['username']){
return $this->attributes['username'];
}
return $this->id;
}
So whenever they visit a URL that is has route model binding, it will get their information (the ID)
But now let's say I want to use a username for the route model biding as well in case they have updated and set a username I can do that like so:
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'username';
}
Which works for usernames. Now my problem is, it no longer works for those without usernames (ids) and instead is looking for usernames:
where username = ?
Due to the way route model binding works, and the load order, I can not do an if statement to check if their username exists, because, well, how would laravel know to get either an ID or username.
Is there a way around this without creating a pull-request? Have any of you experienced something like this?
If you scroll past the getRouteKeyName section in the docs, you'll find a section on customising the resolution logic.
https://laravel.com/docs/5.5/routing#explicit-binding
This allows you to specify the query that is run to find a model. E.g.
// RouteServiceProvider.php
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::where(function ($query) use ($value) {
return $query->where('id', $value)->orWhere('username', $value);
})
->firstOrFail();
});
}