I would like to check from inside a controller if it is a secured page or not.
How to do this ?
My use case is the following :
User can register and log in
If he logs in and tries to access a secured page, he will be redirected to a "beta version" page until the end of June.
If he tries to access a normal page (not secured), he will be able to access it without any redirection.
Thanks for your help !
Aurel
When Symfony2 processes a request it matches the url pattern with each firewall defined in app/config/security.yml. When url pattern matches with a pattern of the firewall Symfony2 creates some listener objects and call handle method of those objects. If any listener returns a Response object then the loop breaks and Symfony2 outputs the response. Authentication part is done in authentication listeners. They are created from config defined in matched firewall e.g form_login, http_basic etc. If user is not authenticated then authenticated listeners create a RedirectResponse object to redirect user to login page. For your case, you can cheat by creating a custom authentication listener and add it in your secured page firewall. Sample implementation would be following,
Create a Token class,
namespace Your\Namespace;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class MyToken extends AbstractToken
{
public function __construct(array $roles = array())
{
parent::__construct($roles);
}
public function getCredentials()
{
return '';
}
}
Create a class that implements AuthenticationProviderInterface. For form_login listener it authenticates with the given UserProvider. In this case it will do nothing.
namespace Your\Namespace;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Acme\BaseBundle\Firewall\MyToken;
class MyAuthProvider implements AuthenticationProviderInterface
{
public function authenticate(TokenInterface $token)
{
if (!$this->supports($token)) {
return null;
}
throw new \Exception('you should not get here');
}
public function supports(TokenInterface $token)
{
return $token instanceof MyToken;
}
Create an entry point class. The listener will create a RedirectResponse from this class.
namespace Your\Namespace;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\HttpUtils;
class MyAuthenticationEntryPoint implements AuthenticationEntryPointInterface
{
private $httpUtils;
private $redirectPath;
public function __construct(HttpUtils $httpUtils, $redirectPath)
{
$this->httpUtils = $httpUtils;
$this->redirectPath = $redirectPath;
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
//redirect action goes here
return $this->httpUtils->createRedirectResponse($request, $this->redirectPath);
}
Create a listener class. Here you will implement your redirection logic.
namespace Your\Namespace;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
class MyAuthenticationListener implements ListenerInterface
{
private $securityContext;
private $authenticationEntryPoint;
public function __construct(SecurityContextInterface $securityContext, AuthenticationEntryPointInterface $authenticationEntryPoint)
{
$this->securityContext = $securityContext;
$this->authenticationEntryPoint = $authenticationEntryPoint;
}
public function handle(GetResponseEvent $event)
{
$token = $this->securityContext->getToken();
$request = $event->getRequest();
if($token === null){
return;
}
//add your logic
$redirect = // boolean value based on your logic
if($token->isAuthenticated() && $redirect){
$response = $this->authenticationEntryPoint->start($request);
$event->setResponse($response);
return;
}
}
}
Create the services.
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="my_firewall.security.authentication.listener"
class="Your\Namespace\MyAuthenticationListener"
parent="security.authentication.listener.abstract"
abstract="true">
<argument type="service" id="security.context" />
<argument /> <!-- Entry Point -->
</service>
<service id="my_firewall.entry_point" class="Your\Namespace\MyAuthenticationEntryPoint" public="false" ></service>
<service id="my_firewall.auth_provider" class="Your\Namespace\MyAuthProvider" public="false"></service>
</services>
</container>
Register the listener. Create a folder named Security/Factory in your bundles DependencyInjection folder. Then create the factory class.
namespace Your\Bundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
class MyFirewallFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$provider = 'my_firewall.auth_provider.'.$id;
$container->setDefinition($provider, new DefinitionDecorator('my_firewall.auth_provider'));
// entry point
$entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPoint);
// listener
$listenerId = 'my_firewall.security.authentication.listener'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('my_firewall.security.authentication.listener'));
$listener->replaceArgument(1, new Reference($entryPointId));
return array($provider, $listenerId, $entryPointId);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'my_firewall'; //the listener name
}
protected function getListenerId()
{
return 'my_firewall.security.authentication.listener';
}
public function addConfiguration(NodeDefinition $node)
{
$node
->children()
->scalarNode('redirect_path')->end()
->end()
;
}
protected function createEntryPoint($container, $id, $config, $defaultEntryPointId)
{
$entryPointId = 'my_firewall.entry_point'.$id;
$container
->setDefinition($entryPointId, new DefinitionDecorator('my_firewall.entry_point'))
->addArgument(new Reference('security.http_utils'))
->addArgument($config['redirect_path'])
;
return $entryPointId;
}
}
Then in your NamespaceBundle.php of your bundle folder add the following code.
public function build(ContainerBuilder $builder){
parent::build($builder);
$extension = $builder->getExtension('security');
$extension->addSecurityListenerFactory(new Security\Factory\MyFirewallFactory());
}
Authentication listener is created, phew :). Now in your app/config/security.yml do following.
api_area:
pattern: ^/secured/
provider: fos_userbundle
form_login:
check_path: /login_check
login_path: /login
csrf_provider: form.csrf_provider
my_firewall:
redirect_path: /beta
logout: true
anonymous: true
I dont know if this is the correct method
But you can try the following
/vendor/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php has a method handleRaw that converts a request to a response . The request object is accessable from there . You can check if the client has requested to access the secured page . If so , you can manually set the controller like
$request->attributes->set('_controller','your\Bundle\SecureBundle\Controller\SecureController::secureAction');
Another solution would be to set a session if the user try to access a secured page and check for the same inside your controller
Again , this might not be the correct method but it is a possible workaround
Related
According to this "Old" article Is there any sort of "pre login" event or similar? I can extend UsernamePasswordFormAuthenticationListener to add some code pre-login.
In symfony3 seems that there's no security.authentication.listener.form.class parameter, so how can I reach the same result without changing symfony security_listener.xml config file?
To perform some pre/post-login checks (that means before/after the user authentication) one of the most simple and flexible solutions, offered by the Symfony framework, is to learn How to Create and Enable Custom User Checkers.
If you need more control and flexibility the best option is to learn How to Create a Custom Authentication System with Guard.
Take a look at the simple implementation example below:
security.yml
firewall_name:
guard:
authenticators:
- service_name_for_guard_authenticator
entry_point: service_name_for_guard_authenticator <-- important to add a default one (as described in the docs) if you have many custom authenticators (facebook...)
service.xml
<service id="service_name_for_guard_authenticator"
class="AppBundle\ExampleFolderName\YourGuardAuthClassName">
<argument type="service" id="router"/>
<argument type="service" id="security.password_encoder"/>
</service>
YourGuardAuthClassName.php
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
class YourGuardAuthClassName extends AbstractGuardAuthenticator
{
private $router;
private $passwordEncoder;
public function __construct(
Router $router,
UserPasswordEncoder $passwordEncoder)
{
$this->router = $router;
$this->passwordEncoder = $passwordEncoder;
}
public function start(Request $request, AuthenticationException $authException = null)
{
$response = new RedirectResponse($this->router->generate('your_user_login_route_name'));
return $response;
}
public function getCredentials(Request $request)
{
# CHECK IF IT'S THE CHECK LOGIN ROUTE
if ($request->attributes->get('_route') !== 'your_user_login_route_name'
|| !$request->isMethod('POST')) {
return null;
}
# GRAB ALL REQUEST PARAMETERS
$params = $request->request->all();
# SET LOGIN CREDENTIALS
return array(
'email' => $params['email'],
'password' => $params['password'],
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$email = $credentials['email'];
$user = $userProvider->loadUserByUsername($email);
if (! $user){
throw new UsernameNotFoundException();
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
# YOU CAN ADD YOUR CHECKS HERE!
if (! $this->passwordEncoder->isPasswordValid($user, $credentials['password'])) {
throw new BadCredentialsException();
}
return true;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
# OYU CAN ALSO USE THE EXCEPTIONS TO ADD A FLASH MESSAGE (YOU HAVE TO INJECT YOUR OWN FLASH MESSAGE SERVICE!)
if ($exception instanceof UsernameNotFoundException){
$this->flashMessage->error('user.login.exception.credentials_invalid');
}
if ($exception instanceof BadCredentialsException){
$this->flashMessage->error('user.login.exception.credentials_invalid');
}
return new RedirectResponse($this->router->generate('your_user_login_route_name'));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return new RedirectResponse($this->router->generate('your_success_login_route_name'));
}
public function supportsRememberMe()
{
return false;
}
}
I have a user object that has a property 'enabled'. I want every action to first check if the user is enabled before continuing.
Right now I have solved it with a Controller that every other controller extends, but using the setContainer function to catch every Controller action feels really hacky.
class BaseController extends Controller{
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
$user = $this->getUser();
// Redirect disabled users to a info page
if (!$user->isEnabled() && !$this instanceof InfoController) {
return $this->redirectToRoute('path_to_info');
}
}
I have tried building this using a before filter (http://symfony.com/doc/current/event_dispatcher/before_after_filters.html), but could not get the User object..any tips?
EDIT:
This is my solution:
namespace AppBundle\Security;
use AppBundle\Controller\AccessDeniedController;
use AppBundle\Controller\ConfirmController;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class UserEnabledListener
{
private $tokenStorage;
private $router;
public function __construct(TokenStorage $tokenStorage, Router $router)
{
$this->tokenStorage = $tokenStorage;
$this->router = $router;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller passed can be either a class or a Closure.
* This is not usual in Symfony but it may happen.
* If it is a class, it comes in array format
*/
if (!is_array($controller)) {
return;
}
$controller = $controller[0];
// Skip enabled check when:
// - we are already are the AccessDenied controller, or
// - user confirms e-mail and becomes enabled again, or
// - Twig throws error in template
if ($controller instanceof AccessDeniedController ||
$controller instanceof ConfirmController ||
$controller instanceof ExceptionController) {
return;
}
$user = $this->tokenStorage->getToken()->getUser();
// Show info page when user is disabled
if (!$user->isEnabled()) {
$redirectUrl = $this->router->generate('warning');
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
}
}
EDIT 2:
Ok so turns out checking for each controller manually is really bad, as you will miss Controllers from third party dependencies. I'm going to use the Security annotation and do further custom logic in a custom Exception controller or template etc.
You can use an event listener to listen for any new request.
You'll need to inject the user and then do your verification:
<service id="my_request_listener" class="Namespace\MyListener">
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
<argument type="service" id="security.token_storage" />
</service>
Edit: Here is a snippet to give an example
class MyRequestListener {
private $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->getRequest()->isMasterRequest()) {
// don't do anything if it's not the master request
return;
}
if ($this->tokenStorage->getToken()) {
$user = $this->tokenStorage->getToken()->getUser();
//do your verification here
}
}
In your case I would use the #Security annotation, which can be very flexible if you use the expression language.
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* #Security("user.isEnabled()")
*/
class EventController extends Controller
{
// ...
}
In the end it's only 1 line in each of your controller files, and it has the advantage of being very readable (a developer new to the project would know immediately what is going on without having to go and check the contents of a BaseController or any potential before filter...)
More documentation on this here.
You can override also getuser() function in your BaseController also.
/**
* Get a user from the Security Token Storage.
*
* #return mixed
*
* #throws \LogicException If SecurityBundle is not available
*
* #see TokenInterface::getUser()
*/
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application.');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
// Redirect disabled users to a info page
if (!$user->isEnabled() && !$this instanceof InfoController) {
return $this->redirectToRoute('path_to_info');
}
return $user;
}
Hi I have a event Listener in symfony2, That I have registered accordingly as well, it need to call at before of any function call of any controller in my module. But it is calling on Whole application, I mean every module. but I want it to called only when some one will open My Module only.
//My Event Listener
namespace Edu\AccountBundle\EventListener;
use Doctrine\ODM\MongoDB\DocumentManager;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Edu\AccountBundle\CommonFunctions\CommonFunctions;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Edu\AccountBundle\Controller\FinancialYearController;
/*
* Class:BeforeControllerListener
* #DESC:its a Listener which will execute at very first of any action of any controller in Account module (Act as a beforeFilter Symfony2)
* #param : #session,#route,#db
* #sunilrawat#indivar.com
* #09-07-2015
*/
class BeforeControllerListener
{
private $session;
private $router;
private $commonFunctions;
public function __construct(Session $session, Router $router, DocumentManager $dm)
{
$this->session = $session;
$this->router = $router;
$this->dm = $dm;
$this->commonFunctions = new CommonFunctions();
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if (!$controller[0] instanceof FinancialYearController) {
if ($this->commonFunctions->checkFinancialYear($this->dm) !== 0 ) {
return;
}
$this->session->getFlashBag()->add('error', 'OOPS!, YOU MUST CREATE FINANCIAL YEAR TO MAKE ANY PROCESS IN ACCOUNTS!');
$redirectUrl= $this->router->generate('financialyear');
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
}
}
//Services.xml
<service id="edu.account.listener" class="Edu\AccountBundle\EventListener\BeforeControllerListener">
<argument type="service" id="session"/>
<argument type="service" id="router"/>
<argument type="service" id="doctrine_mongodb.odm.document_manager"/>
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/>
</service>
Now when the Method is calling corectly very begining of any action of any controller, but it is calling for every controller of whole project, Instead I want it to call only in My Particular Module in my application.
Please guide what is missing in this.
Thanks in advance
It's quite normal that a kernel.controller event listener is called for every controller, it's the check inside the event listener that matters and that allows you for an early return if the controller does not match.
Your check does seem wrong though. You probably want to return if the controller is not the class you expect:
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if (!$controller[0] instanceof FinancialYearController) {
return;
}
if ($this->commonFunctions->checkFinancialYear($this->dm) !== 0 ) {
return;
}
$this->session->getFlashBag()->add('error', 'OOPS!, YOU MUST CREATE FINANCIAL YEAR TO MAKE ANY PROCESS IN ACCOUNTS!');
$redirectUrl= $this->router->generate('financialyear');
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
}
I get an Access Token from a social network help with HWIO Bundle and to redirect after service is called. I tried adding router to the service:
<argument type="service" id="router" />
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserProvider implements OAuthAwareUserProviderInterface
{
protected $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function connect(UserInterface $user, UserResponseInterface $response)
{
$service = $response->getResourceOwner()->getName();
$serviceProvider = $service."Provider";
$user = $this->$serviceProvider->setUserData($user, $response);
$grabProject = $this->grabProject->grabProject($response->getAccessToken(), $user);
return new RedirectResponse($this->router->generate('application_frontend_default_index'));
}
after my action I turn in controller HWIO Bundle in connectServiceAction
public function connectServiceAction(Request $request, $service)
{
Maybe need overwrite this controller and action, how to make this?
The controller does not redirect because you did not return the RedirectResponse returned by your method when it is called. If you want a quick fix to make it work, just replace:
$this->container->get('hwi_oauth.account.connector')->connect($currentUser, $userInformation);
by:
return $this->container->get('hwi_oauth.account.connector')->connect($currentUser, $userInformation);
However, as I told you in my comment: this should not be your service's responsibility to redirect. You should do that in your Controller, right after your connect call.
I am using Synfony2 with FOSUserBundle and I have a custom userChecker where I want to validate the host of the user (we have several hosts pointing to the same IP). My problem is that inside my custom userChecker I can't access REQUEST, thus not the HOST of the request.
This is my user checker code
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien#symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
//Override by Mattias
namespace BizTV\UserBundle\Controller;
//namespace Symfony\Component\Security\Core\User;
use Symfony\Component\Security\Core\Exception\CredentialsExpiredException;
use Symfony\Component\Security\Core\Exception\LockedException;
use Symfony\Component\Security\Core\Exception\DisabledException;
use Symfony\Component\Security\Core\Exception\AccountExpiredException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserChecker as OriginalUserChecker;
use Symfony\Component\HttpFoundation\Request as Request; //ADDED BY MW
/**
* UserChecker checks the user account flags.
*
* #author Fabien Potencier <fabien#symfony.com>
*/
class UserCheckerNew extends OriginalUserChecker
{
/**
* {#inheritdoc}
*/
public function checkPreAuth(UserInterface $user)
{
/*
//Test for companylock...
if ( !$user->getCompany()->getActive() ) {
throw new LockedException('The company of this user is locked.', $user);
}
if ( $user->getLocked() ) {
throw new LockedException('The admin of this company has locked this user.', $user);
}
*/
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isCredentialsNonExpired()) {
throw new CredentialsExpiredException('User credentials have expired.', $user);
}
}
/**
* {#inheritdoc}
*/
public function checkPostAuth(UserInterface $user)
{
//Test for companylock...
if ( !$user->getCompany()->getActive() ) {
throw new LockedException('The company of this user is locked.');
}
if ( $user->getLocked() ) {
throw new LockedException('The admin of this company has locked this user.');
}
/*
Validate HOST here
*/
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isAccountNonLocked()) {
throw new LockedException('User account is locked.', $user);
}
if (!$user->isEnabled()) {
throw new DisabledException('User account is disabled.', $user);
}
if (!$user->isAccountNonExpired()) {
throw new AccountExpiredException('User account has expired.', $user);
}
}
}
In the checkPostAuth function I tried different things like passing the request
public function checkPostAuth(UserInterface $user, Request $request)
Error saying my override must conform to the original/interface.
Trying to get the request as in a controller
$this->container->get('request_stack')->getCurrentRequest();
or like this
$currentHost = $request->getHost();
or like this
$cont = $this->getContainer();
or like this
$request = $this->getRequest();
or like this
$request = $container->get('request');
Yet no luck =) I'm no Symfony2 guru, as you can tell, I'm shooting from the hip here =)
Added parameters to config.yml according to gp-sflover's answer, my config.yml now looks like this:
services:
security.user_checker:
class: BizTV\UserBundle\Controller\UserCheckerNew
arguments: [ "#request" ]
scope: request
public: true
The error delivered before scope:request was added to the config was:
Scope Widening Injection detected: The definition "security.user_checker" references the service "request" which belongs to a narrower scope. Generally, it is safer to either move "security.user_checker" to scope "request" or alternatively rely on the provider pattern by injecting the container itself, and requesting the service "request" each time it is needed. In rare, special cases however that might not be necessary, then you can set the reference to strict=false to get rid of this error.'
When adding scope: request a very similar error is returned
Scope Widening Injection detected: The definition "security.authentication.provider.dao.main" references the service "security.user_checker" which belongs to a narrower scope. Generally, it is safer to either move "security.authentication.provider.dao.main" to scope "request" or alternatively rely on the provider pattern by injecting the container itself, and requesting the service "security.user_checker" each time it is needed. In rare, special cases however that might not be necessary, then you can set the reference to strict=false to get rid of this error
Adding public: true doesn't seem to make a difference. Also, I don't know what this public stuff really means, perhaps a security issue? The word public is always scary =)
Instead of extend the "OriginalUserChecker" class you can override the security.user_checker service to be able to inject the request_stack as argument and then retrieve it in your UserChecker class like this simple example:
service.xml
// Symfony >=2.6
<service id="security.user_checker"
class="Your\Bundle\Path\ToYour\UserCheckerClass">
<argument type="service" id="request_stack"/>
</service>
// Symfony <2.6
<service id="security.user_checker"
class="Your\Bundle\Path\ToYour\UserCheckerClass">
<argument type="service" id="request" public="true" scope="request"/>
</service>
UserCheckerClass
use Symfony\Component\Security\Core\User\UserCheckerInterface;
// Symfony >=2.6
use Symfony\Component\HttpFoundation\RequestStack;
// Symfony <2.6
use Symfony\Component\HttpFoundation\Request;
class UserChecker implements UserCheckerInterface
{
private $request;
public function __construct(
// Symfony >=2.6
RequestStack $request
// Symfony <2.6
Request $request
) {
$this->request = $request;
}
public function checkPreAuth(UserInterface $user)
{
// your checks here
}
public function checkPostAuth(UserInterface $user)
{
// your checks here
}
}
I never did get the request injection to work. I did however get an injection of the entire service container to work.
This is how I ended up doing it, with guidance from gp_sflover (if you post an answer with this code I will check yours as the correct answer, I don't want to steal cred, just get the truth out there ;] )
services:
security.user_checker:
#class: BizTV\UserBundle\Controller\UserChecker
class: BizTV\UserBundle\Controller\UserCheckerNew
arguments: ["#service_container"]
namespace BizTV\UserBundle\Controller;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\CredentialsExpiredException;
use Symfony\Component\Security\Core\Exception\LockedException;
use Symfony\Component\Security\Core\Exception\DisabledException;
use Symfony\Component\Security\Core\Exception\AccountExpiredException;
class UserCheckerNew implements UserCheckerInterface
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function checkPreAuth(UserInterface $user)
{
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isCredentialsNonExpired()) {
throw new CredentialsExpiredException('User credentials have expired.', $user);
}
}
/**
* {#inheritdoc}
*/
public function checkPostAuth(UserInterface $user)
{
//Validate HOST here, make it look as though account doesn't exist if on wrong host
$host = $this->container->get('request')->getHost();
if ($host != "localhost") { //bypass all checks when on localhost
$brandedHost = $user->getCompany()->getBrandedHost();
if ( $brandedHost == "" ) { //if unset assume main
$brandedHost = "login.mydomain.se";
}
if ( $host != $brandedHost ) {
throw new LockedException('Invalid username or password.'); //dot added for debug
}
}
// end of host validation
//Test for companylock...
if ( !$user->getCompany()->getActive() ) {
throw new LockedException('The company of this user is locked.');
}
if ( $user->getLocked() ) {
throw new LockedException('The admin of this company has locked this user.');
}
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isAccountNonLocked()) {
throw new LockedException('User account is locked.', $user);
}
if (!$user->isEnabled()) {
throw new DisabledException('User account is disabled.', $user);
}
if (!$user->isAccountNonExpired()) {
throw new AccountExpiredException('User account has expired.', $user);
}
}
}