I have update and store method like this
public function update(ContactRequest $request)
{
if (Auth::user()->can('edit_contact'))
$request->update();
else
return $this->accessDenied();
}
public function store(ContactRequest $request)
{
if (Auth::user()->can('add_contact'))
$request->store();
else
return $this->accessDenied();
}
and authorize in FormRequest class
public function authorize()
{
return \Gate::allows('test', $this->route('contact'));
}
I want to pass permission name to authorize method like this:
public function authorize($permissionName)
{
if (Auth::user()->can($permissionName))
return \Gate::allows('test', $this->route('contact'));
}
and in controller like this
public function update(ContactRequest $request)
{
$request->update('edit_contact');
}
public function store(ContactRequest $request)
{
$request->store('add_contact');
}
You have 3 options:
Change your authorization method to this:
public function authorize()
{
return $this->user()->can(
$this->route()->getActionMethod() === 'store'
? 'add_contact'
: 'edit_contact'
)
&& \Gate::allows('test', $this->route('contact'));
}
Make your authorize method of request return true and check authorization by defining another gate an call it on your controller:
public function authorize()
{
return true;
}
Gate::define('modify_contact', function ($user, $permissionName) {
return $user->can($permissionName)
&& $user->can('test', $request->route('contact'));
});
public function update(ContactRequest $request)
{
Gate::authorize('modify_contact', 'edit_contact');
//...
}
public function store(ContactRequest $request)
{
Gate::authorize('modify_contact', 'add_contact');
//...
}
Define and use policy the same way and pass your arguments to it.
There is no direct way of passing argument to authorize method of form request, but you can do the implementation this way:
public function authorize()
{
$method = Request::method();
if($method == 'post') {
$permission = 'add_contact';
} elseif($method == 'put') {
$permission = 'edit_contact';
}
if (Auth::user()->can($permission))
return \Gate::allows('test', $this->route('contact'));
}
If you are using laravel's default post, put routes then this will help you out.
It is better to make two different Requests for store and update, anyway you need to check some values depended on action.
So you can user default laravel's policy approach for Resource controllers and not use Request::authorize for authorization logic.
Laravel policy controller helpers
Related
I have a method that accepts a request
public function createUser(Request $request)
{
...
}
I want to call it from another method
public function someMethod(){
$array = [...];
return $this->createUser($array); <----
}
and how can I pass a new request to it with the array I need?
How about instead of trying to call a controller method, you move the logic to create a user to a service class and then use the service class in your createUser and someMethod methods?
UserService.php
class UserService
{
public function __construct() { }
public function createUser(array $userData)
{
// TODO use $userData to create a user here
return $newUser;
}
}
SomeController.php
public function createUser(Request $request)
{
$this->userService->createUser($request->all());
}
public function someMethod(){
$array = [...];
return $this->userService->createUser($array);
}
I have update method like this
public function update(Contact $contact)
{
$this->authorize('ownItems', $contact);
......
}
and ContactPolicy :
public function ownItem(User $user,Contact $contact)
{
return true;
}
It work correctly but when I replace Contcact to ContactRequest in my update method
show me this :
403 This action is unauthorized.
update method :
public function update(ContactRequest $contact)
{
$this->authorize('ownItems', $contact);
.......
}
authorize method in ContactRequest:
public function authorize()
{
return true;
}
ContactRequest is a laravel Request class instance
public function update(ContactRequest $request,Contact $contact)
{
$this->authorize('ownItems', $contact);
.......
}
You misspelled method name in $this->authorize('ownItems', $contact);, it should be "ownItem"
UPD
ContactRequest is probably instance of Illuminate\Http\Request but authorize method waiting for Model instance, if you do not have model identifier in yout request. first you should to find model: $model = Contact::find($contact->input('id')) and than check your policy with $this->authorize('ownItems', $model)
I am seeing some behaviour. I can't explain when accessing user data via the Auth facade in Laravel class. Here's an extract of my code:
private $data;
private $userID;//Set property
function __construct()
{
$this->middleware('auth');//Call middleware
$this->userID = Auth::id();//Define property as user ID
}
public function index() {
return view('');
}
public function MyTestMethod() {
echo $this->userID;//This returns null
echo Auth::id();//This works & returns the current user ID
}
I am logged in and have included use Illuminate\Support\Facades\Auth; in the class thus the code works, but only when accessing Auth in methods - else it returns a null value.
Most odd, I can't work out what is causing this. Any thoughts much appreciated as ever. Thanks in advance!
In Laravel Laravel 5.3.4 or above, you can't access the session or authenticated user in your controller's constructor, since the middlware isn't runnig yet.
As an alternative, you may define a Closure based middleware directly in your controller's constructor.:
try this :
function __construct()
{
$this->middleware(function ($request, $next) {
if (!auth()->check()) {
return redirect('/login');
}
$this->userID = auth()->id(); // or auth()->user()->id
return $next($request);
});
}
another alternative solution go you your base controller class and add __get function like this :
class Controller
{
public function __get(string $name)
{
if($name === 'user'){
return Auth::user();
}
return null;
}
}
and now if your current controller you can use it like this $this->user:
class YourController extends Controller
{
public function MyTestMethod() {
echo $this->user;
}
}
You should try this :
function __construct() {
$this->userID = Auth::user()?Auth::user()->id:null;
}
OR
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->userID = Auth::user()->id;
return $next($request);
});
}
I have request class like below.
class CategoryRequest extends Request
{
public function response(array $errors){
return \Redirect::back()->withErrors($errors)->withInput();
}
public function authorize()
{
return false;
}
public function rules()
{
return [
'Category' => 'required|unique:tblcategory|max:25|min:5'
];
}
}
There is rules function.
In the controller, there are multiple methods that have Request as a Parameter. Most of them vary in the validation point of view. I mean, if I am admin,. I can update 4 fields. If I am manager, I can update 3 and if I am normal user, I can update 2. So validation will change according to roles.
Is that possible to have multiple rules function in Request class ?
You can use here any conditions you want, so you you could do something like this:
public function rules()
{
$rules = [];
if (Auth::user()->isAdmin()) {
$rules['Category'] = '...';
}
elseif (Auth::user()->isManager()) {
$rules['Category'] = '...';
}
return $rules;
}
Of course you need to create isAdmin and isManager in your User model
In my controller, I have something like the following:
public function index()
{
$questions = Question::all();
return view('questions.index', compact('questions'));
}
However, I would like this route to also be used by my ajax requests. In which case, I'd like to return JSON. I'm considering the following:
public function index()
{
$questions = Question::all();
return $this->render('questions.index', compact('questions'));
}
public function render($params)
{
if ($this->isAjax()) {
return Response::json($params);
} else {
return view('questions.index')->with($params);
}
}
..by the way, I haven't tested any of this yet, but hopefully you get the idea.
However, I was wondering if I can alter the built in view(...) functionality itself to keep things even lighter. So I just keep the following:
public function index()
{
$questions = Question::all();
// this function will detect the request and deal with it
// e.g. if header X-Requested-With=XMLHttpRequest/ isAjax()
return view('questions.index', compact('questions'));
}
Is this possible?
You probably want to make custom response:
add ResponseServiceProvider.php
namespace App\Providers;
use Request;
use Response;
use View;
use Illuminate\Support\ServiceProvider;
class ResponseServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
Response::macro('smart', function($view, $data) {
if (Request::ajax()) {
return Response::json($data);
} else {
return View::make($view, $data);
}
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Add 'App\Providers\ResponseServiceProvider' to providers list in config/app.php:
'providers' => [
'App\Providers\ResponseMacroServiceProvider',
];
Use new helper in controller:
return Response::smart('questions.index', $data);
Simply check if the Request is an Ajax request in your index method itself.
public method index() {
$questions = Question::all();
if(\Request::ajax())
return \Response::json($questions);
else
return view('questions.index', compact('questions'));
}
Use Request::ajax(), or inject the request object:
use Illuminate\Http\Request;
class Controller {
public function index(Request $request)
{
$data = ['questions' => Question::all()];
if ($request->ajax()) {
return response()->json($data);
} else {
return view('questions.index')->with($data);
}
}
}
Your view should never know anything about the HTTP request/response.
I guess the simple method is just to put a method inside the parent Controller class:
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController {
...
protected function render($view, $data)
{
if (Request::ajax()) {
return Response::json($data);
} else {
return view($view, $data);
}
}
}
and then instead of doing view('questions.index, $data);, do $this->render('questions.index', $data);