Use multiple Auth guards for one Policy - laravel

I have implemented multiple Auth guards in a Laravel 5.4 project (one of for admins and the other for regular users). This has worked successfully so far and both admins and users are able to log in. I am now trying to implement a Policy class that works for both Auth guards. This is because I have certain models that I want all administrators to edit and only users who own the model to be able to edit. So I have defined a policy with this method.
App\Policies\ModelPolicy
public function update(User $user, Model $model)
{
if ($user->id === $model->user_id) {
return true;
}
if (Auth::guard('admin')->check()) {
return true;
}
return false;
}
Then in whatever controller method I have for my model:
App\Http\Controllers\ModelController
public function update(Model $model)
{
$this->authorize('update', $model);
// update model
}
This works perfectly if a regular user is logged in. However, when an admin user is logged in, it doesn't even reach the policy (I know this from error logging). I am guessing that the Policy class does something to automatically deny a request if the default guard in Auth::check() fails. However, since it is valid for my users to have one of several guards (not just the default), I need to bypass this behavior.
I know I could implement the admin logic in my controller method and only use the policy if I know I am dealing with a non-admin:
public function update(Model $model)
{
if (!Auth::guard('admin')->check()) {
$this->authorize('update', $model);
}
// update model
}
However, this can quickly spiral out of control if my admin condition is more complicated than simply being logged in. More importantly, all of this logic belongs in a Policy, not muddying up my controller.
How is it possible to use the same Policy class for multiple authentication guards?

I ended up overriding the authorize method on the base controller class to make the correct Guard the default Guard. Then, the $user argument passed into my policy will be an instance of whichever Auth guard the current user is logged in as.
app/Http/Controllers/Controller.php
use Auth
class Controller extends BaseController
{
use DispatchesJobs, ValidatesRequests;
use AuthorizesRequests {
authorize as protected baseAuthorize;
}
public function authorize($ability, $arguments = [])
{
if (Auth::guard('admin')->check()) {
Auth::shouldUse('admin');
}
$this->baseAuthorize($ability, $arguments);
}
}
Now that the Policy will be passed in either my User model or my Admin model, I need to make sure that I remove the type-hinting for the argument and check the type of the model that is passed in. I don't need to do any Auth::check() because I know that the $user that is passed in must be a logged in user of the type that I want.
App\Policies\ModelPolicy
use App\User;
public function update($user, Model $model)
{
if ($user instanceof User) {
return $user->id == $userId;
}
// Is an Admin
return true;
}
And now I have access to desired Auth guard to do whatever I want with it in my Policy.

You can override the "authorize" method in your common controller (/app/Http/Controllers/Controller.php):
class Controller extends BaseController
{
use AuthorizesResources, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests {
authorize as protected laravelAuthorize;
}
public function authorize($ability, $arguments = [])
{
if (!Auth::guard('admin')->check()) {
$this->laravelAuthorize($ability, $arguments);
}
}
}

Related

Policy for agents to view route

I'm trying to set up a policy where only agents can view their properties, and eventually only agents should be able to update their own properties. I want to also make it so that administrators will be able to go in and access ALL properties. Right now, I can't seem to do it and I don't know what I'm doing wrong.
// this is my controller method
public function index($id)
{
$this->authorize('view', $id);
$properties = Property::where('agent_id', $id)->get();
return view('agents.index', compact('properties'));
}
// this is in my PropertyPolicy
public function view(User $user, Agent $agent)
{
return $agent->email_address == $user->email_address;
}
use App\Property;
use App\User;
public function view(User $user, Property $property)
{
return $user->id === $property->agent_id;
}
You can check that the id of the current authenticated user matches the agent_id of the property (assuming this is how you setup the foreign key and relationship).
I am not sure if you are using the User model for authentication or the Agent model.
To allow admins to just pass through you can create a before method:
public function before($user, $ability)
{
// how ever you determine if they are an admin
if ($user->isAdmin()) {
return true;
}
}

Adding custom where clause to AuthenticatesUser Trait Laravel

We have decided to use Laravel for a project as a test run for future frameworks and are really enjoying it. There is one issue we are having though.
We use the trait Illuminate\Foundation\Auth\AuthenticatesUsers which handles user authentication. It works well. However, we have a column in the database called userstatus which could be a 0 or a 1.
How do we inject this where clause into the Illuminate\Foundation\Auth\AuthenticatesUsers trait?
I was thinking maybe something here (in my LoginController):
public function authenticated($request , $user){
//if $user->userstatus != 1 logout and redirect to start page
}
But I dont know how to logout (im looking into that now) .
your logic is right, you should redefine login and authenticated methods within LoginController.
your methods should be like below:
this method should be within your LoginController.php:
class LoginController extends Controller
{
use AuthenticatesUsers {
login as public loginParent;
}
protected function login(Request $request){
$default = '/';
$user = User::where('email', $request->get('email'))->NotActive->first();
if($user){
return redirect()->intended($default);
}
return $this->loginParent($request);
}
protected function authenticated(Request $request, $user)
{
if($user->not_active) {
$this->logout($request);
}
}
}
then we should create ScopeNotActive method within User.php Model as Local Scope:
//User.php
public function ScopeNotActive($query){
return $query->where('userStatus', '!=', 1);
}
and a Mutator to check if the user is not active:
// User.php
public function getNotActiveAttribute(){
return $this->userStatus != 1;
}

Should I place logic about authentication on the model?

I'm developing an application which involves authentication and files acl.
Now I want to write a method on the file model called "userCanAccess" which check if the given user/ the user role is in the file acl.
The code will be something along those lines:
public function userCanAccess($user = null) {
$user = is_null($user) ? auth()->user() : $user;
if($this->acl->users->contains($user)
|| $this->acl->roles->contains($user->role)) {
return true;
}
return false
}
Is it right to place this kind of logic on the model?
Laravel has a neat built-in bit of functionality called Policies.
You'd create a FilePolicy that applies to the File model:
php artisan make:policy FilePolicy --model=File
and in the resulting app/Policies/FilePolicy.php, you'll see some ready-to-edit existing policies, one of which is called view. Put your authorization logic here.
Once you've built that, you can apply the policy in a variety of ways, like controller functions, middleware on your routes, or directly within views using the #can Blade directive.
https://laravel.com/docs/5.8/authorization#authorizing-actions-using-policies
This should work just fine for me, but rather than bombing the model class, I would extract it to the trait.
You can make roles and permissions tables
User model:
public function roles()
{
return $this->belongsToMany(Role::class);
}
Role model:
public function users()
{
return $this->belongsToMany(User::class);
}
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
Permission model:
public function roles()
{
return $this->belongsToMany(Role::class);
}
then in app/Providers/AuthServiceProvider you can make Gate like this:
public function boot()
{
$this->registerPolicies();
foreach ($this->getPermissions() as $permission) {
Gate::define($permission->name,function($user) use($permission){
return $user->hasRole($permission->roles);
});
}
}
private function getPermissions(){
return Permission::with('roles')->get();
}
at the end you can use ACL everywhere you want by just write Gate name like:show-comments or access-files or ....

Implicit route model binding 401 unauthorized

I have a super simple learning app. My Laravel version is 5.5.13. A User can create a Pet. I am implicitly throwing 404 but I need to also implicitly throw 401 is this possible?
Details on setup:
Pet model:
class Pet extends Model
{
protected $fillable = ['name', 'user_id'];
public function user()
{
return $this->belongsTo('App\User');
}
}
And User model giving the relationship hasMany:
class User extends Authenticatable
{
use Notifiable;
// ... some stuff hidden for brevity
public function pets()
{
return $this->hasMany('App\Pet');
}
}
I used implicit route model binding to throw 404 status when the id is not found like this:
Route::group(['middleware' => 'auth:api'], function() {
Route::get('pets', 'PetController#index');
Route::get('pets/{pet}', 'PetController#show');
Route::post('pets', 'PetController#store');
Route::put('pets/{pet}', 'PetController#update');
Route::delete('pets/{pet}', 'PetController#delete');
});
Notice the {pet} instead of {id}.
However I also want to throw 401 unauthorized status if the $pet->user_id does not equal Auth::guard('api')->user()->id. Is this implicitly possible?
If not possible, may you please show me how to explicitly do this in the controller? I was doing this, but I don't think it's the recommended way is it?
public function show(Pet $pet)
{
if ($pet->user_id != Auth::guard('api')->user()->id) {
return response()->json(['message'=>'Not authenticated to view this pet'], 401);
}
return $pet;
}
The more Laravel centric way todo this is using policies.
Then for each action you want to authorize you register them inside your policy. Your show method would then become:
public function show(Pet $pet)
{
$this->authorize('show', $pet);
return $pet;
}
So your steps would be:
Create a new Policy for Pets
Add the actions you want to authorize
Register the policy in the AuthServiceProvider
Use the authorize
call inside your Controller action

How to create Policies without parameters in Laravel 5.2?

I am trying to create a project to add edit contacts.
To restrict the user can add/edit their own contacts, So added policy ContactPolicy as below
<?php
namespace App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use App\User;
use App\Contact;
class ContactPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function before($user, $ability)
{
if ($user->isAdmin == 1) {
return true;
}
}
public function add_contact(User $user)
{
return $user->id;
}
public function update_contact(User $user, Contact $contact)
{
return $user->id === $contact->user_id;
}
}
And registered in AuthServiceProvider as below
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Contact::class => ContactPolicy::class,
];
To restrict adding of contact by current user I added Gate in my controller function as below without passing parameters
if (Gate::denies('add_contact')) {
return response('Unauthorized.', 401);
}
Even if current user tries to add contact, it shows Unauthorized message.
How will I solve this problem?
Policies are intended to have all authorization logic related to a certain class of resource in one place.
So, you define Contact::class => ContactPolicy::class meaning the ContactPolicy has all policies regarding Contacts. When you write Gate::denies('add_contact'), how could the framework know which policy to search? You have to pass an object of type Contact as second parameter in order to access the ContactPolicy.
Anyway, there is in fact a place to write authorization logic which is not particular to any class of resource. In the method boot of AuthServiceProvider you could add
$gate->define('add_contact', function ($user) {
return $user->id;
});
By the way, what's the intention with returning the user id? I think you just need to return a boolean.
Also, if you are checking the permission within a controller, you should just call $this->authorize('add_contact') and the controller itself will check and return a Forbidden response (for which the proper code is 403, not 401) if it fails, no need to return it yourself.

Resources