I just discovered SensioLabsInsight and found very interesting tips on how to write good code. It would be great if there was some explanation on why (or why not) something should be used - even for basic stuff like exit and die. It would help me to explain things to people I work with.
So my question is specifically for AccessDeniedHttpException - it says:
Symfony applications should not throw AccessDeniedHttpException
So how do I return 403 Forbidden from the application controller or EventListener?
What is the best practice?
To be honest I thought it would be
throw new AccessDeniedHttpException()
Since for 404 you have
throw $this->createNotFoundException()
But it looks like I was wrong.
I think it means that you must throw AccessDeniedException instead of directly throwing AccessDeniedHttpException.
Main reason is that AccessDeniedException is catched by the event listener in Symfony\Component\Security\Http\Firewall\ExceptionListener and then you can make some stuff with it. Check onKernelException function.
That sentence has to be considered with the whole architecture of Symfony in mind.
In the Symfony framework there is a whole subsystem devoted to security applying the 2 step Authentication + Authorization process.
That said in the architecture of Symfony the Controllers, that are what basically the framework leaves to you to develop and so they are "the application", will be called only if the Authentication + Authorization has been passed.
So that sentence say that you should not need to throw that Exception becouse that is the work for the Security component. Doing that it is not forbidden or even made impossible but it is not the way which the framework has been normally thinked to work.
This can happen in two situations:
Your application is particular and you need to do that way
You are doing the security work out of the framework way of doing. It is your choice, just evaluate cost/benefits of not using the framework features and write your own ones.
Looking here http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html it seems like you might be able to throw an AuthenticationException which returns a 403 Response (?)
Here is Controller::createNotFoundException() implementation:
public function createNotFoundException($message = 'Not Found', \Exception $previous = null)
{
return new NotFoundHttpException($message, $previous);
}
It throws a bit different exception.
I don't know the reason for this tip. Maybe its because in controller or event listener You can directly return the Response, without throwing exception and thus triggering other event listeners.
Symfony uses event listeners to handle exceptions. You can create your own listeners and manage the response. Might be useful for API. For example I have used it to return pretty json responses in dev environment (with stack trace and additional debugging info).
Related
Given the following pest test:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The above should fully work; however, the second request post('/courses')...
fails saying that:
Failed asserting that <...> contains "WebTechnologies".
If I remove the first request:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The test passes.
If I remove the second request instead:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
});
It also passes.
So why should the combination of the two cause them to fail? I feel Laravel is caching the original response, but I can't find anything within the documentation supporting this claim.
I have created an issue about this on Laravel/Sanctum as my problem was about authentication an stuff...
https://github.com/laravel/sanctum/issues/377
One of the maintainers of Laravel Said:
You can't perform two HTTP requests in the same test method. That's not supported.
I would have wanted a much clearer explanation on why it's not supported.
but I guess, we would never know. (Unless we dive deep into the Laravel framework and trace the request)
UPDATE:
My guess is that, knowing how Laravel works, for each REAL request Laravel initializes a new instance of the APP...
but when it comes to Test, Laravel Initializes the APP for each Test case NOT for each request, There for making the second request not valid.
here is the file that creates the request when doing a test...
vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php
it's on the call method line: 526 (Laravel v9.26.1)
as you can see...
Laravel only uses 1 app instance... not rebuilding the app...
Line 528: $kernel = $this->app->make(HttpKernel::class);
https://laravel.com/docs/9.x/container#the-make-method
the $kernel Variable is an instance of vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
My guess here is that the HttpKernel::class is a singleton.
P.S. I can do a little more deep dive, but I've procrastinated too much already by answering this question, it was fun thou.
TL;DR.
You can't perform two HTTP requests in the same test method. That's not supported.
UPDATE:
I was not able to stop myself...
I found Laravel initializing Kernel as a singleton
/{probject_dir}/bootstrap/app.php:29-32
Please make sure to not use any classic singleton pattern which isn't invoked with singleton binding or facades.
https://laravel.com/docs/9.x/container#binding-a-singleton
$this->app->singleton(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
The Laravel app won't be completely restarted during tests unlike different incoming HTTP requests - even if you call different API endpoints in your tests
The Problem in a Nutshell
I'm looking for a way to remove VerifyCsrfToken from the global middleware pipeline from within a package without the user having to modify App\Http\Middleware\VerifyCsrfToken. Is this possible?
The Use Case
I'm developing a package that would make it easy to securely add push-to-deploy functionality to any Laravel project. I'm starting with Github. Github uses webhooks to notify 3rd party apps about events, such as pushes or releases. In other words, I would register a URL like http://myapp.com/deploy at Github, and Github will send a POST request to that URL with a payload containing details about the event whenever it happens, and I could use that event to trigger a new deployment. Obviously, I don't want to trigger a deployment on the off chance that some random (or perhaps malicious) agent other than the Github service hits that URL. As such, Github has a process for securing your webhooks. This involves registering a secret key with Github that they will use to send a special, securely hashed header along with the request that you can use to verify it.
My approach to making this secure involves:
Random Unique URL/Route and Secret Key
First, I automatically generate two random, unique strings, that are stored in the .env file and used to create a secret key route within my app. In the .env file this looks like:
AUTODEPLOY_SECRET=BHBfCiC0bjIDCAGH2I54JACwKNrC2dqn
AUTODEPLOY_ROUTE=UG2Yu8QzHY6KbxvLNxcRs0HVy9lQnKsx
The config for this package creates two keys, auto-deploy.secret and auto-deploy.route that I can access when registering the route so that it never gets published in any repo:
Route::post(config('auto-deploy.route'),'MyController#index');
I can then go to Github and register my webook like this:
In this way, both the deployment URL and the key used to authenticate the request will remain secret, and prevent a malicious agent from triggering random deployments on the site.
Global Middleware for Authenticating Webhook Requests
The next part of the approach involves creating a piece of global middleware for the Laravel app that would catch and authenticate the webhook requests. I am able to make sure that my middleware gets executed near the beginning of the queue by using an approach demonstrated in this Laracasts discussion thread. In the ServiceProvider for my package, I can prepend a new global middleware class as follows:
public function boot(Illuminate\Contracts\Http\Kernel $kernel)
{
// register the middleware
$kernel->prependMiddleware(Middleware\VerifyWebhookRequest::class);
// load my route
include __DIR__.'/routes.php';
}
My Route looks like:
Route::post(
config('auto-deploy.route'), [
'as' => 'autodeployroute',
'uses' => 'MyPackage\AutoDeploy\Controllers\DeployController#index',
]
);
And then my middleware would implement a handle() method that looks something like:
public function handle($request, Closure $next)
{
if ($request->path() === config('auto-deploy.route')) {
if ($request->secure()) {
// handle authenticating webhook request
if (/* webhook request is authentic */) {
// continue on to controller
return $next($request);
} else {
// abort if not authenticated
abort(403);
}
} else {
// request NOT submitted via HTTPS
abort(403);
}
}
// Passthrough if it's not our secret route
return $next($request);
}
This function works right up until the continue on to controller bit.
The Problem in Detail
Of course the problem here is that since this is a POST request, and there is no session() and no way to get a CSRF token in advance, the global VerifyCsrfToken middleware generates a TokenMismatchException and aborts. I have read through numerous forum threads, and dug through the source code, but I can't find any clean and easy way to disable the VerifyCsrfToken middleware for this one request. I have tried several workarounds, but I don't like them for various reasons.
Workaround Attempt #1: Have user modify VerifyCsrfToken middleware
The documented and supported method for solving this problem is to add the URL to the $except array in the App\Http\Middleware\VerifyCsrfToken class, e.g.
// The URIs that should be excluded from CSRF verification
protected $except = [
'UG2Yu8QzHY6KbxvLNxcRs0HVy9lQnKsx',
];
The problem with this, obviously, is that when this code gets checked into the repo, it will be visible to anyone who happens to look. To get around this I tried:
protected $except = [
config('auto-deploy.route'),
];
But PHP didn't like it. I also tried using the route name here:
protected $except = [
'autodeployroute',
];
But this doesn't work either. It has to be the actual URL. The thing that actually does work is to override the constructor:
protected $except = [];
public function __construct(\Illuminate\Contracts\Encryption\Encrypter $encrypter)
{
parent::__construct($encrypter);
$this->except[] = config('auto-deploy.route');
}
But this would have to be part of the installation instructions, and would be an unusual install step for a Laravel package. I have a feeling this is the solution I'll end up adopting, as I guess it's not really that difficult to ask users to do this. And it has the upside of at least possibly making them conscious that the package they're about to install circumvents some of Laravel's built in security.
Workaround Attempt #2: catch the TokenMismatchException
The next thing I tried was to see if I could just catch the exception, then ignore it and move on, i.e.:
public function handle($request, Closure $next)
{
if ($request->secure() && $request->path() === config('auto-deploy.route')) {
if ($request->secure()) {
// handle authenticating webhook request
if (/* webhook request is authentic */) {
// try to continue on to controller
try {
// this will eventually trigger the CSRF verification
$response = $next($request);
} catch (TokenMismatchException $e) {
// but, maybe we can just ignore it and move on...
return $response;
}
} else {
// abort if not authenticated
abort(403);
}
} else {
// request NOT submitted via HTTPS
abort(403);
}
}
// Passthrough if it's not our secret route
return $next($request);
}
Yeah, go ahead and laugh at me now. Silly wabbit, that's not how try/catch works! Of course $response is undefined within the catch block. And If I try doing $next($request) in the catch block, it just bangs up against the TokenMismatchException again.
Workaround Attempt #3: Run ALL of my code in the middleware
Of course, I could just forget about using a Controller for the deploy logic and trigger everything from the middleware's handle() method. The request lifecycle would end there, and I would never let the rest of the middleware propagate. I can't help feeling that there's something inelegant about that, and that it departs from the overall design patterns upon which Laravel is built so much that it would end up making maintenance and collaboration difficult moving forward. At least I know it would work.
Workaround Attempt #4: Modify the Pipeline
Philip Brown has an excellent tutorial describing the Pipeline pattern and how it gets implemented in Laravel. Laravel's middleware uses this pattern. I thought maybe, just maybe, there was a way to get access to the Pipeline object that queues up the middleware packages, loop through them, and remove the CSRF one for my route. Best I can tell, there are ways to add new elements to the pipeline, but no way to find out what's in it or to modify it in any way. If you know of a way, please let me know!!!
Workaround Attempt #5: Use the WithoutMiddleware trait
I haven't investigated this one quite as thoroughly, yet, but it appears that this trait was added recently to allow testing routes without having to worry about middleware. It's clearly NOT meant for production, and disabling the middleware would mean that I'd have to come up with a whole new solution for figuring out how to get my package to do its thing. I decided this was not the way to go.
Workaround Attempt #6: Give up. Just use Forge or Envoyer
Why reinvent the wheel? Why not just pay for one or both of these service that already supports push-to-deploy rather than go to the trouble of rolling my own package? Well, for one, I only pay $5/month for my server, so somehow the economics of paying another $5 or $10 per month for one of these services doesn't feel right. I'm a teacher who builds apps to support my teaching. None of them generate revenue, and although I could probably afford it, this kinda thing adds up over time.
Discussion
Okay, so I've spent the better part of two solid days banging my head against this problem, which is what brought me here looking for help. Do you have a solution? If you've read this far, perhaps you'll indulge a couple of closing thoughts.
Thought #1: Bravo to the Laravel guys for taking security seriously!
I'm really impressed with how difficult it is to write a package that circumvents the built-in security mechanisms. I'm not talking about "circumvention" in the I'm-trying-to-do-something-bad way, but in the sense that I'm trying to write a legitimate package that would save me and lots of other people time, but would, in effect, be asking them to "trust me" with the security of their applications by potentially opening them up to malicious deployment triggers. This should be tough to get right, and it is.
Thought #2: Maybe I shouldn't be doing this
Frequently if something is hard or impossible to implement in code, that is by design. Maybe it's Bad Design™ on my part to want to automate the entire installation process for this package. Maybe this is the code telling me, "Don't do that!" What do you think?
In summary, here are two questions:
Do you know a way to do this that I haven't thought of?
Is this bad design? Should I not do it?
Thanks for reading, and thank you for your thoughtful answers.
P.S. Before someone says it, I know this might be a duplicate, but I provided much more detail than the other poster, and he never found a solution, either.
I know it is not good practice to use the Reflection API in production code, but this is the only solution i could think of where no additional configuration is needed. This is more like a proof of concept and I would not use it in production code.
I think a better and more stable solution is to have the user update his middleware to work with your package.
tl;dr - you can place this in your packages boot code:
// Just remove CSRF middleware when we hit the deploy route
if(request()->is(config('auto-deploy.route')))
{
// Create a reflection object of the app instance
$appReflector = new ReflectionObject(app());
// When dumping the App instance, it turns out that the
// global middleware is registered at:
// Application
// -> instances
// -> Illuminate\Contracts\Http\Kernel
// -> ... Somewhere in the 'middleware' array
//
// The 'instance' property of the App object is not accessible
// by default, so we have to make it accessible in order to
// get and set its value.
$instancesProperty = $appReflector->getProperty('instances');
$instancesProperty->setAccessible(true);
$instances = $instancesProperty->getValue(app());
$kernel = $instances['Illuminate\Contracts\Http\Kernel'];
// Now we got the Kernel instance.
// Again, we have to set the accessibility of the instance.
$kernelReflector = new ReflectionObject($kernel);
$middlewareProperty = $kernelReflector->getProperty('middleware');
$middlewareProperty->setAccessible(true);
$middlewareArray = $middlewareProperty->getValue($kernel);
// The $middlewareArray contains all global middleware.
// We search for the CSRF entry and remove it if it exists.
foreach ($middlewareArray as $i => $middleware)
{
if ($middleware == 'App\Http\Middleware\VerifyCsrfToken')
{
unset($middlewareArray[ $i ]);
break;
}
}
// The last thing we have to do is to update the altered
// middleware array on the Kernel instance.
$middlewareProperty->setValue($kernel, $middlewareArray);
}
I haven't tested this with Laravel 5.1 - for 5.2 it works.
So you could create a Route::group where you can explicitly say which middleware you want to use.
For example in your ServiceProvider you could do something like this:
\Route::group([
'middleware' => ['only-middleware-you-need']
], function () {
require __DIR__ . '/routes.php';
});
So just exclude VerifyCsrfToken middleware, and put what you need.
So I wrote my PHP MVC framework, and have Dispatcher class that can instantiate appropriate Controller class and call defined method passing arguments.
Now inside Dispatcher I check if this Controller exists and if method exists, what should I do if controller or method does not exist?
At the moment I just return HTTP Object that prints 404 - Page not found.
But there is no way for me to customize this message from inside application, and I want to provide users a way to customize 404 messages without editing dispatcher.
Is a good way to go to always have Error controller that would get instantiated when there is a error, and that would load lets say Error404.html view file?
So users would be able to customize this view file to fit their application design.
Is there any other way to achive this? And what would be the best way to return error from dispatcher, and let "users" or developers that are working on that MVC to easily customize 404 and other messages?
Thanks!
Since i do not know your API, I am going to guess. Lets assume that you have a bootstrap stage in your application, when the dispatcher is actually used. Something like:
$dispatcher->dispatch( $request );
Then for handling request, that try to access non-existent controllers or methods within those controllers, you can do something like this:
try
{
$dispatcher->dispatch( $request );
}
catch ( ClassNotFoundException $e )
{
$dispatcher->dispatch( new Request('/error/404/controller'));
}
catch ( MethodNotFoundException $e )
{
$dispatcher->dispatch( new Request('/error/404/method'));
}
The ClassNotFoundException can be thrown by your classloader, while dispatcher itself would always be responsible for throwing the MethodNotFoundException.
You can check, whether controller has a particular method, with method_exists(), before executing it in your dispatcher.
P.S. in my humble opinion, the Dispatcher conept is better suited for event driven architectures and not for MVC-inspired patterns in web applications.
I would propose you have an error controller that takes in an error code (number or string) as an argument. This allows you to gracefully handle various kinds of errors and be able to provide a stack trace if necessary. You can even utilize this work for 500 errors.
My answer comes with the assumption that a controller can return various actions and each action can have it's own template.
Symfony also seems to handle errors in a similar fashion. They have a separate module and action for each error.
sfContext::getInstance()->getController()->forward(sfConfig::get('sf_error_404_module'), sfConfig::get('sf_error_404_action'));
I'm used to Zend Framework, when you write your own component, you make it's own Exception file, but on per file basis, then you have such structure:
Zend/View/Exception.php
Zend/View/Helper/Exception.php
Zend/View/Renderer/Exception.php
etc.
I'm ok with, I also use Doctrine2 and Exception are "stored" in a different way
something like (in a Zend way)
and in Zend/View/Exception.php
class Exception {
public static function invalidArguement() {
return new self('Invalid arguement was given, etc..');
}
I understand that the second approach is less flexible but more accurate because it throws exception according the error.
The first approach is just a way to be able to throw a Zend_View_Exception with a custom messagE.
Also, what about one Exception file per, Exception.
Like the following structure :
Exception/InvalidArguement.php
Exception/AuthentificationFailed.php
Exception/QuantityLimit.php
Is there any best practices? Any pros/cons?
For me the best practice is to group exceptions related to their issue.
For example if you have a number of Auth exceptions, like InvalidDetails, UserNotFound put them here
Library/Auth/Exceptions/InvalidDetails.php
Library/Auth/Exceptions/UserNotFound.php
Each exception should be an extension of Zend_Exception ( unless you've extended it yourself )
this way you can do:
throw new Library_Auth_Exception_InvalidDetails("Invalid details when trying to login");
the benefit of using this method is you DONT need to have a message, the Exception name can cover it enough.
My assumptions here is you setup a namespace for Library called Library and everything is within there.
I tend to group everything, so a typical Auth library could be:
Auth/Forms/Login.php
Auth/Exception/InvalidUser.php
Auth/Orm/Abstract.php
Auth/Orm/Doctrine.php
HTH
I've never worked with Zend framework but if this at all helps, I would at least make a common Exception class and all those other ones extend that rather than just make one for each.
How should I write error reporting modules in PHP?
Say, I want to write a function in PHP: 'bool isDuplicateEmail($email)'.
In that function, I want to check if the $email is already present in the database.
It will return 'true', if exists. Else 'false'.
Now, the query execution can also fail, In that time I want to report 'Internal Error' to the user.
The function should not die with typical mysql error: die(mysql_error(). My web app has two interfaces: browser and email(You can perform certain actions by sending an email).
In both cases it should report error in good aesthetic.
Do I really have to use exception handling for this?
Can anyone point me to some good PHP project where I can learn how to design robust PHP web-app?
In my PHP projects, I have tried several different tacts. I've come to the following solution which seems to work well for me:
First, any major PHP application I write has some sort of central singleton that manages application-level data and behaviors. The "Application" object. I mention that here because I use this object to collect generated feedback from every other module. The rendering module can query the application object for the feedback it deems should be displayed to the user.
On a lower-level, every class is derived from some base class that contains error management methods. For example an "AddError(code,string,global)" and "GetErrors()" and "ClearErrors". The "AddError" method does two things: stores a local copy of that error in an instance-specific array for that object and (optionally) notifies the application object of this error ("global" is a boolean) which then stores that error for future use in rendering.
So now here's how it works in practice:
Note that 'Object' defines the following methods: AddError ClearErrors GetErrorCodes GetErrorsAsStrings GetErrorCount and maybe HasError for convenience
// $GLOBALS['app'] = new Application();
class MyObject extends Object
{
/**
* #return bool Returns false if failed
*/
public function DoThing()
{
$this->ClearErrors();
if ([something succeeded])
{
return true;
}
else
{
$this->AddError(ERR_OP_FAILED,"Thing could not be done");
return false;
}
}
}
$ob = new MyObject();
if ($ob->DoThing())
{
echo 'Success.';
}
else
{
// Right now, i may not really care *why* it didn't work (the user
// may want to know about the problem, though (see below).
$ob->TrySomethingElse();
}
// ...LATER ON IN THE RENDERING MODULE
echo implode('<br/>',$GLOBALS['app']->GetErrorsAsStrings());
The reason I like this is because:
I hate exceptions because I personally believe they make code more convoluted that it needs to be
Sometimes you just need to know that a function succeeded or failed and not exactly what went wrong
A lot of times you don't need a specific error code but you need a specific error string and you don't want to create an error code for every single possible error condition. Sometimes you really just want to use an "opfailed" code but go into some detail for the user's sake in the string itself. This allows for that flexibility
Having two error collection locations (the local level for use by the calling algorithm and global level for use by rendering modules for telling the user about them) has really worked for me to give each functional area exactly what it needs to get things done.
Using MVC, i always use some sort of default error/exception handler, where actions with exceptions (and no own error-/exceptionhandling) will be caught.
There you could decide to answer via email or browser-response, and it will always have the same look :)
I'd use a framework like Zend Framework that has a thorough exception handling mechanism built all through it.
Look into exception handling and error handling in the php manual. Also read the comments at the bottom, very useful.
There's aslo a method explained in those page how to convert PHP errors into exceptions, so you only deal with exceptions (for the most part).