Force Laravel model to check ownership before fetching - laravel

I am unsure how to accomplish this, but I would like to have either a middleware, trait or whatever that would force an ownership check on specified models. For instance, I would like to do this:
Posts::all()
But instead of getting all posts, I would like to get only the posts of the current logged user. Of course I could add a ::where(['user_id' => auth()->user()->id]) but I would like to manage that on a lower, more secure level.
Basically, I would like to force this where condition in my model, if possible.

You probably want to write a scope for your model class.
For instance (in Post.php):
/**
* Example usage:
* Post::ownedByCurrentUser()->get();
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOwnedByCurrentUser($query) {
return $query->where([
'user_id' => auth()->user()->id,
]);
} // end scopeOwnedByCurrentUser()
You could go a step further and make this more flexible with a separate scope allowing you to query ANY user's posts:
/**
* Example usage:
* // get all posts belonging to a user
* Post::owner(auth()->user()->id)->get();
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param int $userId User ID of owner
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOwner($query, int $userId) {
return $query->where([
'user_id' => $userId,
]);
} // end scopeOwner()
They're flexible since you can add extra query bits after them:
Post::owner(1234)->orderBy('date')->whereModified(null); // etc
Use your imagination. :-)

Related

Is possible in Laravel Nova 4 use a field of nested relation in search fields?

I have the following db:
Showcases (n to 1) Workers (1 to 1) Users
I need in the showcase resource section find showcase by user's name. In the Nova's documentation they explains that is possible search by related field like this:
public static $search = [
'id', 'author.name'
];
If I try 'worker.user.name' it doesn't works. Any idea?
You'll have to define it on your Laravel Model, otherwise it wont work.
use Laravel\Nova\Query\Search\SearchableRelation;
/**
* Get the searchable columns for the resource.
*
* #return array
*/
public static function searchableColumns()
{
return ['id', new SearchableRelation('author', 'name')];
}
You can use this package titasgailius/search-relations.
<?php
namespace App\Nova\Resources\OrderManagement;
use App\Nova\Resources\Resource;
use Titasgailius\SearchRelations\SearchesRelations;
class Showcase extends Resource
{
use SearchesRelations;
/**
* The relationship columns that should be searched.
*
* #var array
*/
public static $searchRelations = [
'worker.user' => ['name'],
];
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [];
}
}
*Assuming you have set the belongsTo relationships properly in your models.

Having some issue with laravel collection and a callback function

pro's, amateurs and php enthousiasts.
I am working on a Laravel task wicht envolved dynamic data, collections and graphs.
In order to see what is wrong i kinda need some help, since I can't see it clearly anymore. I should pause and work on something else but this is a bottleneck for me.
I have a collection called orders.
in those orders I have grouped them by date. So far so good. Example below is a die and dump.
Exactly what i need in this stage.
"2022-01-29" => Illuminate\Support\Collection {#4397 ▶}
Now comes the mweh part.
I have a class called Datahandler
in that class I have three methods in it
simplified version of it:
Abstract Class DataHandler
{
/**
* Handles the conversion to dataset for the chart
*
* #param string $label
* #return void
*/
public function handle(string $label):void
{
$this->chart->addDataset($this->process->map(
$this->bind([$this, 'dataLogic'])
)->toArray()
, $label
);
}
/**
* Binds callbacks for the Handler of the class
*
* #param array $callable
* #return Closure
*/
function bind(array $callable): Closure
{
return function () use ($callable) {
call_user_func_array($callable, func_get_args());
};
}
/**
* Defines the fields I need to return to the collection
*
* #param Collection $group
* #return array
*/
#[Pure] #[ArrayShape(['total' => "int"])]
protected function dataLogic(Collection $group): array
{
return [
'total' => $group->count()
];
}
}
So in the handle function you can see I am binding ($this->bind()) my $this->process (collection data) to a callback ( $this->dataLogic() ). The protected function dataLogic is protected because every child of this Abstract class needs to have it's own logic in there.
so this function is being executed from within the parent, this is good cause it should be the default behaviour unless the child has the same function. If i do a var_dump on $group in method dataLogic I also have the correct value and the $group->count() also presents the corrent count of said data.
however the return is null. I am not so well trained in the use of callbacks, has anyone an idea on what is going wrong or even a better solution then the one I am trying to create?
forgot to mention the result of my code:
"2022-01-29" => null
It should be
"2022-01-29" => 30
Kind Regards,
Marcel
I solved it by doing the following.
I completely removed the bind function and handled my function as a callable for it got the needed solution, is there a better one, sure there is somewhere so any ideas are still welcome, but for now i can continue further.
Abstract Class DataHandler
{
/**
* Handles the conversion to dataset for the chart
*
* #param string $label
* #return void
*/
public function handle(string $label):void
{
$this->chart->addDataset($this->process->map($this->dataLogic()
)->toArray()
, $label
);
}
/**
* Defines the fields I need to return to the collection
*
* #return array
*/
#[Pure] #[ArrayShape(['total' => "int"])]
protected function dataLogic(): callable
{
return function ($group) {
return $group->count();
};
}
}

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]

Laravel 5.3 Auth block user

I have a question, I'm currently developing a little site with Laravel 5.3 and I'm using the Basic Auth from them for users to register and login.
Now I want the following: Everybody can register and login, but if I click on a button (as an admin), I can "block" one specific user (for example if he did something not allowed), I don't completely delete the row in the database, but somehow make sure that if the user tries to login he get's a message saying something like "you can't login any more, your account is blocked, contact admin for more info" or something similar. The question is: Whats the best way to do this? I didn't find something built in, correct me if I'm wrong...
Ofcourse, I could just alter the users table and add a column called "blocked", set to false normally, then with the button, set it to true and then when logging in somehow checking for this value and (if it's true) showing this message and not allowing log in. Is this the best way to do this? If yes, where would I have to check for this value and how can I show the message then? If not, whats the better way?
I would do what you're suggesting - use a blocked or active column to indicate if the user should be able to log in. When I've done something similar in the past, to check this value upon login, I moved the out-of-the-box login function into my LoginController and added to it a bit. My login method now looks like this:
/**
* Handle a login request to the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function login(Request $request)
{
$this->validateLogin($request);
$user = User::where('email', $request->email)->firstOrFail();
if ( $user && !$user->active ) {
return $this->sendLockedAccountResponse($request);
}
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
I also added these functions to handle users who weren't active:
/**
* Get the locked account response instance.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
protected function sendLockedAccountResponse(Request $request)
{
return redirect()->back()
->withInput($request->only($this->loginUsername(), 'remember'))
->withErrors([
$this->loginUsername() => $this->getLockedAccountMessage(),
]);
}
/**
* Get the locked account message.
*
* #return string
*/
protected function getLockedAccountMessage()
{
return Lang::has('auth.locked')
? Lang::get('auth.locked')
: 'Your account is inactive. Please contact the Support Desk for help.';
}
You can use soft deleting feature.
In addition to actually removing records from your database, Eloquent can also "soft delete" models. When models are soft deleted, they are not actually removed from your database. Instead, a deleted_at attribute is set on the model and inserted into the database. If a model has a non-null deleted_at value, the model has been soft deleted.
step1:
add new field to the User table called ‘status’ (1:enabled, 0:disabed)
step2:
to block the web login , in app/Http/Controllers/Auth/LoginController.php add the follwoing function:
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(\Illuminate\Http\Request $request)
{
$credentials = $request->only($this->username(), ‘password’);
return array_add($credentials, ‘status’, ‘1’);
}
Step3:
to block the user when using passport authentication ( token ) , in the User.php model add the following function :
public function findForPassport($identifier) {
return User::orWhere(‘email’, $identifier)->where(‘status’, 1)->first();
}
refer to this link ( tutorial) will help you : https://medium.com/#mshanak/solved-tutorial-laravel-5-3-disable-enable-block-user-login-web-passport-oauth-4bfb74b0c810
There is a package which not only blocks users but also lets you to monitor them before making a decision to block them or not.
Laravel Surveillance : https://github.com/neelkanthk/laravel-surveillance
Solved: this link ( tutorial) will help you : https://medium.com/#mshanak/solved-tutorial-laravel-5-3-disable-enable-block-user-login-web-passport-oauth-4bfb74b0c810
step1:
add new field to the User table called ‘status’ (1:enabled, 0:disabed)
step2:
to block the web login , in app/Http/Controllers/Auth/LoginController.php add the follwoing function:
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(\Illuminate\Http\Request $request)
{
$credentials = $request->only($this->username(), ‘password’);
return array_add($credentials, ‘status’, ‘1’);
}
Step3:
to block the user when using passport authentication ( token ) , in the User.php model add the following function :
public function findForPassport($identifier) {
return User::orWhere(‘email’, $identifier)->where(‘status’, 1)->first();
}
Done :)

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

Resources