I need to give admin access to all methods in controller, but only to some for customer-admin.
I tried to go with this
public function __construct()
{
$this->middleware('auth');
$this->middleware('role:customer-admin')->only(['show', 'edit', 'update', 'upload_picture']); // should give access to select methods
$this->middleware('role:admin'); // should give access to all methods
}
But it seems that in this case you have to conform to both.
As counterintitive it may seem, here you have to combine roles based on methods. So the right answer would be:
public function __construct()
{
$this->middleware('auth');
$this->middleware('role:customer-admin|admin')->only(['show', 'edit', 'update', 'upload_picture']);
$this->middleware('role:admin')->only(['index', 'create', 'store', 'destroy]); //Indicate methods that are exlusive to admin
}
Related
As it's write in doc, i used Laravel's Gate::before() method.
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
public function boot()
{
$this->registerPolicies();
// 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;
});
}
}
But it doesn't work. I had the role in middleaware groupe in web.php and it's work. The role 'Super Admin' dont have any permission.
Route::group(['middleware' => ['auth:api','role:Super Admin|Provider']], function() {
...
}
In my ProviderController
class ProviderController extends Controller
{
function __construct(){
$this->middleware('permission:provider-list|provider-create|provider-edit|provider-delete', ['only' => ['index','show']]);
$this->middleware('permission:provider-create', ['only' => ['create','store']]);
$this->middleware('permission:provider-edit', ['only' => ['edit','update']]);
$this->middleware('permission:provider-delete', ['only' => ['destroy']]);
}
My question it's the right way or not ? I thank you in advance.
We use Spatie/laravel-permission often (docs). Your code for checking if a user has a role is correct. Spatie's hasRole function does not care for spaces or dashes. The function does however care for whether the role actually exists. Ensure you migrated the right tables, and actually inserted the role into your database.
\Spatie\Permission\Models\Role::create([
'name' => 'Super Admin',
'guard_name' => 'web',
]);
$user->assignRole(\Spatie\Permission\Models\Role::findByName('Super Admin'));
Your issue is likely that you only inserted the role for the web guard. You probably have it as the default guard, and that was the one inserted in your migration. The issue is that you are using the api guard to access that route. So you have to create the role for that guard too. I think they mention that in the Spatie documentation.
I'm using the permission role package by Spatie.
There's two ways that I know to prevent the user to access the controller methods.
1. __construct
public function __construct()
{
$this->middleware('permission:show products');
$this->middleware('permission:create products', ['only' => ['create','store']]);
$this->middleware('permission:edit products', ['only' => ['edit','update']]);
$this->middleware('permission:destroy products', ['only' => ['destroy']]);
}
2. Each method
public function edit()
{
//
if (Gate::denies('edit products')) {
//get out
}
//do something
}
public function show()
{
//
if (Gate::denies('show products')) {
//get out
}
//do something
}
Is there a better way to do it without defining on each method?
Sounds like you need Middleware Groups. This makes it possible to group a collection of routes and apply middlewares to all routes in that group.
In my Laravel application, i have several policies working, but one will not work.
Controller
public function store(Project $project, CreateActionRequest $request)
{
$this->authorize('store', $project);
Action::create([
'name' => $request->name,
]);
return redirect()->route('projects.show', $project->id)->withSuccess('Massnahme erfolgreich gespeichert');
}
Policy
namespace App\Policies\Project;
use App\Models\Project\Project;
use App\Models\User;
use App\Models\Project\Action;
use Illuminate\Auth\Access\HandlesAuthorization;
class ActionPolicy
{
use HandlesAuthorization;
public function store(User $user, Project $project)
{
return $user->company_id === $project->company_id;
}
}
AuthServiceProvider
protected $policies = [
'App\Models\User' => 'App\Policies\CompanyAdmin\UserPolicy',
'App\Models\Company' => 'App\Policies\CompanyAdmin\CompanyPolicy',
'App\Models\Team' => 'App\Policies\CompanyAdmin\TeamPolicy',
'App\Models\Department' => 'App\Policies\CompanyAdmin\DepartmentPolicy',
'App\Models\Location' => 'App\Policies\CompanyAdmin\LocationPolicy',
'App\Models\Division' => 'App\Policies\CompanyAdmin\DivisionPolicy',
'App\Models\Costcenter' => 'App\Policies\CompanyAdmin\CostcenterPolicy',
'App\Models\Workplace' => 'App\Policies\CompanyAdmin\WorkplacePolicy',
'App\Models\Product' => 'App\Policies\CompanyAdmin\ProductPolicy',
'App\Models\Project\Action' => 'App\Policies\Project\ActionPolicy',
'App\Models\Project\Project' => 'App\Policies\Project\ProjectPolicy',
];
CreateActionRequest
namespace App\Http\Requests\Project;
use Illuminate\Foundation\Http\FormRequest;
class CreateActionRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|min:3',
];
}
}
All policies are working except ActionPolicy and ProjectPolicy.
I added in the policy a __construct() method to check if the policy is called. But ActionPolicy and ProjectPolicy are not working.
How can i search the error? I tried with dd() but i got only allways the message: This action is unauthorized
Since you are injecting CreateActionRequest instead of Request that means you are defining your own set of rules to authorize the FormRequest which comes inside of your method. Further it means that you gotta define a few rules which the "FormRequest" has to pass in order to EVEN reach your controller, this is a nice concept that I like about Laravel since the code is not centralized, but rather spread and every layer has it's own responsibility. Now, you don't have to call any method from your CreateActionRequest nor you have to write any code regarding that class in your controller, because Laravel runs authorize method by default before allowing the Request to reach your controller, before running authorizemethod in your CreateActionRequest it runs rules method which verifies that all the given fields pass the expressions you assigned them, so the execution is something like this CreateActionRequest => rules => authorize => IF(authorized) Controller ELSE Not authorized, hope that makes sense. In order to fix your code:
1.) Remove $this->authorize('store', $project);
This will allow you to pass not authorized error in case your name passes the truth test inside of rules method inside of your CreateActionRequest. If you wish to utilize your Action Policy you will need to hook up your custom Request(CreateActionRequest) with it and this is how:
public function authorize()
{
$store = $this->route('project');
//The above line will return Project object if your mapping is correct
//If it's not it will return the value you passed to your route for {project}
return $this->user() && $this->user()->can('store', $store);
}
EDIT:
Here is the link where you can see how to properly authorize and connect policy with CreateActionRequest
Do you have all your controller methods defined with the Request object last?
public function store(Project $project, CreateActionRequest $request)
The Request object should be the first parameter in the methods signature:
public function store(CreateActionRequest $request, Project $project)
Dependency Injection & Route Parameters
If your controller method is also expecting input from a route parameter you should list your route parameters after your other dependencies.
Most Laravel authorization mechanisms have identical method signatures allowing them to work across varying classes.
I have a route resource
Route::resource('campaign', 'CampaignController');
I want to restrict some of these routes to the users.
For example the index page lists all the campaigns and they should not see this only their own ones.
I have a custom middleware that simply checks the if the user is an admin
However I cannot apply this to individual methods.
public function index()
{
$this->middleware('checkuser');
}
Just the constructor
public function __construct()
{
$this->middleware('checkuser');
}
How do I get around this and apply to individual route in the controller
Sorry my mistake I should have read the docs you can add exceptions or allowed.
$this->middleware('auth');
$this->middleware('log', ['only' => ['fooAction', 'barAction']]);
$this->middleware('subscribed', ['except' => ['fooAction', 'barAction']]);
}
This is a very long question for (I think) a very simple answer, but I am a total newbie in Laravel and I need to know if I am addressing it the right way.
What I did
I followed the Intermediate Tutorial and modified it just a little bit, and now I have a simple task list that uses authentication. I had this in my TaskController constructor:
public function __construct(TaskRepository $tasks)
{
$this->middleware('auth');
}
This checks if the user is logged in before launching any methods, so I just need to call them like this in my routes file:
Route::get('/home', 'TaskController#index');
Route::get('/tasks', 'TaskController#indexUser');
Then I wanted to remove the authentication requirement for the index method, so that all users can see the /home page (where I list all the tasks), and only authenticated users can see the /tasks page (where I list only the user tasks and allow to delete them). I managed to do it like this:
1) I removed $this->middleware('auth') from the TaskController constructor
2) I modified my routes file to look like this:
Route::get('/home', 'TaskController#index');
Route::get('/tasks', [
'middleware' => 'auth',
'uses' => 'TaskController#indexUser'
]);
Ok, this works, but:
What I want to achieve
I don't want to have this logic in my routes file, I want that my Controller decides which methods are public and which not. But I got stuck. I think it should be something like this:
class TaskController extends Controller
{
/**
* Display a list of all current tasks
*/
public function index()
{
return view('tasks.index', [
'tasks' => Task::orderBy('created_at', 'asc')->get()
]);
}
/**
* Display a list of all of the logged user's task.
*
*/
public function indexUser(Request $request)
{
if (Auth::check() {
return view('tasks.index_user', [
'tasks' => $this->tasks->forUser($request->user()),
]);
} else {
//This is what I don't know how to do:
redirect_to_login
}
}
}
How can I achieve this?
You can decide which controller method should execute the middleware:
public function __construct()
{
$this->middleware('auth', ['only' => ['indexUser', 'update'] ];
}
just add in only the method you want to protect.
When a user try to access a method will automatically redirect to the login page.
Here you can find the documentation: https://laravel.com/docs/5.1/controllers#controller-middleware
You can do the following if you want this logic in your controller:
if (!Auth::check()) {
return redirect('/path/to/login/page');
}
This way you don't have giant if else statement in your controller. (Handy if your controller contains more logic then the sample above)
I personally would go for the answer Christian Giupponi provided. Cause it makes much more sense to handle this logic in the construct function then in your controller.