Get user's plain password on registration with FOSUser bundle - symfony

I'm using the FosUserBundle to handle my application login/registration workflows. Then I need to obtain the user's plain password on registration, because I would like to generate an access token using the grant_type=password workflow with the FOSOAuthServer bundle.
I tried with the following events:
FOSUserEvents::REGISTRATION_INITIALIZE : No password here
FOSUserEvents::REGISTRATION_SUCCESS : Plain password but no user in database because this event is called right before the user creation
FOSUserEvents::REGISTRATION_COMPLETED: The user is created in the database, but the $event->getUser()->getPlainPassword() return null, because on user creation the method $userManager->updatePassword() set the plainPassword attribute to null
Do you have any secure idea of how can I handle this? I've got to main ideas but I'm not sure if it is the right way to do it:
Put the password in session on FOSUserEvents::REGISTRATION_SUCCESS and generate the access token then remove the password on FOSUserEvents::REGISTRATION_COMPLETED
Override the user manager class to make the method $userManager->updatePassword() do no call $userManager->eraseCredentials() and use it on registration (not surehow to do it)
Cheers guys :)

you can save plainpassword as new parameter on user model (for example as tempPlainPassword parameter ) at event FOSUserEvents::REGISTRATION_SUCCESS and use it whenever you want!
for security reasons you must empty value of that parameter (tempPlainPassword) after use it ;
namespace [someBundle]\UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RegistrationListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$user = $event->getForm()->getData();
$user->setTempPlainPass($user->getPlainPassword());
}
}

Related

Efficient way to access JWT data from Controllers

I'm using Symfony 5.4 with a custom authenticator which reads & validates a JWT with each request.
Inside the JWT is data which I need accessible in the controller.
Rather than re-read the JWT in the controller, I'd like to store the decoded data, or even 1 element of that data, so that it doesn't need to be re-read in a controller.
What is an efficient way to access data detected in an authenticator, so it is available in the context of a controller action?
I would implement service for this in Symfony, which should be something like this
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\RequestStack;
class JWTInterceptor
{
protected $request;
protected $data;
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
// Get JWT token from request header, decode and store it in $this->data
}
// Get decoded data
public function getData()
{
return $this->data;
}
}
And in your controller just use Dependency Injection to insert the service and call JWTInterceptor::getData() to use decoded data.
There should be other approach as well, like using EventListener or EventSubscriber or implement a root/base controller with relevant methods and make it accessible to all child controllers etc.
Or if you are using https://github.com/lexik/LexikJWTAuthenticationBundle it already comes packaged with events so you can modify as per your need.

Issue programmatically authenticating Users for PhpUnit functional test - Unmanaged by Doctrine - Symfony 4.3

I'm trying to get a simple "200 Response" test to work for a part of a website requiring an authenticated user. I think I've got the creation of the Session working, as during debugging the Controller function is called and a User is retrieved (using $this->getUser()).
However, afterwards the function fails with the following message:
1) App\Tests\Controller\SecretControllerTest::testIndex200Response
expected other status code for 'http://localhost/secret_url/':
error:
Multiple non-persisted new entities were found through the given association graph:
* A new entity was found through the relationship 'App\Entity\User#role' that was not configured to cascade persist operations for entity: ROLE_FOR_USER. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade
persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
* A new entity was found through the relationship 'App\Entity\User#secret_property' that was not configured to cascade persist operations for entity: test123. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade pe
rsist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). (500 Internal Server Error)
Failed asserting that 500 matches expected 200.
This would make sense if this was not already stored in the (MySQL) database and retrieved with Doctrine. The records are created using Fixtures on each run/for each test. This is why in the Controller $this->getUser() functions as expected.
The test I'm wanting to work:
public function testIndex200Response(): void
{
$client = $this->getAuthenticatedSecretUserClient();
$this->checkPageLoadResponse($client, 'http://localhost/secret_url/');
}
Get a user:
protected function getAuthenticatedSecretUserClient(): HttpKernelBrowser
{
$this->loadFixtures(
[
RoleFixture::class,
SecretUserFixture::class,
]
);
/** #var User $user */
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => 'secret_user']);
$client = self::createClient(
[],
[
'PHP_AUTH_USER' => $user->getUsername(),
'PHP_AUTH_PW' => $user->getPlainPassword(),
]
);
$this->createClientSession($user, $client);
return $client;
}
Create a session:
// Based on https://symfony.com/doc/current/testing/http_authentication.html#using-a-faster-authentication-mechanism-only-for-tests
protected function createClientSession(User $user, HttpKernelBrowser $client): void
{
$authenticatedGuardToken = new PostAuthenticationGuardToken($user, 'chain_provider', $user->getRoles());
$tokenStorage = new TokenStorage();
$tokenStorage->setToken($authenticatedGuardToken);
$session = self::$container->get('session');
$session->set('_security_<security_context>', serialize($authenticatedGuardToken));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
self::$container->set('security.token_storage', $tokenStorage);
}
This works for the creating of the client, session and cookie.
When the Request is executed to the $url in the first function, it gets into the endpoint, confirming the User is indeed authenticated.
According to the documentation here a User should be "refreshed" from via the configured provider (using Doctrine in this case) to check if a given object matches a stored object.
[..] At the beginning of the next request, it's deserialized and then passed to your user provider to "refresh" it (e.g. Doctrine queries for a fresh user).
I would expect this would also ensure that the session User is replaced with a Doctrine managed User object to prevent the error above.
How can I go about solving that the User in the session becomes a managed User during PhpUnit testing?
(Note: the production code works without any issue, this problem only arises during testing (legacy code now starting to get tests))
Ok, had multiple issues, but got it working doing the following:
First, was creating a Client using incorrect password, I was creating (in Fixtures) User entities with username and password being identical. The function getPlainPassword, though present in an interface, was not something stored, so was a blank value.
Corrected code:
$client = self::createClient(
[],
[
'PHP_AUTH_USER' => $user->getUsername(),
'PHP_AUTH_PW' => $user->getUsername(),
]
);
Next, a User not being refreshed took some more.
In config/packages/security.yaml, add the following:
security:
firewalls:
test:
security: ~
This is to create the "test" key, as creating that immediately in the next file will cause a permission denied error. In config/packages/test/security.yaml, create the following:
security:
providers:
test_user_provider:
id: App\Tests\Functional\Security\UserProvider
firewalls:
test:
http_basic:
provider: test_user_provider
This adds a custom UserProvider specifically for testing purposes (hence usage App\Tests\ namespace). You must register this service in your config/services_test.yaml:
services:
App\Tests\Functional\Security\:
resource: '../tests/Functional/Security'
Not sure you'll need it, but I added in config/packages/test/routing.yaml the following:
parameters:
protocol: http
As PhpUnit is testing via CLI, there by default is no secure connection, can vary by environment so see if you need it.
Lastly, config for test framework in config/packages/test/framework.yaml:
framework:
test: true
session:
storage_id: session.storage.mock_file
All of the above config (apart from the http bit) is to ensure that a custom UserProvider will be used to provider User objects during testing.
This might excessive for others, but our setup (legacy) has some custom work for providing Users for authentication (which seems very related but far out of my current issue scope).
Back on to the UserProvider, it's setup like so:
namespace App\Tests\Functional\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class UserProvider implements UserProviderInterface
{
/** #var UserRepository */
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function loadUserByUsername($username)
{
try {
return $this->userRepository->getByUsername($username);
} catch (UserNotFoundException $e) {
throw new UsernameNotFoundException("Username: $username unknown");
}
}
public function refreshUser(UserInterface $user)
{
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return User::class === $class;
}
}
Note: should you use this, you need to have a getByUsername function in your UserRepository.
Please note, this might not be the solution for you. Maybe you need to change it up, maybe it's completely off. Either way, thought to leave a solution for any future souls.

Dynamically assign controller action permissions to roles in asp.net MVC

I am working on asp.net mvc 5. I want to assign permissions of controllers' action methods to roles dynamically without hard conding the roles in Authorize attribute.
Here is the scenario -
In my project, I have four roles - Student, Teacher, Program Officer and Admin.
I want that admin can change the accessability of each role whenever he wishes. I do not want to hard code the authorize attribute with role names before every action name of controller because admin will not then be able to change the permissions of each role.
I want to create a page where every action method of controller will be listed as checkbox and admin can select action checkboxes for a role. Then that role users will get accessability of those action methods.
Here I want to make the UI as following -
Can anyone please help me to do this by giving any suggestion or code or link?
Imagine you have service which returns array of roles based on controller and action name like this:
public class RoleProvider
{
public string[] Get(string controller, string action)
{
// get your roles based on the controller and the action name
// wherever you want such as db
// I hardcoded for the sake of simplicity
return new string[]{"Student", "Teacher"};
}
}
Now you can write your own authorization attribute something like this:
public class DynamicRoleAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var controller = httpContext.Request.RequestContext
.RouteData.GetRequiredString("controller");
var action = httpContext.Request.RequestContext
.RouteData.GetRequiredString("action");
// feed the roles here
Roles = string.Join("," ,_rolesProvider.Get(controller, action));
return base.AuthorizeCore(httpContext);
}
}
Now use your custom authorization attribute instead of older one like this:
[DynamicRoleAuthorize]
public ActionResult MyAction()
{
}
I think the only way is to implement your own Authorize Attribute where you can implement your own logic for authorization.
And in your case you should have a table where associate roles and controllers action and check this table in your custom Authorize Attribute.
While this does not give you the dynamics web page assignment you're looking for, If you are flexible in your approach... you can set up an Enum list of Roles Admin, Editor editor etc, and pass them as a parameter object (ENUM) as a param, so that the DynamicRoleAuthorize can use it load the roles that are allowed
from vivians blog The constructor accepts parameters of type object, that is the little trick. If you use parameters of type Enum, you will get the same error message as above. We can do that because an Enum is an object.
To ensure that we are passing parameters of type Enum, we check the type of every roles. If one role is not of type Enum, the constructor will throw an ArgumentException.
Then we set the standard Roles property with the name of our roles with the string.Join method.
using System;
using System.Linq;
using System.Web.Mvc;
namespace MvcApplication.HowTo.Attributes
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeEnumAttribute : AuthorizeAttribute
{
public AuthorizeEnumAttribute(params object[] roles)
{
if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
throw new ArgumentException("roles");
this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
}
}
}

how to pass username to rollbar via monolog, symfony2

I am using rollbar.com to collect all details about exceptions in symfony2 app. However I don't understand how can I configure monolog so it would pass username and user id to rollbar.
I see that I can pass rollbar config as shown here and I am thinking person_fn is what I need. Still I don't know where to put this function (this should be in service because I need to check security token) and how to pass it to rollbar.
# config_prod.yml
rollbar:
type: rollbar
level: error
token: %rollbar_token%
config:
person_fn: getUserForRollbarRightAboutNowOrSomething
Found solution:
update monolog/monolog bundle to at least 1.17.0 version.
create ContextProcessor and update user information
#src/AppBundle/Monolog/RollbarContextProcessor
namespace AppBundle\Monolog;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class RollbarContextProcessor
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function processRecord($record)
{
if ($this->tokenStorage->getToken()) {
$user = $this->tokenStorage->getToken()->getUser();
if ($user instanceof User) {
$record['context']['payload']['person'] = [
'id' => $user->getId(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
];
}
}
return $record;
}
}
configure ContextProcessor as service with monolog.processor tag.
# app/config/config_prod.yml
services:
monolog.processor.rollbar_context:
class: AppBundle\Monolog\RollbarContextProcessor
arguments: [#security.token_storage]
tags:
- { name: monolog.processor, method: processRecord, handler: rollbar }
monolog:
handlers:
rollbar:
type: rollbar
level: error
token: %rollbar_token%
Your question has two parts:
Rollbar
person_fn is exactly what you need. You should be able to add a reference to the function by using a string (e.g.: "MyClass::static_function_reference" or "my_function_name").
Symfony
Disclaimer: I don't use or know much about Symfony.
This question has some excellent examples of how to get the current user in Symfony. (Punch line: in a controller you can call $this.getUser())
This question has a good example of how to inject the current user in a service. (Make a Twig Extension that depends on the SecurityContext or TokenStorage, use those dependencies to get a user objet).
Finally, there's the classic PHP move: as soon as you have a user add it to $_REQUEST. I'm not sure if Symfony co-opts this, but it'd be a valid way in a non-framework PHP application.

Detect first FOSFacebookBundle login

I implemented successfully the FOSFacebookBundle with the FOSUserBundle. Everything works great.
Now, I'd like to be able to detect the first connection of an user with the FOSFacebookBundle.
I have a FacebookAuthenticationSuccessHandler which is called every time a Facebook authentication successes, but I'd like to declare a kind of "FirstFacebookAuthenticationSuccessHandler", called only if the user didn't exist yet. The goal of this handler is to be able to send a welcome email to each new user.
For the classic registration, I simply use a RegistrationSuccessListener.
Do you have any idea on how I could do this?
Answering my own question as I found the solution.
1/ I created (and registered) a new event listener called NewFacebookUserListener:
...
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
MyCustomEvents::NEW_FACEBOOK_USER => 'onNewFacebookUser'
);
}
public function onNewFacebookUser(UserEvent $event)
{
// we get the user
$user = $event->getUser();
// and now you can do what you wish
}
2/ In my FacebookProvider (located under Security/User/Provider), I added a few lines to dispatch this new event:
$user->setFBData($fbdata);
// we send a "FB user created" event
$dispatcher = $this->container->get('event_dispatcher');
$dispatcher->dispatch(MyCustomEvents::NEW_FACEBOOK_USER, new UserEvent($user, $this->container->get('request')));
And as expected, the event is dispatched only when a new Facebook user is created.

Resources