Integrated FOSUserBundle (#216)

This commit is contained in:
Kevin Papst
2018-07-21 22:36:36 +02:00
committed by GitHub
parent 2d2525cff2
commit 75246e9db2
77 changed files with 1413 additions and 514 deletions

View File

@@ -104,6 +104,7 @@ class CreateUserCommand extends Command
$user->setUsername($username)
->setPlainPassword($password)
->setEmail($email)
->setEnabled(true)
->setRoles(explode(',', $role))
;

View File

@@ -459,7 +459,7 @@ class KimaiImporterCommand extends Command
->setAlias($oldUser['alias'])
->setEmail($oldUser['mail'])
->setPlainPassword($password)
->setActive($isActive)
->setEnabled($isActive)
->setRoles([$role])
;

View File

@@ -72,6 +72,7 @@ class UserController extends AbstractController
$password = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$user->setEnabled(true);
$user->setRoles([User::DEFAULT_ROLE]);
$entityManager = $this->getDoctrine()->getManager();

View File

@@ -1,46 +0,0 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as SymfonyAbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
/**
* Controller used to manage the application security.
* See http://symfony.com/doc/current/cookbook/security/form_login_setup.html.
*/
class SecurityController extends SymfonyAbstractController
{
/**
* @Route("/login", name="security_login")
*/
public function login(AuthenticationUtils $helper): Response
{
return $this->render('security/login.html.twig', [
'last_username' => $helper->getLastUsername(),
'error' => $helper->getLastAuthenticationError(),
]);
}
/**
* This is the route the user can use to logout.
*
* But, this will never be executed. Symfony will intercept this first
* and handle the logout automatically. See logout in config/packages/security.yaml
*
* @Route("/logout", name="security_logout")
*/
public function logout(): void
{
throw new \Exception('This should never be reached!');
}
}

View File

@@ -70,7 +70,7 @@ class AppFixtures extends Fixture
->setEmail($userData[3])
->setRoles([$userData[4]])
->setAvatar($userData[5])
->setActive($userData[6])
->setEnabled($userData[6])
->setPassword($passwordEncoder->encodePassword($user, self::DEFAULT_PASSWORD))
;

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Doctrine;
use Doctrine\Migrations\AbstractMigration as BaseAbstractMigration;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for all Doctrine migrations.
*/
abstract class AbstractMigration extends BaseAbstractMigration implements ContainerAwareInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @param ContainerInterface $container
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* @return ContainerInterface
*/
public function getContainer()
{
return $this->container;
}
/**
* @param string $name
* @return string
*/
protected function getTableName($name)
{
return getenv('DATABASE_PREFIX') . $name;
}
/**
* @return string
* @throws \Doctrine\DBAL\DBALException
*/
protected function getPlatform()
{
return $this->connection->getDatabasePlatform()->getName();
}
/**
* Call me like this:
* $schema = $this->getClassMetaData(User::class);
*
* @param string $entityName
* @return \Doctrine\Common\Persistence\Mapping\ClassMetadata
*/
protected function getClassMetaData($entityName)
{
$em = $this->getContainer()->get('doctrine')->getManager();
return $em->getClassMetadata($entityName);
}
/**
* we do it via addSql instead of $schema->getTable($users)->dropIndex()
* otherwise the commands will be executed as last ones.
*
* @param string $indexName
* @param string $tableName
* @throws \Doctrine\DBAL\DBALException
*/
protected function addSqlDropIndex($indexName, $tableName)
{
$dropSql = 'DROP INDEX ' . $indexName;
if ($this->getPlatform() === 'mysql') {
$dropSql .= ' ON ' . $tableName;
}
$this->addSql($dropSql);
}
}

View File

@@ -9,10 +9,10 @@
namespace App\Entity;
use App\Validator\Constraints as KimaiAssert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
@@ -24,14 +24,14 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Table(
* name="users",
* uniqueConstraints={
* @ORM\UniqueConstraint(columns={"name"}),
* @ORM\UniqueConstraint(columns={"mail"})
* @ORM\UniqueConstraint(columns={"username"}),
* @ORM\UniqueConstraint(columns={"email"})
* }
* )
* @UniqueEntity("username")
* @UniqueEntity("email")
*/
class User implements UserInterface
class User extends BaseUser implements UserInterface
{
public const ROLE_CUSTOMER = 'ROLE_CUSTOMER';
public const ROLE_USER = 'ROLE_USER';
@@ -47,40 +47,7 @@ class User implements UserInterface
* @ORM\GeneratedValue
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=60, nullable=false, unique=true)
* @Assert\NotBlank()
* @Assert\Length(min=5, max=60)
*/
private $username;
/**
* @var string
*
* @ORM\Column(name="mail", type="string", length=160, nullable=false, unique=true)
* @Assert\NotBlank()
* @Assert\Email()
*/
private $email;
/**
* @var string
*
* @ORM\Column(name="password", type="string", length=254, nullable=true)
*/
private $password;
/**
* @var string
*
* @Assert\NotBlank(groups={"registration", "passwordUpdate"})
* @Assert\Length(min=6, max=4096, groups={"registration", "passwordUpdate"})
*/
private $plainPassword;
protected $id;
/**
* @var string
@@ -90,14 +57,6 @@ class User implements UserInterface
*/
private $alias;
/**
* @var bool
*
* @ORM\Column(name="active", type="boolean", nullable=false)
* @Assert\NotNull()
*/
private $active = true;
/**
* @var \DateTime
*
@@ -119,14 +78,6 @@ class User implements UserInterface
*/
private $avatar;
/**
* @var string[]
*
* @ORM\Column(type="json_array")
* @KimaiAssert\Role()
*/
private $roles = [];
/**
* @var UserPreference[]|Collection
*
@@ -139,6 +90,7 @@ class User implements UserInterface
*/
public function __construct()
{
parent::__construct();
$this->registeredAt = new \DateTime();
$this->preferences = new ArrayCollection();
}
@@ -189,107 +141,6 @@ class User implements UserInterface
return $this->alias;
}
/**
* @param bool $active
* @return $this
*/
public function setActive($active)
{
$this->active = (bool) $active;
return $this;
}
/**
* @return bool
*/
public function isActive()
{
return $this->active;
}
/**
* @param string $password
* @return $this
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Only for form editing, you don't need this method!
*
* @return string
*/
public function getPlainPassword()
{
return $this->plainPassword;
}
/**
* Only for form editing, you don't need this method!
*
* @param string $password
* @return $this
*/
public function setPlainPassword($password)
{
$this->plainPassword = $password;
return $this;
}
/**
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* @param string $username
* @return $this
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* @param string $email
* @return $this
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* @return string
*/
@@ -328,34 +179,6 @@ class User implements UserInterface
return $this;
}
/**
* Returns the roles or permissions granted to the user for security.
*
* @return string[]
*/
public function getRoles()
{
$roles = $this->roles;
// guarantees that a user always has at least one role for security
if (empty($roles)) {
$roles[] = 'ROLE_USER';
}
return array_unique($roles);
}
/**
* @param string[] $roles
* @return $this
*/
public function setRoles(array $roles)
{
$this->roles = $roles;
return $this;
}
/**
* @return UserPreference[]|Collection
*/
@@ -419,35 +242,6 @@ class User implements UserInterface
return $this;
}
/**
* @return bool
*/
public function isEnabled()
{
return $this->isActive();
}
/**
* Returns the salt that was originally used to encode the password.
*/
public function getSalt()
{
// See "Do you need to use a Salt?" at http://symfony.com/doc/current/cookbook/security/entity_provider.html
// we're using bcrypt in security.yml to encode the password, so
// the salt value is built-in and you don't have to generate one
return;
}
/**
* Removes sensitive data from the user.
*/
public function eraseCredentials()
{
// if you had a plainPassword property, you'd nullify it here
// $this->plainPassword = null;
}
/**
* @return string
*/

View File

@@ -132,7 +132,7 @@ class UserPreference
}
/**
* @return string|int|bool
* @return mixed
*/
public function getValue()
{
@@ -147,7 +147,10 @@ class UserPreference
}
/**
* @param string|int|bool $value
* Given $value will not be serialized before its stored, so it should be one of the types:
* integer, string or boolean
*
* @param mixed $value
* @return UserPreference
*/
public function setValue($value): UserPreference

View File

@@ -12,6 +12,7 @@ namespace App\EventSubscriber;
use App\Event\ConfigureAdminMenuEvent;
use App\Event\ConfigureMainMenuEvent;
use KevinPapst\AdminLTEBundle\Event\SidebarMenuEvent;
use KevinPapst\AdminLTEBundle\Event\ThemeEvents;
use KevinPapst\AdminLTEBundle\Model\MenuItemModel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -48,7 +49,7 @@ class MenuBuilderSubscriber implements EventSubscriberInterface
public static function getSubscribedEvents(): array
{
return [
'theme.sidebar_setup_menu' => ['onSetupNavbar', 100],
ThemeEvents::THEME_SIDEBAR_SETUP_MENU => ['onSetupNavbar', 100],
];
}
@@ -89,7 +90,7 @@ class MenuBuilderSubscriber implements EventSubscriberInterface
}
$event->addItem(
new MenuItemModel('logout', 'menu.logout', 'security_logout', [], 'fas fa-sign-out-alt')
new MenuItemModel('logout', 'menu.logout', 'fos_user_security_logout', [], 'fas fa-sign-out-alt')
);
$this->activateByRoute(

View File

@@ -11,6 +11,7 @@ namespace App\EventSubscriber;
use App\Entity\User;
use KevinPapst\AdminLTEBundle\Event\ShowUserEvent;
use KevinPapst\AdminLTEBundle\Event\ThemeEvents;
use KevinPapst\AdminLTEBundle\Model\UserModel;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
@@ -40,8 +41,8 @@ class NavbarShowUserSubscriber implements EventSubscriberInterface
public static function getSubscribedEvents(): array
{
return [
'theme.navbar_user' => ['onShowUser', 100],
'theme.sidebar_user' => ['onShowUser', 100],
ThemeEvents::THEME_NAVBAR_USER => ['onShowUser', 100],
ThemeEvents::THEME_SIDEBAR_USER => ['onShowUser', 100],
];
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\EventSubscriber;
use App\Entity\User;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* This class intercepts the registration to make sure the first-ever
* registered user will get the SUPER_ADMIN role.
*/
class RegistrationSubscriber implements EventSubscriberInterface
{
/**
* @var UserManagerInterface
*/
protected $userManager;
/**
* @param UserManagerInterface $userManager
*/
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* @return array
*/
public static function getSubscribedEvents(): array
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => ['onRegistrationSuccess', 200]
];
}
/**
* @param FormEvent $event
*/
public function onRegistrationSuccess(FormEvent $event)
{
/** @var $user \FOS\UserBundle\Model\UserInterface */
$user = $event->getForm()->getData();
$roles = [User::ROLE_USER];
if (empty($this->userManager->findUsers())) {
$roles = [User::ROLE_SUPER_ADMIN];
}
$user->setRoles($roles);
}
}

View File

@@ -47,7 +47,7 @@ class UserEditType extends AbstractType
'label' => 'label.email',
])
// boolean
->add('active', YesNoType::class, [
->add('enabled', YesNoType::class, [
'label' => 'label.active',
])
;

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Initial database structure of Kimai 2.
* This file is only required for testing the migrations!
*/
final class Version20180701120000 extends AbstractMigration
{
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
*/
public function up(Schema $schema): void
{
$platform = $this->getPlatform();
if (!in_array($platform, ['sqlite', 'mysql'])) {
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
}
if ($platform === 'sqlite') {
$this->addSql('CREATE TABLE ' . $this->getTableName('users') . ' (id INTEGER NOT NULL, name VARCHAR(60) NOT NULL, mail VARCHAR(160) NOT NULL, password VARCHAR(254) DEFAULT NULL, alias VARCHAR(60) DEFAULT NULL, active BOOLEAN NOT NULL, registration_date DATETIME DEFAULT NULL, title VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, roles CLOB NOT NULL --(DC2Type:json_array)
, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCE5E237E06 ON ' . $this->getTableName('users') . ' (name)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCE5126AC48 ON ' . $this->getTableName('users') . ' (mail)');
$this->addSql('CREATE TABLE ' . $this->getTableName('user_preferences') . ' (id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, name VARCHAR(50) NOT NULL, value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_8D08F631A76ED395 ON ' . $this->getTableName('user_preferences') . ' (user_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D08F631A76ED3955E237E06 ON ' . $this->getTableName('user_preferences') . ' (user_id, name)');
$this->addSql('CREATE TABLE ' . $this->getTableName('customers') . ' (id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, number VARCHAR(50) DEFAULT NULL, comment CLOB DEFAULT NULL, visible BOOLEAN NOT NULL, company VARCHAR(255) DEFAULT NULL, contact VARCHAR(255) DEFAULT NULL, address CLOB DEFAULT NULL, country VARCHAR(2) NOT NULL, currency VARCHAR(3) NOT NULL, phone VARCHAR(255) DEFAULT NULL, fax VARCHAR(255) DEFAULT NULL, mobile VARCHAR(255) DEFAULT NULL, mail VARCHAR(255) DEFAULT NULL, homepage VARCHAR(255) DEFAULT NULL, timezone VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE TABLE ' . $this->getTableName('projects') . ' (id INTEGER NOT NULL, customer_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, order_number CLOB DEFAULT NULL, comment CLOB DEFAULT NULL, visible BOOLEAN NOT NULL, budget NUMERIC(10, 2) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_407F12069395C3F3 ON ' . $this->getTableName('projects') . ' (customer_id)');
$this->addSql('CREATE TABLE ' . $this->getTableName('activities') . ' (id INTEGER NOT NULL, project_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, comment CLOB DEFAULT NULL, visible BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_8811FE1C166D1F9C ON ' . $this->getTableName('activities') . ' (project_id)');
$this->addSql('CREATE TABLE ' . $this->getTableName('timesheet') . ' (id INTEGER NOT NULL, user INTEGER DEFAULT NULL, activity_id INTEGER DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $this->getTableName('timesheet') . ' (user)');
$this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $this->getTableName('timesheet') . ' (activity_id)');
$this->addSql('CREATE TABLE ' . $this->getTableName('invoice_templates') . ' (id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, company VARCHAR(255) NOT NULL, address CLOB DEFAULT NULL, due_days INTEGER NOT NULL, vat INTEGER DEFAULT NULL, calculator VARCHAR(20) NOT NULL, number_generator VARCHAR(20) NOT NULL, renderer VARCHAR(20) NOT NULL, payment_terms CLOB DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1626CFE95E237E06 ON ' . $this->getTableName('invoice_templates') . ' (name)');
} else {
$this->addSql('CREATE TABLE ' . $this->getTableName('users') . ' (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(60) NOT NULL, mail VARCHAR(160) NOT NULL, password VARCHAR(254) DEFAULT NULL, alias VARCHAR(60) DEFAULT NULL, active TINYINT(1) NOT NULL, registration_date DATETIME DEFAULT NULL, title VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json_array)\', UNIQUE INDEX UNIQ_B9AC5BCE5E237E06 (name), UNIQUE INDEX UNIQ_B9AC5BCE5126AC48 (mail), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $this->getTableName('user_preferences') . ' (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(50) NOT NULL, value VARCHAR(255) DEFAULT NULL, INDEX IDX_8D08F631A76ED395 (user_id), UNIQUE INDEX UNIQ_8D08F631A76ED3955E237E06 (user_id, name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $this->getTableName('customers') . ' (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, number VARCHAR(50) DEFAULT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, company VARCHAR(255) DEFAULT NULL, contact VARCHAR(255) DEFAULT NULL, address TEXT DEFAULT NULL, country VARCHAR(2) NOT NULL, currency VARCHAR(3) NOT NULL, phone VARCHAR(255) DEFAULT NULL, fax VARCHAR(255) DEFAULT NULL, mobile VARCHAR(255) DEFAULT NULL, mail VARCHAR(255) DEFAULT NULL, homepage VARCHAR(255) DEFAULT NULL, timezone VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $this->getTableName('projects') . ' (id INT AUTO_INCREMENT NOT NULL, customer_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, order_number TINYTEXT DEFAULT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, budget NUMERIC(10, 2) NOT NULL, INDEX IDX_407F12069395C3F3 (customer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $this->getTableName('activities') . ' (id INT AUTO_INCREMENT NOT NULL, project_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, INDEX IDX_8811FE1C166D1F9C (project_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $this->getTableName('timesheet') . ' (id INT AUTO_INCREMENT NOT NULL, user INT DEFAULT NULL, activity_id INT DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INT DEFAULT NULL, description TEXT DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, INDEX IDX_4F60C6B18D93D649 (user), INDEX IDX_4F60C6B181C06096 (activity_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $this->getTableName('invoice_templates') . ' (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, company VARCHAR(255) NOT NULL, address TEXT DEFAULT NULL, due_days INT NOT NULL, vat INT DEFAULT NULL, calculator VARCHAR(20) NOT NULL, number_generator VARCHAR(20) NOT NULL, renderer VARCHAR(20) NOT NULL, payment_terms TEXT DEFAULT NULL, UNIQUE INDEX UNIQ_1626CFE95E237E06 (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE ' . $this->getTableName('user_preferences') . ' ADD CONSTRAINT FK_8D08F631A76ED395 FOREIGN KEY (user_id) REFERENCES ' . $this->getTableName('users') . ' (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $this->getTableName('projects') . ' ADD CONSTRAINT FK_407F12069395C3F3 FOREIGN KEY (customer_id) REFERENCES ' . $this->getTableName('customers') . ' (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $this->getTableName('activities') . ' ADD CONSTRAINT FK_8811FE1C166D1F9C FOREIGN KEY (project_id) REFERENCES ' . $this->getTableName('projects') . ' (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $this->getTableName('timesheet') . ' ADD CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $this->getTableName('users') . ' (id)');
$this->addSql('ALTER TABLE ' . $this->getTableName('timesheet') . ' ADD CONSTRAINT FK_4F60C6B181C06096 FOREIGN KEY (activity_id) REFERENCES ' . $this->getTableName('activities') . ' (id) ON DELETE CASCADE');
}
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
*/
public function down(Schema $schema): void
{
$platform = $this->getPlatform();
if (!in_array($platform, ['sqlite', 'mysql'])) {
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
}
if ($platform === 'sqlite') {
$this->addSql('DROP TABLE ' . $this->getTableName('users'));
$this->addSql('DROP TABLE ' . $this->getTableName('user_preferences'));
$this->addSql('DROP TABLE ' . $this->getTableName('customers'));
$this->addSql('DROP TABLE ' . $this->getTableName('projects'));
$this->addSql('DROP TABLE ' . $this->getTableName('activities'));
$this->addSql('DROP TABLE ' . $this->getTableName('timesheet'));
$this->addSql('DROP TABLE ' . $this->getTableName('invoice_templates'));
} else {
$this->addSql('ALTER TABLE ' . $this->getTableName('user_preferences') . ' DROP FOREIGN KEY FK_8D08F631A76ED395');
$this->addSql('ALTER TABLE ' . $this->getTableName('timesheet') . ' DROP FOREIGN KEY FK_4F60C6B18D93D649');
$this->addSql('ALTER TABLE ' . $this->getTableName('projects') . ' DROP FOREIGN KEY FK_407F12069395C3F3');
$this->addSql('ALTER TABLE ' . $this->getTableName('activities') . ' DROP FOREIGN KEY FK_8811FE1C166D1F9C');
$this->addSql('ALTER TABLE ' . $this->getTableName('timesheet') . ' DROP FOREIGN KEY FK_4F60C6B181C06096');
$this->addSql('DROP TABLE ' . $this->getTableName('users'));
$this->addSql('DROP TABLE ' . $this->getTableName('user_preferences'));
$this->addSql('DROP TABLE ' . $this->getTableName('customers'));
$this->addSql('DROP TABLE ' . $this->getTableName('projects'));
$this->addSql('DROP TABLE ' . $this->getTableName('activities'));
$this->addSql('DROP TABLE ' . $this->getTableName('timesheet'));
$this->addSql('DROP TABLE ' . $this->getTableName('invoice_templates'));
}
}
}

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
/**
* Migration for FOSUserBundle
*
* Changes the table structure of "users" table and migrates from json_array type to serialized array,
* probably also fixing the higher required MariaDB version.
*/
final class Version20180715160326 extends AbstractMigration
{
/**
* @var Index[]
*/
protected $indexesOld = [];
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function up(Schema $schema): void
{
$platform = $this->getPlatform();
if (!in_array($platform, ['sqlite', 'mysql'])) {
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
}
$users = $this->getTableName('users');
// delete all existing indexes
$indexesOld = $schema->getTable($users)->getIndexes();
foreach ($indexesOld as $index) {
if (in_array('name', $index->getColumns()) || in_array('mail', $index->getColumns())) {
$this->indexesOld[] = $index;
$this->addSqlDropIndex($index->getName(), $users);
}
}
if ($platform === 'sqlite') {
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $users . ' AS SELECT id, name, mail, password, alias, active, registration_date, title, avatar, roles FROM ' . $users);
$this->addSql('DROP TABLE ' . $users);
$this->addSql('CREATE TABLE ' . $users . ' (id INTEGER NOT NULL, alias VARCHAR(60) DEFAULT NULL COLLATE BINARY, registration_date DATETIME DEFAULT NULL, title VARCHAR(50) DEFAULT NULL COLLATE BINARY, avatar VARCHAR(255) DEFAULT NULL COLLATE BINARY, enabled BOOLEAN NOT NULL, password VARCHAR(255) NOT NULL, roles CLOB NOT NULL --(DC2Type:array)
, username VARCHAR(180) NOT NULL, username_canonical VARCHAR(180) NOT NULL, email VARCHAR(180) NOT NULL, email_canonical VARCHAR(180) NOT NULL, salt VARCHAR(255) DEFAULT NULL, last_login DATETIME DEFAULT NULL, confirmation_token VARCHAR(180) DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO ' . $users . ' (id, username, username_canonical, email, email_canonical, password, alias, enabled, registration_date, title, avatar, roles) SELECT id, name, name, mail, mail, password, alias, active, registration_date, title, avatar, roles FROM __temp__' . $users);
$this->addSql('DROP TABLE __temp__' . $users);
} else {
$this->addSql('ALTER TABLE ' . $users . ' CHANGE name username VARCHAR(180) NOT NULL, ADD username_canonical VARCHAR(180) NOT NULL, CHANGE mail email VARCHAR(180) NOT NULL, ADD email_canonical VARCHAR(180) NOT NULL, ADD salt VARCHAR(255) DEFAULT NULL, ADD last_login DATETIME DEFAULT NULL, ADD confirmation_token VARCHAR(180) DEFAULT NULL, ADD password_requested_at DATETIME DEFAULT NULL, CHANGE password password VARCHAR(255) NOT NULL, CHANGE alias alias VARCHAR(60) DEFAULT NULL, CHANGE registration_date registration_date DATETIME DEFAULT NULL, CHANGE title title VARCHAR(50) DEFAULT NULL, CHANGE avatar avatar VARCHAR(255) DEFAULT NULL, CHANGE roles roles LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', CHANGE active enabled TINYINT(1) NOT NULL');
$this->addSql('UPDATE ' . $users . ' set username_canonical = username');
$this->addSql('UPDATE ' . $users . ' set email_canonical = email');
}
$this->addSql('UPDATE ' . $users . ' SET roles = \'a:1:{i:0;s:16:"ROLE_SUPER_ADMIN";}\' WHERE roles LIKE "%ROLE_SUPER_ADMIN%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'a:1:{i:0;s:10:"ROLE_ADMIN";}\' WHERE roles LIKE "%ROLE_ADMIN%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'a:1:{i:0;s:13:"ROLE_TEAMLEAD";}\' WHERE roles LIKE "%ROLE_TEAMLEAD%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'a:0:{}\' WHERE roles LIKE "%ROLE_USER%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'a:1:{i:0;s:13:"ROLE_CUSTOMER";}\' WHERE roles LIKE "%ROLE_CUSTOMER%"');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCE92FC23A8 ON ' . $users . ' (username_canonical)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCEA0D96FBF ON ' . $users . ' (email_canonical)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCEC05FB297 ON ' . $users . ' (confirmation_token)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCEF85E0677 ON ' . $users . ' (username)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCEE7927C74 ON ' . $users . ' (email)');
}
/**
* @param Schema $schema
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
*/
public function down(Schema $schema): void
{
$platform = $this->getPlatform();
if (!in_array($platform, ['sqlite', 'mysql'])) {
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
}
$users = $this->getTableName('users');
$indexToDelete = ['UNIQ_B9AC5BCE92FC23A8', 'UNIQ_B9AC5BCEA0D96FBF', 'UNIQ_B9AC5BCEC05FB297', 'UNIQ_B9AC5BCEF85E0677', 'UNIQ_B9AC5BCEE7927C74'];
foreach ($indexToDelete as $index) {
$this->addSqlDropIndex($index, $users);
}
if ($platform === 'sqlite') {
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $users . ' AS SELECT id, username, email, enabled, password, roles, alias, registration_date, title, avatar FROM ' . $users);
$this->addSql('DROP TABLE ' . $users);
$this->addSql('CREATE TABLE ' . $users . ' (id INTEGER NOT NULL, alias VARCHAR(60) DEFAULT NULL, registration_date DATETIME DEFAULT NULL, title VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, active BOOLEAN NOT NULL, password VARCHAR(254) DEFAULT NULL COLLATE BINARY, roles CLOB NOT NULL COLLATE BINARY --(DC2Type:json_array)
, name VARCHAR(60) NOT NULL COLLATE BINARY, mail VARCHAR(160) NOT NULL COLLATE BINARY, PRIMARY KEY(id))');
$this->addSql('INSERT INTO ' . $users . ' (id, name, mail, active, password, roles, alias, registration_date, title, avatar) SELECT id, username, email, enabled, password, roles, alias, registration_date, title, avatar FROM __temp__' . $users);
$this->addSql('DROP TABLE __temp__' . $users);
} else {
$this->addSql('ALTER TABLE ' . $users . ' CHANGE username name VARCHAR(60) NOT NULL COLLATE utf8mb4_unicode_ci, CHANGE email mail VARCHAR(160) NOT NULL COLLATE utf8mb4_unicode_ci, DROP username_canonical, DROP email_canonical, DROP salt, DROP last_login, DROP confirmation_token, DROP password_requested_at, CHANGE password password VARCHAR(254) DEFAULT \'NULL\' COLLATE utf8mb4_unicode_ci, CHANGE roles roles JSON NOT NULL COLLATE utf8mb4_bin COMMENT \'(DC2Type:json_array)\', CHANGE alias alias VARCHAR(60) DEFAULT \'NULL\' COLLATE utf8mb4_unicode_ci, CHANGE registration_date registration_date DATETIME DEFAULT \'NULL\', CHANGE title title VARCHAR(50) DEFAULT \'NULL\' COLLATE utf8mb4_unicode_ci, CHANGE avatar avatar VARCHAR(255) DEFAULT \'NULL\' COLLATE utf8mb4_unicode_ci, CHANGE enabled active TINYINT(1) NOT NULL');
}
$this->addSql('UPDATE ' . $users . ' SET roles = \'["ROLE_SUPER_ADMIN"]\' WHERE roles LIKE "%ROLE_SUPER_ADMIN%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'["ROLE_ADMIN"]\' WHERE roles LIKE "%ROLE_ADMIN%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'["ROLE_TEAMLEAD"]\' WHERE roles LIKE "%ROLE_TEAMLEAD%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'["ROLE_USER"]\' WHERE roles LIKE "%ROLE_USER%"');
$this->addSql('UPDATE ' . $users . ' SET roles = \'["ROLE_CUSTOMER"]\' WHERE roles LIKE "%ROLE_CUSTOMER%"');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCE5E237E06 ON ' . $users . ' (name)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_B9AC5BCE5126AC48 ON ' . $users . ' (mail)');
$usersTable = $schema->getTable($users);
foreach ($this->indexesOld as $index) {
$usersTable->addIndex($index->getColumns(), $index->getName(), $index->getFlags(), $index->getOptions());
}
}
}

View File

@@ -60,13 +60,20 @@ class UserRepository extends AbstractRepository implements UserLoaderInterface
->orderBy('u.' . $query->getOrderBy(), $query->getOrder());
if (UserQuery::SHOW_VISIBLE == $query->getVisibility()) {
$qb->andWhere('u.active = 1');
$qb->andWhere('u.enabled = 1');
} elseif (UserQuery::SHOW_HIDDEN == $query->getVisibility()) {
$qb->andWhere('u.active = 0');
$qb->andWhere('u.enabled = 0');
}
if ($query->getRole() !== null) {
$qb->andWhere('u.roles LIKE :role')->setParameter('role', '%' . $query->getRole() . '%');
$rolesWhere = 'u.roles LIKE :role';
$qb->setParameter('role', '%' . $query->getRole() . '%');
// a hack as FOSUserBundle does not save the ROLE_USER in the database as it is the default role
if ($query->getRole() === User::ROLE_USER) {
$rolesWhere .= ' OR u.roles LIKE :role1';
$qb->setParameter('role1', '%{}');
}
$qb->andWhere($rolesWhere);
}
return $this->getPager($qb->getQuery(), $query->getPage(), $query->getPageSize());

View File

@@ -10,19 +10,28 @@
namespace App\Security;
use App\Entity\User;
use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\Exception\LockedException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Advanced checks during aithentication to make sure the user is allowed to use Kimai.
* Advanced checks during authentication to make sure the user is allowed to use Kimai.
*/
class UserChecker implements UserCheckerInterface
{
/**
* @param UserInterface $user
* @throws AccountStatusException
*/
public function checkPreAuth(UserInterface $user)
{
}
/**
* @param UserInterface $user
* @throws AccountStatusException
*/
public function checkPostAuth(UserInterface $user)
{
if (!$user instanceof User) {