I try to use Laravel's access policies, however, I receive over and over and over and over again the same error and I do not see what I need to import and / or use in functions or models.
First I show you my AuthServiceProvider
use Illuminate\Support\Facades\Gate;
use App\User;
use App\Policies\UserPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as
ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
User::class => UserPolicy::class,
];
public function boot()
{
$this->registerPolicies();
//
}
}
Now my UserPolicy
namespace App\Policies;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function __construct()
{
//
}
public function edit(User $authUser, User $user)
{
return $authUser === $user;
}
}
And finally the edit function of my UsersControllers
use App\User;
use Illuminate\Http\Request;
use App\Http\Requests\UpdateUserRequest;
public function edit($id)
{
$user = User::findOrFail($id);
$this->authorize($user);
return view('users.edit', compact('user'));
}
With my UpdateUserRequest with authorize
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required|unique:users,email,'.$this->route('usuario')
];
}
}
When I try to access it always tells me that I do not have authorization, even if I modify the edit function of UserPolicy.
Your authorization call is wrong. The first argument of the authorization method needs to be the action you are trying to authorize. So the correct code for authorization check would be:
$this->authorize('edit', $user);
You are using identity operator === for comparing the user models. Like return $authUser === $user;.
According to the php manual:
When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.
However, the $authUser instance and $user instance are different instances of same model. Use the comparison operator ==.
When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values (values are compared with ==), and are instances of the same class.
Now your user policy method will be:
public function edit(User $authUser, User $user) {
return $authUser == $user;
}
Related
I have the following policy called UserPolicy.
I want only admin users to access/edit the users data, even though I have set the return value to true(for testing) I still get 403 response no matter what.
namespace App\Policies;
use App\Models\Auth\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
// return $user->admin();
return true;
}
}
I have registered the policy as follows
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\Auth\User;
use App\Policies\UserPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
User::class => UserPolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
The following code is how I use it in the controller
if (Gate::denies('viewAny')) {
return response('Not Authorized!', 403);
}
You should use authorize() method in your controller and pass User class as the second parameter. It will point the request to the targeted policy.
authorize() method is by default provided by Illuminate\Foundation\Auth\Access\AuthorizesRequests trait in your base controller.
Your controller could be like below:
try {
$this->authorize('viewAny', User::class);
} catch (AuthorizationException $e) {
return response('Not Authorized!', 403);
}
I have the following code in my model:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new CompanyScope);
}
Here is my CompanyScope:
class CompanyScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
\Log::info('apply');
if (Auth::hasUser()) {
\Log::info('auth');
$builder->where($model->getTable() . '.company_id', company()->id);
}
}
}
In my log, I only see apply, I do not see auth, nor is the scope being applied.
Why?
I just tested what you have here except I've used \Illuminate\Support\Facades\Log rather than \Log and it seems it works fine as long as you are actually testing it with the authenticated user:
app/User.php
<?php
namespace App;
use App\Scopes\CompanyScope;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new CompanyScope);
}
}
app/Scopes/CompanyScope.php
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\Eloquent\Scope;
class CompanyScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
Log::info('apply');
if (Auth::hasUser()) {
Log::info('auth');
$builder->where($model->getTable() . '.company_id', company()->id);
}
}
}
In my controller I just fetch the user with id 1:
/**
* Get landing view.
*
* #return \Illuminate\Http\JsonResponse
*/
public function index(): JsonResponse
{
$user = User::find(1);
return new JsonResponse([
'user' => $user,
]);
}
and the corresponding test:
/**
* #test
*/
public function global_scope()
{
$this->actingAs($user = factory(User::class)->create(['id' => 1]));
$this->assertTrue($user->exists);
$this->assertAuthenticatedAs($user);
$response = $this->get(route('home'));
$response->assertExactJson([
'user' => $user->toArray()
]);
}
After running the test, my log file contains:
[2020-02-16 14:18:59] testing.INFO: apply
[2020-02-16 14:18:59] testing.INFO: auth
If you try the same without logging the user in, then only apply will be logged.
Unable to find answers anywhere
I am using Laravel 5.5 policy to restrict a user from listing properties that are not registered by the user (authenticated via API).
I have two classes User and HostProperty. Additionally, I have registered a policy for the user to access their hosted property list by ID.
Here are my models.
The Main Problem is not able to call on controller method - which throws above error:
$authUser = auth('api')->user();
if ($authUser->can('access', $property)) {
return response()->json(['success' => 'success']);
} else {
return response()->json(['error' => 'error']);
}
User.php
namespace App;
use Illuminate\Notifications\Notifiable;
use Cartalyst\Sentinel\Users\EloquentUser;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Passport\HasApiTokens;
use Illuminate\Auth\Authenticatable as AuthenticableTrait;
use Illuminate\Contracts\Auth\Authenticatable;
class User extends EloquentUser implements Authenticatable
{
use HasApiTokens, Notifiable;
use SoftDeletes;
use AuthenticableTrait;
protected $guarded=[];
protected $dates = ['deleted_at'];
protected $hidden = [
'password', 'remember_token',
];
//hosts relation
public function hostproperty()
{
return $this->hasMany('App\Models\Hosts\HostProperty','user_id');
}
}
HostProperty.php
namespace App\Models\Hosts;
use Illuminate\Database\Eloquent\Model;
class HostProperty extends Model
{
public $timestamps = true;
protected $guarded=[];
protected $hidden = [
'user_id',
];
public function user()
{
return $this->belongsTo('App\User','user_id');
}
}
HostPropertyPolicy
namespace App\Policies\Host;
use App\User;
use App\Models\Hosts\HostProperty;
use Illuminate\Auth\Access\HandlesAuthorization;
class HostPropertyPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function access(User $user, HostProperty $HostProperty)
{return TRUE;
//return $user->id === $HostProperty->user_id;
}
}
AuthServiceProvider
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use App\Models\Hosts\HostProperty;
use App\Policies\Host\HostPropertyPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
HostProperty::class=>HostPropertyPolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
HostPropertyController
use App\User;
use App\Models\Hosts\HostProperty;
use App\Http\Controllers\Controller;
class HostPropertyController extends Controller
{
public function listOneProperty($propertyId)
{
$authUser = auth('api')->user();
$property=HostProperty::with('user')->find($propertyId);
if ($authUser->can('access', $property)) {
return response()->json(['success' => 'success']);
} else {
return response()->json(['error' => 'error']);
}
}
}
Route
Route::get('listOneProperty/{propertyId}', array('as' => 'listOneProperty.get', 'uses' => 'HostPropertyController#listOneProperty'));
Please note: I am calling from API - the above route is for API, I am not able to use the policy on the API routes. I keep getting the above error while calling this route.
I tried
$this->authorize('access', $property);
However, since API doesn't store login session the above could not be completed so I again tried with
$authUser = auth('api')->user();
$authUser->authorize('access', $property);
Does not work either. I have tried all I can but still, I cannot get it done right.
If someone has an example of using Laravel policy in API authenticated by Passport it would be helpful for anybody looking to get this done right.
With regards
Looks like
$authUser = auth('api')->user();
is returning a query instead of a User model.
Please make sure that $authUser is a User model before calling ->can()
However, since API doesn't store login session the above could not be completed so I again tried with
Authentication is typically handled by middleware in Laravel. If you're using Passport, you should be able to use the auth:api middleware to authenticate requests, as long as you're sending the correct Authorization header and token.
Try changing this
$authUser = auth('api')->user();
to this
$authUser = auth()->user();
And add the auth middleware to your route:
Route::get('listOneProperty/{propertyId}', 'HostPropertyController#listOneProperty')
->name('listOneProperty.get')
->middleware('auth:api');
If you're consuming the api from your own web views and you want it to work with the current session, check out the Consuming Your API With JavaScript section of the Passport docs:
https://laravel.com/docs/5.7/passport#consuming-your-api-with-javascript
I'm trying to figure out why my custom request class cannot be called by one of my methods.
I created my class with, php artisan make:request ValidateUserSecretRequest.
This created my custom request file in the Http/Requests folder as expected.
However, ValidateUserSecretRequest called within my Auth\LoginController.php, , I get Class App\Http\Controllers\Auth\ValidateUserSecretRequest does not exist.
Here's the controller, with unnecessary methods removed:
namespace App\Http\Controllers\Auth;
use Cache;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use App\Http\Requests\ValidateSecretUserRequest;
use App\Http\Controllers\Controller;
class LoginController extends Controller
{
use AuthenticatesUsers;
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function postValidateToken(ValidateUserSecretRequest $request)
{
// get user id and create cache key
$userId = $request->session()->pull('2fa:user:id');
$key = $userId . ':' . $request->totp;
// use cache to store token to blacklist
Cache::add($key, true, 4);
// login and redirect user
Auth::loginUsingId($userId);
return redirect()->intended($this->redirectTo);
}
And my custom request class:
namespace App\Http\Requests;
use Cache;
use Crypt;
use Google2FA;
use App\User;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory as ValidatonFactory;
use Illuminate\Foundation\Http\FormRequest;
class ValidateUserSecretRequest extends FormRequest
{
/**
*
* #var \App\User
*/
private $user;
/**
* Create a new FormRequest instance.
*
* #param \Illuminate\Validation\Factory $factory
* #return void
*/
public function __construct(ValidatonFactory $factory)
{
$factory->extend(
'valid_token',
function ($attribute, $value, $parameters, $validator) {
$secret = Crypt::decrypt($this->user->google2fa_secret);
return Google2FA::verifyKey($secret, $value);
},
'Not a valid token'
);
$factory->extend(
'used_token',
function ($attribute, $value, $parameters, $validator) {
$key = $this->user->id . ':' . $value;
return !Cache::has($key);
},
'Cannot reuse token'
);
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
try {
$this->user = User::findOrFail(
session('2fa:user:id')
);
} catch (Exception $exc) {
return false;
}
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'totp' => 'bail|required|digits:6|valid_token|used_token',
];
}
}
I've tried:
composer dump-autoload
composer update
scratching my head multiple times
recreating the custom request with artisan with a different name, same problem
What the hell is going on here?
You have written "use App\Http\Requests\ValidateSecretUserRequest;" while you are using "ValidateUserSecretRequest" class, There is a typo.
Error in use App\Http\Requests\ValidateSecretUserRequest; You can check it again.
You get Class App\Http\Controllers\Auth\ValidateUserSecretRequest does not exist since it doesn't really exists. App\Http\Controllers\Auth\ prefix in the error means that it uses current namespace.
Look at ValidateUserSecretRequest - you should swap User and Secret to write correct class name.
You have the class name wrong
You have it as
App\Http\Requests\ValidateSecretUserRequest;
but actually it is ValidateUserSecretRequest
I'm using Laravel 5.3 and I'm trying to get the authenticated user's id in the constructor method so I can filter the user by assigned company as follows:
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\View;
use App\Models\User;
use App\Models\Company;
use Illuminate\Support\Facades\Auth;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests ;
public $user;
public $company;
public function __construct()
{
$companies = Company::pluck('name', 'id');
$companies->prepend('Please select');
view()->share('companies', $companies);
$this->user = User::with('profile')->where('id', \Auth::id())->first();
if(isset($this->user->company_id)){
$this->company = Company::find($this->user->company_id);
if (!isset($this->company)) {
$this->company = new Company();
}
view()->share('company', $this->company);
view()->share('user', $this->user);
}
}
However this doesn't return the user id. I've even tried Auth::check() and it doesn't work.
If I move the Auth::check() out of the __construct() method then this works as follows:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
$this->middleware('auth');
}
/**
* Show the application dashboard.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
dd(\Auth::check());
return view('home');
}
}
However this fails if I put this in the construct method in the HomeController too!
Any ideas why this is failing?
docs
you can't access the session or authenticated user in your
controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly
in your controller's constructor. Before using this feature, make sure
that your application is running Laravel 5.3.4 or above:
class ProjectController extends Controller
{
/**
* All of the current user's projects.
*/
protected $projects;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->projects = Auth::user()->projects;
return $next($request);
});
}
}
Since 5.3 Auth::check will not work in a controller's construtor, it's one of undocumented changes. So, you need to move it to middleware or do check in controller methods instead or move project to 5.2.x.
It fails because you call $this->middleware('auth'); after parent::__construct();. It means that you auth middleware is not loaded properly.