Laravel 5.1 - How to send parameter to authorize() function - php

I'm making a real estate application. When the user opens one of his published properties to edit, in the edit page, the form opens like this:
{!! Form::model($property, ['method'=>'PUT', 'route'=>['property.update', 'id'=>$property->id], 'files'=>true]) !!}
As you can see, in the 'route' array I'm sending the named route and the id of the property to be edited. But how do I access that $id in the Request Class?
class EditPropertyRequest extends Request
{
/**
* Determine if the user owns this property and is authorized to make this request.
*
* #return bool
*/
public function authorize($id)
{
return Property::where('user_id', auth()->user()->id)
->where('id', $id)
->exists();
}
}
The error I get is
Missing argument 1 for
App\Http\Requests\EditPropertyRequest::authorize()

This is from doc
public function authorize()
{
$commentId = $this->route('comment');
return Comment::where('id', $commentId)
->where('user_id', Auth::id())->exists();
}
So $this->route('comment'); is a URL parameter Route::post('comment/{comment}');

request('id')
You can use request();

Related

Laravel - How to get a foreign key and pass it to controller

I'm creating a survey and in order to create a question I need to get the survey_id from the survey table and place in it a create a question view in a hidden field in a form and pass it to the question controller.
Since you passe survey_id all you need to the do is use the right model (Question) in the store method, everything else same correct.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$input = $request->all();
Question::create($input); //<==== You need to use Question insteed of Survey
return redirect('survey');
}
You can try below code,i hope this is help you:
$input = $request->all();
$survey = new Survey ();
$survey ->fill($input)->save();
if (isset($input['survey_id']) && !empty($input['survey_id'])) {
$survey_id= $input['survey_id'];
foreach ($survey_id as $index => $value) {
Question::create([
'survey_id' => $survey ->id,
'title' => $value
]);
}
}
#Rachid's comments is right, but its not the reason of the error...
The problem is the resource's route -> controler#action mapping doesn't include any param for the /create route as we can see bellow...
... and your QuestionController::create function needs the param $id.
//...
class QuestionController extends Controller
{
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create($id) // <---- Right here...
//...
So, to bind your foreign key to this resource i'll need to do something like this...
Route::resource('survey.question', 'QuestionController');
This way you'll have your routes mapped sothing like this...
GET /survey/{survey}/question survey.question.index
GET /survey/{survey}/question/create survey.question.create
Then change your template, from:
{!! Form::open(array('action' => 'QuestionController#store', 'id' => 'Question')) !!}
To:
{!! Form::open(array('action' => 'QuestionController#store', 'id' => 'Question'), ['survey' => Request::route('survey')]) !!}
Then you can use your QuestionController::create function like this...
public function create(Survey $survey)
{
return view('question.create')->with('survey', $survey);
}
You can also use parameters in resourceful routes like this:
Route::resource('survey/{survey}/question', 'QuestionController');
Then in your controller you can use:
class QuestionController extends Controller
{
// ...
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create($id)
{
// $id will be the {survey} id from the route parameter.
}
// ...
}
You could as well use route model binding to avoid the manual Survey retrival in the controller:
use App\Survey;
class QuestionController extends Controller
{
// ...
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create(Survey $survey)
{
return view('question.create', compact('survey'));
}
// ...
}
Remember to update the form action as well to include the pameter
Be Careful!
If you register the parameter in the resource like I have shown, methods as show, edit, update and destroy (that accepts the id of the resource to act on in the url) will receive two route parameters, one will be the survey id, the second one would be the question id (your resource).
Keep that in mind!

Laravel Controller Type Hinting

After creating a model with -mcr (php artisan make:model Institution -mrc), the show function in controller was scaffolded as:
/**
* Display the specified resource.
*
* #param \App\Organization\Institution $institution
* #return \Illuminate\Http\Response
*/
public function show(Institution $institution)
{
return view('institutions.show', ['institution' => $institution]);
}
The return view... was inserted by me. I was expecting it to have it populated with the object whose id was sent in the parameters.
/institutions/1
But, after using dd($institution), I verified that it has the ID, not the object.
Shouldn't this variable return me the object?
This is called Route Model Binding. Your route will need to look something like:
Route::get('institutions/{institution}', 'InstitutionController#show');
and then as per your controller
public function show(Institution $institution)
{
return view('institutions.show', compact($institution))
}
You can read more on this here.
I imagine your route had the parameter called {id} rather than {institution}.
Replace the parameter of show function
public function show(Institution $institution)
{
return view('institutions.show', compact($institution))
}
becomes
public function show($id)
{
$institution = App\Institution::findOrFail($id);;
return view('institutions.show', compact('institution'));
}
and in your routes
Route::get('institutions/{id}', 'InstitutionController#show');

Laravel: How to throw 403 if the user enter the ID manually in the route?

Building an app (Blog/posts).
Where only auth users can edit their post(which ofcourse belongs to them only).
For example, Post with an id of 15 belongs to particular user, so if he edits it, the route will be like this
http://localhost:8000/post/15/edit
this is correct.
But when the user enters any other post ID(which doesn't belongs to him) in the route, it shows
http://localhost:8000/post/16/edit
ErrorException (E_NOTICE)
Trying to get property 'user_id' of non-object
How to show unauthorised page in this case?
This is the postController
public function edit($id)
{
$post = Post::find($id);
if(Auth::user()->id == $post->user_id){
return view('post-edit',compact('post'));
}else {
return redirect()->route('home');
}
}
The following code checks if the post exist (which is why you are getting the error Trying to get property 'user_id' of non-object, because it doesn't exist), and then checks if it belongs to the user in the same condition. If it's not valid it aborts with a 403 UNAUTHORIZED error code.
public function edit($id)
{
$post = Post::find($id);
if (empty($post) || Auth::id() != $post->user_id) {
abort(403);
}
else {
return view('post-edit',compact('post'));
}
}
Here is a better version that checks if a post exist, with the specified ID, but also with the right user and throws an exception otherwise:
public function edit($id)
{
$post = Post::whereHas('user', function ($q) {
$q->where('users.id', Auth::id());
})->findOrFail($id);
return view('post-edit',compact('post'));
}
A third version, on the same idea as the 2nd one, but simpler:
public function edit($id)
{
$post = Post::where('user_id', Auth::id())->findOrFail($id);
return view('post-edit',compact('post'));
}
use laravel authorization policy to authorize users.
php artisan make:policy PostPolicy --model=Post
This command will create PostPolicy.php in app\policies dir.
now you'll have to register the policy in AuthServiceProvider. So first add use statements of your policy and model for example.
use App\Post;
use App\Policies\PostPolicy;
then find protected $policies and in that array register your policy. Model followed by policy.
protected $policies = [
Post::class => PostPolicy::class,
];
Now in your Policy that we generated using artisan command. will hold all CRUD related methods. each of them accepts two parameters one is User and second is the model you want to authorize except create method. note that you can modify create or other methods to accept more parameters. it's upto you.
Now for example in your policy let's build logic for update method.
/**
* Determine if the given post can be updated by the user.
*
* #param \App\User $user
* #param \App\Post $post
* #return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
As you can see return Boolean here. you can customize methods as you want. Next in your controller method. where you want to authorize user simply add
public function update(Post $post)
{
$this->authorize('update', $post);
// then your logic here.
}
For create authorization you just pass pass empty class
$this->authorize('create', Post::class);
It accepts two parameters one is authorization method name and second is model.It automatically get's authenticated user and authorize user. if not authorized then throws Illuminate\Auth\Access\AuthorizationException which is 403.
Also if you need to modify the 403 error view you'll need to create 403 blade in
resources/views/errors/403.blade.php
Everything is well documented in laravel doc.
Extra tip if you are going to use some Boolean datatype value for returned from database as tinyint which are 1 or 0. for example
public function view(User $user, Post $post)
{
if(! $post->isPrivate) {
return true;
}
return $user->id === $post->user_id;
}
then make sure to cast that value to Boolean in model to return as true or false. because it was not working for when i deployed my application on shared hosting. Later i found that it was returning as a string. also the version of the database was old.

Pass in an id for Authorization Laravel

In my user controller I have an posts function, which gives access to a sub-resource of users. This is accessed through the /users/{id}/posts endpoint.
I want the pass the $id from the request URL into a UserPolicy method:
public function resource($user, $id)
{
return $user->id === $id;
}
My UserController method:
public function posts(Request $request, $id)
{
$this->authorize('resource', $id);
return response()->json(['events' => []], 200);
}
Is there anyway to do this? I notice that Policy methods seem to ignore anything that isn't an object.
Edit:
I am currently using a helper method for this authorization but would like to move it to my Policy to keep all rules together:
public function authorizeResource($id)
{
if ((int)$id !== (int)$this->auth->user()->id) {
throw new \Exception;
}
}
Laravel needs to know which policy class to use. For that you need to specify the model, in this case passing an array with an instance of user first and then the $id. Laravel uses the spread operator and will inject the $id as a parameter on your callback function.
//UserController.php
public function posts(Request $request, $id)
{
$this->authorize('resource', [User::class, $id]);
return response()->json(['events' => []], 200);
}

Forbidden error when creating nested controller object laravel

I'm folowing a tutorial on https://laracasts.com/series/laravel-5-fundamentals
then I started digging into nested controllers from this tutorial https://www.flynsarmy.com/2015/02/creating-a-basic-todo-application-in-laravel-5-part-4/
I've got a similar logic Project which has one hypothesis.
So I've setup my nested routes
Route::resource('project','ProjectsController');
Route::resource('project.hypothesis','HypothesisController');
Then created a form for adding a hypothesis to a Project
{!! Form::model(new App\Hypothesis, ['route' => ['project.hypothesis.store', $project->id]]) !!}
#include ('hypothesis.form',['submitButtonText'=>'create']);
{!! Form::close() !!}
I also created a HyphothesisRequest class with basic validation rules
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class HyphothesisRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'description' =>'required'
];
}
}
Now according to the above tutorials in my controller i've got
public function store(Project $project, HyphothesisRequest $request)
{
$this->validate($request);
$h = new Hypothesis;
$h->description = $request->description;
$h->project_id = $project->id;
$h->save();
return Redirect::route('project.show', $project->id);
}
The problem is that when the HyphothesisRequest $request is passed as an argument I get an forbidden page from laravel. When I remove this it goes to the desired page but without validation.
I'm at the basic level of this so please be patient :)
Try change
public function authorize()
{
return false;
}
to
public function authorize()
{
return true;
}

Categories