Laravel Policies registration return false on Voyager - laravel

I added Voyager to my project, which uses a policy to check if a user can view/edit a specific project.
When I try to open the Projects table from Voyager, I get a 403 error.
It seems that when I remove the policy from my "policies" array in AuthServiceProvider, I am able to access the projects table in Voyager just fine.
I tried adding in my policy a check on $user->role_id == 1 (check if user is admin), but still no success, even if I replace the response of the policy to true.
Any idea what the issue might be? Thanks
Edit:
Desired behavior: I want to restrict the user from editing/viewing projects that are not his, however I want the admin to be able to access all projects from Voyager.

I ended up fixing it by adding the policy that I am using on the Projects bread, and had to add browse, add, edit, delete and read methods returning true.
It seems that once you have a policy registered Voyager will pick it up so you need to tell it how it should behave.
<?php
namespace App\Policies;
use App\User;
use App\Project;
use Illuminate\Support\Facades\DB;
use Illuminate\Auth\Access\HandlesAuthorization;
class ProjectPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
}
public function browse(){
return true;
}
public function add(){
return true;
}
public function delete(){
return true;
}
public function edit(){
return true;
}
public function read(){
return true;
}
public function access(User $user, Project $project){
return $project->owner_id == $user->id ;
}
}

Have you tried some basics steps like :
php artisan route:clear
composer dump-autoload

Related

Voyager and Jetstream: Login to Admin Panel leads to Dashboard Page

I just started my first Laravel project and try to combine Jetstream Authentification with Voyager Admin Panel.
First of all, I installed Jetstream on a fresh Laravel installation and it worked so far:
Afterwards, I tried to add Voyager to generate the CRUDs for my website and added a new user with
php artisan voyager:admin your#email.com --create
But whenever I tried to login through the url "../admin", I was redirected to "../dashboard" from Jetstream.
Even if I reentered "../admin" as URL, I was redirected. As long as I was logged in, I cannot enter the Voyager Backend.
So I guess it's some kind of routing / middleware issue, but I cannot find out which issue it is.
Within the web.php Routing file, there's only the basic stuff:
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
Route::group(['prefix' => 'admin'], function () {
Voyager::routes();
});
Not sure if that's relevant, but my IDE recognizes Voyager:: as unknown class, even it works the same way on a different Laravel installation.
But from the look of it, I expected the Route::middleware() to redirect a logged in person which types the url "../dashboard" to the Dashboard view, but nothing more. Removing this Route also didnt help the problem, so I guess I was wrong.
But beside this, only the pure Voyager Routes are left, so I'm not sure where else I can look to solve this problem.
You can add custom responses on app/Http/Responses directory.
just make new responses called LoginResponse
then use this code
<?php
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class LoginResponse implements LoginResponseContract
{
/**
* #param $request
* #return mixed
*/
public function toResponse($request)
{
$home = auth()->user()->is_admin ? '/admin' : '/dashboard';
return redirect()->intended($home);
}
}
Then, bind your LoginResponse in FortifyServiceProvider
You can use this code
<?php
namespace App\Providers;
// ...
use App\Http\Responses\LoginResponse;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// ...
$this->app->singleton(LoginResponseContract::class, LoginResponse::class);
}
}
i know that it to late but for other users who had the same problem.
first i installed jetstream
int .env file APP_URL=http://localhost:8000
i installed voyager with dummy data
i added manualy in table user_roles this ligne ( the admin )
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES ('1', '1');
and it work
you can see this video i found in youtube i think it will help you .
https://www.youtube.com/watch?v=UDYZx5uIwmQ

Laravel 6: How to override sendPasswordResetNotification function

I've been working on resetting password but using a custom notification instead of Laravel notification. This is the file vendor\laravel\framework\src\Illuminate\Auth\Passwords\CanResetPassword.php
public function sendPasswordResetNotification($token)
{
//use custom notification to change the url instead of modifying the original class
//$this->notify(new ResetPasswordNotification($token));
$this->notify(new CustomResetPassword($token));
}
That's ow I call it in a service class
$reset_password_status = Password::reset($credentials, function ($user, $password) {
$user->password = $password;
$user->save();
});
if ($reset_password_status == Password::INVALID_TOKEN) {
return $this->returnError->error("Invalid token provided");
}
But the problem is building the project online is done automatically and run composer install every time and can't just upload the change manually so I'm looking for a way to override this function in my code to reset the password instead of editing CanResetPassword.php file which exists in the vendor folder
On your Class that is using this trait you can override this function. Every function written in class have priority over trait function.
In user.php model
public function sendPasswordResetNotification($token)
{
$this->notify(new CustomResetPassword($token));
}

Change InertiaJS-Laravel default RootView

I am using Laravel 8 and I have installed InertiaJS, but in my directory resources/views/ I have a single file called index.blade.php which I plan to use with InertiaJS.
By default, InertiaJS looks for a file inside that directory called app.blade.php. I know writing the following statement:
\Inertia\Inertia::setRootView('index');
Change the rootView and allow me to use the file I have created. It may seem like a stupid question, but as far as I see it, I can do 2 things ..
Rename file index.blade.php to app.blade.php
Write the previous sentence .. in one of the ServiceProviders that I have
I wonder the following:
InertiaJS-Laravel does not allow publishing a ServiceProvider with the command php artisan vendor:publish? (the output of this command does not show me anything to publish regarding this package)
To solve my problem I should create a ServiceProvider like: php artisan make:provider InertiaServiceProvider and then register it?
Or just add the previous statement to one of the ServiceProvider that already exist? Like in app/Http/Providers/RouteServiceProvider.php
What do you recommend that would be better?
I want to seek the largest possible organization in my project. Thank you very much in advance...
Update; after my initial answer (on 20-09-2020), Inertia introduced middleware to handle your Inertia requests.
As described in the answers below, you can use the command php artisan inertia:middleware to generate this middleware. You can set the root index with:
// Set root template via property
protected $rootView = 'app';
// OR
// Set root template via method
public function rootView(Request $request)
{
return 'app';
}
You can find more info in the docs.
Even tighter, just override the rootView method in App\Http\Middleware\HandleInertiaRequests like this...
public function rootView(Request $request)
{
if ($request->route()->getPrefix() == 'admin') {
return 'layout.admin';
}
return parent::rootView($request);
}
You can do this inside your controller on the fly.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Inertia\Inertia;
class NewsController extends Controller
{
public function index()
{
Inertia::setRootView('layouts.news');
$users = User::all();
return Inertia::render('News/Index', compact('users'));
}
}
Replace in the App\Http\Middleware\HandleInertiaRequests
protected $rootView = 'app';
with:
public function rootView(Request $request): string
{
if ($request->route()->getPrefix() === '/admin') {
return 'admin.app';
}
return 'app';
}
I think it would be easier to change it in App\Http\Middleware\HandleInertiaRequests.
Be sure to run php artisan inertia:middleware during inertia server-side installation.
Also include it in your web middleware group.
Then go to App\Http\Middleware\HandleInertiaRequests and change the $rootView property to the name of the blade file you want to use. Example:
protected $rootView = 'index';
Extended #Olu Udeh answer
overwrite handle method of App\Http\Middleware\HandleInertiaRequests middleware
public function handle(Request $request, Closure $next)
{
if($request->route()->getPrefix() == 'admin'){
$this->rootView = 'layouts.admin';
}
return parent::handle($request, $next);
}
In laravel 8 this work for me
App\Http\Middleware\HandleInertiaRequests
Code
public function rootView(Request $request)
{
if(request()->is('admin/*') or request()->is('admin'))
{
return 'admin';
}
return parent::rootView($request);
}

Laravel permission cache

I am using my own implementation for user roles and permissions in laravel how to cache all the permissions and roles on user login and also to refresh the cache when new record is added.
My tables are users ,roles, permissions,permission_role,permission_user,role_user. This is my table structure
i have created on of the provider and added this code to boot method
Permission::get()->map(function ($permission)
{
Gate::define($permission->name, function ($user) use ($permission)
{
return $user->hasPermission($permission);
});
});
it is working fine but it is running the query each time which gets slowing down my application is there any way to cache all the permissions
here is my PermissionServiceProvider class
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Blade;
use App\Models\Permission;
class PermissionServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
if (Schema::hasTable('permissions'))
{
Permission::get()->map(function ($permission)
{
Gate::define($permission->name, function ($user) use ($permission)
{
return $user->hasPermission($permission);
});
});
}
Blade::directive('role', function ($role)
{
return "<?php if(Auth::user()->hasRole({$role})): ?>";
});
Blade::directive('endrole', function ($role)
{
return "<?php endif; ?>";
});
}
/**
* Register services.
*
* #return void
*/
public function register()
{
//
}
}
and finally inside the user model
public function hasPermission($permission)
{
return $this->hasPermissionThroughRole($permission) || (bool) $this->permissions->where('name',$permission->name)->count();
}
public function hasPermissionThroughRole($permission)
{
foreach($permission->roles as $role)
{
if($this->roles->contains($role))
{
return true;
}
}
return false;
}
and also here is my bit bucket repo
https://manojkiran#bitbucket.org/manojkiran/userrolesandpermissions.git
i have tried the method of #emtiaz-zahid https://stackoverflow.com/a/53511803/8487424 its working fine to cache all the permisisons in permissions table but is there any way to cahe all the permisisons and also the permissions to specifc user and role of the currently logged in user
You could use Laravel model cache like shown in the following link:
https://laravel-news.com/laravel-model-caching
I would create a method for getting and caching the current users roles and permissions.
You can read more about Caching here:
https://laravel.com/docs/5.7/cache
I hope this helps
Update
Try something like this
if (Schema::hasTable('permissions') ) {
$userId = Auth::user()->id
return Cache::remember($this->cacheKey() . $userId . ':permissions', 60, function() use ($userId) {
return Permission::where('user_id', $userId)->get();
}
}
Then you just need to update your hasPermission() method to check the cache.
Also try to avoid map() as php takes a lot longer than sql
You can store your cache in service provider. I just sharing my method
$permissions = Cache::rememberForever('permissions', function () {
return Permission::get();
});
this will remember permissions with key name permissions forever until you change it
you can also remember this with specific time
$permissions = Cache::remember('permissions', 3600, function () {
return Permission::get();
});
I placed it in app service provider so that every time on app load it check if permissions found in cache then don't run the query and if not found then take it from DB
so your script could be like this after the cache part:
$permissions->map(function ($permission)
{
Gate::define($permission->name, function ($user) use ($permission)
{
return $user->hasPermission($permission);
});
});
find more on Laravel Documentation
finally I found the solution.
Artisan cache clear command is not working well.
Try to remove the cache on this folder.
storage/framework/cache/data
This should work for sure.
Good luck

global variable for all controller and views

In Laravel I have a table settings and i have fetched complete data from the table in the BaseController, as following
public function __construct()
{
// Fetch the Site Settings object
$site_settings = Setting::all();
View::share('site_settings', $site_settings);
}
Now i want to access $site_settings. in all other controllers and views so that i don't need to write the same code again and again, so anybody please tell me the solution or any other way so i can fetch the data from the table once and use it in all controllers and view.
Okay, I'm going to completely ignore the ridiculous amount of over engineering and assumptions that the other answers are rife with, and go with the simple option.
If you're okay for there to be a single database call during each request, then the method is simple, alarmingly so:
class BaseController extends \Controller
{
protected $site_settings;
public function __construct()
{
// Fetch the Site Settings object
$this->site_settings = Setting::all();
View::share('site_settings', $this->site_settings);
}
}
Now providing that all of your controllers extend this BaseController, they can just do $this->site_settings.
If you wish to limit the amount of queries across multiple requests, you could use a caching solution as previously provided, but based on your question, the simple answer is a class property.
At first, a config file is appropriate for this kind of things but you may also use another approach, which is as given below (Laravel - 4):
// You can keep this in your filters.php file
App::before(function($request) {
App::singleton('site_settings', function(){
return Setting::all();
});
// If you use this line of code then it'll be available in any view
// as $site_settings but you may also use app('site_settings') as well
View::share('site_settings', app('site_settings'));
});
To get the same data in any controller you may use:
$site_settings = app('site_settings');
There are many ways, just use one or another, which one you prefer but I'm using the Container.
Use the Config class:
Config::set('site_settings', $site_settings);
Config::get('site_settings');
http://laravel.com/docs/4.2/configuration
Configuration values that are set at run-time are only set for the current request, and will not be carried over to subsequent requests.
In Laravel, 5+ you can create a file in the config folder and create variables in that and use that across the app.
For instance, I want to store some information based on the site.
I create a file called site_vars.php,
which looks like this
<?php
return [
'supportEmail' => 'email#gmail.com',
'adminEmail' => 'admin#sitename.com'
];
Now in the routes, controller, views you can access it using
Config::get('site_vars.supportEmail')
In the views if I this
{{ Config::get('site_vars.supportEmail') }}
It will give email#gmail.com
Hope this helps.
EDiT-
You can also define vars in .env file and use them here.
That is the best way in my opinion as it gives you the flexibility to use values that you want on your local machine.
So, you can do something this in the array
'supportEmail' => env('SUPPORT_EMAIL', 'defaultmail#gmail.com')
Important - After you do this, don't forget to do this on production env
php artisan config:cache
In case, there's still some problem, then you can do this (usually it would never happen but still if it ever happens)
php artisan cache:clear
php artisan config:cache
In your local env, always do this after this adding it
php artisan config:clear
It's always a good practice not to cache config vars in local. in case, it was cached, this would remove the cache and would load the new changes.
I see, that this is still needed for 5.4+ and I just had the same problem, but none of the answers were clean enough, so I tried to accomplish the availability with ServiceProviders. Here is what i did:
Created the Provider SettingsServiceProvider
php artisan make:provider SettingsServiceProvider
Created the Model i needed (GlobalSettings)
php artisan make:model GlobalSettings
Edited the generated register method in \App\Providers\SettingsServiceProvider. As you can see, I retrieve my settings using the eloquent model for it with Setting::all().
public function register()
{
$this->app->singleton('App\GlobalSettings', function ($app) {
return new GlobalSettings(Setting::all());
});
}
Defined some useful parameters and methods (including the constructor with the needed Collection parameter) in GlobalSettings
class GlobalSettings extends Model
{
protected $settings;
protected $keyValuePair;
public function __construct(Collection $settings)
{
$this->settings = $settings;
foreach ($settings as $setting){
$this->keyValuePair[$setting->key] = $setting->value;
}
}
public function has(string $key){ /* check key exists */ }
public function contains(string $key){ /* check value exists */ }
public function get(string $key){ /* get by key */ }
}
At last I registered the provider in config/app.php
'providers' => [
// [...]
App\Providers\SettingsServiceProvider::class
]
After clearing the config cache with php artisan config:cache you can use your singleton as follows.
$foo = app(App\GlobalSettings::class);
echo $foo->has("company") ? $foo->get("company") : "Stack Exchange Inc.";
You can read more about service containers and service providers in Laravel Docs > Service Container and Laravel Docs > Service Providers.
This is my first answer and I had not much time to write it down, so the formatting ist a bit spacey, but I hope you get everything.
I forgot to include the boot method of SettingsServiceProvider, to make the settings variable global available in views, so here you go:
public function boot(GlobalSettings $settinsInstance)
{
View::share('globalsettings', $settinsInstance);
}
Before the boot methods are called all providers have been registered, so we can just use our GlobalSettings instance as parameter, so it can be injected by Laravel.
In blade template:
{{ $globalsettings->get("company") }}
View::share('site_settings', $site_settings);
Add to
app->Providers->AppServiceProvider file boot method
it's global variable.
Most popular answers here with BaseController didn't worked for me on Laravel 5.4, but they have worked on 5.3. No idea why.
I have found a way which works on Laravel 5.4 and gives variables even for views which are skipping controllers. And, of course, you can get variables from the database.
add in your app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Using view composer to set following variables globally
view()->composer('*',function($view) {
$view->with('user', Auth::user());
$view->with('social', Social::all());
// if you need to access in controller and views:
Config::set('something', $something);
});
}
}
credit: http://laraveldaily.com/global-variables-in-base-controller/
In Laravel 5+, to set a variable just once and access it 'globally', I find it easiest to just add it as an attribute to the Request:
$request->attributes->add(['myVar' => $myVar]);
Then you can access it from any of your controllers using:
$myVar = $request->get('myVar');
and from any of your blades using:
{{ Request::get('myVar') }}
In Laravel 5.1 I needed a global variable populated with model data accessible in all views.
I followed a similar approach to ollieread's answer and was able to use my variable ($notifications) in any view.
My controller location: /app/Http/Controllers/Controller.php
<?php
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;
use App\Models\Main as MainModel;
use View;
abstract class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function __construct() {
$oMainM = new MainModel;
$notifications = $oMainM->get_notifications();
View::share('notifications', $notifications);
}
}
My model location: /app/Models/Main.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use DB;
class Main extends Model
{
public function get_notifications() {...
I have found a better way which works on Laravel 5.5 and makes variables accessible by views. And you can retrieve data from the database, do your logic by importing your Model just as you would in your controller.
The "*" means you are referencing all views, if you research more you can choose views to affect.
add in your app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Contracts\View\View;
use Illuminate\Support\ServiceProvider;
use App\Setting;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// Fetch the Site Settings object
view()->composer('*', function(View $view) {
$site_settings = Setting::all();
$view->with('site_settings', $site_settings);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
}
}
If you are worried about repeated database access, make sure that you have some kind of caching built into your method so that database calls are only made once per page request.
Something like (simplified example):
class Settings {
static protected $all;
static public function cachedAll() {
if (empty(self::$all)) {
self::$all = self::all();
}
return self::$all;
}
}
Then you would access Settings::cachedAll() instead of all() and this would only make one database call per page request. Subsequent calls will use the already-retrieved contents cached in the class variable.
The above example is super simple, and uses an in-memory cache so it only lasts for the single request. If you wanted to, you could use Laravel's caching (using Redis or Memcached) to persist your settings across multiple requests. You can read more about the very simple caching options here:
http://laravel.com/docs/cache
For example you could add a method to your Settings model that looks like:
static public function getSettings() {
$settings = Cache::remember('settings', 60, function() {
return Settings::all();
});
return $settings;
}
This would only make a database call every 60 minutes otherwise it would return the cached value whenever you call Settings::getSettings().
You can also use Laravel helper which I'm using.
Just create Helpers folder under App folder
then add the following code:
namespace App\Helpers;
Use SettingModel;
class SiteHelper
{
public static function settings()
{
if(null !== session('settings')){
$settings = session('settings');
}else{
$settings = SettingModel::all();
session(['settings' => $settings]);
}
return $settings;
}
}
then add it on you config > app.php under alliases
'aliases' => [
....
'Site' => App\Helpers\SiteHelper::class,
]
1. To Use in Controller
use Site;
class SettingsController extends Controller
{
public function index()
{
$settings = Site::settings();
return $settings;
}
}
2. To Use in View:
Site::settings()
A global variable for using in controllers; you can set in AppServiceProvider like this :
public function boot()
{
$company=DB::table('company')->where('id',1)->first();
config(['yourconfig.company' => $company]);
}
usage
config('yourconfig.company');
using middlwares
1- create middlware with any name
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\View;
class GlobalData
{
public function handle($request, Closure $next)
{
// edit this section and share what do you want
$site_settings = Setting::all();
View::share('site_settings', $site_settings);
return $next($request);
}
}
2- register your middleware in Kernal.php
protected $routeMiddleware = [
.
...
'globaldata' => GlobalData::class,
]
3-now group your routes with globaldata middleware
Route::group(['middleware' => ['globaldata']], function () {
// add routes that need to site_settings
}
In file - \vendor\autoload.php, define your gobals variable as follows, should be in the topmost line.
$global_variable = "Some value";//the global variable
Access that global variable anywhere as :-
$GLOBALS['global_variable'];
Enjoy :)
I know I am super late to the party, but this was the easiest way I found.
In app/Providers/AppServiceProvider.php, add your variables in the boot method. Here I am retrieving all countries from the DB:
public function boot()
{
// Global variables
view()->composer('*',function($view) {
$view->with('countries', Country::all());
});
}
There are two options:
Create a php class file inside app/libraries/YourClassFile.php
a. Any function you create in it would be easily accessible in all the views and controllers.
b. If it is a static function you can easily access it by the class name.
c. Make sure you inclued "app/libraries" in autoload classmap in composer file.
In app/config/app.php create a variable and you can reference the same using
Config::get('variable_name');
Hope this helps.
Edit 1:
Example for my 1st point:
// app/libraries/DefaultFunctions.php
class DefaultFunctions{
public static function getSomeValue(){
// Fetch the Site Settings object
$site_settings = Setting::all();
return $site_settings;
}
}
//composer.json
"autoload": {
"classmap": [
..
..
..
"app/libraries" // add the libraries to access globaly.
]
}
//YourController.php
$default_functions = new DefaultFunctions();
$default_functions->getSomeValue();

Resources