Symfony2 FOSUserBundle not using UserManager to provide cached User Entity - php

I am currently trying to build a (hopefully fast) Website in Symfony2. I am using the FOSUserBundle to manage Users easily. I wrote a custom CacheManager to store Entities in Redis and to fetch back valid Entities that are managed by Doctrine. My custom UserManager uses this Service to provide Users Data faster. That all works quiet well, but in fact the UserManager seems to be ignored. Every Request results in a DB query.
My config for the FOSUserBundle (sry. had to replace the Project Name by 'XXX'):
fos_user:
db_driver: orm
firewall_name: main
user_class: XXX\MainBundle\Entity\User
registration:
form:
type: XXX_user_registration
validation_groups: [XXXRegistration]
confirmation:
enabled: true
template: XXXMainBundle:E-Mail:registration.email.html.twig
from_email:
address: registrierung#XXX.XXX
sender_name: XXX Registrierung
profile:
form:
type: XXX_user_profile
validation_groups: [XXXProfile]
resetting:
email:
template: XXXMainBundle:E-Mail:resetting.email.html.twig
from_email:
address: reset#XXX.XXX
sender_name: XXX.XXX
service:
mailer: fos_user.mailer.twig_swift
user_manager: XXX_main_bundle.security.user_manager
My service config:
XXX_main_bundle.security.user_manager:
class: XXX\Bundle\MainBundle\Security\UserManager
arguments: [#XXX_main_bundle.cache_manager, #fos_user.entity_manager, #fos_user.util.canonicalizer.default, #security.password_encoder]
My security config:
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
ROLE_DEVELOPER: ROLE_SUPER_ADMIN
firewalls:
main:
pattern: .*
form_login:
provider: fos_userbundle
check_path: /login_check
login_path: /login
logout:
path: /logout
anonymous: true
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login$
security: false
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 can't show you my UserManager and my CacheManager, but if call and use them manually they are working fine! I also tried to write a custom UserProvider with dependency on the UserManager. But that always results in the Error:
ServiceNotFoundException in CheckExceptionOnInvalidReferenceBehaviorPass.php line 59:
The service "security.authentication.manager" has a dependency on a non-existent service "security.user.provider.concrete.fos_userbundle".
If I enable both UserProviders the Error disappears but the UserManager ist still ignored.
It seems to be used if i am using the built-in Commands of the FOSUserBundle but not when Symfony2 loads the User from the current Session.
Sry. for my bad English and maybe dumb Question but I am open for any criticism.
thx,
Justus Klein
EDIT 1: Seems like my UserManager isn`t completely ignored. It returns the User from the Cache but Symfony2 still triggers a DB Query to fetch the User.
EDIT 2: Found out that the refresh function of the UserProvider from the FOSUserBundle loads the User itself from the DB and not through the UserManager (wtf?). So overriding the UserProvider seems to make the clue. But I still get the following Error if I replace it:
ServiceNotFoundException in CheckExceptionOnInvalidReferenceBehaviorPass.php line 59:
The service "security.authentication.manager" has a dependency on a non-existent service "security.user.provider.concrete.fos_userbundle".
EDIT 3: I was able to override the UserProvider by giving it the same key as the one from the FOS Bundle:
security:
providers:
fos_userbundle:
id: xxx_main_bundle.security.user_provider
That canĀ“t be best Practice ^^ No everything works fine. I think thats the best & fastest way to store the User.

The best solution would be to enable the Second Level Cache feature available in Doctrine ORM 2.5, which performs exactly that: it caches entities in Redis (or any other cache you configure) to retrieve them much faster.
This will not help much when FOSUserBundle loads the user by username (because the ORM uses the id as identifier in its SLC, not the username), but this happens only when submitting the login form, or when reading a remember-me cookie if you use this feature. However, it will work for refresh where the user is loaded by primary key (and also in every other place of your project needing to load a user by primary key). Doctrine will also take care of updating the cache when the user is updated so you get valid data (be sure that you don't edit the user without using the ORM though).
And if you want to optimize the loading of user by username, you could write your own UserProvider which could keep a mapping between usernames and primary keys in Redis and then ask Doctrine for the User object by primary key (which would read it from the ORM cache then), while keeping a refreshing by primary as done in FOSUserBundle own provider (this is done for security reasons). This service can then either use the UserManager or Doctrine directly to load the user by username in case it is not yet in your username-to-id cache.
Overwriting the UserManager would not be needed anymore here, as it would not be used anymore in the critical path of the authentication.

Related

Symfony app that serves static Twig routes gated by SSO SAML 2.0?

I have a static website where its pages may only be accessed if a user has authenticated via SAML2 SSO. Specifically, these pages are written with Twig, and the content is stored in JSON files which are fed in as variables to the Twig templates.
I was wondering if there was a simple way to leverage a PHP framework like Symfony to do this. Ideally, there would also be no database layer. Once a user has authenticated some cookie should be set that just permits them to cruise around as needed.
My background is with Drupal so that's why I'm looking in the direction of Symfony.
I do realize this question is kinda broad, so if there is a more appropriate place to inquire about this then please vote to close and point me in the right direction.
I've completed this functionality, posting my solution in the event this is useful to someone else down the line...
For a Symfony 5 project, I used https://github.com/hslavich/OneloginSamlBundle. Fill in config/packages/hslavich_onelogin_saml.yaml per the package's README.md, and according to how your SP and IdP are configured. One pro tip, the baseurl configuration value should be set to the application domain with /saml concatenated on to it (e.g. http://myapp.com/saml), there is a bug which strips off everything between the last path value (acs in /saml/acs) and the domain.
Update config/packages/security.yaml to look something like:
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
saml_provider:
saml:
user_class: App\Security\User
default_roles:
- ROLE_USER
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
app:
saml:
provider: saml_provider
# Match SAML attribute 'uid' with username.
# Uses getNameId() method by default.
username_attribute: eduPersonTargetedID
# Use the attribute's friendlyName instead of the name
check_path: saml_acs
login_path: saml_login
logout:
path: saml_logout
main:
lazy: true
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# 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: ^/saml/login, roles: PUBLIC_ACCESS }
- { path: ^/saml/metadata, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
The net result is /saml/login and /saml/metadata are publicly available, while all other routes require the ROLE_USER role. Upon a successful authentication with the IdP, the user is redirected back and is granted a session, and can then access all routes within the site.

No token from TokenStorage on test environment

I want to include the logged in user's id to my logger.
So I have added a monolog.processor that adds the user id to the 'extra'-portion of the record, and added a custom format string that displays the id.
On my dev environment this works (mostly) as expected, but on the test environment it does not work at all, the TokenStorage always returns null on getToken().
There are no specific security configs for dev or test. The biggest differences between the configs is this part:
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
I have add this to my dev config but could not reproduce the symptoms. I can only reproduce by making symfony think it really is in test.
To be honest, I don't even know where to begin to debug this.
Any ideas what might be causing this behaviour?
Any ideas how I could debug this so I can get to an answer?
In order to have a token you should be inside on of the symfony firewalls.
If any of the firewalls aren't matched by the URI, symfony security is not triggered and you will not have a token.
If it is a public area allow anonymous users from root '/*' and use ACL for the rest of the URI (or actions). Anonymous users will have the role IS_AUTHENTICATED_ANONYMOUSLY
# app/config/security.yml
security:
firewalls:
main:
pattern: ^/
anonymous: ~
Documentation:
http://symfony.com/doc/current/security.html

Symfony2 API Key Authentication - No route found for

I followed the documentation, and this is how my code is organized:
src/Company/AuthBundle/Security/ApiKeyAuthenticator.php
src/Company/AuthBundle/Security/ApiKeyUserProvider.php
(they are the same as sample classes in doc)
# File: services.yml
apikey_authenticator:
class: Company\AuthBundle\Security\ApiKeyAuthenticator
public: false
# File: security.yml
firewalls:
api:
pattern: ^/api
stateless: false
simple_preauth:
authenticator: apikey_authenticator
provider: fos_userbundle
security: false
anonymous: true
And now, If I try to access e.g. http://symfony-project.dev/api?apikey=somekey, I got the following error:
No route found for "GET /api"
Any idea what's wrong?
The error message is fairly clear really - you haven't defined a route for a GET request for '/api'.
The security component you've configured takes care of authentication and granting access, but it does not control any logic specific to your application. You must create a controller and a route for requests like this.
You can see your currently defined routes with $ php app/console debug:router - if you've tried to define a route, that will help you debug it. If not, you must create one.

Symfony2 Beginner Security Context Issue

Up until recently I have had an area open to the public (/ and /whatever) and a secured area that required ROLE_USER (/portal and /portal/whatever). Now I want to create an admin area inside my secured area which would require ROLE_ADMIN. (/portal/admin and /portal/admin/whatever).
I have security.yml looks as follows:
firewalls:
login_firewall:
pattern: ^/portal/login$
anonymous: ~
secured_area:
pattern: ^/portal
form_login:
login_path: portal_login
check_path: login_check
default_target_path: portal_dashboard
logout:
path: portal_logout
target: portal_dashboard
http_basic:
realm: "MyFreelancer Client Portal"
access_control:
- { path: ^/portal/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/portal/, roles: ROLE_USER }
- { path: ^/portal/admin, roles: ROLE_ADMIN }
I have just added the third line. My problem is that a user with ROLE_USER can still access ^/portal/admin. Could someone please explain to me why this does not work?
An alternative is to have the admin area under /admin and /admin/whatever, but this requires a completely different firewall context and I am not quite sure how to do that. I tried, but it kept taking me back to /portal/ and when I browse to /admin/ it asks to log in again and then takes me back to /portal/ again (endless loop).
If, as an ancilliary answer, you could explain to me the pro's and con's of single firewall context for user area and admin area vs seperate firewall contexts and how this is implemented (if necessary)?
Thank you in advance.
UPDATE: I have found that simply swopping the two bottom lines fixes the problem, as it runs through them sequetially and thus /portal/admin/ got run against the second line, which only requires ROLE_USER. If someone could still give me some insight on whether it would be better to run the admin area on a seperate firewall context or not and why, and if so, how?
When setting up your access_control, make sure that your routes are in such an order that a route will not match one of the routes above. For instance, place ^/admin/login/ above ^/admin/, otherwise the first match (which would be ^/admin/) will be triggered.

There is no user provider for user "Symfony\Component\Security\Core\User\User"

After struggling all day with a simple taks for Symfony 2 with no luck, I decided to ask you guys for a solution.
Here is the problem: I would like to make a http_basic authentication using doctrine, so users would be prompted to enter username/password which are kept in a database.
So, I followed these steps:
1) Created a new entity called User with the interactive console generator.
This is how it looks like:
http://pastebin.com/3RzrwFzL
2) As stated in the documentation I have implemented UserInterface and added the 4 missing methods. Now the entity looks like this:
http://pastebin.com/Epw3YrwR
3) I have modified the security.yml as little as possible to make it work, and it looks like this:
http://pastebin.com/tp6Gd7t7
I cleared the cache and tried to access app_dev.php/admin and of course I get the same error all day:
There is no user provider for user "Symfony\Component\Security\Core\User\User".
500 Internal Server Error - RuntimeException
Can anyone tell me where is the problem?
I have tried this thousand different ways and weirdly it worked for a moment, but when I tried to add sha1 as encoder algorithm instead of plaintext, and cleared the cache, I came back to the same error.. since then I get nothing else but it. It is like if there is a hidden cache that is being erased whenever symfony decides :D
I think the error might also be in the 4 methods of the entity, but I cannot fix them since there is no documentation about what should they do.
I am currently using RC4.
Thanks in advance, hope someone will help.
I had this problem once.
It was because I was logged with a user from the previous provider (in_memory). Had to restore the in_memory part, logout and then put the new provider.
My guess:
The info of the user was in the session and it couldn't acces it since we took it off the security.yml
Had the same problem. It seems that this works. I will only use it in the development process later on i will find a solution.!
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
providers: [in_memory, user_db]
in_memory:
users:
cheese: { password: olo, roles: ROLE_ADMIN }
user_db:
entity: { class: Abc\BaseBundle\Entity\User, property: username }
encoders:
Symfony\Component\Security\Core\User\User: plaintext
Abc\BaseBundle\Entity\User: plaintext
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
panel:
pattern: ^/(panel|login_check)
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
default_target_path: /panel/
logout:
path: /logout
target: /
for me problem occured in dev environment. It happened because I has active session from other project.
Cleaning browser cookies helped.
You could save yourself the headache and try the friends of symfony UserBundle.
At the very least looking at that bundle will help you learn and fix your own code. It has plenty of well written code/examples.

Categories