diff --git a/config/bolt/permissions.yaml b/config/bolt/permissions.yaml index bf976685b..ae0b5d79e 100644 --- a/config/bolt/permissions.yaml +++ b/config/bolt/permissions.yaml @@ -1,147 +1,150 @@ -# This file defines role-based access control for your Bolt site. -# Before making any modifications to this file, make sure you've thoroughly -# read the documentation at https://docs.bolt.cm/configuration/permissions -# and understand the consequences of making uninformed changes to the roles and -# permissions. - -# List of roles that are presented in the list of options when editing a user. -# Roles that are not in this list are left 'as is' when editing users. -# Note: ROLE_USER is assigned to Bolt Entity Users if no roles have been set. -assignable_roles: [ROLE_DEVELOPER, ROLE_ADMIN, ROLE_CHIEF_EDITOR, ROLE_EDITOR, ROLE_USER] - -# These permissions are the 'global' permissions; these are not tied -# to any content types, but rather apply to global, non-content activity in -# Bolt's backend. Most of these permissions map directly to backend routes; -# keep in mind, however, that routes do not always correspond to URL paths 1:1. -# The default set defined here is appropriate for most sites, so most likely, -# you will not have to change it. -# Also note that the 'editcontent' and 'overview' routes are special-cased -# inside the code, so they don't appear here. -global: - about: [ ROLE_EDITOR ] # view the 'About Bolt' page - clearcache: [ ROLE_CHIEF_EDITOR ] - dashboard: [ IS_AUTHENTICATED_REMEMBERED ] - extensions: [ ROLE_ADMIN ] - # these control /bolt/file-edit and /bolt/filemanager -> combined create/read/update/delete permission - # the part after the files: is the 'location' where the files are part of - managefiles:config: [ ROLE_ADMIN ] # all configuration yml files /bolt/filemanager/config and /bolt/file-edit/config?file=/bolt/menu.yaml - managefiles:files: [ ROLE_EDITOR ] - managefiles:themes: [ ROLE_ADMIN ] - editprofile: [ IS_AUTHENTICATED_FULLY ] # edit own profile - translation: [ ROLE_ADMIN ] - user:list: [ ROLE_ADMIN ] # overview listing of users and a list of active sessions - user:add: [ ROLE_ADMIN ] # add user - allows editing user _before_ saving, can set roles, status on create, after saving 'useredit' is needed. - user:status: [ ROLE_ADMIN ] # user enable/disable - only used for changing status outside of add/edit context - user:delete: [ ROLE_ADMIN ] # user delete - user:edit: [ ROLE_ADMIN ] # user edit all fields, includes user:status permissions - maintenance-mode: [ ROLE_EDITOR ] # view the frontend when in maintenance mode - systemlog: [ ROLE_ADMIN ] - api_admin: [ ROLE_ADMIN ] # WARNING: this only shows/hides api in the bolt admin, it doesn't protect the /api route(s) - bulk_operations: [ ROLE_CHIEF_EDITOR ] - kitchensink: [ ROLE_ADMIN ] - upload: [ ROLE_EDITOR ] - extensionmenus: [ IS_AUTHENTICATED_REMEMBERED ] # allows you to see menu items added by extensions - media_edit: [ ROLE_CHIEF_EDITOR ] # edit metadata for images etc. - fetch_embed_data: [ ROLE_EDITOR ] # get embed (meta)data for urls via the back-end (needed to embed youtube etc.) - list_files:config: [ ROLE_ADMIN ] # should probably not be used? - list_files:files: [ ROLE_EDITOR ] # get list of files (images?) available for use as site-content - list_files:themes: [ ROLE_ADMIN ] # should probably not be used? - -# For content type related actions, permissions can be set individually for -# each content type. For this, we define three groups of permission sets. -# The 'contenttype-base' permission sets *overrides*; any roles specified here -# will grant a permission for all content types, regardless of the rest of this -# section. -# The 'contenttype-default' contains rules that are used when the desired -# content type does not define a rule for this permission itself. -# The 'contenttypes' section specifies permissions for individual content -# types. -# -# To understand how this works, it may be best to follow the permission checker -# through its decision-making process. -# -# First it checks whether any of the current user's roles match any of the -# roles in contenttype-base/{permission}. If so, the search is over, and the -# permission can be granted. -# -# The next step is to find contenttypes/{contenttype}/{permission}. If it is -# found, then the permission can be granted if and only if any of the user's -# roles match any role in contenttypes/{contenttype}/{permission}. -# -# If either contenttypes/{contenttype} or -# contenttypes/{contenttype}/{permission} is absent, the permission checker -# uses contenttype-default/{permission} instead. If any role exists in both the -# user's roles and contenttype-default/{permission}, the permission can be -# granted. -# -# Note especially that an *empty* set of roles in the contenttype section means -# something else than the *absence* of the permission. If the permission is -# defined with an empty role list, it overrides the role list in -# contenttype-default; but if the permission is not mentioned, the -# corresponding entry in contenttype-default applies. -# -# The following permissions are available on a per-contenttype basis: -# -# - edit: allows updating existing records -# - create: allows creating new records -# - change-status: allows changing the published status of a record -# - delete: allows (hard) deletion of records -# - change-ownership: allows changing a record's owner. Note that ownership may -# grant additional permissions on a record, so this -# permission can indirectly enable users more permissions -# in ways that may not be immediately obvious. -# - view: allows viewing records in the backend (listings, content/fields) -# -# Note that all permissions imply 'view' permission, so you don't have to give 'view' along with 'edit' -# Note change-ownership is available as a setting but is not implemented in the bolt admin at the moment - -# these permissions will be set for all contenttypes, config below can add additional roles to these, but they can not be overridden -contenttype-base: - edit: [ ROLE_CHIEF_EDITOR ] - create: [ ROLE_CHIEF_EDITOR ] - change-status: [ ROLE_CHIEF_EDITOR ] # Note: You can have 'change-status' permission without 'edit' but you cannot use that at the moment as there is no screen that only handles status changes - delete: [ ROLE_CHIEF_EDITOR ] - change-ownership: [ ROLE_CHIEF_EDITOR ] - view: [ ROLE_CHIEF_EDITOR ] # = show in menu, show listings, open 'edit' view without actually being able to edit, any of the other permissions always imply 'view' - -# these permissions are used as a default for contenttypes, they are added to the base permissions -# you can override these settings per contenttype by adding it to the `contenttypes:` array -contenttype-default: - edit: [ ROLE_EDITOR, CONTENT_OWNER ] - create: [ ROLE_EDITOR ] - change-ownership: [ CONTENT_OWNER ] # <-- NOT IMPLEMENTED YET (and: how to handle chance-ownership permission without 'edit'?) - view: [ ROLE_EDITOR ] - - -contenttypes: - -# This is an example of how to define Contenttype specific permissions -# -# contenttypes: -# # Keys in this dictionary map to keys in the contenttypes.yml specification. -# showcases: -# # Rules defined here *override* rules defined in contenttype-default, -# # but *add* to rules in contenttype-base. This means that permissions -# # granted through contenttype-base cannot be revoked here, merely -# # amended. -# -# # Only the Admin and Chief Editor are allowed to edit records -# edit: [ ROLE_CHIEF_EDITOR ] -# create: [ ROLE_CHIEF_EDITOR ] -# change-status: [ ROLE_CHIEF_EDITOR ] -# delete: [ ROLE_CHIEF_EDITOR ] -# pages: -# edit: [ ROLE_EDITOR, CONTENT_OWNER ] -# create: [ ROLE_EDITOR ] -# change-ownership: [ CONTENT_OWNER ] -# view: [ ROLE_USER ] -# entries: -# edit: [ ROLE_EDITOR ] -# edit: [ ROLE_EDITOR, CONTENT_OWNER ] -# create: [ ROLE_EDITOR ] -# change-ownership: [ CONTENT_OWNER ] -# view: [ ROLE_EDITOR ] -# homepage: # singleton -# view: [ ROLE_EDITOR ] -# edit: [ ROLE_EDITOR ] -# create: [ ROLE_EDITOR ] +# This file defines role-based access control for your Bolt site. +# Before making any modifications to this file, make sure you've thoroughly +# read the documentation at https://docs.bolt.cm/configuration/permissions +# and understand the consequences of making uninformed changes to the roles and +# permissions. + +# List of roles that are presented in the list of options when editing a user. +# Roles that are not in this list are left 'as is' when editing users. +# Note: ROLE_USER is assigned to Bolt Entity Users if no roles have been set. +assignable_roles: [ROLE_DEVELOPER, ROLE_ADMIN, ROLE_CHIEF_EDITOR, ROLE_EDITOR, ROLE_USER] + +# These permissions are the 'global' permissions; these are not tied +# to any content types, but rather apply to global, non-content activity in +# Bolt's backend. Most of these permissions map directly to backend routes; +# keep in mind, however, that routes do not always correspond to URL paths 1:1. +# The default set defined here is appropriate for most sites, so most likely, +# you will not have to change it. +# Also note that the 'editcontent' and 'overview' routes are special-cased +# inside the code, so they don't appear here. +global: + about: [ ROLE_EDITOR ] # view the 'About Bolt' page + clearcache: [ ROLE_CHIEF_EDITOR ] + dashboard: [ IS_AUTHENTICATED_REMEMBERED ] + extensions: [ ROLE_ADMIN ] + # these control /bolt/file-edit and /bolt/filemanager -> combined create/read/update/delete permission + # the part after the files: is the 'location' where the files are part of + managefiles:config: [ ROLE_ADMIN ] # all configuration yml files /bolt/filemanager/config and /bolt/file-edit/config?file=/bolt/menu.yaml + managefiles:files: [ ROLE_EDITOR ] + managefiles:themes: [ ROLE_ADMIN ] + editprofile: [ IS_AUTHENTICATED_FULLY ] # edit own profile + translation: [ ROLE_ADMIN ] + user:list: [ ROLE_ADMIN ] # overview listing of users and a list of active sessions + user:add: [ ROLE_ADMIN ] # add user - allows editing user _before_ saving, can set roles, status on create, after saving 'useredit' is needed. + user:status: [ ROLE_ADMIN ] # user enable/disable - only used for changing status outside of add/edit context + user:delete: [ ROLE_ADMIN ] # user delete + user:edit: [ ROLE_ADMIN ] # user edit all fields, includes user:status permissions + maintenance-mode: [ ROLE_EDITOR ] # view the frontend when in maintenance mode + systemlog: [ ROLE_ADMIN ] + api_admin: [ ROLE_ADMIN ] # WARNING: this only shows/hides api in the bolt admin, it doesn't protect the /api route(s) + bulk_operations: [ ROLE_EDITOR ] + kitchensink: [ ROLE_ADMIN ] + upload: [ ROLE_EDITOR ] # TODO PERMISSIONS upload media/files ? Or should this be handled by managefiles:files + extensionmenus: [ IS_AUTHENTICATED_REMEMBERED ] # allows you to see menu items added by extensions + media_edit: [ ROLE_CHIEF_EDITOR ] # edit metadata for images etc. + fetch_embed_data: [ ROLE_EDITOR ] # get embed (meta)data for urls via the back-end (needed to embed youtube etc.) + list_files:config: [ ROLE_ADMIN ] # should probably not be used? + list_files:files: [ ROLE_EDITOR ] # get list of files (images?) available for use as site-content + list_files:themes: [ ROLE_ADMIN ] # should probably not be used? + api:get: [ ROLE_WEBSERVICE ] # allow read access to Bolt's RESTful and GraphQL API + api:post: [ ROLE_WEBSERVICE ] # allow write access to Bolt's RESTful and GraphQL API + api:delete: [ ROLE_WEBSERVICE ] # allow delete access to Bolt's RESTful and GraphQL API + +# For content type related actions, permissions can be set individually for +# each content type. For this, we define three groups of permission sets. +# The 'contenttype-base' permission sets *overrides*; any roles specified here +# will grant a permission for all content types, regardless of the rest of this +# section. +# The 'contenttype-default' contains rules that are used when the desired +# content type does not define a rule for this permission itself. +# The 'contenttypes' section specifies permissions for individual content +# types. +# +# To understand how this works, it may be best to follow the permission checker +# through its decision-making process. +# +# First it checks whether any of the current user's roles match any of the +# roles in contenttype-base/{permission}. If so, the search is over, and the +# permission can be granted. +# +# The next step is to find contenttypes/{contenttype}/{permission}. If it is +# found, then the permission can be granted if and only if any of the user's +# roles match any role in contenttypes/{contenttype}/{permission}. +# +# If either contenttypes/{contenttype} or +# contenttypes/{contenttype}/{permission} is absent, the permission checker +# uses contenttype-default/{permission} instead. If any role exists in both the +# user's roles and contenttype-default/{permission}, the permission can be +# granted. +# +# Note especially that an *empty* set of roles in the contenttype section means +# something else than the *absence* of the permission. If the permission is +# defined with an empty role list, it overrides the role list in +# contenttype-default; but if the permission is not mentioned, the +# corresponding entry in contenttype-default applies. +# +# The following permissions are available on a per-contenttype basis: +# +# - edit: allows updating existing records +# - create: allows creating new records +# - change-status: allows changing the published status of a record +# - delete: allows (hard) deletion of records +# - change-ownership: allows changing a record's owner. Note that ownership may +# grant additional permissions on a record, so this +# permission can indirectly enable users more permissions +# in ways that may not be immediately obvious. +# - view: allows viewing records in the backend (listings, content/fields) +# +# Note that all permissions imply 'view' permission, so you don't have to give 'view' along with 'edit' +# Note change-ownership is available as a setting but is not implemented in the bolt admin at the moment + +# these permissions will be set for all contenttypes, config below can add additional roles to these, but they can not be overridden +contenttype-base: + edit: [ ROLE_CHIEF_EDITOR ] + create: [ ROLE_CHIEF_EDITOR ] + change-status: [ ROLE_CHIEF_EDITOR ] # Note: You can have 'change-status' permission without 'edit' but you cannot use that at the moment as there is no screen that only handles status changes + delete: [ ROLE_CHIEF_EDITOR ] + change-ownership: [ ROLE_CHIEF_EDITOR ] + view: [ ROLE_CHIEF_EDITOR ] # = show in menu, show listings, open 'edit' view without actually being able to edit, any of the other permissions always imply 'view' + +# these permissions are used as a default for contenttypes, they are added to the base permissions +# you can override these settings per contenttype by adding it to the `contenttypes:` array +contenttype-default: + edit: [ ROLE_EDITOR, CONTENT_OWNER ] + create: [ ROLE_EDITOR ] + change-ownership: [ CONTENT_OWNER ] # <-- NOT IMPLEMENTED YET (and: how to handle chance-ownership permission without 'edit'?) + view: [ ROLE_EDITOR ] + + +contenttypes: + +# This is an example of how to define Contenttype specific permissions +# +# contenttypes: +# # Keys in this dictionary map to keys in the contenttypes.yml specification. +# showcases: +# # Rules defined here *override* rules defined in contenttype-default, +# # but *add* to rules in contenttype-base. This means that permissions +# # granted through contenttype-base cannot be revoked here, merely +# # amended. +# +# # Only the Admin and Chief Editor are allowed to edit records +# edit: [ ROLE_CHIEF_EDITOR ] +# create: [ ROLE_CHIEF_EDITOR ] +# change-status: [ ROLE_CHIEF_EDITOR ] +# delete: [ ROLE_CHIEF_EDITOR ] +# pages: +# edit: [ ROLE_EDITOR, CONTENT_OWNER ] +# create: [ ROLE_EDITOR ] +# change-ownership: [ CONTENT_OWNER ] +# view: [ ROLE_USER ] +# entries: +# edit: [ ROLE_EDITOR ] +# edit: [ ROLE_EDITOR, CONTENT_OWNER ] +# create: [ ROLE_EDITOR ] +# change-ownership: [ CONTENT_OWNER ] +# view: [ ROLE_EDITOR ] +# homepage: # singleton +# view: [ ROLE_EDITOR ] +# edit: [ ROLE_EDITOR ] +# create: [ ROLE_EDITOR ] diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 220f35f6f..fa9eb01ee 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -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 @@ -20,7 +21,11 @@ security: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false - + + api: + pattern: ^/api + http_basic: ~ + main: pattern: ^/ anonymous: true diff --git a/config/services.yaml b/config/services.yaml index 12eeeab5f..266899f38 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -145,3 +145,7 @@ services: class: Jasny\Twig\ArrayExtension tags: - { name: twig.extension } + + Bolt\Api\ContentDataPersister: + decorates: 'api_platform.doctrine.orm.data_persister' + diff --git a/src/Api/ContentDataPersister.php b/src/Api/ContentDataPersister.php new file mode 100644 index 000000000..25ac9d3a2 --- /dev/null +++ b/src/Api/ContentDataPersister.php @@ -0,0 +1,58 @@ +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); + } +} diff --git a/src/Entity/Content.php b/src/Entity/Content.php index 5ed36877d..7d3a1dd94 100644 --- a/src/Entity/Content.php +++ b/src/Entity/Content.php @@ -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") @@ -47,7 +60,7 @@ class Content * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") - * @Groups("get_content") + * @Groups({"get_content", "api_write"}) */ private $id; @@ -55,7 +68,7 @@ class Content * @var string * * @ORM\Column(type="string", length=191) - * @Groups("get_content") + * @Groups({"get_content","api_write"}) */ private $contentType; @@ -71,7 +84,7 @@ class Content * @var string * * @ORM\Column(type="string", length=191) - * @Groups("get_content") + * @Groups({"get_content","api_write"}) */ private $status; @@ -79,7 +92,7 @@ class Content * @var \DateTime * * @ORM\Column(type="datetime") - * @Groups("get_content") + * @Groups({"get_content","api_write"}) */ private $createdAt; @@ -87,7 +100,7 @@ class Content * @var \DateTime|null * * @ORM\Column(type="datetime", nullable=true) - * @Groups("get_content") + * @Groups({"get_content","api_write"}) */ private $modifiedAt = null; @@ -95,7 +108,7 @@ class Content * @var \DateTime|null * * @ORM\Column(type="datetime", nullable=true) - * @Groups("get_content") + * @Groups({"get_content","api_write"}) */ private $publishedAt = null; @@ -103,7 +116,7 @@ class Content * @var \DateTime|null * * @ORM\Column(type="datetime", nullable=true) - * @Groups("get_content") + * @Groups({"get_content","api_write"}) */ private $depublishedAt = null; @@ -122,6 +135,7 @@ class Content * cascade={"persist"} * ) * @ORM\OrderBy({"sortorder": "ASC"}) + * @Groups("api_write") */ private $fields; @@ -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)); }); } diff --git a/src/Entity/Field.php b/src/Entity/Field.php index c5680665c..d1413cd2e 100644 --- a/src/Entity/Field.php +++ b/src/Entity/Field.php @@ -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") @@ -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; @@ -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; @@ -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); diff --git a/src/Entity/Field/CollectionField.php b/src/Entity/Field/CollectionField.php index 7c0ff9e56..02d1ab818 100644 --- a/src/Entity/Field/CollectionField.php +++ b/src/Entity/Field/CollectionField.php @@ -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); } diff --git a/src/Entity/Field/SetField.php b/src/Entity/Field/SetField.php index fdda6b441..f9c8fb3f4 100644 --- a/src/Entity/Field/SetField.php +++ b/src/Entity/Field/SetField.php @@ -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; } diff --git a/src/Entity/Relation.php b/src/Entity/Relation.php index 37972790e..e4d278322 100644 --- a/src/Entity/Relation.php +++ b/src/Entity/Relation.php @@ -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={ diff --git a/tests/cypress/integration/api_getcontent.spec.js b/tests/cypress/integration/api_getcontent.spec.js new file mode 100644 index 000000000..5dd0b4ae6 --- /dev/null +++ b/tests/cypress/integration/api_getcontent.spec.js @@ -0,0 +1,150 @@ +/// + +describe('As a user I want to fetch all contents of an API' , () => { + it('Checks that GET response equals 200', () => { + cy.login(); + cy.visit('/bolt/api'); + cy.get('#operations-Content-getContentCollection').eq(0).click(); + cy.get('.response-col_status').should('contain', '200'); + }) + + it('Checks if the contents.json is filled with all content', () => { + cy.login(); + cy.request({ + url: '/api/contents.json', + failOnStatusCode: false, + auth: { + username: 'admin', + password: 'admin%1', + }, + }).then((response) => { + return new Promise(resolve => { + expect(response).property('status').to.eq(200) + expect(response.body[0]).property('id').to.not.be.oneOf([null, ""]) + const respBody = response.body[0]; + const fieldId = respBody; + resolve(fieldId) + }); + }) + }) + + it('Check if it returns JSON of a single record', () => { + cy.login(); + cy.request({ + url: '/api/contents/1.json', + failOnStatusCode: false, + auth: { + username: 'admin', + password: 'admin%1', + }, + }).then((response) => { + return new Promise(resolve => { + expect(response).property('status').to.eq(200) + expect(response.body).property('id').to.not.be.oneOf([null, ""]) + const respBody = response.body[0]; + const fieldId = respBody; + resolve(fieldId) + }); + }) + }) + + it('Check if the JSON LD format is working', () => { + cy.login(); + cy.request({ + url: '/api/contents.jsonld', + failOnStatusCode: false, + auth: { + username: 'admin', + password: 'admin%1', + }, + }).then((response) => { + return new Promise(resolve => { + expect(response).property('status').to.eq(200) + expect(response.body).property('hydra:totalItems').to.not.be.oneOf([null, "", 0]) + const respBody = response.body; + const fieldId = respBody; + resolve(fieldId) + }); + }) + }) + //TODO fix this test once we can navigate inside object + it('Check if the JSON LD format is working for single contenttypes like homepage', () => { + cy.login(); + cy.request({ + url: '/api/contents.jsonld?contentType=homepage', + failOnStatusCode: false, + auth: { + username: 'admin', + password: 'admin%1', + }, + }).then((response) => { + return new Promise(resolve => { + expect(response).property('status').to.eq(200) + expect(response.body).property('hydra:totalItems').to.not.be.oneOf([null, "", 0]) + const respBody = response.body; + const fieldId = respBody; + resolve(fieldId) + }); + }) + }) + + it('Check if the JSON LD format is working for single records', () => { + cy.login(); + cy.request({ + url: '/api/contents/1.jsonld', + failOnStatusCode: false, + auth: { + username: 'admin', + password: 'admin%1', + }, + }).then((response) => { + return new Promise(resolve => { + expect(response).property('status').to.eq(200) + expect(response.body).property('id').to.not.be.oneOf([null, ""]) + const respBody = response.body; + const fieldId = respBody; + resolve(fieldId) + }); + }) + }) +}) + +describe('Test reading JSON Fields', () => { + it('should read the values of the returned data in JSON', () => { + cy.request({ + url:`/api/contents/1/fields.json`, + failOnStatusCode: false, + auth: { + username: 'admin', + password: 'admin%1', + }, + }).then((response) => { + return new Promise(resolve => { + expect(response).property('status').to.eq(200) + expect(response.body[0]).property('name').to.not.be.oneOf([null, ""]) + const respBody = response.body[0]; + const fieldId = respBody; + resolve(fieldId) + }); + }) + }) + + it('should read the values of the returned data in JSON ld', () => { + cy.request({ + url:`/api/contents/1/fields.jsonld`, + failOnStatusCode: false, + auth: { + username: 'admin', + password: 'admin%1', + }, + }).then((response) => { + return new Promise(resolve => { + expect(response).property('status').to.eq(200) + expect(response.body).property('hydra:totalItems').to.not.be.oneOf([null, "", 0]) + const respBody = response.body; + const fieldId = respBody; + resolve(fieldId) + }); + }) + }) +}) diff --git a/yaml-migrations/m_2021-11-23-security.yaml b/yaml-migrations/m_2021-11-23-security.yaml new file mode 100644 index 000000000..2afd52491 --- /dev/null +++ b/yaml-migrations/m_2021-11-23-security.yaml @@ -0,0 +1,14 @@ +# See: https://github.com/bolt/core/pull/2648 + +file: packages/security.yaml +since: 5.1.0 + +add: + security: + firewalls: + api: + pattern: ^/api + http_basic: ~ + + role_hierarchy: + ROLE_WEBSERVICE: [] diff --git a/yaml-migrations/m_2021-11-23-services.yaml b/yaml-migrations/m_2021-11-23-services.yaml new file mode 100644 index 000000000..71106f756 --- /dev/null +++ b/yaml-migrations/m_2021-11-23-services.yaml @@ -0,0 +1,9 @@ +# See: https://github.com/bolt/core/pull/2648 + +file: services.yaml +since: 5.1.0 + +add: + services: + Bolt\Api\ContentDataPersister: + decorates: 'api_platform.doctrine.orm.data_persister'