I cant retrieve authenticated webmaster in controller. As you can see below, i authenticate user in constructor via $this->middleware:
class DomainController
.....
public function __construct()
{
$this->middleware('auth:webmasters');
}
public function requestNewName(Request $request, Webmaster $webmaster, DomainRepositoryInterface $domainRepository): array
{
// $webmaster->id === null here
/** #var Webmaster $webmaster */
$webmaster = Auth::user(); // $webmaster->id === 1, all OK
$domainRepository->requestChangeName($webmaster, $request->input('newName', ''));
return ['result' => true];
}
....
I think i need to bind it somewhere, but i dont understand where or how?
P. S.
Now i have in AuthServiceProvider:
foreach ([Webmaster::class, Admin::class] as $class) {
$this->app->bind($class, static function($app) use ($class) {
$authenticated = Auth::user();
/** #noinspection GetClassUsageInspection */
return $authenticated && get_class($authenticated) === $class ? $authenticated : null;
});
}
}
And call this function in boot method. I bet that laravel has something for it.
You want the webmaster to be injected into your method by laravel's dependency injection.
The way you would do this is through the Service Container, which is the guy who handles the injections. When you as for a Webmaster $webmaster, it looks for a binding with that type, since you haven't done an explicit bind, it tries to give you an instance anyways, but that's a generic one.
All you gotta do is add this code in your service provider:
$this->app->bind('App\Webmaster', function ($app) {
return Auth::user();
});
Now laravel knows how you want Webmaster to be injected into the function.
This should be a comment but I don't have enough reputation. Your code looks ok to me. But one thing comes to mind.
Since $webmaster->id gives you null means that the Webmaster class is imported properly in the DomainController. But maybe you haven't imported the Webmaster class properly in the AuthServiceProvider. Also maybe dd(get_class($webmaster)); will help. What does Auth::user() return in your app, anyway?
Related
I have written an RBAC (Role-based access control) implementation within my Symfony project a few days ago using Symfony Voters.
This is my universal voter for checking if the member has the ability to, for example, create on a specific endpoint:
class AbilityVoter extends Voter
{
public function __construct(MemberRepositoryInterface $memberRepository)
{
$this-memberRepository = $memberRepository;
}
protected function supports(
string $attribute,
$subject
): bool {
if (!in_array($attribute, ['create'])) {
return false;
}
return true;
}
protected function voteOnAttribute(
string $attribute,
$subject,
TokenInterface $token
): bool {
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
/** #var Member $member */
$member = $user;
return $this->memberRepository->hasAbility(
$member->getId(),
$attribute,
$subject
);
}
}
There is a part I don’t think I've addressed though: how do I hook into that within command handlers or other such places in my code?
On some example endpoints, that means that restricting access to certain endpoints is not enough and there will need to be RBAC-aware logic somewhere in the endpoint that determines whether the request is allowable.
For example, if the member is not allowed to post content for others but the request contains someone else’s member ID, it should be denied, otherwise, it should be allowed.
Can someone suggest the best way to do this and implement proof-of-concept code for the example above? Should I create some custom voters (that extend my base class) and use that? Or do we need to inject a service into the command handler so that it makes that decision?
Can someone help with that? What would be the beast approach in this case? Thanks
I want to make some entries of Analysis publicly available. I tried to implement it with Policies but failed. I think it's because the AuthServiceProvider fails with AccessDeniedHttpException every time I try to access without an authorized user.
AuthServiceProvider
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Analysis::class => AnalysisPolicy::class
];
public function boot()
{
$this->registerPolicies();
}
}
AnalysisPolicy
public function view(User $user, Analysis $analysis)
{
if($analysis->demo === true){
return true;
}
return $user->id === $analysis->user_id;
}
AnalysisController
public function show(int $analysis)
{
$ana = Analysis::find($analysis);
$this->authorize('view', $ana);
...
}
I tried to just create a new Service Provider, but that didn't work either as I cannot call the registerPolicies function without extending from AuthServiceProvider.
Basically, all I want is to now check for anything if the demo Attribute is true.
Edit:
My Quick-Fix form now is just checking in the controller if it's a demo. But that's not a great solution in my opinion as I think the goal with Policies should be that I don't have Access Management in the Controller. So I'd love to find a better solution.
if(!$ana->demo){
$this->authorize('view', $ana);
}
Ok so it turns out you should always read the release notes of the latest Laravel version before asking a question.
As of Laravel 5.7 there is a proper solution for this:
public function update(?User $user, Post $post)
{
return $user->id === $post->user_id;
}
(https://laravel.com/docs/5.7/authorization#writing-policies)
By declaring $user optional, it's null for guest users and can be handled in the policy.
I have two entites Person and Nursery and a ManyToMany association between them.
A user can have the role ROLE_MANAGER and be a manager for several nurseries.
For that in every action on his dashboard I need to verify if he's linked to the nursery if I don't do it he can modify the nursery slug in the url and have access to a nursery that he is not linked with.
Is there a way to check that on every action in the nursery manager dashboard without copy/paste a verification code in every action ?
As I understood Symfony Events (or Voters ?) can do that but I've never used them before ...
EDIT : Maybe it's easier to understand with a little bit of code !
So my nursery dashboard function is :
public function dashboardAction($nursery_slug)
{
//$currentUser = $this->get('security.token_storage')->getToken()->getUser();
$nurseryRepo = $this->getDoctrine()->getRepository('VSCrmBundle:Nursery');
$nursery = $nurseryRepo->findOneBy(array('slug' => $nursery_slug));
// Sometimes may help
if(!$nursery)
{
throw $this->createNotFoundException("The nursery has not been found or you are not allowed to access it.");
}
return $this->render("VSCrmBundle:Manager:dashboard.html.twig", array(
'nursery' => $nursery
));
}
To protect this dashboard I need to verify if the current user is linked to the nursery, somethink like :
$verification = $nurseryRepo->findOneBy(array('person' => $currentUser));
if(!$verification){throw accessDeniedException();}
But at the moment I'm obliged to do this test on every action in the manager dashboard ....
There are two things you need to implement to make this work smoothly.
First off, you need a NurseryVoter: http://symfony.com/doc/current/security/voters.html
Something like:
class NurseryVoter extends Voter
{
const MANAGE = 'manage';
protected function supports($attribute, $subject)
{
if (!in_array($attribute, array(self::MANAGE))) {
return false;
}
if (!$subject instanceof Nursery) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $nursery, TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}
// Check the role and do your query to verify user can manage specific nursery
Wire everything up per the link. And at this point your controller code is reduces to:
$this->denyAccessUnlessGranted('manage', $nursery);
Get all that working first. After that, use a Kernel:Controller event to move the deny access code from the controller to a listener. Follow the docs: http://symfony.com/doc/current/event_dispatcher.html
Your controller listener gets called after the controller is assigned but before the controller action is actually called. The trick here is how to determine which action actually needs the check to be done. There are a couple of approaches. Some folks like to flag the actual controller class perhaps by adding a NurseryManagerInterface. The listeners check the controller to see if it has the interface. But I don't really care for that.
I like to add this sort of stuff directly to the route. So I might have:
// routes.yml
manage_nursery:
path: /manage/{nursery}
defaults:
_controller: manage_nursery_action
_permission: CAN_MANAGE_NURSERY
Your listener would then check the permission.
Updated with a few more details on the kernel listener. Basically you inject the authorization checker and pull _permission from the request object.
class KernelListener implements EventSubscriberInterface
{
// #security.authorization_checker service
private $authorizationChecker;
public function __construct($authorizationChecker,$nuseryRepository)
{
$this->authorizationChecker = $authorizationChecker;
$this->nurseryRepository = $nuseryRepository;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::CONTROLLER => [['onController']],
];
}
public function onController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$permission = $request->attributes->get('_permission');
if ($permission !== 'CAN_MANAGE_NURSERY') {
return;
}
$nursery = $this->nurseryRepository->find($request->attributes->get('nursery');
if ($this->authorizationChecker->isGranted('MANAGE',$nursery) {
return;
}
throw new AccessDeniedException('Some message');
}
I am somewhat new to OOP, although I know about interfaces and abstract classes a bit. I have a lot of resource controllers that are somewhat similar in the bigger scheme of things, they all look like the example below, the only main difference is the index and what I pass to the index view.
What I simply need to know is, can I OO things up a bit with my resource controllers? For example, create one "main" resource controller in which I simply pass the correct instances using an interface for example? I tried playing around with this but I got an error that the interface wasn't instantiable, so I had to bind it. But that means I could only bind an interface to a specific controller.
Any advice, tips and pointers will help me out :)
class NotesController extends Controller
{
public function index()
{
$notes = Note::all();
return view('notes.index', compact('notes'));
}
public function create()
{
return view('notes.create');
}
public function show(Note $note)
{
return view('notes.show', compact('note'));
}
public function edit(Note $note)
{
return view('notes.edit', compact('note'));
}
public function store(Request $request, User $user)
{
$user->getNotes()->create($request->all());
flash()->success('The note has been stored in the database.', 'Note created.');
return Redirect::route('notes.index');
}
public function update(Note $note, Request $request)
{
$note->update($request->all());
flash()->success('The note has been successfully edited.', 'Note edited.');
return Redirect::route('notes.index');
}
public function delete($slug)
{
Note::where('slug', '=', $slug)->delete();
return Redirect::to('notes');
}
}
Note: Totally my opinion!
I would keep them how you have them. It makes them easier to read and understand later. Also will save you time when you need to update one to do something different from the rest. We tried this in a project I worked on and while granted it wasn't the best implementation, it is still a pain point to this day.
Up to you though. I'm sure people have done that in a way that they love and works great. Just hasn't been the case in my experience. I doubt anyone would look at your code though and criticize you for not doing it.
In Case you need to bind different Model instanses then you may use Contextual Binding, for example, put the following code in AppServiceProvider's register() method:
$this->app->when('App\Http\Controllers\MainController')
->needs('Illuminate\Database\Eloquent\Model')
->give(function () {
$path = $this->app->request->path();
$resource = trim($path, '/');
if($pos = strpos($path, '/')) {
$resource = substr($path, 0, $pos);
}
$modelName = studly_case(str_singular($resource));
return app('App\\'.$modelName); // return the appropriate model
});
In your controller, use a __construct method to inject the model like this:
// Put the following at top of the class: use Illuminate\Database\Eloquent\Model;
public function __construct(Model $model)
{
$this->model = $model;
}
Then you may use something like this:
public function index()
{
// Extract this code in a separate method
$array = explode('\\', get_class($this->model));
$view = strtolower(end($array));
// Load the result
$result = $this->model->all();
return view($view.'.index', compact('result'));
}
Hope you got the idea so implement the rest of the methods.
I am working in Laravel authentication login using socialite. Now I can able to save data of user from socialite. But now I am facing problem how to authenticate user from gmail, github.
After some research I understood that I need to create custom authentication. I googled but all are Laravel 4.1 topics. If any one work on this please provide your answers.
I already read following topics but I didn't got how to do it?
http://laravel.com/docs/5.1/authentication#social-authentication
http://laravel.com/docs/5.1/providers
http://laravel-recipes.com/recipes/115/using-your-own-authentication-driver
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Update
public function handleProviderCallback() {
$user = Socialite::with('github')->user();
$email=$user->email;
$user_id=$user->id;
//$authUser = User::where('user_id',$user_id)->where('email', $email)->first();
$authUser = $this->findOrCreateUser($user);
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
}
private function findOrCreateUser($user) {
if ($authUser = User::where('user_id',$user->id)->first()) {
return $authUser;
}
return User::create([
'user_id' => $user->id,
'name' => $user->nickname,
'email' => $user->email,
'avatar' => $user->avatar
]);
}
This answer is most suited for Laravel 5.1. Please take care if you
are in some other version. Also keep in mind that IMHO this is a rather advanced level in Laravel, and hence if you don't fully understand what you are doing, you may end up crashing your application. The solution is not end to end correct. This is just a general guideline of what you need to do in order for this to work.
Adding Custom Authentication Drivers In Laravel 5.1
Hint: Laravel documentation for this topic is here.
Hint2: The last link you mentioned is quite useful in my opinion. I learned all of this after reading that link.
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Before we start, I would first like to describe the login flow which will help you understand the process. Laravel uses a driver to connect to the database to fetch your records. Two drivers come pre-bundled with laravel - eloquent & database. We want to create a third so that we can customize it to our needs.
Illuminate\Auth\Guard inside your vendor folder is the main file which has code for the user to log in and log out. And this file mainly uses two Contracts (or interfaces) that we need to override in order for our driver to work. From Laravel's own documentation read this:
The Illuminate\Contracts\Auth\UserProvider implementations are only
responsible for fetching a Illuminate\Contracts\Auth\Authenticatable
implementation out of a persistent storage system, such as MySQL,
Riak, etc. These two interfaces allow the Laravel authentication
mechanisms to continue functioning regardless of how the user data is
stored or what type of class is used to represent it.
So the idea is that for our driver to work we need to implement Illuminate\Contracts\Auth\UserProvider and Illuminate\Contracts\Auth\Authenticatable and tell Laravel to use these implementations instead of the defaults.
So let's begin.
Step 1:
Choose a name for your driver. I name mine socialite. Then in your config/auth.php, change the driver name to socialite. By doing this we just told laravel to use this driver for authentication instead of eloquent which is default.
Step 2:
In your app/Provider/AuthServiceProvider in the boot() method add the following lines:
Auth::extend('socialite', function($app) {
$provider = new SocialiteUserProvider();
return new AuthService($provider, App::make('session.store'));
});
What we did here is:
We first used Auth facade to define the socialite driver.
SocialiteUserProvider is an implementation of UserProvider.
AuthService is my extension of Guard class. The second parameter this class's constructor takes is the session which laravel uses to get and set sessions.
So we basically told Laravel to use our own implementation of Guard class instead of the default one.
Step 3:
Create SocialiteUserProvider. If you read the Laravel's documentation, you will understand what each of these methods should return. I have created the first method as a sample. As you can see, I use my UserService class to fetch results. You can fetch your own results however you want to fetch them. Then I created an User object out of it. This User class implements the Illuminate\Contracts\Auth\Authenticatable contract.
<?php
namespace App\Extensions;
use App\User;
use App\Services\UserService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
class SocialiteUserProvider implements UserProvider
{
private $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function retrieveById($identifier)
{
$result = $this->userService->getUserByEmail($identifier);
if(count($result) === 0)
{
$user = null;
}
else
{
$user = new User($result[0]);
}
return $user;
}
public function retrieveByToken($identifier, $token)
{
// Implement your own.
}
public function updateRememberToken(Authenticatable $user, $token)
{
// Implement your own.
}
public function retrieveByCredentials(array $credentials)
{
// Implement your own.
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
// Implement your own.
}
}
Step 4:
Create User class which implements the Authenticatable. This class has to implement this interface because the Guard class will use this class to get values.
<?php
namespace App;
use Illuminate\Contracts\Auth\Authenticatable;
class User implements Authenticatable
{
protected $primaryKey = 'userEmail';
protected $attributes = [];
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
public function getUserAttributes()
{
return $this->attributes;
}
public function getAuthIdentifier()
{
return $this->attributes[$this->primaryKey];
}
public function getAuthPassword()
{
// Implement your own.
}
public function getRememberToken()
{
// Implement your own.
}
public function setRememberToken($value)
{
// Implement your own.
}
public function getRememberTokenName()
{
// Implement your own.
}
}
Step 5:
Finally create the AuthService class that will call the Guard methods. This is my own implementation. You can write your own as per your needs. What we have done here is extended the Guard class to implement two new functions which are self explanatory.
<?php
namespace App\Services;
use Illuminate\Auth\Guard;
class AuthService extends Guard
{
public function signin($email)
{
$credentials = array('email' => $email);
$this->fireAttemptEvent($credentials, false, true);
$this->lastAttempted = $user = $this->provider->retrieveById($email);
if($user !== null)
{
$this->login($user, false);
return true;
}
else
{
return false;
}
}
public function signout()
{
$this->clearUserDataFromStorage();
if(isset($this->events))
{
$this->events->fire('auth.logout', [$this->user()]);
}
$this->user = null;
$this->loggedOut = true;
}
}
Step 6: Bonus Step
Just to complete my answer, I will also explain the structure that UserService class expects. First lets understand what this class does. In our above steps we created everything to let laravel know how to use our authentication driver, instead of theirs. But we still haven't told laravel that how should it get the data. All we told laravel that if you call the userService->getUserByEmail($email) method, you will get your data. So now we simply have to implement this function.
E.g.1 You are using Eloquent.
public function getUserByEmail($email)
{
return UserModel::where('email', $email)->get();
}
E.g.2 You are using Fluent.
public function getUserByEmail($email)
{
return DB::table('myusertable')->where('email', '=', $email)->get();
}
Update: 19 Jun 2016
Thank you #skittles for pointing out that I have not clearly shown where the files should be placed. All the files are to be placed as per the namespace given. E.g. if the namespace is App\Extensions and the class name is SocialiteUserProvider then location of file is App\Extensions\SocialiteUserProvider.php. The App directory in laravel is the app folder.
Good tutorial for setting up laravel socialite here: https://mattstauffer.co/blog/using-github-authentication-for-login-with-laravel-socialite
Auth::login doesn't return a boolean value you can use attempt to do a Auth::attempt
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
Follow the tutorial and do this, and just have middleware configured on the home route
$authUser = $this->findOrCreateUser($user);
Auth::login($authUser, true);
return Redirect::to('home');