Authentification with HTTP cookie in Selenium using Behat - selenium

I would like to create a behat definition to authenticate a user using a cookie.
It works with the Behat BrowserKitDriver, when there is no #javascript tag on the behat scenario.
But it did not work with the Behat Selenium2Driver, when there is the #javascript tag like here.
I used the symfony-demo application for demonstrate my tests.
What's wrong in my definition ?
/**
* #Given I am logged in as :username
*/
public function iAmLoggedInAs($username)
{
$driver = $this->getSession()->getDriver();
$session = $this->kernel->getContainer()->get('session');
$user = $this->kernel->getContainer()->get('security.user.provider.concrete.database_users')->loadUserByUsername($username);
$providerKey = 'secured_area';
$token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
$session->set('_security_'.$providerKey, serialize($token));
$session->save();
if ($driver instanceof BrowserKitDriver) {
$client = $driver->getClient();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
} else if ($driver instanceof Selenium2Driver) {
$this->visitPath('/');
} else {
throw new \Exception('Unsupported Driver');
}
$this->getSession()->setCookie($session->getName(), $session->getId());
}
I just want that my last behat test works.
I don't know if I'm clear... ask if not.
If you can do a Pull Request with a fix it will be perfect.

You have a few ways
Option 1. Just Use Mink
If you use Mink, your Context will extend RawWebContext or WebContext so you can access to the Mink Session with getSession().
Then, use setCookie.
As you can see, luckily for us, and this is the point with working with Mink, Cookie Manipulation is supported by many drivers
// ...
$this->visitPath('/');
$this->getSession()->setCookie(
$session->getName(),
$session->getId()
);
Note 1: Mind you, the following are not the same thing:
$session (ref. your question) is an instance of Symfony\Component\HttpFoundation\Session\Session
$this->getSession() returns an instance of Behat\Mink\Session
Note 2: Want to see your cookies?
var_dump($driver->getClient()->getCookieJar());
Option 2. Access Selenium2 Session
Don't hesitate to dive in the code to see how Selenium2 WebDriver Session works.
You will surely find absolute joy and peace of mind.
else if ($driver instanceof Selenium2Driver) {
$this->visitPath('/');
$cookie = array(
"domain" => "", <----- You can even add your domain here
"name" => $session->getName(),
"value" => $session->getId(),
"path" => "/",
"secure" => False
);
$driver->getWebDriverSession()->setCookie($cookie);
}
Note 3: Want to see your cookies?
var_dump($driver->getWebDriverSession()->getAllCookies());

Here you are the code I am using for automatically authenticate as a USER. This allows me saving a lot of time (login test should be made anyway in another feature file):
Sample Scenario into a YOUR_FILE.feature example file:
Scenario: List Backend Products
Given I auto authenticate as "YOUR_USER_NAME"
Then I go to "http://YOUR_SITE/backend/products"
Then I should see "Backend Products Management"
/**
* #Given /^I auto authenticate as "([^"]*)"$/
*/
public function iAutoAuthenticateAs($userName)
{
$user = $this->getContainer()->get('fos_user.user_manager')->findUserByUsername($userName);
$providerKey = $this->getContainer()->getParameter('fos_user.firewall_name');
$token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
$context = $this->getContainer()->get('security.token_storage');
$session = $this->getContainer()->get('session');
$context->setToken($token);
$session->set('_security_'.$providerKey, serialize($token));
$session->save();
$this->getSession()->setCookie($session->getName(), $session->getId());
}

Related

Auth in Symfony 4.4 tests only works for first request

I have tests in my Symfony 4.4 application that assert on a page that requires authentication. If my test simply hits an authenticated page and asserts on its content, it works as expected. However, if I request a page and then attempt to interact with the page — like submitting a form — I get an Access Denied error for the interaction request.
For example, this works fine:
/**
* #test
*/
public function it_allows_the_user_to_edit_their_profile()
{
$user = UserFactory::new()->createOne();
$this->auth($user);
$this->appClient->request('GET', '/profile');
$this->assertResponseIsSuccessful();
}
/**
* Simulate authentication
*
* #param $user
*/
protected function auth($user)
{
$session = self::$container->get('session');
$firewallName = 'main';
$user = $user->object();
$token = new UsernamePasswordToken($user, null, $firewallName, $user->getRoles());
$session->set('_security_'.$firewallName, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->appClient->getCookieJar()->set($cookie);
}
But this throws an error of Symfony\Component\Security\Core\Exception\AccessDeniedException: Access Denied. on the $this->appClient->submit($form); call:
/**
* #test
*/
public function it_allows_the_user_to_edit_their_profile()
{
$user = UserFactory::new()->createOne();
$this->auth($user);
$crawler = $this->appClient->request('GET', '/profile');
$form = $crawler->filter('[data-test="user-edit-profile"]')->form();
$form['profile_edit_form[first_name]'] = 'Foo';
$form['profile_edit_form[last_name]'] = 'Bar';
$this->appClient->submit($form);
$this->assertResponseIsSuccessful();
}
I have tried making a 2nd call to the auth() method before calling submit(), but the same error is thrown.
So it seems like the session isn't being terminated after the first appClient request which causes the 2nd request to fail.
Any ideas what might be a way around this?

Respect\Validation custom Rule with PDO?

I am learning Slim Framework v4 and decided to use Respect\Validation to validate inputted data and have hit a snag where I do not know how to inject the PDO into my custom rule I created.
The idea is to validate some inputs against the database if the provided data exist (or in another instances, if it was inputted correctly). In this specific case, I am tying to validate user's credentials for log in. My idea is this:
AuthController.php:
v::with('app\\Validators\\');
$userValidation = v::notBlank()->email()->length(null, 255)->EmailExists()->setName('email');
EmailExists() is my custom rule.
EmailExists.php:
namespace app\Validators;
use PDO;
use Respect\Validation\Rules\AbstractRule;
class EmailExists extends AbstractRule
{
protected $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function validate($input, $id = null)
{
// a PDO query that checks if the email exists in database
}
}
But I get an error of Too few arguments to function app\Validators\EmailExists::__construct(), 0 passed and exactly 1 expected, which is somewhat expected since the AbstractRule does not have a PDO injected and my class extends it.
So how to inject the PDO interface so that I can use it in my custom rules?
Are you guys using another approach in validating this kind of data? Do note that I am writing an API, so the database validation is somewhat a must and after Googling for past two days, I have no solutions at hand.
I am also using a PHP-DI where I create PDO interface. This is my dependencies.php file:
declare(strict_types=1);
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use app\Handlers\SessionMiddleware;
return function (ContainerBuilder $containerBuilder) {
$containerBuilder->addDefinitions([
PDO::class => function (ContainerInterface $c) {
$settings = $c->get('settings')['db'];
$db = new PDO("mysql:host={$settings['host']};dbname={$settings['database']};charset={$settings['charset']},{$settings['username']},{$settings['password']}");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8',time_zone='{$offset}'");
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $db;
},
'session' => function(ContainerInterface $c) {
return new SessionMiddleware;
}
]);
};
And (part of) index.php:
declare(strict_types=1);
use DI\ContainerBuilder;
use Slim\Factory\AppFactory;
// Instantiate PHP-DI ContainerBuilder
$containerBuilder = new ContainerBuilder();
// Set up settings
$settings = require __DIR__ . '/../app/settings.php';
$settings($containerBuilder);
// Set up dependencies
$dependencies = require __DIR__ . '/../app/dependencies.php';
$dependencies($containerBuilder);
// Build PHP-DI Container instance
$container = $containerBuilder->build();
// Instantiate the app
AppFactory::setContainer($container);
$app = AppFactory::create();
// Register middleware
$middleware = require __DIR__ . '/../app/middleware.php';
$middleware($app);
// Register routes
$routes = require __DIR__ . '/../app/routes.php';
$routes($app);
// Add Routing Middleware
$app->addRoutingMiddleware();
// Run App & Emit Response
$response = $app->handle($request);
$responseEmitter = new ResponseEmitter();
$responseEmitter->emit($response);
Any help would be appreciated.
Use your user model to count the number of rows in the user table where there is a hit.
If it is not exactly 0, the check returns false, if it is exactly 0, the check passes.
So you don't have to include a PDO at this point. I use Slim 3 and that works quite well.
namespace app\Validators;
use Respect\Validation\Rules\AbstractRule;
class EmailAvailable extends AbstractRule {
/**
* #param $input
*
* #return bool
*/
public function validate ($sInput) {
return User::where('user_email', $sInput)->count() === 0;
}
}
class EmailAvailable extends AbstractRule {
/**
* #param $input
*
* #return bool
*/
public function validate ($sInput) {
return User::where('user_email', $sInput)->count() === 0;
}
}

google account login for the first time displays error

I am trying to login the user with google account in my application. I am having a problem when the user is logged in for the first time with google account in my app it shows this error:
Argument 1 passed to Illuminate\Auth\Guard::login() must implement interface Illuminate\Auth\UserInterface, null given
In my controller I have this:
public function loginWithGoogle() {
// get data from input
$code = Input::get('code');
// get google service
$googleService = Artdarek\OAuth\Facade\OAuth::consumer("Google");
if (!empty($code)) {
// This was a callback request from google, get the token
$token = $googleService->requestAccessToken($code);
// Send a request with it
$result = json_decode($googleService->request('https://www.googleapis.com/oauth2/v1/userinfo'), true);
$user = User::whereEmail($result['email'])->first(['id']);
if (empty($user)) {
$data = new User;
$data->Username = $result['name'];
$data->email = $result['email'];
$data->google_id = $result['id'];
$data->first_name = $result['given_name'];
$data->last_name = $result['family_name'];
$data->save();
}
Auth::login($user);
return Redirect::to('/');
}
// if not ask for permission first
else {
// get googleService authorization
$url = $googleService->getAuthorizationUri();
// return to facebook login url
return Redirect::to((string) $url);
}
}
I know the problem is with Auth::login($user); as insert is performed at the same time with Auth::login($user); and it doesn't find data from database for the first time, but I don't know how to avoid this error and instead redirects to the main page even when the user is logged in for the first time. After this error the user is logged in, but how to avoid this?
Without knowing whether the rest of the code works, you definitely have a problem here:
if (empty($user)) {
$data = new User;
(...)
$data->save();
}
Auth::login($user);
When you're done creating your user, the $user variable is still empty. Your user is actually called $data. You should either rename the variable, or do the login with $data instead. Hopefully, that's enough to make the code work. :)

ZF2 Authentication

i am developing an application using ZF2. I have done the user authentication with username & password. But, i would like to check an additional column(example: status) in authentication.
I have done the following codes.
public function authenticate()
{
$this->authAdapter = new AuthAdapter($this->dbAdapter,
'usertable',
'username',
'password'
);
$this->authAdapter->setIdentity($this->username)
->setCredential($this->password)
->setCredentialTreatment('MD5(?)');
$result = $this->authAdapter->authenticate();
return $result;
}
How can i check the column 'status' in authentication?
Note: status value should be 1.
Thanks.
When I was building my authentication using zf2 and doctrine, I have created authorization plugin and customized this adapter for passing extra column for authentication.
You probably need to go on similar directions.
$adapter = new AuthAdapter($db,
'users',
'username',
'password',
'MD5(?)'
);
// get select object (by reference)
$select = $adapter->getDbSelect();
$select->where('active = "TRUE"');
// authenticate, this ensures that users.active = TRUE
$adapter->authenticate();
Reference
After changes your code should look something like this.
public function authenticate()
{
$this->authAdapter = new AuthAdapter($this->dbAdapter,
'usertable',
'username',
'password'
);
$select = $this->authAdapter->getDbSelect();
$select->where('status= "1"');
$this->authAdapter->setIdentity($this->username)
->setCredential($this->password)
->setCredentialTreatment('MD5(?)');
$result = $this->authAdapter->authenticate();
return $result;
}
ZF2 provides a another way to handle additional checks using other columns than the ones foreseen for identity and credential thanks to the method getResultRowObject. All columns of usertable in your example are available as properties of the object returned by getResultRowObject(). So you could expand your code with this :
if ($result->isValid()) {
$identityRowObject = $this->authAdapter->getResultRowObject();
$status = $identityRowObject->status;
// do whatever complex checking you need with $status...
}
Regards,
Marc

How to write a functional test with user authentication?

I am writing a functional test for a page that requires user authentication. I am using the sfDoctrineGuard plugin.
How do I authenticate a user in my test?
Do I have to enter every test through the sign in screen?
Here is my incorrect code:
$b->post('/sfGuardAuth/signin',
array('signin[password]' => 'password',
'signin[username]' => 'user',
'signin[_csrf_token]' => '7bd809388ed8bf763fc5fccc255d042e'))->
with('response')->begin()->
checkElement('h2', 'Welcome Humans')->
end()
Thank you
The tricky part about doing a signin is that the test browser wipes out the context object before each request (see sfBrowser::call()).
You can authenticate the user by injecting a listener which will call the user's signIn() method when the context.load_factories event fires during context initialization:
function signin( sfEvent $event )
{
/* #var $user sfGuardSecurityUser */
if( ! $user = $event->getSubject()->getUser() )
{
throw new RuntimeException('User object not created.');
}
if( ! $user instanceof sfGuardSecurityUser )
{
throw new LogicException(sprintf(
'Cannot log in %s; sfGuardSecurityUser expected.',
get_class($user)
));
}
if( $user->isAuthenticated() )
{
$user->signOut();
}
/* Magic happens here: */
$user->signIn($desired_user_to_log_in_as);
$event->getSubject()->getEventDispatcher()->notify(new sfEvent(
$this,
'application.log',
array(sprintf('User is logged in as "%s".', $user->getUsername()))
));
}
/* Set signin() to fire when the browser inits the context for subsequent
* requests.
*/
$b->addListener('context.load_factories', 'signin');
This will cause the browser to sign in the user for all subsequent requests. Note that sfBrowser does not have a removeListener() method.
Adapted from sfJwtPhpUnitPlugin (FD: I'm the lead dev for this project).
Yes, you do have to sign in to carry out tests. Fortunately, this is much simpler than the method you illustrate above. See the "better and simpler way" on this blog post.
You could make the signin method part of any TestFunctional class according to how you've structured your tests.