Laravel policies strangely not working - laravel-5

The point is simple: I have a UserPolicy method that checks if a user wants to edit his/her own profile. So I did like this:
public function update(User $user, User $model)
{
return $user->id === $model->id;
}
And this is called in a UserController as it follows:
public function edit(User $user)
{
$this->authorize('update', $user);
return view('users.edit')->with('user', $user);
}
Everything is the same in a PostController and a PostPolicy, meant to check if a user can edit his/her own post and it works. The only difference is in their signature, since one has two users (the first one is the currently authenticated user injected by Laravel and the other is the instance I want to check it with) and the other has the above seen automatically injected authenticated user and a post instance. Anyway, it throws:
Symfony \ Component \ HttpKernel \ Exception \ AccessDeniedHttpException
This action is unauthorized.
I tried to dd($model) but I got the same exception.
Why? Thanks in advance!
EDIT
In my AuthServiceProvider is all set up, too:
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
Post::class => PostPolicy::class,
User::class => UserPolicy::class,
];
And so is my routes.php:
// Authentication Routes...
$this->post('login', 'Auth\LoginController#login')->name('login');
$this->post('logout', 'Auth\LoginController#logout')->name('logout');
// Registration Routes...
$this->post('register', 'Auth\RegisterController#register')->name('register');
// Password Reset Routes...
$this->get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController#sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController#reset');
Route::get('/', 'HomeController#index')->name('home');
Route::resource('posts', 'PostController');
Route::resource('users', 'UserController')->except('index', 'create', 'store');
Everything above is called right here:
#if ($user->id == Auth::id())
<a class="btn btn-link float-right p-0"
href="{{ route('users.edit', Auth::id()) }}">
<i class="fas fa-cog"></i>
Edit profile
</a>
<br><br><br>
#endif

I'm giving an answer myself: I tried to write the model and policy's full paths instead of registering the policies by the classes' names and it works (I don't know why, of course).
I did like this:
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
'App\User' => 'App\Policies\UserPolicy',
'App\Post' => 'App\Policies\PostPolicy',
];
Anyway, thanks everyone for trying to help me. Hope it will help someone else one day!

I just solved the same issue after fighting a whole day. Using full paths for register did not work for me. I fixed it by modifying my routes. I post my solution here hoping it may help someone someday.
If your routes are not protected by the authentication middleware, an AccessDeniedException will be thrown before applying your policies. The reason is that if your request comes in directly, you will never be treated as a logged-in user, so that you will be kicked off when trying to call $this->authorize('update') within the controller.
Route::middleware("auth:sanctum")->group(function () {
Route::post('/member/{id}', [MembersController::class, 'update']);
// ... and other path.
});

Related

How to pass more than one gate in middleware? (Laravel)

I am creating a Learning Management System for my university final year project (only recently introduced to laravel). I have set up three different roles (admin, instructor and student). I have created two views which only the admin&instructor can access, 'user management' and 'course management'. Within each admin&instructor can create users/courses and delete as required.. a student cannot view these or has access to so that is working as desired. To do so I have created a gate 'manage-user' and then passed this into the middleware.
I have now created a calendar, which I would like all user roles to view.. again i created a gate for this.. due to my current middleware i am getting 'unauthorised access' when a student attempts to view the calendar.. is it possible to pass another gate within the middleware? I tried to do so with no success.. After many attempts of trial and error I have resulted to asking a question on here hoping i can figure this out...
i will paste my code below.. any help is appreciated.
AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
//User Management
Gate::define('manage-users', function($user){
return $user->hasAnyRoles(['admin', 'instructor']);
});
//Calendar
Gate::define('manage-calendar', function($event){
return $event->hasAnyRoles(['admin', 'instructor', 'student']);
});
web.php
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController#index')->name('home');
Route::namespace('Admin')->prefix('admin')->name('admin.')->middleware('can:manage-users')->group(function(){
//Users
Route::resource('/users', 'UsersController', ['except' => ['show']]);
//Courses
Route::resource('/courses', 'CoursesController', ['except' => ['show']]);
Route::get('events', 'EventsController#index')->name('events.index');
Route::post('/addEvents', 'EventsController#addEvent')->name('events.add');
});
I understand that the issue lays within the gate manage-users that I have defined.. I am not sure what way to go about it protect my other routes from students &instructors...
Thanks in advance :)
The manage-users Gate will not allow a user with student role to go through the middleware, even if the manage-calendar Gate does.
I suggest you regroup the routes to apply the middleware that corresponds to each route:
Route::namespace('Admin')->prefix('admin')->name('admin.')->group(function(){
Route::middleware('can:manage-users')->group(function(){
Route::resource('/users', 'UsersController', ['except' => ['show']]);
Route::resource('/courses', 'CoursesController', ['except' => ['show']]);
});
Route::middleware('can:manage-calendar')->group(function(){
Route::get('events', 'EventsController#index')->name('events.index');
Route::post('/addEvents', 'EventsController#addEvent')->name('events.add');
});
});

Form submit intercepted by auth middleware in laravel 5

I've been working on a laravel 5.7 blog project. I want to comment an article.
I need to achieve this:
Before logging in, I can type anything in comment textarea
I submit comment (of course it would be intercepted by auth middleware)
then I'm redirected to login page
After logging in, I hope my app could submit previous form data (or comment) automatically instead of typing the same comment again
I think that's a very common business logic in many websites nowadays, but how am I supposed to achieve this?
My comments controller here:
public function __construct()
{
$this->middleware('auth');
}
public function store(Post $post) {
$comment = Comment::create([
'body' => session('comment')?:request('body'),
'post_id' => $post->id,
'user_id' => auth()->user()->id
//before logging in, you don't have an user_id yet.
]);
return back()->with('success', 'Add comment succeeded');
}
web.php Route here:
Route::post('/posts/{post}/comments', 'CommentsController#store')->name('addComment');
Basically auth middleware intercepted my form data submit, I want to go across the auth middleware with my form data. Not lost them after logging in.
Here is the solution.A little tricky.Save comment to the session first before go to auth middleware.After logging in, GET that route to create comment.
Route:
Route::get('/posts/{post}/comments', 'CommentsController#store')->name('addComment');
Route::post('/posts/{post}/comments', 'CommentsController#commentSave');
Comments controller:
public function __construct()
{
$this->middleware('auth', ['except' => ['commentSave']]);
}
public function commentSave(Request $request){
$url = \URL::previous();
session(['comment' => $request->input('body')]);
return redirect("$url/comments");
}
public function store(Post $post){
if(session('comment')){
$comment = Comment::create([
'body' => session('comment'),
'post_id' => $post->id,
'user_id' => auth()->user()->id
]);
session(['comment' => null]);
return redirect("posts/$post->id")->with('success', 'Add comment succeeded');
}
return redirect("posts/$post->id");
}
I think the solution to your problem is here:
https://laravel.com/docs/5.7/session#storing-data

flash message in auth controller

I'm looking for better solution to show flash message after login/logout/register. These methods are stored in AuthController through trait AuthenticatesAndRegistersUsers. My second condition is not to edit AuthenticatesAndRegistersUsers.
My actually hack is below, but i'm not happy for that.
Have you got better idea?
app/http/controllers/auth/authcontroller.php
public function postLoginwithFlash(Request $request)
{
return $this->postLogin($request)->with('flash_message','You are logged');
}
and routes.php
Route::post('login', ['as' => 'login', 'uses' => 'Auth\AuthController#postLoginWithFlash']);
and views ofc
#if (Session::has('flash_message'))
{{ Session::get('flash_message') }}
#endif
There is no 'native' way to do it. Either way you will have to change/edit the route.
Either you implement everything in the routes.php, or do it the way you already proposed – create a new method in AuthController. Essentially, it's the same thing.
However, I would recommend you to do proper manual check instead of returning postLogin(), eg.:
if (Auth::attempt(['email' => $request->input('email'), 'password' => $request->input('password')])) {
// Authentication passed...
return redirect()->route('dashboard');
} else {
return redirect()->refresh()->with('error', 'Those are not correct credentials!');
}
This way, you can add different flash messages to success and error cases while your proposed code will show the same message irrespective of result.
You can edit the language file ./resources/lang/en/auth.php, then change this line
'failed' => 'Your custom login error message',

laravel soft delete using a form

Hi I am trying to soft delete and restore a user using a form, I am using a couple of packages for user auth and roles which are Zizaco Confide and Zizaco Entrust. I've added the following to the user.php model
use SoftDeletingTrait;
use ConfideUser;
use HasRole;
protected $softDelete = true;
and I've run a test as so to test this works:
Route::get('/deleteme', function(){
User::find(2)->delete();
return 'done';
});
and this updated the timestamp field, however I want to put this into my controller to neaten things up and give it a form. So I've done this in the table of users:
#if(empty($user->deleted_at))
{{Form::open(['method'=>'PATCH','action'=>
['UsersController#softDeleteUser',$user->id]])}}
<button type="submit">Suspend</button>
{{Form::close()}}
#else
{{Form::open(['method'=>'delete','action'=>
['UsersController#restoreUser',$user->id]])}}
<button type="submit">Re-activate</button>
{{Form::close()}}
#endif
and in my Controller:
public function softDeleteUser($id){
$user = User::find($id);
$user->delete();
// redirect
return Redirect::to('/admin');
}
public function restoreUser($id) {
User::find($id)->restore();
$user->save();
Redirect::to("/admin");
}
In my routes:
Route::post('/admin/user/{resource}/delete',
array('as' => 'admin.user.delete', 'uses'
=>'UsersController#softDeleteUser'));
Route::post('/admin/user/{resource}/restore',
array('as' => 'admin.user.restore',
'uses' =>'UsersController#restoreUser'));
However I get this error:
Symfony \ Component \ HttpKernel \ Exception \ MethodNotAllowedHttpException
Any ideas what I'm doing wrong??
Well you've set your two forms to use the PATCH and DELETE method but your routes are set to POST (Route::post).
You can either change the routes:
Route::patch('/admin/user/{resource}/delete',
array('as' => 'admin.user.delete', 'uses'
=>'UsersController#softDeleteUser'));
Route::delete('/admin/user/{resource}/restore',
array('as' => 'admin.user.restore',
'uses' =>'UsersController#restoreUser'));
Or remove the method in your forms (it will default to POST)
{{Form::open(['action'=> ['UsersController#softDeleteUser',$user->id]])}}
<button type="submit">Suspend</button>
{{Form::close()}}
And
{{Form::open(['action'=> ['UsersController#restoreUser',$user->id]])}}
<button type="submit">Re-activate</button>
{{Form::close()}}

Laravel 4 route

I've got a problem with using URL::route. There is a public function in my controller called AuthController called delete_character, here's how it looks:
public function delete_character()
{
$player->delete();
return View::make('index')->with('danger', 'You have successfully deleted your character!');
}
Also, I've created a named route:
Route::post('delete_character', array(
'as' => 'delete_character',
'uses' => 'AuthController#delete_character'
));
All I want to do is to execute the $player->delete. I don't want it to be a site, just when I click a button it's gonna delete the player.
I've also done the button:
<td><a class="btn btn-mini btn-danger" href="{{ URL::route('delete_character') }}"><i class="icon-trash icon-white"></i> Delete</a></td>
But I constantly get MethodNotAllowedHttpException. Any hints?
In my example, I am using GET request method (POST is used when form is being submited, for instance) to capture this action.
I pass ID of client I wish to delete in the reqeust URL, which results into URL in this form: http://localhost:8888/k/public/admin/client/delete/1 (You should post it from form, according to your example/request).
Not posting whole solution for you to force you to learn! My answer is not 100% identical to your situation, but will help, for sure.
// routes.php
Route::group(['prefix' => 'admin'], function(){
Route::get('client/delete/{id}', 'Admin\\ClientController#delete');
});
// ClientController.php
<?php
namespace Admin;
use Client;
class ClientController extends BaseController
{
...
public function delete($clientId)
{
$client = Client::findOrFail($clientId);
// $client->delete();
// return Redirect::back();
}
...
}
// view file, here you generate link to 'delete' action
delete

Resources