My problem is I do have a multi-auth in Laravel, what I am trying to do is to have an authorization to this model App\Models\Admin instead of App\User
in my policy
class AdminsPolicy
{
use HandlesAuthorization;
public function view(\App\Models\Admin $admin)
{
return in_array($admin->role, [2,3,4]);
}
}
now whenever I do something like this in my controller
dd(Auth::guard('admin')->user()->can('view'));
it always return false even if my admin role is correct
Usually the Policies in Laravel are somehow tied to a specific resource.
When using a policy you have to register it in your AuthServiceProvider like this
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
As you can see, the Policy is tied to a Post model in this example.
If you want to check, if a user can 'view' a specific post you have to pass that model as second paramter:
if($user->can('view', $post) { ... }
// or if you don't need a specific instance :
if($user->can('create', Post::class) { ... }
Maybe you are in fact looking for a Gate
You could define in your AuthServiceProviders boot function something like this:
Gate::define('view', function(\App\Models\Admin $admin) {
return in_array($admin->role, [2,3,4]);
});
Related
I have an Observer set up to Listen to a Model's events in order to keep my Controller clean of Logging messages. My implementation is as follows:
First, a store method that does just what it's supposed to do. Create and save a new model from valid parameters.
# app/Http/Controllers/ExampleController.php
namespace App\Http\Controllers;
use App\Http\Requests\StoreExample;
use App\Example;
class ExampleController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
/**
* Create and save an Example from validated form parameters.
* #param App\Http\Requests\StoreExample $request
*/
public function store(StoreExample $request)
{
Example::create($request->validated());
return back();
}
}
The StoreExample Form Request isn't important. It just validates and checks a gate to authorize the action.
The Observer I have set up logs this action.
# app/Observers/ExampleObserver.php
namespace App\Observers;
use App\Example;
class ExampleObserver
{
public function created(Example $example): void
{
\Log::info(auth()->id()." (".auth()->user()->full_name.") has created Example with params:\n{$example}");
}
}
The problem I have, is the way my logs depend on the auth() object to be set. Given the auth middleware and the gate it has to check in order to store an Example, there is no way a guest user will set off this code.
However, I do like to use tinker in my local and staging environments to check the behavior of the site but that can set off an error (Well, PHP notice to be more precise) because I can create Example models without being authenticated and the logger will try to fetch the property full_name from the non-object auth()->user().
So my question is as follows: Is there a way to catch when I'm specifically using the Laravel tinker session to handle my models in the Observer class?
Okay, replying to my own question: There IS a way. It requires using a Request object. Since observers do not deal with requests on their own, I injected one in the constructor. request() can be used instead, so no DI is needed.
Why is a Request important?
Because a request object has an accessible $server attribute that has the information I want. This is the relevant information I get by returning a dd($request->server) (I'm not gonna paste the whole thing. My Request's ServerBag has over 100 attributes!)
Symfony\Component\HttpFoundation\ServerBag {#37
#parameters: array:123 [
"SERVER_NAME" => "localhost"
"SERVER_PORT" => 8000
"HTTP_HOST" => "localhost:8000"
"HTTP_USER_AGENT" => "Symfony" // Relevant
"REMOTE_ADDR" => "127.0.0.1"
"SCRIPT_NAME" => "artisan" // Relevant
"SCRIPT_FILENAME" => "artisan" // Relevant
"PHP_SELF" => "artisan" // Relevant
"PATH_TRANSLATED" => "artisan" // Relevant
"argv" => array:2 [ // Relevant
0 => "artisan"
1 => "tinker"
]
"argc" => 2
]
}
So there's all these attributes I can filter by using $request->server('attribute') (returns $request->server->attribute or null, so no risk of accessing an undefined property). I can also do $request->server->has('attribute') (returns true or false)
# app/Observers/ExampleObserver.php
namespace App\Observers;
use App\Example;
class ExampleObserver
{
/* Since we can use request(), there's no need to inject a Request into the constructor
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
*/
public function created(Example $example): void
{
\Log::info($this->getUserInfo()." has created Example with params:\n{$example}");
}
private function getUserInfo(): string
{
// My logic here.
}
}
I want to authorize my API of my Laravel application.
My structure right now is like this:
Users belong to an organization, and the organization has many (lets say) objects.
Now I want that only users can view/edit/create/delete objects, that belong to the organization they are part of.
My API route for viewing the objects is:
Route::get('organizations/{id}/objects','ObjectController#indexOrganization')->middleware('auth:api');
I created the Models User, Organization and Object. They all have their own Controller.
I created the ObjectPolicy and tried this:
public function view(User $user, Object $object)
{
return $user->organization_id === $object->organization_id;
}
And then I added ->middleware('can:view,object'); to the route.
Unfortunately, it does not work and the Laravel documentation does not provide the information I need.
Can someone help?
Thanks!
EDIT
I have no idea what I'm doing wrong!! I Changed everything but I still get a 403 response.
Here is my code:
Route:
Route::get('organizations/{organization}/objects','ObjectController#index Organization')->middleware('auth:api', 'can:view, organization');
OrganizationPolicy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
ObjectController:
public function indexOrganization(Organization $organization)
{
$objects = $organization->objects;
return ObjectResource::collection($objects);
}
I also added this to my AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
App\Organization::class => App\Policies\OrganizationPolicy::class,
];
EDIT 2 / SOLUTION
The answer from newUserName02 works! The problem was inside the AuthServiceProvider. After I changed the code (see above in Edit) there to:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Organization' => 'App\Policies\OrganizationPolicy',
];
it worked!
The policy method should to match the arguments you are passing to the controller. It looks like you are passing the id of the Organization in the route, but you are trying to check the Object on the policy.
https://laravel.com/docs/5.7/authorization#via-middleware
You can take advantage of Laravel's implicit model binding to inject the Organization into the controller like this:
Route:
Route::get('organizations/{organization}/objects','ObjectController#indexOrganization')->middleware('auth:api', 'can:view,organization');
Policy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
Controller:
public function indexOrganization(Organization $organization)
{
...
}
Notice that {organization} in the route matches organization in the ->middleware() call, which matches $organization in the policy and controller.
In laravel 5.5 I create the policy
public function view()
{
return true;
}
and register it in the AuthServiceProvider
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
Post::class => PostPolicy::class,
];
In the controller I use the policy like this:
$this->authorize('view');
I get the error This action is unauthorized whether the function view() returns true or false.
Your policy is registered for the Post model.
I assume your view() method is inside the PostPolicy class. It appears as if you'd want to use it without a model instance.
Use $this->authorize('view', Post:class); if the policy code does not require a model instance.
Your view method should furthermore receive a user model.
public function view(User $user) { ... };
Otherwise, for whom would you want to check permissions.
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.
I have the following problem, I want to share an array to all views in my project so I followed the documentation and it works fine, but I want to get the authenticated user in service provider boot function and it always return null ?
any suggestions ?
this is my code
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public $myusers;
public function boot()
{
$origLat=\Auth::user()->lat;
$origLon=\Auth::user()->lng;
$dist=5;
$lon1=$origLon-$dist/cos(deg2rad($origLat))*73.2044736;
$lon2=$origLon+$dist/cos(deg2rad($origLat));
$lat1=$origLat-($dist/73.2044763);
$lat2=$origLat+($dist/73.2044763);
$id=\Auth::user()->id;
$pictures=User::find($id)->pictures;
$this->myusers = DB::table('users')->select(
DB::raw("*,
3956 * 2 *
ASIN(SQRT( POWER(SIN(($origLat- lat)*pi()/180/2),2)
+COS($origLat*pi()/180 )*COS(lat*pi()/180)
*POWER(SIN(($origLon-lng)*pi()/180/2),2)))*1.609344
as distance"
))
->where('users.id', '!=', \Auth::user()->id)
->whereBetween('lng',[$lon1,$lon2])
->whereBetween('lat',[$lat1,$lat2])
->having("distance", "<", "$dist")
->orderBy("distance")
->get();
view()->share('myusers', $this->myusers);
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Unfortunately, at this point the Laravel application request lifecycle works in such a way that when the boot method of the App\Providers\AppServiceProvider class is executed the session is not yet initialised (since that's done in a middleware that is executed after the boot method).
Since the authentication systems needs the session in order to get the authenticated user, in your particular case you can't use view()->share() successfully there (although it's the recommended approach). Instead you can use an alternative approach by doing that in a middleware. Here are the steps that you can follow to make this work:
1. Create a middleware class, let's call it LoadUsers, by running this command:
php artisan make:middleware LoadUsers
2. That will generate a class in app/Http/Middleware/LoadUsers.php. Now you just need to move your code from the AppServiceProvider to the handle method of the middleware:
class LoadUsers
{
public function handle($request, Closure $next)
{
// Your code that shares the data for all views goes here
return $next($request);
}
}
3. Next you need to register the middleware with the App\Http\Kernel class. You can add it to the web group from $routeMiddleware if you want to apply the middleware to all routes that that use that or create your specific group or route middleware. So something like this if you want to add it to web:
protected $middlewareGroups = [
'web' => [
...
// Make sure to add this line is after the
// StartSession middleware in this list
\App\Http\Middleware\LoadUsers::class,
],
...
];
Now you should have the proper shared data for all your views that can depend on Auth::user().