Laravel policy view resource not triggered - laravel

I have this route :
Route::Resource('/additional_role_countries', 'AdditionalRoleCountryController');
In the controller I have this authorization :
class AdditionalRoleCountryController extends Controller
{
public function __construct() {
$this->authorizeResource(AdditionalRoleCountry::class, 'additional_role_countries');
}
}
And finally I have this policy for the model :
class AdditionalRoleCountryPolicy extends AbstractAuthorization
{
use HandlesAuthorization;
public function __construct()
{
var_dump('je suis dans le construct de la policy');
}
/**
* Determine whether the user can view any additional role countries.
*
* #param Member $member
* #return mixed
*/
public function viewAny(Member $member)
{
//
}
/**
* Determine whether the user can view the additional role country.
*
* #param Member $member
* #param AdditionalRoleCountry $additionalRoleCountry
* #return mixed
*/
public function view(Member $member, AdditionalRoleCountry $additionalRoleCountry)
{
var_dump('titi');
return true;
}
/**
* Determine whether the user can create additional role countries.
*
* #param Member $member
* #return mixed
*/
public function create(Member $member)
{
return $this->isAuthorizedBranch($member);
}
}
My problem is that all is running fine for the POST (= create). It sends a 403 error if necessary.
BUT for a GET (= view), it is not working. It even never executes the __construct methods. And it returns directly a 403.
Why might this be happening?
Edit
I tried to change this :
$this->authorizeResource(AdditionalRoleCountry::class, 'additional_role_countries');
with this :
$this->authorizeResource(AdditionalRoleCountry::class);
And now it seems it is working for the post and the get. Does it mean that the second parameter is not mandatory ?

It does not work because you are passing the incorrect route parameter to authorizeResource.
Route::resource('additional_role_countries') generates the following route parameter
additional_role_country
but you are passing
additional_role_countries
The problem is that Laravel can not find the model corresponding to additional_role_countries but still runs a can check but returns false by default (403).
Solution
Removing the second parameter or changing it to additional_role_country should solve the problem.
$this->authorizeResource(AdditionalRoleCountry::class, 'additional_role_country');
$this->authorizeResource(AdditionalRoleCountry::class);
Why it worked for your store request
As you can see here Laravel does not use the second parameter for the methods index, create and store, instead it uses the Classname from the first parameter, in your case AdditionalRoleCountry.

Related

laravel resource function works correct but when i use it manualy doesent work

i am using laravel 5 when i use laravel Route::resource function in my route.php file i can get my model collection inside a parameter of my method like this:
//**web.php** file
Route::resource('factors', 'FactorsController');
//called url localhost:8000/factors/1/edit
//**FactorsController**
/**
* #param Request $request
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function edit(Request $request, Factor $factor)
{
//$factor is a collection of Factor Model that contains id '1' information in factor table
return view('factors.edit', compact('factor'));
}
it is correct and works but when i make a custom url like this:
Route::get('factors/{id}/newEdit', 'FactorsController#newEdit');
i can't get the collection inside method parameters and it returns empty collection like this:
//called url localhost:8000/factors/1/newEdit
1)
//**FactorsController**
/**
* #param Request $request
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function newEdit(Request $request, Factor $factor)
{
return view('factors.newEdit', compact('factor'));
}
$factor is a empty collection of Factor Model but i want my selected row in database. when i use that like this works correct:
2)
//**FactorsController**
/**
* #param Request $request
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function newEdit(Request $request, $id)
{
$factor = Factor::find($id);
return view('factors.newEdit', compact('factor'));
}
but i don't want to call it like 2 i want to call it like 1
thanks for any help.
For model binding, you should have type-hinted variable names match a route segment name :
Route::get('factors/{factor}/newEdit', 'FactorsController#newEdit');
Since the $factor variable is type-hinted as the Factor model and the variable name matches the {factor} URI segment, Laravel will automatically inject the model instance that has an ID matching the corresponding value from the request URI.

Laravel policy not running on update/delete model

I'm trying to make this policy stuff work, and i have followed the documentation. But it doesn't seem like the policy code is not even run.
I have Role model. And i created RolePolicy. The thing i want to do in the policy is to ensure that the role with the ID of 1 never-ever gets updated or deleted.
My RolePolicy looks like this:
<?php
namespace App\Policies;
use App\Models\Role;
use Illuminate\Support\Facades\Response;
class RolePolicy
{
/**
* Determine whether the user can update the model.
*
* #param \App\User $user
* #param \App\Models\Role $role
* #return mixed
*/
public function update(Role $role)
{
return $role->id === 1
? Response::deny('Cannot change super-admin role')
: Response::allow();
}
/**
* Determine whether the user can delete the model.
*
* #param \App\User $user
* #param \App\Models\Role $role
* #return mixed
*/
public function delete(Role $role)
{
return $role->id === 1
? Response::deny('Cannot delete super-admin role')
: Response::allow();
}
}
I even tried to do a dd() inside both delete and update method in the policy, but when i try to delete/update the model with the ID of 1, nothing happens. The dd wont run, nor will the response in the current code above.
I have registered the policy in the AuthServiceProvider where i also have this gate to give the super-admin all the permissions.
<?php
namespace App\Providers;
use App\Models\Role;
use App\Policies\RolePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Role::class => RolePolicy::class
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
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;
});
}
}
Here is also my RoleController method for updating the Role model:
/**
* Edit role
*
* #param Edit $request
* #param Role $role
* #return void
*/
public function postEdit(Edit $request, Role $role)
{
# Validation checks happens in the FormRequest
# Session flash also happens in FormRequest
# Update model
$role->update([
'name' => $request->name
]);
# Sync permissions
$permissions = Permission::whereIn('name', $request->input('permissions', []))->get();
$role->syncPermissions($permissions);
return redirect(route('dashboard.roles.edit.get', ['role' => $role->id]))->with('success', 'Changes saved');
}
Does the gate i use to give all permissions have anything to do with the policy not running? Or what am i doing wrong here?
Thanks in advance if anyone can point me in the right direction.
The User model that is included with your Laravel application includes two helpful methods for authorizing actions: can and cant. The can method receives the action you wish to authorize and the relevant model. For example, let's determine if a user is authorized to update a given Role model:
if ($user->can('update', $role)) {
//
}
If a policy is registered for the given model, the can method will automatically call the appropriate policy and return the boolean result. If no policy is registered for the model, the can method will attempt to call the Closure based Gate matching the given action name.
Via Controller Helpers
In addition to helpful methods provided to the User model, Laravel provides a helpful authorize method to any of your controllers which extend the App\Http\Controllers\Controller base class. Like the can method, this method accepts the name of the action you wish to authorize and the relevant model. If the action is not authorized, the authorize method will throw an Illuminate\Auth\Access\AuthorizationException, which the default Laravel exception handler will convert to an HTTP response with a 403 status code:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Role;
use Illuminate\Http\Request;
class RoleController extends Controller
{
/**
* Update the given role.
*
* #param Request $request
* #param role $role
* #return Response
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Role $role)
{
$this->authorize('update', $role);
// The current user can update the role...
}
}
The Gate::before method in the AuthServiceProvider was the problem. Removed this and rewrote the permissions, policies and some gates to get the error messages from the policies.
Decided to give the role super-admin the permission * and check for this with $user->can() and middleware .....->middlware('can:*') and everything is working now.

Laravel only retrieve Model Records if they belong to authenticated user

I'm using Laravel 5.4 and have a Model called Order.
To test things I've created two users and two Orders, each user having one Order.
I've just seen that I'm able to retrieve the order of someone who is not my current user. I'm retrieving a list of user's own orders using Auth::user()->orders. But in order to show the details of a specific order I do this:
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$order = CustomerOrder::findOrFail($id)->with('products')->get();
return view('order.show')
->with('order', $order);
}
What am I missing out here? Is there a middleware or something to tell the application to only allow access to orders associated with the authenticated user?
Edit: So I've tried to do it using a Policy OrderPolicy (CRUD).
The view() fucntion of the Policy:
/**
* Determine whether the user can view the customerOrder.
*
* #param \App\User $user
* #param \App\CustomerOrder $customerOrder
* #return mixed
*/
public function view(User $user, CustomerOrder $customerOrder)
{
return $user->id === $customerOrder->user_id;
}
And I've registered it in the AuthServiceProvider.php:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Adress::class => AdressPolicy::class, //Doesn't work either
Order::class => OrderPolicy::class
];
I can still check the Order for another user.
You have a few options. The best option in my option is the use Policies. The documentation for this can be found here:
https://laravel.com/docs/5.4/authorization
Alternatively do could do something like:
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$user = request()->user();
$order = $user->orders()->with('products')->find($id)->get();
return view('order.show', compact('order'));
}
With an orders relationship function on your user model.
Updated Reply
With the policy you gave, and with your resource route, you should be able to do:
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show(CustomerOrder $order)
{
$this->authorize('view', $order);
return view('order.show', compact('order'));
}
Another way would be to use the defined relationship and tell it to only retrieve the one with id $id. Like this:
$customerOrder = auth()->user()->orders()->with('products')->find($id);
If you want to get orders which belong to authenticated user, do this:
CustomerOrder::where('user_id', auth()->user()->id)->with('products')->find($id);
Remember,
first you create policy.
second you register it.
third you use something like $this->authorize('view', $order); in your normal controller.
You are missing the third step, you can find the doc here:
[https://laravel.com/docs/5.8/authorization#authorizing-actions-using-policies][1]

Validator event dispatched before Entity validation starts

Question
Is it possible in Symfony 2.8+ / 3.x+ to dispatch event before starting entity validation?
Situation:
Let's say we have 100 entities, they have #LifeCycleCallbacks, they have #postLoad Event that do something, but the result of this is only used for valiation of Entity, in 99% of situations result of #postLoad is unimportant for system. So if we have hundrets or thousands of Entities fetched from DB there will be a lot of machine-cycles lose for unimportant data.
It would be nice to run some kind of event, that will run method, that will populate that data for that specific Entity, just before validations starts.
instead of:
$entity->preValidate();
$validator = $this->get('validator');
$errors = $validator->validate($entity);
we could have:
$validator = $this->get('validator');
$errors = $validator->validate($entity);
And in validate() situation, preValidate() will be dispatched autmaticly as Event (of course with check if Entity does have such method).
CaseStudy:
I have a system that stores pages/subpages as entities. There can be 10 or 10000 pages/subpages
Pages/subpages can have file.
Entities stores only files names (becouse we can't store SplFileInfo - resource serialization restriction)
While Entity->file property is type of string, when I want to make it to be instance of File (so we can do validation of type File) I have something like:
/**
* #postLoad()
*/
public function postLoad()
{
//magicly we get $rootPath
$this->file = new File($rootPath . '/' . $this->file);
}
/**
* #prePersist()
* #preUpdate()
*/
public function preSave()
{
if ($this->file instance of File)
$this->file = $this->file->getFilename();
}
}
Ok, but postLoad() will CHANGE the property, and Doctrine will NOTICE that. So in next
$entityManager->flush()
ALL entities will be flushed - even if preSave() will change it back to be just string as it was before.
So if I have any other entity, let's say TextEntity, and I want to remove it
$entityManager->remove($textEntity);
$entityManager->flush();
All other Entities that are somehow changed (change was noticed by Doctrine), are flushed, no matter if value of file property is the same as in DB (and change was only temporary).
It will be flushed.
So we have hundrets, or thousends of pointless sql updates.
Btw.
1. ->flush($textEntity) will throw Exception, becouse ->remove($textEntity) have already "deleted" that entity.
2. Entity property ->file must be of type File for Assert/File, becouse FileValidator can only accept values of File or absolute-path-to-file.
But I will NOT store absolute-path-to-file, becouse it will be completly different on Dev, Stage, and Production environments.
This is problem that occured when I tried to make file uploading as it was described in Symfony cookbook http://symfony.com/doc/current/controller/upload_file.html.
My solution was to, in postLoad(), create File instance in property that is not Doctrine column, and is anoted to have assertion, etc.
That works, but the problem of useless postLoad()s stays, and i thought about events. That could be elastic, and very elegant solution - instead of controllers getting "fat".
Any one have better solution? Or know how to dispatch event if ->validate() happends?
Hello Voult,
Edit: first method is deprecated in symfony 3 as the thread op mentioned in a comment. Check the second method made for symfony 3.
Symfony 2.3+,Symfony < 3
What I do in this cases, since symfony and most other bundles are using parameters for service class definition, is to extend that service. Check the example below and for more information on extending services check this link
http://symfony.com/doc/current/bundles/override.html
First you need to add a some marker to your entities that require pre-validation. I usually use interfaces for stuff like this something like
namespace Your\Name\Space;
interface PreValidateInterface
{
public function preValidate();
}
After this you extend the validator service
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Validator;
class MyValidator extends Validator //feel free to rename this to your own liking
{
/**
* #inheritdoc
*/
public function validate($value, $groups = null, $traverse = false, $deep = false)
{
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
return parent::validate($value, $groups, $traverse, $deep);
}
}
And final step, you need to add the class value parameter to your 'parameters' config block in config.yml, something like this:
parameters:
validator.class: Your\Name\Space\MyValidator
This is the basic idea. Now you can mix end match this idea with whatever you want to achieve. For instance instead of calling a method on the entity (I usually like to keep business logic outside of my entities), you can look for the interface and if it is there you can launch a pre.validate event with that entity on it, and use a listener to do the job. After that you can keep the result from parent::validate and also launch a post.validate event. You see where i'm going with this. You basically can do whatever you like now inside that validate method.
PS: The example above is the easy method. If you want to go the event route, the service extension will be harder, since you need to inject the dispatcher into it. Check the link I provided at the beginning to see the other way to extend a service and let me know if you need help with this.
For Symfony 3.0 -> 3.1
In this case they managed to make it hard and dirtier to extend
Step 1:
Create your own validator something like this:
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception;
use Symfony\Component\Validator\MetadataInterface;
use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class myValidator implements ValidatorInterface
{
/**
* #var ValidatorInterface
*/
protected $validator;
/**
* #param ValidatorInterface $validator
*/
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
/**
* Returns the metadata for the given value.
*
* #param mixed $value Some value
*
* #return MetadataInterface The metadata for the value
*
* #throws Exception\NoSuchMetadataException If no metadata exists for the given value
*/
public function getMetadataFor($value)
{
return $this->validator->getMetadataFor($value);
}
/**
* Returns whether the class is able to return metadata for the given value.
*
* #param mixed $value Some value
*
* #return bool Whether metadata can be returned for that value
*/
public function hasMetadataFor($value)
{
return $this->validator->hasMetadataFor($value);
}
/**
* Validates a value against a constraint or a list of constraints.
*
* If no constraint is passed, the constraint
* {#link \Symfony\Component\Validator\Constraints\Valid} is assumed.
*
* #param mixed $value The value to validate
* #param Constraint|Constraint[] $constraints The constraint(s) to validate
* against
* #param array|null $groups The validation groups to
* validate. If none is given,
* "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validate($value, $constraints = null, $groups = null)
{
//the code you are doing all of this for
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
//End of code
return $this->validator->validate($value, $constraints, $groups);
}
/**
* Validates a property of an object against the constraints specified
* for this property.
*
* #param object $object The object
* #param string $propertyName The name of the validated property
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validateProperty($object, $propertyName, $groups = null)
{
$this->validator->validateProperty($object, $propertyName, $groups);
}
/**
* Validates a value against the constraints specified for an object's
* property.
*
* #param object|string $objectOrClass The object or its class name
* #param string $propertyName The name of the property
* #param mixed $value The value to validate against the
* property's constraints
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
{
$this->validator->validatePropertyValue($objectOrClass, $propertyName, $value, $groups);
}
/**
* Starts a new validation context and returns a validator for that context.
*
* The returned validator collects all violations generated within its
* context. You can access these violations with the
* {#link ContextualValidatorInterface::getViolations()} method.
*
* #return ContextualValidatorInterface The validator for the new context
*/
public function startContext()
{
$this->validator->startContext();
}
/**
* Returns a validator in the given execution context.
*
* The returned validator adds all generated violations to the given
* context.
*
* #param ExecutionContextInterface $context The execution context
*
* #return ContextualValidatorInterface The validator for that context
*/
public function inContext(ExecutionContextInterface $context)
{
$this->validator->inContext($context);
}
}
Step 2:
Extend Symfony\Component\Validator\ValidatorBuilder something like this:
namespace Your\Name\Space;
use Symfony\Component\Validator\ValidatorBuilder;
class myValidatorBuilder extends ValidatorBuilder
{
public function getValidator()
{
$validator = parent::getValidator();
return new MyValidator($validator);
}
}
You need to override Symfony\Component\Validator\Validation. This is the ugly/dirty part, because this class is final so you can't extend it, and has no interface to implement, so you will have to pay attention to in on future versions of symfony in case backward compatibility is broken. It goes something like this:
namespace Your\Name\Space;
final class MyValidation
{
/**
* The Validator API provided by Symfony 2.4 and older.
*
* #deprecated use API_VERSION_2_5_BC instead.
*/
const API_VERSION_2_4 = 1;
/**
* The Validator API provided by Symfony 2.5 and newer.
*/
const API_VERSION_2_5 = 2;
/**
* The Validator API provided by Symfony 2.5 and newer with a backwards
* compatibility layer for 2.4 and older.
*/
const API_VERSION_2_5_BC = 3;
/**
* Creates a new validator.
*
* If you want to configure the validator, use
* {#link createValidatorBuilder()} instead.
*
* #return ValidatorInterface The new validator.
*/
public static function createValidator()
{
return self::createValidatorBuilder()->getValidator();
}
/**
* Creates a configurable builder for validator objects.
*
* #return ValidatorBuilderInterface The new builder.
*/
public static function createValidatorBuilder()
{
return new MyValidatorBuilder();
}
/**
* This class cannot be instantiated.
*/
private function __construct()
{
}
}
And last step overwrite the parameter validator.builder.factory.class in your config.yml:
parameters:
validator.builder.factory.class: Your\Name\Space\MyValidation
This is the least invasive way to do it, that i can find. Is not that clean and it could need some maintaining when you upgrade symfony to future versions.
Hope this helps, and happy coding
Alexandru Cosoi

How to extend the Symfony2 Debug Toolbar with custom data?

I want to extend the Symfony2 Debug Toolbar with my own custom data.
I have a service where I want to log specific method calls and then display them in the web debug toolbar.
I read the cookbook article, but it's not very helpful.
I created my own DataCollector class:
class PermissionDataCollector extends DataCollector
{
private $permissionCalls = array();
private $permissionExtension;
public function __construct(PermissionExtension $permissionExtension)
{
$this->permissionExtension = $permissionExtension;
}
/**
* Collects data for the given Request and Response.
*
* #param Request $request A Request instance
* #param Response $response A Response instance
* #param \Exception $exception An Exception instance
*
* #api
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->permissionCalls = $this->permissionExtension->getPermissionCalls();
$this->data = array(
'calls' => $this->permissionCalls
);
}
public function getPermissionCallsCount()
{
return count($this->permissionCalls);
}
public function getFailedPermissionCallsCount()
{
return count(array_filter($this->permissionCalls, array(&$this, "filterForFailedPermissionCalls")));
}
private function filterForFailedPermissionCalls($var)
{
return $var['success'];
}
/**
* Returns the name of the collector.
*
* #return string The collector name
*
* #api
*/
public function getName()
{
return 'permission';
}
}
The PermissionExtension logs all calls and then I want to retrieve this array of calls in
PermissionDataCollector.
And a template just outputting {{ collector.permissionCallsCount }}.
The section gets displayed in the in the toolbar but it just shows a 0 which is wrong.
I'm not sure if I'm even doing this right, because the documentation lacks this section. I'm using Symfony 2.1
Has anybody extended the toolbar with custom data?
ah great! It works. I basically need to refer to $this->data all the time.
The reason for this that ->data is used by the Symfony\Component\HttpKernel\DataCollector\DataCollector and serialized (see DataCollector::serialize).
This is later stored (somehow, I don't know where, but it is later unserialized). If you use own properties the DataCollector::unserialize just prunes your data.
See https://symfony.com/doc/current/profiler/data_collector.html#creating-a-custom-data-collector
As the profiler serializes data collector instances, you should not store objects that cannot be serialized (like PDO objects) or you need to provide your own serialize() method.
Just use $this->data all the time, or implement your own \Serializable serializing.

Resources