Laravel Gate method not being called - laravel

In my Laravel 5.4 application users can create Projects and then Posts inside those projects.
I'm trying to prevent users from creating or editing posts inside a project they don't have access to.
To do this I implemented a Gate as explained here: https://laravel.com/docs/5.4/authorization#gates
The gate checks if a user is the owner of the project.
Gate::define('create-post', function ($user, $project) {
Log::info($project) // !!! Never gets called
return $project->owner_id == $user->id;
});
On the PostController I call Gate::denies passing the project as an argument
if (Gate::denies('create-post', $project)) {
abort(403);
}
The problem is the code I defined for the gate never gets called. Instead it always returns false and goes to the 403 error.
However, the code does get called if I don't pass the project as an argument but that makes it useless.
I also want to add that in this case I cannot use a Policy because the create method only takes one argument ($user) and if I try to pass the $project it fails the same way it does with the Gate.
Is this a bug? Is there another, better way to implement this funcionality? Thanks.

I have the same issue. It seems something wrong happens when the second parameter in Gate::allows() is an eloquent model.
If you pass in denies() any other variable (even object, but not eloquent model), your Log::info() will work.
I wasted the whole day with it and switched to $user->can()

I was able to fix it by using a policy. I created the following method in ProjectPolicy:
public function createOrEditPosts(User $user, Project $project)
{
return $project->owner_id == $user->id;
}
Then from PostController I call:
$this->authorize('createOrEditPosts', $project));
I still don't know why the gate approach doesn't work.

I had the same issue.
I replaced Gate::denies() with Gate::allows().
I'm not sure why but this worked for me. The policy framework is a little bit tricky to be honest

first, check to log in to your project then gate work because of the gate when work that you log in to your project.

Related

Laravel: How to use function of relation

I'm facing a situation where a I have to call a function from the controller of a relation instance. For better explanation I will write an example below
I have a Article controller in which I have a preview() function.
A User can have multiple Article.
Let's say that the preview() function parse a text and replace special pieces of text by the user's name.
So my function will looks like this
//In ArticleController
public function preview(Article $article , User $user){
return str_replace("username", $user->name , $article->text);
}
But for a specific situation I want to display a preview of the article when I list all the users
So in UserController
public function index(){
foreach( User::all() as $user){
echo $user->articles[0]->preview( ... );
}
}
Obviously this piece of code will not work.
But I'm more looking of the way to proceed when I face this kind demand.
Should I create a Repository? Use this preview() function somewhere else? Or Is it just a bad practice to do that? What's the best approach or way of thinking when we face this?
Or maybe I'm just missing something important in Laravel's ORM. :/
I assume Article is a model. So you have to add hasMany relation to User (user has many articles). Inside article you have to add preview function. In this case you will be able to find $user->article (or user->articles) and run ->preview function. This is the easiest solution I guess.
You can also add custom attribute like getPreviewAttribute and append it to article model. This way you would have $user->article->preview.

Where to put user related get functions

I have a problem I am working on for quite a while now.
I am using Laravel 5.0 and have relationships set up:
A user can have many itineraries
A itinerary can have many destinations
A user has many destinations through itineraries
Now I am trying to set up a function which I need in many controllers. I have this function in User.php:
public function last_destination() {
return $last_destination =
\App\Destination::where('itinerary_id', auth()->user()->active_itinerary_id)
->orderBy('order_index', 'DESC')
->first();
}
When I try to retrieve the last destination, it works fine, but when I pass the last destination as a variable to a view, it throughs me the error: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
Should I put this function in another class? If so where should I put it to still be able to call it as $user->last_destination()?
I appreciate any help!
Thanks a lot!
Sebastian
You are most likely trying to call $user->last_destination somewhere, as you would if that method returned a Relation.
Try using $user->last_destination() instead. Or updating your method to return a Relation instance.

Search object by slug and not by id

I'm a relative beginner with Laravel (using version 5.2.3) and have been working through tutorials on Laracasts and then doing a bit of my own experimenting.
I successfully set up a route that fetches an item from a table by its ID, as shown below
Route::get('/wiseweasel/{id}', 'WiseweaselController#singleArticle');
For simplicity, the controller simply dd's the article
public function singleArticle($id)
{
$article = ww_articles::find($id);
dd($article);
}
This works absolutely fine - I visit eg /wiseweasel/2 and get the contents of the record with id2.
So, I then wanted to use the slug field from the record instead of the id. Since I know the ID method was working, I've tried just modifying this route and controller (also tried creating anew, neither worked) So I now have:
Route::get('/wiseweasel/{slug}', 'WiseweaselController#singleArticle');
and
public function singleArticle($slug)
{
$article = ww_articles::find($slug);
dd($article);
}
The slug for the second record is "secondarticle". So, visiting the url /wiseweasel/secondarticle, I would expect to see the same record as previously dd'd out. Instead, I end up with null.
Even more oddly, using the original id route (/wiseweasel/2) still returns the record... when I have removed all trace of this from the routes and controller, so I would expect this to fail...
This is making me wonder if this could be some odd caching issue? I've tried
php artisan route:clear
in case the route was being cached. I've also tried restarting both Apache and MySql (I'm using XAMMP for both).
Still no luck though... not sure if I've misunderstood how something works or what's going on... so if anyone has any suggestions as to what I might have done wrong, or anything to try, I would be very grateful! :)
You also have the option of using Route Model Binding to take care of this and inject the resolved instance into your methods.
With the new implicit Route Model Binding you can tell the model what key it should use for route binding.
// routes
Route::get('/wiseweasel/{article}', 'WiseweaselController#singleArticle');
// Article model
public function getRouteKeyName()
{
return 'slug';
}
// controller
public function singleArticle(Article $article)
{
dd($article);
}
Laravel Docs - Route Model Binding
Laravel won't automatically know that for slug it should search record in different way.
When you are using:
$article = ww_articles::find($slug);
you are telling Laravel - find record of www_articles by ID. (no matter you call this id $slug).
To achieve what you want change:
$article = ww_articles::find($slug);
into
$article = ww_articles::where('slug', $slug)->first();
This will do the trick (for slug put the name of column in table in database). Of course remember that in this case slug should be unique in all records or you won't be able to get all the slugs.
Maybe it's a bit late for the answer but there is another way to keep using find method and use slug as your table identifier. You have to set the protected $primaryKey property to 'slug' in your model.
class ww_articles extends Model
{
protected $primaryKey = 'slug';
...
}
This will work because find method internally uses the getQualifiedKeyName method from Model class which uses the $primaryKey property.
If you have both routes like this
Route::get('/wiseweasel/{id}', 'WiseweaselController#singleArticle');
Route::get('/wiseweasel/{slug}', 'WiseweaselController#singleArticle');
it will always use the first one. Obviously, there is no id 'secondarticle', so it returns null (although in this case it doesn't matter, they both point to the same method).
The reason is route will search through possible routes till it finds a matching, which is always the one with {id}. Why? You're not telling Route that {id} must match an integer!
You can make sure {id} is understood as an integer, however I suggest using urls like this is a better option
/wiseweasel/{id}/{slug?}
Another suggestion. Do not use names such as xx_articles for a model, but Article instead. This way you can use the new implicit route binding. So using implicit route binding your url would look like this (assuming your model is called Article)
Route::get('/wiseweasel/{article}', 'WiseweaselController#singleArticle');

Laravel created_by/modified_by relations

I was trying to get this working in a typical belongsTo relation. However it keeps saying that the column is not set in the model, even if looking in the actual database it is there.
I have tried to look at the source code as well as try many approaches to bypass this issue, however nothing seems to do anything.
public function modifiedBy()
{
return $this->belongsTo('\Modules\Users\Model\User', 'modified_by');
}
public function createdBy()
{
return $this->belongsTo('\Modules\Users\Model\User', 'created_by');
}
This is the code inside the model, I use PSR-0 to define modules, better splitting up logic (no issues with that) but using this it would give an error of
Undefined property: \Modules\Module\Model\CurrentModel::$modified_by
This is coming from a seed to push some initial info into the database.
$user = Sentinel::findById(1);
$model = new CurrentModel;
$model->modifiedBy()->associate($user);
$model->save();
This is basically how it goes together, I have tried for some time to figure out what is wrong but I am calling blanks. Any ideas?
Found out a solution. Not a fix though but I would consider this an issue with laravel so I may look into adding it as a bug report (although this could be fixed in laravel 5?).
Basically with modified_by I need to define the column it is using and not let laravel automatically generate it (in order to do this "cleanly"). However the "bug" (only calling it a bug as currently I can only see this as an unintended problem) makes it so you cannot define the column it will be using, you have to let laravel decide it for you.
So I changed the functions to look like this:
public function modifiedby()
{
return $this->belongsTo('\Modules\Users\Model\User');
}
This makes laravel assume the column is modifiedby_id, but by changing my migrations to reflect that there was no more error.

Sentry & Laravel, getting users within a group. changing findAllUsersWithAccess to have pagination

I'm trying to find all users w/ a specific permissions list in Sentry with laravel. The problem is that Sentry::findAllUsersWithAccess() returns an array().
as stated in their github repository i pinpointed their code to be
public function findAllWithAccess($permissions)
{
return array_filter($this->findAll(), function($user) use ($permissions)
{
return $user->hasAccess($permissions);
});
}
right now, it gets all users and filter it out with users with permission list. the big problem would be when I as a developer would get the set of users, it'll show ALL users, i'm developing an app which may hold thousands of users and i only need to get users with sepcific permission lists.
With regards to that would love to use one with a ->paginate() capability.
Any thoughts how to get it without getting all the users.
Why dont you override the findAllWithAccess() method and write your own implementation, which uses mysql where instead of array_filter().
I dont know your project structure and the underlying db schema, so all i can give you atm is the link to the eloquent documentation Querying Relations (whereHas).
In case you dont know where to start: its always a good idea to look at the ServiceProvider (SentryServiceProvider, where the UserProvider, which holds the findAllWidthAccess() method, is registered). Override the registerUserProvider method and return your own implementation of the UserProvider (with the edited findAllWithAccess() method).
Hope that will point you in the right direction.
In Laravel you can do pagination manually on arrays:
$paginator = Paginator::make($items, $totalItems, $perPage);
Check the docs: http://laravel.com/docs/pagination

Resources