Laravel 8 - Global Object available throughout application (not just in view files) - laravel

In my application, users can belong to different accounts and have different roles on those accounts. To determine which account is "current" I am setting a session variable in the LoginController in the authenticated() method.
$request->session()->put('account_id', $user->accounts()->first()->id);
Then, throughout the application I am doing a simple Eloquent query to find an account by ID.
While this "works", I am basically repeating the same exact query in every single Controller, Middleware, etc. The maintainability is suffering and there are duplicate queries showing in Debugbar.
For example, in every controller I am doing:
protected $account;
public function __construct()
{
$this->middleware(function($req, $next){
$this->account = Account::find($req->session()->get('account_id'));
return $next($req);
});
}
In custom middleware and throughout the entire application, I am essentially doing the same thing - finding Account by ID stored in session.
I understand you can share variable with all views, but I need a way to share with the whole application.
I suppose much in the same way you can get the auth user with auth()->user.
What would be the way to do this in Laravel?

I would create a class to handle this logic. Making it a singleton, to ensure it is the same class you are accessing. So in a provider singleton the class you are gonna create in a second.
$this->app->singleton(AccountContext::class);
Create the class, where you can set the account in context and get it out.
class AccountContext
{
private $account;
public function getAccount()
{
return $this->account;
}
public function setAccount($account)
{
$this->account = $account;
}
}
Now set your account in the middleware.
$this->middleware(function($req, $next){
resolve(AccountContext::class)->setAccount(Account::find($req->session()->get('account_id')));
return $next($req);
});
Everywhere in your application you can now access the account, with this snippet.
resolve(AccountContext::class)->getAccount();

Related

Laravel Nova Observe not connecting to tenant database

I have a multi tenant App. My system database I have models- User, Billing, FrontEnd ... and using policies I'm able to show, hide and prevent viewing and actions by tenant.
Each tenant has a database with models- Member, Event, Item ...
I set each model database based on the Auth::user()->dbname in the _construct method. This allows me to set my dbname to a clients database for tech support.
class Item extendsw Model {
public function __construct() {
parent::__construct();
if(Auth::user()->dbname) {
Config::set('database.connections.tenant.database', auth()->user()->dbname);
$this->connection = 'tenant';
}
}
This all works as planned until I add and Observer for a client model e.g. Member
I now get an error on any Observer call.
Trying to get property on non object Auth::user()->dbname.
Where should I be registering the Observer? I tried AppServiceProvider and NovaServiceProvider.
I think that happens because the observer instantiates your User model before the request cycle has started and that means that your User instance does not exist yet neither has been bound in the Auth facade.
Thus, Auth::user() returns null and you are trying to get a property from it.
A way to solve the issue may be to check if the user instance exists or not:
public function __construct() {
parent::__construct();
if (optional(Auth::user())->dbname !== null) {
Config::set('database.connections.tenant.database', auth()->user()->dbname);
$this->connection = 'tenant';
}
}
The optional helper return the value of the accessed property (dbname in your case) if and only if the argument is not null, otherwise the whole call will return a null value instead throwing an exception.
If that is not the case, maybe update the question with the error stacktrack and the code/action that triggers the error

Controller constructor to check Auth middleware for two different guards

I have a dashboard view that shows certain contain depending on which user is viewing, whether it be an admin or just a regular user.
I can get my admins onto that page, but regular users aren't able to currently because of my middleware guard.
class DashboardController extends Controller {
public function __construct()
{
$this->middleware('auth:admin');
}
public function index()
{
return view('dashboard.index');
}
}
The following code checks on each DashboardController call for auth:admins, but I want regular users to access this too, is there a way to check the auth middleware twice like so?
$this->middleware(['auth:admin','auth']);
So ideally it will check if you're an admin or just a regular auth user.
Also on my view page, when accessing properties of an admin I'm using:
{{ Auth::user()->admin_username }}
Is this normal? I have an admin Model but I'm still accessing it via Auth::user() which feels strange to me, shouldn't it be Auth::admin()->admin_username
Accessing a particular page for users with differing roles is more suited for laravels gates and policy authorization mechanisms.
https://laravel.com/docs/5.5/authorization#writing-gates
These allow you to write fine tuned rules for each use case you have. Simple gates can be defined as closures within your application AuthServiceProvider. For example:
public function boot()
{
$this->registerPolicies();
Gate::define('access-dashboard', function ($user, $post) {
return auth()->check() && (auth()->user()->hasRole('admin') || auth()->user()->hasRole('regular'));
});
}
Then you can use the gate facade wherever necessary, for instance a controller method or constructor.
if (Gate::allows('access-dashboard', $model)) {
// The current user can access dashboard, load their data
}
Alternatively use the can or cant helpers on the user model directly.
if (auth()->user()->can('access-dashboard')) {
//
}
Of course, you can achieve similar via middleware, the advantage of using the above is you can authorize actions at specific points in your code as well as reusability.
As for for last question, as you have it written is correct.
{{ Auth::user()->admin_username }}
Auth::user() or auth()->user() simply returns the currently authenticated user, regardless of their role.
Policies will never work without auth middleware

Laravel 5 route protection

Assume we´ve got a User and Conversation model with a many-to-many relation.
class User extends Model ... {
public function conversations()
{
return $this->belongsToMany('App\Conversation');
}
}
class Conversation extends Model {
public function users()
{
return $this->belongsToMany('App\User');
}
}
Besides authentication (logging in) which comes out of the box with laravel: How can I protect a specific conversation route for it´s related users?
Which would be the most maintainable way to achieve this? Middleware? Guard? Route model binding? ... right now I´m a bit lost ...
Good question. In this case you'd be best off using Laravel's authorization features. Here are the differences:
Middleware: used to run logic based on either routes or logged in / logged out state. So, if you want to block the conversations entirely from non-logged in users, use a middleware.
Authorization (policies): not to be confused with authentication, is intended for cases where the rules to block someone is not based on route but on some other, more specific reason. These reasons can be anything from roles, to teams, entity ownership, and so on. If you wanted to hide a conversation to only those in the conversation, you can create a policy that kicks the user back to their previous page if they were not in the conversation.
Here's a quick policy you might create:
class ConversationPolicy {
public function view(User $user, Conversation $conv) {
return in_array($user->id, $conv->users->pluck('id'));
}
}
You could check your policy in a controller like the following:
if($request->user()->can('view', $conversation))
{
return view('conversation', ['conversation' => $conversation]);
}
return back()->withError('You are not authorized to view this conversation');
Just be aware you'll have to bind this policy in the AuthServiceProvider before it can be used.

Laravel inject sentry user into model

I keen to make my code decouple and ready for testing.
I have an Eloquent model getBudgetConvertedAttribute is depend on sentry user attribute.
public function getBudgetConvertedAttribute()
{
return Sentry::getUser()->currency * $this->budget;
}
This throw error while testing because Sentry::getUser is return null.
My question is, How shall I code to inject user into model from controller or service provider binding or testing?
Inject a $sentry object as a dependency in the constructor instead of using the Sentry Facade.
Example
use Path\To\Sentry;
class ClassName
{
protected $sentry
public function __construct(Sentry $sentry)
{
$this->sentry = $sentry;
}
public function methodName()
{
$this->sentry->sentryMethod();
}
}
Why not just create a method on the model, then takes a Sentry user object as a parameter?
public function getBudgetConverted(SentryUser $user)
{
return $user->currency * $this->budget;
}
You’ll need to change the type-hint (SentryUser) to the actual name of your user class.
If this is to aid testing, you could go one step furhter and type-hint on an interface (which you should be any way), that way you could test your method with a mock user object rather than one that may have a load of other dependencies like a database connection, which Eloquent models do.

How to restrict access to controllers functions using sessions - CodeIgniter

I made an application where there are two controllers (user and home). The user controller performs actions such as registering a user and logging them in/out, while the home controller is used to perform actions once the user is authenticated.
I found this answer on SO: What is the best practice for restricting specific pages to logged in users only in Codeigniter?
But I saw that the verifying of a session value is performed in a separate, custom controller. I was wondering why that's necessary?
In my home controller, I have a function that checks to see whether a session value for an item is set to true and allow the user the functionality provided in the home controller, otherwise redirect them back to the user controllers index method which loads the login view. This function is called in the constructor of the home controller like so:
public function __construct()
{
parent::__construct();
$this->is_loggedIn();
}
public function is_loggedIn()
{
$check_login = $this->session->userdata('isLoggedIn');
if(!isset($check_login) || $check_login != 1)
{
redirect('user');
}
}
This works great; the user is not allowed access to pages/functions within the home controller unless they are authenticated.
However, this isn't the case for functions within my user controller. If I login and then logout, I can still access a function within the user controller i.e. localhost/sitename/user/register even though I've logged out and shouldn't be able to access such functions. They should redirect to the login page (user controllers index method) instead, but instead allow the user to carry on using the functions in the user controller.
I tried including the above is_loggedin() function inside the user controller to check whether the users session is valid and call the function from the user controllers constructor, but I ended up with a redirect loop (rightfully so).
So I was wondering how it would be possible to disallow access to the user controllers functions when the user is not authenticated.
Another way I've thought of is to include the is_loggedin() function in each of the functions within the user controller, but I was wondering if there was a cleaner way to do this. For now this is my temporary fix. Please let me know if there is a more OOP friendly way
You should only check if the session === FALSE.
public function __construct()
{
parent::__construct();
$this->is_loggedIn();
}
public function is_loggedIn()
{
$check_login = $this->session->userdata('isLoggedIn');
if($check_login === FALSE)
{
redirect('user');
}
}
Be sure to delete isLoggedIn when the user logs out.
$this->session->unset_userdata('isLoggedIn');

Resources