Could Symfony2 works without UserProviders? - php

The reason of this question was born from a collaborator's request: I want to know if I can remove the provider. Is mandatory? I want to remove it if I can leave its methods empty.
So, in Symfony providers configuration is mandatory. Also i need to configure at least one Provider. Done! My custom providere is this:
final class CustomListener implements UserProviderInterface
{
public function loadUserByUsername($username)
{
// ...
}
public function refreshUser(UserInterface $user)
{
return $user;
}
public function supportsClass($class)
{
// ...
}
}
I can login. Logout. ... but I am a bit confused. Why an interface want these "useless", at the moment, loadUserByUSername and supportsClass? Useless in the meaning of: I have empty implementation, and it works!!! Work, means that I set session manually with $request->getSession()->set('firefall', $token); and user is authenticated. I can logout users invalidating session.
I am sure that at least, this provider NEEDS that refreshUser return a UserInterface:
public function refreshUser(UserInterface $user)
{
return $user;
}
Because login does not work without this return. But I can leave other methods empty.
I've looked inside tests and in UserProviderInterface method's comments. But nothing. I didnt get if I can:
remove provider;
I am missing something in the documentation;
I am trying to badly use Symfony (it is possible);
I am a Teapot;
...
-
And, .... Why login does not work when I comment this line even if My firewall does not use this provider?
public function refreshUser(UserInterface $user)
{
// return $user;
}
--
I've created this CustomListener just because I've defined
a service as provider. Then I've received this message:
Argument 1 passed to Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider::__construct() must be an instance of Symfony\Component\Security\Core\User\UserProviderInterface,

The user provider tells Symfony where to load the users from, so you need one, but you don't need to create your own unless you have a complex authentication system.
If you're loading the users from the database you can use the default db provider http://symfony.com/doc/current/cookbook/security/entity_provider.html

Related

Symfony error on login page on implement UserLoaderInterface

I am using symfony version 5.4.4., and the first page of my application is a login page, but when I load it, I get this error:
You must either make the "App\Entity\mUser" entity Doctrine Repository
("App\Repository\mUserRepository") implement
"Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the
"property" option in the corresponding entity provider configuration.]
Whats causing this error?
Symfony does not know how to get the users from the Doctrine repository.
The error is pretty explicit about what you need to do.
You have two options:
Either you change your existing user repository (App\Repository\mUserRepository) so it implements the Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface
The interface has only one method:
public function loadUserByUsername(string $username);
So you'd need to do something like:
class App\Repository\mUserRepository implements UserLoaderInterface
{
public function loadUserByUsername(string $username): ?UserInterface
{
// implement logic to get user from the repository by its username.
}
}
Note that if you check that interface, the method loadUserByUsername() is actually deprecated, and has been replaced by loadUserByIdentifier() in Symfony 6+. To future-proof your implementation you should have something like:
class App\Repository\mUserRepository implements UserLoaderInterface
{
public function loadUserByUsername(string $username): ?UserInterface
{
return $this->loadUserByIdentifier($username);
}
public function loadUserByIdentifier(string $identifier): ?UserInterface
{
// implement logic to get user from the repository by its username.
}
}
Alternatively, the error message is telling you to just configure which property you use as identifier for your users.
Let's say that you get them by email, and there is an mUser::email property.
providers:
mUserProvider:
entity:
class: App\Entity\mUser
property: email

Resolve Laravel Auth Authenticatable to User model to address static analysis issues

We have a Laravel 8 application.
We're using the standard Laravel Auth facade to retrieve the authenticated user.
Our User model has a few custom functions, the most important of which is a shorthand function, hasPermissionTo(). (The reason why is because we have a very custom RBAC setup.)
So in a lot of our controllers, we have something like this...
use Illuminate\Routing\Controller as BaseController;
class ExampleController extends BaseController
{
public function index()
{
if (\Auth::user()->hasPermissionTo('Management:View Users')) {
// do something.
}
// etc.
}
}
That's all well and good until we start running static analysis. We're using Larastan, which is giving me these errors:
------ -------------------------------------------------------------------------------------------
Line Http/Controllers/ExampleController.php
------ -------------------------------------------------------------------------------------------
48 Call to an undefined method Illuminate\Contracts\Auth\Authenticatable::hasPermissionTo().
This also makes sense because the Auth facade proxies Illuminate\Auth\AuthManager and Auth::user(), via __call() magic, normally resolves to Illuminate\Auth\SessionGuard::user() and that typehints this...
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
...
So finally, my question:
Where is the failure here? Do I need to a) configure my static analysis tool better, b) configure Laravel better to more accurately return a specific type, or c) do I need to add explicit if (Auth::user() instanceof User) { ... } clauses all throughout my code?
Is there a correct way to override one of the Laravel stock classes with a more specific one of my own to address more specific functionality? Is there way to type-hint the actual authenticated User into the function declaration so I can declare function index(User $authenticatedUser) and have Laravel autopopulate this in with a more specific type hint?
I understand that I could just add an exclusion for this particular issue in Larastan and move on with my life, but the error is designed to protect against a specific class of error--i.e. if I added Auth0 and replaced App\Model\User with Auth0\Login\User, then I would have an Authenticatable class that fails to run hasPermissionTo(), and I'd have to now fix a bunch of code.
Eventually, this is how we worked around the problem. We added a type-hint for Larastan, so it can infer that $user has this HasRolesContract trait which provides hasPermissionTo().
public function index()
{
/** #var \App\Traits\HasRolesContract */
$user = \Auth::user();
if ($user->hasPermissionTo('Management:View Users')) {
Hopefully this helps someone else!
(Thanks for the nudge, #djjavo)

Symfony firewall login: How to access previous session before it gets invalidated

I am running a Symfony 2.8 based webpage which uses the FOSUserBundle. When the user switches from the public part of the webpage to the private part by logging in, the session is invalided (PHPSESSID changes). Thus after logging in it it not possible any more to access the session which was used on the public part.
In the Symfony docs I found information about the invalidate_session in the logout config.
While it makes sense to clean the session data when logging out, I do not understand what's the reason to the same when logging in.
Question 1:
Is there an option to prevent Symfony from invalidating the session when logging in?
Even if there an option to change this behavior I would preferr to keep it this way (to prevent any unforeseen side effects). This brings us to the second question:
Question 2:
Is there any event or other way that can be used to access the public session before it gets invalidated during the login process?
The Firewall.php uses an onKernelRequest handler with priority 8 to run its authentication methods. Thus I tried to use my on own onKernelRequest handler with a higher priority to access the session first, but this did not work out. I get only access to the new session.
How to solve this?
You should implement an EventSubscriber and subscribe to the events
SecurityEvents::INTERACTIVE_LOGIN and FOSUserEvents::REGISTRATION_COMPLETED. At that point the public session is not yet invalidated and you can get the user from the event.
namespace AppBundle\EventListener;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FilterUserResponseEvent;
class YourCustomListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onUserAuthentication',
FOSUserEvents::REGISTRATION_COMPLETED => ['onUserRegistration', -10]
];
}
public function onUserAuthentication(InteractiveLoginEvent $event): void
{
$user = $event->getAuthenticationToken()->getUser();
$this->yourFuntionUsingTheSessionHere($user);
}
public function onUserRegistration(FilterUserResponseEvent $event): void
{
$user = $event->getUser();
$this->yourFunctionUsingTheSessionHere($user);
}
private function yourFunctionUsingTheSessionHere(User $user): void
{
// do your thing here
// I don't know if Eventsubscribers are containeraware maybe you need to inject the container or Symfony\Component\HttpFoundation\Session\SessionInterface to have access to the session
}
}

FactoryMuff skips laravel model callbacks

When I write tests for a model, I put fake model creating inside the setUp method using FactoryMuff.
$this->user = FactoryMuff::create('User', array('password' => '12345678'));
Inside a model there is a saving callback that hashes the password. Callback is triggered (password becomes hashed) until the second test is reached (where password is not hashed). I even checked it with simple var_dump.
public function testFirst() { // $this->user is a good model }
public function testSecond() { // $this->user is a bad model }
Looks like I found an answer, I just had to call User::boot() manually in setUp to register callbacks

How to make a REST API first web application in Laravel

I want to make an API first application in Laravel. I don't know what is the best approach to do this, I will explain what I am trying to do, but please feel free to give answers how to do this in a different way.
I don't want all my frontend to be written in javascript and parse the JSON output of the API with angular.js or something similar. I want my Laravel application to produce the HTML views. I am trying to go down the road of having two controllers one on for the API and one for the web. For the show User action my routes.php looks like this:
# the web controller
Route::controller('user', 'WebUserController');
# the api controller
Route::group(array('prefix' => 'api'), function() {
Route::resource('user', 'UserController');
});
So /user will take me to WebUserController and /api/user will take me to the UserController. Now I want to put all my logic in the API UserController, and call its actions from the WebUserController. Here is the code for both of them:
class UserController extends BaseController
{
public function show($id)
{
$user = User::find($id);
return Response::json(array('success'=>true,'user'=>$user->toArray()));
}
}
class WebUserController extends UserController
{
public function getView($id)
{
# call the show method of the API's User Controller
$response = $this->show($id);
return View::make('user.view')->with('data', $response->getData());
}
}
In the WebUserController I am able to get the json content of the response with getData(), but I am not able to get the headers and status code (they are protected properties of Illuminate\Http\JsonResponse).
I think that my approach might not be the best, so I am open to suggestions how to make this app.
EDIT: The question how to get the headers and status of the response has been answered by Drew Lewis, but I still think that there might be a better way how to design this
You should utilize the Repository / Gateway design pattern: please see the answers here.
For example, when dealing with the User model, first create a User Repository. The only responsibility of the user repository is to communicate with the database (performing CRUD operations). This User Repository extends a common base repository and implements an interface containing all methods you require:
class EloquentUserRepository extends BaseRepository implements UserRepository
{
public function __construct(User $user) {
$this->user = $user;
}
public function all() {
return $this->user->all();
}
public function get($id){}
public function create(array $data){}
public function update(array $data){}
public function delete($id){}
// Any other methods you need go here (getRecent, deleteWhere, etc)
}
Then, create a service provider, which binds your user repository interface to your eloquent user repository. Whenever you require the user repository (by resolving it through the IoC container or injecting the dependency in the constructor), Laravel automatically gives you an instance of the Eloquent user repository you just created. This is so that, if you change ORMs to something other than eloquent, you can simply change this service provider and no other changes to your codebase are required:
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider {
public function register() {
$this->app->bind(
'lib\Repositories\UserRepository', // Assuming you used these
'lib\Repositories\EloquentUserRepository' // namespaces
);
}
}
Next, create a User Gateway, who's purpose is to talk to any number of repositories and perform any business logic of your application:
use lib\Repositories\UserRepository;
class UserGateway {
protected $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function createUser(array $input)
{
// perform any sort of validation first
return $this->userRepository->create($input);
}
}
Finally, create your User web controller. This controller talks to your User Gateway:
class UserController extends BaseController
{
public function __construct(UserGatway $userGateway)
{
$this->userGateway = $userGateway;
}
public function create()
{
$user = $this->userGateway->createUser(Input::all());
}
}
By structuring the design of your application in this way, you get several benefits: you achieve a very clear separation of concerns, since your application will be adhering to the Single Responsibility Principle (by separating your business logic from your database logic) . This enables you to perform unit and integration testing in a much easier manner, makes your controllers as slim as possible, as well as allowing you to easily swap out Eloquent for any other database if you desire in the future.
For example, if changing from Eloquent to Mongo, the only things you need to change are the service provider binding as well as creating a MongoUserRepository which implements the UserRepository interface. This is because the repository is the only thing talking to your database - it has no knowledge of anything else. Therefore, the new MongoUserRepository might look something like:
class MongoUserRepository extends BaseRepository implements UserRepository
{
public function __construct(MongoUser $user) {
$this->user = $user;
}
public function all() {
// Retrieve all users from the mongo db
}
...
}
And the service provider will now bind the UserRepository interface to the new MongoUserRepository:
$this->app->bind(
'lib\Repositories\UserRepository',
'lib\Repositories\MongoUserRepository'
);
Throughout all your gateways you have been referencing the UserRepository, so by making this change you're essentially telling Laravel to use the new MongoUserRepository instead of the older Eloquent one. No other changes are required.
You should be use Repository for this design.
Example -
//UserRepository Class
class UserRepository {
public function getById($id)
{
return User::find($id);
}
}
// WebUser Controller
class WebUserController extends BaseController {
protected $user;
public function __construct(UserRepository $user)
{
$this->user = $user;
}
public function show($id)
{
return View::make('user.view')->with('data', $this->user->getById($id));
}
}
// APIUser Controller
class UserController extends BaseController {
protected $user;
public function __construct(UserRepository $user)
{
$this->user = $user;
}
public function show($id)
{
$data =>$this->user->getById($id);
return Response::json(array('success'=>true,'user'= $data->toArray()));
}
}
Checkout Laravel's RESTful controllers:
http://laravel.com/docs/controllers#restful-controllers
Their docs do a pretty good job.
But even better is this tutorial:
http://code.tutsplus.com/tutorials/laravel-4-a-start-at-a-restful-api-updated--net-29785
This is a video by Jeffrey Way he is one of the better Laravel developers. In this tutorial he is connecting a BackboneJS application to a RESTful service that he sets up in Laravel. It doesn't get any better then this. I can write you a lot of boilerplate, but just learn it by watching a nice video and having a coffee. ;)
https://www.youtube.com/watch?v=uykzCfu1RiQ
I have a response to the problem you are having with the Response.
You can get the headers, status code and data from the Response.
// your data
$response->getData();
// the status code of the Response
$response->getStatusCode();
// array of headers
$response->headers->all();
// array of headers with preserved case
$response->headers->allPreserveCase();
$response->headers is a Symfony\Component\HttpFoundation\ResponseHeaderBag which inherits from Symfony\Component\HttpFoundation\HeaderBag
I would also recommend using a repository.
Attempting to call one controller from another would be falling into a pattern called HMVC (Hierarchical model–view–controller).
This means that your entire application relies on lower modules.
In this case, your API would serve as a repository for your data (which isn't the worst thing in the world at first).
However, when you then modify the structure of how data is returned in your API, everything else relying on it would have to know how to respond.
Say you wanted to have authorization checks to see if a logged in user should be able to see the details of a returned user and there was an error.
In the API, you would return a Response object with a 403 forbidden code and some meta data.
Your HTML controller would have to know how to handle this.
Contrast this to a repository which could throw an exception.
public function findById ($id)
{
$user = User::findOrFail($id);
if (Auth::user->hasAccessTo($user)) {
return $user;
} else {
throw new UnauthorizedAccessException('you do not have sufficient access to this resource');
}
}
And your API controller would look more like this:
public function show($id)
{
try {
return $this->user->findById($id);
} catch (UnauthorizedAccessException $e) {
$message = $e->getMessage();
return Response::json('403', ['meta' => ['message' => $message]]));
}
}
Your HTML controller would then look like this:
public function show($id)
{
try {
$user = $this->user->findById($id);
} catch (UnauthorizedAccessException $e) {
Session::flash('error', $e->getMessage());
// Redirect wherever you would like
return Response::redirect('/');
}
}
This gives you very reusable code and let's you change your controller implementations independently without worry of changing the other's behavior.
I wrote more on how to implement the repository pattern in this post: you can ignore the interface and skip right to the implementations if you would like.

Categories