Laravel policy send 403 for update and 201 for create - php

I have this policy :
class ProjectPagePolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return true;
}
public function view(User $user, ProjectPage $projectPage)
{
return true;
}
public function create(User $user)
{
return $user->isAdmin() || $user->isDeveloper();
}
public function update(User $user, ProjectPage $projectPage)
{
return $user->isAdmin() || $user->isDeveloper();
}
..........
}
ProjectPageController :
class ProjectPageController extends Controller
{
public function __construct()
{
$this->authorizeResource(ProjectPage::class, 'project-page');
}
public function index(Request $request)
{
return response(
[],
HttpStatusCode::OK
);
}
public function store(ProjectPageRequest $projectPageRequest)
{
$inputs = $projectPageRequest->validated();
$projectPage = ProjectPage::create($inputs);
return response()->json([
'data' => new ProjectPageResource($projectPage)
], HttpStatusCode::Created);
}
public function update(
ProjectPageRequest $projectPageRequest,
ProjectPage $projectPage
) {
$inputs = $projectPageRequest->validated();
$projectPage->fill($inputs)->save();
return response(status: HttpStatusCode::NoContent);
}
In the routes file :
Route::middleware(['auth:sanctum'])->group(function () {
Route::post(
'/refresh-token',
fn () => app(RefreshTokenResponse::class)
);
Route::apiResources([
'project-page' => ProjectPageController::class,
]);
});
When I try to save a project page, I received 201 CREATED so all good in this case.
When I try to update it I have 403 forbidden.
Where the problem is ? Why is working on creation and not on updated ? Have an idea about that ?

TL;DR Remove the second argument ('project-page') from your authorizeResource call.
When using Route::resource(...), Laravel will convert a hyphenated route parameter to be snake-case (this will not have any affect on the URL itself, just how laravel accesses the parameter). This will mean that that when you call authorizeResource with project-page, it won't match. This will in-turn cause the authorize method to fail.
You can view your routes via the CLI with the following:
php artisan route:list
which should show your route param for your project-page routes to be project_page e.g. project-page/{project_page}

Related

Laravel Restful Api: Call to a member function toBase() on null

I'm using Laravel 5.8 and I wanted to retrieve some data from an Api route:
Route::prefix('v1')->namespace('Api\v1')->group(function(){
Route::get('/articles','ArticleController#index');;
});
Then I made a Resource Collection and a Controller as well:
ArticleCollection.php:
public function toArray($request)
{
return parent::toArray($request);
}
ArticleController:
public function index()
{
$articles = Article::all();
return new ArticleCollection($articles);
}
Now it properly shows data:
{"data":[{"art_id":3,"art_cat_id":15,"art_author_id":23973,"art_title":"\u0627\u062d\u06cc\u0627\u06cc...
But when I change the Resource Collection to this:
public function toArray($request)
{
return [
'title' => $this->art_title,
'desc' => $this->art_description,
];
}
And trying to find an specific article in the Controller:
public function index()
{
$articles = Article::find(1);
return new ArticleCollection($articles);
}
I will get this error somehow:
FatalThrowableError
Call to a member function toBase() on null
So what's going wrong here? How can I solve this issue?
Change this:
public function index()
{
$articles = Article::find(1);
return new ArticleCollection($articles);
}
to this:
public function index()
{
$articles = Article::where('id',1)->get();
return new ArticleCollection($articles);
}
This is because the find returns just one record and you are converting that to a collection, by using get() you can return a collection as you intend to here.

Laravel 5.8 send parameters to authorize method FormRequest Class

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

deleting a user with query builder laravel

I'm trying to delete a user account using laravel query builder so I'm doing this
AuthRepository
class AuthRepository implements IAuthRepository
{
....
public function delete($user_id)
{
$res = User::where('id', $user_id->id)->delete();;
if ($res) {
return response('Success, user was deleted', 204);
} else {
return response()->json(error);
}
}
}
In controller
class AuthController extends Controller
{
protected $auth;
public function delete($user_id)
{
return $user_id->delete();
}
}
in api.php
Route::group(['prefix' => 'auth'], function () {
Route::group(['middleware' => 'auth:api'], function () {
// Delete user
Route::post('user/delete/{user_id}', 'AuthController#delete');
});
});
Passing user_id to ${API_URL}/auth/user/delete/{user_id} I'm facing
Call to a member function delete() in Controller on line return $user_id->delete();. Can someone please explain me why is this happening, thanks.
Take advantage of the route model binding and to this instead:
public function delete(User $user)
{
return $user->delete();
}
And your route:
Route::post('user/delete/{user}', 'AuthController#delete');
You cannot call delete() on an integer.
If you don't want to use the Route model binding as suggested by #nakov and insist on using id then you have to get the user first before deleting.
public function delete($user_id)
{
$user = User::findOrFail($user_id);
return $user->delete();
}

Laravel Dingo FindOrFail returns empty array

I have a table sites with list of sites with following columns ('id', 'path', 'site_link'). I've written in a Site model public $timestamps = false; so that it won't try to work with time what I don't need.
Also I have the following routes
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', function ($api) {
$api->get('sites', 'App\Http\Controllers\SiteController#index');
$api->get('sites/{site}', 'App\Http\Controllers\SiteController#show');
});
The first one is working fine and returning all the data, however the second one is returning just [].
I have a controller which is below
namespace App\Http\Controllers;
use Illuminate\Http\Request;
Use App\Site;
class SiteController extends Controller
{
public function index()
{
return Site::all();
}
public function show(Site $site)
{
return Site::findOrFail($site);
}
public function store(Request $request)
{
$site = Site::create($request->all());
return response()->json($site, 201);
}
public function update(Request $request, Site $site)
{
$site->update($request->all());
return response()->json($site, 200);
}
public function delete(Site $site)
{
$site->delete();
return response()->json(null, 204);
}
}
The show method in your SiteController is taking a Site object. However the route is set up to only take the siteId. The code below should work for you based on how you've set up your routes.
public function show($site)
{
return Site::findOrFail($site);
}
Apply same to all your other controller methods since you want pass the site id via the url to the controller methods.

Run Middleware Before Controller's Constructor On Laravel 5.1?

I have a middleware that authenticates a JWT user using tymon/jwt-auth package:
public function handle($request, \Closure $next)
{
if (! $token = $this->auth->setRequest($request)->getToken()) {
return $this->respond('tymon.jwt.absent', 'token_not_provided', 400);
}
try {
$user = $this->auth->authenticate($token);
} catch (TokenExpiredException $e) {
return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
} catch (JWTException $e) {
return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
}
if (! $user) {
return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404);
}
$this->events->fire('tymon.jwt.valid', $user);
return $next($request);
}
Then I have a controller and I want to pass the user from the middleware to the controller.
So I did on the controller:
public function __construct()
{
$this->user = \Auth::user();
}
The problem is that $this->user is null, but when I do this on a method of the controller, it's not null.
So:
public function __construct()
{
$this->user = \Auth::user();
}
public function index()
{
var_dump($this->user); // null
var_dump(\Auth::user()); // OK, not null
}
So the issue is that __construct is running before the middleware. How can I change that, or do you have another solution?
Update: I'm using dingo/api for routing, maybe it's an error on their side?
You should use the middleware(s) in the routes
Route::middleware('jwt.auth')->group(function() {
// your routes
});
1) Remove Your middleware from Your kernel's $middleware array
2) Put Your middleware to $routeMiddleware array with custom name jwt.auth:
protected $routeMiddleware = [
// ...
'jwt.auth' => 'App\Http\Middleware\YourAuthMiddleware'
];
2) Create BaseController in parent directory of needle controller, with function:
public function __construct() {
$this->middleware('jwt.auth');
}
3) Extend needle controller from BaseController
4) Make __construct function of needle controller to look like this:
public function __construct() {
parent::__construct();
$this->user = \Auth::user();
}

Categories