Symfony 3.4 - new security provider - symfony

I try add provider.What I'm doing wrong.
I have entity class, and I would like to extend this class
namespace AppBundle\Service;
use AppBundle\Entity\Pracownik;
use Symfony\Component\Security\Core\User\UserInterface;
class Userek extends Pracownik implements UserInterface
{
public function getUsername()
{
return $this->getLogin();
}
public function getSalt()
{
// you *may* need a real salt depending on your encoder
// see section on salt below
return null;
}
public function getPassword()
{
return $this->getLogin();
}
public function getRoles()
{
return array('ROLE_PRACOWNIK');
}
public function eraseCredentials()
{
}
public function isSuperAdmin()
{
return false;
}
}
in security.yml i add
encoders:
AppBundle\Service\Userek: plaintext
providers:
pracownik_db_provider:
entity:
class: AppBundle\Service\Userek
property: login
firewalls:
main:
pattern: ^/
form_login:
provider: pracownik_db_provider
login_path: /login
check_path: /login_check
logout:
path: /logout
target: /login
When I try login, i got message:
The class 'AppBundle\Service\Userek' was not found in the chain configured namespaces AppBundle\Entity, FOS\UserBundle\Model"
If I implements Class Pracownik adding methods from the implemented class without creating an additional new class extending the Pracownik class - works

Try to move your Userek class to the AppBundle\Entity\ folder, because Doctrine expects to find model classes on that folder Entity\.
Just move AppBundle\Service\Userek.php to AppBundle\Entity\Userek.php and come back if you still get the error message.
If you want Doctrine use another folder to store your model classes or entities, you can find some help to do that here or on the full documentation Doctrine configurations.

Related

How to use Symfony 4.1 Security / Authentication Providers without Doctrine / ORM

In my current app, we've chosen not to use Doctrine or an ORM. I'm attempting to use Symfony 4.1's authentication system to log people in. What I'd like to do is use PDO directly to fetch users from the database.
I'm following this guide: http://symfony.com/doc/current/security/custom_provider.html. In security.yaml, I've created a new provider entry and added it to the firewall:
providers:
db_provider:
id: App\Utility\Security\UserAuthenticationProvider
encoders:
App\Utility\Security\UserAuthenticationProvider: bcrypt
firewalls:
main:
pattern: ^/
form_login:
login_path: login
check_path: check_login
default_target_path: user
anonymous: ~
provider: db_provider
And I've created UserAuthenticationProvider:
class UserAuthenticationProvider implements UserProviderInterface {
private $config;
private $userDAO;
public function __construct() {
$this->config = new Config();
$this->userDAO = new MySqlUserDAO($this->config);
}
public function loadUserByUsername($username) {
$user = $this->userDAO->getUserByUsername();
...
return $user;
}
public function refreshUser(UserInterface $user) {
...
}
public function supportsClass($class) {
...
}
My userDAO returns an object that implements UserInterface.
So when I got to my route /login, I get my login form. However, no users are loaded from the database. I can see that my UserProviderInterface gets created (by using dump in the constructor), but loadUserByUsername does not.
Do I need to implement something else that uses my UserAuthenticationProvider and calls loadUserByUsername?
Is there perhaps a better way to do authentication in Symfony without using Doctrine?
UPDATE
I found this guide which is older but has a bit more detail / context.
I've changed my classes / configs like so (edited for brevity):
#security.yaml
security:
providers:
db_provider:
id: database_user_provider
main:
pattern: ^/
form_login:
provider: db_provider
login_path: login
check_path: check_login
default_target_path: do_some_stuff
.
#services.yaml
services:
database_user_provider:
class: App\Utility\Security\DatabaseUserProvider
.
class DatabaseUser
implements
UserInterface,
EquatableInterface
{
protected $user;
public function getUser(): User {
return $this->user;
}
public function setUser(User $user): void {
$this->user = $user;
}
public function getRoles() {
return array("ROLE_USER");
}
public function getPassword() {
return $this->getUser()->getPassword();
}
public function getUsername() {
return $this->getUser()->getUsername();
}
}
.
class DatabaseUserProvider implements UserProviderInterface {
private $config;
private $userDAO;
public function __construct() {
$this->config = new Config();
$this->userDAO = new MySqlUserDAO($this->config);
}
public function loadUserByUsername($username): UserInterface {
$user = $this->userDAO->getUserByUsername($username);
$dbUser = new DatabaseUser();
$dbUser->setUser($user);
return $dbUser;
}
public function refreshUser(UserInterface $user) {
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class) {
return DatabaseUser::class === $class;
}
}
So what each file is doing (in a nutshell is):
services.yaml: naming my class DatabaseUserProvider to the service name database_user_provider for use in security.yaml.
security.yaml: setting database_user_provider to the alias db_provider and adding that as the provider on the main firewall.
DatabaseUser: my 'entity' class to represent my user from the database. I have a dumb User class that just has a few properties (username, password) and getters / setters for those. It is set as a property for the DatabaseUser object.
DatabaseUserProvider: Loads my DatabaseUser objects from the database using the DAO and returns them. (more specifically, the DAO returns a user, which is added to a new DatabaseUser object, which is returned).
The DAO simply runs a sql query to get a single result from the user table. it then takes this result and populates a User Value Object and returns it.
Results
When I was using Doctrine (and following this guide for loading users from the database and this one for a login form), the login route on the SecurityController would handle both rendering of the form and processing of the login request. Somehow, Symfony / Doctrine was smart enough to automatically (using a listener maybe???) load the appropriate user entity from the DB and authenticate them against the password they provided (and then set the user token and redirect them to the page they were trying to access.)
When I bypass doctrine (and use my own DAOs) and the classes above, authentication still does not occur. I can see that an instance of DatabaseUserProvider is being created (by dumping some vars in the constructor), and the Security tab of the profilier shows database_user_provider as the provider. But that seems to be as far as it gets.
Question
It seems to me that DatabaseUserProvider::loadUserByUsername should be the next thing that should happen. From where does this method get called from? Do I need to be passing the username into the constructor and kick it off from there? Should I be using this class as a service in my Controller and call the method manually from the controller (something I did not have to do when using doctrine - none of this logic was in the controller)?

Throwing an AuthenticationException but access to secured area still allowed

Our legacy application has been updated to be using Symfony 2.7 and simple_form authentication seems to work fine. Now trying to add some more logic into the custom authenticator class and receiving some unexpected behavior.
Here is what happens:
The user can login with the correct credentials.
When incorrect credentials are provided, the user is redirected back to the login path, with the appropriate error shown on the form.
The problem: In the custom authenticator class we check a condition and then throw an AuthenticationException. However the login is still successful and the user receives the role of "USER".
The question is: Is this the expected behavior?
The reason I ask is that we have this legacy PHP app that has been converted to Symfony and I was not involved in this initial conversion process. So it is possible that the authentication system may have been altered in order to facilitate this process, but I am unable to track down what is going on.
So far I have used this page as a reference, and tried the suggestions to create a custom exception listener, calling pre-/post-auth methods on the UserChecker, and several other alternatives. None have been able to alleviate the behavior of the "ignoring" of the AuthenticationException thrown in the custom authenticator's authenticateToken method.
# security.yml
security:
providers:
our_db_provider:
entity:
class: AccountBundle:User
firewalls:
default:
anonymous: ~
http_basic: ~
provider: our_db_provider
simple_form:
authenticator: our_authenticator
login_path: /secure/login/
check_path: /secure/login_check/
main:
anonymous: ~
access_control:
- { path: ^/secure/, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
- { path: ^/account/, roles: ROLE_USER, requires_channel: https }
# custom authenticator class
class OurAuthenticator implements SimpleFormAuthenticatorInterface
{
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
throw new AuthenticationException('$error');
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof UsernamePasswordToken && $token->getProviderKey() === $providerKey;
}
public function createToken(Request $request, $username, $password, $providerKey)
{
return new UsernamePasswordToken($username, $password, $providerKey);
}
}
As a note, sadly we do not have access to the Symfony profiler in this app.

Persist failed authentication login attempts in Symfony2

I am using Symfony2 and need to save the failed login attempts to the database.
I have the following methods which I think I should be using:
// Symfony/Component/Security/Http/Autentication/AbstractAuthenticationListener.php
onFailure()
onSuccess()
But I am not sure how to access the database connection from within them.
How could run a database insert from within these functions?
You need to define your own failure handler in security.yml
form_login:
provider: fos_userbundle
login_path: /user/login
csrf_provider: form.csrf_provider
failure_handler: myBundle.login.failure
Create a service to handle the failures
bundle.login.failure:
class: 'MyBundle\Services\AuthenticationFailureHandler'
arguments: ['#kernel']
Then build your failure handler:
<?php
namespace MyBundle\Services;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler
{
public function __construct(HttpKernelInterface $httpKernel)
{
$this->httpKernel = $httpKernel;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// the auth just failed :-(
}
}
To save attempts to the database inject your Doctrine manager into the service and persist the attempt from within the onFail method.
Just for logging there is an easy way by using security.authentication.failure listener. See Symfony documentation. You could use this blog post on how to persist logs to the actual database.
namespace AppBundle\EventListener;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
class AuthenticationFailureListener implements LoggerAwareInterface
{
use LoggerAwareTrait;
public function onFailure(AuthenticationFailureEvent $event)
{
$token = $event->getAuthenticationToken();
$username = $token->getUsername();
$this->logger->info('Authentication failed', ['username' => $username]);
}
}
And in services.yml
app.authentication_failure_listener:
class: AppBundle\EventListener\AuthenticationFailureListener
calls:
- [setLogger, ['#logger']]
tags:
- { name: monolog.logger, channel: security }
- { name: kernel.event_listener, event: security.authentication.failure, method: onFailure }

AuthenticationSuccessHandlerInterface and correct redirect strategy

I have a login success_handler which redirects some users to a special form.
Whatever, the AuthenticationSuccessHandlerInterface needs to return a Response and is working well except one case. if a user first fill in his credentials wrong and gets redirected to login page again, the handler redirects him AGAIN to login page after correct login.
if i simply use the option use_referer: true it works correct. So i could put the logic to the controller instead of an event but maybe someone of you guys has a solution for me.
thank you
firewall
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
success_handler: applypie_userbundle.login.handler
#default_target_path: applypie_user_dashboard
use_referer: true
logout: true
anonymous: true
event
namespace Applypie\Bundle\UserBundle\EventListener;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
class NewUserListener implements AuthenticationSuccessHandlerInterface
{
protected $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
if(!$user->getApplicant() && !count($user->getCompanies())) {
return new RedirectResponse($this->router->generate('applypie_user_applicant_create'));
}
return new RedirectResponse($request->headers->get('referer'));
}
}
service
applypie_userbundle.login.handler:
class: Applypie\Bundle\UserBundle\EventListener\NewUserListener
arguments: ["#router"]
I'm glad to see that you've accomplished the redirect stuff.
You don't need the use_referer since it'll be the login page URI (because it's the real referer sent by the browser in the headers), just take the URI the user tried to reach from the session:
return new RedirectResponse($request->getSession()->get('_security.main.target_path'));
more about this here:
http://symfony.com/doc/current/cookbook/security/target_path.html
If you are in dev mode (app_dev.php), you'll find a lot of great information, like session values for every request, take a look at the dev toolbar.

When the session expires, how can I force the user logout?

I'm trying to force the user when the session has expired becomes a logout but I can not access the time of the session
namespace mio\mioBundle;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpFoundation\Request;
class RequestListener{
protected $router;
protected $security;
public function __construct(RouterInterface $router, SecurityContext $security)
{
$this->router = $router;
$this->security = $security;
}
public function onKernelRequest(GetResponseEvent $event)
{
echo $event->getRequest()->getSession()->('timeout');
}
}
hello here I leave the configuration file security.yml.
security:
firewalls:
frontend:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
default_target_path: /index
success_handler: authentication_handler
logout:
path: /logout
target: /login
success_handler: authentication_handler
security: true
remember_me:
key: loksea
lifetime: 1800
path: /
access_denied_handler: accessdenied_handler
#primero deben de ir los usuarios anonimos si no se entra en loop redirect
access_control:
- { path: /login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/pruebita, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/js, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_A }
- { path: ^/nuevoinforme, roles: ROLE_M }
- { path: ^/, roles: IS_AUTHENTICATED_REMEMBERED }
providers:
user_db:
entity: { class: mio\mioBundle\Entity\Empleado, property: username }
role_hierarchy:
ROLE_M: ROLE_U
ROLE_A: ROLE_U
encoders:
mio\mioBundle\Entity\Empleado: { algorithm: sha1 }
Symfony\Component\Security\Core\User\User: plaintext
When the session ends asks me to login again, but not user logout. I have a listener to save the logout so:
public function onLogoutSuccess(Request $request){
$empleado = $this->security->getToken()->getUser();
$log = new Log();
$log->setFechalog(new \DateTime('now'));
$log->setTipo("Salida");
$log->setEmpleado($empleado);
$this->em->persist($log);
$this->em->flush();
}
Would you call this method when the session ends? thanks.
Tell me if I'm right, do you need to execute your method "onLogoutSuccess" when the user logs out ?
So the logout process works well, right ?
To logout explicitly, have you tried the "clear()" method of session object ?
I have had the same problem, but I managed to create a listener that throws a CredentialsExpiredException when the user reached the maximum idle time.
Users that have been idle for too long will be redirected to the login/logout page (for your situation its "/login" by looking at your logout target).
This is how I resolved the problem.
namespace mio\mioBundle;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Security\Core\Exception\CredentialsExpiredException;
class RequestListener{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function onKernelRequest(GetResponseEvent $event)
{
$session = $this->container->get('session');
$maxTime = 5*60; //5 minutes is the maximum lifetime
// Get the current idle time and compare it with the max allowed time
if (time() - $session->getMetadataBag()->getLastUsed() > $maxTime) {
//Invalidate the current session and throw an exception
$session->invalidate();
throw new CredentialsExpiredException();
}
}
}
This should be it. If you have some additional questions please let me know!
You need to configure this behaviour in your security.yml config file, and it should work automatically.
VoilĂ 

Resources