Thomas Sebert
Thomas Sebert

Reputation: 31

Symfony 4 - Security - Share context from multiple firewalls to one another

I think I reached some limit with the Symfony security component. Here's my problem: I have two firewall to manage two users type (with two distinct entities) authentication and access to two different part of the website. I have a third part to manage files, uploads, ... that have to be private and both users types need to access it.

So I made multiple providers in security.yml:

providers:
    # used to reload user from session & other features (e.g. switch_user)
    core_user_provider:
        entity:
            class: Akyos\CoreBundle\Entity\User
            property: email

    platform_user_provider:
        entity:
            class: App\Entity\Platform\UserPlatform
            property: email

    file_manager_provider:
        chain:
            providers: [core_user_provider, platform_user_provider]

And multiple firewalls

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false

    core:
        pattern: ^/(app|admin)/
        context: shared
        provider: core_user_provider
        anonymous: lazy
        guard:
            authenticators:
                - Akyos\CoreBundle\Security\CoreBundleAuthenticator
        logout:
            path: app_logout
            target: 'home'
        remember_me:
            secret:   '%kernel.secret%'
            lifetime: 604800 # 1 week in seconds
            path:     /

    file_manager:
        pattern: ^/(file-manager)
        context: shared
        provider: file_manager_provider
        anonymous: lazy
        guard:
            authenticators:
                - App\Security\FileManagerAuthenticator
        logout:
            path: file_manager_logout
            target: 'home'
        remember_me:
            secret:   '%kernel.secret%'
            lifetime: 604800 # 1 week in seconds
            path:     /

    platform:
        pattern: ^/(platorm_login|plateforme)
        context: shared
        provider: platform_user_provider
        anonymous: lazy
        guard:
            authenticators:
                - App\Security\PlatformAuthenticator
        logout:
            path: platform_logout
            target: 'home'
        remember_me:
            secret:   '%kernel.secret%'
            lifetime: 604800 # 1 week in seconds
            path:     /

    main:
        anonymous: lazy

So a Platform user can't access Core, and a Core user can't access Platform. But both users needs to access File-manager, without re-log in. I can't place /file-manager urls under Core or Platform firewall because the other wouldn't grant access to it. So I need a third firewall to manage File-manager access. It use a chain provider that groups both Core and Platform users. It doesn't work either because if a Core user authenticate through the Core firewall it is not authenticated for the File-manager one, so it redirect to File-manager login page.. if the user logs in File-manager part it can access it, but when it turn back to Core part it has to re-connect again.

I tried several things but the closest solution is to use the context option on firewalls, so when a user is logged in through Core part it can access File-manager part without re-log because both firewalls shared the same context. That's what I want. But I also need it for the Platform firewall! So I also add same context option to it, and it works, both users types can access File-manager without log in again :D But as the three firewalls share the same context, Core users can access to Platform and vice-versa, and that breaks all the separation logic.. :'(

I need a way to tell security component "File-manager firewall has same context as Core firewall, and File-manager firewall has same context as Platform firewall, but Core and Platform firewalls doesn't share the same context". Something like this:

firewalls:
    core:
        context: core
    file_manager:
        context: [core,platform]
    platform:
        context: platform
    main:
        anonymous: lazy

I found nothing about it. Maybe it can't be done, maybe I have to create custom provider or authenticator to hack it. Maybe I can make it without Symfony, it's only php after all, so could I make the file-manager part accessible to every one (so under the main firewall) and add a Listener that would check if the request is for file-manager, find in session if there is a previous logged-in user, check if the user is a Core or a Platform user and then redirect if not... ? How can I find the previous Core or Platform user in session, when on a "main firewall" page (= authenticated as anonymous), without Symfony functions ? I'm not good enough to know how I could achieve that. Help ?

Upvotes: 1

Views: 1304

Answers (1)

Thomas Sebert
Thomas Sebert

Reputation: 31

I've finally let 3 providers and firewalls with context shared between it. To prevent Core users to access Platform, and vice-versa, I added access_control:

- { path: ^/file-manager, roles: [ROLE_PLATFORM, ROLE_CORE] }
- { path: ^/core, roles: ROLE_CORE }
- { path: ^/plateforme, roles: ROLE_PLATFORM }

so it ends with a 403 access denied error. That's not the behavior I want so I also added 'access_denied_url' option on both core and platform firewall to redirect user on the good login page. As contexts are shared, users are already logged, so on login template I check instance of user object to advice him to disconnect first before trying to access this part.

{% if instanceOf(app.user, 'App\\Entity\\PlatformUser') %}
    You're already logged in Platform space, please <a href="{{ path('platform_logout') }}">log out</a> before access Core space.
{% else %}
    You're already logged in as {{ app.user.username }}, <a href="{{ path('core_logout') }}">log out</a> or <a href="{{ path('core_index') }}">access core panel</a>.
{% endif %}

It's a bit confusing to have context shared between parts that shouldn't share anything but access to file-manager, but no user can access other part so.. that works.

Upvotes: 2

Related Questions