Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Role-based API support for POST, PUT and DELETE operations #2648

Merged
merged 30 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f8003af
Role-based API support for `POST`, `PUT` and `DELETE` operations
I-Valchev Jun 21, 2021
8d7e6c3
Creating content
I-Valchev Nov 16, 2021
370c732
Add WIP for persistence
I-Valchev Nov 16, 2021
9ac0dbd
Make the correct field types
I-Valchev Nov 18, 2021
35d4a37
Update Content.php
I-Valchev Nov 18, 2021
d6d999c
Update FieldFillListener.php
I-Valchev Nov 18, 2021
bf0b5ce
Update permissions.yaml
I-Valchev Nov 19, 2021
bfb0bce
Role-based API support for `POST`, `PUT` and `DELETE` operations
I-Valchev Jun 21, 2021
9aed4a2
Creating content
I-Valchev Nov 16, 2021
2cb89aa
Add WIP for persistence
I-Valchev Nov 16, 2021
fc89223
Update Content.php
I-Valchev Nov 18, 2021
eda9c99
Update FieldFillListener.php
I-Valchev Nov 18, 2021
a938825
csfix
I-Valchev Nov 19, 2021
bb68e2d
Update security.yaml
I-Valchev Nov 19, 2021
ef16dff
Role-based API support for `POST`, `PUT` and `DELETE` operations
I-Valchev Jun 21, 2021
77f27dd
Creating content
I-Valchev Nov 16, 2021
abcb536
Add WIP for persistence
I-Valchev Nov 16, 2021
23ae2e5
Make the correct field types
I-Valchev Nov 18, 2021
5dce70b
Update Content.php
I-Valchev Nov 18, 2021
0e1c428
Update FieldFillListener.php
I-Valchev Nov 18, 2021
d932f8a
Creating content
I-Valchev Nov 16, 2021
36587fb
Add WIP for persistence
I-Valchev Nov 16, 2021
78b669d
Make the correct field types
I-Valchev Nov 18, 2021
f9722af
Update Content.php
I-Valchev Nov 18, 2021
b3e4415
Update FieldFillListener.php
I-Valchev Nov 18, 2021
9caa2a9
csfix
I-Valchev Nov 19, 2021
1755254
Cleanup and documenting
I-Valchev Nov 23, 2021
a6ddcef
Add yaml migrations
I-Valchev Nov 23, 2021
922d2b2
Add new tests and fix old ones
Joossensei Nov 23, 2021
3dfd45a
Revert changes to standard tests
Joossensei Nov 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 150 additions & 147 deletions config/bolt/permissions.yaml

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ security:
ROLE_EDITOR: [ROLE_USER]
# ROLE_USER is assigned to Bolt Entity Users if no roles have been set
ROLE_USER: []
ROLE_WEBSERVICE: []

encoders:
Bolt\Entity\User: auto
Expand All @@ -20,7 +21,11 @@ security:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false


api:
pattern: ^/api
http_basic: ~

main:
pattern: ^/
anonymous: true
Expand Down
4 changes: 4 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,7 @@ services:
class: Jasny\Twig\ArrayExtension
tags:
- { name: twig.extension }

Bolt\Api\ContentDataPersister:
decorates: 'api_platform.doctrine.orm.data_persister'

58 changes: 58 additions & 0 deletions src/Api/ContentDataPersister.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace Bolt\Api;

use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use Bolt\Configuration\Config;
use Bolt\Configuration\Content\FieldType;
use Bolt\Entity\Content;
use Bolt\Repository\FieldRepository;

class ContentDataPersister implements ContextAwareDataPersisterInterface
{
/** @var ContextAwareDataPersisterInterface */
private $decorated;

/** @var Config */
private $config;

public function __construct(ContextAwareDataPersisterInterface $decorated, Config $config)
{
$this->decorated = $decorated;
$this->config = $config;
}

public function supports($data, array $context = []): bool
{
return $this->decorated->supports($data, $context);
}

public function persist($data, array $context = [])
{
if ($data instanceof Content) {
$contentTypes = $this->config->get('contenttypes');

$data->setDefinitionFromContentTypesConfig($contentTypes);

foreach ($data->getFields() as $field) {
$fieldDefinition = FieldType::factory($field->getName(), $data->getDefinition());
$newField = FieldRepository::factory($fieldDefinition);

// todo: This works for standalone fields only.
// See CollectionField.php and SetField.php
$newField->setName($field->getName());
$newField->setValue($field->getValue());

$data->removeField($field);
$data->addField($newField);
}
}

$this->decorated->persist($data, $context);
}

public function remove($data, array $context = [])
{
$this->decorated->persist($data, $context);
}
}
45 changes: 33 additions & 12 deletions src/Entity/Content.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,22 @@
/**
* @ApiResource(
* normalizationContext={"groups"={"get_content","get_definition"}},
* collectionOperations={"get"},
* itemOperations={"get"},
* graphql={"item_query", "collection_query"}
* denormalizationContext={"groups"={"api_write"},"enable_max_depth"=true},
* collectionOperations={
* "get"={"security"="is_granted('api:get')"},
* "post"={"security"="is_granted('api:post')"}
* },
* itemOperations={
* "get"={"security"="is_granted('api:get')"},
* "put"={"security"="is_granted('api:post')"},
* "delete"={"security"="is_granted('api:delete')"}
* },
* graphql={
* "item_query"={"security"="is_granted('api:get')"},
* "collection_query"={"security"="is_granted('api:get')"},
* "create"={"security"="is_granted('api:post')"},
* "delete"={"security"="is_granted('api:delete')"}
* }
* )
* @ApiFilter(SearchFilter::class)
* @ORM\Entity(repositoryClass="Bolt\Repository\ContentRepository")
Expand All @@ -47,15 +60,15 @@ class Content
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups("get_content")
* @Groups({"get_content", "api_write"})
*/
private $id;

/**
* @var string
*
* @ORM\Column(type="string", length=191)
* @Groups("get_content")
* @Groups({"get_content","api_write"})
*/
private $contentType;

Expand All @@ -71,39 +84,39 @@ class Content
* @var string
*
* @ORM\Column(type="string", length=191)
* @Groups("get_content")
* @Groups({"get_content","api_write"})
*/
private $status;

/**
* @var \DateTime
*
* @ORM\Column(type="datetime")
* @Groups("get_content")
* @Groups({"get_content","api_write"})
*/
private $createdAt;

/**
* @var \DateTime|null
*
* @ORM\Column(type="datetime", nullable=true)
* @Groups("get_content")
* @Groups({"get_content","api_write"})
*/
private $modifiedAt = null;

/**
* @var \DateTime|null
*
* @ORM\Column(type="datetime", nullable=true)
* @Groups("get_content")
* @Groups({"get_content","api_write"})
*/
private $publishedAt = null;

/**
* @var \DateTime|null
*
* @ORM\Column(type="datetime", nullable=true)
* @Groups("get_content")
* @Groups({"get_content","api_write"})
*/
private $depublishedAt = null;

Expand All @@ -122,6 +135,7 @@ class Content
* cascade={"persist"}
* )
* @ORM\OrderBy({"sortorder": "ASC"})
* @Groups("api_write")
*/
private $fields;

Expand Down Expand Up @@ -727,10 +741,17 @@ private function convertToUTCFromLocal(?\DateTime $dateTime): ?\DateTime
*/
private function standaloneFieldsFilter(): Collection
{
$keys = $this->getDefinition()->get('fields')->keys()->all();
$definition = $this->getDefinition();

$keys = $definition ?
$this->getDefinition()->get('fields')->keys()->all()
: [];
// If the definition is missing, we cannot filter out keys. ¯\_(ツ)_/¯


return $this->fields->filter(function (Field $field) use ($keys) {
return ! $field->hasParent() && in_array($field->getName(), $keys, true);
return ! $field->hasParent() &&
(in_array($field->getName(), $keys, true) || empty($keys));
});
}

Expand Down
32 changes: 25 additions & 7 deletions src/Entity/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,29 @@

/**
* @ApiResource(
* denormalizationContext={"groups"={"api_write"},"enable_max_depth"=true},
* normalizationContext={"groups"={"get_field"}},
*
* subresourceOperations={
* "api_contents_fields_get_subresource"={
* "method"="GET",
* "normalization_context"={"groups"={"get_field"}}
* "method"="GET"
* },
* },
* collectionOperations={"get"},
* itemOperations={"get"},
* graphql={"item_query", "collection_query"}
* collectionOperations={
* "get"={"security"="is_granted('api:get')"},
* "post"={"security"="is_granted('api:post')"}
* },
* itemOperations={
* "get"={"security"="is_granted('api:get')"},
* "put"={"security"="is_granted('api:post')"},
* "delete"={"security"="is_granted('api:delete')"}
* },
* graphql={
* "item_query"={"security"="is_granted('api:get')"},
* "collection_query"={"security"="is_granted('api:get')"},
* "create"={"security"="is_granted('api:post')"},
* "delete"={"security"="is_granted('api:delete')"}
* }
* )
* @ApiFilter(SearchFilter::class)
* @ORM\Entity(repositoryClass="Bolt\Repository\FieldRepository")
Expand All @@ -53,7 +67,7 @@ class Field implements FieldInterface, TranslatableInterface

/**
* @ORM\Column(type="string", length=191)
* @Groups("get_field")
* @Groups({"get_field","api_write"})
*/
public $name;

Expand All @@ -64,8 +78,9 @@ class Field implements FieldInterface, TranslatableInterface
private $version;

/**
* @ORM\ManyToOne(targetEntity="Bolt\Entity\Content", inversedBy="fields", cascade={"persist"})
* @ORM\ManyToOne(targetEntity="Bolt\Entity\Content", inversedBy="fields", fetch="EAGER")
* @ORM\JoinColumn(nullable=false)
* @Groups("api_write")
*/
private $content;

Expand Down Expand Up @@ -279,6 +294,9 @@ public function set(string $key, $value): self
return $this;
}

/**
* @Groups("api_write")
*/
public function setValue($value): self
{
$this->translate($this->getLocale(), ! $this->isTranslatable())->setValue($value);
Expand Down
2 changes: 2 additions & 0 deletions src/Entity/Field/CollectionField.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public function setValue($fields): Field
{
/** @var Field $field */
foreach ($fields as $field) {
// todo: This should be able to handle an array of fields
// in key-value format, not just Field.php types.
$field->setParent($this);
}

Expand Down
2 changes: 2 additions & 0 deletions src/Entity/Field/SetField.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public function setValue($fields): Field

/** @var Field $field */
foreach ($fields as $field) {
// todo: This should be able to handle an array of fields
// in key-value format, not just Field.php types.
$field->setParent($this);
$value[$field->getName()] = $field;
}
Expand Down
18 changes: 15 additions & 3 deletions src/Entity/Relation.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,21 @@
/**
* @ApiResource(
* normalizationContext={"groups"={"get_relation"}},
* collectionOperations={"get"},
* itemOperations={"get"},
* graphql={"item_query", "collection_query"}
* collectionOperations={
* "get"={"security"="is_granted('api:get')"},
* "post"={"security"="is_granted(‘api:post’)"}
* },
* itemOperations={
* "get"={"security"="is_granted('api:get')"},
* "put"={"security"="is_granted('api:post')"},
* "delete"={"security"="is_granted('api:delete')"}
* },
* graphql={
* "item_query"={"security"="is_granted('api:get')"},
* "collection_query"={"security"="is_granted('api:get')"},
* "create"={"security"="is_granted('api:post')"},
* "delete"={"security"="is_granted('api:delete')"}
* }
* )
* @ORM\Entity(repositoryClass="Bolt\Repository\RelationRepository")
* @ORM\Table(indexes={
Expand Down
Loading