panther
panther copied to clipboard
Panther with Authentication
Hello,
First I would like to thank you to improve this part of symfony and helping us to get rid of Behat.
I have created a symfony app which needs authentication. This means that / is protected by the firewall main and every URL needs an authenticated user. On top of that I have custom authenticator which extends AbstractGuardAuthenticator. The authentication process is based on CAS (not my choice I have to cope with it).
I have tried to start my functional tests using Panther and followed the documentation here : https://github.com/symfony/panther
So far so good :) Then I have to deal immediately with the authentication, so I found this tutorial : https://symfony.com/doc/4.3/testing/http_authentication.html
First thing I have discovered is that I need to visit a page on my website otherwise I get this error :
Facebook\WebDriver\Exception\InvalidCookieDomainException: invalid cookie domain
(Session info: chrome=79.0.3945.117)
(Driver info: chromedriver=78.0.3904.70 (edb9c9f3de0247fd912a77b7f6cae7447f6d3ad5-refs/branch-heads/3904@{#800}),platform=Mac OS X 10.15.2 x86_64)
Ok then I have created a test page accessible anonymously and got rid of the error. Not sure this is a best practice.
Then I was able to run the following code :
<?php
namespace App\Tests;
use App\Entity\User;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
class HomeControllerTest extends PantherTestCase
{
private $client = null;
public function setUp()
{
$this->client = static::createPantherClient();
}
public function testHome()
{
$this->logIn();
$crawler = $this->client->request('GET', '/test');
sleep(5);
$user = self::$container->get('security.helper')->getUser();
echo ('The user is :'.$user);
$crawler = $this->client->request('GET', '/');
}
private function logIn()
{
$doctrine = self::$container->get('doctrine.orm.default_entity_manager');
/** @var User $user */
$user = $doctrine->getRepository(User::class)->findOneBy(['email' => '[email protected]']);
$session = self::$container->get('session');
// you may need to use a different token class depending on your application.
// for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
$token = new PostAuthenticationGuardToken($user, 'main', $user->getRoles());
$session->set('_security_main', serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
}
The problem is that I am still redirected to the CAS authentication page calling $crawler = $this->client->request('GET', '/');
My user is always NULL which means I am not authenticated.
I thought Session Storage Mock file could be a suspect but actually all my tests were unsuccessful.
Help :)
Thanks a lot for reading
I have the same issue.
Hello,
If anyone is getting the same error about user authentication in functional tests implementing the token, I found the issue.
This was not related to Panther but to the security component, indeed I did not notice the logs telling me that the token needed to be refresh and then the user was automatically deauthenticated => cannot refresh the token because the user has changed. This means that symfony was not able to compare properly the user with the authentication system which I guess is by default comparing username password and salt. But in the case of CAS authentication you do not have password or salt but only the username.
In order to solve this, you need to implement Symfony\Component\Security\Core\User\EquatableInterface and the related method isEqualTo.
public function isEqualTo(UserInterface $user)
{
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
So when the security component will check if it needs a refresh, it will work properly.
I am now able to run a classic WebTestCase and to access my home page.
The only issue remaining is the invalid cookie domain which is preventing me to use Panther for now. I will investigate on this side and let you know in case I fund something.
Cheers
Hello,
I have tried many things on my side, and this is not possible to get rid of the "invalid cookie domain" error, unless you create a new page (simple page with nothing or an h1 is enough) to initiate the process. This is not related to Panther but to php webdriver. I found many entries about it on stackoverflow.
The real issue is that my code to log in is working with absolutely no issue doing a WebTestCase but is not working with Panther.
private function logIn($email)
{
/** @var User $user */
$user = self::$container->get('doctrine')->getRepository(User::class)->findOneBy(['email' => $email]);
$session = self::$container->get('session');
$firewallName = 'main';
// if you don't define multiple connected firewalls, the context defaults to the firewall name
// See https://symfony.com/doc/current/reference/configuration/security.html#firewall-context
$firewallContext = 'main';
// you may need to use a different token class depending on your application.
// for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
$token = new PostAuthenticationGuardToken($user, $firewallName, ['ROLE_ADMIN']);
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
As far as I understand, despite I had the cookie to Panther Cookie Jar it is not used to authenticate and it keeps sending me to the CAS authentication page.
Here is the content of the cookie Jar of my Panther client:
array(1) {
[0]=>
object(Symfony\Component\BrowserKit\Cookie)#602 (9) {
["name":protected]=>
string(10) "MOCKSESSID"
["value":protected]=>
string(64) "a1c18866bc13ca0b2233feb351b02788a93961672793420bfa24dcfc25a93923"
["expires":protected]=>
NULL
["path":protected]=>
string(1) "/"
["domain":protected]=>
string(9) "127.0.0.1"
["secure":protected]=>
bool(false)
["httponly":protected]=>
bool(true)
["rawValue":protected]=>
string(64) "a1c18866bc13ca0b2233feb351b02788a93961672793420bfa24dcfc25a93923"
["samesite":"Symfony\Component\BrowserKit\Cookie":private]=>
NULL
}
}
Based on the log in my test environment I can see no entry from the Security component like using the WebTestCase. So my conclusion is that the cookie is not even considered and used for authentication in Symfony. PantherClient is just redirecting me to the authentication page.
Does anyone have a clue ?
Thanks a lot,
here same problem... some glue? thanks!
Same problem here... any idea ?
@andrescevp , @gponty , I ended up using classic classic Web test case and the Crawler which are working perfectly with the code above. I even found a way to add items to the collection without using JS. Maybe I will look into Panther once we have a solution...
This it what i had to throw together to get it working. I had to set readinessPath in the option to make sure the client wasn't redirected to the login page as I use SAML SSO. Then you need to make another request to your site before it will allow you to set a cookie. It doesn't matter if the page returns a 404 response.
class HomeControllerTest extends PantherTestCase
{
private $client;
protected function setUp()
{
$this->client = static::createPantherClient(['readinessPath' => '/error']);
}
public function testHome()
{
$this->logIn();
$this->client->request('GET', '/');
$this->assertPageTitleContains('Home Page');
}
private function logIn()
{
$this->client->request('GET', '/error');
$doctrine = self::$container->get('doctrine.orm.default_entity_manager');
/** @var User $user */
$user = $doctrine->getRepository(User::class)->findOneBy(['email' => '[email protected]']);
$session = self::$container->get('session');
// you may need to use a different token class depending on your application.
// for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
$token = new PostAuthenticationGuardToken($user, 'main',$user->getRoles());
$session->set('_security_main', serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
}
Hi @JohnstonCode, you should check the upcoming changes on SF 5.1 :) There will be a new auth system for the tests. As far as I understood there will be no need for us anymore to generate this code. https://symfony.com/blog/new-in-symfony-5-1-simpler-login-in-tests
Cheers :)
Thanks, looks good hopefully this resolves our issue.
Same issue here :/
Same here with :
$client = static::createPantherClient();
/** @var User $user */
$user = $this->getSuperAdminAccount();
$session = self::$container->get('session');
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$session->set('_security_main', serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
Any solution ?
For setting-up cookie's domain, you have to request firstly on the domain, then set-up the cookie...
$client->request('GET', '/');
@nizarRebhi thanks for the comment, working a treat for me now
So I've been investigating the login process as well and it seems $client->loginUser is not present in the PantherClient. I'm not sure if this should be implemented or not, but if not implemented, I suppose the project should provide an equally simple and efficient method to authenticate.
Us cypress users are used to just send a POST request to the login Url that gets us logged in for the e2e tests without going through the UI login process. May be panther can allow for POST calls in their client (I've tried this and it just plainly said POST calls are not supported) or provide a shortcut function like loginUser
@shadowc Have you been able to login yourself? I have tried methods outlined earlier - but continue to get CSRF errors. The createClient and then ->loginUser() method works fine. But using the pantherClient and setting the cookies with a generated token value doesn't seem to work. I suspect it is because the test is running in test env but the URL it's hitting is in the dev environment, but I haven't figured out where to go from here.
@Sethbass have you found a solution to getting PantherClient to work with authentication?
@JohnstonCode did you say you got it working with PantherClient and authentication?
Would love to get this working.
So I've been investigating the login process as well and it seems
$client->loginUseris not present in the PantherClient. I'm not sure if this should be implemented or not, but if not implemented, I suppose the project should provide an equally simple and efficient method to authenticate.Us cypress users are used to just send a POST request to the login Url that gets us logged in for the e2e tests without going through the UI login process. May be panther can allow for POST calls in their client (I've tried this and it just plainly said POST calls are not supported) or provide a shortcut function like
loginUser
Dear SethBass,
I'm trying to do functional tests on an app protected by CAS and I'm not able to pass the authentication yet.
I have a few questions, could you please help me?
- Could you show me your security.yaml ?
- Is there something else, besides implements
EquatableInterfaceto do to get this working? - Could you please tell me which CAS bundle you're using?
Thanks!
Hi,
Has anyone found a solution to use :
static::createClient()->loginUser($myUser)
in a PanterClient :
static::createPantherClient()->loginUser($myUser)
PS : it doesn't work so (Error: Call to undefined method Symfony\Component\Panther\Client::loginUser())
Symfony 5.3 PphUnit 9.5
Thanks!
For me it was the issue with empty cookies when env switched to "test"
Try to comment out mock session storage factory:
#framework.yaml
when@test:
framework:
test: true
# session:
# storage_factory_id: session.storage.factory.mock_file
I want to use panther to test an application which uses a SSO to authenticate. Unfortunantaly up to now it is not possible to test this application with panther because of the missing login fuction wich is implemented in the WebTestCase of the kernel application. When I want to login into the application I get sveral redirects ant he redirect url has to be correct as well. so the effort is so huge to simulate it with a SSO provider.
I would be so great if here is an update planned.
I'm also experiencing this same issue.
// Assuming an existing user:
$user = iterator_to_array($userRepo->findByRole(User::ROLE_SUPER_ADMIN, 1))[0];
// Get the Panther client:
$client = static::createPantherClient();
// Get a new token:
$token = new OAuthToken('test', $user->getRoles());
$token->setUser($user);
// Assign it to the session:
$session = static::getContainer()->get('session');
$session->set('_security_main', serialize($token));
$session->save();
// Add cookies to the client:
$client->request(Request::METHOD_GET, '/'); // Need to hit the domain first before we can set cookies
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
$client->request(Request::METHOD_GET, '/'); // <-- Still redirected back to the login page
Previously, I tried creating the panther client with the hostname and port configured so that I can use PhpStorm breakpoints with traffic coming from the Panther browser, and I could see that the cookies are being set as expected. But the $_SESSION global doesn't have any of the security token information that I'm setting as shown above. Is there a better way to set session properties that will be used by the Panther browser? Could that be the issue preventing SSO authentication?
Hi,
Has anyone found a solution to use :
static::createClient()->loginUser($myUser)in a PanterClient :static::createPantherClient()->loginUser($myUser)PS : it doesn't work so (Error: Call to undefined method Symfony\Component\Panther\Client::loginUser())
Symfony 5.3 PphUnit 9.5
Thanks!
Still facing this problem, any solution ?
I can authenticate in Panther with CAS, but I am not using a bundle but rather my own Guard implementation.
I can navigate to pages that require authentication, and click through things, but I have hit a wall where my Ajax POST requests are not working. They get triggered but return 404 not found. Posting to json endpoints provided 404, but to HTML endpoints gives a 302 redirect to my login page.
More info on my POST issue and my authentication implementation here: https://github.com/symfony/panther/issues/547
While I am not using a CAS bundle, I based by Guard implementation on this (its a few years old so probably a bit out of date, but the same basic idea): https://github.com/PRayno/CasAuthBundle/blob/master/Security/CasAuthenticator.php
P.S. I don't implement EquatableInterface, and I do have to hit / before setting my login token. I've tested implementing EquatableInterface just now and it does not solve my POST issue.
@shadowc, you said
May be panther can allow for POST calls in their client (I've tried this and it just plainly said POST calls are not supported)
Where is this documented?
I can authenticate in Panther with CAS, but I am not using a bundle but rather my own Guard implementation.
I can navigate to pages that require authentication, and click through things, but I have hit a wall where my Ajax POST requests are not working. They get triggered but return 404 not found. Posting to json endpoints provided 404, but to HTML endpoints gives a 302 redirect to my login page.
More info on my POST issue and my authentication implementation here: https://github.com/symfony/panther/issues/547
While I am not using a CAS bundle, I based by Guard implementation on this (its a few years old so probably a bit out of date, but the same basic idea): https://github.com/PRayno/CasAuthBundle/blob/master/Security/CasAuthenticator.php
P.S. I don't implement EquatableInterface, and I do have to hit / before setting my login token. I've tested implementing EquatableInterface just now and it does not solve my POST issue.
How about using a bundle for CAS ? Wouldn't it be a bit more practical?
Interestingly, other ajax posts seem to work, so maybe there is something about this specific implementation that's wonky. I will test further and report back.
@drupol no, I've found it is not. The CAS protocol is very simple and easy to implement (at least on the consumer side). My coworkers who are depending on phpCas and various CAS bundles have experienced breakage from updates to our CAS servers, due to their package implementations. I, on the other hand, have not :)
In addition, if something breaks or changes (hasn't happened in 4 years), its much easier/quicker for me to fix it myself rather than wait on third party developers to acknowledge and fix the issue or accept my pull request. In general, programming has become way too reliant on dependencies for simple functionality that can be implemented/controlled in house. This comes with all sorts of problems. I can't say my software if free from this problem altogether, not by a long shot. But, thankfully, it is in the context of CAS.
If CAS were more complex, I'd reach for a library, but its very simple XML parsing.
Out of curiosity, which CAS bundle do you recommend?
P.S. I've resolved my ajax POST mystery, and it turned out authentication was not the problem.
@drupol no, I've found it is not. The CAS protocol is very simple and easy to implement (at least on the consumer side). My coworkers who are depending on phpCas and various CAS bundles have experienced breakage from updates to our CAS servers, due to their package implementations. I, on the other hand, have not :)
In addition, if something breaks or changes (hasn't happened in 4 years), its much easier/quicker for me to fix it myself rather than wait on third party developers to acknowledge and fix the issue or accept my pull request. In general, programming has become way too reliant on dependencies for simple functionality that can be implemented/controlled in house. This comes with all sorts of problems. I can't say my software if free from this problem altogether, not by a long shot. But, thankfully, it is in the context of CAS.
If CAS were more complex, I'd reach for a library, but its very simple XML parsing.
Out of curiosity, which CAS bundle do you recommend?
P.S. I've resolved my ajax POST mystery, and it turned out authentication was not the problem.
Try this one: https://github.com/ecphp/cas-bundle
Sorry for the brevity, replying from the smartphone.
all good, thanks for the recommendation
EDIT: I see you're the primary contributor, nice! Thanks for the FOSS offering.
@shadowc, you said
May be panther can allow for POST calls in their client (I've tried this and it just plainly said POST calls are not supported)
Where is this documented?
Not documented. I believe I was referring to an error message that came out in the console. This message is a year old so things could have changed since!
all good, thanks for the recommendation
EDIT: I see you're the primary contributor, nice! Thanks for the FOSS offering.
You're welcome! I developed this bundle for the European Commission and we are using everyday in every Symfony app. Feel free to let me know if anything goes wrong, we're quite responsive!