Symfony2 debug log doesn't show username - symfony

I've got a problem with debug logs – if an error occured error log doesn't show which user causes error, but set username blank — security.DEBUG: Username "" was reloaded from user provider. [] []
I use custom entity provider written according cookbook tutorial. Other messages – for example security.INFO: User "......" has been authenticated successfully is displayed correctly.
Note: I post this problem also to the forum.

I had the same issue and it took me a lot of digging to figure this out.
The problem is that you don't serialize the username in the serialize function of the User object.
Many people use the following for their serialization functions:
public function serialize()
{
return serialize($this->id);
}
public function unserialize($data)
{
$this->id= unserialize($data);
}
This way only the id is known when loading the user from the session token (note this also breaks the ?_switch_user=_exit functionality).
To fix this you should use the following code:
public function serialize()
{
return serialize(array($this->id,$this->username));
}
public function unserialize($data)
{
list($this->id,$this->username) = unserialize($data);
}
This way the username AND id are available and the issue is fixed.
NOTE: The 'refreshUser' function of your custom user provider will NEVER be used as it will be overruled by the EntityUserProvider!

Related

Best way to use voters/permissions on Symfony messenger async message handler? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
I am developing an application that have a Symfony Messenger component installed to handle async messages. The handler of message need to check some permissions for some particulars users, like if one determinate user should receive an email with information if have edition permissions for example.
To achieve that we use Symfony voters, but when we haven't any user logged into the system like in console commands and async messages is very annoying. What is the best solution to that? That are my main ideas:
Force a "login" with the security context for message
Pro: One way to check permissions without additional services. Voter is the service.
Cons: When I have a collection of users check I should do "security context login" action multiple times. I think that is hard.
Design a domain service to handle that.
Pros: Solves the problem without force a login
Cons: Duplicate code or differents ways to do the same things depending on the context (request, console command or async queue)
A service that should be called by voter and domain service
Cons: I think that add complexity to more simple issue
What is the best way? Any ideas outside of the previous three points?
Thank you so much
I would probably prefer to check user's permissions before dispatching a message, but let's think how we can approach if it's not a suitable case.
In order to check user permissions, you need to authenticate a user. But in case you're consuming a message asynchronously or executing a console command it's not straightforward, as you don't have an actual user. However, you can pass user id with your message or to a console command.
Let me share my idea of a simple solution for Symfony Messenger. In the Symfony Messenger, there is a concept of Stamps, which allows you to add metadata to your message. In our case it would be useful to pass a user id with a message, so we can authenticate a user within the message handling process.
Let's create a custom stamp to hold a user id. It's a simple PHP class, so no need to register it as a service.
<?php
namespace App\Messenger\Stamp;
use Symfony\Component\Messenger\Stamp\StampInterface;
class AuthenticationStamp implements StampInterface
{
private $userId;
public function __construct(string $userId)
{
$this->userId = $userId;
}
public function getUserId(): string
{
return $this->userId;
}
}
Now we can add the stamp to a message.
$message = new SampleMessage($payload);
$this->messageBus->dispatch(
(new Envelope($message))
->with(new AuthenticationStamp($userId))
);
We need to receive and handle the stamp in order to authenticate a user. Symfony Messenger has a concept of Middlewares, so let's create one to handle stamp when we receive a message by a worker. It would check if the message contains the AuthenticationStamp and authenticate a user if the user is not authenticated at the moment.
<?php
namespace App\Messenger\Middleware;
use App\Messenger\Stamp\AuthenticationStamp;
use App\Repository\UserRepositoryInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class AuthenticationMiddleware implements MiddlewareInterface
{
private $tokenStorage;
private $userRepository;
public function __construct(TokenStorageInterface $tokenStorage, UserRepositoryInterface $userRepository)
{
$this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository;
}
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
/** #var AuthenticationStamp|null $authenticationStamp */
if ($authenticationStamp = $envelope->last(AuthenticationStamp::class)) {
$userId = $authenticationStamp->getUserId();
$token = $this->tokenStorage->getToken();
if (null === $token || $token instanceof AnonymousToken) {
$user = $this->userRepository->find($userId);
if ($user) {
$this->tokenStorage->setToken(new UsernamePasswordToken(
$user,
null,
'provider',
$user->getRoles())
);
}
}
}
return $stack->next()->handle($envelope, $stack);
}
}
Let's register it as a service (or autowire) and include into the messenger configuration definition.
framework:
messenger:
buses:
messenger.bus.default:
middleware:
- 'App\Messenger\Middleware\AuthenticationMiddleware'
That's pretty much it. Now you should be able to use your regular way to check user's permissions, for example, voters.
As for console command, I would go for an authentication service, which would authenticate a user if the user id is passed to a command.

How is the callback from a resource owner processed in HWIOAuthBundle?

I am trying to understand how HWIOauthBUndle works. I can see how the initial authorization request to a resource owner is built and made.
I do not see however, how a callback made from a resource owner triggers any controller/action in my application (which it most obviously does, though).
When following the generally available instructions, the callback will be made to something like <path to my app>/check-[resourceOwner], e.g. http://www.example.com/oauth/check-facebook.
In my routing.yml file, I put
facebook_login:
pattern: /oauth/check-facebook
I don't see how any controller is associated with that route, so what actually happens when a callback is made to my application?
The authentication provider system is one of the more complicated features. You will probably want to read through here: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
Callbacks are handled through a request listener. Specifically:
namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall\OAuthListener;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
class OAuthListener extends AbstractAuthenticationListener
{
public function requiresAuthentication(Request $request)
{
// Check if the route matches one of the check paths
foreach ($this->checkPaths as $checkPath) {
if ($this->httpUtils->checkRequestPath($request, $checkPath)) {
return true;
}
}
return false;
}
protected function attemptAuthentication(Request $request)
{
// Lots of good stuff here
How checkPaths get's initialized and how all the calls are made would require a very long explanation. But the authentication provider chapter will get you going.

Authentication and Custom Error pages

I have a site that is using Azure ACS for authentication, backed by ADFS. When things are going well and people do things they are supposed to its great but that doesn't happen always so we have been implementing custom error pages.
The problem is, it doesn't seem to catch authentication errors, such as
ID3206: A SignInResponse message may only redirect within the current web application
Key not valid for use in specified state.
These errors still produce the ugly yellow error screen no matter what I say in my web.config. They are clearly ASP.NET errors and not IIS errors, so my question is how and where can I put custom error pages to display such errors in a 'pretty' way, as setting a page in web.config isn't working?
EDIT: To be clear, we have ACS set up to use an error page, have customErrors on with a different error page, neither or being used.
You have to have an action on a controller in your web app that accepts a POST from ACS and takes a parameter of type string. You must also configure your relying party application in ACS to point to that action for errors. Then in the action code you can do something like this:
namespace ASPNETSimpleMVC.Controllers
{
public class ErrorController : Controller
{
// Errors can be mapped to custom strings here.
static Dictionary<string, string> ErrorCodeMapping = new Dictionary<string, string>();
static ErrorController()
{
ErrorCodeMapping["ACS50019"] = "You chose to cancel log-in to the identity provider.";
ErrorCodeMapping["ACS60001"] = "No output claims were generated. You may be unauthorized to visit this site.";
}
//
// POST: /Error/
//
// If an error occurs during sign-in, ACS will post JSON-encoded errors to this endpoint.
// This function displays the error details, mapping specific error codes to custom strings.
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult Index( string ErrorDetails )
{
// The error details contain an array of errors with unique error codes to indicate what went wrong.
// Additionally, the error details contain a suggested HTTP return code, trace ID, and timestamp, which may be useful for logging purposes.
ErrorDetails parsedErrorDetails = new JavaScriptSerializer().Deserialize<ErrorDetails>( ErrorDetails );
ViewData["ErrorMessage"] = String.Format( "An error occurred during sign-in to {0}. ", parsedErrorDetails.identityProvider );
// Loop through all ACS errors, looking for ones that are mapped to custom strings.
// When a mapped error is found, stop looking and append the custom string to the error message.
foreach ( ErrorDetails.Error error in parsedErrorDetails.errors )
{
if ( ErrorCodeMapping.ContainsKey( error.errorCode ) )
{
ViewData["ErrorMessage"] += ErrorCodeMapping[error.errorCode];
break;
}
}
return View( "Error" );
}
}
}
You may also find this article helpful.

Drupal 7 test login throws exception Undefined property: MyTest::$public_files_directory

I want to do some tests in my drupal's 7 module. So i followed drupal's SimpleTest Api and i implemented some tests
For example i'm testing the login process.
public function testLogin() {
$user = $this->drupalCreateUser(array());
$this->drupalLogin($user);
}
When i'm trying to login, i'm getting always this exception:
Undefined property: MyModuleTest::$public_files_directory drupal_web_test_case.php 1692 DrupalWebTestCase->curlInitialize()
After that exception login fails.
The verbose message was this:
User created with name zPUkpgzN and pass 6NXRDGWy5k Pass
GET http://www.example.com/user returned 200 (1 byte). Pass
Valid HTML found on "http://www.example.com/user" Pass
Failed to set field name to zPUkpgzN Pass
Failed to set field pass to 6NXRDGWy5k Fail
Found the Log in button Fail
Found the requested form fields at user Fail
User zPUkpgzN successfully logged in. Fail
I found this post http://drupal.org/node/1789942, and I followed some suggestions but the only thing i succeeded was this:
User created with name zPUkpgzN and pass 6NXRDGWy5k Pass
GET http://www.example.com/user returned 200 (1 byte). Pass
Valid HTML found on "http://www.example.com/user" Pass
Failed to set field name to zPUkpgzN Pass
Failed to set field pass to 6NXRDGWy5k Pass
Found the Log in button Fail
Found the requested form fields at user Pass
User zPUkpgzN successfully logged in. Fail
Any suggestions?
Thanks.
Thandem.
I fixed it a minute ago. The Exception remains but the test has succeeded.
I followed this approach:
public function testLogin() {
$user = $this->drupalCreateUser(array());
$user->roles[] = 'authenticated user'; //Defining user role
user_save( $user ); //Save user in testing database
$this->drupalLogin($user);//success!!!!
}
If you don't call parent::setUp(), $public_files_directory does not get defined.

Extending Access Token Expiration not functioning

I am at the intermediate level in php and am new with facebook development. I have looked through the facebook documents and Stack Overflow previous comments.
All I basically wanted to do was let the user log in with their Facebook account and display their name.
My php page has a graph, and the page auto refreshes every 2 or 5 min.
I authenticate and get the facebook first_name to put on the page.
$graph = $facebook->api('/me');
echo $graph['first_name'] to get the first name of the user .. (for which I thought that no access token was required).
After about 90 min. I have been receiving the error:
fatal error: Uncaught OAuthException: An active access token must be used to query information about the current user......
and I have no value ( 0 ), in the $facebook->getUser(); parameter
I do know that off line access permission has been depreciated, (and I have have this enabled in my apps advanced settings)
I am trying to get an extended access token. In the FB docs. I see:
https://graph.facebook.com/oauth/access_token?
client_id=APP_ID&
client_secret=APP_SECRET&
grant_type=fb_exchange_token&
fb_exchange_token=EXISTING_ACCESS_TOKEN
I included my information in the link(an existing valid access token and all) and received a access token:
access_token=AAADbZBPuUyWwBAFubPaK9E6CnNsPfNYBjQ9OZC63ZBN2Ml9TCu9BYz89frzUF2EnLttuZAcG2fWZAHbWozrvop9bQjQclxVYle7igvoZCYUAg2KNQLMgNP&expires=4050
Yet this token expired in about 1 hour or so.(....expires=4050)
I assume I am using server side auth because I am using PHP?
I assume you need to enable "deprecate offline_access" in your Apps Advanced Settings page. As this worked for me:
//added code in base_facebook.php inside the facebook class
public function getExtendedAccessToken(){
try {
// need to circumvent json_decode by calling _oauthRequest
// directly, since response isn't JSON format.
$access_token_response =
$this->_oauthRequest(
$this->getUrl('graph', '/oauth/access_token'),
$params = array( 'client_id' => $this->getAppId(),
'client_secret' => $this->getAppSecret(),
'grant_type'=>'fb_exchange_token',
'fb_exchange_token'=>$this->getAccessToken(),
));
} catch (FacebookApiException $e) {
// most likely that user very recently revoked authorization.
// In any event, we don't have an access token, so say so.
return false;
}
if (empty($access_token_response)) {
return false;
}
$response_params = array();
parse_str($access_token_response, $response_params);
if (!isset($response_params['access_token'])) {
return false;
}
return $response_params['access_token'];
}
The token can still be invalid for several reasons, See How-To: Handle expired access tokens.
Hope it helps
There's a bug on this:
https://developers.facebook.com/bugs/241373692605971
But, another question on SO has a workaround (user uninstalls and re-installs):
fb_exchange_token for PHP only working once user removes app

Resources