ZF2 - Make onbootstrap events in one module supersede every other module - php

I have a ZF2 module called "Browsercheck", when used with other modules, checks the user's browser and shows an "Unsupported Browser" page if the browser is not supported. The issue I am having is, that the following code which is onBootstrap of the "Browsercheck" is not taking the first preference when other modules encounter an exception or a 404 error. IT works otherwise.
How can I make sure this code executes for every event and supersede any other event? Ideally in ZF1, I use code like this in index.php.. but not sure how it should be in ZF2. Basically if the browser is not supported, it shouldn't matter whether it's a 404 or an exception. It should go ahead and render the unsupported browser page.
public function onBootstrap(MvcEvent $e)
{
$sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
$sharedEvents->attach('Zend\Mvc\Controller\AbstractController','dispatch',
function($event)
{
$browser = new \BrowserCheck\Service\BrowserCheck();
if (!$browser->isCompatible())
{
$viewModel = new \Zend\View\Model\ViewModel();
$viewModel->setTerminal(true);
$request = $event->getRequest();
$response = $event->getResponse();
$viewModel->setTemplate('browser-check/index/index.phtml');
//Replace the entire view
$event->setViewModel($viewModel);
$event->stopPropagation();
$response->setStatusCode(200);
return $viewModel;
}
});
}
UPDATE:
$browserEventListener = function($event)
{
$browser = new \BrowserCheck\Service\BrowserCheck();
if (!$browser->isCompatible())
{
$viewModel = new \Zend\View\Model\ViewModel();
$viewModel->setTerminal(true);
$request = $event->getRequest();
$response = $event->getResponse();
$viewModel->setTemplate('browser-check/index/index.phtml');
//Replace the entire view
$event->setViewModel($viewModel);
$event->stopPropagation();
$response->setStatusCode(200);
return $viewModel;
}
};
$sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
$sharedEvents->attach('Zend\Mvc\Controller\AbstractController',
array('dispatch','dispatch.error'), $browserEventListener, 100);

The problem here is that you register your event listener on the dispatch event, which occurs after bootstrap and after route events. If the event listener triggered at route event cannot identify the route, you receive a 404 error page and the application lifecycle is terminated before any other dispatch event listener could be invoked.
In order to invoke your event listener before any routing is applied, you now have two options:
either register your event listener on bootstrap event with lower prio (e.g. lower than 10000)
or to register it on route listener with some higher priority, 10 should be enough
According to ZF2 doc page, onBoostrap() module methods are called with priority 10000 and onRoute() methods of \Zend\Mvc\ModuleRouteListener and \Zend\Mvc\RouteListener are called with priority 1 (higher the number higher the priority).
EDIT:
You should be doing something like this:
public function onBootstrap(MvcEvent $e)
{
/* ... all the default stuff here ... */
$this->registerBrowserCheckEvent($e);
}
private function registerBrowserCheckEvent(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$browserEventListener = function ($event) {
$browser = new \BrowserCheck\Service\BrowserCheck();
if (!$browser->isCompatible()) {
$viewModel = new \Zend\View\Model\ViewModel();
$viewModel->setTerminal(true);
$request = $event->getRequest();
$response = $event->getResponse();
$viewModel->setTemplate('browser-check/index/index.phtml');
//Replace the entire view
$event->setViewModel($viewModel);
$event->stopPropagation();
$response->setStatusCode(200);
return $viewModel;
}
};
$eventManager->attach(MvcEvent::EVENT_BOOTSTRAP, $browserEventListener, 100);
}
Also please notice that ZF2 uses PSR-2 (at least) and your code should follow this convention as well (mind the small differences between your code and mine).

Related

How I can hide some parameters from request body once an error is reported into sentry?

In a laravel php application I use the sentry to keep error info for example this controller:
class MuController
{
private function someMethodThatThrowsException()
{
throw new \Exception('Told ya');
}
public function foo()
{
try {
$this->someMethodThatThrowsException();
return new JsonResponse(204);
} catch(\Exception $e) {
app('sentry')->captureException($e);
return new JsonResponse(500);
}
}
}
I have setup my sentry as documentation says so:
use Sentry\Laravel\Integration;
....
public function register(): void
{
$this->reportable(function (Throwable $e) {
Integration::captureUnhandledException($e);
});
}
And I have exposed the sentry like this:
php artisan sentry:publish --dsn=___PUBLIC_DSN___
But sometimes I want some information from incomming http call to be hidden for security reasponse once reported to sentry. Is there a way to hide information from sentry regarding the http body?
I see that there's the functionality in https://docs.sentry.io/platforms/php/guides/laravel/configuration/filtering/ but Idk where this code should be places upon in my laravel project.
According to sentry's documentation you can set the following config at config/sentry.php:
return [
'dsn' => env('SENTRY_LARAVEL_DSN'),
// misc changed go here,
'before_send' => function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
$request = $event->getRequest();
// process request body here
$event->setRequest($request);
return $event;
}
];
For example you can remove any field in the body that contains password information:
return [
'dsn' => env('SENTRY_LARAVEL_DSN'),
// misc changed go here,
'before_send' => function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
$request = $event->getRequest();
// process request body here
foreach(['pass','password'] as $filtered){
if(isset($request['body'][$filtered])){
$request['body'][$filtered] = '[FILTERED]';
}
}
$event->setRequest($request);
return $event;
}
];
As you can see I use the $request['body'] and I check for any input, if input parameter matches then I replace the item with [FILTERED] therefore I avoid leaking sensitive info to 3rd party sentry.

Replacement for notFoundHandler setting

I'm migrating from Slim/3 to Slim/4. I've found or figured out replacements for all the features I was using that have been removed, except 404 Not Found Handler (part of the now gone App::$settings):
Slim App::$settings have been removed, multiple middleware have been implemented to replace the functionality from each individual settings.
Is there a middleware for notFoundHandler? If there isn't, how can I implement it?
Mine used to look like this:
use Slim\Container;
$config = new Container();
$config['notFoundHandler'] = function (Container $c) {
return function (Request $request, Response $response) use ($c): Response {
$page = new Alvaro\Pages\Error($c);
return $page->notFound404($request, $response);
};
};
According to Slim 4 documentation on error handling
Each Slim Framework application has an error handler that receives all
uncaught PHP exceptions
You can set a custom error handler to handle each type of exceptions thrown. A list of predefined exception classes is available on same page.
Here is a very basic example of how to register a closure as an error handler, to handle only HttpNotFoundException exceptions. You can also put the handler in a class that extends Slim\Handlers\ErrorHandler. Also, I did not actually use your Alvaro\Pages\Error to generate the response, but changing it should be straightforward:
<?php
require '../vendor/autoload.php';
$app = Slim\Factory\AppFactory::create();
// Define Custom Error Handler
$customErrorHandler = function (
Psr\Http\Message\ServerRequestInterface $request,
\Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails
) use ($app) {
$response = $app->getResponseFactory()->createResponse();
// seems the followin can be replaced by your custom response
// $page = new Alvaro\Pages\Error($c);
// return $page->notFound404($request, $response);
$response->getBody()->write('not found');
return $response->withStatus(404);
};
// Add Error Middleware
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// Register the handler to handle only HttpNotFoundException
// Changing the first parameter registers the error handler for other types of exceptions
$errorMiddleware->setErrorHandler(Slim\Exception\HttpNotFoundException::class, $customErrorHandler);
$app->get('/', function ($request, $response) {
$response->getBody()->write('Hello Slim 4');
return $response;
});
$app->run();
Another approach is to create a generic error handler and register it as the default handler, and inside that handler, decide what response should be sent based on type of exception that is thrown. Something like:
$customErrorHandler = function (
Psr\Http\Message\ServerRequestInterface $request,
\Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails
) use ($app) {
$response = $app->getResponseFactory()->createResponse();
if ($exception instanceof HttpNotFoundException) {
$message = 'not found';
$code = 404;
} elseif ($exception instanceof HttpMethodNotAllowedException) {
$message = 'not allowed';
$code = 403;
}
// ...other status codes, messages, or generally other responses for other types of exceptions
$response->getBody()->write($message);
return $response->withStatus($code);
};
Then you can set this as the default error handler:
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
$errorMiddleware->setDefaultErrorHandler($customErrorHandler);

Return response from a kernel event listener

I'm using the OAuth2 Server Bundle and I want to authenticate the user for all methods in my controller. It's very similar to the use case done in the documentation How to Setup before and after Filters, except that I want to return the response instead of throwing an exception.
onKernelController with FilterControllerEvent gives me access to the controller so I can access OAuth2 Server Bundle's response method. But I can't return any response in there. I can return a response in onKernelController using GetResponseEvent, but it gets called before onKernelController.
I looked into kernel.exception too but getResponse() returns different error messages so I rather not just throw an ambiguous exception.
What's the best practice for what I am trying to accomplish?
This is my code:
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if ($controller[0] instanceof \Foo\Bundle\AuthBundle\Controller\TokenAuthenticatedController) {
$server = $controller[0]->get('oauth2.server');
$request = $controller[0]->get('oauth2.request');
$response = $controller[0]->get('oauth2.response');
if (!$server->verifyResourceRequest($request, $response)) {
return $server->getResponse();
}
}
}
public function onKernelRequest(GetResponseEvent $event)
{
$event->setResponse(new Response('Some response', 501));
}
In the EventListener Approach you can't return a response but you must set it on the event itself.
The problem is that the Event FilterControllerEvent can't manage a returned response, so a better approach is to manage(listen for) the GetResponseForControllerResultEvent where you can set a response and stop the propagation.
Hope this help.

ZF2 from any module redirect to module login

I study ZF2 and have problem with login process. I have two modules login and moduleExample.
Login module is based on http://samsonasik.wordpress.com/2012/10/23/zend-framework-2-create-login-authentication-using-authenticationservice-with-rememberme/
I can redirect moduleExample to login route with condition hasIdentity() in Controllers, but can I set redirecting to this module at one place? Controllers will probably be more. I've already tried in onBootstrap method (Module.php), but then it is redirected everywhere (in all modules).
Where I can do what I describe?
It is possible to handle the login redirection at one place, one way is to define a custom event which is called in the routing event chain. To do this, you have to add the handler in your Module.php (preferably in the authentication module):
class Module
{
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$sm = $e->getApplication()->getServiceManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
//attach event here
$eventManager->attach('route', array($this, 'checkUserAuth'), 2);
}
public function checkUserAuth(MvcEvent $e)
{
$router = $e->getRouter();
$matchedRoute = $router->match($e->getRequest());
//this is a whitelist for routes that are allowed without authentication
//!!! Your authentication route must be whitelisted
$allowedRoutesConfig = array(
'auth'
);
if (!isset($matchedRoute) || in_array($matchedRoute->getMatchedRouteName(), $allowedRoutesConfig)) {
// no auth check required
return;
}
$seviceManager = $e->getApplication()->getServiceManager();
$authenticationService = $seviceManager->get('Zend\Authentication\AuthenticationService');
$identity = $authenticationService->getIdentity();
if (! $identity) {
//redirect to login route...
$response = $e->getResponse();
$response->setStatusCode(302);
//this is the login screen redirection url
$url = $e->getRequest()->getBaseUrl() . '/auth/login';
$response->getHeaders()->addHeaderLine('Location', $url);
$app = $e->getTarget();
//dont do anything other - just finish here
$app->getEventManager()->trigger(MvcEvent::EVENT_FINISH, $e);
$e->stopPropagation();
}
}
}
what you tried is correct, so then you just need this little bit here
$controller = $e->getTarget();
$params = $e->getApplication()->getMvcEvent()->getRouteMatch()->getParams();
if(!$params['controller'] == 'your login conroller path e.g. RouteFolder/Controller/LoginControllerName'){
return $controller->redirect()->toRoute('your redirect route')
}
do a
print_r($params['controller'])
and see what it returns and you will understand what i mean
so the event onBoostrap wont redirect you if your current location is the controller where the user comes to login

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...

Categories