I am using Symfony 3.2 with the FOSUserBundle and I am trying to write functional tests for function that require authentication in specific roles.
I used the approach posted by #tftd here, but when I run a phpunit test, I get a 500 error: There is no user provider for user "Symfony\Component\Security\Core\User\User".
My webTestCase class looks like this:
abstract class CustomWebTestCase extends WebTestCase
{
/**
* #param array|null $roles
* #return \Symfony\Bundle\FrameworkBundle\Client
*
* https://stackoverflow.com/questions/35565196/fosuserbundle-phpunit-mock-a-user
*/
protected static function createAuthenticatedClient(array $roles = null) {
// Assign default user roles if no roles have been passed.
if($roles == null) {
$role = new Role('ROLE_SUPER_ADMIN');
$roles = array($role);
} else {
$tmpRoles = array();
foreach($roles as $role)
{
$role = new Role($role);
$tmpRoles[] = $role;
}
$roles = $tmpRoles;
}
$user = new User('test_super_admin', 'passwd', $roles);
return self::createAuthentication(static::createClient(), $user);
}
private static function createAuthentication(Client $client, User $user) {
// Read below regarding config_test.yml!
$session = $client->getContainer()->get('session');
// Authenticate
$firewall = 'main'; // This MUST MATCH the name in your security.firewalls.->user_area<-
$token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
$session->set('_security_'.$firewall, serialize($token));
$session->save();
// Save authentication
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
return $client;
}
And my test routine looks like this:
class TryoutAdminControllerTest extends CustomWebTestCase
{
public function testTryoutListAction()
{
$authenticatedClient = self::createAuthenticatedClient(array('ROLE_USER'));
$crawler = $authenticatedClient->request('GET', '/admin/tryout');
$this->assertEquals(302, $authenticatedClient->getResponse()->getStatusCode(), 'No access allowed!');
$authorizedClient = self::createAuthenticatedClient(array('ROLE_ADMIN'));
$crawler = $authorizedClient->request('GET', '/admin/tryout');
$this->assertEquals(200, $authorizedClient->getResponse()->getStatusCode(), 'Access granted!');
}
}
security.yml:
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
logout: true
anonymous: true
switch_user: true
remember_me:
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
domain: ~
user_provider: fos_userbundle
And finally config_test.yml
imports:
- { resource: config_dev.yml }
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
web_profiler:
toolbar: false
intercept_redirects: false
swiftmailer:
disable_delivery: true
If someone could let me know what I'm missing, I'd appreciate it!
You probably need to instantiate your own User class that extends from FOS\UserBundle\Model\User instead of Symfony\Component\Security\Core\User\User in your WebTestCase since you are using the FosUserBundle user provider.
Consider using a test database and data fixtures as you will probably need it for your functional tests. The LiipFunctionalTestBundle may help.
Keep in mind that nothing is being mocked in this test, as suggested in the question title. Mocks must be used in unit tests, not in functional tests.
Related
I am trying to implement a simple LDAP authentication in my Symfony application.
A user should first be authenticated against LDAP, whereby a custom user entity should be returned from the database.
If a user is not in the database but could be authenticated successfully, I want to create the user.
Except for the automatic creation of the user in the database, it works so far.
providers:
users_db:
entity:
# the class of the entity that represents users
class: 'App\Entity\User'
# the property to query by - e.g. email, username, etc
property: 'username'
users_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: '%env(LDAP_BASE_DN)%'
search_dn: '%env(LDAP_SEARCH_DN)%'
search_password: '%env(LDAP_SEARCH_PASSWORD)%'
default_roles: ROLE_USER
uid_key: uid
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_db
http_basic_ldap:
service: Symfony\Component\Ldap\Ldap
dn_string: '%env(LDAP_DN_STRING)%'
With the above configuration, the auth runs against LDAP but the user comes from the DB. So if I have not created a corresponding user, the login attempt won't work.
I have tried the automatic creation of users via a UserChecker, a UserProvider and a LoginEventListener (listens to event onAuthenticationSuccess), unfortunately without success.
The onAuthenticationSuccess event is only called after successful authentication and can therefore not be used in the configuration described above, because the users_db provider does not (yet) contain the user, even if the LDAP Basic auth works.
Then I tried it with a UserChecker and chained provider [users_ldap, users_db]. This is also executed, but then I no longer get a user object but an LDAP user. So I tried to create my own UserProvider, unfortunately without success.
If anyone knows a good way to do this, I would appreciate an answer or a short comment. Thank you!
I've faced your problem before, I didn't able to save their informations automaticly in my local database, so I followes these steps:
signing in by using cas auth and Ldap as a user provider.
redirect them to form and retrieve them inside the fields like this:
<div>
{{ form_row(user_form.uid, { label: 'UID :*',
required: 'true',
attr: {
value : app.user.uid,
readonly: true
}
}) }}
</div>
After submiting the form thier roles will change from "ROLE_VISIT" to "ROLE_USER" and all LDAP information will be saved at my local database.
you can save some data privatly like this:
<div class="invisible" >
{{ form_row(user_form.genre,{
attr: { value : app.user.supannCivilite }
}) }}
</div>
I'm using this bundle for LDAP so please let me know if you need any help!
Also, you can take a look at my security configration as below, hope it will be useful:
providers:
chain_provider:
chain:
providers: [in_memory, database, ldap]
in_memory:
memory:
users:
__NO_USER__:
password:
roles: ROLE_ANON
database:
entity:
class: App\Entity\User
property: uid
ldap:
id: ldap_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
l3_firewall:
pattern: ^/
provider: chain_provider
security: true
guard:
authenticators:
- cas.security.authentication.authenticator
logout:
path: /logout
success_handler: authentication_handler
invalidate_session: false
access_denied_handler: App\EventListener\AccessDeniedListener
main:
pattern: ^/
security: true
lazy: true
provider: chain_provider
guard:
authenticators:
- cas.security.authentication.authenticator
Thanks to this answer I now got it working.
config/services.yaml
services:
App\Security\UserProvider:
arguments:
$em: '#Doctrine\ORM\EntityManagerInterface'
$ldap: '#Symfony\Component\Ldap\Ldap'
$baseDn: "%env(LDAP_BASE_DN)%"
$searchDn: "%env(LDAP_SEARCH_DN)%"
$searchPassword: "%env(LDAP_SEARCH_PASSWORD)%"
$defaultRoles: ["ROLE_USER"]
$uidKey: "uid"
$extraFields: []
App\EventListener\LoginListener:
arguments:
- "#doctrine.orm.entity_manager"
config/packages/security.yml
security:
enable_authenticator_manager: true
password_hashers:
App\Entity\User: 'auto'
providers:
users:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users
stateless: false
http_basic_ldap:
service: Symfony\Component\Ldap\Ldap
dn_string: 'uid={username},ou=accounts,dc=example,dc=com'
src/Security/UserProvider.php
<?php
namespace App\Security;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Ldap\Security\LdapUserProvider;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Ldap\Security\LdapUser;
class UserProvider extends LdapUserProvider
{
private $em;
public function __construct(EntityManagerInterface $em, Ldap $ldap, string $baseDn, string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [])
{
parent::__construct($ldap, $baseDn, $searchDn, $searchPassword, $defaultRoles, $uidKey, $filter, $passwordAttribute, $extraFields);
$this->em = $em;
}
/**
* Refreshes the user after being reloaded from the session.
*
* #return UserInterface
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
$refreshUser = $this->em->getRepository(User::class)->findOneBy(['username' => $user->getUserIdentifier()]);
return $refreshUser;
}
/**
* Tells Symfony to use this provider for this User class.
*/
public function supportsClass(string $class): bool
{
return User::class === $class || is_subclass_of($class, User::class) || LdapUser::class === $class || is_subclass_of($class, LdapUser::class);
}
}
src/EventListener/LoginListener.php
<?php
namespace App\EventListener;
use App\Entity\User;
use Doctrine\ORM\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Ldap\Security\LdapUser;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class LoginListener implements EventSubscriberInterface
{
private $em;
private $tokenStorage;
function __construct(EntityManager $em, TokenStorageInterface $tokenStorage)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents(): array
{
return [LoginSuccessEvent::class => 'onLoginSuccess'];
}
public function onLoginSuccess(LoginSuccessEvent $loginSuccessEvent)
{
$ldapUser = $loginSuccessEvent->getAuthenticatedToken()->getUser();
if (!($ldapUser instanceof LdapUser)) {
return;
}
$localUser = $this->em->getRepository(User::class)->findOneBy(['username' => $ldapUser->getUserIdentifier()]);
if (!$localUser) {
// No local user found in database -> create new user
$localUser = new User();
$localUser->setUsername($ldapUser->getUserIdentifier());
}
// We don't store user passwords -> generate random token
$rmdBytes = random_bytes(32);
$localUser->setPassword($rmdBytes);
$this->em->persist($localUser);
$this->em->flush();
// Login user
$token = new UsernamePasswordToken($localUser, $rmdBytes, 'main', $localUser->getRoles());
$this->tokenStorage->setToken($token);
}
}
I also implemented EquatableInterface in the User entity as suggested in the referenced stackoverflow.
I'm writing test to do the unit testing of controller. I want to check if I get the proper content for the url, which requires user to be logged in.
I have following test written
class AdminControllerTest extends WebTestCase
{
private $client = null;
public function setUp()
{
$this->client = static::createClient();
}
public function testAdminHome(): void
{
$this->logIn();
$crawler = $this->client->request('GET', '/admin/');
$this->assertSame(Response::HTTP_OK, $this->client->getResponse()->getStatusCode());
$this->assertGreaterThan(0, $crawler->filter('html:contains("Welcome to Admin Panel")')->count());
}
private function logIn()
{
$user = new User();
$user->setId(1);
$user->setEmail('test#admin.com');
$user->setFullName('Admin User');
$user->setEnabled(true);
$session = $this->client->getContainer()->get('session');
$firewallName = 'main';
$firewallContext = 'main';
$token = new UsernamePasswordToken($user, null, $firewallName, ['ROLE_ADMIN']);
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
}
Configuration in my security.yml
security:
encoders:
App\Entity\User: auto
providers:
database_users:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
pattern: ^/
context: main
form_login:
check_path: security_login
login_path: security_login
csrf_token_generator: security.csrf.token_manager
default_target_path: admin_home
logout:
path: security_logout
target: security_login
access_control:
- { path: ^/admin, roles: [ROLE_ADMIN, ROLE_EDITOR] }
role_hierarchy:
ROLE_ADMIN: ROLE_EDITOR
ROLE_EDITOR: ROLE_USER
However assertion for the Response code returns me 200, but I land on login page instead of admin home. Also there is not required content, as I'm on login page not an admin home.
Could you please check the code and help me find out the issue?
User #isom had right, I should have the user object persisted, so I have used fixtures to have a user persisted and then everything is fine.
Instead of creating new user object I pull it from DB with following code:
$user = self::$kernel->getContainer()->get('doctrine')->getRepository(User::class)->find(1);
We have a FOSUserBundle login system authenticating via LDAP and the fr3d LDAP bundle. It behaves like a normal multiple page application using sessions. We also have several RESTful endpoints using the FOSRestbundle and normal sessions for authentication. However, we need to share a few end points with an external application.
We managed to implement JWT using the Lexik bundle. It returns a token just fine. However, I don't know the best way to let a user using our login form to get this token so their request can pass it along in the header or session. My question is how to allow a user to login to our application in a stateful manner, but also receive the JWT and pass it to the server on ajax requests. This way I can allow external clients to connect directly to the API. Below is my symfony2 security configuration, security.yml:
security:
#erase_credentials: false
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [my_user_provider, fr3d_ldapbundle]
in_memory:
memory:
users:
admin: { password: secret, roles: 'ROLE_ADMIN' }
my_user_provider:
id: app.custom_user_provider
fos_userbundle:
id: fos_user.user_provider.username
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
fr3d_ldap: ~
provider: chain_provider
anonymous: true
stateless: false
form_login:
check_path: /api/login_check
username_parameter: username
password_parameter: password
require_previous_session: false
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
provider: chain_provider
stateless: false
lexik_jwt:
throw_exceptions: true
create_entry_point: true
main:
pattern: ^/
fr3d_ldap: ~
form_login:
# provider: fos_userbundle
provider: chain_provider
always_use_default_target_path: true
default_target_path: /
csrf_provider: security.csrf.token_manager
logout: true
anonymous: true
switch_user: { role: ROLE_LIMS-BIOINFO}
EDIT:
Based on Kévin's answer I decided to implement a custom Twig extension to get the token for the logged in user on each page load:
AppBundle/Extension/JsonWebToken.php:
<?php
namespace AppBundle\Extension;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class JsonWebToken extends \Twig_Extension
{
/**
* #var ContainerInterface
*/
private $container;
/**
* #var JWTManagerInterface
*/
private $jwt;
public function __construct(ContainerInterface $container, JWTManagerInterface $jwt)
{
$this->container = $container;
$this->jwt = $jwt;
}
public function getName()
{
return 'json_web_token';
}
public function getFunctions()
{
return [
'json_web_token' => new \Twig_Function_Method($this, 'getToken')
];
}
public function getToken()
{
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$token = $this->jwt->create($user);
return $token;
}
}
app/config/services.yml:
app.twig_jwt:
class: AppBundle\Extension\JsonWebToken
arguments: ["#service_container", "#lexik_jwt_authentication.jwt_manager"]
tags:
- { name: twig.extension }
app/Resources/views/layout.html.twig
<script>window.jsonWebToken = '{{ json_web_token() }}';</script>
app/Resources/modules/layout/app.js:
var jsonWebToken = window.jsonWebToken;
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization","Bearer " + jsonWebToken);
}
});
So far this seems to be working well. It let's my external API users and internal application users share the same authentication methods.
As the JWT token must be stored client-side (and not in a cookie to prevent CSRF attacks), you can use the create method of the lexik_jwt_authentication.jwt_manager service provided by LexikJWTAuthenticationBundle to generate a token after the login, then inject this token in a <script> tag in the generated HTML.
Hey i recently came across this same situation.
To generate the JWT I created a redirect listener
class RedirectListener implements EventSubscriberInterface
{
//the private variables go up here
public function __construct(\Twig_Environment $twig, TokenStorageInterface $sam, EntityManagerInterface $em, JWTTokenManagerInterface $JWTTokenManager)
{
$this->twig = $twig;
$this->sam = $sam;
$this->em = $em;
$this->JWTTokenManager = $JWTTokenManager;
}
public function kernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$route = $request->get('_route');
$routeParams = $request->get('_route_params');
$pathInfo = $request->getPathInfo();
$matchApp = preg_match('/\/app/', $pathInfo);
if ($event->isMasterRequest()) {
if ($matchApp) {
$token = $this->sam->getToken();
if ($token) {
/** #var User $user */
$user = $token->getUser();
if($user instanceof User){
$token = $this->JWTTokenManager->create($user);
$this->twig->addGlobal('jwt', $token);
}
}
}
}
return $event;
}
}
This helped me get the JWT to my twig template (I used my base template to make sure it's present on every page)
{% if jwt is defined %}
<span class="hidden" id="jwt" data-jwt="{{ jwt ? jwt : 'null' }}"></span>
{% endif %}
Now using autobahn JS I can subscribe using the JWT :
let jwt = $('#jwt').data('jwt');
let connection = new Connection({url:"ws://127.0.0.1:8080/ws", realm:"realm1"});
connection.onopen = (session) => {
function onevent(args) {
console.log("Event:", args[0])
}
session.subscribe(jwt, onevent);
}
connection.open();
Next the server can now receive messages with the JWT from the JS
public function __construct(ContainerInterface $container)
{
$router = new Router();
$realm = "realm1";
$router->addInternalClient(new Pusher($realm, $router->getLoop()));
$router->addTransportProvider(new RatchetTransportProvider("0.0.0.0", 8080));
try{
$router->start();
} catch (\Exception $exception){
var_dump($exception->getMessage());
}
}
I now need to register a module for the router that will act as a listener and send messages back to the registered topic (JWT).
I'm not 100% there yet so any advice would be appreciated and I'll keep this updated as I go along.
I've been trying to get a database login system working with Symfony2. I've read through the docs but I'm totally stumped now as to what to do...
Every time I try to log in I just get 'Bad Credentials'.
security.yml
security:
encoders:
D\UserBundle\Entity\User:
algorithm: sha512
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
providers:
user_db:
entity: { class: DUserBundle:User}
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
logout:
path: /logout
target:
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/user, roles: ROLE_USER }
UserRepository.php
<?php
namespace D\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$q = $this
->createQueryBuilder('u')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery()
;
try {
// The Query::getSingleResult() method throws an exception
// if there is no record matching the criteria.
$user = $q->getSingleResult();
} catch (NoResultException $e) {
throw new UsernameNotFoundException(sprintf('Unable to find an active admin AcmeUserBundle:User object identified by "%s".', $username), null, 0, $e);
}
return $user;
}
public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $class));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $this->getEntityName() === $class || is_subclass_of($class, $this->getEntityName());
}
}
User.php
UserController.php
that's my database, table(users)
id:5
username:Pawel
salt:5633267d072cd3119af5270d64b4b45d
password:test
email:test#test.pl
is_active1
Your security.yml should look like this
providers:
user_db:
entity: { class: DUserBundle:User, property: username }
Yo do not need the UserRepository class at all.
Really late sorry but as the question is not answered yet...
I am not sure if you can use the alias of your entity as the "class" value for your provider.
Maybe try to set it up with the complete namespaced entity name:
volunteers:
entity:
class: D\UserBundle\Entity\User
But I think the main problem is that your password should - at least, and probably HAVE TO - be encrypted in your database.
If you want to use the security encoder that you define in your security.yml config file,
the right way - according to the documentation - is to manage it after your registration form validation.
Let's imagine you have a registration form for your users :
# Any routing file
d_userbundle_register:
pattern: /register
defaults:
_controller: DUserBundle:Users:register
requirements:
_method: get
d_userbundle_register_check:
pattern: /register_check
defaults:
_controller: DUserBundle:Users:registerCheck
requirements:
_method: post
And the registerCheckAction of your controller:
// UsersController.php
public function registerCheckAction()
{
$request = $this->getRequest();
$user = new D\UserBundle\Entity\User();
$registrationForm = $this->createForm(new D\UserBundle\Form\UserType(), $user);
$registrationForm->bind($request);
if ($registrationForm->isValid()) {
$em = $this->getDoctrine()->getManager(); // use getEntityManager() instead of getManager() for old symfony2 versions
// Get the factory encoder service
$encoderFactory = $this->get('security_encoder.factory');
// Retrieve the algorithms, iterations and everything defined in your security.yml file
$encoder = $encoderFactory->getEncoder($user);
// Encrypt the password with the user's plaintext password AND the user salt
$encryptedPassword = $encoder->encodePassword($user->getPassword(), $user->getSalt());
// Then set the user's encrypted password :)
$user->setPassword($encryptedPassword);
$em->persist($user);
$em->flush();
// Thanks the user
$request->getSession()->getFlashBag()->add('success', 'Heyoh, welcome on board !');
return $this->redirect($this->generateUrl('d_userbundle_login'));
}
return $this->render('DUserBundle:User:registration.html.twig', array(
'registration_form' => $registrationForm->createView()
));
}
I am working on setting up a site with fosUserBundle. I need to write some unit tests, but I cannot seems to get authentication to work correctly for use with them. I have have tried a few methods with no luck.
approach1 - chain providers in security settings to make a standard login I can rely on. When I do this the only authentication that works is the in_memory provider meaning I can login in with the admin user but not any users created by fosUserBundle
security.yml
security:
encoders:
"FOS\UserBundle\Model\UserInterface": plaintext
"Symfony\Component\Security\Core\User\User": plaintext
providers:
chain_provider:
providers: [in_memory, fos_userbundle]
in_memory:
users:
admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
fos_userbundle:
id: fos_user.user_manager
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
login_path: /
csrf_provider: form.csrf_provider
default_target_path: /media
logout: true
anonymous: true
http_basic:
provider: in_memory
Method 2- I created a user in my setup of the unit test. But with this method I can never login with the user I just created, I am not requiring user confirmation.
public function setUp() {
$kernel = static::createKernel();
$this->repo = $kernel->boot();
$this->repo = $kernel->getContainer();
$userManager = $this->repo->get('fos_user.user_manager');
$email = "testing".(string) self::newRandomNumber()."#test.com";
$un = "test".(string) self::newRandomNumber();
$fn = "testin";
$ln = "laster";
$pwd = "passworD1";
$user = $userManager->createUser();
$user->setEmail($email);
$user->setUsername($un);
$user->setFirstName($fn);
$user->setLastName($ln);
$user->setPlainPassword($pwd);
$user->setBimStaff(True);
// Persist the user to the database
$userManager->updateUser($user);
$this->user = $user;
$this->client = static::createClient();
$crawler = $this->client->request('GET', '/');
$form = $crawler->selectButton('Login')->form();
// set some values
$un = 'Lucas'.self::newRandomNumber();
$form['_username'] = $un;
$form['_password'] = $pwd;
// submit the form
$crawler = $this->client->submit($form);
print_r($this->client->getResponse());
$this->client->followRedirects();
}
I have tried both login in at the form and http basic authentication but neither seem to work.
Any one have any advice?
Thanks,
Cory
Hmm, It seems that if you chain providers in the security you cannot use just one of the providers, at least that solved the issue for me.
// security.yml
providers:
chain_provider:
providers: [in_memory, fos_userbundle]
in_memory:
users:
admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
fos_userbundle:
id: fos_user.user_manager
firewalls:
main:
pattern: ^/
form_login:
provider: chain_provider
login_path: /
csrf_provider: form.csrf_provider
default_target_path: /media
logout: true
anonymous: true
http_basic:
provider: chain_provider
FOr some reason as soon as I changed both providers to chain_provider it started cooperating.
My unit test now looks like this
public function setUp() {
$kernel = static::createKernel();
$this->repo = $kernel->boot();
$this->repo = $kernel->getContainer();
$this->client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'admin',
'PHP_AUTH_PW' => 'admin',
));
$this->client->followRedirects();
}
If copying this I would suggest using stronger passwords for your users :)
I had the same problem but I wanted option 2 to work. Your example was almost there and helped me. I think the reason it wasn't working for you is that the user wasn't enabled. The user constructor in FOSUserBundle will create disabled users by default, so you must call $user->setEnable(true)
Here's my working example
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class MyControllerTest extends WebTestCase
{
private $client = null;
public function setUp()
{
$this->client = static::createClient();
}
private function logIn()
{
$session = $this->client->getContainer()->get('session');
$userManager = $this->client->getContainer()->get('fos_user.user_manager');
$user=$userManager->findUserByUsername('tester');
if (!$user) {
$user = $userManager->createUser();
$user->setEmail('test#example.com');
$user->setUsername('tester');
$user->setPlainPassword('foo');
$user->setEnabled(true);
$user->addRole('ROLE_SUPER_ADMIN');
$userManager->updateUser($user);
}
$firewall = 'main_firewall';
$token = new UsernamePasswordToken($user, null, $firewall, array('ROLE_SUPER_ADMIN'));
$session->set('_security_'.$firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
public function testCompleteScenario()
{
$this->logIn();
$crawler = $this->client->request('GET', '/foo/');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode(),
"Unexpected HTTP status code for GET /foo/".$this->client->getResponse()->getContent());
}
}