[GUZZLE][SYMFONY3] listen guzzle exception event - php

First, i apologize for my bad english.
I'm here because i'm looking for some way to listen exception guzzle event to redirect to login page if i receive status code 401.
I found an event "PostTransactionEvent" allowing to get data struct of my response. It makes his job but i can't redirect to login page. It seems RedirectResponse method was not executed.
services.yml :
glpi.expire_listener:
class: GlpiBundle\Expire\ExpireListener
arguments: ["#router","#request_stack"]
tags:
- { name: kernel.event_listener, event: guzzle_bundle.post_transaction, method: check }
ExpireListener.php
namespace GlpiBundle\Expire;
use EightPoints\Bundle\GuzzleBundle\Events\GuzzleEventListenerInterface;
use EightPoints\Bundle\GuzzleBundle\Events\PostTransactionEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class ExpireListener implements GuzzleEventListenerInterface
{
protected $service_name;
protected $request_stack;
public function __construct($router,$request_stack)
{
$this->router = $router;
$this->request_stack = $request_stack;
}
public function check(PostTransactionEvent $event)
{
$response_transaction = $event->getTransaction();
$e = new ExpireApi();
$available = $e->deconnect($response_transaction);
if ($available) {
return new RedirectResponse($this->router->generate('logout'));
}
$event->setTransaction($response_transaction);
}
public function setServiceName($serviceName){
$this->service_name = $serviceName;
}
}
request :
$reponse_categories =$client->get('/apirest.php/itilcategory?searchText[itilcategories_id]='.self::ID_CAMPUS_ID,
[
"headers"=>
[
"App-Token"=>TOKEN,
"Session-Token"=>SESSION
],
'exceptions'=>false
]);
Thanks in advance for your help,

Use this code:
// redirect to login page
$redResponse = new RedirectResponse( $this->router->generate('login') );
$redResponse->send();
Here is the full code:
https://github.com/viher3/yuwik-frontend/blob/develop/src/EventListeners/Guzzle/ExpiredJwt.php
Regards!

Related

Adding public data to the JWT response with lexik

I am currently using Symfony 5 with lexik and when I to generate the JWT token, I would like for the response to get me the token and the username so I could have something like this:
{
"username":"username"
"token": "token"
}
I tried of course to use the doc here : https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/2-data-customization.md#eventsauthentication_success---adding-public-data-to-the-jwt-response but whenever I test it in Postman, I still only get the token and I am completely stuck on what to do...
My EventListener:
<?php
namespace App\EventListener;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
class AuthenticationSuccessListener
{
/**
* #param AuthenticationSuccessEvent $event
*/
public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $event)
{
$data = $event->getData();
$user = $event->getUser();
if (!$user instanceof UserInterface) {
return;
}
$data['data'] = array(
'username' => $user->getUsername(),
);
$event->setData($data);
}
}
the service in services.yaml
acme_api.event.authentication_success_listener:
class: App\EventListener\AuthenticationSuccessListener
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.handler.authentication_success, method: onAuthenticationSuccessResponse }
Any help would be greatly appreciated
Try to add the user interface use inside your Listener it might be that you are not getting throw your if statment because of this:
use Symfony\Component\Security\Core\User\UserInterface;

Dynamic route prefix in Symfony2

I am creating a SaaS with symfony2 providing private websites.
What I am trying to do is to let people access the website this way :
http://www.mydomain.com/w/{website_name}
Here is the routing configuration i am using :
websites:
resource: "#MyBundle/Resources/config/routing.yml"
prefix: /w/{website_name}
The problem is that when I try to access, for exemple, http://www.mydomain.com/w/chucknorris I am getting the error :
An exception has been thrown during the rendering of a template ("Some
mandatory parameters are missing ("website_name") to generate a URL
for route "websites_homepage".") in
"MyBundle:Publication:publicationsList.html.twig".
What I understood is that my route configuration is working well but when I am calling the router to generates url in the website it isn't aware of the "context" {website_name} url parameter.
One solution I've imagined is to find a way to automatically and seemlessly inject this parameter when it's set in the context.
Until now all I've been able to do is to create a service to get this parameter this way :
public function __construct(Registry $doctrine, ContainerInterface $container) {
$website_name = $container->get('request')->get("website_name");
if (!empty($website_name)) {
$repository = $doctrine->getManager()->getRepository('MyBundle:website');
$website = $repository->findOneByDomain($website_name);
if ($website) {
$this->website = $website;
} else {
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException();
}
} else {
$this->isPortal = true;
}
}
My question is : How do I inject that argument to all url generated to avoid getting the error of parameter missing and without having to specify it manualy everytime I call the router in controllers or twig ? (I guess it's something about request event but i have no clue on how to do it and especialy how to do it according to symfony2 good usages)
UPDATE
Here is the listener i created base on locallistener provided by symfony:
<?php
class WebsiteNameRouteEventListener implements EventSubscriberInterface {
private $router;
public function __construct(RequestContextAwareInterface $router = null) {
$this->router = $router;
}
public function onKernelResponse(FilterResponseEvent $event) {
$request = $event->getRequest();
$this->setWebsiteName($request);
}
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
$this->setWebsiteName($request);
}
public static function getSubscribedEvents() {
return array(
// must be registered after the Router to have access to the _locale
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
KernelEvents::RESPONSE => 'onKernelResponse',
);
}
private function setWebsiteName(Request $request) {
if (null !== $this->router) {
echo "NEW CODE IN ACTION";die();
$this->router->getContext()->setParameter('website_name', $request->attributes->get("website_name"));
}
}
}
But i am still getting this error :
An exception has been thrown during the rendering of a template ("Some
mandatory parameters are missing ("website_name") to generate a URL
for route "homepage".") in
"MyBundle:Publication:publicationsList.html.twig". 500 Internal
Server Error - Twig_Error_Runtime 1 linked Exception:
MissingMandatoryParametersException ยป
Without my echo "...."; die() being executed so i guess twig is not firing the event i am listening on when it execute the path(routename) code.
Any idea ?
You could use the router context.
$this->router->getContext()->setParameter('website_name', $website_name);
See this file: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php

Symfony2: controller results in two responses

I'm trying to create a controller action that results in two responses. The SwiftMailer uses the kernel.terminate event to accomplish this. I can build an event listener for the event but I don't know how to tell it what I want it to do. I know what it takes to create and download a pdf file, but how will the listener know when to do it?
Edit:
The "tell it" is found here in SO by doing this:
if ($nextAction) {
$request->attributes->set('household_id', $id);
}
But it is not at all clear how to get the event listener to do all of this (copied from a controller, but for the first line):
$em = $this->container->get('doctrine.orm.default_entity_manager');
$household = $em->getRepository('ManaClientBundle:Household')->find($id);
$fname = $household->getHead()->getFname();
$sname = $household->getHead()->getSname();
$filename = $sname . $fname . 'card.pdf';
$stylesheetXml = $this->renderView('ManaClientBundle:Test:pdfstyle.xml.twig', array());
$facade = $this->get('ps_pdf.facade');
$response = new Response();
$this->render('ManaClientBundle:Test:card.pdf.twig', array(
'household' => $household,
'date' => date_create(),
), $response);
$xml = $response->getContent();
$content = $facade->render($xml, $stylesheetXml);
header('content-type:application/pdf;' .
'Content-Disposition:attachment; filename=' . $filename);
echo $content;
Form includes:
->add('save', 'submit')
->add('saveCreate', 'submit')
Controller includes:
$nextAction = $form->get('saveCreate')->isClicked();
if ($nextAction) {
// tell event to create and download a pdf file using $id
}
return $this->redirect($this->generateUrl('household_show', array('id' => $id)));
service:
listener.pdfresponse:
class: Mana\ClientBundle\EventListener\PdfListenerSubscriber
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.terminate, method: onKernelTerminate }
Listener
namespace Mana\ClientBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PdfListenerSubscriber implements EventSubscriberInterface {
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function onKernelTerminate(PostResponseEvent $event) {
//create and download pdf file
}
static public function getSubscribedEvents()
{
return array(
KernelEvents::TERMINATE => 'onKernelTerminate');
}
}
Two responses to an HTTP request would never be possible - once the first request has been responded to, the connection is dropped by the web server so there would be nowhere for the second response to be sent.

Symfony2 redirect for event listener?

I have a kernel event listener setup (kernel.controller) to redirect the user if he's not logged in. The event listener is succesfully getting called, however I'm having trouble figuring out how to redirect. Here's what I got:
$cont = $event->getController('testpost');
$event->setResponse($cont);
Which gives me the error:
Fatal error: Call to undefined method Symfony\Component\HttpKernel\Event\FilterControllerEvent::setResponse()
If you wanna redirect from FilterControllerEvent you should use this one:
public function onKernelController(FilterControllerEvent $event)
{
// do something
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
You can't set a response to a FilterControllerEvent object. You need to listen for the kernel.request event because its GetResponseEvent object has the setResponse() method:
$route = 'route_name';
if ($route === $event->getRequest()->get('_route')) {
return;
}
$url = $this->router->generate($route);
$response = new RedirectResponse($url);
$event->setResponse($response);
More ore less clear way to redirect on 'kernel.controller' event
public function onKernelController(FilterControllerEvent $event)
{
/* #var $controller \Symfony\Bundle\FrameworkBundle\Controller\Controller */
$controller = $event->getController()[0]; // PHP 5.4 or $controller = $controller[0];
throw new HttpException(307, null, null, array('Location' => $controller->generateUrl('homepage')));
}
This ain't no way to go, my friend. You should use a default Symfony Security to cover that for you...

Symfony2 AJAX Login

I have an example where I am trying to create an AJAX login using Symfony2 and FOSUserBundle. I am setting my own success_handler and failure_handler under form_login in my security.yml file.
Here is the class:
class AjaxAuthenticationListener implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
/**
* This is called when an interactive authentication attempt succeeds. This
* is called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* #see \Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener
* #param Request $request
* #param TokenInterface $token
* #return Response the response to return
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
/**
* This is called when an interactive authentication attempt fails. This is
* called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* #param Request $request
* #param AuthenticationException $exception
* #return Response the response to return
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => false, 'message' => $exception->getMessage());
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
}
This works great for handling both successful and failed AJAX login attempts. However, when enabled - I am unable to login via the standard form POST method (non-AJAX). I receive the following error:
Catchable Fatal Error: Argument 1 passed to Symfony\Component\HttpKernel\Event\GetResponseEvent::setResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given
I'd like for my onAuthenticationSuccess and onAuthenticationFailure overrides to only be executed for XmlHttpRequests (AJAX requests) and to simply hand the execution back to the original handler if not.
Is there a way to do this?
TL;DR I want AJAX requested login attempts to return a JSON response for success and failure but I want it to not affect standard login via form POST.
David's answer is good, but it's lacking a little detail for newbs - so this is to fill in the blanks.
In addition to creating the AuthenticationHandler you'll need to set it up as a service using the service configuration in the bundle where you created the handler. The default bundle generation creates an xml file, but I prefer yml. Here's an example services.yml file:
#src/Vendor/BundleName/Resources/config/services.yml
parameters:
vendor_security.authentication_handler: Vendor\BundleName\Handler\AuthenticationHandler
services:
authentication_handler:
class: %vendor_security.authentication_handler%
arguments: [#router]
tags:
- { name: 'monolog.logger', channel: 'security' }
You'd need to modify the DependencyInjection bundle extension to use yml instead of xml like so:
#src/Vendor/BundleName/DependencyInjection/BundleExtension.php
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
Then in your app's security configuration you set up the references to the authentication_handler service you just defined:
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
success_handler: authentication_handler
failure_handler: authentication_handler
namespace YourVendor\UserBundle\Handler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
AuthenticationFailureHandlerInterface
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// If the user tried to access a protected resource and was forces to login
// redirect him back to that resource
if ($targetPath = $request->getSession()->get('_security.target_path')) {
$url = $targetPath;
} else {
// Otherwise, redirect him to wherever you want
$url = $this->router->generate('user_view', array(
'nickname' => $token->getUser()->getNickname()
));
}
return new RedirectResponse($url);
}
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// Create a flash message with the authentication error message
$request->getSession()->setFlash('error', $exception->getMessage());
$url = $this->router->generate('user_login');
return new RedirectResponse($url);
}
}
}
If you want the FOS UserBundle form error support, you must use:
$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);
instead of:
$request->getSession()->setFlash('error', $exception->getMessage());
In the first answer.
(of course remember about the header: use Symfony\Component\Security\Core\SecurityContext;)
I handled this entirely with javascript:
if($('a.login').length > 0) { // if login button shows up (only if logged out)
var formDialog = new MyAppLib.AjaxFormDialog({ // create a new ajax dialog, which loads the loginpage
title: 'Login',
url: $('a.login').attr('href'),
formId: '#login-form',
successCallback: function(nullvalue, dialog) { // when the ajax request is finished, look for a login error. if no error shows up -> reload the current page
if(dialog.find('.error').length == 0) {
$('.ui-dialog-content').slideUp();
window.location.reload();
}
}
});
$('a.login').click(function(){
formDialog.show();
return false;
});
}
Here is the AjaxFormDialog class. Unfortunately I have not ported it to a jQuery plugin by now... https://gist.github.com/1601803
You must return a Response object in both case (Ajax or not). Add an `else' and you're good to go.
The default implementation is:
$response = $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
in AbstractAuthenticationListener::onSuccess
I made a little bundle for new users to provide an AJAX login form : https://github.com/Divi/AjaxLoginBundle
You just have to replace to form_login authentication by ajax_form_login in the security.yml.
Feel free to suggest new feature in the Github issue tracker !
This may not be what the OP asked, but I came across this question, and thought others might have the same problem that I did.
For those who are implementing an AJAX login using the method that is described in the accepted answer and who are ALSO using AngularJS to perform the AJAX request, this won't work by default. Angular's $http does not set the headers that Symfony is using when calling the $request->isXmlHttpRequest() method. In order to use this method, you need to set the appropriate header in the Angular request. This is what I did to get around the problem:
$http({
method : 'POST',
url : {{ path('login_check') }},
data : data,
headers: {'X-Requested-With': 'XMLHttpRequest'}
})
Before you use this method, be aware that this header does not work well with CORS. See this question

Categories