Unable to get Policies working in Laravel 5.3 - php

I've been following the Laravel Authorization docs trying to build "is the user allowed to do this" functionality by using Policies, but I can't get it to work. I keep getting This action is unauthorized and I've tried with route middleware too.
PagePolicy.php:
namespace App\Policies;
use App\Models\User;
use App\Models\Page;
use Illuminate\Auth\Access\HandlesAuthorization;
class PagePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the page.
*
* #param App\Models\User $user
* #param App\Models\Page $page
* #return mixed
*/
public function view(User $user, Page $page)
{
return $user->id === $page->user_id;
}
/**
* Determine whether the user can create pages.
*
* #param App\Models\User $user
* #return mixed
*/
public function create(User $user)
{
}
/**
* Determine whether the user can update the page.
*
* #param App\Models\User $user
* #param App\Models\Page $page
* #return mixed
*/
public function update(User $user, Page $page)
{
//
}
/**
* Determine whether the user can delete the page.
*
* #param App\Models\User $user
* #param App\Models\Page $page
* #return mixed
*/
public function delete(User $user, Page $page)
{
//
}
}
PageController.php:
namespace App\Http\Controllers;
use Auth;
use Carbon\Carbon;
use App\Models\Page;
use App\Http\Requests\PageRequest;
class PageController extends ApiController
{
public function createNewPage(PageRequest $request)
{
$this->authorize('create', Page::class);
$request->merge([
'user_id' => Auth::id(),
'published_at' => Carbon::now(),
]);
if (Page::create($request->all())) {
return response()->json('success', 201);
}
return response()->json('error', 500);
}
}
AuthServiceProvidor.php:
namespace App\Providers;
use App\Models\Page;
use App\Policies\PagePolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Page::class => PagePolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}

I managed to figure it out. I wasn't using Route Model Binding. So I added authorize() after the page call and used the $page variable instead of Page::class.
public function update(PageUpdateRequest $request, $pageSlug)
{
$page = Page::where(['user_id' => Auth::id(), 'slug' => $pageSlug])->first();
$this->authorize('update', $page);
$page->update($request->all());
return fractal()->item($page, new PageTransformer())->toArray();
}

It's not totally clear to me which action you're attempting to authorize since you've provided the call to create in the controller but only provided a policy check in place for viewing a page. Having said that, I would be sure to var_dump/dd the values you're attempting to do a type comparison of to verify they're of the same type. If anything's been explicitly cast, it may cause issues with certain database drivers that return integers as strings.

I think the problem is not in your policies, rather in your PageRequest class. Make sure the authorize() method in your App\Http\Requests\PageRequest class returns true .
class PageRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true; // you can also check the authorization using PagePolicy here
}
}

Current code:
protected $policies = [
Task::class => TaskPolicy::class,
];
Solution code:
protected $policies = [
'App\Task' => 'App\Policies\TaskPolicy',
];
I experienced the same problem, while following the Intermediate Task List Tutorial on the Laravel website.
The solution is actually present in the Github code for this tutorial.

Related

Laravel multiple policies always authenticated?

I'm using Laravel 9 with the Laravel Spatie Permissions package. I have users and roles in my system. Users have roles, and depending on their permissions on their role they either can or can't create new users / new roles etc.
I've set up my UserPolicy and RolePolicy, and am passing my User model to each since it's the user that needs to be checked against what permissions they have, then in the controller of my choice, such as my RoleController I run:
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$this->authorize('viewAny', User::class);
$roles = Role::with('permissions')->get();
if (!$roles || count($roles) <= 0) {
return response()->json([
'message' => 'No roles found'
], 404);
}
return response()->json([
'roles' => $roles
], 200);
}
Strangely, if I edit my RolePolicy's viewAny permission and return false, I'm still able to see the data? I shouldn't be. what am I missing?
Here's my RolePolicy
<?php
namespace App\Policies\UserManagement;
use Spatie\Permission\Models\Role;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class RolePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function viewAny(User $user)
{
// TODO: if I return false I still have access?
if ($user->can('role_index')) {
return true;
}
}
/**
* Determine whether the user can view the model.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function view(User $user)
{
if ($user->can('role_show')) {
return true;
}
}
/**
* Determine whether the user can create models.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user)
{
if ($user->can('role_store')) {
return true;
}
}
/**
* Determine whether the user can update the model.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function update(User $user)
{
if ($user->can('role_update')) {
return true;
}
}
/**
* Determine whether the user can delete the model.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function delete(User $user)
{
if ($user->can('role_destroy')) {
return true;
}
}
}
And my AuthServiceProvider:
<?php
namespace App\Providers;
use App\Models\User;
use Spatie\Permission\Models\Role;
use App\Policies\UserManagement\UserPolicy;
use App\Policies\UserManagement\RolePolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* #var array<class-string, class-string>
*/
protected $policies = [
User::class => UserPolicy::class,
User::class => RolePolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
ResetPassword::createUrlUsing(function ($user, string $token) {
$frontendUrl = trim(rtrim(config('lespro.frontend_url'), '/'));
return $frontendUrl . '/account/reset/?email=' . $user->email . '&token=' . $token;
});
// Implicitly grant "super_admin" role all permissions
// This works in the app by using gate-related functions like auth()->user->can() and #can()
Gate::before(function ($user, $ability) {
return $user->hasRole('super_admin') ? true : null;
});
}
}

Laravel middleware limits access to unwanted functions

I am doing a project in Laravel.
I have a database with posts and users. These posts can be modified and edited by the user who created it and the admin.
To do this I created a new field for users, there is an admin and two editor.
After limiting the access with the middleware, only the admin and editor can access the posts.
$this->middleware('auth',['only' => ['create', 'store', 'edit', 'update', 'destroy']]);
$this->middleware(['auth', 'roles:admin'],['only' => ['edit', 'update', 'destroy']]);
The problem is that now only the admin can access the edit and delete post functions. Publishers are redirected to the home page.
Is there a way to put an if that bypasses the middleware redirect or something similar?
I'd use a policy to simplify things, and remove the middleware.
Create Policy
php artisan make:policy PostPolicy --model=Post
Resulting in this file
<?php
namespace App\Policies;
use App\Post;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* #param \App\User $user
* #return mixed
*/
public function viewAny(User $user)
{
//
}
/**
* Determine whether the user can view the model.
*
* #param \App\User $user
* #param \App\Post $post
* #return mixed
*/
public function view(User $user, Post $post)
{
//
}
/**
* Determine whether the user can create models.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
//
}
/**
* Determine whether the user can update the model.
*
* #param \App\User $user
* #param \App\Post $post
* #return mixed
*/
public function update(User $user, Post $post)
{
//
}
/**
* Determine whether the user can delete the model.
*
* #param \App\User $user
* #param \App\Post $post
* #return mixed
*/
public function delete(User $user, Post $post)
{
//
}
/**
* Determine whether the user can restore the model.
*
* #param \App\User $user
* #param \App\Post $post
* #return mixed
*/
public function restore(User $user, Post $post)
{
//
}
/**
* Determine whether the user can permanently delete the model.
*
* #param \App\User $user
* #param \App\Post $post
* #return mixed
*/
public function forceDelete(User $user, Post $post)
{
//
}
}
Modify the rules for each action, so for example we need to specify that only the admin or the post owner can update a post, so
public function update(User $user, Post $post)
{
if ($user->role === 'admin') {
return true;
}
return $post->user_id === $user->id;
}
And then register the policy
https://laravel.com/docs/8.x/authorization#registering-policies
<?php
namespace App\Providers;
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
And finally authorize your controller, add this line to the constructor
public function __construct()
{
$this->authorizeResource(Post::class, 'post');
}
Note that the 2nd parameter in the function call is the name of the route parameter, post is going to be your route parameter if you created the a resourceful controller
If you are not using a resourceful controller, or want to authorize actions manually, then you can use without adding the above line in the constructor
https://laravel.com/docs/8.x/authorization#via-controller-helpers
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// The current user can update the blog post...
}
the first parameter is the name of the policy method, and the 2nd paramter is the post object

Instance have been given, still detect other instance in controllers

Im trying to get client IP through controller named LoginController but the error still there.
Argument 1 passed to App\Http\Controllers\Auth\LoginController::authenticated() must be an instance of App\Http\Controllers\Auth\Request, instance of Illuminate\Http\Request given
I've follow this SO question but still get the same error.
LoginController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Auth\Request;
class LoginController extends Controller
{
/**
* The user has been authenticated.
*
* #param App\Http\Controllers\Auth\Request $request
* #param mixed $user
*
* #return mixed
*/
protected function authenticated(Request $request, $user)
{
$user->update([
'last_login_at' => Carbon::now()->toDateTimeString(),
'last_login_ip' => $request->getClientIp()
]);
if($user->isAdmin === 1) {
return redirect()->intended('admin');
}
}
}
EDITED
So, I just found out about AuthenticatesUsers.php which is a trait(?) and found this code. Should I edit this code or not?
/**
* The user has been authenticated.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function authenticated(Request $request, $user)
{
//
}
Change your use statement:
use Illuminate\Http\Request;
// Instead of
use App\Http\Controllers\Auth\Request;
You're overriding this method from the AuthenticatesUsers trait, which receives a Illuminate\Http\Request, not a App\Http\Controllers\Auth\Request

Adding Middleware for a controller in laravel

With the laravel 5.3 and above the concept of filters is gone and middleware is used instead. I have migrated my project with laravel 4 to 5.4.
I want to modify the DeviceLoginController that is when I am not logged in it must refresh to the login page. Other details can be seen in the controller page.
Problem: The controller page is useless as even when I am not logged in anyone can access this page and and anyone can fill anything. I have been trying to resolve this issue from 2 days still I am no where.
DeviceLoginController page looks like this:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\BaseController;
use Auth;
use Format;
use Input;
use DB;
use Session;
use Validator;
use Hash;
use Redirect;
use User;
use App\Models\License;
use App\Models\LicenseCount;
use App\Models\Manufacturer;
use App\Models\DeviceModel as Model;
use App\Models\Device;
use App\Models\Application;
class DeviceLoginController extends BaseController {
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
public function attempt()
{
$username = Format::ltr(Input::get("username"));
$device_key = Input::get("device_key");
$imei = Format::ltr(Input::get('imei'));
$model = Format::utr(Input::get('model'));
$manufacturer = Format::utr(Input::get('manufacturer'));
$app_code = Format::ltr(Input::get('app_code'));
$user = User::where('username', $username)->first();
if(!Hash::check($device_key, $user->device_key)) {
Event::fire('auth.login.fail', array($username, Request::getClientIp(), time()));
die("1");
}
Auth::loginUsingId($user->id);
// check if device is already registered under given user for given app
$license = License::where('device_imei', $imei)->where('app_code', $app_code)->where('user_username', $username);
// if device isn't registered, first check if device is registered by different user. If not, check if licenses are available or not with the user to register new device
if(!$license->count()) {
// checking if licenses are available or not
$license_count = LicenseCount::where('user_username', $username)->where('app_code', $app_code)->first();
// if licenses are left, register the device
if((int) $license_count['left']) {
$manufacturer = Manufacturer::firstOrCreate(array('name' => $manufacturer));
$model = Model::firstOrCreate(array('name' => $model, 'manufacturer_code' => $manufacturer->code));
$device = Device::where('imei', $imei)->first();
if(!$device) {
$device = Device::firstOrCreate(array('imei' => $imei, 'model_code' => $model->code));
}
License::create(array('device_imei' => $imei, 'app_code' => $app_code, "user_username" => $username, "expiry_date" => date("Y-m-d H:i:s", strtotime("+1 year"))));
$license_count->left = Format::itr($license_count->left) - 1;
$license_count->save();
} else {
// Prints 3, if the device is not registered and user has no more licenses left for the given app
die("3");
}
// Prints 2, if the device was not previously registered and it is now registered under given user for given app
Session::put('login_response', '2');
} else {
// Prints 0, if device is already registered under given user for given app
Session::put('login_response', '0');
}
}
}
My authenticate.php file looks like this
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate
{
/**
* The authentication factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string[] ...$guards
* #return mixed
*
* #throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, $guards=null)
{
if($this->auth ->guest())
{
if($request->ajax())
{
return response('unauthorized',401);
}
else
{
return redirect()->guest('login');
}
}
//$this->authenticate($guards);
return $next($request);
}
/**
* Determine if the user is logged in to any of the given guards.
*
* #param array $guards
* #return void
*
* #throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
}
I am new to Laravel please forgive me if I have asked some silly question. I am clueless what to do at this point. Please help and let me know if I need to add some other file.
It's great you have done the migration to Laravel 5.4. However, I suggest you go through the documentation first or watch the Laravel 5.4 from Scratch series.
For your question, you need the put the route that calls the controller function under the 'auth' middleware. Laravel provides this middleware out of the box. You can change the route to where the user will be redirected if he is not logged and calls the route.
Please go through the documentation for this.
Suppose your route is 'admin/profile' and you have defined this in the web.php routes file, you can add a middleware to it as shown (picked this example from the DOC.)
Route::get('admin/profile', function () {
//
})->middleware('auth');
To place multiple routes under the same middleware, you can use Route groups.

Laravel index policy

I using Laravel 5.4 and I am trying to write a policy for my index view. I am trying to use a Method Without a Model, I am receiving the following error:
HttpException in Handler.php line 133:
This action is unauthorized.
Here is my Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\County;
use Session;
use App\Http\Controllers\Controller;
class CountyController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$counties = County::orderBy('id', 'desc')->paginate(5);
$this->authorize('index');
return view('county.index', array(
'counties' => $counties
));
}
Here is my AuthServicePovider:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use App\Role;
use App\County;
use App\Policies\CountyPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
County::class => CountyPolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('is-Admin', function ($user) {
if($user->roles()->where('name','Admin')->first()){
return true;
}
return false;
});
}
}
Here is my Policy:
<?php
namespace App\Policies;
use App\User;
use App\Role;
use App\County;
use Illuminate\Auth\Access\HandlesAuthorization;
class CountyPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the county.
*
* #param \App\User $user
* #param \App\County $county
* #return mixed
*/
public function index(User $user)
{
$userRoles = $user->getRoleNames();
$acceptedRoles = ['Sudo','Admin'];
$testArr = array_intersect($acceptedRoles, $userRoles);
dd($testArr);
if(!empty($testArr)){
return true;
}
return false;
//
}
/**
* Determine whether the user can view the county.
*
* #param \App\User $user
* #param \App\County $county
* #return mixed
*/
public function view(User $user, County $county)
{
$userRoles = $user->getRoleNames();
$acceptedRoles = ['Sudo','Admin','Client'];
$testArr = array_intersect($acceptedRoles, $userRoles);
if(!empty($testArr)){
return true;
}
return false;
//
}
/**
* Determine whether the user can create counties.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
//
}
/**
* Determine whether the user can update the county.
*
* #param \App\User $user
* #param \App\County $county
* #return mixed
*/
public function update(User $user, County $county)
{
//
}
/**
* Determine whether the user can delete the county.
*
* #param \App\User $user
* #param \App\County $county
* #return mixed
*/
public function delete(User $user, County $county)
{
//
}
}
I never get to dd($testArr) in the index policy. Also the view policy is working perfectly.
How do I write a policy for my index view?
keeping everything the same but changing:
$this->authorize('index');
to
$this->authorize('index', County::class);
fixed the problem. Apparently the model class needs to be passed on actions that don't require a model. This is only described under the middleware section of Laravel's docs, not the controller helpers... A little confusing.

Categories