Laravel middleware for control user access to datas - laravel

I'm using this structure of user levels:
Company Owner
- Group Manager
-- Unit Manager
---Employee
I need to control users to access the datas in the database. Employee can access only that datas what he stored. The unit manager can access his own datas and datas of his emplyee too. Group manager can access to entire group's datas. And the company owner can acceess to everything.
I have some controllers like this:
class ClientController extends Controller
{
public function index()
{
return Client::all();
}
// ...
}
What is the best practice in Laravel to control access to datas (not the controller) in some controllers, but not everywhere? Is here a good implementation for this issue?

You can create a middleware for each role, then in your web.php file, use route groups to assign access to the routes that the users can access. So for a route that both employee and unit manager can access, you pass the two middleware, for those that only group manager can access, you just pass group manager.
Route::group(['middleware' => ['auth', 'group-manager','unit-manager']],
function() {
Route::get('client','ClientController#index');
});

In Route. One good place for all middleware.
Route::get('/', function () { ... })->middleware('web');

middleware is the right place for access control. For group middleware you can use by this format
Route::group(['middleware' => ['role:Company Owner']], function() {
Route::get('/', 'AdminController#welcome');
Route::get('/manage', ['middleware' => ['permission:manage-admins'], 'uses' => 'AdminController#manageAdmins']);
});
Route::group(['middleware' => ['role:Employee']], function() {
Route::get('/', 'AdminController#welcome');
Route::get('/manage', ['middleware' => ['permission:manage-admins'], 'uses' => 'AdminController#manageAdmins']);
});
For single route
Route::put('post/{id}', function ($id) {
//
})->middleware('role:Employee');
You can use a package for user role base access control
https://github.com/Zizaco/entrust

Maybe the question wansn't clear enough, but I found a solution:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Auth;
use App\User;
class DataAccessScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
// find the actual user
$user_id = Auth::user()->id;
$user = User::find( $user_id );
// get all employees
$employees = $user->allEmployeeFlatten();
// get only the employee's ids
$user_ids = $employees->pluck('id');
// add current user's id too
$user_ids->push( $user_id );
// add the condition to every sql query
$builder->whereIn('user_id', $user_ids);
}
}
Here is the User model's allEmployeeFaletten() function:
public function allEmployeeFlatten() {
$employees = new Collection();
foreach ( $this->employee()->get() as $employee ) {
$employees->push( $employee );
// run only if the user is on a leader level
if ( $employee->user_role_id != 5 ) {
$employees = $employees->merge( $employee->allEmployeeFlatten() );
}
}
return $employees;
}
This scope add a condition to all SQL queries every time I use the scope.

Related

Laravel 6 perform role based routing

I have a route group with different routes. I want to have different role levels access without changing the URL of the application.
For example I want to have /admin as the route and then I want to allow or disallow users based on their roles. Basically, I want every user to be able to see the same page but with different menu options(I know how to do this) but also secure the links from direct access.
Is there a nice way to achieve that without the need of using different middlewares seperately on each route? Since there doesn't seem to be a way to retrieve the $request variable inside the web.php file but only inside a controller. I'm using the sentinel package for auth.
Some sample code of my web.php:
Route::group(
['prefix' => 'admin', 'middleware' => 'customer', 'as' => 'admin.'],
function () {
// Ad list
Route::get('getMyAnnonsList', 'Admin\BackEndController#getMyAdList')->name('getMyAdList');
}
);
Great answer by #lagbox. This is what I did in the end. Very elegant.
web.php:
Route::group(['prefix' => 'admin', 'as' => 'admin.'], function () {
Route::middleware('admin:admin,user')->group(function(){
Route::get('getMyAnnonsList', 'Admin\BackEndController#getMyAdList')->name('getMyAdList');
});
});
middleware:
public function handle($request, Closure $next, ...$roles)
{
if (!Sentinel::check())
return redirect('admin/signin')->with('info', 'You must be logged in!');
foreach($roles as $role)
if($role == Sentinel::getUser()->roles[0]->slug)
return $next($request);
return redirect()->back();
}
I had already answered something like this before, should be working the same still.
You can create a middleware that can be applied to your group. In that middleware it is asking the route itself for the specific roles to check.
How to assign two middleware to the same group of routes. Laravel
Example of middleware:
class CheckMiddleware
{
public function handle($request, Closure $next)
{
$roles = $request->route()->getAction('roles', []);
foreach ((array) $roles as $role) {
// if the user has this role, let them pass through
if (...) {
return $next($request);
}
}
// user is not one of the matching 'roles'
return redirect('/');
}
}
Example route definition:
Route::middleware('rolescheck')->group(function () {
Route::get('something', ['uses' => 'SomeController#method', 'roles' => [...]])->name(...);
});
You can apply this arbitrary data at the group level, the individual route level or both, as all routes are individually registered; groups just allow for cascading of configuration.
You could also have this middleware take parameters, and just merge them with the arbitrary roles, then it is a dual purpose middleware:
public function handle($request, $next, ...$roles)
{
$roles = array_merge($roles, $request->route()->getAction('roles', []));
...
}
Route::middleware('rolescheck:admin,staff')->group(...);
You can use Laravel Gate And Policies
You can define the gate inside the App > Providers > AuthServiceProvider
and you can also create policies per CRUD. just see info in php artisan help make:policy. This will create a folder in your app called policies you can define the who can access it.
In your controller you can do is this: (this is a gate middleware)
I define the gate first:
Gate::define('check', function ($user, $request) {
return $user->roles->contains('name', $request) || $user->roles->contains('name', 'root');
});
then I initialise it in the controller
abort_if(Gate::denies('check', 'admin only'), 403);
This will throw 403 error if the user don't have access on that role. It will check if the user has admin only role. If it doesn't have it will throw the error
In your view if you want to disable anchor links you can do like this:
#can('check', 'admin only')
dashboard
#endcan
EDIT:
Controller
public function index() {
abort_if(Gate::denies('check', 'admin only'), 403);
// Your Code...
}

Use Auth in AppServiceProvider

I need the ID of the user who is logged in to get a photo in the profile table, here I am trying to use View but only in the index function that gets $profile, I want all files in the view to have $profile
public function index(){
$profil = Profil_user::where('user_id',$auth)->first();
View::share('profil', $profil);
return view('user.index');
}
I have also tried AppServiceProvider but I get an error in the form of a null value if I don't log in, is there a solution to my problem?
public function boot(){
$auth = Auth::user();
dd($auth);
}
exist several way to pass a variable to all views. I explain some ways.
1. use middleware for all routes that you need to pass variable to those:
create middleware (I named it RootMiddleware)
php artisan make:middleware RootMiddleware
go to app/Http/Middleware/RootMiddleware.php and do following example code:
public function handle($request, Closure $next) {
if(auth()->check()) {
$authUser = auth()->user();
$profil = Profil_user::where('user_id',$authUser->id)->first();
view()->share([
'profil', $profil
]);
}
return $next($request);
}
then must register this middleware in app/Http/Kernel.php and put this line 'root' => RootMiddleware::class, to protected $routeMiddleware array.
then use this middleware of routes or routes group, for example:
Route::group(['middleware' => 'root'], function (){
// your routes that need to $profil, of course it can be used for all routers(because in handle function in RootMiddleware you set if
});
or set for single root:
Route::get('/profile', 'ProfileController#profile')->name('profile')->middleware('RootMiddleware');
2. other way that you pass variable to all views with view composer
go to app/Http and create Composers folder and inside it create ProfileComposer.php, inside ProfileComposer.php like this:
<?php
namespace App\Http\View\Composers;
use Illuminate\View\View;
class ProfileComposer
{
public function __construct()
{
}
public function compose(View $view)
{
$profil = Profil_user::where('user_id', auth()->id)->first();
$view->with([
'profil' => $profil
]);
}
}
now it's time create your service provider class, I named it ComposerServiceProvider
write this command in terminal : php artisan make:provider ComposerServiceProvider
after get Provider created successfully. message go to config/app.php and register your provider with put this \App\Providers\ComposerServiceProvider::class to providers array.
now go to app/Providers/ComposerServiceProvider.php and do like following:
namespace App\Providers;
use App\Http\View\Composers\ProfileComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
View::composer(
'*' , ProfileComposer::class // is better in your case use write your views that want to send $profil variable to those
);
/* for certain some view */
//View::composer(
// ['profile', 'dashboard'] , ProfileComposer::class
//);
/* for single view */
//View::composer(
// 'app.user.profile' , ProfileComposer::class
//);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
}
}
3. is possible that without create a service provider share your variable in AppServiceProvider, go to app/Provider/AppServiceProvider.php and do as follows:
// Using class based composers...
View::composer(
'profile', 'App\Http\View\Composers\ProfileComposer'
);
// Using Closure based composers...
View::composer('dashboard', function ($view) {
//
});
I hope be useful
you can use this
view()->composer('*', function($view)
{
if (Auth::check()) {
$view->with('currentUser', Auth::user());
}else {
$view->with('currentUser', null);
}
});

How to access controller via both admin guards and normal user guards without using roles

I need to download the form posted from the supervisor. I can access the page but not the form since I can't download it. It's giving me the ERR_INVALID_RESPONSE error, but the supervisor can download it easily.
Could it be something wrong with the middleware? I struggled a lot but still can't download, please if anyone might know the problem please help me.
Controller
class DutiesController extends Controller
{
public function assignSupervisor(Request $request, $id)
{
$assignS = new Duty;
$assignS->student_id = $id;
$assignS->user_id = $request->supervisor_id;
$assignS->student_name = $request->student_name;
$assignS->save();
return back();
}
public function assignInstructor(Request $request, $id)
{
$assignS = Duty::where('student_id', $id)->first();
$assignS->admin_id = $request->instructor_id;
$assignS->save();
return back();
}
public function duties($id)
{
$duty = Duty::where('student_id', $id)->orWhere('user_id', $id)->orWhere('admin_id', $id)->first();
return view('Duty.show', compact('duty'));
}
public function assign(Request $request, $id)
{
$assign = Duty::findOrfail($id);
if ($request->hasFile('duty')) {
$this->validate($request, [
'duty' => 'required|file|mimes:pdf,doc'
]);
$fileNameWithExt = $request->file('duty')->getClientOriginalName();
$fileName = pathinfo($fileNameWithExt, PATHINFO_FILENAME);
$extension = $request->file('duty')->getClientOriginalExtension();
$fileNameToStore = $fileName.'_'.time().'.'.$extension;
$path = $request->file('duty')->storeAs('public/duty', $fileNameToStore);
$assign->duty = $fileNameToStore;
}
$assign->marks = $request->marks;
$assign->save();
return back();
}
public function getduty($id) // my download function
{
$download = Duty::findOrfail($id);
return Storage::download("/public/duty/".$download->duty);
}
public function assignSupervisorInstructor()
{
$users = User::with('campus')->where('role_id', 4)->get();
$supervisors = User::with('campus')->where('role_id', 2)->get();
$instructors = Admin::where('role_id', 3)->get();
return view('Assigning.index', compact('users', 'supervisors', 'instructors'));
}
}
Routes
Route::group(['middleware' => 'auth:web,admin'], function () {
//Now this routes can be accessible by both admin as well as
Route::get('/duties/downloads/{id}', 'DutiesController#getduty');
Route::post('/duties/assign/{id}', 'DutiesController#assign');
Route::get('/duties/myduties/{id}', 'DutiesController#duties');
Route::get('/duties/mydutty/{id}', 'DuttyController#duties');
Route::post('/duties/{id}', 'DutiesController#assignSupervisor');
Route::get('/assign', 'DutiesController#assignSupervisorInstructor');
Route::post('/duties/inst/{id}', 'DutiesController#assignInstructor');
});
Blade
<td>
<a href="/duties/downloads/{{$duty->id}}">
<button class="btn btn-success"><i class="fa fa-download"></i> Dowload Document</button>
</a>
</td>
I hope this is what you mean but this is how I differentiate between user types via Middleware.
Basically creating custom middleware (I guess you can also do it via the built in functionality but I prefer this), for example:
1) The MiddleWare:
app/Http/Middleware/CheckUserType.php:
namespace App\Http\Middleware;
use Auth;
use Closure;
use App\Usergroup;
class CheckUserType
{
/**
* This middleware checks if the user is logged in as a specific type
*
* #var array
*/
public function handle($request, Closure $next, ...$userGroups)
{
if (in_array(UserGroup::where('id', Auth::user()->groupid)->first()->title, $userGroups)) {
return $next($request);
} else {
return response('Unauthorized...', 401);
}
}
}
In my case I have a usergroups table that links to the user->groupid so I use that Model to cross-reference the id to the group title I give in in my router.
But you can modify this obviously.
Also note that I do ...$userGroups so I can iterate over multiple user types if I want to in the router (see below).
2) Register in your kernel:
Then register in app/Http/Kernel.php:
add to the protected $routeMiddleware:
checkUserType' => CheckUserType::class
Make sure to include you custom middleware (use App\Http\Middleware\CheckUserType;)
3) Routes:
So then at last in my router I have for example the following:
/**
* #section Routes that require the user to be a Manager or an Administrator
*/
Route::group(['middleware' => 'checkUserType:Managers,Administrators'], function () {
//
});
or:
/**
* #section Routes that require the user to be an Administrator
*/
Route::group(['middleware' => 'check.usertype:Administrators'], function () {
// User Routes
Route::delete('/users/delete/{id}', 'UserController#deleteUser');
});

Laravel 5.3 - Best way to implement Entrust role on signup?

I'm working with Laravel 5.3 and I'm trying to set a role when someone signs up, I've used the Zizaco Entrust library.
I'm unsure on the best way to achieve something like this.
I tried to do this inside RegisterController's create method like below:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$user = User::where('email', '=', $data['email'])->first();
// role attach alias
$user->attachRole($employee);
}
But obviously that's not right. So I'm a bit unsure on what the best practice is with this sort of thing.
If, as your comment on the OP suggests, you always want to assign the same role to a registered user, you can use a Model Observer for this - it's really simple.
// app/Observers/UserObserver.php
<?php namespace App\Observers;
use App\Models\User;
use App\Models\Role; // or the namespace to the Zizaco Role class
class UserObserver {
public function created( User $user ) {
$role = Role::find( 1 ); // or any other way of getting a role
$user->attachRole( $role );
}
Then you simply register the observer in your AppServiceProvider:
// app/Providers/AppServiceProvider.php
use App\Models\User;
use App\Observers\UserObserver;
class AppServiceProvider extends Provider {
public function boot() {
User::observe( new UserObserver );
// ...
}
// ...
}
This answer is mainly based off looking at your current solution, with a dash of original question.
Rather than filling out your model with methods like createNew, you'll probably find things easier to manage if you create a type of class specifically for interacting with models. You can call this a Repository or a Service or whatever takes your fancy, but we'll run with Service.
// app/Services/UserService.php
<?php namespace App\Services;
use App\Models\User; // or wherever your User model is
class UserService {
public function __construct( User $user ) {
$this->user = $user;
}
public function create( array $attributes, $role = null ) {
$user = $this->user->create( $attributes );
if ( $role ) {
$user->attachRole( $role );
}
return $user;
}
}
Now we need to deal with the fact that we've lost the hashing of passwords:
// app/Models/User.php
class User ... {
public function setPasswordAttribute( $password ) {
$this->attributes[ 'password' ] = bcrypt( $password );
}
}
And now we have the problem of sending out an activation email - that can be solved cleanly with events. Run this in the terminal:
php artisan make:event UserHasRegistered
and it should look something like this:
// app/Events/UserHasRegistered.php
<?php namespace App\Events;
use App\Models\User;
use Illuminate\Queue\SerializesModels;
class UserHasRegistered extends Event {
use SerializesModels;
public $user;
public function __construct( User $user ) {
$this->user = $user;
}
}
Now we need a listener for the event:
php artisan make:listener SendUserWelcomeEmail
And this can be as complex as you like, here's one I'm just copy/pasting from a project I have lying around:
// app/Listeners/SendUserWelcomeEmail.php
<?php namespace App\Listeners;
use App\Events\UserHasRegistered;
use App\Services\NotificationService;
class SendUserWelcomeEmail {
protected $notificationService;
public function __construct( NotificationService $notificationService ) {
$this->notify = $notificationService;
}
public function handle( UserHasRegistered $event ) {
$this->notify
->byEmail( $event->user->email, 'Welcome to the site', 'welcome-user' )
->send();
}
}
All that remains is to tell Laravel that the Event and Listener we've just created are related, then to fire the event.
// app/Providers/EventServiceProvider.php
use App\Events\UserHasRegistered;
use App\Listeners\SendUserWelcomeEmail;
class EventServiceProvider extends ServiceProvider {
// find this array near the top, and add this in
protected $listen = [
UserHasRegistered::class => [
SendUserWelcomeEmail::class,
],
];
// ...
}
Now we just need to raise the event - see my other post about Model Observers. First off you'll need to import Event and App\Events\UserHasRegistered, then in your created method, just call Event::fire( new UserHasRegistered( $user ) ).
What I ended up doing, since I do need to do more than just one operation on the user creation is having another function for user creations.
User model
/**
* Create a new user instance after a valid registration.
*
* #param array $attributes
* #param null $role
* #param bool $send_activation_email
*
* #return User $user
*
* #internal param array $args
*/
public function createNew(array $attributes, $role = null, $send_activation_email = true)
{
$this->name = $attributes['name'];
$this->company_id = $attributes['company_id'];
$this->email = $attributes['email'];
$this->password = bcrypt($attributes['password']);
$this->save();
if (isset($role)) {
// Assigning the role to the new user
$this->attachRole($role);
}
//If the activation email flag is ok, we send the email
if ($send_activation_email) {
$this->sendAccountActivationEmail();
}
return $this;
}
and calling it like:
User Controller
$user = new User();
$user->createNew($request->all(), $request->role);
It might not be the best solution, but it does the job, and it's future prof, so if the logic on the user creation grows can be implemented aswell.

How to create Policies without parameters in Laravel 5.2?

I am trying to create a project to add edit contacts.
To restrict the user can add/edit their own contacts, So added policy ContactPolicy as below
<?php
namespace App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use App\User;
use App\Contact;
class ContactPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function before($user, $ability)
{
if ($user->isAdmin == 1) {
return true;
}
}
public function add_contact(User $user)
{
return $user->id;
}
public function update_contact(User $user, Contact $contact)
{
return $user->id === $contact->user_id;
}
}
And registered in AuthServiceProvider as below
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Contact::class => ContactPolicy::class,
];
To restrict adding of contact by current user I added Gate in my controller function as below without passing parameters
if (Gate::denies('add_contact')) {
return response('Unauthorized.', 401);
}
Even if current user tries to add contact, it shows Unauthorized message.
How will I solve this problem?
Policies are intended to have all authorization logic related to a certain class of resource in one place.
So, you define Contact::class => ContactPolicy::class meaning the ContactPolicy has all policies regarding Contacts. When you write Gate::denies('add_contact'), how could the framework know which policy to search? You have to pass an object of type Contact as second parameter in order to access the ContactPolicy.
Anyway, there is in fact a place to write authorization logic which is not particular to any class of resource. In the method boot of AuthServiceProvider you could add
$gate->define('add_contact', function ($user) {
return $user->id;
});
By the way, what's the intention with returning the user id? I think you just need to return a boolean.
Also, if you are checking the permission within a controller, you should just call $this->authorize('add_contact') and the controller itself will check and return a Forbidden response (for which the proper code is 403, not 401) if it fails, no need to return it yourself.

Resources