Symfony IP Access Control Configuration Not Working - symfony

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.

Related

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

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

How to set _locale dynamically based on host

Is there any way to set _locale parameter of routing configuration as a function call or an expression result? I have multiple hosts running on the same symfony app, and there is an i18n turned on. Everything is working fine, but now i need to have another locales set for a specified host.
Right now my routing config looks like
app:
resource: '#AppBundle/Controller/'
...
requirements:
_locale: '%route_locales%'
...
and i have something like this in parameters:
...
route_locales: en|de|fr
...
That would be perfect if i can use something like
"#=service('AppBundle\\\\...\\\\LocalesConfigurator').getLocales()"
as a _locale: value to get this value based on a function call result. Or maybe there are some other options to get another _locale set for a specified host?
You can do it like #Jakumi suggested (via env params passed from server)
or using only application logic:
In route_locales put all possible locales that can be used in any host.
Validate incoming _locale inside "request listener" depending on request host - if some _locale is not allowed for host then return 404 response or show 404 error page etc.
Seems like i've found at least one solutuin, thanks to Jakumi for the idea. I've created a parameter in my config file, which receives an environment varaible. Then i use this parameter in my routing file. The main idea is that i can control this environment variable value using bootstrap file. So it looks like
bootstrap.php (in the example it's just a static value, but in a real life this value will depend on host).
bootstrap.php
...
$_SERVER['SYMFONY__ROUTE__LOCALES'] = 'en|es';
...
config.yml
parameters:
route_locales: '%route.locales%'
routing.yml
app:
resource: '#AppBundle/Controller/'
...
requirements:
_locale: '%route_locales%'
...
I don't realy like this solution, and will be thankful for any better solutions, but at least it does what i want.
I was in the same issue some months ago and I was able to found a solution that worked correctly, but there may also be some better ones.
As I needed different route names to be loaded depending on the locale (i.e. /contact in English and /contacto in Spanish) what I did was creating the methods in the controller and creating an individual link to them in routes.yaml
contact-en:
path: /contact/
controller: App\Controller\ContactController::index
host: domain1.tld
contact-es:
path: /contacto/
controller: App\Controller\ContactController::index
host: domain2.tld
Then I created an EventListener called LocaleSwitcher:
class LocaleSwitcher
{
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$host = $request->getHost();
switch($host) {
case 'domain1.tld': {
$request->setLocale('en');
break;
}
case 'domain2.tld': {
$request->setLocale('es');
break;
}
}
}
}
And then adding it in services.yaml for event kernel.request and priority 20:
App\EventListener\LocaleSwitcher:
tags:
- { name: kernel.event_listener, event: kernel.request, priority: 20 }

How do I force a 404 from the symfony routing in YML without specifying a custom controller?

Problem to solve
I want to force to trigger a 404 for a certain specific route in symfony, without having to specify a custom controller that just throws the exception, and before the router continues to explore the rest of the routing file.
Can I say that in the routing.yml file for a specific route?
Context
I have this routing for form submitting in my travel agency:
POST /submit/{submitterForm}
where submitterForm can only take 2 values: purchase-trip and contact depending on if the visitor submits a form in a page where he is visiting a trip or from the contact page. For example POST /submit/contact would be valid.
I have this other route:
GET /{destinationSeoUri}/{tripSeoUri}
to display a specific trip for a destination. For example GET /thailand/trip-bangkok-beaches would be valid.
Limiting values from the router
This is the relevant extract of my routing.yml:
submit:
path: /submit/{submitterForm}
methods: [ POST ]
defaults: { _controller: AppBundle:DataSubmission:submit }
requirements:
submitterForm: purchase-trip|contact
trip:
path: /{destinationSeoUri}/{tripSeoUri}
methods: [ GET ]
defaults: { _controller: AppBundle:Trip:trip }
What I want
For any POST request with an invalid value of {submitterForm} in the route /submit/{submitterForm} I want that the system returns a 404 Not Found
It does not happen now because when the router does not match the submit route pattern, it still falls down to the next ones and then matches the trip pattern.
Then, the system says "okey, I have a route that matches, instead, this is only allowed for GET, so I'll respond a 405 Method not allowed".
This would hold true for something like POST /thailand/trip-bangkok-beaches but I want to specifically signal the client that if the POST matches the /submit/any-invalid-form-submitter-name it is then a Not Found.
Initial solution
To do so, I've inserted, in order, in the middle of the previous two, a catch-all for /submit/* like in this routing excerpt:
submit:
path: /submit/{submitterForm}
methods: [ POST ]
defaults: { _controller: AppBundle:DataSubmission:submit }
requirements:
submitterForm: purchase-trip|contact
submit_fallback:
path: /submit/{submitterForm}
methods: [ POST ]
defaults: { _controller: AppBundle:DataSubmission:notFound }
trip:
path: /{destinationSeoUri}/{tripSeoUri}
methods: [ GET ]
defaults: { _controller: AppBundle:Trip:trip }
This way, requesting to POST /submit/any-invalid-form-submitter-name is matched by the submit_fallback route.
The controller
The controller is a pretty simple "on-liner":
{
throw new NotFoundHttpException();
}
Preferred solution
I think I'm looking for something similar to this:
submit_fallback:
path: /submit/{submitterForm}
methods: [ POST ]
status: 404
So, questions
Is it possible for me to tell the submit_fallback something like: hey guy you don't need a _controller just because you have to send a 404 Not found (or any other status I would want) and nothing else.
If so, how?
Just add requirements onto your other route so that destinationSeoUri cannot equal submit. This should force that route to not match, and throw the 404 instead of the 405 error.
trip:
path: /{destinationSeoUri}/{tripSeoUri}
methods: [ GET ]
defaults: { _controller: AppBundle:Trip:trip }
requirements:
destinationSeoUri: "^(?!submit$)"

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

Symfony2 route based on host. With parameters

I have followed the instructions here:
http://symfony.com/doc/current/components/routing/hostname_pattern.html
To make the route based on the host. However I want to use parameters instead of hard coding. The documentation says you can use service parameters, but I seem to be having trouble getting the parameters to work.
Here is the code from routing.yml:
rc_course_new:
pattern: /course/new
host: "{ domain }"
defaults: { _controller: CoursesRCWizardBundle:Wizard:new }
requirements:
domain: "%rc_domain%"
And here is the code from services.yml:
parameters:
rc_domain: my.domain.com
I get this error (looks like it is not picking up the parameter but seeing it as a hard code):
Oops! Google Chrome could not find { domain }
Managed to fix this:
In the routing:
rc_course_new:
pattern: /course/new
host: "%rc_domain%"
defaults: { _controller: CoursesRCWizardBundle:Wizard:new }
In the services (might work better in the parameters file) file:
parameters:
rc_domain: my.domain.com

Resources