How to define policy for a list or array in laravel? - laravel

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.

Related

How to used Tucker-Eric/EloquentFilter Laravel

good day, I am using Tucker-Eric/EloquentFilter Laravel.
I want to filter it by relationship using Models
I want to automate it, instead of using the following:
public function users($users)
{
// dd($users);
return $this->r('users', $users);
}
public function user($user)
{
// dd($user);
return $this->r('user', $user);
}
public function owner($owner)
{
// dd($owner);
return $this->r('owner', $owner);
}
I want to make it one function that based on the relationship
so that I want to add another relationship on the model I don't need anymore to add another function.
Thanks!
We specifically stayed away from the type of implicit functionality you're looking for and opted for explicit filter methods to avoid security issues if/when new relations/properties were added to a model they wouldn't implicitly be available to filter against.
With that, what you're looking for isn't recommended because of the security concerns above but it can still exist if you implement it.
It sounds like the setup method would be the best place to implement it since it would be called first every time ->filter() is called.
public function setup()
{
foreach($this->input() as $key => $val) {
if($this->getModel()->$key() instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
// Your logic here
}
}
}

How to authorize depending on the request?

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.

Different state for Eloquent model fields depending on current user in laravel

I have the model:
class Task extends Model {
}
with some fields
protected $fillable = ['message', 'due_time', 'status', 'etc...'];
I've added custom function:
public function getEditableStateFor{AttributeName}
In my helper function I check that if
method_exists($class, 'getEditableStateForField1')
than I allow to edit this field depending on boolean value returned from this function.
Example:
if( ! $class->getEditableStateForField1() ) {
return "You can not edit field field1";
}
Here is how looks like some functions in Task:
private function isCreator() {
$user = Auth::user();
if($user) {
return $user->id === $this->creator_id;
}
return false;
}
public function getEditableStateForMessage() {
return $this->isCreator();
}
public function getEditableStateForDueTime() {
return $this->isCreator();
}
Is this a good way to do it or it is very bad design because of hidden dependency on Auth::user()?
What is a better way?
I do not want to put this logic inside controllers because this logic propagates to another models and is universal across application.
I'm like you and like to have Models that contain as much of the business logic as possible while remaining totally free of depencies on the "web" part of the application, which I believe should stay in Controllers, Request objects, etc. Ideally, Models should be easily usable from command line interfaces to the application, from within the Tinker REPL, and elsewhere while still guaranteeing data integrity and that business rules are observed.
That said, it seems the Laravel creators had slightly different ideas, hence the Auth facade being easily available in the model.
What I would likely do is add a parameter of type User to the getEditableStateFor series functions, and then in turn pass that parameter to isCreator ($user) and elsewhere. That also frees you up to be able to allow associated users to edit each other's Tasks if that ever became a desired feature in the future.
Edit: another, perhaps better or perhaps worse, is to have an instance method like setCurrentUser ($user) then use setFieldNameAttribute methods so that the controller doesn't have to check the editability of fields, keeping that the model's responsibility. Then you could call the getEditableStateFor methods, which now check for the current user set by the above method (maybe falling back to Auth::user() or throwing a helpful error), inside the setter.

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

Clean way of checking if resource exists and if user can edit it

I have two very common steps that I have to repeat in almost every CRUD method in my Controllers. I have my Users split into 2 groups ( Users, Administrators ). Now Users can edit, update and delete only their own entries while admins can do all the CRUD operations.
The second piece of code I find my self writing every time is checking if the resource exist which is repetitive and somewhat annoying.
Here is what I attempted:
<?php
class BaseController extends Controller
{
// Received Eloquent model each model has user_id field
public function authorize($resource)
{
// Check if currently logged in users id matches user_id
// value of the resource
if($resource->user_id !== CurrentUser::getUser()->id)
{
// Users id does not match with resource user_id check if user is admin
if(!CurrentUser::getGroup() === 'Admin')
{
// The id's do not match and user is not admin redirect him back to root
Session::flash('error', 'You cannot edit this resource');
return Redirect::to('/');
}
}
}
}
class CarController extends BaseController
{
public function edit($id)
{
// Attempt to find the resource
$car = Car::find($id);
// Check if found
if(!$car)
{
// Resource was not found
Session::flash('error', 'Resource was not found');
return Redirect::to('/cars');
}
// First check if user is allowed to edit the resource
// this however does not work because returned Redirect is simply ignored I would
// have to return boolean and then check it but...
$this->authorize($car);
// ... rest of the code
}
}
This would not be a problem if I had 3-4 methods but I have some 6-10 methods and as you can see this part takes some 20 lines of code add that 6-10 times not to mention it's repetitive to the point where it get's annoying.
I have tried to solve the problem using a filter but the problem is that I can pass the id to the filter but not get it to work in a way that I would pass the model as well.
There has to be a cleaner way to implement all this. I'm somewhat happy with authorize function/process but it would be awesome not having to call is every time possibly having some filter and each controller would define global variable/array of methods that require authorization.
As for checking if record was found I was hoping maybe a filter could be done to catch all RecordNotFound exceptions and redirect back to controllers index route with a message.
You can use findOrFail() and catch the exception in your BaseController and you also have two options:
try
{
$post = $this->post->findOrFail($id);
return View::make('posts.show', compact('post'));
}
catch(ModelNotFoundException $e)
{
return Redirect::route('posts.index');
}
Or
$post = $this->post->findOrFail($id);
return View::make('posts.show', compact('post'));
And a exception handler returning back to your form with the input:
App::error(function(ModelNotFoundException $exception)
{
return Redirect::back()->withErrors()->withInput();
});
Note that those are just examples, not took from your code.

Resources