Check read property on part entity objects.

This commit is contained in:
Jan Böhmer 2019-09-16 13:27:53 +02:00
parent 44bad9029b
commit 3ecbe19fd6
7 changed files with 98 additions and 26 deletions

View file

@ -32,6 +32,7 @@
namespace App\DataTables\Column;
use App\Entity\Base\DBElement;
use App\Entity\Base\NamedDBElement;
use App\Entity\Parts\Part;
use App\Services\EntityURLGenerator;
@ -76,14 +77,19 @@ class EntityColumn extends AbstractColumn
$resolver->setDefault('render', function (Options $options) {
return function ($value, Part $context) use ($options) {
/** @var DBElement $entity */
$entity = $this->accessor->getValue($context, $options['property']);
if ($entity) {
return sprintf(
'<a href="%s">%s</a>',
$this->urlGenerator->listPartsURL($entity),
$value
);
if ($entity->getID() !== null) {
return sprintf(
'<a href="%s">%s</a>',
$this->urlGenerator->listPartsURL($entity),
$value
);
} else {
return sprintf('<i>%s</i>', $value);
}
}
};
});

View file

@ -74,7 +74,10 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class Part.
* Part class.
*
* DONT USE orphanRemoval on properties with ColumnSecurity!! An empty collection will be created as placeholder,
* and the partlots are deleted, even if we want dont want that!
*
* @ORM\Entity(repositoryClass="App\Repository\PartRepository")
* @ORM\Table("`parts`")
@ -84,7 +87,8 @@ class Part extends AttachmentContainingDBElement
public const INSTOCK_UNKNOWN = -2;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=false)
* @ColumnSecurity(type="collection", prefix="attachments")
* @Assert\Valid()
*/
protected $attachments;
@ -93,6 +97,7 @@ class Part extends AttachmentContainingDBElement
* @var Category
* @ORM\ManyToOne(targetEntity="Category", inversedBy="parts")
* @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false)
* @ColumnSecurity(prefix="category", type="App\Entity\Parts\Category")
* @Selectable()
*/
protected $category;
@ -101,8 +106,7 @@ class Part extends AttachmentContainingDBElement
* @var Footprint|null
* @ORM\ManyToOne(targetEntity="Footprint", inversedBy="parts")
* @ORM\JoinColumn(name="id_footprint", referencedColumnName="id")
*
* @ColumnSecurity(prefix="footprint", type="object")
* @ColumnSecurity(prefix="footprint", type="App\Entity\Parts\Footprint")
* @Selectable()
*/
protected $footprint;
@ -111,8 +115,7 @@ class Part extends AttachmentContainingDBElement
* @var Manufacturer|null
* @ORM\ManyToOne(targetEntity="Manufacturer", inversedBy="parts")
* @ORM\JoinColumn(name="id_manufacturer", referencedColumnName="id")
*
* @ColumnSecurity(prefix="manufacturer", type="object")
* @ColumnSecurity(prefix="manufacturer", type="App\Entity\Parts\Manufacturer")
* @Selectable()
*/
protected $manufacturer;
@ -128,7 +131,7 @@ class Part extends AttachmentContainingDBElement
/**
* @var Orderdetail[]
* @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=false)
* @Assert\Valid()
* @ColumnSecurity(prefix="orderdetails", type="collection")
*/
@ -174,15 +177,15 @@ class Part extends AttachmentContainingDBElement
/**
* @var string
* @ORM\Column(type="text")
*
* @ColumnSecurity(prefix="description")
*/
protected $description = '';
/**
* @var ?PartLot[]
* @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=false)
* @Assert\Valid()
* @ColumnSecurity(type="collection", prefix="lots")
*/
protected $partLots;
@ -233,14 +236,14 @@ class Part extends AttachmentContainingDBElement
* @var string
* @ORM\Column(type="string")
* @Assert\Url()
* @ColumnSecurity(prefix="manufacturer", type="string", placeholder="")
* @ColumnSecurity(prefix="mpn", type="string", placeholder="")
*/
protected $manufacturer_product_url = '';
/**
* @var string
* @ORM\Column(type="string")
* @ColumnSecurity(prefix="manufacturer", type="string", placeholder="")
* @ColumnSecurity(prefix="mpn", type="string", placeholder="")
*/
protected $manufacturer_product_number = '';
@ -248,12 +251,14 @@ class Part extends AttachmentContainingDBElement
* @var string
* @ORM\Column(type="string", length=255, nullable=true)
* @Assert\Choice({"announced", "active", "nrfnd", "eol", "discontinued", ""})
* @ColumnSecurity(type="string", prefix="status", placeholder="")
*/
protected $manufacturing_status = "";
/**
* @var bool Determines if this part entry needs review (for example, because it is work in progress)
* @ORM\Column(type="boolean")
* @ColumnSecurity(type="boolean")
*/
protected $needs_review = false;
@ -261,18 +266,21 @@ class Part extends AttachmentContainingDBElement
* @var ?MeasurementUnit The unit in which the part's amount is measured.
* @ORM\ManyToOne(targetEntity="MeasurementUnit", inversedBy="parts")
* @ORM\JoinColumn(name="id_part_unit", referencedColumnName="id", nullable=true)
* @ColumnSecurity(type="object", prefix="unit")
*/
protected $partUnit;
/**
* @var string A comma seperated list of tags, assocciated with the part.
* @ORM\Column(type="text")
* @ColumnSecurity(type="string", prefix="tags", placeholder="")
*/
protected $tags = '';
/**
* @var float|null How much a single part unit weighs in gramms.
* @ORM\Column(type="float", nullable=true)
* @ColumnSecurity(type="float", placeholder=null)
* @Assert\PositiveOrZero()
*/
protected $mass;
@ -482,7 +490,7 @@ class Part extends AttachmentContainingDBElement
*
* @return Category the category of this part
*/
public function getCategory(): Category
public function getCategory(): ?Category
{
return $this->category;
}

View file

@ -41,7 +41,6 @@ use App\Entity\Parts\Part;
use App\Entity\Parts\Storelocation;
use App\Entity\PriceInformations\Orderdetail;
use App\Form\AttachmentFormType;
use App\Form\AttachmentType;
use App\Form\Type\SIUnitType;
use App\Form\Type\StructuralEntityType;
use Doctrine\DBAL\Types\FloatType;
@ -122,7 +121,7 @@ class PartBaseType extends AbstractType
'required' => false,
'label' => $this->trans->trans('part.edit.footprint'),
'disable_not_selectable' => true,
'disabled' => !$this->security->isGranted('move', $part),
'disabled' => !$this->security->isGranted('footprint.edit', $part),
])
->add('tags', TextType::class, [
'required' => false,

View file

@ -29,6 +29,7 @@
namespace App\Security\Annotations;
use App\Entity\Base\NamedDBElement;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Collections\ArrayCollection;
use \InvalidArgumentException;
@ -90,10 +91,25 @@ class ColumnSecurity
public function getPlaceholder()
{
//Check if a class name was specified
if (class_exists($this->type)) {
$object = new $this->type();
if ($object instanceof NamedDBElement) {
if (is_string($this->placeholder) && $this->placeholder !== "") {
$object->setName($this->placeholder);
}
$object->setName('???');
}
return $object;
}
if (null === $this->placeholder) {
switch ($this->type) {
case 'integer':
return 0;
case 'float':
return 0.0;
case 'string':
return '???';
case 'object':

View file

@ -33,7 +33,11 @@ use App\Entity\Base\DBElement;
use App\Security\Annotations\ColumnSecurity;
use Doctrine\Common\Annotations\Reader;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\PostLoad;
use Doctrine\ORM\Mapping\PreUpdate;
use ReflectionClass;
@ -50,11 +54,13 @@ class ElementPermissionListener
{
protected $security;
protected $reader;
protected $em;
public function __construct(Security $security, Reader $reader)
public function __construct(Security $security, Reader $reader, EntityManagerInterface $em)
{
$this->security = $security;
$this->reader = $reader;
$this->em = $em;
}
/**
@ -80,7 +86,39 @@ class ElementPermissionListener
//Check if user is allowed to read info, otherwise apply placeholder
if ((null !== $annotation) && !$this->security->isGranted($annotation->getReadOperationName(), $element)) {
$property->setAccessible(true);
$property->setValue($element, $annotation->getPlaceholder());
$value = $annotation->getPlaceholder();
if($value instanceof DBElement) {
$this->em->detach($value);
}
$property->setValue($element, $value);
}
}
}
/**
* @ORM\PreFlush()
* This function is called before flushing. We use it, to remove all placeholder DBElements (with name=???),
* so we dont get a no cascade persistance error.
*/
public function preFlushHandler(DBElement $element, PreFlushEventArgs $eventArgs)
{
//$eventArgs->getEntityManager()->getUnitOfWork()->
$reflectionClass = new ReflectionClass($element);
$properties = $reflectionClass->getProperties();
foreach ($properties as $property) {
$annotation = $this->reader->getPropertyAnnotation(
$property,
ColumnSecurity::class
);
if (null !== $annotation) {
//Check if the current property is an DBElement
$property->setAccessible(true);
$value = $property->getValue($element);
if ($value instanceof DBElement && !$this->security->isGranted($annotation->getEditOperationName(), $element)) {
$property->setValue($element, null);
}
}
}
}
@ -97,7 +135,7 @@ class ElementPermissionListener
foreach ($properties as $property) {
/**
* @var ColumnSecurity
* @var ColumnSecurity $annotation
*/
$annotation = $this->reader->getPropertyAnnotation($property,
ColumnSecurity::class);
@ -108,7 +146,8 @@ class ElementPermissionListener
//Check if user is allowed to edit info, otherwise overwrite the new value
// so that nothing is changed in the DB.
if ($event->hasChangedField($field_name) &&
!$this->security->isGranted($annotation->getEditOperationName(), $element)) {
(!$this->security->isGranted($annotation->getEditOperationName(), $element)
|| !$this->security->isGranted($annotation->getReadOperationName(), $element))) {
$event->setNewValue($field_name, $event->getOldValue($field_name));
}
}

View file

@ -11,7 +11,11 @@
<div class="col-md-9">
<h5 class="text-muted pt-2" title="{% trans %}manufacturer.label{% endtrans %}">
{% if part.manufacturer %}
<a href="{{ part.manufacturer | entityURL('list_parts') }}">{{ part.manufacturer.name}}</a>
{% if part.manufacturer.id is not null %}
<a href="{{ part.manufacturer | entityURL('list_parts') }}">{{ part.manufacturer.name}}</a>
{% else %}
{{ part.manufacturer.name }}
{% endif %}
{% endif %}
{% if part.manufacturerProductUrl %}
<small>

View file

@ -81,7 +81,7 @@
<ul class="structural_link d-inline">
{% for e in entity.pathArray %}
<li>
{% if link_type is not empty %}
{% if link_type is not empty and e.id is not null %}
<a href="{{ e | entityURL(link_type) }}">{{ e.name }}</a>
{% else %}
{{ e.name }}
@ -99,7 +99,7 @@
<ol class="breadcrumb">
{% for e in entity.pathArray %}
<li class="breadcrumb-item {% if loop.last %}active{% endif %}">
{% if link_type is not empty and not loop.last %}
{% if link_type is not empty and not loop.last and e.id is not null %}
<a href="{{ e | entityURL(link_type) }}">{{ e.name }}</a>
{% else %}
{{ e.name }}