Symfony 2 BDD Behat context "I am not logged in" - symfony

How can I manualy make sure that user is not logged in my FeatureContext class in BDD Behat scenario? For example I have this scenario:
Scenario: passing wrong values will not get me in
Given I am on "/login"
And I am not logged in
When I fill in "login" with "KtokolwiekWidzialKtokolwiekWie"
When I fill in "password" with "root_as_allways"
When I press "Login"
Then I should be on "/"
And I should see "Wrong login or password"
And I've defined this context:
/**
* #Given /^I am not logged in$/
*/
public function iAmNotLoggedIn()
{
# logout currently login user, if any
$request = $this->kernel->getContainer()->get('request');
$request->getSession()->invalidate();
}
But this gives me an error when running this feature test.
You cannot create a service ("request") of an inactive scope ("request").
Note: I'm using Symfony 2.1 RC-1

Here's the custom step you can use:
/**
* #Given /^I am not logged in$/
*/
public function iAmNotLoggedIn()
{
$this->getMink()
->getSession()
->visit($this->locatePath('/logout'))
;
}
But you probably need to update your steps order:
Given I am not logged in
And I am on "/login"

Just visit "/logout" and you will be logged out. Do this before you go to "/login"

Related

Laravel 5.3 Redefine "reset email" blade template

How to customize the path of the reset email blade template in Laravel 5.3?
The template used is: vendor/laravel/framework/src/Illuminate/Notifications/resources/views/email.blade.php
I'd like to build my own.
Also, how to change the text of this email predefined in: vendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php
public function toMail()
{
return (new MailMessage)
->line([
'You are receiving this email because we received a password reset request for your account.',
'Click the button below to reset your password:',
])
->action('Reset Password', url('password/reset', $this->token))
->line('If you did not request a password reset, no further action is required.');
}
To change template you should use artisan command php artisan vendor:publish it will create blade templates in your resources/views/vendor directory. To change text of email you should override the sendPasswordResetNotification method on your User model. This is described here https://laravel.com/docs/5.3/passwords in Reset Email Customization section.
You must add new method to your User model.
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
and use your own class for notification instead ResetPasswordNotification.
UPDATED: for #lewis4u request
Step by step instruction:
To create a new Notification class, you must use this command line php artisan make:notification MyResetPassword . It will make a new Notification Class 'MyResetPassword' at app/Notifications directory.
add use App\Notifications\MyResetPassword; to your User model
add new method to your User model.
public function sendPasswordResetNotification($token)
{
$this->notify(new MyResetPassword($token));
}
run php artisan command php artisan vendor:publish --tag=laravel-notifications After running this command, the mail notification templates will be located in the resources/views/vendor/notifications directory.
Edit your MyResetPassword class method toMail() if you want to. It's described here https://laravel.com/docs/5.3/notifications
Edit your email blade template if you want to. It's resources/views/vendor/notifications/email.blade.php
Bonus: Laracast video: https://laracasts.com/series/whats-new-in-laravel-5-3/episodes/9
PS: Thanks #Garric15 for suggestion about php artisan make:notification
I wanted to elaborate on a very helpful Eugen’s answer, but didn’t have enough reputation to leave a comment.
In case you like to have your own directory structure, you don’t have to use Blade templates published to views/vendor/notifications/... When you create a new Notification class and start building your MailMessage class, it has a view() method that you can use to override default views:
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->view('emails.password_reset');
// resources/views/emails/password_reset.blade.php will be used instead.
}
In Addition to the above answer for Laravel 5.6, here it is easier to pass variables in an array to your custom email template.
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$url = url('/invoice/'.$this->invoice->id);
return (new MailMessage)
->subject('Invoice Paid')
->markdown('emails.password_reset', ['url' => $url]);
}
https://laravel.com/docs/5.6/notifications

"Cannot set session ID after the session has started" when generating csrf token within Behat test

I'm trying to test that a user with the wrong permissions sees the correct response when visting a page that has a csrf token in the path.
I've added a Behat context step to create a csrf token using the 'security.csrf.token_manager', however then visit the page with this token in the path, I get a "Cannot set session ID after the session has started" 500 error.
Can anyone advise what I'm doing wrong, or how I work around this, please?
/**
* #When /^I go to the application admin archive page for "(?P<status>[^"]*)" application (?P<number>\d+) with a valid token$/
*
* #param string $status
* #param int $number
*/
public function iGoToTheApplicationAdminArchivePageForApplicationWithAValidToken($status, $number)
{
$tokenManager = $this->kernel->getContainer()->get('security.csrf.token_manager');
$token = $tokenManager->getToken(ApplicationAdminController::CSRF_ARCHIVE);
var_dump($token);
$this->visitAdminPage('archive', $status, $number, ['token' => $token]);
}
The specific error message you get seems to be a result of a bug in symfony2. Try to apply maryo's suggestion about extending MockFileSessionStorage class and checking if the id is empty before attempting to set it like this:
public function setId($id)
{
if ($this->id !== $id) {
parent::setId($id);
}
}
You can then use this fixed class in your functional test by doing something like this after creating the client:
$client->getContainer()->set('session', new Session(new FixedMockFileSessionStorageHelper()));
I still got this error at this moment. I fixed this issue by checking your file config of env at
app/config/config_test.yml
I found this configuration under framework section
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
All what you have to do just remove this line test: ~
to be as below:
framework:
session:
storage_id: session.storage.mock_file
profiler:
collect: false
Then clear the cache by
php bin/console cache:clear --env=test --no-debug
Refresh and Enjoy ;)

Symfony 2 inconsistency in logout route: logout redirect to login?

I'd like to solve this inconsistency in my Symfony 2 application: when user is not authenticated path /app/logout redirects to /app/login. Instead, user not authenticated should view an error page (maybe 403).
Here is the security configuration. The IS_AUTHENTICATED_FULLY seems mandatory, as an user can do logout only if it's previously authenticated fully:
access_control:
- { path: ^/app/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/app/logout, roles: IS_AUTHENTICATED_FULLY }
And the logout action of my AccessController:
/**
* #Extra\Route("logout")
* #Extra\Template
*/
public function logoutAction()
{
// Set the token to null and invalidate the session
$this->getSecurityContext()->setToken(null);
$this->getSession()->invalidate();
// Redirect url and seconds (window.location)
$seconds = 5;
$redirect = $this->getRouter()->generate('access_login');
return array('seconds' => $seconds, 'redirect' => $redirect);
}
One solution would be removing the route /app/logout from access control and then throwing an exception if user it's not fully authenticated:
if(false === $this->getSecurityContext()->isGranted('IS_AUTHENTICATED_FULLY'))
throw new AccessDeniedException();
But this way /app/logout would be accessible even from not authenticated users! Anyone knows a better solution?
Just remove the logout path from access_control. Nothing bad is going to happen if a not authenticated user goes to the logout page — it's safe. Don't overengineer this stuff. ;)
BTW, why aren't you using the Symfony's built-in logout controller? You could create a logout handler to put your custom code in it instead of reinventing the wheel by handling all the logout stuff yourself.

Symfony 2 FOSFacebookBundle custom redirection before login

i'm using the FOSUserBundle and the FOSFacebookBundle (for version SF 2.0.x) in my project. Additionally, i implemented and custom FacebookProvider as described in the FOSFacebookBundle documentation. I would like to achieve the following workflow:
1.) A user visits my portal the first time
2.) He clicks the Facebook-Login-Button
3.) Now i need to check, if this user, who clicked the Facebook-Login-Button has already Facebook-Friends on my portal.
4.) If he has friends, redirect him to a sign-up page (including information from Facebook like, username, first_name, last_name, etc.) with prefilled input fields.
5.) If he has no Facebook-friends on my portal, redirect him to another page
I've started looking at the Webprofiler, which events are called. I've started creating my own event listener as descried on this page: http://www.dobervich.com/2011/10/13/login-redirection-revisited/ but the profile shows me my listener in the list of "not called listeners": security.interactive_login SecurityListener::onSecurityInteractiveLogin
Does anyone know, how i could customize this pre-login-check and redirect a user to a page?
Would be great to get some help on this.
Thank you,
Ramo
You need to configure a custom authentication success handler. Configure a service that implements AuthenticationSuccessHandlerInterface:
facebook_auth_success_handler:
class: MyHandler
public: false
arguments:
# your dependencies...
Then add this handler to security.yml under your fos_facebook block:
firewalls:
foo:
fos_facebook:
success_handler: facebook_auth_success_handler
The handler itself should look something like this:
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
$hasFriendsHereAlready = // your logic here
if ($hasFriendsHereAlready) {
$route = 'foo';
} else {
$route = 'bar';
}
return new RedirectResponse($this->router->generate($route));
}

Behat authenticate Symfony2 user

I'm using Behat in Symfony2 / Doctrine2. Now, I have this scenario that boils down to the fact that "if I'm logged in and I go to /login, I shoud go to / instead":
#login
Scenario: Go to the login page while being logged in
Given I am logged in
When I go to "/login"
Then I should be on "/"
For the #login, I created the following:
/**
* #BeforeScenario #login
*/
public function loginUser()
{
$doctrine = $this->getContainer()->get('doctrine');
$userRepository = $doctrine->getRepository('MyTestBundle:User');
$user = $userRepository->find(1); // 1 = id
$token = new UsernamePasswordToken($user, NULL, 'main', $user->getRoles());
$this->getContainer()->get('security.context')->setToken($token);
}
In the "when I go to /login" code (the controller gets called), the token seems gone (not what I intended):
/**
* #Route("/login", name="login")
*/
public function loginAction()
{
$token = $this->get('security.context')->getToken();
$fd = fopen('/tmp/debug.log', 'a');
fwrite($fd, $token);
// prints 'AnonymousToken(user="anon.", authenticated=true, roles="")'
...
But in the FeatureContext, it seems to stick around (the way I hoped it would work). In the "Given I am logged in":
/**
* #Given /^I am logged in$/
*/
public function iAmLoggedIn()
{
$token = $this->getContainer()->get('security.context')->getToken();
$fd = fopen('/tmp/debug.log', 'a');
fwrite($fd, $token);
// prints 'UsernamePasswordToken(user="admin", authenticated=true, roles="ROLE_ADMIN")'
...
I run behat like this:
app/console -e=test behat
I also did this in the controller to be sure it's test:
fwrite($fd, $this->get('kernel')->getEnvironment());
// prints 'test'
Any clue how to authenticate a user? I will have to test a lot of admin pages, so it would be nice if I could hook the login into #BeforeSuite, #BeforeFeature (or #BeforeScenario ...) so that I don't get blocked.
(Suggestions on disabling the authentication mechanism for testing, or a way to stub/mock a user are also welcome.)
Oh my. It doesn't work because the DIC inside your FeatureContext isn't shared with your app - your app has separate kernel and DIC. You can get it through Mink. Or, you can simply do it right way :-)
Right way means, that every part of behavior, that is observable by the enduser, should be described inside *.feature, not inside FeatureContext. It means, that if you want to login a user, you should simply describe it with steps (like: "i am on /login", "and i fill in username ...", "i fill in password" and stuf). If you want to do it in multiple times - you should create a metastep.
Metasteps are simply steps, that describe multiple other steps, for example - "i am logged in as everzet". You could read bout them here: http://docs.behat.org/guides/2.definitions.html#step-execution-chaining
Here is an solution for login with OAuth I've used. After number of times of searching for the answer and landing on this page I thought it would be great to share the solution. Hopefully it will help someone.
Background: Symfony2 App using HWIOAuthBundle, hooked up to some OAuth2 provider.
Problem: How do I implement Given I'm logged in when Behat context in not shared with Symfony context?
Solution:
HWIOAuthBundle uses #buzz service for all API calls to OAuth providers. So all you need to do is replace Buzz client with your implementation which doesn't call external services, but returns the result straight away. This is my implementation:
<?php
namespace Acme\ExampleBundle\Mocks;
use Buzz\Client\ClientInterface;
use Buzz\Message\MessageInterface;
use Buzz\Message\RequestInterface;
class HttpClientMock implements ClientInterface
{
public function setVerifyPeer()
{
return $this;
}
public function setTimeout()
{
return $this;
}
public function setMaxRedirects()
{
return $this;
}
public function setIgnoreErrors()
{
return $this;
}
public function send(RequestInterface $request, MessageInterface $response)
{
if(preg_match('/\/oauth2\/token/', $request->getResource()))
{
$response->setContent(json_encode([
'access_token' => 'valid',
'token_type' => 'bearer',
'expires_in' => 3600
]));
}
elseif(preg_match('/\/oauth2\/me/', $request->getResource()))
{
$response->setContent(json_encode([
'id' => 1,
'username' => 'doctor',
'realname' => 'Doctor Who'
]));
}
else throw new \Exception('This Mock object doesn\'t support this resource');
}
}
Next step is to hijack the class used by HWIOAuthBundle/Buzz and replace it with the implementation above. We need to do it only for test environment.
# app/config/config_test.yml
imports:
- { resource: config_dev.yml }
parameters:
buzz.client.class: Acme\ExampleBundle\Mocks\HttpClientMock
And finally, you need to set require_previous_session to false for test environment - therefore I suggest to pass it as parameter.
# app/config/security.yml
security:
firewalls:
secured_area:
oauth:
require_previous_session: false
Now you can implement your step like this.
Specification:
Feature: Access restricted resource
Scenario: Access restricted resource
Given I'm logged in
When I go to "/secured-area"
Then I should be on "/secured-area"
And the response status code should be 200
Implementation:
<?php
/**
* #Given /^I\'m logged in$/
*/
public function iMLoggedIn()
{
$this->getSession()->visit($this->locatePath('/login/check-yourOauthProvider?code=validCode'));
}
The code you're passing is not relevant, anything you pass will be OK as it's not being checked. You can customise this behaviour in HttpClientMock::send method.
http://robinvdvleuten.nl/blog/handle-authenticated-users-in-behat-mink/ is simple, clean article on how to create a login session and set the Mink session cookie so that the Mink session is logged in. This is much better than using the login form every time to login a user.
It’s ok to call into the layer “inside” the UI layer here (in symfony: talk to the models).
And for all the symfony users out there, behat recommends using a Given step with a tables arguments to set up records instead of fixtures. This way you can read the scenario all in one place and make sense out of it without having to jump between files:
Given there are users:
| username | password | email |
| everzet | 123456 | everzet#knplabs.com |
| fabpot | 22#222 | fabpot#symfony.com |

Resources