0
votes

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 | [email protected] |   10  | xxxxxxxx
2 | [email protected] |   11  | xxxxxxxx
3 | [email protected] |   10  | xxxxxxxx
4 | [email protected] |   13  | xxxxxxxx
5 | [email protected] |   12  | xxxxxxxx
2

2 Answers

0
votes

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;
    }
}
0
votes

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.