L5 Using Hashid in middleware returns null - laravel

I am using hashid to hash the id parameters in url. I have it set up in my model to automatically hash the id. This is working fine. My problem is decoding the hash in a middleware returns null. I'm not sure if this is a problem with my middleware or because of the hashing.
Model:
public function getIdAttribute($value)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
return $hashids->encode($value);
}
Middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class HashIdsDecode
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
dd($request->id); //Returns null on show method - example localhost:8000/invoices/afewRfshTl
if($request->has('id'))
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
dd($hashids->decode($request->input('id')));
}
return $next($request);
}
}
Route:
Route::resource('invoices','InvoiceController');
Controller:
public function show($id)
{
$invoice = Invoice::find($id);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
NOTE: if I bypass the middleware and do it directly in my controller like this it works but it requires me to repeat myself over and over and probably not the best way to do it.
public function show($id)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
$invoiceId = $hashids->decode($id)[0];
$invoice = Invoice::find($invoiceId);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}

Personally, I would be more inclined to write a model trait. You can then use the trait on only the models required, rather than assuming every ID argument in a request is a Hash ID.
E.g.
namespace App\Models\Traits;
use Hashids\Hashids;
use Illuminate\Database\Eloquent\Builder;
trait HashedId
{
public function scopeHashId(Builder $query, $id)
{
$hashIds = new Hashids(env('APP_KEY'), 10);
$id = $hashIds->decode($id)[0];
return $query->where('id', $id);
}
}
Then to use it, you'd use the trait on your Invoice model (edit):
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Invoice extends Model
{
use \App\Models\Traits\HashedId;
// ...
}
And execute the following query in your controller:
public function show($id)
{
$invoice = Invoice::hashId($id)->firstOrFail();
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}

Related

Merge Form Request Validation for store and update

I am using Request validation to validate the user's input.
This is UpdateUser:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
class UpdateUser extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return Gate::allows('update-user');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$user_id = Arr::get($this->request->get('user'), 'id');
return [
'user.firstname' => 'required|string|max:255',
'user.lastname' => 'required|string|max:255',
'user.email' => "required|string|email|max:255|unique:users,email,{$user_id}",
'user.password' => 'sometimes|nullable|string|min:4|confirmed',
];
}
}
As you can see, there is some update-specific stuff happening:
The authorize() method checks whether the user is allowed to update-user and inside the rules I am excluding the row of the current user from being unique:
'user.email' => "required|string|email|max:255|unique:users,email,{$user_id}",
As I would like to merge UpdateUser and StoreUser, what would be the most efficient and readable way to determine, whether I am updating or saving?
This is my current approach:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
class UpdateUser extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
if($this->isUpdating())
{
return Gate::allows('update-user');
}
return Gate::allows('create-user');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
if($this->isUpdating()){
$user_id = Arr::get($this->request->get('user'), 'id');
return [
...
];
}
return [];
}
/**
* #return bool
*/
protected function isUpdating(){
return $this->isMethod('put') || $this->isMethod('patch');
}
}
I am wondering if I may extend the FormRequest class and provide isUpdating() by default.
Your update and store method are not the same request type, you have PUT and PATCH method on your request instance, so you can check the request type as like :
switch ($request->method()) {
case 'PATCH':
// do anything in 'patch request';
break;
case 'PUT':
// do anything in 'put request';
break;
default:
// invalid request
break;
}
I learnt about a new approach to validation some time ago using separate validator class and I kinda like it a lot. Let me show you
Create a directory Validators and a class inside UserValidator
class UserValidator
{
public function rules(User $user)
{
return [
'user.firstname' => [
'required',
'string',
'max:255',
],
'user.lastname' => [
'required',
'string',
'max:255',
],
'user.email' => [
$user->exists ? 'sometimes' : null,
'required',
'string',
'email',
'max:255',
Rule::unique('users', 'email')->ignore($user->exists ? $user->id : null)
],
'user.password' => [
$user->exists ? 'sometimes' : null,
'required',
'string',
'min:8'
],
];
}
public function validate(array $data, User $user)
{
return validator($data, $this->rules($user))
//->after(function ($validator) use ($data, $user) {
// Custom validation here if need be
//})
->validate();
}
}
Then authorization can be done in Controller
class UserController
{
use AuthorizesRequests;
/**
* #param Request $request
*/
public function store(Request $request)
{
$this->authorize('create_user', User::class);
$data = (new UserValidator())->validate(
$request->all(),
$user = new User()
);
$user->fill($data)->save();
}
/**
* #param Request $request
* #param \App\user $user
*/
public function update(Request $request, User $user)
{
$this->authorize('update_user', $user);
$data = (new UserValidator())->validate(
$request->all(),
$user
);
$user->fill($data)->save();
}
}
This was proposed and explained by (twitter handle) #themsaid

How to use the ignore rule in Form Request Validation

this is PostsRequest.php in http/request:
<?php
namespace App\Http\Requests;
use App\Post;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class PostsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => ['required','max:255', Rule::unique('posts')->ignore($this->id)],
'slug' => ['required', Rule::unique('posts')->ignore($this->id),],
'content' => 'required',
'type' => 'required|in:blog,download,page',
'status' => 'required',
];
}
}
and this is edit() method in PostController.php
public function update(PostsRequest $request, $id)
{
$validated = $request->validated();
$validated['user_id'] = auth()->user()->id;
$post = Post::find($id)->fill($validated);
$post->save();
return redirect()->action('PostController#index');
}
Problem: show error in update page that this value is already exists.
who to resolve problem unique fields in edit form?
Problem Solved
change:
Rule::unique('posts')->ignore($this->route('id'))
with:
Rule::unique('posts')->ignore($this->route('post'))
If you're wanting to resolve the $id from the route then you can use the route() method in your request class e.g.
Rule::unique('posts')->ignore($this->route('id'))

Access Controller resources based on Laravel/Spatie Permissions

I am working on Laravel passport api in which i am using spatie package for user role's and permission's. I have to perform certain operation ('store','view','update','delete') based on user permission's.
For this purpose i have created a trait and used in controller but it is not working correctly.
On every api request it throw's an exception "This action is unauthorized" either the user has permission or not.
Authorize Trait :
<?php
namespace App;
/*
* A trait to handle authorization based on users permissions for given controller
*/
trait Authorizable
{
/**
* Abilities
*
* #var array
*/
private $abilities = [
'index' => 'view',
'edit' => 'edit',
'show' => 'view',
'update' => 'edit',
'create' => 'add',
'store' => 'add',
'destroy' => 'delete'
];
/**
* Override of callAction to perform the authorization before it calls the action
*
* #param $method
* #param $parameters
* #return mixed
*/
public function callAction($method, $parameters)
{
if( $ability = $this->getAbility($method) ) {
$this->authorize($ability);
}
return parent::callAction($method, $parameters);
}
/**
* Get ability
*
* #param $method
* #return null|string
*/
public function getAbility($method)
{
$routeName = explode('.', \Request::route()->getName());
$action = array_get($this->getAbilities(), $method);
return $action ? $action . '_' . $routeName[0] : null;
}
/**
* #return array
*/
private function getAbilities()
{
return $this->abilities;
}
/**
* #param array $abilities
*/
public function setAbilities($abilities)
{
$this->abilities = $abilities;
}
}
Routes:
Route::middleware('auth:api')->group(function () {
Route::post('user', 'ApiController#user');
Route::post('view_department', 'DepartmentController#index');
Route::post('add_department', 'DepartmentController#store');
Route::post('edit_department', 'DepartmentController#update');
Route::post('delete_department', 'DepartmentController#destroy');
Route::post('/logout', 'ApiController#logout');
}); // auth middleware ends
Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\User;
use App\Authorizable;
use Illuminate\Support\Facades\Validator;
use App\Department;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class DepartmentController extends Controller
{
use Authorizable;
//
public function index(Request $request) {
// return response
return response()->json([
'success' => 'You have the permission to view departments!']);
}
//
public function store(Request $request) {
// validate the posted data
$validator = Validator::make($request->all(), [
'name' => 'required|string|unique:departments',
]);
// return errors
if ($validator->fails())
{
return response(['errors'=>$validator->errors()->all()]);
}
$department = new Department;
$department->name = $request->name;
$department->save();
// return response
return response()->json([
'success' => 'Successfully created department!']);
}
}
I am badly stack at it, don't know where i am going wrong. I would highly appreciate if anyone guide me through this.
Thanks,

Default controller in Route::group for routing purposes?

I want to show some user data by id. Simple.
user/{id} -> get data from a Controller's method.
I have this:
Route::group(['prefix' => 'user/{id}', 'where' => ['id' => '[0-9]+']], function() {
Route::get('delete', 'UserController#delete')->name('admin.access.user.delete-permanently');
Route::get('restore', 'UserController#restore')->name('admin.access.user.restore');
Route::get('mark/{status}', 'UserController#mark')->name('admin.access.user.mark')->where(['status' => '[0,1]']);
Route::get('password/change', 'UserController#changePassword')->name('admin.access.user.change-password');
Route::post('password/change', 'UserController#updatePassword')->name('admin.access.user.change-password');
});
How can I access a method as a default for user/{id}?
You can do this in your controller
<?php
namespace App\Http\Controllers;
use App\User;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*
* #param int $id
* #return Response
*/
public function show($id)
{
//Fetch and send the user to the "profile" blade file in the folder "resources/views/user"
return view('user.profile', ['user' => User::findOrFail($id)]);
}
public function changePassword($id)
{
$user = User::findOrFail($id);
return $user->update(
'password' => bcrypt(123456)
);
}
}

Method [validate] does not exist. - Laravel 5.5

I'm trying "laravel-modules" and "Laravel-permission package". But when run post, it has issue 'Method [validate] does not exist.'. i have added 'use Validator;' but no thing change. In some topics, i remove "use Illuminate\Routing\Controller;" in PermissionController, but it have error
Controller
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
My PermissionController
namespace Modules\User\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Auth;
//Importing laravel-permission models
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Session;
class PermissionController extends Controller
{
// use Validator;
public function __construct() {
$this->middleware(['auth', 'isAdmin']); //isAdmin middleware lets only users with a //specific permission permission to access these resources
}
/**
* Display a listing of the resource.
* #return Response
*/
public function index()
{
$permissions = Permission::all(); //Get all permissions
return view('user::permissions/index')->with('permissions', $permissions);
// return view('user::index');
}
/**
* Show the form for creating a new resource.
* #return Response
*/
public function create()
{
$roles = Role::get(); //Get all roles
return view('user::permissions/create')->with('roles', $roles);
}
/**
* Store a newly created resource in storage.
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$this->validate($request, [
'name'=>'required|max:40',
]);
$name = $request['name'];
$permission = new Permission();
$permission->name = $name;
$roles = $request['roles'];
$permission->save();
if (!empty($request['roles'])) { //If one or more role is selected
foreach ($roles as $role) {
$r = Role::where('id', '=', $role)->firstOrFail(); //Match input role to db record
$permission = Permission::where('name', '=', $name)->first(); //Match input //permission to db record
$r->givePermissionTo($permission);
}
}
return redirect()->route('permissions.index')
->with('flash_message',
'Permission'. $permission->name.' added!');
}
}
Route
Route::group(['middleware' => 'web', 'prefix' => 'permissions', 'namespace' => 'Modules\User\Http\Controllers'], function()
{
Route::get('/', 'PermissionController#index');
Route::get('/create', 'PermissionController#create');
Route::post('/', 'PermissionController#store');
Route::delete('/', ["as" => "permissions.destroy", "uses" => "PermissionController#destroy"]);
});
Add back in use Validator in your PermissionController. Add this right after use Auth;. You have currently added this in the wrong place.
Then change your $this->validate(...) code to:
// validate the input
$validation = Validator::make( $request->all(), [
'name'=>'required|max:40',
]);
// redirect on validation error
if ( $validation->fails() ) {
// change below as required
return \Redirect::back()->withInput()->withErrors( $validation->messages() );
}
I was trying
$this->validate($request, [
'password' => 'required|confirmed|min:6',
]);
But in Laravel 5.7 following code did the trick for me
$request->validate([
'password' => 'required|confirmed|min:6',
]);

Resources