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.
Related
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.
in my Symfony 4 project I am using default FOSUSerBundle configuration which looks llike this:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/login #problem here
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
success_handler: fos_authentication_handler
failure_handler: fos_authentication_handler
logout: true
anonymous: true
So when I use pattern: ^/ it works fine, but when I do it like this: pattern: ^/login it prevents me from logging in saying that variable _SESSION is undefined. In my controller I am using the following code which works fine when firewall pattern is '^/':
$this->get("session")->save();
I dont want it to block the base route of my site ('mysite.com/') and I want to implement custom logic there. Any ideas how to fix it would be welcome. Thank you.
issue solved
finally I undertand that firewall pattern means pattern which covers all routing naming for the entire site. If it starts with '^/login', all other routes starting not with login wont be covered by firewall and thus error appears. Moreover, I got my index path under restricted access in security.yml so it threw the following error.
After a few hours I got a working LDAP-Login, based on the 'new' symfony2 ldap component from November 11, 2015 (see here). Also I followed the docs about the log out.
But everytime I request on that logout function, nothing seems to happen. The user is still logged in.
This is my code:
app/config/security.yml
security:
firewalls:
main:
pattern: ^/
stateless: true
http_basic_ldap:
service: service.key
dn_string: "{username}#example.tld"
logout:
path: /logout
Bundle/Resources/config/routing.yml
vendor_bundlename_logout:
path: /logout
Depending on this answere my question is:
Do I have to manage the logout by myself with incorret login credentials? Or did I missconfigured the logout aspect in the security.yml?
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.
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.