I want to create custom user checker to validate login action against last accepted eula.
'
Idea is quite simple, there will be many versions of eula and user can't login untill he accept the lastest eula.
Scenario is:
User creates new account and accepts eula.
Eula gets updated
User tries to login, but he didnt accept lastest eula
User gets the same login form but with additional field "accept newest eula"
User logs in and system inserts information: Current date and time, User id, Eula id to keep track of eula acceptance.
I found this:
https://groups.google.com/forum/#!msg/symfony2/D0V0bFks9S0/Qg9mrbpfB3IJ
But unfortunately there is no full version of custom user checker. How do I implement the rest of it?
The answer is actually quite obvious.
In your custom bundle:
config.yml
parameters:
security.user_checker.class: Acme\Bundle\UserBundle\Security\UserChecker
Userchecker:
class UserChecker extends BaseUserChecker
{
/**
* {#inheritdoc}
*/
public function checkPreAuth(UserInterface $user)
{
//do your custom preauth stuff here
parent::checkPreAuth($user);
}
}
You can redefine Symfony's user checker service (security.user_checker) in your bundle:
# services.yml
security.user_checker:
class: MyBundle\Checker\UserChecker
arguments: [ "#some.service", "%some.param%" ]
Then extend Symfony's UserChecker:
# UserChecker.php
use Symfony\Component\Security\Core\User\UserChecker as BaseUserChecker;
class UserChecker extends BaseUserChecker
{
public function checkPreAuth(UserInterface $user)
{
parent::checkPreAuth($user);
// your stuff
}
public function checkPostAuth(UserInterface $user)
{
parent::checkPostAuth($user);
// your stuff
}
}
The security.user_checker service gets called in \Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider::authenticate() during the authentication process
Why not attach event listener to the kernel.request event and watch if the current logged user has accepted the latest EULA?
To get the current user you can use something like this:
$securityContext = $this->container->get('security.context');
if (!$securityContext) {
return;
}
$user = $securityContext->getToken()->getUser();
Related
I am looking for the right way on how to check, if a user is logged in, in the Shopware 6 storefront. I am writing a plugin (not an app), and want to use this in Controllers and/or Subscribers.
Should I:
Use the Storefront API? (but how? which path?)
Use the default symfony way? (isGranted) - but with which Roles? Isn't the role handling different?
Use some built-in functionality like a special service that I can fetch by Dependeny Injection (but which one?)?
Solution:
Thanks to #Uwe Kleinmann, I found a solution, that works in a subscriber like this:
public static function getSubscribedEvents()
{
return [
ProductPageLoadedEvent::class => 'onProductPageLoaded'
];
}
public function onProductPageLoaded(ProductPageLoadedEvent $event): void
{
$saleschannelContext = $event->getSaleschannelContext();
$customer = $saleschannelContext->getCustomer();
if(NULL === $customer) {
$customer = 'not-logged-in';
}
$event->getPage()->addExtension(
'myextension', new ArrayStruct([
'test' => $customer
])
);
}
The SalesChannelContext has a $customer (accessible with getCustomer()) attribute. This context is usually injected into both Storefront controllers and subscribers for any Storefront events.
It is only set, if the current user is logged-in.
You may also use the _loginRequired and _loginRequiredAllowGuest flags in the #Route annotation of a storefront controller's method. This is handy if you only want to allow access for logged in customers as this will automatically redirect logged out users to the login page and back to the origin after they logged in.
/**
* #Route("/my/custom/page", name="frontend.custom.page", methods={"GET"}, defaults={"_loginRequired"=true, "_loginRequiredAllowGuest"=true})
*/
In Sylius (Symfony3 bundle), I have customized the register form to add some fields, in particular 'type of account' (pro or private). According to the type, some functionalities will not be enabled. In order to do that, I was thinking about giving users different roles.
As the authentication is made by Sylius, I was wondering how to override the default behavior to set the role according to the type data ?
Thanks for your help !
Sylius has no built-in roles or rbac system - whole security configuration is done with standard Symfony security system. So if you need to differentiate functionalities based on User role, just base on $roles parameter from User model, and override Sylius security configuration with your custom firewalls, as it's said in Symfony tutorial. Hope it will help ;)
What I have done :
In Sylius, there is an event sylius.customer.post_register fired after registration. I have created a listener (defined in services.yml) :
app.registration_listener:
class: AppBundle\EventListener\RegistrationListener
tags:
- { name: kernel.event_listener, event: sylius.customer.post_register, method: setUserRole }
arguments:
- "#sylius.manager.shop_user"
The ShopUserManager is passed as an argument to the setUserRole method.
public function __construct(ObjectManager $userManager) {
$this->userManager = $userManager;
}
In the listener, I get the $user object as the 'subject' of the event :
public function setUserRole(GenericEvent $event)
{
$customer = $event->getSubject();
$user = $customer->getUser();
....
$this->userManager->persist($user);
$this->userManager->flush();
}
Then I can modify the $user (add my role) and save it with the ShopUserManager.
I have an AuthController, where I have extended the getCredentials method so the user needs to be both existant and active to be able to login. This is the method:
protected function getCredentials(Request $request) {
$credentials = $request->only($this->loginUsername(), 'password');
return array_add($credentials, 'status', '1');
}
I would also like the failed login messages to be different, depending on whether or not the user is active, so the user knows if he is failing his username / password, or just because he hasn't activated his account yet.
I could override the login method of the AuthenticatesUser trait, but it seems overkill to duplicate all the logic just to change that.
Can I extend the sendFailedLoginResponse method to make some sort of validation there, based on the previous Auth::guard->attempt() call? I mean, does that method leave any information behind that allows me to know, after the call has been made, what made the attempt return false?
Or how would I approach this, without having to override a method completely just to make a simple validation?
Thank you.
public function authenticated(Request $request, User $user ) {
// The user was authenticated check if it the active column is true or not
if($user->active == false){
//Store some flash message here saying he's not account is not active
//Log him out after.
Auth::logout();
}
return redirect()->intended( $this->redirectPath() );
}
In Laravel 5.2, i need to do something while the authentication process is being triggered. (Not "after" the login.)
Here are what i have done.
In EventServiceProvider.php:
protected $listen = [
'Illuminate\Auth\Events\Attempting' => ['App\Listeners\UserLoginAttempt#handle'],
];
In app/Listeners/UserLoginAttempt.php:
<?php
namespace App\Listeners;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Login;
use Carbon\Carbon;
use Auth;
class UserLoginAttempt
{
public function __construct()
{
}
public function handle($event)
{
dd(Auth::user());
exit;
}
}
This always returns null.
** In fact, i have one "custom" field in the Login form, of which i need to capture from the above Event Handler. (I need to check something before actual Login event is done.)
How do i capture the Auth::user() from this (Illuminate\Auth\Events\Attempting) Handler please?
(In other words) How do i talk with the Login Form from the (Illuminate\Auth\Events\Attempting) Handler?
OR ----- Is the Auth::user() object created ONLY AFTER the login is fully processed? Which means i can not get it while in "attempting" stage?
Because i need to capture the login elements first, and if it is something "unauthorized" (after some calculations done in Handler), then i need to terminate the login attempt and route back to the login form from here.. by carrying the Failing Message along.
Please let me know what i'm missing here. Thank you.
According to documentation you are right that Auth::user() object is only created when user is authenticated.
Inside the Illuminate\Auth\Events\Attempting listener;
you can capture the form field by
public function handle(Attempting $event)
{
//return dd($event); // will echo all fields
$event->credentials['email']; // capturing the email field
}
you can also use the User model and then access the user table in DB, to get / check a user field in DB...
The question's title resumes pretty much: where do I verify authorization for a Command?
For example, setting a customer as preferred involves:
MarkAsPreferred controller action (could be Winforms or whatever);
SetCustomerAsPreferredCommand;
SetCustomerAsPreferredCommandHandler;
Customer.MarkAsPreferred() (domain);
I identified 3 places to check for authorization:
UI for displaying purposes (user should not see a link/button if he/she does not have access to it);
controller action to verify the user is authorized to call that command; commands are assumed to always succeed (regarding validation, but I'm assuming authorization too) and we have a chance to inform the user about lack of access;
inside the command just before calling domain logic;
SomeView.cshtml
if (authorizationService.Authorize("MarkCustomerAsPreferred))
{
// show link
}
CustomerController
[HttpPost]
public ActionResult MarkAsPreferred(Guid id)
{
if (!authorizationService.Authorize("MarkCustomerAsPreferred))
{
return RedirectToAction("Unauthorized");
}
var MarkCustomerAsPreferredCommand { Id = id };
...
}
MarkCustomerAsPreferredCommandHandler
public void Handle(MarkCustomerAsPreferredCommand command)
{
if (!authorizationService.Authorize("MarkCustomerAsPreferred"))
{
throw new Exception("...");
}
customer.MarkAsPreferred();
}
My question is: Do I need to verify authorization in 3 places or I'm just being overzealous?
I searched all over the internet but couldn't find any example or reference about this.
Edit
After more research and some tests I think wrapping the commands to add behavior (authorization, validation, logging) as Dennis Taub suggested is easier and cleaner to implement.
I found this blog post which explains exactly this concept.
About having multiple handlers for one command, I don't need to implement one command handler for each behavior for each original command, one wrapping command can wrap all handlers.
I think final authorization should be done on the application service level, i.e. as part of handling the command. You could wrap the command handler with an authorization handler for example.
class AuthorizationHandler : IHandle<SetCustomerAsPreferred> {
IHandle<SetCustomerAsPreferred> innerHandler;
public AuthorizationHandler(IHandle<SetCustomerAsPreferred> handler)
{
innerHandler = handler;
}
public void Handle(SetCustomerAsPreferred command)
{
if (/* not authorized */)
throw ...
innerHandler.Handle(command);
}
}
class SetCustomerAsPreferredCommandHandler : IHandle<SetCustomerAsPreferred> {
public void Handle(SetCustomerAsPreferred command)
{
// do the work
}
}
It's good UI to have that verification in the View, so the user won't click it by mistake. I consider the controller verification the 'real' one, because there is where the command is created. If an user doesn;t have the rights, she shouldn't be able to create (or even reach that action) the command.
I think that putting the check in the handler is a bit overzelous, as it's not its responsibility to do authorization and is not like that handler can be reached by an user directly.