2

I am trying to make my own User entity which extends the SuluUser entity (so I can add some properties). After following the instructions from the Sulu documentation: Extend Entities I then created my own symfony authentication for the (front) website however, when I try to authenticate I get the following error.

Edit: added the imports and annotations for the user entity

An exception occurred while executing 'SELECT t1.username AS username_2, t1.password AS password_3, t1.locale AS locale_4, t1.salt AS salt_5, t1.locked AS locked_6, t1.enabled AS enabled_7, t1.lastLogin AS lastLogin_8, t1.confirmationKey AS confirmationKey_9, t1.passwordResetToken AS passwordResetToken_10, t1.passwordResetTokenExpiresAt AS passwordResetTokenExpiresAt_11, t1.passwordResetTokenEmailsSent AS passwordResetTokenEmailsSent_12, t1.privateKey AS privateKey_13, t1.apiKey AS apiKey_14, t1.email AS email_15, t1.id AS id_16, t1.firstname AS firstname_17, t1.lastname AS lastname_18, t1.phonenumber AS phonenumber_19, t1.gender AS gender_20, t1.password_changed_date AS password_changed_date_21, t1.confirmation_token AS confirmation_token_22, t1.idContacts AS idContacts_23 FROM user t1 WHERE t0.email = ? LIMIT 1' with params ["test@test.com"]:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.email' in 'where clause'

I'm not sure why it's using t0.email when the rest of the query uses t1 as an alias, but this breaks the login from the (front) website. Admin can login just fine for the sulu backend. I believe it has something to do with the inheritance with the SuluUser that my User entity extends. Any help would be very much appreciated. I have read about doctrine inheritance here but I don't think that applies to me as I cannot (should not) change the class in Sulu\Bundle\SecurityBundle\Entity\User. I have configured the following

App\Entity\User

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Sulu\Bundle\SecurityBundle\Entity\User as SuluUser;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @UniqueEntity(fields={"email"})
 */
class User extends SuluUser
{

    /**

     * @Groups({"get", "post", "put", "get-comment-with-author", "get-blog-post-with-author"})
     * @ORM\Column(type="string", length=25)
     * @Assert\NotBlank(groups={"post"})
     * @Assert\Length(min=4, max="100")
     */
    private $Firstname;

    /**
     * @Groups({"get", "post", "put"})
     * @ORM\Column(type="string", length=25)
     * @Assert\NotBlank(groups={"post"})
     * @Assert\Length(min=4, max="100")
     */
    private $Lastname;

    /**
     * @ORM\Column(type="string", length=10, nullable=true)
     *
     * @Groups({"get", "post", "put"})
     */
    private $phonenumber;

    /**
     * @ORM\Column(type="string", nullable=true)
     * @Groups({"get", "post", "put"})
     * @Assert\Collection()
     */
    private $gender;


    /**
     * @Groups({"post"})
     * @Assert\NotBlank(groups={"post"})
     * @Assert\Expression(
     *     "this.getPassword() === this.getRetypedPassword()",
     *     message="Passwords do not match"
     * )
     */
    private $retypedPassword;


    /**
     * @Assert\Length(min=10, max="100")
     * @Assert\NotBlank(groups={"put-reset-password"})
     * @Groups({"put-reset-password"})
     * @Assert\Regex(
     *     pattern="/(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{7,}/",
     *     message="Your password needs to be at least 10 characters long and contain the folloiwing"
     * )
     */
    private $newPassword;


    /**
     * @Groups({"put-reset-password"})
     * @Assert\NotBlank(groups={"put-reset-password"})
     * @Assert\Expression(
     *     "this.getNewPassword() === this.getNewRetypedPassword()",
     *     message="Passwords does not match",
     *     groups={"put-reset-password"}
     * )
     */
    private $newRetypedPassword;


    /**
     * @Groups({"put-reset-password"})
     * @Assert\NotBlank(groups={"put-reset-password"})
     * @UserPassword(groups={"put-reset-password"})
     */
    private $oldPassword;
    /**
     * @ORM\Column(type="integer", nullable=true)
     */
    private $passwordChangedDate;

    /**
     * @ORM\Column(type="string", length=40, nullable=true)
     */
    private $confirmationToken;


    public function __construct()
    {
        $this->confirmationToken = null;

    }


    public function getFirstname(): ?string
    {
        return $this->Firstname;
    }

    public function setFirstname( $Firstname): self
    {
        $this->Firstname = $Firstname;

        return $this;
    }

    public function getLastname(): ?string
    {
        return $this->Lastname;
    }

    public function setLastname( $Lastname): self
    {
        $this->Lastname = $Lastname;

        return $this;
    }

    public function getPhonenumber(): ?string
    {
        return $this->phonenumber;
    }

    public function setPhonenumber(?string $phonenumber): self
    {
        $this->phonenumber = $phonenumber;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getGender()
    {
        return $this->gender;
    }

    /**
     * @param mixed $gender
     */
    public function setGender($gender): void
    {
        $this->gender = $gender;
    }


    /**
     * @return mixed
     */
    public function getRetypedPassword()
    {
        return $this->retypedPassword;
    }

    /**
     * @param mixed $retypedPassword
     */
    public function setRetypedPassword($retypedPassword): void
    {
        $this->retypedPassword = $retypedPassword;
    }

    public function getNewPassword(): ?string
    {
        return $this->newPassword;
    }

    public function setNewPassword($newPassword): void
    {
        $this->newPassword = $newPassword;
    }

    public function getNewRetypedPassword(): ?string
    {
        return $this->newRetypedPassword;
    }

    public function setNewRetypedPassword($newRetypedPassword): void
    {
        $this->newRetypedPassword = $newRetypedPassword;
    }

    public function getOldPassword(): ?string
    {
        return $this->oldPassword;
    }

    public function setOldPassword($oldPassword): void
    {
        $this->oldPassword = $oldPassword;
    }

    public function getPasswordChangedDate()
    {
        return $this->passwordChangedDate;
    }

    public function setPasswordChangedDate($passwordChangedDate): void
    {
        $this->passwordChangedDate = $passwordChangedDate;
    }


    public function getConfirmationToken()
    {
        return $this->confirmationToken;
    }


    public function setConfirmationToken($confirmationToken): void
    {
        $this->confirmationToken = $confirmationToken;
    }



    public function __toString(): string
    {
        return $this->Firstname . ' ' . $this->Lastname;
    }

}

App\config\packges\security_website.yaml

security:
  encoders:
    App\Entity\User:
      algorithm: auto


  providers:
    app_user_provider:
      entity:
        class: App\Entity\User
        property: email
  firewalls:
    dev:
      pattern: ^/(_(profiler|wdt)|css|images|js)/
      security: false
    main:
      anonymous: false
      lazy: true
      provider: app_user_provider
      guard:
        authenticators:
          - App\Security\AppAuthenticator
      logout:
        path: app_logout
        # where to redirect after logout
        target: home

App\Security\AppAuthenticator

class AppAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
    use TargetPathTrait;

    public const LOGIN_ROUTE = 'app.login';

    private $entityManager;
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->urlGenerator = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    public function supports(Request $request)
    {
        return self::LOGIN_ROUTE === $request->attributes->get('_route')
            && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'email' => $request->request->get('email'),
            'password' => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['email']
        );

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);

        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('Email could not be found.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    /**
     * Used to upgrade (rehash) the user's password automatically over time.
     */
    public function getPassword($credentials): ?string
    {
        return $credentials['password'];
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        $role =  $token->getUser()->getRoles();

        // For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
       // throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
      //  return new RedirectResponse('admin');

                return new RedirectResponse('/');


    }

    protected function getLoginUrl()
    {
        return $this->urlGenerator->generate(self::LOGIN_ROUTE);
    }
}
True Saint
  • 41
  • 6

1 Answers1

1

Its very important when you override the Sulu entities that you set it on the same table name else the override of the entity does not work correctly so as in the documentation set the @ORM\Table with "se_users" and @ORM\Entity to it.

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sulu\Bundle\SecurityBundle\Entity\User as SuluUser;

/**
 * Following annotations are required and should not be changed:
 *
 * @ORM\Table(name="se_users")
 * @ORM\Entity
 */
class User extends SuluUser
{
}
Alexander Schranz
  • 2,154
  • 2
  • 25
  • 42
  • Sorry, please disregard my previous comment. I seem to have changed back to what I have now it while debugging and left it there. I have changed it to be exactly as the above answer however I now just get a doctrine error saying The table with name 'sulu-workshop.se_users' already exists – True Saint Nov 11 '20 at 15:21
  • Never mind once again. Thank you very much for pointing this out. This showed me the error of my ways. Very much appreciated sir! – True Saint Nov 11 '20 at 15:33
  • What was the solution @TrueSaint ? I'm currently struggling with the same behaviour but the configuration and the table configuration are in place. – berkyl May 02 '21 at 08:12
  • Ok, there seems to be a combination of package versions that produces the same "table already exists" error even though the attributes are set. After composer update, it works as expected. – berkyl May 02 '21 at 08:53