Laravel permission on same resource for multiple roles (Spatie) - laravel

I need to grant access to users with different roles to actions of one resource.
I tried the following but no luck in web.php routes file:
Route::resource('trampas', 'TrampaController')->middleware('role:Administrador|Supervisor'); //Access to all actions
Route::resource('trampas', 'TrampaController')->middleware('role:Monitoreador|Coordinador')->only('index', 'show');
But when i declare the 2nd line the first in annulled.
Same thing happens when it's declared in the controller's construct method:
$this->middleware('role:Administrador');
$this->middleware('role:Coordinador')->only('index','show');
Last line permission prevails.
Any ideas?

Middleware must all pass before the request is processed. If you want index and show to pass one of the roles you need to explicitly put all roles in the parameters.
In your controller:
class TampaController extends Controller {
public function __construct() {
$this->middleware('role:Administrador|Supervisor');
$this->middleware('role:Administrador|Supervisor|Monitoreador|Coordinador')->only('index', 'show');
}
// ...
}

I can't get why this is working:
public function __construct()
{
$this->middleware('role:Administrador|Supervisor')->except('index', 'show');
$this->middleware('role:Administrador|Supervisor|Monitoreador|Coordinador');
}
when what it seems to do is the opposite of what i need.
I need the the roles Administrador and Supervisor can access all actions and roles Monitoreador y Coordinador can only access to index and show actions. But the way it works seems to declare that Administrador and Supervisor can access all except for index and show actions.
Why could this be happening?

Related

Laravel 8 - Global Object available throughout application (not just in view files)

In my application, users can belong to different accounts and have different roles on those accounts. To determine which account is "current" I am setting a session variable in the LoginController in the authenticated() method.
$request->session()->put('account_id', $user->accounts()->first()->id);
Then, throughout the application I am doing a simple Eloquent query to find an account by ID.
While this "works", I am basically repeating the same exact query in every single Controller, Middleware, etc. The maintainability is suffering and there are duplicate queries showing in Debugbar.
For example, in every controller I am doing:
protected $account;
public function __construct()
{
$this->middleware(function($req, $next){
$this->account = Account::find($req->session()->get('account_id'));
return $next($req);
});
}
In custom middleware and throughout the entire application, I am essentially doing the same thing - finding Account by ID stored in session.
I understand you can share variable with all views, but I need a way to share with the whole application.
I suppose much in the same way you can get the auth user with auth()->user.
What would be the way to do this in Laravel?
I would create a class to handle this logic. Making it a singleton, to ensure it is the same class you are accessing. So in a provider singleton the class you are gonna create in a second.
$this->app->singleton(AccountContext::class);
Create the class, where you can set the account in context and get it out.
class AccountContext
{
private $account;
public function getAccount()
{
return $this->account;
}
public function setAccount($account)
{
$this->account = $account;
}
}
Now set your account in the middleware.
$this->middleware(function($req, $next){
resolve(AccountContext::class)->setAccount(Account::find($req->session()->get('account_id')));
return $next($req);
});
Everywhere in your application you can now access the account, with this snippet.
resolve(AccountContext::class)->getAccount();

Create generic route-based authorization in Laravel

I'm coming from conventional PHP background and trying to create my first big project in Laravel.
I usually user User/Role/Permission to manage user permissions in my applications. It works like follows:
User has many Roles
Role has many Permissions
to make things simple, I actually used the page names as permissions, so that I check the current page name against user permissions.
That was all easy in PHP, now I am trying to implement a similar approach in Laravel. I have User, Role, Permission models, and I check if user has permission using a method in User model as follows (inspired from a Laracasts tutorial):
public function permissions()
{
return $this->roles->map->permissions->flatten()->pluck('name')->unique();
}
And in my AuthServiceProvider I added the following code:
Gate::before(function ($user, $permission){
return $user->permissions()->contains($permission);
});
So if I add some permission (for example 'add_user') to the user, I can simply do the following in the route, and it works just fine:
Route::get('/test', function () {
return 'You are authorized';
})->name('add_user')->middleware('can:add_user');
Now since I have a lot of pages, I wouldn't like to pass specific permission name to the middleware, rather find a better and more generic way.
The only way I could come up with is to use the permission name same as the route name, and create a new middleware to take care of authorization.
So In my solution I added the following middleware class:
class BeforeMiddleware
{
public function handle($request, Closure $next)
{
$route_name = $request->route()->getName();
if(!Auth::user()->permissions()->contains($route_name)) {
throw new \Exception('Not Authorized');
}
return $next($request);
}
}
Added it to Kernel.php:
protected $routeMiddleware = [
'before' => \App\Http\Middleware\BeforeMiddleware::class,
...
];
And finally changed the route to be as follows:
Route::middleware(['before'])->group(function () {
Route::get('/test', function () {
return 'You are authorized';
})->name('add_user');
});
This way I don't actually have to pass the permission name when I check the permission, and directly get it from the route name.
I have many questions about my solution: is it really a good approach? Does it have any drawbacks? Is there a better approach?
Also I preferred to use AuthServiceProvider instead of the new middleware, but I couldn't retrieve the route name from ServiceProvider scope. Can I somehow use AuthServiceProvider for a similar case?
Sorry if I made the post somehow long, but I needed to be as clear as I could.

Controller constructor to check Auth middleware for two different guards

I have a dashboard view that shows certain contain depending on which user is viewing, whether it be an admin or just a regular user.
I can get my admins onto that page, but regular users aren't able to currently because of my middleware guard.
class DashboardController extends Controller {
public function __construct()
{
$this->middleware('auth:admin');
}
public function index()
{
return view('dashboard.index');
}
}
The following code checks on each DashboardController call for auth:admins, but I want regular users to access this too, is there a way to check the auth middleware twice like so?
$this->middleware(['auth:admin','auth']);
So ideally it will check if you're an admin or just a regular auth user.
Also on my view page, when accessing properties of an admin I'm using:
{{ Auth::user()->admin_username }}
Is this normal? I have an admin Model but I'm still accessing it via Auth::user() which feels strange to me, shouldn't it be Auth::admin()->admin_username
Accessing a particular page for users with differing roles is more suited for laravels gates and policy authorization mechanisms.
https://laravel.com/docs/5.5/authorization#writing-gates
These allow you to write fine tuned rules for each use case you have. Simple gates can be defined as closures within your application AuthServiceProvider. For example:
public function boot()
{
$this->registerPolicies();
Gate::define('access-dashboard', function ($user, $post) {
return auth()->check() && (auth()->user()->hasRole('admin') || auth()->user()->hasRole('regular'));
});
}
Then you can use the gate facade wherever necessary, for instance a controller method or constructor.
if (Gate::allows('access-dashboard', $model)) {
// The current user can access dashboard, load their data
}
Alternatively use the can or cant helpers on the user model directly.
if (auth()->user()->can('access-dashboard')) {
//
}
Of course, you can achieve similar via middleware, the advantage of using the above is you can authorize actions at specific points in your code as well as reusability.
As for for last question, as you have it written is correct.
{{ Auth::user()->admin_username }}
Auth::user() or auth()->user() simply returns the currently authenticated user, regardless of their role.
Policies will never work without auth middleware

Multiple auth controller middleware Laravel

I'm trying specify which functions are able to each user but the problem is when I pass more than one authenticated user to middleware in my controller.
Here are my controller. I'm trying allow just one method for the users but at that piece of code, the second line don't work out. Just show a response for not authorized (401).
public function __construct()
{
$this->middleware('auth:admin');
$this->middleware('auth:web')->only('listAll');
}
Someone have some idea how to solve it ?
I tried use:
public function __construct()
{
$this->middleware('auth:admin')->except('listAll');
$this->middleware('auth:web')->only('listAll');
}
Works but it means that will be allowed for all users...

Models accessible only for authenticated user THAT CREATED THEM (Laravel)

I'm writing a software application to let the people have their own private archive of cooking recipes.
The RecipeController constructor contains:
$this->middleware('auth')
because only registered users can use recipes, but I need to protect also the access to the models.
The point is that users can view and modify only their own recipes.
Example: The user TortelliEngineer can create a recipe "Tortelli Secret Recipe" using the model Recipe; he can view, update and delete his recipe(s), but nobody else can see his precious "Tortelli Secret Recipe".
So, which is the cleanest way?
I added a user_id attribute to the model Recipe.
I must use this parameter every single time that I ask to the database for a Recipe (goodbye "findOrFail" by ID)
That means that every time I make a request I must access the Request object that contains User that contains User_id
using Auth::id() EVERY SINGLE TIME that I need one (or n) recipe
Like this:
class RecipeRepository{
public function all(){
return Recipe::where('user_id', Auth::id())
->orderBy('created_at', 'asc')
->get();
}
public function find($recipe_id){
return Recipe::where('user_id', Auth::id())
->where('id', $recipe_id)
->firstOrFail();
}
Is that correct? Do you hate me for this? Do you know better or more correct ways to do it?
Most of the time I make a method inside the model to check if someone is authorised, owner etc.. of something.
An example would be:
// User model
public function owns_recipe($recipe)
{
return ($recipe->user_id == $this->id);
}
You can call this at the very beginning in of the methods of your controller:
// Controller
public function index (Request $request)
{
$recipe = Recipe::find($request->id); // Get recipe
$user = ... // Get user somehow
if (!$recipe) App::abort(404); // Show 404 not found, or something
if (!$user->owns_recipe($recipe)) App::abort(403); // Show 403 permission denied, or something
... // Do whatever you want :)
}
While there are many ways of approaching this, Laravel does provide some built-in methods for handling general authentication of actions. In the first place I'd do something along the lines of what you intended (have a getRecipesByOwner method in RecipeRepository) and you can pass the user to it from the injected Request object:
// RecipeController
public function index(Request $request)
{
$recipes = $this->recipeRepo->findRecipesByOwner($request->user());
}
In addition though, I'd recommend creating policies to manage whether or not a user is capable of updating/deleting/viewing individual recipes. You can then authorize their actions in the controllers/blade templates/etc. via built-in methods like:
// Controller
public function update(Request $request, Recipe $recipe)
{
$this->authorize('update', $recipe);
}
// Blade template
#can('update', $recipe)
#endcan
The documentation is available at: https://laravel.com/docs/5.3/authorization#creating-policies

Resources