Absolute URL fails in Symfony2 WebCaseTest - php

The following code always results in no Route found. But the Route does exist.
$client = static::createClient();
$crawler = $client->request(
'GET',
'/app_dev.php/admin/',
array(),
array(),
array("HTTP_HOST" => "dev.example:8080"));
But always fails. If I go to http://dev.example:8080/app_dev.php/admin/ in my browser then it works fine.
Its like PHPUnit cannot see that host?

$crawler->request() should not receive an actual URI, but the part after the front controller. So in your case, use:
$client = static::createClient();
$crawler = $client->request(
'GET',
'/admin/',
array(),
array(),
array("HTTP_HOST" => "dev.example:8080"));
The reason behind this is that the client doesn't actually request for a page (to a host). It just simulates a request, by creating a Request object, passing it to the AppKernel and then parsing the Response. This is much faster.
If you want to test using a real request, I recommend installing and using Mink or Goutte.

Related

Getting "SignatureDoesNotMatch" when using GuzzleHttp to follow a redirect to a pre-signed S3 URL

I am consuming an API which receives a POST request with some parameters and uses them to generate a file in S3. This API returns a 303 redirect response with Location: set to the signed URL to access the S3 file.
This works file when accessing the API via e.g. Postman however when accessing it via GuzzleHttp (v7.4) it fails with error SignatureDoesNotMatch.
While debugging I have used code:
$client = new Client([
'allow_redirects' => true,
'on_stats' => function (TransferStats $stats) {
var_dump($stats->getEffectiveUri());
}
]);
$client->post('https://api.example.com', [
'json' => [ ... ]
]);
Doing this I have confirmed that the URL is correct and copy/pasting the exact url that is accessed actually works. However using GuzzleHttp it does not work at all.
Update: The API developer has informed me that this issue was also because they were using v2 of the AWS S3 signature. They have now changed it to v4 which makes my code work as is (I think they may have had the same issues from other clients).
The issue turned out to be that when Guzzle follows redirects, it retains most of the original request headers. However the Amazon computed signature also validates the signature against (at least some of) the headers. The offending header was the Content-Type header which was still sent even though the request no longer had any content.
To fix this I created a new Guzzle middleware:
use Psr\Http\Message\RequestInterface;
class RemoveContentType {
public function __invoke(callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
if ($request->getHeaderLine('Content-Type')
&& $request->getBody()->getSize() === 0) {
return $handler($request->withoutHeader('Content-Type'), $options);
}
return $handler($request, $options);
};
}
}
this would remove the content type from a request which has an empty body.
I then modified my code to use this middleware:
$stack = HandlerStack::create();
$stack->push(new RemoveContentType ());
$client = new Client([
'handler' => $stack,
'allow_redirects' => true,
]);
$client->post('https://api.example.com', [
'json' => [ ... ]
]);
This finally solved my issue.

How do I do HTTP basic authentication using Guzzle?

I want to do basic access authentication using Guzzle and I am very new to programming. I have no clue what to do. I tried to do this using curl but my environment requires using guzzle.
If you're using Guzzle 5.0 or newer, the docs say that basic auth is specified using the auth parameter:
$client = new GuzzleHttp\Client();
$response = $client->get('http://www.server.com/endpoint', [
'auth' => [
'username',
'password'
]
]);
Please note that the syntax is different if you're using Guzzle 3.0 or earlier. The constructor is different, and you also need to explicitly use the send method on a request to get a response:
$client = new Guzzle\Http\Client();
$request = $client->get('http://www.server.com/endpoint');
$request->setAuth('username', 'password');
$response = $request->send();
A brief addendum
In response to #Matthwew-Knill, yes, you can set a default authorization and implicitly have Guzzle send it in each further request. #Nick's answer is on point. The client constructor takes every parameter you could think of and then some.
Another approach, if you want to get creative, would be to instantiate the client passing it default headers to send on every further request. Simple auth is, after all, an Authorization header. It's computed as:
$client = new Client([
'headers'=>[
'Authorization'=> Basic base64_encode(<username>:<password>)
]
]);
Last but not least please note that filling a simple auth dialog happens only once (upon the virst visit of a given session). This is usually achieved by setting a cookie on the visitor's browser. That cookie in turn contains enough info for the server to identify its matching active session.
Usually, Guzzle requests are stateless, but you can configure Guzzle with a middleware chain to either modify request or responses, for debug purposes and, for this use case, to remember cookies, thus becoming partially stateful.
Please check the detailed procedure in Guzzle Docs. The important thing is that, by instantiating the client with a cookiejar middleware, therefore having the client include it from then on, the first request will remember the server's set-cookie header, and will send it as every further cookie header, making the server recognize the client as a logged in user. Of course, you could also inspect the first response's headers yourself and send its value from then on.
There might be other ways, but I can't think of another right now.
In additional to #amenadiel answer. Sometimes handy specify auth parameters in constructor:
$client = new Client([
'auth' => ['username', 'password'],
]);
Then every request will use this default auth parameters.
This dint work when I used Guzzlev6 and used the advice from #amenadiel. When you use curl, your syntax would look something like
curl -u someone#gmail.com:password http://service.com
behind the scenes it actually takes the "someone#gmail.com:password" bit, base64 encodes it and sends the request with an "Authorization" Header with the encoded value. For this example, that will be:
Authorization: Basic c29tZW9uZUBnbWFpbC5jb206cGFzc3dvcmQ=
Advice from #amenadiel appended an "auth: username,password" header and hence, my authentication kept failing. To achieve this successfully, just craft the header when you are instantiating a Guzzle Client request, i.e
$client = new GuzzleHttp\Client();
$credentials = base64_encode('someone#gmail.com:password');
$response = $client->get('http://www.server.com/endpoint', [
'Authorization' => ['Basic '.$credentials]
]);
That would append the header as curl would, and whatever service you are trying to connect to will stop yelling at you,
Cheers.
According to the Guzzle 6 documentation, you can do a request with basic authorization as simple as this:
$client = new Client();
$response = $client->request(
'POST', /*instead of POST, you can use GET, PUT, DELETE, etc*/
$url,
[
'auth' => ['username', 'password'] /*if you don't need to use a password, just leave it null*/
]
);
echo $response->getBody();
NOTE: You don't need to use base64_encode() at all because it already does it before the request.
I've tested and it works :)
See more at: Guzzle 6 Documentation
$response = $client->request( 'GET', 'your_url', [
'auth' => [
'your_username',
'your_password'
],
'headers' => [
'if you want to pass something in the headers'
]
]
);
You can also configure the auth params when instantiating the client instead of adding it to each request:
$this->client = new \GuzzleHttp\Client([
'base_uri' => $this->endpoint,
'headers' => [
'Authorization' => ['Basic '.base64_encode($this->username.':'.$this->password)],
],
]);
Here are the various doc links for Guzzle 6:
Creating a Client
Request Options
Auth Request Options
According to what #bourgeois247 said about base64 encoding, the following worked perfectly for me on Guzzle 6:
$client = new Client();
$credentials = base64_encode('username:password');
$response = $client->post('url',
[
'headers' => [
'Authorization' => 'Basic ' . $credentials,
],
]);
If you use it with symfony, you can also define it in your configuration file (config/packages/eight_points_guzzle.yaml for symfony4 or flex or config.yml for the other version)
In your configuration file :
eight_points_guzzle:
clients:
your_service:
# Write here the host where to do requests
base_url: "yourURL"
options:
timeout: 30
auth:
- yourLogin # login
- yourPassword # password
plugin: ~
Then, in your service, controller, etc....
$client = $this->getContainer()->get('eight_points_guzzle.client.your_service');
$response = $client->get('yourRoute');
See : https://packagist.org/packages/eightpoints/guzzle-bundle

How to use Goutte without cookie

How to use goutte but don't send cookies back to the server?
I want to do that because the server can manage sessionid in the URL.
I end up using guzzle and browserkit directly without using goutte. Guzzle let you chose to manage or not cookies http://guzzle.readthedocs.org/en/latest/quickstart.html#cookies.
This solved this problem amount others.
If really you like goutte, I guess you can also delete cookie between each requests.
The only way I see is to instantiate the GuzzleClient yourself and pass it to the Goutte client.
Like this:
use Goutte\Client as GoutteClient;
use GuzzleHttp\Client as GuzzleClient;
$guzzleClient = new GuzzleClient(array('defaults' => array(
'allow_redirects' => false,
'cookies' => false
));
$client = new GoutteClient();
$client->setClient($guzzleClient);
$client->request('GET', 'http://example.org');

How to make https requests in symfony2 functional test?

Hi i am using phpunit for testing and Symfony\Bundle\FrameworkBundle\Test\WebTestCase for unit testing. So far there were no problem but now we start to use https and my tests are not working anymore.I start to get 301 response code for my every request in my tests. My question is how do i tell to Symfony\Component\HttpKernel\Client to make requests to
https://localhost.com/uri instead of http://localhost.com/uri ?
EDIT
In symfony website http://symfony.com/doc/current/book/testing.html they show how to configure server parameters and there is a code peace like
$client->request(
'GET',
'/demo/hello/Fabien',
array(),
array(),
array(
'CONTENT_TYPE' => 'application/json',
'HTTP_REFERER' => '/foo/bar',
'HTTP_X-Requested-With' => 'XMLHttpRequest',
)
);
I tried to give HTTPS element as mentioned in http://php.net/manual/en/reserved.variables.server.php changed my code as
$client->request('GET',
'/'.$version.'/agencies/'.$agencyId,
array(),
array(),
array('HTTPS' => 'on')
);
However, it is still not working?
Thanks to #WouterJ i changed my client creation from :
static::createClient();
to:
static::createClient(array(),array('HTTPS' => true));
it solved my problem.
It turns out that I cant give HTTP_HOST and HTTPS parameters in client ->request. It should be determined while client creation.
I am using Symfony4 and this how I created my functional test. I have tested following code and it works fine.
namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class CategoryControllerTest extends WebTestCase
{
public function testList()
{
$client = static::createClient();
$client->request('GET', '/category/list');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
}

Symfony2: Unable to simulate HTTP authentication in functional test

I'm trying to use the following technique described on symfony.com : http://symfony.com/doc/current/cookbook/testing/http_authentication.html
in an attempt to functionally test a controller that requires a user to be logged in.
So far, my login form is working, I can login, and the Symfony2 debug web toolbar is showing my user as authenticated. Also, I have already written a functional test for the login process itself, this passes. So I now by two scenarios, that my login is working.
The only problem I'm having, is when trying to simulate HTTP authentication like so for other controllers:
$client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'tester',
'PHP_AUTH_PW' => 'testpass',
));
I can see by inspecting the $client, that I am being redirected to my login page, the moment I try something like this:
$crawler = $client->request('GET', '/');
I know that the user tester with password testpass exists in the DB, as I can login with that account via browser as well.
I can use the following code from the security controller test:
$client = $this->createClient(array(), array('HTTP_HOST' => 'myapp.test'));
// Visit user login page and login
$crawler = $client->request('GET', '/login');
$form = $crawler->selectButton('Login')->form();
$crawler = $client->submit($form, array('_username' => 'tester', '_password' => 'testpass'));
// ... carry on with remainder of tests
but I'm not too sure if this is the most efficient way to do this.
I'm a bit stunned as to what is wrong. Any ideas? Was there a change applied to Symfony2 that means that this process has changed and the HTTP authentication simulation now doesn't work or works differently?
Thinking about it, I might just do the login using the following setUp method:
public function setUp()
{
// Perform user login.
$this->client = $this->createClient(array(), array('HTTP_HOST' => 'scire.test'));
$crawler = $this->client->request('GET', '/login');
$form = $crawler->selectButton('Login')->form();
$this->client->submit($form, array('_username' => 'tester', '_password' => 'tester'));
}
The HTTP authentication won't work here, unless I alter my config_test.yml with some security setup to allow HTTP authentication.
Note to self: HTTP authentication is different from using a Doctrine user provider!!!
Send your request like this:
<?php
$client->request(
'GET',
'/',
array(),
array(),
array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word')
);
This actually sends the information as real authentication header information.

Categories