I am currently adding permissions/roles/authorization to a Laravel application.
In the application's database are users, companies and products. Companies have many users and a product belongs to a company.
Now I want to authorize company users, to create a product for their company.
In my ProductController.php I have something like this:
public function create(Request $request)
{
$company = Company::findOrFail($request->get('company_id'));
return view('product.create', compact('company');
}
One option would be to use Gate::authorize() after getting the company and pass the $company to the authorize-method.
Question: But how can I solve this, if I don't want to use Gate::authorize(). So in case I want to use policies?
Another Question: I see several ways to authorize: Gate::authorize(), policies, StoreProduct's authorize(), ...
Which one should I use? Should I always implement StoreProduct's authorize() event if I use policies for example?
You can have the create ability on the ProductPolicy take a Company instance as an argument and you can then check that the User belongs to that Company in the policy:
use App\Company;
use App\User;
class ProductPolicy
{
public function create(User $user, Company $company)
{
return $user->company_id === $company->id;
}
...
}
You can call authorize in your Controller to use the ProductPolicy to authorize the User. These authorization methods can take an array as the second argument which allows you to send additional data [the first argument will be the model for the policy, in this case it doesn't take an instance, but we still need it to know which model's policy we want so it takes the class name]. This will check the create ability on the policy for Product and pass it an additional value of $company:
use App\Company;
use App\Product;
...
public function create(Request $request)
{
$company = Company::findOrFail($request->input('company_id'));
$this->authorize('create', [Product::class, $company]);
return view('product.create', compact('company');
}
Remember to register the Policy for the Product model.
You have options for how you want to do authorization, that is why there are different methods to achieve it. Depends what you prefer or what fits your current need.
Related
I have a many to many relationship that I'm working with between User and Task models. A user belongs to many tasks and a task belongs to many users. I have a pivot table called task_user.
In my API, I have a route defined as follows:
Route::get('/users/{user}/tasks', 'TaskUserController#all');
I want to write a policy to enforce that the currently logged in user, auth()->user, is the user being requested in the route. Basically, a user can only view their own tasks.
How can I write a policy class for the nested resource controller TaskUserController?
Nesting of your resource has nothing to do with making policies.
Make your UserPolicy.
class UserPolicy()
{
public function view(User $authorizedUser, User $user) {
return $authorizedUser->is($user);
}
}
In your controller, you can authorize the action, with the authorize() helper. Alternatively it can be executed in your form request with Auth::user()->can().
class TaskController {
public function all(User $user)) {
$this->authorize('view', $user);
return $user->tasks;
}
}
I have the following policy which determines if a user is able to view a contract.
public function view(User $user, Contract $contract)
{
if ($user->user_type->id == 2) { // If user is a vecino
if ($user->id == $contract->customer_id) {
return true;
}
} else if ($user->user_type->is_admin == true) { // If user is an admin
return true;
}
return false;
}
Which is then checked for authorization with
$this->authorize('view', $contract);
How do I check authorization for a list/array/collection? Like if I get a list of contracts via Contract::all()
I haven't found any way to do this. I could do a loop and call $this->authorize for every iteration to check for authorization but that might impact performance.
Is there a better way of doing this?
One solution I am currently using is a hybrid approach where you define your rules within a scope and then reference that scope from the policy allowing you to reuse your authorization logic.
// Contract model
public function scopeViewable($query)
{
// If the user is admin, just return the query unfiltered.
if (Auth::user()->user_type->is_admin) {
return $query;
}
// Check the contract belongs to the logged in user.
return $query->where('customer_id', Auth::id());
}
And then in your policy, reference that scope but restrict it to the current model. Make sure to return a boolean using exists(). This essentially checks that your model is viewable.
// Contract Policy
public function view(User $user, Contract $contract)
{
return Contract::viewable()
->where('id', $contract->id)
->exists()
;
}
Importantly, you should use the scope when retrieving a collection of models and not the policy which would run the scope query for each model in the collection. Policies should be used on individual model instances.
Contract::viewable()->paginate(10);
// Or
Contract::viewable()->get();
But, when you want to check an individual contract you can use your policy directly.
$this->authorize('view', $contract);
// Or
Auth::user()->can('view', [Contract::class, $contract]);
The design i often sees in this case, is to check if all elements in the query is allowed to be viewed through the policy. This does not scale well and works bad with pagination.
Instead of filtering out the contracts with policies, the better solution is to filter the contracts already in the query. This mainly because if you want to do pagination down the line, you want to do all filtering before the query is executed to avoid having weird pagination meta data. While also having to run n operations for each element, which would already be a problem at 1000 elements.
There for doing the following query clause, can obtain the same result as your policy.
Contract::where('user_id', $user->id)->get();
A version of this i usually do to make things easier for my self is creating a scope in the user model.
public function scopeOwned($query, User $user)
{
return $this->query->where('user_id', $user->id);
}
Contract::owned($user)->get();
You have to loop, one way to another. There is no difference between looping over Contract object in your controller, or on your policy, but policies are made to check a single resource so I would do that in your controller.
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
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
Assume we´ve got a User and Conversation model with a many-to-many relation.
class User extends Model ... {
public function conversations()
{
return $this->belongsToMany('App\Conversation');
}
}
class Conversation extends Model {
public function users()
{
return $this->belongsToMany('App\User');
}
}
Besides authentication (logging in) which comes out of the box with laravel: How can I protect a specific conversation route for it´s related users?
Which would be the most maintainable way to achieve this? Middleware? Guard? Route model binding? ... right now I´m a bit lost ...
Good question. In this case you'd be best off using Laravel's authorization features. Here are the differences:
Middleware: used to run logic based on either routes or logged in / logged out state. So, if you want to block the conversations entirely from non-logged in users, use a middleware.
Authorization (policies): not to be confused with authentication, is intended for cases where the rules to block someone is not based on route but on some other, more specific reason. These reasons can be anything from roles, to teams, entity ownership, and so on. If you wanted to hide a conversation to only those in the conversation, you can create a policy that kicks the user back to their previous page if they were not in the conversation.
Here's a quick policy you might create:
class ConversationPolicy {
public function view(User $user, Conversation $conv) {
return in_array($user->id, $conv->users->pluck('id'));
}
}
You could check your policy in a controller like the following:
if($request->user()->can('view', $conversation))
{
return view('conversation', ['conversation' => $conversation]);
}
return back()->withError('You are not authorized to view this conversation');
Just be aware you'll have to bind this policy in the AuthServiceProvider before it can be used.