Reputation: 364
Im having some trouble getting the Neo4j OGM/Symfony bundle to work with the Symfony Guard. I have added users to the database successfully. unfortunately it doesn't want to sign in and I get the following error:
Symfony\Component\Security\Core\Exception\AuthenticationServiceException: Class "App\Entity\Generic\User" is not a valid entity or mapped super class. in /home/vagrant/Code/support4neo/vendor/symfony/security/Core/Authentication/Provider/DaoAuthenticationProvider.php:85 Stack trace:
#0 /home/vagrant/Code/support4neo/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php(142): session_start()
#1 /home/vagrant/Code/support4neo/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php(299): Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start()
#2 /home/vagrant/Code/support4neo/vendor/symfony/http-foundation/Session/Session.php(249): Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->getBag('attributes')
#3 /home/vagrant/Code/support4neo/vendor/symfony/http-foundation/Session/Session.php(271): Symfony\Component\HttpFoundation\Session\Session->getBag('attributes')
#4 /home/vagrant/Code/support4neo/vendor/symfony/http-foundation/Session/Session.php(73): Symfony\Component\HttpFoundation\Session\Session->getAttributeBag()
#5 /home/vagrant/Code/support4neo/vendor/symfony/security/Http/Firewall/ContextListener.php(88): Symfony\Component\HttpFoundation\Session\Session->get('_security_main')
#6 /home/vagrant/Code/support4neo/vendor/symfony/security-bundle/Debug/WrappedListener.php(46): Symfony\Component\Security\Http\Firewall\ContextListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#7 /home/vagrant/Code/support4neo/vendor/symfony/security-bundle/Debug/TraceableFirewallListener.php(35): Symfony\Bundle\SecurityBundle\Debug\WrappedListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#8 /home/vagrant/Code/support4neo/vendor/symfony/security/Http/Firewall.php(56): Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener->handleRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), Object(Symfony\Component\DependencyInjection\Argument\RewindableGenerator))
#9 /home/vagrant/Code/support4neo/vendor/symfony/security-bundle/EventListener/FirewallListener.php(48): Symfony\Component\Security\Http\Firewall->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#10 [internal function]: Symfony\Bundle\SecurityBundle\EventListener\FirewallListener->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher))
#11 /home/vagrant/Code/support4neo/vendor/symfony/event-dispatcher/Debug/WrappedListener.php(104): call_user_func(Array, Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher))
#12 /home/vagrant/Code/support4neo/vendor/symfony/event-dispatcher/EventDispatcher.php(212): Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\EventDispatcher))
#13 /home/vagrant/Code/support4neo/vendor/symfony/event-dispatcher/EventDispatcher.php(44): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#14 /home/vagrant/Code/support4neo/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php(139): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#15 /home/vagrant/Code/support4neo/vendor/symfony/http-kernel/HttpKernel.php(125): Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#16 /home/vagrant/Code/support4neo/vendor/symfony/http-kernel/HttpKernel.php(66): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#17 /home/vagrant/Code/support4neo/vendor/symfony/http-kernel/Kernel.php(190): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#18 /home/vagrant/Code/support4neo/public/index.php(37): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#19 {main}
What could i be doing wrong?
Thanks in advance!
User Class:
<?php
/**
* Created by PhpStorm.
* User: kevin.oosterhout
* Date: 24/04/2018
* Time: 20:58
*/
namespace App\Entity\Generic;
use GraphAware\Neo4j\Client\Client;
use GraphAware\Neo4j\OGM\Annotations as OGM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
*
* @OGM\Node(label="User")
*/
class User implements UserInterface
{
/**
* @OGM\GraphId()
*/
protected $id;
/**
* @OGM\Property(type="string")
*/
protected $firstname;
/**
* @OGM\Property(type="string")
*/
protected $lastname;
/**
* @OGM\Property(type="string")
*/
protected $email;
/**
* @OGM\Property(type="string")
*/
protected $password;
/**
* @return mixed
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* @return mixed
*/
public function getLastname()
{
return $this->lastname;
}
/**
* @return mixed
*/
public function getEmail()
{
return $this->email;
}
/**
* @return mixed
*/
public function getPassword()
{
return $this->password;
}
/**
* @param mixed $firstname
*/
public function setFirstname($firstname): void
{
$this->firstname = $firstname;
}
/**
* @param mixed $lastname
*/
public function setLastname($lastname): void
{
$this->lastname = $lastname;
}
/**
* @param mixed $email
*/
public function setEmail($email): void
{
$this->email = $email;
}
/**
* @param mixed $password
*/
public function setPassword($password): void
{
$this->password = $password;
}
/**
* Returns the roles granted to the user.
*
* <code>
* public function getRoles()
* {
* return array('ROLE_USER');
* }
* </code>
*
* Alternatively, the roles might be stored on a ``roles`` property,
* and populated in any number of different ways when the user object
* is created.
*
* @return (Role|string)[] The user roles
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
* Returns the salt that was originally used to encode the password.
*
* This can return null if the password was not encoded using a salt.
*
* @return string|null The salt
*/
public function getSalt()
{
return null;
}
/**
* Returns the username used to authenticate the user.
*
* @return string The username
*/
public function getUsername()
{
return $this->email;
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
// TODO: Implement eraseCredentials() method.
}
}
Authenticator class:
<?php
/**
* Created by PhpStorm.
* User: kevin.oosterhout
* Date: 03/05/2018
* Time: 17:37
*/
namespace App\Security;
use App\Entity\Generic\User;
use App\Forms\LoginForm;
use GraphAware\Neo4j\OGM\EntityManager;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
public function getCredentials(Request $request)
{
return array(
'username' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if($credentials['username'] === null){
return null;
}
$user = $userProvider->loadUserByUsername($credentials['username']);
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return true;
}
protected function getLoginUrl()
{
// TODO: Implement getLoginUrl() method.
}
public function supports(Request $request)
{
return false;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// TODO: Implement onAuthenticationSuccess() method.
}
}
Security.Yaml
security:
encoders:
App\Entity\Generic\User:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
UserProvider:
id: 'App\Security\UserProvider'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
form_login:
login_path: login
check_path: login
guard:
authenticators:
- 'App\Security\LoginFormAuthenticator'
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
User Provider:
<?php
/**
* Created by PhpStorm.
* User: kevin.oosterhout
* Date: 04/05/2018
* Time: 07:38
*/
namespace App\Security;
use App\Entity\Generic\User;
use GraphAware\Neo4j\OGM\EntityManager;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class UserProvider implements UserProviderInterface
{
protected $userRepository;
public function __construct(EntityManager $entityManager)
{
$this->userRepository = $entityManager->getRepository(User::class);
}
/**
* @param string $username
* @return null|User
*/
public function loadUserByUsername($username)
{
return $this->userRepository->findOneBy(['email' => $username]);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(
sprintf('Instance of %s is not support', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return User::class === $class;
}
}
Edit: Currently it doesn't sign me in, and it doesn't give me an error. I have followed the steps explained by @dbrumann and @Christophe Willemsen
Upvotes: 0
Views: 397
Reputation: 20185
The answer from @dbrumman is correct, you will need a custom UserProvider.
I have a demo example on Github, check here : https://github.com/ikwattro/neo4j-ogm-symfony-security
There is also a PullRequest that shows how to add roles based on the Neo4j content :
https://github.com/ikwattro/neo4j-ogm-symfony-security/pull/1/files
Upvotes: 1
Reputation: 17166
I think the problem might be with your security.yaml
:
providers:
UserProvider:
entity:
class: 'App\Entity\Generic\User'
property: email
This provider tries to use Doctrine ORM to load users from. Since your User-entity is not a Doctrine-Entity, i.e. is missing the doctrine annotations, it fails. Even though the bundle registers entity managers, they don't seem to be used by the user provider.
You could create a custom user provider that is inspired by the EntityUserProvider.
I am not sure if the ogm entity managers adhere to doctrine's interfaces, but if they do, you might be able to configure the default doctrine user provider to use the neo4j entity manager by adjusting the service configuration, but it's a real pain because you have to overwrite the UserProvider-service and then inject a new ManagerRegistry with your entity manager in it. So, writing a custom UserProvider really seems to be the preferred way.
Upvotes: 2