Can you set a default route in Fat-Free Framework - fat-free-framework

If the Fat-Free Framework (F3) sees that an incoming HTTP request does not match any of the routes defined in your application, is there a way to set a default route for these cases. For example, to put at the end of all the routes you have defined in the file, a route where any incoming HTTP request that did not match any preceding routes to go there?
Basically, I would like to route any request that doesn't find a match to a specific class/controller. It seems like this would be something that is possible to do, but I cannot find it anywhere in the F3 docs.

Not able to test it but what if you use a wildcard as last route option?
$f3->route('GET /*')

Instead of registering a default route it's better to register a custom error handler which is able to process 404 and other error codes. This approach allows to reuse the error controller or error function when triggering these errors programmatically; e.g. with Base->error(404).
Register the handler with ONERROR
Parse ERROR with the registered ONERROR handler
It's also possible to use the beforeRoute() and afterRoute() events.
Example
<?php
/** #var base $f3 */
$f3->set('ONERROR', 'App\Module\Error\Controller\ErrorController->onError');
class ErrorController
{
public function onError(Base $f3)
{
if ($f3->get('ERROR.code') == 404) {
/**
* TODO Generate an appropriate HTTP 404 page
*/
// Handled the `404` error.
return true;
}
// Let Fat-Free Framework's default error handler do the work.
return false;
}
}

Related

Is it possible to enable some static checking for tag helpers?

For example I can write this code in default mvc template
<a asp-area="" asp-route-returnUrlFoo="foo"
asp-controller="Account" asp-action="RegisterFoo">Register</a>
And it would generate incorrect url
/Account/RegisterFoo?returnUrlFoo=foo
Is it possible to throw an error when there is incorrect action name or route argument?
Fallback Method: "Universally" Handling Mismatched Controller and Actions
You can add a new route that will redirect all your invalid requests to your own specific controller and action:
app.UseMvc(routes =>
{
// your other routes here
routes.MapRoute(_
name: "Fallback",
url: "{*any}",
defaults: new { controller = "Error", action = "Handler"});
}
In the above case, this will map everything (that is not defined) to go to /Error/Handler. You can modify this to meet your own requirements.
You can read more about route templates here:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-template-reference
Alternative: Handle it per controller
Of course, there is the alternative which is to just deal with it on a controller basis instead. This approach is less generic and will allow you to create more targetted ways to deal with URI / routing errors.
Alternative: Check if the controller or action exists
string actionName = this.ControllerContext.RouteData.Values["action"].ToString();
string controllerName = this.ControllerContext.RouteData.Values["controller"].ToString();

Capturing the login failed event in Symfony3

I have this set up in security.yml;
failure_path: /login
This redirects a user to the log in page if they are not authenticated when trying to access specific URLs set in access_control e.g.
access_control:
- { path: ^/admin, role: ROLE_ADMIN }
But I cannot seem to capture this redirect.
I have tried to use a service for the security.authentication.failure event but this does not work
app.security.authentication_failure_event_listener:
class: MemberBundle\Event\AuthenticationListener
tags:
- { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
The redirect still occurs and the onAuthenticationFailure is never called. Presumably because authentication has not explicitly failed. The user just isn't logged in and is redirected.
The reason is because I want to give different response based on the expected format. For example I want html requests to go to the login page, but JSON requests should return valid JSON - and not the HTML login form page.
I feel like the FOSRestBundle may handle this, but it seems overkill for this relatively simple need. And its not a RESTFul web site so I shouldn't need that bundle to do this?
I also tried a service to listen for Exceptions but this did not work either. I guess the redirect that occurs doesn't throw an Exception?
app.exception_listener:
class: AppBundle\EventListener\ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception }
So how can I always capture the redirect event that occurs when failure_path is set in security.yml?
My Tip (also for the future) is to "Create a Custom Authentication System with Guard. Much More simple and more flexible/customizable to handle that stuff (specially if a bit complicated with many authenticators like facebook, twitter, etc... and with a remember me option activated...).
But... as showed in the SecurityBundle Configuration ("security") you can set a failure_handler parameter for each login system in that firewall:
security:
firewalls:
your_firewall_name:
form_login:
failure_handler: your_custom_failure_handler_service_name <-- THIS ONE
Then you can create a service injecting the needed dependencies to handle the stuff you need and to return a different response based on the request format.
NOTE: I'm not sure if you can achieve this also creating a listener for the Security authentication event failure, but you can give it a try.
So the answer to this lay in a subtle thing about Symfony and the AccessDeniedException.
In that the Exception is only thrown based on your access controls if the current user is not allowed access. It is not thrown if you are not authenticated at all.
The same is true for any sort of authentication failure hook. Its only called with some sort of auth fails, not if you're simply not logged in and therefore not allowed.
So its done via two new services
app.exception_listener:
class: AppBundle\EventListener\ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception }
app.entry_point:
class: AppBundle\EventListener\EntryPoint
arguments: ["#router"]
The first does capture the AccessDeniedException as you would expect when you're denied access to a resource.
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
do {
if ($exception instanceof AccessDeniedException) {
$request = $event->getRequest();
if ('json' == $request->get("format") || $request->isXmlHttpRequest()) {
$json = new JsonResponse(["status" => 0, 'not_logged_in' => true, "msg" => "You must be logged in"]);
$event->setResponse($json);
}
}
} while (null !== $exception = $exception->getPrevious());
}
}
The entry point service needs to be set in security.yml like to
security
firewalls:
main:
entry_point: app.entry_point
This class handles the start of the firewall and handles the redirection to authenticate. I check for the Exception is present and then test for XHR and json format and handle accordingly.
/**
* Returns a response that directs the user to authenticate.
*
* This is called when an anonymous request accesses a resource that
* requires authentication. The job of this method is to return some
* response that "helps" the user start into the authentication process.
*
* Examples:
* A) For a form login, you might redirect to the login page
* return new RedirectResponse('/login');
* B) For an API token authentication system, you return a 401 response
* return new Response('Auth header required', 401);
*
* #param Request $request The request that resulted in an AuthenticationException
* #param AuthenticationException $authException The exception that started the authentication process
*
* #return Response
*/
class EntryPoint implements AuthenticationEntryPointInterface
{
/** #var Router */
protected $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function start(Request $request, AuthenticationException $authException = null) {
if($authException) {
if('json' == $request->get("format") || $request->isXmlHttpRequest()) {
return new JsonResponse(["status" => 0, 'not_logged_in' => true, "msg" => "You must be logged in"]);
}
}
return new RedirectResponse($this->router->generate("fos_user_security_login"));
}
}

Send a specific response, or at least a specific HTTP status code, using an exception

In Django, there are a couple of exceptions that are designed to be intercepted by the framework and turned into specific HTTP response codes, such as 404 Not Found and 403 Forbidden.
This is especially useful for request validation, because it allows you to factor out common validation logic into utility functions and cleanup your controller actions.
Whenever the utility functions decide that the current request must be aborted with a specific HTTP error code, they can do so by throwing the relevant exception, without any support code in the controller action, in the form of a return statement or a try/catch.
For example, given a tree of nested REST resources:
static mappings = {
"/authors" (resources: "author") {
"/sagas" (resources: "saga") {
"/books" (resources: "book") {
}
}
}
Then the URL pattern for the Book resource is /authors/$authorId/sagas/$sagaId/books/$id, which means that any of the show(), delete(), or update() actions in BookController have this signature and must include some boilerplate validation logic:
def actionName(int authorId, int sagaId, Book book) {
// -- common validation logic ----------
// fetch parent objects
def author = Author.get(authorId)
def saga = Saga.get(sagaId)
// check that they exists
if (author == null || saga == null || book == null) {
return render(status: NOT_FOUND)
}
// check consistency
if (book.author != author || book.saga != saga || saga.author != author) {
return render(status: BAD_REQUEST)
}
// -- end of commond code --------------
...
}
What is the Grails way of factoring this out into a common method, while still allowing it to terminate request processing whenever an exceptional condition occurs?
I would think the best way is a NotFoundException, ForbiddenException, BadRequestException, and so on, or maybe a generic exception that accepts a HTTP status code. Is there anything like it in Grails? If not, where is the best place to add it? A filter?
Edit: I see now that the standard method is to add an error controller with a matching URL pattern, such as:
"500" (controller: "error")
The problem with this is that Grails will still log full stacktraces for all exceptions, including those that are not programming errors. This spams log files with all sorts of useless tracebacks.
Is there a solution?
You catch the exception in the beforeInterceptor closure of your controller. I resolved this same problem by examining the exception thrown and then acting accordingly. For example:
class BaseController {
/**
* Define DRA exception handlers. This prevents the default Grails
* behavior of returning an HTTP 500 error for every exception.
*
* Instead the exceptions are intercepted and modified according to
* the exception that was thrown. These exceptions are not logged
* whereas application exceptions are.
*/
def beforeInterceptor = {
request.exceptionHandler = { exception ->
def cause = exception.cause
def exceptionBody = [:]
if(cause.class == BadRequestException) {
response.setStatus(HttpStatus.BAD_REQUEST.value()) // HTTP 400 BAD REQUEST
exceptionBody.httpStatus = HttpStatus.BAD_REQUEST.value()
exceptionBody.error = cause.message
}
// render the exception body, the status code is set above.
render exceptionBody as JSON
return true
}
}
}
In order to get this to work you will have to create an ErrorController or something where all server errors are processed and rendered. For example:
class ErrorController {
def serverError() {
def handler = request.exceptionHandler
if(handler) {
request.exceptionHandler = null
if(handler.call(request.exception)) {
return
}
}
}
I have tested this an it does work. I copied the code from a running project that I have been working on. You can build out the if statement in the beforeInterceptor to catch any type of Exception you wish.

symfony 1.4: How to pass exception message to error.html.php?

I tried using special variable $message described here http://www.symfony-project.org/cookbook/1_2/en/error_templates but it seems this variable isn't defined in symfony 1.4, at least it doesn't contain message passed to exception this way throw new sfException('some message')
Do you know other way to pass this message to error.html.php ?
You'll need to do some custom error handling. We implemented a forward to a custom symfony action ourselves. Be cautious though, this action itself could be triggering an exception too, you need to take that into account.
The following might be a good start. First add a listener for the event, a good place would be ProjectConfiguration.class.php:
$this->dispatcher->connect('application.throw_exception', array('MyClass', 'handleException'));
Using the event handler might suffice for what you want to do with the exception, for example if you just want to mail a stack trace to the admin. We wanted to forward to a custom action to display and process a feedback form. Our event handler looked something like this:
class MyClass {
public static function handleException(sfEvent $event) {
$moduleName = sfConfig::get('sf_error_500_module', 'error');
$actionName = sfConfig::get('sf_error_500_action', 'error500');
sfContext::getInstance()->getRequest()->addRequestParameters(array('exception' => $event->getSubject()));
$event->setReturnValue(true);
sfContext::getInstance()->getController()->forward($moduleName, $actionName);
}
}
You can now configure the module and action to forward to on an exception in settings.yml
all:
.actions:
error_500_module: error
error_500_action: error500
In the action itself you can now do whatever you want with the exception, eg. display the feedback form to contact the administrator. You can get the exception itself by using $request->getParameter('exception')
I think I found a much simpler answer. On Symfony 1.4 $message is indeed not defined, but $exception is (it contains the exception object).
So just echo $exception->message.
Et voilĂ !
I've found another trick to do that - sfContext can be used to pass exception message to error.html.php but custom function have to be used to throw exception. For example:
class myToolkit {
public static function throwException($message)
{
sfContext::getInstance()->set('error_msg', $message);
throw new sfException($message);
}
than insted of using throw new sfException('some message') you should use myToolkit::throwException('some message')
To display message in error.html.php use <?php echo sfContext::getInstance()->get('error_msg') ?>

Symfony 1.4 - forward404 pass message to error404 template in production mode

In my app I want to use $this->forward404("Data not found"); to output individual error messages when necessary.
In the dev mode it works fine. When I run my app in production mode - where debug is set to false in getApplicationConfiguration() - I don't get the messages anymore.
in settings.yml I set a custom 404 action.
all:
.actions:
error_404_module: main
error_404_action: custom404
How can I pass the forward404 to my custom404Success.php template???
If you are able to get code of method you can see that method throws an sfError404Exception, that handles respect of enviroment. In dev it simple throws an exception and in prod it might be handle via set_exception_handler("my_handle").
You can override this method(it's public)
public function forward404($message = null)
{
// write to session and call from 404_template via <?php echo $sf_user->getFlash("404message") ?>
$this->getUser()->setFlash("404message", $message);
parent::forward404($message);
}
The 404 handling is completely different between prod and dev. In prod, the message you pass to the forward404 is simply written to the log and not passed to the handler action.
There are many ways to get round this, but the quickest (albeit dirtiest) would be to store the message in a static variable somewhere so the 404 handler can access it once the process gets there, e.g.
In your action,
<?php
CommonActions::$message404 = 'My message';
$this->forward404();
And in your 404 handler (assuming it's in CommonActions),
<?php
if (self::$message404) {
$this->message = self::$message404;
}