mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
add email to user and allow to connect through email or username
This commit is contained in:
parent
45a9937dbb
commit
25d00877ae
@ -22,6 +22,24 @@ class User implements AdvancedUserInterface {
|
||||
*/
|
||||
private $username;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $usernameCanonical;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $email;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $emailCanonical;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
@ -115,9 +133,47 @@ class User implements AdvancedUserInterface {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getUsernameCanonical()
|
||||
{
|
||||
return $this->usernameCanonical;
|
||||
}
|
||||
|
||||
public function getEmail()
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getEmailCanonical()
|
||||
{
|
||||
return $this->emailCanonical;
|
||||
}
|
||||
|
||||
public function setUsernameCanonical($usernameCanonical)
|
||||
{
|
||||
$this->usernameCanonical = $usernameCanonical;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEmail($email)
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEmailCanonical($emailCanonical)
|
||||
{
|
||||
$this->emailCanonical = $emailCanonical;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
function setPassword($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ class UserType extends AbstractType
|
||||
{
|
||||
$builder
|
||||
->add('username')
|
||||
->add('email')
|
||||
;
|
||||
if ($options['is_creation']) {
|
||||
$builder->add('plainPassword', UserPasswordType::class, array(
|
||||
|
@ -14,6 +14,21 @@ Chill\MainBundle\Entity\User:
|
||||
username:
|
||||
type: string
|
||||
length: 80
|
||||
usernameCanonical:
|
||||
name: username_canonical
|
||||
type: string
|
||||
length: 80
|
||||
unique: true
|
||||
email:
|
||||
type: string
|
||||
length: 150
|
||||
nullable: true
|
||||
emailCanonical:
|
||||
name: email_canonical
|
||||
type: string
|
||||
length: 150
|
||||
nullable: true
|
||||
unique: true
|
||||
password:
|
||||
type: string
|
||||
length: 255
|
||||
|
@ -9,4 +9,9 @@ services:
|
||||
|
||||
chill.main.role_provider:
|
||||
class: Chill\MainBundle\Security\RoleProvider
|
||||
|
||||
chill.main.user_provider:
|
||||
class: Chill\MainBundle\Security\UserProvider\UserProvider
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
|
@ -5,3 +5,9 @@ services:
|
||||
- "@chill.main.security.authorization.helper"
|
||||
tags:
|
||||
- { name: "validator.constraint_validator" }
|
||||
|
||||
Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername:
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
tags:
|
||||
- { name: "validator.constraint_validator" }
|
||||
|
@ -16,9 +16,12 @@ Chill\MainBundle\Entity\User:
|
||||
- Length:
|
||||
max: 70
|
||||
min: 3
|
||||
email:
|
||||
- Email: ~
|
||||
constraints:
|
||||
- Callback:
|
||||
callback: isGroupCenterPresentOnce
|
||||
- \Chill\MainBundle\Validation\Constraint\UserUniqueEmailAndUsernameConstraint: ~
|
||||
|
||||
Chill\MainBundle\Entity\RoleScope:
|
||||
constraints:
|
||||
|
87
Resources/migrations/Version20180709181423.php
Normal file
87
Resources/migrations/Version20180709181423.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Application\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* add username and username canonical, email and email canonical columns
|
||||
* to users
|
||||
*/
|
||||
final class Version20180709181423 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema) : void
|
||||
{
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||
|
||||
$this->addSql('ALTER TABLE users ADD usernameCanonical VARCHAR(80) DEFAULT NULL');
|
||||
$this->addSql('UPDATE users SET usernameCanonical=LOWER(UNACCENT(username))');
|
||||
$this->addSql('ALTER TABLE users ALTER usernameCanonical DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE users ALTER usernameCanonical SET DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE users ADD email VARCHAR(150) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE users ADD emailCanonical VARCHAR(150) DEFAULT NULL');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9F5A5DC32 ON users (usernameCanonical)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9885281E ON users (emailCanonical)');
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE OR REPLACE FUNCTION canonicalize_user_on_update() RETURNS TRIGGER AS
|
||||
$BODY$
|
||||
BEGIN
|
||||
IF NEW.username <> OLD.username OR NEW.email <> OLD.email OR OLD.emailcanonical IS NULL OR OLD.usernamecanonical IS NULL THEN
|
||||
UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$BODY$ LANGUAGE PLPGSQL
|
||||
SQL
|
||||
);
|
||||
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TRIGGER canonicalize_user_on_update
|
||||
AFTER UPDATE
|
||||
ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE canonicalize_user_on_update();
|
||||
SQL
|
||||
);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE OR REPLACE FUNCTION canonicalize_user_on_insert() RETURNS TRIGGER AS
|
||||
$BODY$
|
||||
BEGIN
|
||||
UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$BODY$ LANGUAGE PLPGSQL;
|
||||
SQL
|
||||
);
|
||||
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TRIGGER canonicalize_user_on_insert
|
||||
AFTER INSERT
|
||||
ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE canonicalize_user_on_insert();
|
||||
SQL
|
||||
);
|
||||
}
|
||||
|
||||
public function down(Schema $schema) : void
|
||||
{
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||
|
||||
$this->addSql('DROP INDEX UNIQ_1483A5E9F5A5DC32');
|
||||
$this->addSql('DROP INDEX UNIQ_1483A5E9885281E');
|
||||
$this->addSql('ALTER TABLE users DROP usernameCanonical');
|
||||
$this->addSql('ALTER TABLE users DROP email');
|
||||
$this->addSql('ALTER TABLE users DROP emailCanonical');
|
||||
$this->addSql('DROP TRIGGER canonicalize_user_on_insert ON users');
|
||||
$this->addSql('DROP FUNCTION canonicalize_user_on_insert()');
|
||||
$this->addSql('DROP TRIGGER canonicalize_user_on_update ON users');
|
||||
$this->addSql('DROP FUNCTION canonicalize_user_on_update()');
|
||||
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
{{ form_start(edit_form) }}
|
||||
|
||||
{{ form_row(edit_form.username) }}
|
||||
{{ form_row(edit_form.email) }}
|
||||
{{ form_row(edit_form.enabled, { 'label': "User'status"}) }}
|
||||
|
||||
{{ form_widget(edit_form.submit, { 'attr': { 'class' : 'sc-button green center' } } ) }}
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.username) }}
|
||||
{{ form_row(form.email) }}
|
||||
{{ form_row(form.plainPassword.password) }}
|
||||
{{ form_widget(form.submit, { 'attr' : { 'class': 'sc-button blue' } }) }}
|
||||
{{ form_end(form) }}
|
||||
|
83
Security/UserProvider/UserProvider.php
Normal file
83
Security/UserProvider/UserProvider.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\MainBundle\Security\UserProvider;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class UserProvider implements UserProviderInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
|
||||
public function loadUserByUsername($username): UserInterface
|
||||
{
|
||||
$user = $this->em->createQuery(sprintf(
|
||||
"SELECT u FROM %s u "
|
||||
. "WHERE u.usernameCanonical = UNACCENT(LOWER(:pattern)) "
|
||||
. "OR "
|
||||
. "u.emailCanonical = UNACCENT(LOWER(:pattern))",
|
||||
User::class))
|
||||
->setParameter('pattern', $username)
|
||||
->getSingleResult();
|
||||
|
||||
if (NULL === $user) {
|
||||
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function refreshUser(UserInterface $user): UserInterface
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
throw new UnsupportedUserException("Unsupported user class: cannot reload this user");
|
||||
}
|
||||
|
||||
$reloadedUser = $this->em->getRepository(User::class)->find($user->getId());
|
||||
|
||||
if (NULL === $reloadedUser) {
|
||||
throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId()));
|
||||
}
|
||||
|
||||
return $reloadedUser;
|
||||
}
|
||||
|
||||
public function supportsClass($class): bool
|
||||
{
|
||||
return $class === User::class;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\MainBundle\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class UserUniqueEmailAndUsernameConstraint extends Constraint
|
||||
{
|
||||
public $messageDuplicateUsername = "A user with the same or a close username already exists";
|
||||
public $messageDuplicateEmail = "A user with the same or a close email already exists";
|
||||
|
||||
public function validatedBy()
|
||||
{
|
||||
return UserUniqueEmailAndUsername::class;
|
||||
}
|
||||
|
||||
public function getTargets()
|
||||
{
|
||||
return [ self::CLASS_CONSTRAINT ];
|
||||
}
|
||||
}
|
95
Validation/Validator/UserUniqueEmailAndUsername.php
Normal file
95
Validation/Validator/UserUniqueEmailAndUsername.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\MainBundle\Validation\Validator;
|
||||
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class UserUniqueEmailAndUsername extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!$value instanceof User) {
|
||||
throw new \UnexpectedValueException("This validation should happens "
|
||||
. "only on class ".User::class);
|
||||
}
|
||||
|
||||
$countUsersByUsername = $this->em->createQuery(
|
||||
sprintf(
|
||||
"SELECT COUNT(u) FROM %s u "
|
||||
. "WHERE u.usernameCanonical = LOWER(UNACCENT(:username)) "
|
||||
. "AND u != :user",
|
||||
User::class)
|
||||
)
|
||||
->setParameter('username', $value->getUsername())
|
||||
->setParameter('user', $value)
|
||||
->getSingleScalarResult();
|
||||
|
||||
if ($countUsersByUsername > 0) {
|
||||
$this->context
|
||||
->buildViolation($constraint->messageDuplicateUsername)
|
||||
->setParameters([
|
||||
'%username%' => $value->getUsername()
|
||||
])
|
||||
->atPath('username')
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
|
||||
$countUsersByEmail = $this->em->createQuery(
|
||||
sprintf(
|
||||
"SELECT COUNT(u) FROM %s u "
|
||||
. "WHERE u.emailCanonical = LOWER(UNACCENT(:email)) "
|
||||
. "AND u != :user",
|
||||
User::class)
|
||||
)
|
||||
->setParameter('email', $value->getEmail())
|
||||
->setParameter('user', $value)
|
||||
->getSingleScalarResult();
|
||||
|
||||
if ($countUsersByEmail > 0) {
|
||||
$this->context
|
||||
->buildViolation($constraint->messageDuplicateEmail)
|
||||
->setParameters([
|
||||
'%email%' => $value->getEmail()
|
||||
])
|
||||
->atPath('email')
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user