Symfony2 - detect if user is inside a secure firewall - php

I'm trying to find out if a user is inside a secure firewall.
security.yml:
firewalls:
non_secure_area:
pattern: ^/
anonymous: true
secure_area:
pattern: ^/admin
form_login:
#etc.
logout:
#etc.
So I need to know if the user is inside the 'secure_area' secure part of the site.
I have used this, but of course it only tells me if somebody is 'logged in' AND on a HTTPS page. There must be a better way:
if( $request->isSecure() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') ) {
}

You can get security token and access provider key on it.
$token = $securityContext->getToken();
$providerKey = $token->getProviderKey(); // secured_area
Dont forget to check that token exist and its not an instance of AnonymousToken

If you are into something that is ContainerAware, you may get the Request, and then the URI [see docs]:
$request = $this->container->get('request');
$uri = $request->getUri();
Then you can check such string against /admin as you wish.

Related

How do I redirect to a dynamic custom route after register with FOSUserBundle

I've been looking for a while for this, but can't find the answer.
Expected behavior:
User goes on a product page
User wants to buy the product, so he needs to login / register (with a modal)
User doesn't have an account so he registers
After registration (there is no email confirmation), user is signed in and redirected to the current product page
User can proceed to checkout, etc...
This behavior is working in the case of a login, with the use of the _target_path parameter in the form.
However, this parameter does not apply in registration. This is kinda annoying, where did I miss something out ? I am looking into implementing a Listener on the registration success event but it seems really odd to not being as simple as for the login form.
Edit: found my solution, see my own answer below
Answering for those wondering too.
TL;DR : the registration process does not follow the login's
Thanks to #yceruto for the comment.
I made a quick listener on the REGISTRATION_SUCCESS event:
/**
* #param FormEvent $event
*/
public function redirectOnRegistration(FormEvent $event)
{
$route = $event->getRequest()->headers->get('referer', $this->router->generate('homepage'));
$response = new RedirectResponse($route);
$event->setResponse($response);
}
(I redirect on homepage if there is no referer).
In your security settings you must turn on referer:
firewalls:
dev:
pattern: ^/(_(profiler|wdt|console)|css|images|js)/
security: false
main:
pattern: ^/any_pattern
anonymous: ~
form_login:
login_path: any_pattern_login
check_path: any_pattern_login
use_referer: true
logout: true
access_denied_handler: app.security.access_denied_handler
guard:
authenticators:
- app.api_authenticator
provider: api_provider
logout:
path: any_pattern_logout
invalidate_session: true

Symfony, No Authentication Provider found for token of class UsernamePasswordToken after login, how to?

I have a loginAction where I attempt to get a user from the db and log her in.
It seems that the 'main' firewall rules are used rather than the secure_area ones. because i get:
No Authentication Provider found for token of class "Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken"
if i add
guard:
authenticators:
- token_authenticator
to the 'main' firewall rule, then the error goes away, but the token_authenticatortakes over and I get Authentication Required on all routes
If I dump the $token or the session after I login, I can see my user in there.
Any ideas what am I doing wrong?
public function loginAction(Request $request)
{
if ($request->isMethod('POST')) {
$userManager = $this->container->get('entity.user');
$hasEmail = $this->getDoctrine()->getRepository('AppBundle:User')->findOneBy([
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
]);
// if credentials found
if ($hasEmail) {
// login the user
$token = new UsernamePasswordToken($hasEmail, $hasEmail->getPassword(), "secure_area", $hasEmail->getRoles());
$this->get("security.token_storage")->setToken($token);
$this->get('session')->set('_security_secure_area', serialize($token));
//$event = new InteractiveLoginEvent($request, $token);
//$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
} else {
$error = 'invalid credentials';
}
}
return [];
}
here are my firewall rules:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
provider: db_provider
main:
anonymous: ~
secure_area:
pattern: ^/
anonymous: ~
logout:
path: /logout
target: /
guard:
authenticators:
- token_authenticator
Firewalls are differentiated by the set of pattern which is the prefix of your route parameter. So, in your case, secure_area will ever be reached as the main firewall will takeover.
You can use any unique pattern like /secured or /api to reach out the firewall when you use a route with similar pattern.
Moreover, You shouldn't handle the user login explicitly unless required. Instead you should use $authenticationUtils. Please follow this document(for 2.8).
Setting up providers and firewalls in security.yml is depends on source of Users who can login to the application. This will be bit clear once you mention about those.
Hope this helps!

Symfony - custom login

I need to implement one thing. In Symfony I have 2 firewalls (admin and whole web) - both using FOSUserBundle. Now I need to do a login for one simple section, which is a standalone part. I do not want to use User entity which I have for admin and web login, because I do not want to mix the users with the new section. So I created a standalone entity SectionUser and new firewall. Now I tried to log the user manually - with creating a new UsernamePasswordToken against the new firewall.
$token = new UsernamePasswordToken($user, $user->getPassword(), "section", array());
$securityContext = $this->container->get('security.context'); // do it your way
$securityContext->setToken($token);
but I end up in infinite loop on /new-section/login. This is my security.yml and firewall settings:
section:
pattern: ^/new-section(.*)
form_login:
login_path: /new-section/login
use_forward: true
check_path: /new-section/login_check
failure_path: null
logout:
path: /new-section/logout
anonymous: true
Do I need to implement anything else or my approach is wrong and I should use FOSUserBundle in any way? Thank you for pointing me a direction.
Is custom login provider a way? I am stuck here and do not know how to handle it.

Symfony authentication providers

I'm using fr3d/ldap-bundle. It logs me in and imports users from AD if they're not in db. That's fine.
Despite AD users I also have local users, which are in my db. There is special column authType which says how user should be authenticated - via LDAP or natively ( FOS ). I've created my own user provider:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
PROBLEM: Chain provider isn't an option - it allows user to login with his LDAP password AND with his local password! That's a big security issue.
Is there a way to login user via different authentication providers, depending on the db field?
EDIT:
My security.yml:
providers:
fos_userbundle:
id: fos_user.user_provider.username
appbundle_user_provider:
id: appbundle.user_provider
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin.*
context: user
fr3d_ldap: ~
form_login:
provider: appbundle_user_provider
csrf_provider: security.csrf.token_manager
always_use_default_target_path: true
default_target_path: admin_main
login_path: /admin/login
check_path: /admin/login_check
logout:
path: /admin/logout
target: /admin/login
anonymous: true
Here is security.yml. This line fr3d_ldap: ~ enables the ldap bundle, which authorize ldap users and saves them into my db. Without it I cannot authorize them, probably I would have to write custom AuthenticationProvider.
I am not very familiar with ldap but I would suggest try doing a completely manual login
$token = new UsernamePasswordToken($user, null, "firewallname", $user->getRoles());
$securityContext = $this->container->get('security.context');
$securityContext->setToken($token);
Then you can manually do the checks yourself, and depending on the result of the check decide how you want to verify the user before authenticating. For example, run a query by username and password before executing this login code or whatever, depending on the db field you want.
Your approach seems fine but you should check logic of your methods.
First of all this one:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
You pass $username to this method as an argument, but then use $user object, which seems to be undefined in current context.
Secondly:
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
So as chooseProviderForUsername method actually does not return any value you are not able to chain it this way.
I hope refactoring these issues should make your provider work properly.
Ok, so very brief answer, but I think at the moment Symfony is searching for the user amongst any old User Provider rather than the one you want it to for that particular user (which explains the whole logging in with two passwords thing). A solution should be to make AppBundleUserProvider implement UserProviderInterface, remove the other User Providers from security.yml and then to ensure that the first thing AppBundleUserProvider does it to find out which User Provider is required for that user then mimic it for every method in the UserProviderInterface. You could set $this->realUP based on Username, then set every method to just return $this->realUP->someMethod().
The cleanest way I can think of is to create your own ChainProvider class that only allows login with one provider and use the Dependency Injection Container to use yours.
You just need to override the security.user.provider.chain.clas parameter definition in your bundle's config file.

Check if user is redirected or not in symfony?

I have a route in my symfony project that is restricted, so if I try to go there without being logged on I'm redirected to the login page.
I'd like to distinguish between when I try to go to a restricted area without being logged on, and when I just go to the login page directly.
Any clues of how to do this in symfony?
Update
By distinguish I mean some way to check if auser landed on the login page by going to that url directly, or if the user landed on the login page because he tried to access a restricted page.
Thomas' solution is a good one. Here is an alternative.
Knowing that and you will only be redirected if not logged-in you can just have your security firewall redirect with an additional parameter.
security.yml
...
secured_area:
pattern: ^/
form_login:
check_path: /login_check
login_path: /login/restricted
...
routing.yml
login:
pattern: /login
defaults: { _controller: YourBundle:Default:login }
login:
pattern: /login/{restricted}
defaults: { _controller: YourBundle:Default:login }
controller
public function loginAction($restricted = null)
{
...
Do something with $restricted
...
Now if someone goes to the login page:
yoursite.com/login
everything will be normal and $restricted = null so you know they came directly.
If someone goes to:
yoursite.com/admin
when they are not logged-in they will be redirected to:
yoursite.com/login/restricted
In your controller $restricted will be set and you will know they tried to access a restricted area without authentication.
Of course you can change restricted to what ever would make sense to you.
Did you try $this->get('request')->headers->get('referer') ?
get('referer') will only return an internal and relative path. If you are on test.com and click on a link going to your app get('referer') will return null. So if the user hits a redirect, the next request should have the proper URL.
Otherwise you could create a custom exception listener and catch any AccessDeniedException. When the code catches one you could use the FlashBag to pass values through the redirect.
In addition to hcoat answer, you can do this without creating new URL. You can use query parameter:
secured_area:
pattern: ^/
form_login:
check_path: /login_check
login_path: /login?r
And then in your controller's action check for this parameter:
public function loginAction(Request $request)
{
$redirected = $request->query->has('r');
// ...
}

Categories