How to do test the access control system in Symfony 5? - phpunit

I am using the new Symfony authorization system.
In the security.yaml file I have set the following access restrictions.
security:
enable_authenticator_manager: true
//....
role_hierarchy:
ROLE_ADMIN: [ROLE_USER]
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/signup, roles: PUBLIC_ACCESS }
- { path: ^/reset, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
In my test, I'm trying to catch a 302 redirect.
$client = static::createClient();
$client->request('GET', '/');
$this->assertSame(302, $client->getResponse()->getStatusCode());
But I get the following error message.
Failed asserting that 500 is identical to 302.
Please note that I am getting 500 error instead of 403 redirect code.
Then I try to trace the request with.
$client->catchExceptions(false);
And I see the following request stack.
There was 1 error:
App\Tests\Functional\HomeTest::testGuest
Symfony\Component\Security\Core\Exception\AccessDeniedException:
Access Denied.
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\security-http\Firewall\AccessListener.php:112
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\security-http\Firewall\AccessListener.php:106
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\security-bundle\Debug\WrappedLazyListener.php:49
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\security-bundle\Security\LazyFirewallContext.php:60
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\security-bundle\Debug\TraceableFirewallListener.php:59
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\security-http\Firewall.php:86
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\event-dispatcher\Debug\WrappedListener.php:117
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\event-dispatcher\EventDispatcher.php:230
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\event-dispatcher\EventDispatcher.php:59
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\event-dispatcher\Debug\TraceableEventDispatcher.php:151
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\http-kernel\HttpKernel.php:132
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\http-kernel\HttpKernel.php:78
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\http-kernel\Kernel.php:199
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\http-kernel\HttpKernelBrowser.php:65
C:\Users\webgr\projects\project-manager\manager\vendor\symfony\framework-bundle\KernelBrowser.php:169 C:\Users\webgr\projects\project-manager\manager\vendor\symfony\browser-kit\AbstractBrowser.php:402
C:\Users\webgr\projects\project-manager\manager\tests\Functional\HomeTest.php:15
Why I can't do test the "access control"?
Why am I getting a 500 server error instead of a simple redirect 302 while testing?
I would be glad to any suggestion.
Thank you in advance.
Here's another thing I would like to add, it is quite possible that this information can be decisive. My site is running with https.

Finally, I figured out what the problem was I added the following lines to framework.yaml and everything worked.
when#test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

Related

User Role based routes in Symfony

I've got five types of roles in my Symfony project.
I want to create restrictions for each role, so that they will have access to specific routes. They will not be able to access others paths. I want to do this in an efficient way like Laravel uses middleware in the routes for access. Is there any way like this in Symfony?
You could do that in different ways, and it depends on your Symfony version.
In the older Symfony versions, the most common way to restrict access to certain routes is adding into config/packages/security.yaml some parameters such as:
access_control:
- { path: '^/agent', roles: ROLE_AGENT }
- { path: '^/profile', roles: ROLE_USER }
- { path: '^/admin', roles: ROLE_ADMIN }
- { path: '^/admin/statistics', roles: ROLE_SUPER_ADMIN }
Also, it's good practice to define the role hierarchy into the same file config/packages/security.yaml :
`role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN`
Hence, the users with a SUPER_ADMIN_ROLE will have access to routes restricted to ROLE_ADMIN.
However, my recommendation is if you've several parts to restrict define the general access roles into the config/packages/security.yaml file such as and define a role hierarchy as I said before:
access_control:
- { path: '^/admin', roles: ROLE_ADMIN }
- { path: '^/profile', roles: ROLE_USER }
Then restrict the other routes using annotations thanks to SensioFrameworkExtraBundle, for example:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
/**
* Require ROLE_ADMIN_SYSTEMS for *every* controller method in this class.
*
* #IsGranted("ROLE_ADMIN_SYSTEMS")
*/
public function YourFunction() { }
For more information look into official Symfony Docs that are very clear. Symfony Security official documentation
I hope this could help you, if you've any other doubts just tell me,
Kind regards.

Symfony4 Two authentification methods in security

I want two authentications methods in my application.
One for the entity User, and other (admin) with a plaintext.
Very simple.
Thus, when I configure security.yaml, I specify the providers:
security:
providers:
user:
entity:
class: App\Entity\User
property: username
in_memory:
memory:
users:
admin:
password: admin
roles: 'ROLE_ADMIN'
encoders:
App\Entity\User: bcrypt
Symfony\Component\Security\Core\User\User: plaintext
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
provider: in_memory
pattern: ^/admin/
guard:
provider: in_memory
form_login:
login_path: admin_login
check_path: admin_login
logout:
path: /admin/logout
target: /
default:
provider: user
anonymous: ~
guard:
provider: user
form_login:
login_path: login
check_path: login
default_target_path: login_redirect
use_referer: true
logout:
path: /logout
target: /
access_control:
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/dashboard, roles: ROLE_USER }
And return the error:
In GuardAuthenticationFactory.php line 121:
Because you have multiple guard configurators, you need to set the "guard.e
ntry_point" key to one of your configurators ()
Then, if I have to set the guard.entry_point, I need do something like this:
admin:
entry_point: app.form_admin_authenticator
main:
entry_point: app.form_user_authenticator
And therefore, if I undestard, I need to configure a Authentication Listener like this: https://symfony.com/doc/current/components/security/authentication.html
(btw, this particular help page is very ambiguous and incomplete)
Is it necessary? It seems too complex for my purpose
I ran into this particular error. My situation might be a little different, but I had a similar need to authenticate using different authentication strategies depending on the entry point to the application.
One thing your config doesn't include is a reference to any Guard Authenticator objects. See this documentation for an intro to what role those objects play, and how to define them. Symfony's Security package is pretty complicated, and I found that using Guard Authenticators made the process a lot simpler for my use case.
Here is an example of a security.yaml config referencing two different authenticators. The entry_point configuration tells Symfony which one to try first, because in my particular case, Symfony otherwise wouldn't know which authentication strategy to apply first to an incoming request.
security:
providers:
user:
id: App\My\UserProviderClass
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
logout:
path: app_logout
guard:
entry_point: App\My\MainAuthenticator
authenticators:
- App\My\MainAuthenticator
- App\My\OtherAuthenticator
Custom Guard Authenticators contain a method called supports. This method takes the incoming request as its only argument, and returns true or false based on whether the given authenticator should be applied to the incoming request. A common practice might be to check the request's Symfony route name (as defined by the controller) or perhaps something like the full URI for the request. For example:
/**
* Does the authenticator support the given Request?
*
* If this returns false, the authenticator will be skipped.
*
* #param Request $request
*
* #return bool
*/
public function supports(Request $request): bool
{
$matchesMyRoute = 'some_route_name' ===
$request->attributes->get('_route');
$matchesMyUri = '/path/to/secured/resource' ===
$request->getUri();
return $matchesMyRoute || $matchesMyUri;
}
You can imagine that if multiple Guard Authenticators exist in the same application, it's likely the case that one would only want them to apply to a request of a certain type, whether the differentiation is based on the kind of auth applied (eg. a header with an API key vs. a stateful session cookie), whether the difference is more about the specific route being hit, or perhaps a combination of factors.
In this case, telling Symfony which Guard Authenticator to try first may be important for your security strategy, or may have performance ramifications. For example, if you had two authenticators, and one had to hit the database to verify a stateful session, but another could verify the request's authentication statelessly, eg. by verifying a JWT's signature, you'd probably want to make the JWT authenticator run first, because it might not need to make a round trip to the database to authenticate the request.
See this article for a deeper explanation: https://symfonycasts.com/screencast/symfony-security/entry-point

Install FOS UserBundle

I'm following a tutorial to install this FOSUserBundle to my project.
I got my information from this french tutorial: http://www.tutodidacte.com/symfony2-installer-fosuserbundle
So I did those commands:
php ./composer.phar require friendsofsymfony/user-bundle "~2.0#dev"
php composer.phar update
Then I created a new Bundle,
php bin/console generate:bundle
Bundle Namespace : Kingdom/UserBundle
But after doing that, in the AppKernel i can see the new UserBundle, mais the FOSUserBundle isn't here.
I try to add it by myself writting it in the file; but after when i try to create an entity we can see something is clearly wrong.
Sorry for the presentation of this below...I haven't succeed to print it correctly.
php bin/console generate:doctrine:entity
Fatal error: Uncaught exception 'Symfony\Component\Config\Definition\Exception\I
nvalidConfigurationException' with message 'The child node "db_driver" at path "
fos_user" must be configured.' in C:\wamp\www\Kingdom\vendor\symfony\symfony\src
\Symfony\Component\Config\Definition\ArrayNode.php:240
Stack trace:
0 C:\wamp\www\Kingdom\vendor\symfony\symfony\src\Symfony\Component\Config\Defin
ition\BaseNode.php(303): Symfony\Component\Config\Definition\ArrayNode->finalize
Value(Array)
1 C:\wamp\www\Kingdom\vendor\symfony\symfony\src\Symfony\Component\Config\Defin
ition\Processor.php(37): Symfony\Component\Config\Definition\BaseNode->finalize(
Array)
2 C:\wamp\www\Kingdom\vendor\symfony\symfony\src\Symfony\Component\Config\Defin
ition\Processor.php(50): Symfony\Component\Config\Definition\Processor->process(
Object(Symfony\Component\Config\Definition\ArrayNode), Array)
3 C:\wamp\www\Kingdom\vendor\friendsofsymfony\user-bundle\DependencyInjection\F
OSUserExtension.php(51): Symfony\Component\Config\Definition\Processor->processC
onfigur in C:\wamp\www\Kingdom\vendor\symfony\symfony\src\Symfony\Component\Conf
ig\Definition\ArrayNode.php on line 240
It seems you haven't properly configured the bundle. Follow the steps here:
http://symfony.com/doc/current/bundles/FOSUserBundle/index.html
In your case, it seems you are missing at least this:
http://symfony.com/doc/current/bundles/FOSUserBundle/index.html#step-5-configure-the-fosuserbundle
EDIT: After reading your question again, it seems you're not loading the bundle properly, as described here:
http://symfony.com/doc/master/bundles/FOSUserBundle/index.html#step-2-enable-the-bundle
After Installation A bundle needs to be enabled in AppKernel.php file. FOSUserBundle needs a bit of configuration to make it work properly I have written a simple and easy guide on it:
https://www.cloudways.com/blog/implement-fosuserbundle-in-symfony-3-1/
Installation:
composer require friendsofsymfony/user-bundle "~2.0#dev"
Enabling Bundle in AppKernel.php
After installing FOSUserBundle you must enable it in the project. Go to app/config/AppKernel.php and add the highlighted line in the bundles array.
$bundles = [
...
new FOS\UserBundle\FOSUserBundle(),
]
Create User Entity
Create the User entity as you are creating above :)
Configuring Security.yml file
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
# if you are using Symfony < 2.8, use the following config instead:
# csrf_provider: form.csrf_provider
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 }
Configure FOSUserBundle in a Config
Add this config in config.yml
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
Importing Route files of FOSUserBundle
Add this to import routes in Routing.yml
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
Updating Database Schema
The last step is to update the database schema to create table in the database.
php bin/console doctrine:schema:update --force
Now move to the app URL add /login in it you will see the login page.

Symfony IP Access Control Configuration Not Working

We need to restrict part of our application by IP, even though there is other authentication going on. Belt and braces.
The Symfony docs say that this should work...
- { path: ^/api/whatever, role: [ROLE_WHATEVER_API], ips: [ 1.2.3.4, 5.6.7.8 ] }
... but this seems to break the access control altogether and all requests get through.
So we have used an expression instead, which works, but is ugly and once we have a list of 5 or so IPs is just and unwieldy mess...
- { path: ^/api/whatever, allow_if: "('1.2.3.4' == request.getClientIp() or '5.6.7.8' == request.getClientIp()) and has_role('ROLE_WHATEVER_API')" }
Anyone got any ideas why the nice clean method suggested by the docs (http://symfony.com/doc/current/cookbook/security/access_control.html#matching-access-control-by-ip) does not work?
You may want to read the docs again.
IP in access control works, it just does not work the way your "allow_if" expression does.
If you want it to restrict IP's that are not 1.2.3.4 and 5.6.7.8, do as follows :
- { path: ^/api/whatever, role: [ROLE_WHATEVER_API], ips: [ 1.2.3.4, 5.6.7.8 ] }
- { path: ^/api/whatever, role: [ROLE_NO_ACCESS] }
This is covered here.
Your access rule will only match for specified IPs. It means that Symfony will continue matching next rules, and will conclude user is allowed to call the path, if none of the paths rejected it.
You need to explicitly forbid access for others with a second rule:
- { path: ^/api/whatever, role: [ROLE_WHATEVER_API], ips: [ 1.2.3.4, 5.6.7.8 ] }
- { path: ^/api/whatever, role: [ROLE_NO_ACCESS]}
It is explained in the docs you referenced.

Allow anonymous access to specific URL in Symfony firewall protected bundle

I have a Symfony bundle which can only be accessible by using mydomain.com/box
To access /box you must be logged in, however i would like to enable anonymous access into mydomain.com/box/download
# Security.yml
access_control:
- { path: ^/box , roles: ROLE_USER}
How can i do ?
# security.yml
access_control:
- { path: ^/box/download , roles: IS_AUTHENTICATED_ANONYMOUSLY}
- { path: ^/box , roles: ROLE_USER}
Symfony2 firewalls are processed in order, and only first matching one will be applied. Therefore, if you put the /box/download before /box, the /box/download rule will be processed and the rest will be ignored.
http://symfony.com/doc/current/book/security.html
Symfony 6
As of Symfony 6 you need to use the role PUBLIC_ACCESS instead of IS_AUTHENTICATED_ANONYMOUSLY.
https://symfony.com/doc/6.0/security.html#allowing-unsecured-access-i-e-anonymous-users

Resources