Not working laravel Policy for my UsersUpdate - php

I have
(1/1) HttpException
This action is unauthorized.
I think all should work fine and I have done it right but maybe not.
My controller method:
public function update(Request $request, Users $uzytkownik)
{
$this->authorize('update', $uzytkownik);
return 1;
}
UsersPolicy that is in App\Policies\:
<?php
namespace App\Policies;
use App\Models\Users;
use Illuminate\Auth\Access\HandlesAuthorization;
class UsersPolicy
{
use HandlesAuthorization;
public function update(Users $user)
{
return true;
// return $user->login === auth()->login;
}
}
And in AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Models\Users' => 'App\Policies\UsersPolicy',
];
My Users model lays in App\Models\
When I cut $this->authorize('update', $uzytkownik); this line from controller everything works fine and I see '1', when I add it again HttpException.
What do I have wrong here? Thinking and Thinking, looking, I don't see anything bad here.

please make sure that your route is under auth middlware like this :
Route::group(['middleware' => 'auth'], function () {
// ur update route here
});
or in ur controller constructor like this :
public function __construct()
{
$this->middleware('auth');
}
and also like #Laerte said your update policy method should have another parameter of type user which is the user you want to edit, like this :
public function update(Users $userLoggedIn, Users $uzytkownik)
{
return true;
}

In your Policy, you have to add two parameters: The first one is the user logged in, and the second is the actual parameter. Try this in the Policy:
public function update(Users $userLoggedIn, $user)
{
return true;
}

Related

Laravel 'can' middleware return error 500 "This action is unauthorized"

i'm trying to create a website based on laravel framework. I'm stuck in permission control with Policy. This is my code:
+ Policies Register in App\Providers\AuthServiceProvider:
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
User::class => UserPolicy::class,
Category::class => CategoryPolicy::class
];
public function boot()
{
$this->registerPolicies();
try {
Permission::all()->each(function($permission) {
Gate::define($permission->name, function ($user) use($permission) {
return $user->hasPermission($permission->name);
});
});
} catch (\Exception $e) {
Log::notice('Unable to register gates. Either no database connection or no permissions table exists.');
}
}
}
User Policy in App\Policies\UserPolicy
class UserPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return true;
}
public function view(User $user, User $target_user)
{
return $user->id === $target_user->id;
}
}
Api Route in routes/api.php
Route::get('/users', 'Api\UserController#getUsers')->middleware('can:view-users');
Route::get('/users/{user_id}', 'Api\UserController#getUser')->middleware('can:view-users');
Route::put('/users/{user_id}', 'Api\UserController#updateUser')->middleware('can:edit-users');
User Controller in App\Http\Controllers\Api\UserController
public function getUsers(UserRequest $request) {
$users = $this->userRepository->getAll();
$this->authorize('view', $user);
return response($users, 200);
}
public function getUser(UserRequest $request, $user_id) {
$user = $this->userRepository->find($user_id);
$this->authorize('view', $user);
return response($user, 200);
}
When I try to get data by using 2 url above, it returned error 500 Internal Server Error even when user is authenticated :
{
"error": "This action is unauthorized."
}
I tried to log result and found that error come from middleware can or Illuminate\Auth\Middleware\Authorize.
Code line:
$this->gate->authorize($ability, $this->getGateArguments($request, $models)); in handle() function throw error above.
After hours searching, I could not figure out solution.
Anyone can point out my mistake?
Solved. After hours reading source code in library, I figured out problem. I used api guard for my app without passport module. Thus, variable $userResolver in Illuminate\Auth\Access\Gate class could not be set to authenticated user and got null value. To solve this problem, I created my own Authorize middleware instead of laravel middleware Authorize and use Auth::guard('api')->user() to get authenticated user.

Laravel 6.x Policy not being called after authorize (No Middleware)

I am having some problems with Laravel 6 policies. I get 403 unauthorized all the time even though it should be a non-authenticated request.
Files:
api.php
Route::prefix('v2')
->group(function () {
Route::prefix('agencies')->group(function () {
Route::post('/', 'Api\AgencyController#store');
});
}
AgencyController.php
<?php
namespace App\Http\Controllers\Api;
use App\Entities\Agency;
class AgencyController extends Controller {
public function store(AgencyRequest $request)
{
$this->authorize('create', Agency::class);
// Code that is never executed
}
}
AgencyPolicy.php
class AgencyPolicy
{
public function create(User $user)
{
\Log::info('hello?'); // This Log is never executed
return true;
}
}
AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
\App\Entities\Agency::class => \App\Policies\AgencyPolicy::class,
// Other policies
];
public function boot()
{
$this->registerPolicies();
Gate::before(function ($user) {
if ($user->hasRole(SuperAdmin::ROLE_NAME)) {
return true;
}
});
Passport::routes(null, ['prefix' => 'api/oauth']);
}
}
My code is identical to the documentation but nonetheless I keep getting 403 unauthorized, and for the life of me I cannot understand what is going on. All help will be appreciated.
As lagbox thankfully replied, the answer is in the documentation, that states:
By default, all gates and policies automatically return false if the incoming HTTP request was not initiated by an authenticated user. However, you may allow these authorization checks to pass through to your gates and policies by declaring an "optional" type-hint or supplying a null default value for the user argument definition:
Thus, my problem would be solved by using ?User in AgencyPolicy.php:
class AgencyPolicy
{
public function create(?User $user)
{
return true;
}
}
This solves the problem.

Laravel - requests validation loose errors on redirect

I have a custom request file in http/requests which handles validation.
If validation does not pass, by default it should redirect back with $errors in the view (which are generated by \Illuminate\View\Middleware\ShareErrorsFromSession::class) and I see that the errors are in the session while debuging, but after this request with code 302 finishes and 200 request is generated, errors in the session are lost.
What might cause this issue? Thanks
Code:
<?php
namespace App\Http\Requests;
<..>
class AnswerQuestionnaireRequest extends FormRequest
{
private $questionRepository;
/**
* AnswerQuestionnaireRequest constructor.
*/
public function __construct(QuestionRepository $qs)
{
parent::__construct();
$this->questionRepository = $qs;
}
public function authorize()
{
return true;
}
public function rules()
{
$rules = [];
foreach ($this->questionRepository->getAll() as $question){
$index = "question-" . $question->id;
$rules[$index] = "required|in:1,2";
}
return $rules;
}
public function messages()
{
return [
'question-1.required' => __("Incorrect value"),
'question-2.required' => __("Incorrect value"),
];
}
}
class QuestionnairesController extends Controller
{
public function __construct(QuestionnairesService $questionnairesService, QuestionRepository $questionRepository)
{
$this->questionnairesService = $questionnairesService;
$this->questionRepository = $questionRepository;
}
public function show(Request $request){
$questions = $this->questionRepository->getAll();
return view("client/questionnaire", compact("questions"));
}
public function store(AnswerQuestionnaireRequest $request){
var_dump($request);
}
EDIT:
Checking with Xdebug you can see, that the validation worked, but it's only for the request, that return code 302 and redirects back. https://gyazo.com/231c83910f6e57748e1b705ade74e383
And when the request 200 is loading, this error bag is already empty there.
In your controller try this, assuming the function is create
use Illuminate\Http\Request;
public function create(Request $request){
$this->validate($request,[
'field'=>'required|present',
'another_field'=>'required|present',
]);
Post::create($request);
}
Do note that the $this->validate(), if there is a validation error it is automatically redirected back to the previous url with the error messages sent.
Blade
You can then check for errors this way
#if($errors->has('fieldName'))
<small class="text-danger form-text">{{$errors->first('fieldName')}}</small>
#endif
Wrapping everything in routes/web.php to "Route::group(['middleware' => ['web']], function () {" fixed the issue.
I thought that everything in web.php file is already assigned to "WEB" middleware...

is it better to create separate request class for each new method in controller, or edit existing request class in laravel or any better idea?

Is it better to create separate request class for each new method in controller or edit existing request class in laravel or any better idea ?
example
class fooBarController {
public function a(fooBarARequest $r) {
}
public function b(fooBarBrequest $r) {
}
public function c(fooBarCDRequest $r) {
}
public function d(fooBarCDRequest $r) {
}
}
Using extra request classes allows you to define validation rules which your request is checked against before it reaches your controller. You can also handle authorization in the request class. An example would be:
class UpdateAccountEmail extends FormRequest
{
public function authorize()
{
return true; // authorization is handled on route/middleware level
}
public function rules()
{
return [
'new_email' => 'required|email|confirmed',
'new_email_confirmation' => 'required',
];
}
}
So, to sum it up: it does not make sense to use a custom request class for requests which do not have payload that needs to be validated. This means, for a normal GET request we most likely (of course there are exceptions) want to use the normal Request class provided by laravel. A controller like this would be quite normal:
class AccountController
{
public function show(Request $request)
{
return view('account.show', ['user' => $request->user()]);
}
public function edit()
{
return view('account.edit', ['user' => \Auth::user()]);
}
public function updateEmail(UpdateAccountEmail $request)
{
$user = $request->user();
$user->email = $request->input('new_email');
$user->save();
return redirect()->route('account.index');
}
public function logins(Request $request)
{
$logins = $request->user()->logins()
->when($request->get('filter_from'), function ($query, $from) {
$query->where('created_at', '>=', $from);
})
->when($request->get('filter_until'), function ($query, $until) {
$query->where('created_at', '<=', $until);
})
->get();
return view('account.logins', ['logins' => $logins]);
}
}
As you can see, for the GET request that is handled by logins(Request $request), we do not use a custom request class because we don't need to validate anything (well, we could validate the filter parameters, but for simplicity we don't).
The example above also shows different methods of retrieving the current user. Even for that you don't need a request at all.
This is no actual production code, just something off the top of my head...

Laravel resource policy always false

I'm trying to allow user to view their own profile in Laravel 5.4.
UserPolicy.php
public function view(User $authUser, $user)
{
return true;
}
registered policy in AuthServiceProvider.php
protected $policies = [
App\Task::class => App\Policies\TaskPolicy::class,
App\User::class => App\Policies\UserPolicy::class
];
Routes
Route::group(['middleware' => 'auth'], function() {
Route::resource('user', 'UserController');
} );
Blade template
#can ( 'view', $user )
// yes
#else
// no
#endcan
UserController.php
public function profile()
{
return $this->show(Auth::user()->id);
}
public function show($id)
{
$user = User::find($id);
return view('user.show', array( 'user'=>$user,'data'=>$this->data ) );
}
The return is always 'false'. Same for calling policy form the controller. Where do I go wrong?
Answering my own question feels weird, but I hate it when I come across questions without followups.
So after double checking It turned out that if I remove authorizeResource from the constructor:
public function __construct()
{
$this->authorizeResource(User::class);
}
and check for authorization in the controller function:
$this->authorize('view',$user);
everything works.
I must've missed this part when I added $user as a parameter in the policy function. So the user to be viewed is never passed in the authorizeResource method.
Thanks everyone for taking your time to help me.
When you add
public function __construct()
{
$this->authorizeResource(User::class);
}
to your Controller, you have to edit all your function signatures to match it to the class e.g. your show signature has to change from public function show($id)
to public function show(User $user)
After that it should work
Just a different approach here to users viewing their own profile.
First, I will create a route for that
Route::group(['middleware' => 'auth'], function() {
Route::get('profile', 'UserController#profile');
});
Then in the profile function I do
public function profile()
{
$user = Auth::user();
return view('profile', compact('user'));
}
This way, user automatically only views their own profile.
Now, if you want to allow some users to view others' profiles, then you can use Policy. Why? Because I think user should ALWAYS be able to view their own profile. But not all users should view other users profiles.
Solution:
Change the second parameter from #can( 'view', $user ) to #can( 'view', $subject ) and it will work find.
Why:
Because you're doing it the wrong way.
public function view(User $user, $subject){
return true;
}
Just look carefully the policy view method, first parameter is authenticated user or current user and second parameter is $subject, Since policies organize authorization logic around models.
Policies are classes that organize authorization logic around a
particular model or resource. For example, if your application is a
blog, you may have a Post model and a corresponding PostPolicy to
authorize user actions such as creating or updating posts.
if you want to go further deep inside it.
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/Access/Gate.php#L353
/**
* Resolve the callback for a policy check.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
return function () use ($user, $ability, $arguments) {
$instance = $this->getPolicyFor($arguments[0]);
// If we receive a non-null result from the before method, we will return it
// as the final result. This will allow developers to override the checks
// in the policy to return a result for all rules defined in the class.
if (method_exists($instance, 'before')) {
if (! is_null($result = $instance->before($user, $ability, ...$arguments))) {
return $result;
}
}
if (strpos($ability, '-') !== false) {
$ability = Str::camel($ability);
}
// If the first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument list
// because the policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
if (! is_callable([$instance, $ability])) {
return false;
}
return $instance->{$ability}($user, ...$arguments);
};
}
See the last line where it is calling the method with $user and $argument( in our case Model ) is passed.
Laravel Docs for Authorization/Policies
It's possible to escape one or more policies methods using options parameter at authorizeResource with except:
public function __construct()
{
$this->authorizeResource(User::class, 'user', ['except' => ['view']]);
}
This should be on Laravel's documentation, but it isn't. I discovered this just guessing. I think this way it is a better approach thus, by removing authorizeResource method in the construct, it would be necessary to implement the authorization method for each resource action in order to protect the controller.

Categories