Symfony - $request->getSession() = null - php

Since day we are getting a php error from Google's ip's:
[client 66.249.xx.xx:xxxx] Got error 'PHP message: PHP Fatal error: Uncaught Symfony\\Component\\Debug\\Exception\\FatalThrowableError: Call to a member function get() on null in /usr/home/project/myProject/src/AppBundle/Service/LocaleListener.php:22
It seems that Symfony's $request->getSession() returns null sometimes. Can't figure out the reason. Here's where error occurs:
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!empty($request->getSession()->get('_locale'))) {
return;
}
...
}
Any idea?

With symfony you define firewalls, by default a user is authenticated only under one firewall.
For example security.yml:
firewalls:
secondfirewall:
pattern: ^/secondfirewall
provider: broker
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout: true
anonymous: true
I assume your user is logged under the main firewall
You'll have a session on all urls except them starting by /secondfirewall. This might explain why you don't get a session
To debugging which route is going into error you could add log to your listener and exit if no session available.
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if(null === $request->getSession()) {
// Log everything you want to debug, url, paremeters, body content etc
// Then you can redirect the user to the login page
return new RedirectResponse('someloginurl');
// Or you can just exit from your listener
return;
}
if (!empty($request->getSession()->get('_locale'))) {
return;
}
...
}
If as you mentionned in the comment the call which go into error are Google's robot calls, you could log :
$request->headers->get('User-Agent') -> check the returned string,
Do a regex with preg_match to exit from listener if it match the google's user agent.
Moreover you could configure the pages which google robots will parse with the robot.txt file, please check google documentation to controle the urls to crawl

You won't have a session for all requests. If it's from CLI for example. Also unless for some reason it's been designed that way, a "traditional" API request won't either.

Related

User null inside services with symfony 3.4.17

I want to get user inside a profile service, but using tokenStorage and SecurityContext, the user will be everytime null.
that's my services.yml file:
project.service.profiler:
class: Project\Service\Profiler
arguments:
- "#security.helper"
- "#=service('doctrine').getRepository('bundle:ProfileKey')"
- "#=service('doctrine').getRepository('bundle:ProfileKeyUsers')"
- "#=service('doctrine').getRepository('bundle:ProfileKeyRoles')"
- "#logger"
- "#security.token_storage"
- "#security.authorization_checker"
- "#fos_oauth_server.access_token_manager.default"
and that's my class Profiler
class Profiler
{
public function __construct(
Security $security,
ProfileKeyRepositoryInterface $profileKeyRepository,
ProfileKeyUserRepositoryInterface $profileKeyUserRepository,
ProfileKeyRoleRepositoryInterface $profileKeyRoleRepository,
$logger,
$tokenStorage,
$authChecker,
TokenManagerInterface $tokenManager
){
if ($tokenStorage && $tokenStorage->getToken() && $tokenStorage->getToken()->getUser()) {
$this->user = $tokenStorage->getToken()->getUser();
}
}
}
The problem is that tokenStorage->getToken is always null (I'm sure, I'm logged in!).
So, this profiler was called from a controller, where the user is present, then I suspect that when the profiler was called during the symfony loading flow, the user is not created yet.
finally, if I set this line of code:
$security->isGranted('IS_AUTHETICATED_FULLY'); --> thrown an Exception
or getToken method:
$security->getToken() --> return null
I obtain everytime this error:
Why this behaviour?
In previous symfony version (I mean 3.3) this problem never occurred.
Thanks a lot to anyone who helps me
Update 08/04/2019
Following the symfony3.2 docs (https://symfony.com/blog/new-in-symfony-3-2-firewall-config-class-and-profiler), this post says to check if the request is under firewall, otherwise the user token should be null.
But, in my case, I checked with debug toolbar that all it's ok.
Finally, I absolutely have no idea why the user token is null under my service
Here my security firewall section:
security:
restricted_area:
anonymous: ~
access_denied_url: /unauthorized
access_denied_handler: app.security.access_denied_handler
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /
Finally I've found the answer myself!!
So, the problem was in my class code inside the service class: I was trying to obtain the user directly in the constructor (in the symfony flow, services are loaded before token management), and here the TokenStorage->getToken was always null.
So, when I need to obtain the user inside the service procedures, the tokenStorage->getToken() returns the correct value.
Hope that this answer can help someone with my same (old) problem.

How to redirect to external url in symfony 4 logout

Just wondering if there is an easy solution to this in Symfony 4. Normally users would logout and be sent back to the home page. But there is one page where it is checked that the user is currently authenticated on another site, if this is incorrect I have a link that logs the user out of my site and redirects to the external site. I managed this on an old silex based version of the site using the following routing of controllers in app.php
$app->get('/logout', $app->factory(function() use($app) {
$pid = $app['request_stack']->getCurrentRequest()->query->get('pid');
$message = $app['request_stack']->getCurrentRequest()->query->get('message');
$redirect = $app['request_stack']->getCurrentRequest()->query->get('redirect');
return $app->redirect(FRAMEWORK_URL."/logout?pid=$pid&message=$message&redirect=$redirect");
})
);
Thanks
Martyn
Set value of logout.target for a firewall in security.yaml to an external URL:
firewalls:
main:
...
logout:
...
target: 'EXTERNAL URL'
Value of logout.target could be an URL or app route name. App route and related controller could be used to create dynamic redirect targets.
Unfortunately logout.target will not work as there is special check (in Symfony\Component\Security\Http\HttpUtils::createRedirectResponse) to match the request domain to the redirection domain. If they're not the same it will set the target to /.
Proper way of redirecting to external URL is to create class which implements Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface, for example like this:
<?php
// src/Security/CustomLogoutSuccessHandler.php
class CustomLogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
private $target;
public function __construct(string $target)
{
$this->target = $target;
}
public function onLogoutSuccess(Request $request)
{
return new RedirectResponse($this->target);
}
}
Then create service in services.yaml:
services:
App\Security\CustomLogoutSuccessHandler:
arguments: ['%env(resolve:LOGOUT_TARGET_URL)%'] # resolve from .env (or you can get from anywhere)
and finally tell security.yaml to use your handler instead of default one:
security:
firewalls:
main:
logout:
success_handler: 'App\Security\CustomLogoutSuccessHandler'
Ok so tried this
I made a new route in a controller:
/**
* #Route("/redirectafterlogout/{url?/}", name="redirectafterlogout")
*/
public function redirectafterlogout($url)
{
if($url == "/"){
// redirects to the "homepage" route
return $this->redirectToRoute('homepage');
} else {
return $this->redirect('http://'.$url);
}
}
I added this to security.yaml
logout:
path: logout
target: 'redirectafterlogout'
it works fine and logs me out and puts me on the homepage if i use the normal logout link. However, if i try a link like:
it redirects me but does not seem to log me out? I can go back with me browser and I'm still logged in?
Not sure what I am doing wrong?
Thanks

How to get rid of "You must configure the check path to be handled by the firewall" error with GET requests?

When I am authenticating usual way (using login form), it works all right. I am getting this error only when /check_form is accessed via GET method directly, in which case an exception being thrown:
You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.
Here is the relevant security.yml part:
firewalls:
acme_area:
pattern: ^/(acme|admin)/
provider: fos_userbundle
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: acme_login
check_path: /acme/login_check
logout:
path: /acme/logout
target: acme_login
anonymous: true
I am using 2.3, thus no methods option is applicable (though I have no idea if it would help).
It is not really an issue as no proper usage could be spoiled by this error, but it pollutes the error log when some diligent bot is visiting the site and it's just untidy. So, I'd like to know which configuration option I can change to get rid of this error.
To boil this down, it seems that I want some 4xx error to be thrown instead of 500. Ideally it should be 405 Method Not Allowed, but 404 cold do too.
EDIT:
As as I learned from the Alex's answer below, this happens because POST requests are handled by the firewall and GET requests by the Controller. Thus, it seems that default checkAction() have to be extended to be able to handle two cases:
When request is POST but no firewal entry is present (already nandled)
When firewall entry is present but request is GET (my case)
There is no configuration option for that. If the request reach the controller, it unconditionally throws the exception: credible source.
POST request to the route are handled by firewall: official docs; GET ones go to the controller as usual.
There are few options to get rid of the error in the log, if you don't care about such events. The simplest one in my opinion is to override SecurityController::checkAction to return 500 error without throwing an exception. The official docs how to achieve it: Overriding Default FOSUserBundle Controllers.
EDIT:
In the controller you can return whatever code you like:
public function checkAction()
{
return new Response('', 418); // or better use Response constants
}
Another way is to disable GET method to /acme/login_check in the routing config, and let router do its job and return normal 405 Method Not Allowed as usual.
EDIT2:
You can analyse request in the action, and still throw an exception:
public function checkAction(Request $request)
{
if ($request->getMethod() == Request::METHOD_POST) {
throw new \RuntimeException('You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.');
} else {
return new Response('', Response::HTTP_METHOD_NOT_ALLOWED);
}
}
but I would recommend to debug your routes instead. This logic should belong to the router, not controller. In the long run, your routing config will mislead devs who will maintain this code, and they will have several hard debugging hours trying to figure out why it returns 405, when app/console debug:router clearly states that GET method is allowed.

Symfony authentication providers

I'm using fr3d/ldap-bundle. It logs me in and imports users from AD if they're not in db. That's fine.
Despite AD users I also have local users, which are in my db. There is special column authType which says how user should be authenticated - via LDAP or natively ( FOS ). I've created my own user provider:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
PROBLEM: Chain provider isn't an option - it allows user to login with his LDAP password AND with his local password! That's a big security issue.
Is there a way to login user via different authentication providers, depending on the db field?
EDIT:
My security.yml:
providers:
fos_userbundle:
id: fos_user.user_provider.username
appbundle_user_provider:
id: appbundle.user_provider
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin.*
context: user
fr3d_ldap: ~
form_login:
provider: appbundle_user_provider
csrf_provider: security.csrf.token_manager
always_use_default_target_path: true
default_target_path: admin_main
login_path: /admin/login
check_path: /admin/login_check
logout:
path: /admin/logout
target: /admin/login
anonymous: true
Here is security.yml. This line fr3d_ldap: ~ enables the ldap bundle, which authorize ldap users and saves them into my db. Without it I cannot authorize them, probably I would have to write custom AuthenticationProvider.
I am not very familiar with ldap but I would suggest try doing a completely manual login
$token = new UsernamePasswordToken($user, null, "firewallname", $user->getRoles());
$securityContext = $this->container->get('security.context');
$securityContext->setToken($token);
Then you can manually do the checks yourself, and depending on the result of the check decide how you want to verify the user before authenticating. For example, run a query by username and password before executing this login code or whatever, depending on the db field you want.
Your approach seems fine but you should check logic of your methods.
First of all this one:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
You pass $username to this method as an argument, but then use $user object, which seems to be undefined in current context.
Secondly:
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
So as chooseProviderForUsername method actually does not return any value you are not able to chain it this way.
I hope refactoring these issues should make your provider work properly.
Ok, so very brief answer, but I think at the moment Symfony is searching for the user amongst any old User Provider rather than the one you want it to for that particular user (which explains the whole logging in with two passwords thing). A solution should be to make AppBundleUserProvider implement UserProviderInterface, remove the other User Providers from security.yml and then to ensure that the first thing AppBundleUserProvider does it to find out which User Provider is required for that user then mimic it for every method in the UserProviderInterface. You could set $this->realUP based on Username, then set every method to just return $this->realUP->someMethod().
The cleanest way I can think of is to create your own ChainProvider class that only allows login with one provider and use the Dependency Injection Container to use yours.
You just need to override the security.user.provider.chain.clas parameter definition in your bundle's config file.

How to disable redirection after login_check in Symfony 2

I need to disable redirection after login check, because I need to get only that the login was success or not. After submission /login_check url give me the right data, but keep redirecting to /login (on failure).
/login is blank after that.
I am trying to set up login form using extjs 4 so I need to validate trough an ajax post request.
login_check should authenticate, create user session and return whether it was success or failure, but no forwarding anywhere.
my login.html.twig looks like:
{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
{ success:true }
{% else %}
{ success: false }
{% endif %}
and in security.yml:
firewalls:
main:
form_login:
provider: fos_userbundle
failure_path: null
failure_forward: false
Create an authentication handler:
namespace YourVendor\UserBundle\Handler;
// "use" statements here
class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
AuthenticationFailureHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
return new Response(json_encode($result));
} else {
// Handle non XmlHttp request here
}
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => false);
return new Response(json_encode($result));
} else {
// Handle non XmlHttp request here
}
}
}
Register the handler as a service:
services:
authentication_handler:
class: YourVendor\UserBundle\Handler\AuthenticationHandler
Register the service in the firewall:
firewalls:
main:
form_login:
success_handler: authentication_handler
failure_handler: authentication_handler
This is a rough example to give you the general idea — you'll need to figure out the details by yourself. If you're stuck and need further clarifications, put your questions in the comments and I'll try to elaborate the example.
The normal symfony flow is to redirect you to a login page if you are not logged in, which works fine for humans. But you seems to be looking for a programmatic solution.
Have you tried setting _target_path in your form, to declare what the "next page" should be? Symfony is always going to forward you somewhere, but you can set that somewhere to wherever you want.
I found these two pages useful for describing the inner workings of the login form:
How to customize your form login (search for _target_path)
Security page in the handbook

Categories