0

How to override FOSUserBundle such that I may be able to register/authenticate a user based on the combination of:

Email ID + Password + GroupID and not just email + password

So the email will no longer be unique, but the combination of email + groupid will make the user unique (per group) and can have independent passwords.

I'm using FOSUserBundle v2.0.2 with Symfony 3.4.x

Example of fos_user table rows with valid login ids:

ID| EMAIL          | GROUP | PASSWORD
--+----------------+-------+---------
1 | u1@example.com |   10  | xxxxxxxx
2 | u1@example.com |   11  | xxxxxxxx
3 | u2@example.com |   10  | xxxxxxxx
4 | u2@example.com |   13  | xxxxxxxx
5 | u1@example.com |   12  | xxxxxxxx
Ren
  • 330
  • 6
  • 23

2 Answers2

0

With Symfony Guard creating a form-based check and login won't be too hard at all.

Here's some of the basic class required. Wiring it into the user security is shown on the Symfony help pages, and there are other blogposts about using it.

<?php
namespace App\Security;

use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
// other classes to be used

class UserGroupAuthenticator extends AbstractGuardAuthenticator
{
    // other required methods, see the examples

    // this guard is only supported if there is a 'group' form field
    public function supports(Request $request)
    {
        return $request->get->get('group');
    }

    public function getCredentials(Request $request)
    {
        return array(
            'email' => $request->get->get('email'),
            'group' => $request->get->get('group'),
            'password' => $request->get->get('password'),
        );
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $email = $credentials['email'];
        $group = $credentials['group'];

        // if a User object, checkCredentials() is called
        return $userProvider->loadUserByEmailsAndGroup($email, $group);
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        // check credentials - e.g. make sure the password is valid
        // ....

        // return true to cause authentication success
        return true;
    }
}
Alister Bulman
  • 34,482
  • 9
  • 71
  • 110
  • Is it not possible to just override some parts of FOSUserBundle to achieve this result? I would like to still use the forms and all features provided by FOS. – Ren Dec 18 '17 at 14:58
  • You should be able to when you've overridden the view to put the group into the form - this would be run instead of the internal check/login (if ->supports() == true), though you may want to dispatch events in the guard-class as well. – Alister Bulman Dec 18 '17 at 16:06
0

I did not want users to be able to choose their Group, and instead will be managed internally by the system. For registration, it was not much much to be done with FOS. All I needed was to override the User entity with new set of doctrine key constraints. I added the following in the User entity that extended the BaseUser:

/**
 * User.
 *
 * @ORM\Table(name="fos_user",
 *     uniqueConstraints={
 *          @UniqueConstraint(name="group_username_unique", columns={"group_id", "username_canonical"}),
 *          @UniqueConstraint(name="group_email_unique", columns={"group_id", "email_canonical"})
 *      }
 * )
 * @UniqueEntity(fields={"group", "usernameCanonical"}, errorPath="username", message="fos_user.username.already_used")
 * @UniqueEntity(fields={"group", "emailCanonical"}, errorPath="email", message="fos_user.email.already_used")
 * @ORM\Entity
 * @ORM\AttributeOverrides({
 *     @ORM\AttributeOverride(name="emailCanonical", column=@ORM\Column(type="string", length=180, nullable=false, unique=false)),
 *     @ORM\AttributeOverride(name="usernameCanonical",column=@ORM\Column(type="string", length=180, nullable=false, unique=false))
 * })
 */
class User extends BaseUser
{
    /* my entity contents here */
}

While for login, it was a little bit much of work. I had to override the UserProvider, UserManager, create CustomUserManager and add a set of new method with Group constraints:

public function findUserByUsernameOrEmailAndGroup($usernameOrEmail, $group)
{
    if (preg_match('/^.+\@\S+\.\S+$/', $usernameOrEmail)) {
        return $this->findUserByEmailAndGroup($usernameOrEmail, $group);
    }

    return $this->findUserByUsernameAndGroup($usernameOrEmail, $group);
}

public function findUserByEmailAndGroup($email, $group)
{
    return $this->findUserBy(['group' => $group, 'emailCanonical' => $email]);
}

public function findUserByUsernameAndGroup($username, $group)
{
    return $this->findUserBy(['group' => $group, 'usernameCanonical' => $username]);
}

public function findUserByConfirmationTokenAndGroup($token, $group)
{
    return $this->findUserBy(['confirmationToken' => $token, 'group' => $group]);
}

Further, I made the overrides on RegistrationController and ResettingController to adopt to the new changes for login and password reset functionalities.

Ren
  • 330
  • 6
  • 23