Merge branch 'master' into permission_rework

This commit is contained in:
Jan Böhmer 2022-10-30 17:35:57 +01:00
commit 33f8d2ba9e
109 changed files with 5095 additions and 2860 deletions

View file

@ -34,7 +34,12 @@ use LogicException;
* Class Attachment.
*
* @ORM\Entity(repositoryClass="App\Repository\AttachmentRepository")
* @ORM\Table(name="`attachments`")
* @ORM\Table(name="`attachments`", indexes={
* @ORM\Index(name="attachments_idx_id_element_id_class_name", columns={"id", "element_id", "class_name"}),
* @ORM\Index(name="attachments_idx_class_name_id", columns={"class_name", "id"}),
* @ORM\Index(name="attachment_name_idx", columns={"name"}),
* @ORM\Index(name="attachment_element_idx", columns={"class_name", "element_id"})
* })
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="class_name", type="string")
* @ORM\DiscriminatorMap({
@ -104,7 +109,7 @@ abstract class Attachment extends AbstractNamedDBElement
/**
* @var AttachmentType
* @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="attachments_with_type")
* @ORM\JoinColumn(name="type_id", referencedColumnName="id")
* @ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=false)
* @Selectable()
* @Assert\NotNull(message="validator.attachment.must_not_be_null")
*/

View file

@ -34,7 +34,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class AttachmentType.
*
* @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table(name="`attachment_types`")
* @ORM\Table(name="`attachment_types`", indexes={
* @ORM\Index(name="attachment_types_idx_name", columns={"name"}),
* @ORM\Index(name="attachment_types_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class AttachmentType extends AbstractStructuralDBElement
{

View file

@ -93,7 +93,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
* @NoneOfItsChildren()
* @Groups({"include_parents"})
*/
protected $parent;
protected $parent = null;
/** @var string[] all names of all parent elements as a array of strings,
* the last array element is the name of the element itself
@ -271,16 +271,17 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
*/
public function getSubelements(): iterable
{
return $this->children;
return $this->children ?? new ArrayCollection();
}
/**
* @see getSubelements()
* @return Collection<static>|iterable
* @psalm-return Collection<int, static>
*/
public function getChildren(): iterable
{
return $this->children;
return $this->getSubelements();
}
public function isNotSelectable(): bool

View file

@ -78,12 +78,12 @@ class DevicePart extends AbstractDBElement
* @ORM\ManyToOne(targetEntity="Device", inversedBy="parts")
* @ORM\JoinColumn(name="id_device", referencedColumnName="id")
*/
protected Device $device;
protected ?Device $device = null;
/**
* @var Part
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part")
* @ORM\JoinColumn(name="id_part", referencedColumnName="id")
*/
protected Part $part;
protected ?Part $part = null;
}

View file

@ -71,7 +71,11 @@ use Psr\Log\LogLevel;
* This entity describes a entry in the event log.
*
* @ORM\Entity(repositoryClass="App\Repository\LogEntryRepository")
* @ORM\Table("log")
* @ORM\Table("log", indexes={
* @ORM\Index(name="log_idx_type", columns={"type"}),
* @ORM\Index(name="log_idx_type_target", columns={"type", "target_type", "target_id"}),
* @ORM\Index(name="log_idx_datetime", columns={"datetime"}),
* })
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="smallint")
* @ORM\DiscriminatorMap({

View file

@ -34,7 +34,11 @@ use function sprintf;
/**
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @ORM\Table("parameters")
* @ORM\Table("parameters", indexes={
* @ORM\Index(name="parameter_name_idx", columns={"name"}),
* @ORM\Index(name="parameter_group_idx", columns={"param_group"}),
* @ORM\Index(name="parameter_type_element_idx", columns={"type", "element_id"})
* })
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="smallint")
* @ORM\DiscriminatorMap({
@ -91,7 +95,6 @@ abstract class AbstractParameter extends AbstractNamedDBElement
/**
* @var string The unit in which the value values are given (e.g. V)
* @Assert\Length(max=5)
* @ORM\Column(type="string", nullable=false)
*/
protected string $unit = '';

View file

@ -33,7 +33,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class AttachmentType.
*
* @ORM\Entity(repositoryClass="App\Repository\Parts\CategoryRepository")
* @ORM\Table(name="`categories`")
* @ORM\Table(name="`categories`", indexes={
* @ORM\Index(name="category_idx_name", columns={"name"}),
* @ORM\Index(name="category_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class Category extends AbstractPartsContainingDBElement
{

View file

@ -61,7 +61,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class Footprint.
*
* @ORM\Entity(repositoryClass="App\Repository\Parts\FootprintRepository")
* @ORM\Table("`footprints`")
* @ORM\Table("`footprints`", indexes={
* @ORM\Index(name="footprint_idx_name", columns={"name"}),
* @ORM\Index(name="footprint_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class Footprint extends AbstractPartsContainingDBElement
{

View file

@ -61,7 +61,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class Manufacturer.
*
* @ORM\Entity(repositoryClass="App\Repository\Parts\ManufacturerRepository")
* @ORM\Table("`manufacturers`")
* @ORM\Table("`manufacturers`", indexes={
* @ORM\Index(name="manufacturer_name", columns={"name"}),
* @ORM\Index(name="manufacturer_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class Manufacturer extends AbstractCompany
{

View file

@ -55,7 +55,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* This could be something like N, grams, meters, etc...
*
* @ORM\Entity(repositoryClass="App\Repository\Parts\MeasurementUnitRepository")
* @ORM\Table(name="`measurement_units`")
* @ORM\Table(name="`measurement_units`", indexes={
* @ORM\Index(name="unit_idx_name", columns={"name"}),
* @ORM\Index(name="unit_idx_parent_name", columns={"parent_id", "name"}),
* })
* @UniqueEntity("unit")
*/
class MeasurementUnit extends AbstractPartsContainingDBElement
@ -66,7 +69,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
* @ORM\Column(type="string", name="unit", nullable=true)
* @Assert\Length(max=10)
*/
protected string $unit;
protected ?string $unit = null;
/**
* @var bool Determines if the amount value associated with this unit should be treated as integer.

View file

@ -74,7 +74,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Otherwise this class would be too big, to be maintained.
*
* @ORM\Entity(repositoryClass="App\Repository\PartRepository")
* @ORM\Table("`parts`")
* @ORM\Table("`parts`", indexes={
* @ORM\Index(name="parts_idx_datet_name_last_id_needs", columns={"datetime_added", "name", "last_modified", "id", "needs_review"}),
* @ORM\Index(name="parts_idx_name", columns={"name"}),
* })
*/
class Part extends AttachmentContainingDBElement
{

View file

@ -58,7 +58,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* It is the connection between a part and its store locations.
*
* @ORM\Entity()
* @ORM\Table(name="part_lots")
* @ORM\Table(name="part_lots", indexes={
* @ORM\Index(name="part_lots_idx_instock_un_expiration_id_part", columns={"instock_unknown", "expiration_date", "id_part"}),
* @ORM\Index(name="part_lots_idx_needs_refill", columns={"needs_refill"}),
* })
* @ORM\HasLifecycleCallbacks()
* @ValidPartLot()
*/

View file

@ -79,7 +79,7 @@ trait ManufacturerTrait
* @ORM\Column(type="string", length=255, nullable=true)
* @Assert\Choice({"announced", "active", "nrfnd", "eol", "discontinued", ""})
*/
protected string $manufacturing_status = '';
protected ?string $manufacturing_status = '';
/**
* Get the link to the website of the article on the manufacturers website

View file

@ -79,7 +79,7 @@ trait OrderTrait
* @ORM\OneToOne(targetEntity="App\Entity\PriceInformations\Orderdetail")
* @ORM\JoinColumn(name="order_orderdetails_id", referencedColumnName="id")
*/
protected Orderdetail $order_orderdetail;
protected ?Orderdetail $order_orderdetail = null;
/**
* Get the selected order orderdetails of this part.

View file

@ -61,7 +61,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class Store location.
*
* @ORM\Entity(repositoryClass="App\Repository\Parts\StorelocationRepository")
* @ORM\Table("`storelocations`")
* @ORM\Table("`storelocations`", indexes={
* @ORM\Index(name="location_idx_name", columns={"name"}),
* @ORM\Index(name="location_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class Storelocation extends AbstractPartsContainingDBElement
{

View file

@ -65,7 +65,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class Supplier.
*
* @ORM\Entity(repositoryClass="App\Repository\Parts\SupplierRepository")
* @ORM\Table("`suppliers`")
* @ORM\Table("`suppliers`", indexes={
* @ORM\Index(name="supplier_idx_name", columns={"name"}),
* @ORM\Index(name="supplier_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class Supplier extends AbstractCompany
{

View file

@ -59,7 +59,10 @@ use Symfony\Component\Validator\Constraints as Assert;
*
* @UniqueEntity("iso_code")
* @ORM\Entity()
* @ORM\Table(name="currencies")
* @ORM\Table(name="currencies", indexes={
* @ORM\Index(name="currency_idx_name", columns={"name"}),
* @ORM\Index(name="currency_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class Currency extends AbstractStructuralDBElement
{
@ -78,7 +81,7 @@ class Currency extends AbstractStructuralDBElement
* @ORM\Column(type="string")
* @Assert\Currency()
*/
protected string $iso_code;
protected string $iso_code = "";
/**
* @ORM\OneToMany(targetEntity="Currency", mappedBy="parent", cascade={"persist"})

View file

@ -66,7 +66,9 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* Class Orderdetail.
*
* @ORM\Table("`orderdetails`")
* @ORM\Table("`orderdetails`", indexes={
* @ORM\Index(name="orderdetails_supplier_part_nr", columns={"supplierpartnr"}),
* })
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks()
* @UniqueEntity({"supplierpartnr", "supplier", "part"})

View file

@ -66,7 +66,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class Pricedetail.
*
* @ORM\Entity()
* @ORM\Table("`pricedetails`")
* @ORM\Table("`pricedetails`", indexes={
* @ORM\Index(name="pricedetails_idx_min_discount", columns={"min_discount_quantity"}),
* @ORM\Index(name="pricedetails_idx_min_discount_price_qty", columns={"min_discount_quantity", "price_related_quantity"}),
* })
* @ORM\HasLifecycleCallbacks()
* @UniqueEntity(fields={"min_discount_quantity", "orderdetail"})
*/

View file

@ -56,7 +56,10 @@ use Symfony\Component\Validator\Constraints as Assert;
* This entity represents an user group.
*
* @ORM\Entity()
* @ORM\Table("`groups`")
* @ORM\Table("`groups`", indexes={
* @ORM\Index(name="group_idx_name", columns={"name"}),
* @ORM\Index(name="group_idx_parent_name", columns={"parent_id", "name"}),
* })
*/
class Group extends AbstractStructuralDBElement implements HasPermissionsInterface
{
@ -75,8 +78,9 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
/**
* @ORM\OneToMany(targetEntity="User", mappedBy="group")
* @var Collection<User>
*/
protected Collection $users;
protected $users;
/**
* @var bool If true all users associated with this group must have enabled some kind of 2 factor authentication
@ -85,7 +89,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
protected $enforce2FA = false;
/**
* @var Collection<int, GroupAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\ManufacturerAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\GroupAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid()
*/

View file

@ -44,8 +44,7 @@ namespace App\Entity\UserSystem;
use App\Entity\Base\TimestampTrait;
use Doctrine\ORM\Mapping as ORM;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
use u2flib_server\Registration;
use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface;
/**
* @ORM\Entity
@ -56,7 +55,7 @@ use u2flib_server\Registration;
* })
* @ORM\HasLifecycleCallbacks()
*/
class U2FKey implements TwoFactorKeyInterface
class U2FKey implements LegacyU2FKeyInterface
{
use TimestampTrait;
@ -110,14 +109,6 @@ class U2FKey implements TwoFactorKeyInterface
**/
protected ?User $user = null;
public function fromRegistrationData(Registration $data): void
{
$this->keyHandle = $data->keyHandle;
$this->publicKey = $data->publicKey;
$this->certificate = $data->certificate;
$this->counter = $data->counter;
}
public function getKeyHandle(): string
{
return $this->keyHandle;

View file

@ -57,7 +57,9 @@ use App\Entity\PriceInformations\Currency;
use App\Security\Interfaces\HasPermissionsInterface;
use App\Validator\Constraints\Selectable;
use App\Validator\Constraints\ValidPermission;
use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Webauthn\PublicKeyCredentialUserEntity;
use function count;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
@ -65,8 +67,6 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use function in_array;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface as U2FTwoFactorInterface;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
use Scheb\TwoFactorBundle\Model\PreferredProviderInterface;
@ -74,17 +74,20 @@ use Scheb\TwoFactorBundle\Model\TrustedDeviceInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface;
/**
* This entity represents a user, which can log in and have permissions.
* Also this entity is able to save some informations about the user, like the names, email-address and other info.
*
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
* @ORM\Table("`users`")
* @ORM\Table("`users`", indexes={
* @ORM\Index(name="user_idx_username", columns={"name"})
* })
* @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"})
* @UniqueEntity("name", message="validator.user.username_already_used")
*/
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, U2FTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface
{
//use MasterAttachmentTrait;
@ -146,7 +149,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
/**
* @var Group|null the group this user belongs to
* @ORM\ManyToOne(targetEntity="Group", inversedBy="users", fetch="EAGER")
* DO NOT PUT A fetch eager here! Otherwise you can not unset the group of a user! This seems to be some kind of bug in doctrine. Maybe this is fixed in future versions.
* @ORM\ManyToOne(targetEntity="Group", inversedBy="users")
* @ORM\JoinColumn(name="group_id", referencedColumnName="id")
* @Selectable()
*/
@ -239,11 +243,17 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/
protected ?DateTime $backupCodesGenerationDate = null;
/** @var Collection<int, TwoFactorKeyInterface>
/** @var Collection<int, LegacyU2FKeyInterface>
* @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
*/
protected $u2fKeys;
/**
* @var Collection<int, WebauthnKey>
* @ORM\OneToMany(targetEntity="App\Entity\UserSystem\WebauthnKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
*/
protected $webauthn_keys;
/**
* @var Currency|null The currency the user wants to see prices in.
* Dont use fetch=EAGER here, this will cause problems with setting the currency setting.
@ -272,6 +282,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
parent::__construct();
$this->permissions = new PermissionsEmbed();
$this->u2fKeys = new ArrayCollection();
$this->webauthn_keys = new ArrayCollection();
}
/**
@ -762,7 +773,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/
public function isBackupCode(string $code): bool
{
return in_array($code, $this->backupCodes, true);
return in_array($code, $this->getBackupCodes(), true);
}
/**
@ -772,7 +783,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/
public function invalidateBackupCode(string $code): void
{
$key = array_search($code, $this->backupCodes, true);
$key = array_search($code, $this->getBackupCodes(), true);
if (false !== $key) {
unset($this->backupCodes[$key]);
}
@ -836,48 +847,48 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
++$this->trustedDeviceCookieVersion;
}
/**
* Check if U2F is enabled.
*/
public function isU2FAuthEnabled(): bool
{
return count($this->u2fKeys) > 0;
}
/**
* Get all U2F Keys that are associated with this user.
*
* @psalm-return Collection<int, TwoFactorKeyInterface>
*/
public function getU2FKeys(): Collection
{
return $this->u2fKeys;
}
/**
* Add a U2F key to this user.
*/
public function addU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->add($key);
}
/**
* Remove a U2F key from this user.
*/
public function removeU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->removeElement($key);
}
public function getPreferredTwoFactorProvider(): ?string
{
//If U2F is available then prefer it
if ($this->isU2FAuthEnabled()) {
return 'u2f_two_factor';
//if ($this->isU2FAuthEnabled()) {
// return 'u2f_two_factor';
//}
if ($this->isWebAuthnAuthenticatorEnabled()) {
return 'webauthn_two_factor_provider';
}
//Otherwise use other methods
return null;
}
public function isWebAuthnAuthenticatorEnabled(): bool
{
return count($this->u2fKeys) > 0
|| count($this->webauthn_keys) > 0;
}
public function getLegacyU2FKeys(): iterable
{
return $this->u2fKeys;
}
public function getWebAuthnUser(): PublicKeyCredentialUserEntity
{
return new PublicKeyCredentialUserEntity(
$this->getUsername(),
(string) $this->getId(),
$this->getFullName(),
);
}
public function getWebauthnKeys(): iterable
{
return $this->webauthn_keys;
}
public function addWebauthnKey(WebauthnKey $webauthnKey): void
{
$this->webauthn_keys->add($webauthnKey);
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace App\Entity\UserSystem;
use App\Entity\Base\TimestampTrait;
use Doctrine\ORM\Mapping as ORM;
use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource;
/**
* @ORM\Table(name="webauthn_keys")
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks()
*/
class WebauthnKey extends BasePublicKeyCredentialSource
{
use TimestampTrait;
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected int $id;
/**
* @ORM\Column(type="string")
*/
protected string $name;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="webauthn_keys")
**/
protected ?User $user = null;
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
* @return WebauthnKey
*/
public function setName(string $name): WebauthnKey
{
$this->name = $name;
return $this;
}
/**
* @return User|null
*/
public function getUser(): ?User
{
return $this->user;
}
/**
* @param User|null $user
* @return WebauthnKey
*/
public function setUser(?User $user): WebauthnKey
{
$this->user = $user;
return $this;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
public static function fromRegistration(BasePublicKeyCredentialSource $registration): self
{
return new self(
$registration->getPublicKeyCredentialId(),
$registration->getType(),
$registration->getTransports(),
$registration->getAttestationType(),
$registration->getTrustPath(),
$registration->getAaguid(),
$registration->getCredentialPublicKey(),
$registration->getUserHandle(),
$registration->getCounter(),
$registration->getOtherUI()
);
}
}