Symfony functional test: Current request not set when fetching service from container - symfony

One of my services depends on the HTTP_HOST value in the currentRequest object from the requestStack. When this service is used in a functional test it works because I create the client with the host parameter:
$client = static::createClient(array(), array(
'HTTP_HOST' => 'test.' . $this->domain
));
At some point I have the need to get a service from the container that has a dependency on the request so i thought i used the client created with the host value to fetch the service:
$client->getKernel()->getContainer()->get('service')->someMethod();
But the request object is no longer set when the constructer of this service is is called.
Is there any way I can use this service in the test function with a dependency on the Request object ?
Related code:
ControllerTest.php
//Create client with HTTP_HOST
$client = static::createClient(array(), array(
'HTTP_HOST' => 'test.' . $this->domain
));
//Do some request services depending on the request object work because the client is initiated with the HTTP_HOST value
$crawler = $client->request('GET', $redirectUrl);
$this->assertEquals(
1,
$crawler->filter('html:contains("feedback")')->count()
);
//Now I want to check if email feedback is send. This process starts in a EventSubsriber
//I have to trigger this event myself because the $event variable consist of fake data.
$client->getContainer()->get('event_subscriber')->process($event);
//now collect the mail and do some checks
$mailCollector = $client->getProfile()->getCollector('swiftmailer');
$this->assertEquals(1, $mailCollector->getMessageCount());

You should get the Container directly from your created client, as described in the official docs:
$client->getContainer()->get('service')->someMethod();
It may still be necessary to mock the whole service but more code examples would be needed..

Related

Reset Database before each Test has problems with authentication in functional tests

I am implementing functional tests for my REST-Api. The Api is protected by authorization. For this I chose the json_login provider. So far, so good. Authentication works when accessing in the normal environment via Insomnia.
Now I want functional tests. For that, I create an user via the configured User-class and persist it in the database. Works as expected.
But of course the test only works once as the user already exists in the following tests.
So I tried hautelook/alice-bundle with ResetDatabaseTrait or ReloadDatabaseTrait as well as dmaicher/doctrine-test-bundle.
Both show the same behaviour: The authenticator can not find the newly created user. (EntityUserProvider::loadUserByUsername finds no user)
Apparently the EntityUserProvider seems to use a different "connection" into the database that can not look into the transaction those libraries started.
The entity-manager in my test that is responsible for persisting my user is created either with
protected function setUp(): void {
$kernel = self::bootKernel();
$this->em = $kernel->getContainer()
->get('doctrine')
->getManager();
}
or directly before creating the user with
$em = self::$container->get('doctrine')->getManager();
which seems correct for me. But in any case I get the same result -> "Invalid credentials" because the user can not be found.
Maybe someone out there can point me into the right direction?
After a refreshing break I remembered a detail when I was creating my tests. All the examples did not need a setUp-Method with self:bootKernel() in it. But without it the self::$container property was empty, so I added that to my test-class. Maybe there was the solution to the problem?
I was right!
I am using the Api-Platform package. Therefore my test-class is based in ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase. That class does not have a setUp Method, but inspecting createClient() I noticed that there the kernel is created by calling bootKernel() which also stops any running kernel.
So my setUp() method created a kernel. With that kernel I created my user.
Then I called createClient() to create the test-client for the requests. This killed my initial kernel and creates a new one which then leads to the problems.
Rearranging the statements - first create the client, then get the EntityManager from the now created container and create the User after creating the client solved the problem.
After two days , hooh
when you want to call multiple request, for example if you want at first request you get token and the second you call with this token and check auth, in during this calls if you use use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait trait your data base rest after first call, you have token but database is empty, and second call fail.
So, read again this important part of documentation :
There is one caveat though: in some tests, it is necessary to perform multiple requests in one test, for example when creating a user via the API and checking that a subsequent login using the same password works. However, the client will by default reboot the kernel, which will reset the database. You can prevent this by adding $client->disableReboot(); to such tests. Writing Functional Tests
I, know we are lazy developer and first read code, not document :-)
$client = static::createClient();
$client->disableReboot();
$manager = self::getContainer()->get('doctrine')->getManager();
$user = new User();
$user->setEmail('user#example.com');
$user->setPassword(
self::getContainer()->get('security.user_password_hasher')->hashPassword($user, $password = 'pass1234')
);
$manager->persist($user);
$manager->flush();
$response = $client->request('POST', '/authentication-token', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'email' => $user->getEmail(),
'password' => $password ,
],
]);
$token = $response->toArray()['token'] ?? null;
$client->request('GET', '/profile', [
'auth_bearer' => $token
]);
self::assertResponseIsSuccessful();

getRequest Method on Restful Server

I am hitting RestfulServer via an ajax call (url: BaseHref + "api/v1/Post/" + postId + '/PostTracks' to retrieve DataObject relations:
public function PostTracks(){
$controller = Controller::curr();
$request = $controller->getRequest();
$passkey = $request->getHeader('passkey');
$tracks = $this->owner->Tracks();
$set = array();
foreach($tracks as $track)
{
$set[] = array(
'm4aURL' => $track->m4a()->URL,
'oggURL' => $track->ogg()->URL,
'Title' => $track->Title
);
}
$this->outputJSON(200, $set);
}
At the top of the method I am trying to grab the value of a custom header that I sent in my ajax call via the beforeSend method. I have verified that the header is sent in the request to RestfulServer controller, but am having trouble getting the value.I am not getting anything for the value of $passkey.
How can I get header info from a RestfulServer controller. I don't understand why getRequest isn't working since RestfulServer extends from Controller.
You can use print_r($request->getHeaders()) to see all the headers attached to the request. In any case, I suspect the issue is with the casing of "passkey". By default SilverStripe will parse header names in CamelCaseFormat - so I suspect the header will be called Passkey or PassKey.
One nice way to debug issues with request is using Debug::dump($request->getHeaders()) or Debug::log($request->getHeaders()).
The latter will write a log file to the site that you can then track if you have terminal access to the server by "tail -f debug.log", or downloading them again and again.
That way you can see what logs out when you cant drirectly access the url.

guzzle php http client cookies setup

I am trying to migrate from Zend Http Client to Guzzle Http Client. I find Guzzle well featured and easy to use for the most part, But I think it is not well documented when it comes to using Cookie plugin. So my question is how do you set cookies for the HTTP request you are going to make against the server, in Guzzle.
Using Zend Client you would do something as simple as :
$client = new HttpClient($url); // Zend\Http\Client http client object instantiation
$cookies = $request->cookies->all(); // $request Symfony request object that gets all the cookies, as array name-value pairs, that are set on the end client (browser)
$client->setCookies($cookies); // we use the above client side cookies to set them on the HttpClient object and,
$client->send(); //finally make request to the server at $url that receives the cookie data
So, how do you do this in Guzzle. I have looked at http://guzzlephp.org/guide/plugins.html#cookie-session-plugin. But I felt it is not straightforward and couldn't get my head around it. May be someone can help ??
This code should achieve what is asked for, i.e to set the cookies on the request before making guzzle client request
$cookieJar = new ArrayCookieJar(); // new jar instance
$cookies = $request->cookies->all(); // get cookies from symfony symfony Request instance
foreach($cookies as $name=>$value) { //create cookie object and add to jar
$cookieJar->add(new Cookie(array('name'=>$name, 'value'=>$value)));
}
$client = new HttpClient("http://yourhosturl");
$cookiePlugin = new CookiePlugin($cookieJar);
// Add the cookie plugin to the client object
$client->addSubscriber($cookiePlugin);
$gRequest = $client->get('/your/path');
$gResponse = $gRequest->send(); // finally, send the client request
When the response comes back from the server with set-cookie headers you have those cookies available in the $cookieJar.
Cookie jar can also be gotten from the CookiePlugin method
$cookiePlugin->getCookieJar();
Or without cookie plugin
$client = new HttpClient();
$request = $client->get($url);
foreach($cookies as $name => $value) {
$request->addCookie($name, $value);
}
$response = $request->send();

Any way to to use PHPUnit for testing API requests and responses using just PHP?

The responses are in JSON and I am using a custom-built MVC framework which I'm not sure how the request and response process is produced. Service methods are created using the following syntax.
public function getSessionsMethod()
{
// data auto encoded as JSON
return array('hello', 'world');
}
A request from JavaScript would look like this /svc/api/getSessions.
My initial thought was to simply use a streams approach are there best practices for this form of testing?
public function testCanGetSessionsForAGivenId()
{
$params = http_build_query(
array(
'id' => 3,
)
);
$options = array(
'http' => array(
'method' => 'GET',
'content' => $params,
)
);
$context = stream_context_create($options);
$response = file_get_contents(
'http://vbates/svc/api/getSessions', false, $context
);
$json = json_decode($response);
$this->assertEquals(3, $json->response);
}
This doesn't look like unit testing to me but rather integration testing. You can use PHPUnit to do it, but you should understand the difference first.
There are many components involved in getting the response for a given service method:
The dispatcher: Extracts the parameters from the URL and dispatches to the appropriate service method.
The service method: Does the real work to be tested here.
The JSON encoder: Turns the service method's return value into a JSON response.
You should first test these individually in isolation. Once you've verified that the dispatcher and encoder work for general URLs and return values, there's no point in wasting cycles testing that they work with every service method.
Instead, focus your effort on testing each service method without involving these other components. Your test case should instantiate and call the service methods directly with various inputs and make assertions on their return values. Not only will this require less effort on your part, it will make tracking down problems easier because each failure will be limited to a single component.

PHP SDK: How do I capture the access token after user auths app?

This is for a canvas app on the Facebook Platform using the new(est) Facebook PHP SDK.
We are using the PHP example from the Facebook tutorial (https://developers.facebook.com/docs/appsonfacebook/tutorial/) to trigger the OAuth dialog and get the test user to the redirect URL.
At the redirect URL, we use the PHP example from the Facebook signed request docs page (https://developers.facebook.com/docs/authentication/signed_request/) and our test users can successfully authorize the app.
However, after the test user auths the app, we are not able to capture the access token and its expiration. We can see it in the address bar appended to the redirect URL, but it does not show up in the $_REQUEST array. If we add {$access_token = $facebook->getAccessToken();} to the redirect URL page, it shows a value for the access token, but the value it shows is not the full token string that we see when we click on Show Token in the Test User Roles page (which we believe is the correct access token for the test user).
Here is an example of the redirect URL with an access token appended:
http://karmakorn.com/karmakorn/alpha20/kk-fb-auth.php#access_token=126736467765%7C2.AQDavId8oL80P5t9.3600.1315522800.1-100002908746828%7CJICJwM1P_97tKmqkEO5pXDCf-7Y&expires_in=6008
Here is what var_dump shows for the $REQUEST array for that same page:
array(3) { ["_qca"]=> string(26) "P0-709927483-1291994912966" ["__switchTo5x"]=> string(2) "30" ["PHPSESSID"]=> string(26) "euois02ead39ijumca7nffblh2" }
We have no idea why the $_REQUEST array varies from the values appended to the URL, and more importantly -- how to capture the access token and its expiration date.
Can someone show us a working example of how they capture this data after running the parse_signed_request($signed_request, $secret) function on the redirect page? Thanks!
ADDITIONAL INFO:
Here is the pertinent code from A) our test index page, and B) our test redirect page. If we use our text index page as the redirect url it gets stuck in an endless loop -- because the user is never identified.
A) Index Page
// Create kk-fb app instance
$facebook = new Facebook(array(
'appId' => KKFB_ID,
'secret' => KKFB_KY,
'oauth' => true,
));
$app_id = KKFB_ID;
$secret = KKFB_KY;
$canvas_auth = 'http://karmakorn.com/karmakorn/alpha20/kk-fb-auth.php';
$auth_url = "https://www.facebook.com/dialog/oauth?"
. "client_id=" . $app_id
. "&redirect_uri=" . urlencode($canvas_auth)
. "&response_type=token"
. "&scope=email,publish_stream";
$signed_request = $_REQUEST["signed_request"];
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
$data = json_decode(base64_decode(strtr($payload, '-_', '+/')), true);
if (empty($data["user_id"])) {
echo("<script> top.location.href='" . $auth_url . "'</script>");
} else {
echo ("Welcome User: " . $data["user_id"]);
}
B) Redirect Page
// Create kk-fb app instance
$facebook = new Facebook(array(
'appId' => KKFB_ID,
'secret' => KKFB_KY,
'oauth' => true,
));
$app_id = KKFB_ID;
$secret = KKFB_KY;
$signed_request = $_REQUEST["signed_request"];
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
$data = json_decode(base64_decode(strtr($payload, '-_', '+/')), true);
$user = $facebook->getUser();
$access_token = $facebook->getAccessToken();
echo "User: $user <br>";
echo "Access Token: $access_token <br>";
echo "Signed Request: $signed_request <br>";
var_dump($_REQUEST);
Here is what shows up as these echo results:
User: 0
Access Token: 126736467765|**SECRET**
Signed Request:
array(3) { ["_qca"]=> string(26) "P0-709927483-1291994912966" ["_switchTo5x"]=> string(2) "30" ["PHPSESSID"]=> string(26) "frugi545cdl15gjind1fnv6pq1" }
Interestingly, when the test user goes back to the index page the if condition is satisfied and we can get the correct access token:
Welcome User: 100002908746828
Access Token: 126736467765|2.AQBgcyzfu75IMCjw.3600.1315544400.1-100002908746828|m5IYEm976tJAkbTLdxHAhhgKmz8
Obviously, we are still missing something!? Also, we need to learn how to get the expiration time as a variable too so we can store both of these in our database.
OK, let's try this again.
Server-side vs Client-side Authentication
You are exclusively using the PHP SDK, so you want to do server-side authentication, where the authentication code is sent to the server over HTTP via the URL. This will allow you to fetch an access token for the user on the first page load after auth (in your case, the redirect page). The auth_url you are currently constructing is setting response_type=token, which forces the redirect to use client-side auth mode and set the token in the URL fragment instead of in the query. You should remove that parameter completely. In fact, I highly recommend you just use the PHP SDK instead of constructing that URL yourself. See example below.
Application Access Tokens
The odd-looking access token 126736467765|SECRET is your application access token, which is composed of your app ID and secret key. The application access token is returned by getAccessToken() if no user access token is available (because some API calls require at least some sort of access token). This also means that you've revealed your secret key to the world via this blog post, so you should reset your app secret otherwise anyone will be able to make API calls on your behalf. I highly recommend you elide parts of your access tokens if you share them with others.
Token Expiration
The OAuth 2.0 flow and v3.1.1 of the PHP SDK don't make determining the expiration time of a token all that easy. I would suggest attempting to make the API call, and then refreshing the token if the API call fails with an OAuthException. Tokens can be invalid even if they haven't expired, so this deals with more cases. However, if you still want to maintain the expiration date on your end, you might just want to extract it from the token itself. If you have an expiring token, then the expiration timestamp will be contained within that string. Here's a function I put together quickly to extract that:
function extractExpirationFromToken($access_token) {
$segments = explode('|', $access_token);
if(count($segments) < 2) { return 0; }
$segments = explode('.', $segments[1]);
if(count($segments) < 4) { return 0; }
$expires = $segments[3];
$dash_pos = strrpos($expires, '-');
if($dash_pos !== false) {
$expires = substr($expires, 0, $dash_pos);
}
return $expires;
}
New Index Page Code
// Create kk-fb app instance
$facebook = new Facebook(array(
'appId' => KKFB_ID,
'secret' => KKFB_KY,
));
$canvas_auth = 'http://karmakorn.com/karmakorn/alpha20/kk-fb-auth.php';
$auth_url = $facebook->getLoginUrl(array(
'scope' => 'email,publish_stream',
'redirect_uri' => $canvas_auth, // you could just redirect back to this index page though
));
$user = $facebook->getUser();
if (empty($user)) {
echo("<script> top.location.href='" . $auth_url . "'</script>");
} else {
echo ("Welcome User: " . $user);
}
Redirect Page
I don't think you need this page at all. You could just redirect the user back to your original index page.
// Create kk-fb app instance
$facebook = new Facebook(array(
'appId' => KKFB_ID,
'secret' => KKFB_KY,
));
$user = $facebook->getUser();
$access_token = $facebook->getAccessToken();
// also copy the function definition given earlier
$expiration = extractExpirationFromToken($access_token);
echo "User: $user <br>";
echo "Access Token: $access_token <br>";
echo "Expiration: $expiration <br>";
echo "Request: <br>";
var_dump($_REQUEST);
You can use the facebook build in method getAccessToken() for example;
$access_token = $facebook->getAccessToken();
This will give you the access token to your variable, now if you are getting it empty, remember to first check if the fuid is being properly catch, if it isn't you might need to review your settings be sure your "App Domain" is set this part is very important after setting it correctly you need to reset your app secret, then set your new values in your auth code. Hope this help, let me know :)
pd. Also remember to keep the scope of your variables visible in your whole php file or class.
Problem
The access_token in your pasted URL is not part of the query string, but instead contained in the URL fragment (after the #). URL fragments are not sent to the web server, and are readable only by client-side code like Javascript. Therefore the PHP SDK only sees http://karmakorn.com/karmakorn/alpha20/kk-fb-auth.php, which is why $_REQUEST does not contain an access_token key.
Questions / Notes
What are you using for your redirect_uri? I think you want to be using something like http://apps.facebook.com/your_canvas_url/
You shouldn't need to call parse_signed_request yourself or copy any code from the signed request page. The PHP SDK will do that for you. Just call:
$facebook = new Facebook(array(
'appId' => '…',
'secret' => '…',
));
$access_token = $facebook->getAccessToken();
Possible solutions
Also use the Facebook Javascript SDK. You can start by adding its <script> tag in your destination page (kk-fb-auth.php) (see the docs for full details; don't forget to set oauth: true). The JS SDK should set a cookie (named fbsr_126736467765) which the PHP SDK will be able to read via $_REQUEST or $_COOKIE on subsequent page loads.
If you want to do this with PHP, you can get the user's access token with a separate call to the Graph API at your redirect_uri. For this you need to change the response_type of your $auth_url in your index page to "code" or "code token".
Then, at your redirect page, Facebook will add a "code" parameter in the querystring. This API call will return you the full access_token and expiration time:
https://graph.facebook.com/oauth/access_token?
client_id=YOUR_APP_ID&
redirect_uri=YOUR_URL&
client_secret=YOUR_APP_SECRET&
code=$_REQUEST['code']
For more information you can refer to the docs on authentication.

Resources