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.
Related
I am looking for the right way on how to check, if a user is logged in, in the Shopware 6 storefront. I am writing a plugin (not an app), and want to use this in Controllers and/or Subscribers.
Should I:
Use the Storefront API? (but how? which path?)
Use the default symfony way? (isGranted) - but with which Roles? Isn't the role handling different?
Use some built-in functionality like a special service that I can fetch by Dependeny Injection (but which one?)?
Solution:
Thanks to #Uwe Kleinmann, I found a solution, that works in a subscriber like this:
public static function getSubscribedEvents()
{
return [
ProductPageLoadedEvent::class => 'onProductPageLoaded'
];
}
public function onProductPageLoaded(ProductPageLoadedEvent $event): void
{
$saleschannelContext = $event->getSaleschannelContext();
$customer = $saleschannelContext->getCustomer();
if(NULL === $customer) {
$customer = 'not-logged-in';
}
$event->getPage()->addExtension(
'myextension', new ArrayStruct([
'test' => $customer
])
);
}
The SalesChannelContext has a $customer (accessible with getCustomer()) attribute. This context is usually injected into both Storefront controllers and subscribers for any Storefront events.
It is only set, if the current user is logged-in.
You may also use the _loginRequired and _loginRequiredAllowGuest flags in the #Route annotation of a storefront controller's method. This is handy if you only want to allow access for logged in customers as this will automatically redirect logged out users to the login page and back to the origin after they logged in.
/**
* #Route("/my/custom/page", name="frontend.custom.page", methods={"GET"}, defaults={"_loginRequired"=true, "_loginRequiredAllowGuest"=true})
*/
I am trying to implement a feature where, after logging in, a user gets redirected to a URL depending on their role. I have the roles part set up, but I'm having trouble testing the user's properties immediately after login.
I followed the instructions here to create a user login page. I have an AuthController that looks like this:
namespace App\Http\Controllers\Auth;
use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller {
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
protected $redirectTo = '/test';
...
}
My __construct() function validates the user, but I don't know how to access the user object only immediately after login. This is what I presently have:
public function __construct() {
$this->middleware('guest', ['except' => 'getLogout']);
if ( \Auth::check() ) {
$user = \Auth::user();
if ( $user->admin() ) {
// an admin
$this->redirectTo = '/admin';
} else {
// it's a client
$this->redirectTo = '/client/dashboard';
}
}
$user = \Auth::user();
if ( is_object($user) ) {
} else {
$this->redirectTo = '/auth-not-object';
}
}
When I first attempt to log in with an administrator account, I get to the path /auth-not-object, because there isn't any authenticated user object at that point.
After having attempted to log in, but getting a bad redirect, when I revisit the /login url, I get redirected to /home, which I believe is the default $redirectTo in the traits this class uses. So that means we've passed the AuthController __construct() method without having changed the $redirectTo, even though there is an authenticated user.
I've found other questions, such as How to add extra logic on login condition in Laravel 5.2 and laravel redirect to url after login, but I don't understand how to apply those answers. For instance, the accepted answer to the second question shows new methods, getCredentials() and login(), which don't exist in the poster's original class. I am not sure in what class to add them, or where to call them from, in my codebase.
Other similar answers show a radically different way of authenticating users, such as this. It seems that, to use that solution, I would need to re-write my code, and forgo the use of the traits, which include bonus features like login throttling and so on.
Is there a way I can redirect users based on role after login, while still using these built-in traits?
Im not sure if the 5.1 auth is the same as the 5.2 auth, but if it is, remove all that from the construct and add this method:
protected function handleUserWasAuthenticated( Request $request, $throttles, $guard )
{
if ($throttles) {
$this->clearLoginAttempts( $request );
}
if ( method_exists( $this, 'authenticated' ) ) {
return $this->authenticated( $request, Auth::guard( $guard )->user() );
}
return redirect()->intended( $this->redirectTo );
}
this is the method that will determine the redirect and you have access to the user object.
EDIT
I take the above back, just add the following to your controller;
protected function authenticated( $request, $user ) {
return redirect()->intended( $user->admin() ? '/admin' : '/client/dashboard' );
}
That should work nicely
I have an AuthController, where I have extended the getCredentials method so the user needs to be both existant and active to be able to login. This is the method:
protected function getCredentials(Request $request) {
$credentials = $request->only($this->loginUsername(), 'password');
return array_add($credentials, 'status', '1');
}
I would also like the failed login messages to be different, depending on whether or not the user is active, so the user knows if he is failing his username / password, or just because he hasn't activated his account yet.
I could override the login method of the AuthenticatesUser trait, but it seems overkill to duplicate all the logic just to change that.
Can I extend the sendFailedLoginResponse method to make some sort of validation there, based on the previous Auth::guard->attempt() call? I mean, does that method leave any information behind that allows me to know, after the call has been made, what made the attempt return false?
Or how would I approach this, without having to override a method completely just to make a simple validation?
Thank you.
public function authenticated(Request $request, User $user ) {
// The user was authenticated check if it the active column is true or not
if($user->active == false){
//Store some flash message here saying he's not account is not active
//Log him out after.
Auth::logout();
}
return redirect()->intended( $this->redirectPath() );
}
I have a link I am sending via email. For example, www.swings.com/worker?id=3382&tok=jfli3uf
In this case I want the person to click the link, get sent to the login page(which it does) and then be directed to a controller method WITH the $id and $tok variables. I can't get that part to work. Any ideas? I am only using the RedirectIfAuthenticated class and this is what it looks like:
public function handle($request, Closure $next)
{
$user = $request->user();
if ($this->auth->check()) {
if($user && $user->hasRole('worker'))
{
return redirect('worker');
}
return redirect('home');
}
return $next($request);
}
hasRole is a method I created in the User model that checks the role of the logged in user
You can flash data to the session when redirecting by chaining the with() method:
// in your handle() method:
return redirect('home')->with($request->only('id', 'tok'));
// then in home controller method:
$id = session('id');
$tok = session('tok');
AFTER SOME HOURS I WAS ABLE TO HAVE A SOLUTION:
ReturnIfAuthenticated wasn't changed. I just added the following within my controller that this link should go to:
for instance, the route would be:
Route::get('worker', 'WorkerController#methodINeed');
Within this method:
public function methodINeed() {
$id = Input::get('id');
$tok = Input::get('tok');
// Do what I need this variables to do
}
What I didn't understand and what could not be properly understood is that the auth controller in Laravel 5 is triggered when a user is a guest it will still redirect to the actual method with all its original data once auth is successful. Hope this is helpful.
Auth :: attempt works perfect, but when you pass the second parameter "true" apparently does not care or does not recover with viaRemember
viaRemember fails to work, check this
controller User
`$`userdata = array(
'email' => trim(Input::get('username')),
'password' => trim(Input::get('password'))
);
if(Auth::attempt(`$`userdata, true)){
return Redirect::to('/dashboard');
}
view 'dashboard', always show 777
#if (Auth::viaRemember())
{{666}}
#else
{{777}}
#endif
I have hit the same obstacle, so looking into the code one can see that viaRemember is not meant to be used as a function to check if the user was logged into the system in one of all the ways a user can be logged in.
'viaRemember' is meant to check if a user was logged into the system specifically via the `viaRemember' cookie.
From what I gather, authentication of user is remembered in two ways:
a via remember cookie.
The cookie value is compared to the via remember field in the users table.
a session cookie.
The cookie value is used in the server to get the session from the
session store. On the session object from the store there is data attached. One of the
data items is the user id connected to the session. The first time
the session was created, the system attached the user id to the data
of the season.
In Illuminate\Auth\Guard class:
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller))
{
$user = $this->getUserByRecaller($recaller);
}
return $this->user = $user;
}
The getUserByRecaller function is called only if the session cookie authentication did not work.
The viaRemember flag is only set in the getUserByRecaller function. The viaRemember method is only a simple getter method.
public function viaRemember()
{
return $this->viaRemember;
}
So in the end, we can use Auth::check() that does make all the checks including the viaRemember check. It calls the user() function in the Guard class.
It seems also the viaRemember is only an indicator. You need to do a type of Auth::check() the will get the process of authentication started and so the user() function will be called.
It seems that your project is on Laravel 4.0 but viaRemember() is added in Laravel 4.1! So that's expected.
in config\session.php file change the 'expire_on_close' = false to true and once you close restart your browser, it must be ok.