Laravel Socialite Implement stateless for Twitter - php

I would like to implement stateless method to Twitter but it seems that it is not available for TwitterProvider class as it returns
Call to undefined method Laravel\Socialite\One\TwitterProvider::stateless()
Here is my redirectToProvider method currently.
public function redirectToProvider($socialMedia)
{
$provider = strtolower($socialMedia);
return Socialite::driver($provider)->stateless()->redirect();
throw new NotFoundHttpException;
}
What is the correct implementation or what do I miss?

As mentioned by #driesvints from this question #415 I've opened at the Laravel Socialite repository, stateless is unavailable for Twitter since it uses OAuth 1.0.
They already pushed a PR #5661 to update also the Laravel Docs mentioning this specification. Click the link to see the update. Staless Authentication
I would update this answer if whatever my solution would be.

Related

Laravel remembers original response during http tests

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

Capture user info in Sentry

I have just recently started using Sentry on my Laravel 5.1 application.
I see in there docs, and getting started stuff, a reference to capturing user information.
The example they give of passing this in, looks like this:
Raven.setUserContext({
email: 'foo#example.com'
});
Having followed their setup instructions for Laravel, I see no reference for where this could go or any reference in the documentation for how to set this up in a config file or anything for Laravel.
Any ideas on how I could set this up to send user information? Users will always be logged in when using my application.
I realise this question is three months old, so apologies if you’ve already found the answer.
I had the same requirements (log user as part of exceptions in Sentry) so did a little digging myself.
Raven.setUserContext() seems to be a function specific to the JavaScript SDK. The PHP SDK has a set_user_data($id, $email, $data) method on the Raven client that you can use somewhere in your application (most likely in your exception handler before you actually send the exception to Sentry).
Something like this should work:
public function report(Exception $e)
{
// Will only enter if statement if request has a user
if ($user = request()->user()) {
app('sentry')->set_user_data($user->getAuthIdentifier(), $user->email);
}
app('sentry')->captureException($e);
return parent::report($e);
}

Unable to get access token from instagram

Im using this instagram wrapper package for laravel 5.1. Im trying to connect to instagram thorugh the API then auth a user in using
use Vinkla\Instagram\Facades\Instagram;
public function instagramlogin(Request $request)
{
// Get code parameter.
$code = $request->get('code');
// Request the access token.
$data = Instagram::getOAuthToken($code);
// Set the access token with $data object.
Instagram::setAccessToken($data);
// We're done here - how easy was that, it just works!
Instagram::getUserLikes();
// This example is simple, and there are far more methods available.
}
and then be able to get access to his/her profile data. but I keep getting an error stating
ErrorException in Instagram.php line 526:
Undefined property: stdClass::$access_token
How do I auth a user?
I solved this problem. You basically have to use the cosenary instagram package as this package is a wrapper for that package. So if you run through the documentation on the cosenary package and use facades instead, the functions will work.

Programmatically add exception from CSRF check from Laravel package

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.

Read HTTP headers in Controller (Zend Framework)

Long story short:
I'm building a skeleton application for Zend Framework and I got to the part where I need to setup the api module. I'm using Zend_Rest_Controller for this job. All is ok up to this part where I need to get the HTTP headers in a controller to verify the api key.
On various tutorials I've read on the web the thing is done via a front controller plugin, but I need it to be more "plug and play" than that (checking each time the config of the application, deciding which module is the api and so on).
I tried what seemed most obvious $this->getRequest()->getHeaders() but doesn't seem to work, at least not for the HTTP headers where I'll be seding my api key. Neither the reponse object.
Can anyone help me with this one?
I found a way of doing this after all :)
On the preDispatch() method in your controller you can do the following:
public function preDispatch()
{
$request = new Zend_Controller_Request_Http();
$key = $request->getHeader('x-apikey');
}
It seems that Zend_Controller_Request_Http object gives you acces to the headers. More info on the Zend_Controller_Request_Http you can find here
As Bogdan said, you can find that information in the Zend_Controller_Request_HTTP class. It can be found in the controller itself by doing :
$this -> getFrontController() -> getRequest() -> getHeader('Content-Type');
Unfortunatly, you can't access all headers at once but what ZF does is just use apache_request_headers() function if available on the server to get them.

Categories