diff --git a/config/permissions.yaml b/config/permissions.yaml index 47ba61e2..9b615422 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -12,7 +12,7 @@ groups: perms: # Here comes a list with all Permission names (they have a perm_[name] coloumn in DB) -# Part related permissions + # Part related permissions parts: # e.g. this maps to perms_parts in User/Group database group: "parts" @@ -29,10 +29,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co label: "perm.create" bit: 4 alsoSet: ['read', 'edit'] - move: - label: "perm.part.move" - bit: 6 - alsoSet: 'read' + #move: + # label: "perm.part.move" + # bit: 6 + # alsoSet: 'read' delete: label: "perm.delete" bit: 8 @@ -80,15 +80,15 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co bit: 2 alsoSet: 'read' + parts_category: + <<: *PART_ATTRIBUTE + label: "perm.part.category" + parts_description: <<: *PART_ATTRIBUTE label: "perm.part.description" - parts_instock: - <<: *PART_ATTRIBUTE - label: "perm.part.instock" - - parts_mininstock: + parts_minamount: <<: *PART_ATTRIBUTE label: "perm.part.mininstock" @@ -100,24 +100,61 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co <<: *PART_ATTRIBUTE label: "perm.part.comment" - parts_storelocation: - <<: *PART_ATTRIBUTE - label: "perm.part.storelocation" - parts_manufacturer: <<: *PART_ATTRIBUTE label: "perm.part.manufacturer" - parts_orderdetails: + parts_mpn: <<: *PART_ATTRIBUTE + label: "perm.part.mpn" + + parts_status: + <<: *PART_ATTRIBUTE + label: "perm.part.status" + + parts_tags: + <<: *PART_ATTRIBUTE + label: "perm.part.tags" + + parts_unit: + <<: *PART_ATTRIBUTE + label: "perm.part.unit" + + parts_mass: + <<: *PART_ATTRIBUTE + label: "perm.part.mass" + + parts_orderdetails: &PART_MULTI_ATTRIBUTE label: "perm.part.orderdetails" + group: "parts" + operations: + read: + label: "perm.read" + bit: 0 + edit: + label: "perm.edit" + bit: 2 + alsoSet: 'read' + create: + label: "perm.create" + bit: 4 + alsoSet: ['read', 'edit'] + delete: + label: "perm.delete" + bit: 6 + alsoSet: ['read'] + parts_prices: - <<: *PART_ATTRIBUTE + <<: *PART_MULTI_ATTRIBUTE label: "perm.part.prices" + parts_lots: + <<: *PART_MULTI_ATTRIBUTE + label: "perm.part.lots" + parts_attachments: - <<: *PART_ATTRIBUTE + <<: *PART_MULTI_ATTRIBUTE label: "perm.part.attachments" parts_order: @@ -150,7 +187,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co label: "perm.list_parts" bit: 10 show_users: - label: "perm.list_parts" + label: "perm.show_users" bit: 12 footprints: @@ -177,6 +214,14 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co <<: *PART_CONTAINING label: "perm.part.attachment_types" + currencies: + <<: *PART_CONTAINING + label: "perm.currencies" + + measurement_units: + <<: *PART_CONTAINING + label: "perm.measurement_units" + tools: label: "perm.part.tools" operations: diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index f8987a9d..2c2bc58d 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -130,7 +130,7 @@ class Part extends AttachmentContainingDBElement * @var Orderdetail[] * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) * @Assert\Valid() - * @ColumnSecurity(prefix="orderdetails", type="object") + * @ColumnSecurity(prefix="orderdetails", type="collection") */ protected $orderdetails; diff --git a/src/Entity/UserSystem/PermissionsEmbed.php b/src/Entity/UserSystem/PermissionsEmbed.php index a6802ffc..cddd7788 100644 --- a/src/Entity/UserSystem/PermissionsEmbed.php +++ b/src/Entity/UserSystem/PermissionsEmbed.php @@ -129,6 +129,11 @@ class PermissionsEmbed */ protected $parts_name = 0; + /** @var int + * @ORM\Column(type="smallint") + */ + protected $parts_category = 0; + /** * @var int * @ORM\Column(type="smallint") @@ -139,13 +144,7 @@ class PermissionsEmbed * @var int * @ORM\Column(type="smallint") */ - protected $parts_instock = 0; - - /** - * @var int - * @ORM\Column(type="smallint") - */ - protected $parts_mininstock = 0; + protected $parts_minamount = 0; /** * @var int @@ -157,7 +156,24 @@ class PermissionsEmbed * @var int * @ORM\Column(type="smallint") */ - protected $parts_storelocation = 0; + protected $parts_lots = 0; + + /** + * @var int + * @ORM\Column(type="smallint") + */ + protected $parts_tags = 0; + + /** @var int + * @ORM\Column(type="smallint") + */ + protected $parts_unit = 0; + + /** + * @var int + * @ORM\Column(type="smallint") + */ + protected $parts_mass = 0; /** * @var int @@ -165,6 +181,18 @@ class PermissionsEmbed */ protected $parts_manufacturer = 0; + /** + * @var int + * @ORM\Column(type="smallint") + */ + protected $parts_status = 0; + + /** + * @var int + * @ORM\Column(type="smallint") + */ + protected $parts_mpn = 0; + /** * @var int * @ORM\Column(type="smallint") @@ -243,6 +271,17 @@ class PermissionsEmbed */ protected $attachment_types = 0; + /** @var int + * @ORM\Column(type="integer") + */ + protected $currencies = 0; + + /** + * @var int + * @ORM\Column(type="integer") + */ + protected $measurement_units = 0; + /** * @var int * @ORM\Column(type="integer") @@ -276,7 +315,7 @@ class PermissionsEmbed public function getBitValue(string $permission_name, int $bit_n): int { if(!$this->isValidPermissionName($permission_name)) { - throw new \InvalidArgumentException('No permission with the given name is existing!'); + throw new \InvalidArgumentException(sprintf('No permission with the name "%s" is existing!', $permission_name)); } $perm_int = $this->$permission_name; diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 044c7fd6..ead625dc 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -166,6 +166,12 @@ class User extends NamedDBElement implements UserInterface, HasPermissionsInterf */ protected $group; + /** + * @var array + * @ORM\Column(type="json") + */ + protected $settings = []; + /** @var PermissionsEmbed * @ORM\Embedded(class="PermissionsEmbed", columnPrefix="perms_") * @ValidPermission() @@ -173,7 +179,8 @@ class User extends NamedDBElement implements UserInterface, HasPermissionsInterf protected $permissions; /** - * @ORM\Column(type="string", name="config_currency") + * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency", fetch="EAGER") + * @ORM\JoinColumn(name="currency_id", referencedColumnName="id") */ protected $currency = ""; diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index 9590fd4b..8aeacd53 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -109,13 +109,13 @@ class PartBaseType extends AbstractType 'attr' => ['min' => 0, 'placeholder' => $this->trans->trans('part.editmininstock.placeholder')], 'label' => $this->trans->trans('part.edit.mininstock'), 'measurement_unit' => $part->getPartUnit(), - 'disabled' => !$this->security->isGranted('mininstock.edit', $part), + 'disabled' => !$this->security->isGranted('minamount.edit', $part), ]) ->add('category', StructuralEntityType::class, [ 'class' => Category::class, 'label' => $this->trans->trans('part.edit.category'), 'disable_not_selectable' => true, - 'disabled' => !$this->security->isGranted('move', $part), + 'disabled' => !$this->security->isGranted('category.edit', $part), ]) ->add('footprint', StructuralEntityType::class, [ 'class' => Footprint::class, @@ -129,7 +129,7 @@ class PartBaseType extends AbstractType 'label' => $this->trans->trans('part.edit.tags'), 'empty_data' => "", 'attr' => ['data-role' => 'tagsinput'], - 'disabled' => !$this->security->isGranted('edit', $part) + 'disabled' => !$this->security->isGranted('tags.edit', $part) ]); //Manufacturer section @@ -139,46 +139,50 @@ class PartBaseType extends AbstractType 'label' => $this->trans->trans('part.edit.manufacturer.label'), 'disable_not_selectable' => true, 'disabled' => !$this->security->isGranted('manufacturer.edit', $part) - ]) + ]) ->add('manufacturer_product_url', UrlType::class, [ 'required' => false, 'empty_data' => '', 'label' => $this->trans->trans('part.edit.manufacturer_url.label'), - 'disabled' => !$this->security->isGranted('manufacturer.edit', $part), + 'disabled' => !$this->security->isGranted('mpn.edit', $part), ]) ->add('manufacturer_product_number', TextType::class, [ 'required' => false, 'empty_data' => '', 'label' => $this->trans->trans('part.edit.mpn'), - 'disabled' => !$this->security->isGranted('manufacturer.edit', $part)]) + 'disabled' => !$this->security->isGranted('mpn.edit', $part)]) ->add('manufacturing_status', ChoiceType::class, [ 'label' => $this->trans->trans('part.edit.manufacturing_status'), 'choices' => $status_choices, 'required' => false, - 'disabled' => !$this->security->isGranted('manufacturer.edit', $part) + 'disabled' => !$this->security->isGranted('status.edit', $part) ]); //Advanced section $builder->add('needsReview', CheckboxType::class, [ 'label_attr' => ['class' => 'checkbox-custom'], 'required' => false, - 'label' => $this->trans->trans('part.edit.needs_review') + 'label' => $this->trans->trans('part.edit.needs_review'), + 'disabled' => !$this->security->isGranted('edit', $part) ]) ->add('favorite', CheckboxType::class, [ 'label_attr' => ['class' => 'checkbox-custom'], 'required' => false, - 'label' => $this->trans->trans('part.edit.is_favorite') + 'label' => $this->trans->trans('part.edit.is_favorite'), + 'disabled' => !$this->security->isGranted('change_favorite', $part) ]) ->add('mass', SIUnitType::class, [ 'unit' => 'g', 'label' => $this->trans->trans('part.edit.mass'), - 'required' => false + 'required' => false, + 'disabled' => !$this->security->isGranted('mass.edit', $part) ]) ->add('partUnit', StructuralEntityType::class, [ 'class' => MeasurementUnit::class, 'required' => false, 'disable_not_selectable' => true, - 'label' => $this->trans->trans('part.edit.partUnit') + 'label' => $this->trans->trans('part.edit.partUnit'), + 'disabled' => !$this->security->isGranted('unit.edit', $part) ]); @@ -193,10 +197,12 @@ class PartBaseType extends AbstractType //Part Lots section $builder->add('partLots', CollectionType::class, [ 'entry_type' => PartLotType::class, - 'allow_add' => true, 'allow_delete' => true, + 'allow_add' => $this->security->isGranted('lots.create', $part), + 'allow_delete' => $this->security->isGranted('lots.delete', $part), 'label' => false, 'entry_options' => [ - 'measurement_unit' => $part->getPartUnit() + 'measurement_unit' => $part->getPartUnit(), + 'disabled' => !$this->security->isGranted('lots.edit', $part), ], 'by_reference' => false ]); @@ -204,16 +210,19 @@ class PartBaseType extends AbstractType //Attachment section $builder->add('attachments', CollectionType::class, [ 'entry_type' => AttachmentFormType::class, - 'allow_add' => true, 'allow_delete' => true, + 'allow_add' => $this->security->isGranted('attachments.create', $part), + 'allow_delete' => $this->security->isGranted('attachments.delete', $part), 'label' => false, 'entry_options' => [ - 'data_class' => PartAttachment::class + 'data_class' => PartAttachment::class, + 'disabled' => !$this->security->isGranted('attachments.edit', $part), ], 'by_reference' => false ]); $builder->add('master_picture_attachment', EntityType::class, [ 'required' => false, + 'disabled' => !$this->security->isGranted('attachments.edit', $part), 'label' => $this->trans->trans('part.edit.master_attachment'), 'class' => PartAttachment::class, 'attr' => ['class' => 'selectpicker'], @@ -238,12 +247,14 @@ class PartBaseType extends AbstractType //Orderdetails section $builder->add('orderdetails', CollectionType::class, [ 'entry_type' => OrderdetailType::class, - 'allow_add' => true, 'allow_delete' => true, + 'allow_add' => $this->security->isGranted('orderdetails.create', $part), + 'allow_delete' => $this->security->isGranted('orderdetails.delete', $part), 'label' => false, 'by_reference' => false, 'prototype_data' => new Orderdetail(), 'entry_options' => [ - 'measurement_unit' => $part->getPartUnit() + 'measurement_unit' => $part->getPartUnit(), + 'disabled' => !$this->security->isGranted('attachments.edit', $part), ] ]); diff --git a/src/Form/Part/PartLotType.php b/src/Form/Part/PartLotType.php index 4930ca3c..8412fc01 100644 --- a/src/Form/Part/PartLotType.php +++ b/src/Form/Part/PartLotType.php @@ -51,16 +51,19 @@ use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; use function GuzzleHttp\Promise\queue; class PartLotType extends AbstractType { protected $trans; + protected $security; - public function __construct(TranslatorInterface $trans) + public function __construct(TranslatorInterface $trans, Security $security) { $this->trans = $trans; + $this->security = $security; } public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/src/Migrations/Version20190913141126.php b/src/Migrations/Version20190913141126.php new file mode 100644 index 00000000..88e0cb77 --- /dev/null +++ b/src/Migrations/Version20190913141126.php @@ -0,0 +1,41 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE `groups` ADD perms_parts_category SMALLINT NOT NULL, ADD perms_parts_minamount SMALLINT NOT NULL, ADD perms_parts_lots SMALLINT NOT NULL, ADD perms_parts_tags SMALLINT NOT NULL, ADD perms_parts_unit SMALLINT NOT NULL, ADD perms_parts_mass SMALLINT NOT NULL, ADD perms_parts_status SMALLINT NOT NULL, ADD perms_parts_mpn SMALLINT NOT NULL, ADD perms_currencies INT NOT NULL, ADD perms_measurement_units INT NOT NULL, DROP perms_parts_instock, DROP perms_parts_mininstock, DROP perms_parts_storelocation'); + $this->addSql('ALTER TABLE users ADD currency_id INT DEFAULT NULL, ADD settings LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', ADD perms_parts_category SMALLINT NOT NULL, ADD perms_parts_minamount SMALLINT NOT NULL, ADD perms_parts_lots SMALLINT NOT NULL, ADD perms_parts_tags SMALLINT NOT NULL, ADD perms_parts_unit SMALLINT NOT NULL, ADD perms_parts_mass SMALLINT NOT NULL, ADD perms_parts_status SMALLINT NOT NULL, ADD perms_parts_mpn SMALLINT NOT NULL, ADD perms_currencies INT NOT NULL, ADD perms_measurement_units INT NOT NULL, DROP config_currency, DROP perms_parts_instock, DROP perms_parts_mininstock, DROP perms_parts_storelocation'); + $this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE `groups` ADD perms_parts_instock SMALLINT NOT NULL, ADD perms_parts_mininstock SMALLINT NOT NULL, ADD perms_parts_storelocation SMALLINT NOT NULL, DROP perms_parts_category, DROP perms_parts_minamount, DROP perms_parts_lots, DROP perms_parts_tags, DROP perms_parts_unit, DROP perms_parts_mass, DROP perms_parts_status, DROP perms_parts_mpn, DROP perms_currencies, DROP perms_measurement_units'); + $this->addSql('ALTER TABLE `users` DROP FOREIGN KEY FK_1483A5E938248176'); + $this->addSql('DROP INDEX IDX_1483A5E938248176 ON `users`'); + $this->addSql('ALTER TABLE `users` ADD config_currency VARCHAR(255) NOT NULL COLLATE utf8_general_ci, ADD perms_parts_instock SMALLINT NOT NULL, ADD perms_parts_mininstock SMALLINT NOT NULL, ADD perms_parts_storelocation SMALLINT NOT NULL, DROP currency_id, DROP settings, DROP perms_parts_category, DROP perms_parts_minamount, DROP perms_parts_lots, DROP perms_parts_tags, DROP perms_parts_unit, DROP perms_parts_mass, DROP perms_parts_status, DROP perms_parts_mpn, DROP perms_currencies, DROP perms_measurement_units'); + } +} diff --git a/src/Security/Annotations/ColumnSecurity.php b/src/Security/Annotations/ColumnSecurity.php index c56f1518..6a35eb39 100644 --- a/src/Security/Annotations/ColumnSecurity.php +++ b/src/Security/Annotations/ColumnSecurity.php @@ -30,6 +30,7 @@ namespace App\Security\Annotations; use Doctrine\Common\Annotations\Annotation; +use Doctrine\Common\Collections\ArrayCollection; use \InvalidArgumentException; /** @@ -65,7 +66,7 @@ class ColumnSecurity /** * @var string The name of the property. This is used to determine the default placeholder. - * @Annotation\Enum({"integer", "string", "object", "boolean", "datetime"}) + * @Annotation\Enum({"integer", "string", "object", "boolean", "datetime", "collection"}) */ public $type = 'string'; @@ -97,6 +98,8 @@ class ColumnSecurity return '???'; case 'object': return null; + case 'collection': + return new ArrayCollection(); case 'boolean': return false; case 'datetime': diff --git a/src/Security/Voter/PartVoter.php b/src/Security/Voter/PartVoter.php index e59b0fe2..186f5b92 100644 --- a/src/Security/Voter/PartVoter.php +++ b/src/Security/Voter/PartVoter.php @@ -26,10 +26,10 @@ class PartVoter extends ExtendedVoter if (false !== strpos($attribute, '.')) { [$perm, $op] = explode('.', $attribute); - return in_array($op, $this->resolver->listOperationsForPermission('parts_'.$perm), false); + return $this->resolver->isValidOperation('parts_' . $perm, $op); } - return in_array($attribute, $this->resolver->listOperationsForPermission('parts'), false); + return $this->resolver->isValidOperation('parts', $attribute); } return false; diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 3d1c7a6b..d0302c95 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -86,11 +86,9 @@ class StructureVoter extends ExtendedVoter case Supplier::class: return 'suppliers'; case Currency::class: - //TODO: Implement own permission - return 'suppliers'; + return 'currencies'; case MeasurementUnit::class: - //TODO: Implement own permission - return 'suppliers'; + return 'measurement_units'; } //When the class is not supported by this class return null return null; diff --git a/templates/Parts/edit/_attachments.html.twig b/templates/Parts/edit/_attachments.html.twig index 3255dc34..8c5b5c20 100644 --- a/templates/Parts/edit/_attachments.html.twig +++ b/templates/Parts/edit/_attachments.html.twig @@ -1,5 +1,5 @@ {% set delete_btn %} - @@ -7,7 +7,7 @@ {{ form_row(form.master_picture_attachment) }} - +
{% for attachment in form.attachments %} @@ -54,7 +54,7 @@
- diff --git a/templates/Parts/edit/_lots.html.twig b/templates/Parts/edit/_lots.html.twig index 7d3b13de..e90ddadd 100644 --- a/templates/Parts/edit/_lots.html.twig +++ b/templates/Parts/edit/_lots.html.twig @@ -1,12 +1,13 @@ {% set delete_btn %} - {% endset %} - +
{% for lot in form.partLots %} @@ -21,7 +22,8 @@
- diff --git a/templates/Parts/edit/_orderdetails.html.twig b/templates/Parts/edit/_orderdetails.html.twig index feb0edb9..a6e85760 100644 --- a/templates/Parts/edit/_orderdetails.html.twig +++ b/templates/Parts/edit/_orderdetails.html.twig @@ -1,14 +1,14 @@ {% form_theme form with ['Parts/edit/edit_form_styles.html.twig', "bootstrap_4_layout.html.twig"] %} - +
{% for detail in form.orderdetails %} - {{ form_widget(detail) }} + {{ form_widget(detail, {'disable_delete' : not is_granted('orderdetails.delete', part)}) }} {% endfor %}
- diff --git a/templates/Parts/edit/edit_form_styles.html.twig b/templates/Parts/edit/edit_form_styles.html.twig index 1cea4dcf..5f2e96a6 100644 --- a/templates/Parts/edit/edit_form_styles.html.twig +++ b/templates/Parts/edit/edit_form_styles.html.twig @@ -53,7 +53,7 @@ - {{ form_errors(form) }}