How to use Neo4j with FOSUserBundle? - symfony

I am trying to adjust FOSUserBundle to work with my Neo4j database and I cant seem to get it working. After a long time of trying to implement my own user system without any luck (Setting up NEO4j in Symfony2), I started trying to use the FOSUserBundle.
I have used the following articles and repositories:
https://github.com/ikwattro/Neo4jUserBundle
I have taken this and copied all of the files into my UserBundle. I have changed the namespaces.
I have taken the graph manager from here: https://github.com/ikwattro/KwattroNeo4jOGMBundle
For the rest, I have followed the FOSUserBundle documentation.
Now, when I go to the registration form, all fields appear and I can fill in my preferred credentials. This works. After I click on submit I get redirected to the success page, on which an alert overlay is displayed:
An error occurred while loading the web debug toolbar (500: Internal
Server Error). Do you want to open the profiler?
If I then enter the profiler, I can see that I have successfully been authorized and logged in as the user that I just created. The data is also successfully saved in my neo4j database.
The problem now is that if I go to any other page of my Symfony project, I am logged in as Anonymous again. And If I go to the login page, the form is displayed correctly, but it always returns: Invalid credentials.
I am guessing, that there is something wrong with my sessions or my security?
This is my security.yml:
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
Neo4jUserBundle\Entity\User: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: neo4j.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: security.csrf.token_manager
logout: true
anonymous: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
I dont know if this information can help or is relevant, but in the profiler under the Request right after registration (when the user is still authenticated correctly) this is the session information:
Session Metadata
Key Value
Created Tue, 21 Jul 15 17:27:34 +0200
Last used Tue, 21 Jul 15 17:27:34 +0200
Lifetime 0
Session Attributes
Key Value
_csrf/authenticate A_H4Ul1XHFYoxQdOirdmbBQRRCJ01Xh8EkGeC6Y7xw0
_csrf/registration OAXAXhfhcN6z0WekMN0fk8zg4ikk5uCCZBlvhy8DyVY
_security.last_username test
_security_main C:74:"Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken":444:{a:3:{i:0;N;i:1;s:4:"main";i:2;s:404:"a:4:{i:0;C:32:"neo4jProxyUserBundle_Entity_User":192:{a:9:{i:0;s:60:"$2y$13$e49oj61cdjk88kk040wg8exlwqVzbdQB5IVNG18Wqcbe.EW8KXi72";i:1;s:31:"e49oj61cdjk88kk040wg8kcc4cg40c4";i:2;s:4:"test";i:3;s:4:"test";i:4;b:0;i:5;b:0;i:6;b:0;i:7;b:1;i:8;i:66;}}i:1;b:1;i:2;a:1:{i:0;O:41:"Symfony\Component\Security\Core\Role\Role":1:{s:47:"Symfony\Component\Security\Core\Role\Rolerole";s:9:"ROLE_USER";}}i:3;a:0:{}}";}}
Flashes
Key Value
success [0 => registration.flash.user_created, 1 => registration.flash.user_created, 2 => registration.flash.user_created, 3 => registration.flash.user_created, 4 => registration.flash.user_created, 5 => registration.flash.user_created, 6 => registration.flash.user_created, 7 => registration.flash.user_created, 8 => registration.flash.user_created]
Any help or hints would be appreciated.
UPDATE [21.07.2015]
I have now created a repository: https://github.com/JoranBeaufort/Neo4jUserBundle (I hope this works, the first time I have used GitHub)
I guess that there is something off with the session handling?
Another thing to point out is, that the dependency injection does not seem to do anything. I must be missing a few vital things.
It would be great to be able to offer a Neo4jUserBundle that works out of the box with FOSUserBundle and can be configured in the config file of the Symfony project. Great and vital would also be the authentication with the database (use username and password to connect to neo4j).
UPDATE [22.07.2015]
I have changed the bundlename and I think I have finally gotten the DependencyInjection to work. I'm not quite sure but I think I had a problem with how I named my classes.
I have also tried what you suggested with findUserById. I have written a controller which takes the route myapp.com/neo4juser/debug/finduserbyid/{id} and then uses the findUserById method to return the user. This is working. I have a user in my Neo4j-Database with an ID = 68 and an email=test#test.test. If I now enter myapp.com/neo4juser/debug/finduserbyid/68 the page is loaded displaying the right email of that user.
TWIG can be found here: https://github.com/JoranBeaufort/Neo4jUserBundle/blob/master/Resources/views/Debug/finduserbyid.html.twig
And the CONTROLLER here: https://github.com/JoranBeaufort/Neo4jUserBundle/blob/master/Controller/DebugController.php
The methods in the UserManager seem to be returning the desired objects.
Does this help in figuring out why the login does not work in any way? Does the serialization have anything to do with the error or the encryption type? Or could it be something to do with the CSRF? Any further hints?
UPDATE [23.07.2015]
When using in_memory as the provider and setting up an in_memory user, the login works. So now I have narrowed down the problem to the provider.
I am getting closer! Now the error in the dev.log file reads:
[2015-07-23 17:11:54] security.INFO: Authentication request failed. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\BadCredentialsException(code: 0): Bad credentials. at bla/vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php:73, Symfony\\Component\\Security\\Core\\Exception\\UsernameNotFoundException(code: 0): Username \"test\" does not exist. at bla/src/Neo4jUserBundle/Security/UserProvider.php:42)"} []
Important is the part `Username "test" does not exist.I am guessing this means that something is not working in the user provider. Can anyone spot what the problem might be? The Provider which I am calling can be found here: https://github.com/JoranBeaufort/Neo4jUserBundle/tree/master/Security

Ok. I opened a PR for some tweaks, but I couldn't get the stuff working.
The tweaks I've done are adding the possibility to define a user and password for via the neo4j_user config, and load the services.yml file in the DI extension.
When I register a user, it is well created in the database. However for fetching a user, after some debug, I can see that the underlying client (neo4jphp combined with neo4j-php-ogm) are using the legacy indexes and it is throwing some errors at this stage.
I can not help further except to tell you to try to not use an ogm in the beginning and try with raw cypher queries.
I'm afraid trying to update both libraries used can be difficult in a first instance.

I would say the problem, as of 27/12/2015, is that UserManager, at line 73, does not return the found user (is a void function). I have not tried it yet and can´t for a few days, and maybe it´s an answer not sought anymore, but I´m pretty sure that´s the problem.
IDEs won´t find it a problems due to the #return tag:
* Finds a user by username
*
* #param string $username
*
* #return UserInterface
*/
public function findUserByUsername($username)
{
$this->findUserBy(array('usernameCanonical' => $this->canonicalizeUsername($username)));
}

Related

Symfony Class does not exist from Autoloader (User Providers)

The error is just a basic one. A class does not exist, and the error is actually very right. The class indeed does not exist. We're now working on Multi-Tenancy for this project, and there is where it currently gives up. This is the error;
Class "App\Entity\User" does not exist
It is a ReflectionException. For this we did a few things.
We have the following folder structure for our Symfony API-Platform instance;
- Controller
- Messenger
- Entity
--- Main
--- Tenant
- Repository
- ....
And I bet that here is where the problem lies. The User Entity is not App\Entity\User. The Class is in the Main folder, so is named App\Entity\Main\User. However, when trying to use the app, it will always return in an HTTP 500, with the error message given above. It tries to find the class, which isn't there.
So, this is a bit different. We needed to change the provider for the authentication as stated in the Symfony Documentation. We did this by modifying the security.yaml as stated in the documentation.
providers:
database_users:
entity:
class: App\Entity\Main\User
property: email
However, it still tries to use the other Entity. We've search the project, and there is absolutely no reference. We've emptied the cache and rebuild the docker-container, all to no avail. It still is looking for the class, which is not there, but in another folder inside there.
Are we missing something? The error Stack Trace is that it appears in the autoload_runtime.php, but even when dumping and regenerating this, it has no avail.
Firewall from security.yaml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
# this firewall applies to all URLs
pattern: ^/
lazy: true
# The user provider to use.
provider: database_users

Multiple providers in Symfony 2.7

I am using multiple users providers in my project.
My security.yml looks like this :
security:
...
firewalls:
usertype1:
pattern: ^/root/usertype1_area
provider: type1_provider
usertype2
pattern: ^/root
provider: type2_provider
...
Everything is working fine and I can't login with wrong user types at the right pattern, except that I noticed that if I throw an exception in one of my providers, say type1_provider , and try to log in with the /root/login path (which should use only type2_provider), Symfony is going through type1_provider as well as type2_provider, and I get an exception.
The same is also true with /root/usertype1_area/login when I throw at type2_provider.
This is a problem to me because I want to be able to access type2 login when the type1_provider is shut down.
Any guesses ? Is this normal behavior ?
EDIT : As pointed out by Alexander Keil, it was not clear in my question what I was trying to do
One of my providers relies on a 3d party service, and I want it to throw when this service is down, but I still want to be able to access the other login, which is not supposed to rely on the provider that is throwing. Is there a way I can achieve this ?
You can use the method "supportsClass" in your provider. Return false if the current user class does not support the loaded provider. See Symfony\Component\Security\Core\User\UserProviderInterface

Symfony2 User permission managment

I come to you because I have a little concern about the management of users and their Permissions with Symfony2.
Let me explain:
I set up the FOSUserBundle:
What I'd like to do now is a rights management. I have an entity 'Post'.
I have users with roles specified below.
ROLE_GUEST - VIEW,RATE
ROLE_USER - VIEW,CREATE,RATE,EDIT_OWN
ROLE_EDITOR - VIEW,CREATE,RATE,EDIT,DELETE
I want to set permission to each roles for performing certain actions.
Thank you :)
If i understand your necessity correctly you want to have a security layer based on those roles. You can do this in may ways:
The symfony default way -
you can configure the security layer of symfony like in the example below
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/post/view, roles: VIEW }
- { path: ^/post/rate, roles: RATE }
# etc
This will take care of route access control. More info on http://symfony.com/doc/current/cookbook/security/access_control.html
For more complex roles like EDIT_OWN, you can take the direct approach
if (!$post->isAuthor($this->getUser())) {
$this->denyAccessUnlessGranted('EDIT', $post);
// or without the shortcut:
//
// use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...
//
// if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
// throw $this->createAccessDeniedException();
// }
} else {
$this->denyAccessUnlessGranted('EDIT_OWN', $post);
}
For all this and more you can check the symfony site http://symfony.com/doc/current/best_practices/security.html
For even more advanced roles or ACL requirement also take a look here https://symfony.com/doc/current/components/security/authorization.html and at the authorization voters https://symfony.com/doc/current/components/security/authorization.html#voters
In the 4 links I provided in this post you should find all you need to implement RBAC as well as ACL. You can also find information about some annotations you may want to use. Also there are some extensions to the symfony security layer that may come in handy depending on the symfony version you are working on like JMS\SecurityExtraBundle.
Hope this help,
Alexandru Cosoi

Symfony2: Unable to login successfully with two firewalls using two user providers

I am setting up a website which I want to use separate firewalls and authentication systems for frontend and backend. So my security.yml is configured as below. I am using in_memory user provider in early development phase.
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
backend_in_memory:
memory:
users:
admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
frontend_in_memory:
memory:
users:
user: { password: 12345, roles: [ 'ROLE_USER' ] }
firewalls:
# (Configuration for backend omitted)
frontend_login_page:
pattern: ^/login$
security: false
frontend:
pattern: ^/
provider: frontend_in_memory
anonymous: ~
form_login:
check_path: login_check_route # http://example.com/login_check
login_path: login_route # http://example.com/login
access_control:
# (Configuration for backend omitted)
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
I have omitted the backend part because it doesn't matter. The problem is still there when the omitted part is commented out.
The problem is that frontend authentication won't work with the above configuration. Here's what I did:
Visit http://example.com/login
Enter the credential (user:12345), click login
http://example.com/login_check authenticates the user
The authentication service redirects user back to http://example.com/. No error is thrown. In fact, when I turned on the debug_redirects option, it clearly shows that "user" is authenticated on the redirect page.
Expected behavior: The security token should show that I'm logged in as "user" after following the redirect and go back to the index page.
Actual behavior: The security token still shows "anonymous" login after following the redirect and go back to the index page.
But with nearly identical settings (paths and route names aren't the same), the backend part works correctly.
After some investigation I found that the cause is the way user providers is currently written. Notice that frontend_in_memory section is placed below backend_in_memory that is used for backend authentication. So I explicitly specify the frontend_in_memory provider for the frontend firewall. And it kind of works - I must login with "user:12345" in the frontend login page. Logging in with "admin" won't work. So it must be using the correct user provider. But I suspect that the framework cannot update the security token correctly because it is still searching the "user" account from the first user provider which is backend_in_memory. In fact I can make the above config work with either one of the following changes:
add "user" login to the backend_in_memory provider's user list (password needn't be the same), or
swap frontend_in_memory with backend_in_memory so that frontend_in_memory becomes the first user provider.
Of course they are not the correct way of solving this problem. Adding "user" account to the backend makes no sense at all; swapping the order of two user providers fixes the frontend but breaks the backend.
I would like to know what's wrong and how to fix this. Thank you!
I was stuck when I posted the question, but after a sleep the answer is found ;)
Turns out I came across an issue reported long ago:
https://github.com/symfony/symfony/issues/4498
In short,
The problem isn't about the configuration.
And it isn't about authentication neither.
It actually relates to how an authenticated user is refreshed after redirection. That's why the app is correctly authenticated as "user" on the redirect page, but not after that.
Here is the code when the framework refreshes the user (can be found in \Symfony\Component\Security\Http\Firewall\ContextListener):
foreach ($this->userProviders as $provider) {
try {
$refreshedUser = $provider->refreshUser($user);
$token->setUser($refreshedUser);
if (null !== $this->logger) {
$this->logger->debug(sprintf('Username "%s" was reloaded from user provider.', $refreshedUser->getUsername()));
}
return $token;
} catch (UnsupportedUserException $unsupported) {
// let's try the next user provider // *1
} catch (UsernameNotFoundException $notFound) {
if (null !== $this->logger) {
$this->logger->warning(sprintf('Username "%s" could not be found.', $notFound->getUsername()));
}
return; // *2
}
}
The above code shows how the framework loops through the user providers to find the particular user (refreshUser()). *1 and *2 are added by me. If a user provider throws an UnsupportedUserException, this means that the provider isn't responsible for the supplied UserInterface. The listener will then iterate to the next user provider (*1).
However, if what the user provider thrown is a UsernameNotFoundException, this means that the provider is responsible for the supplied UserInterface, but the corresponding account could not be found. The loop will then stop immediately. (*2)
In my question, the same user provider, \Symfony\Component\Security\Core\User\InMemoryUserProvider, is used in both frontend and backend environment. And InMemoryUserProvider is responsible for the UserInterface implemented by Symfony\Component\Security\Core\User\User.
In the frontend, "user" is in fact authenticated successfully. However, in the user refresh attempt,
The order of the user providers will be like this: backend in-memory provider, frontend in-memory provider.
So, backend in-memory provider will run first.
The backend in-memory provider believes it is responsible for the supplied UserInterface because it is also an instance of Symfony\Component\Security\Core\User\User.
But it fails to locate the "user" account (it only has the "admin" account).
It then throws a UsernameNotFoundException.
The refreshUser() routine won't bother to try with next provider because UsernameNotFoundException means that the responsible user provider is already found. Instead it stops trying and removes the authentication token.
This explains why the configuration won't work. Despite using a different user provider, the only way to work around this is to copy the framework's InMemoryUserProvider and User classes and change the refreshUser() method to check against the copied User class, so that the frontend and backend user provider uses different user classes and won't clash.

Remember Me functionality not working in Symfony2

I have implemented remember me functionality in Symfony2. When I log in with remember me box checked, cookie named "REMEMBERME" gets created. That cookie is also available if I close browser and open it after many hours. But when I load home page of my application, the cookie gets automatically deleted and I see no user logged in. Can anyone explain me the reason for cookie deletion?
remember_me:
key: qwerty
lifetime: 604800
path: /
domain: ~
This is my security.yml file section
EDIT: I have still not found the solution to this question...
EDIT2: Now got new problem. The REMEMBERME cookie does not get set at all. How to solve this??
SOLVED: see answer below
Although this question has already been answered, I would like to contribute a possible solution, if only for posterity and Google search referrals for this problem :)
"The issue is simple: a remembered used does not have the IS_AUTHENTICATED_FULLY role but only IS_AUTHENTICATED_REMEMBERED to make a difference between a remembered user and a user who logged in"
Source: http://www.mail-archive.com/symfony-users#googlegroups.com/msg34021.html
What this means is that in your security configuration, you must make sure that for every ACL entry the IS_AUTHENTICATED_REMEMBERED role is configured in addition to the IS_AUTHENTICATED_FULLY role.
For example:
#app/config/security.yml
security:
...
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: [IS_AUTHENTICATED_FULLY,IS_AUTHENTICATED_REMEMBERED] }
John.
I've the same issue as you do (or did), what I've found is that when I am (Symfony2 actually =) ) setting REMEMBERME cookie on line 101 at /vendor/symfony/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeService.php file $user->getPassword() returns NULL, so cookie gets hash calculated with NULL password value.
What happening next, is when you returning to your site being fully confident that you will be automatically authenticated, Symfony begins to check your cookie at the same file as above but on line 58 it founds that cookie hash is not the same as it expects and throws an exception('The cookie\'s hash is invalid.') internally catches it and proceeds somewhere.
So that is the case why in my case cookie doesn't work.
I haven't found a solution yet, but I will dig for it and may be I'm lucky.
Hope your issue is the same and solution will help us both.
The Solution:
When implementing eraseCredentials() which claims to be used to erase user sensitive data from UserInterface do not perform $this->password = null. I've made this mistake because I haven't being understanding its purpose. You can take a glance at Symfony 2 Logout (UserInterface::eraseCredentials) for a little bit of explanation. So it serializes token object and we are in trouble.
I had this problem and the issue was that I did not use single quotation marks in the property key of remember_me section (security.yml).
Change this:
remember_me:
key: qwerty
lifetime: 604800
path: /
domain: ~
to this:
remember_me:
key: 'qwerty'
lifetime: 604800
path: /
domain: ~
You can check it in the symfony documentation:http://symfony.com/doc/2.7/cookbook/security/remember_me.html
try to increase your session lifetime:
(config.yml)
framework:
session:
default_locale: %locale%
auto_start: true
lifetime: 604800
In my case it was a wrong implementation of the supportsClass method of my userProvider, which in turn caused an exception in the TokenBasedRememberMeService class on line 43 (thrown by getUserProvider, and catched elsewhere, thus failing silently).
Digging in the path shown by Dmitry made me solve the issue.
In my case I have implemented a custom Login Handler which was returning a RedirectResponse as per documentation. It turns out that that makes Symfony to bypass the standard login routine, and causing the REMEMBERME cookie not been created/stored.
I had to remove the Login Handler, implement a custom Login Listener with all needed logic.
You can see how to implement a Login Listener here
You should also make sure your "remember_me" input in the login form does not have the value attribute:
This is correct:
<input type="checkbox" id="remember_me" name="_remember_me" />
But this will not work:
<input type="checkbox" id="remember_me" name="_remember_me" value="" />
If you are using form_login, check also that remember_me is enabled in security.yml:
firewalls:
main:
form_login:
# ...
remember_me: true
I had the same issue. After investigation I found that :
/vendor/symfony/doctrine-bridge/Security/User/EntityUserProvider.php::loadUserByUsername() requires to either have set the property field on your entity user provider or that your repository implements Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface and has a method loadUserByUsername().
I just added the property field like so :
providers:
user_provider:
entity:
class: App\Entity\User
property: email
I'm using Symfony 4 and I had a similar problem, the REMEMBERME cookies was not set.
My issue was that I had a value="" set to the input type checkbox field.
So I changed from this
<input type="checkbox" value="" id="remember_me" name="_remember_me">
to this
<input type="checkbox" id="remember_me" name="_remember_me">
In my case, the authenticators was overided with the method supportsRememberMe:
public function supportsRememberMe()
{
return true; // change it to true
}

Resources