diff --git a/core/data-providers.md b/core/data-providers.md index 46ca7595370..819e4175c28 100644 --- a/core/data-providers.md +++ b/core/data-providers.md @@ -115,4 +115,4 @@ services: Previous chapter: [Extending JSON-LD context](extending-jsonld-context.md) -Next chapter: [Security](security.md) +Next chapter: [Extensions](extensions.md) diff --git a/core/extensions.md b/core/extensions.md new file mode 100644 index 00000000000..a3c33672756 --- /dev/null +++ b/core/extensions.md @@ -0,0 +1,169 @@ +# Extensions + +API Platform Core provides a system to extend queries on items and collections. + +Extensions are specific to Doctrine, and therefore, the Doctrine ORM support must be enabled to use this feature. If you use custom providers it's up to you to implement your own extension system or not. + +## Custom Extension + +Custom extensions must implement the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface` and / or the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface` interfaces, to be run when querying for a collection of items and when querying for an item respectively. + +If you use [custom data providers](data-providers.md), they must support extensions and be aware of active extensions to work properly. + +## Example + +In the following example, we will see how to always get the offers owned by the current user. We will set up an exception, whenever the user has the `ROLE_ADMIN`. + +Given these two entities: + +```php +propertyMetadataFactory = $propertyMetadataFactory; + $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; + $this->token = $token; + $this->authorizationChecker = $checker; + } + + /** + * {@inheritdoc} + */ + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + { + $this->addWhere($queryBuilder, $resourceClass); + } + + /** + * {@inheritdoc} + */ + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null) + { + $this->addWhere($queryBuilder, $resourceClass); + } + + /** + * + * @param QueryBuilder $queryBuilder + * @param string $resourceClass + */ + private function addWhere(QueryBuilder $queryBuilder, string $resourceClass) + { + $user = $this->token->getToken()->getUser(); + if ($user instanceof User && Offer::class === $resourceClass && !$this->authorizationChecker->isGranted('ROLE_ADMIN')) { + $rootAlias = $queryBuilder->getRootAliases()[0]; + $queryBuilder->andWhere(sprintf('%s.user = :current_user', $rootAlias)); + $queryBuilder->setParameter('current_user', $user->getId()); + } + } +} + +``` + +Finally register the custom extension: + +```yaml +# app/config/services.yml + +services: + app.doctrine.orm.query_extension.current_user: + class: AppBundle\Doctrine\ORM\Extension\CurrentUserExtension + public: false + arguments: + - '@api_platform.metadata.property.name_collection_factory' + - '@api_platform.metadata.property.metadata_factory' + - '@security.token_storage' + - '@security.authorization_checker' + tags: + - { name: api_platform.doctrine.orm.query_extension.collection, priority: 9 } + - { name: api_platform.doctrine.orm.query_extension.item } +``` + +Thanks to the `api_platform.doctrine.orm.query_extension.collection` tag, API Platform will register this service as a collection extension. The `api_platform.doctrine.orm.query_extension.item` do the same thing for items. + +Notice the priority level for the `api_platform.doctrine.orm.query_extension.collection` tag. When an extension implements the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface` or the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface` interface to return results by itself, any lower priority extension will not be executed. Because the pagination is enabled by default with a priority of 8, the priority of the `app.doctrine.orm.query_extension.current_user` service must be at least 9 to ensure its execution. + +### Blocking Anonymous Users + +This example adds a `WHERE` clause condition only when a fully authenticated user without `ROLE_ADMIN` tries to access to a resource. It means that anonymous users will be able to access to all data. To prevent this potential security issue, the API must ensure that the current user is authenticated. + +To secure the access to endpoints, use the following access control rule: + +```yaml +# app/config/security.yml + +security: + # ... + + access_control: + # ... + - { path: ^/offers, roles: IS_AUTHENTICATED_FULLY } + - { path: ^/users, roles: IS_AUTHENTICATED_FULLY } +``` + +Previous chapter: [Data Providers](data-providers.md) + +Next chapter: [Security](security.md) \ No newline at end of file diff --git a/core/security.md b/core/security.md index 357784be931..d820daed427 100644 --- a/core/security.md +++ b/core/security.md @@ -9,6 +9,6 @@ For instance, if you wish to restrict the access of some endpoints, you can use It is also possible to use the [event system](events.md) for more advanced logic or even [custom actions](operations.md#creating-custom-operations-and-controllers) if you really need to. -Previous chapter: [Data Providers](data-providers.md) +Previous chapter: [Extensions](extensions.md) Next chapter: [Performance](performance.md) diff --git a/core/serialization-groups-and-relations.md b/core/serialization-groups-and-relations.md index 5850eee4a6e..93693cdc209 100644 --- a/core/serialization-groups-and-relations.md +++ b/core/serialization-groups-and-relations.md @@ -333,7 +333,7 @@ final class BookContextBuilder implements SerializerContextBuilderInterface { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); $subject = $request->attributes->get('data'); - + if ($subject instanceof Book && $this->authorizationChecker->isGranted('ROLE_ADMIN') && false === $normalization) { $context['groups'][] = 'admin_input'; } diff --git a/index.md b/index.md index 7bbdd0fbc09..824b7a92f4f 100644 --- a/index.md +++ b/index.md @@ -57,24 +57,27 @@ 13. [Data Providers](core/data-providers.md) 1. [Custom Collection Data Provider](core/data-providers.md#creating-a-custom-data-provider#custom-collection-data-provider) 2. [Custom Item Data Provider](core/data-providers.md#returning-a-paged-collection#custom-item-data-provider) -14. [Security](core/security.md) -15. [Performance](core/performance.md) +14. [Extensions](core/extensions.md) + 1. [Custom Extension](core/extensions.md#custom-extension) + 2. [Filter upon the current user](core/extensions.md#example) +16. [Security](core/security.md) +17. [Performance](core/performance.md) 1. [Enabling the Metadata Cache](core/performance.md#enabling-the-metadata-cache) 2. [Using PPM (PHP-PM)](core/performance.md#using-ppm-php-pm) 3. [Doctrine Queries and Indexes](core/performance.md#doctrine-queries-and-indexes) 1. [Search Filter](core/performance.md#search-filter) 2. [Unserialized Properties Hydratation](core/performance.md#unserialized-properties-hydratation) -16. [Operation Path Naming](core/operation-path-naming.md) +18. [Operation Path Naming](core/operation-path-naming.md) 1. [Configuration](core/operation-path-naming.md#configuration) 2. [Create a Custom Operation Path Naming](core/operation-path-naming.md#create-a-custom-operation-path-resolver) 1. [Defining the Operation Path Naming](core/operation-path-naming.md#defining-the-operation-path-resolver) 2. [Registering the Service](core/operation-path-naming.md#registering-the-service) 3. [Configure it](core/operation-path-naming.md#configure-it) -17. [Accept `application/x-www-form-urlencoded` Form Data] (core/form-data.md) -18. [FOSUserBundle Integration](core/fosuser-bundle.md) +19. [Accept `application/x-www-form-urlencoded` Form Data] (core/form-data.md) +20. [FOSUserBundle Integration](core/fosuser-bundle.md) 1. [Creating a `User` Entity with Serialization Groups](core/fosuser-bundle.md#creating-a-user-entity-with-serialization-groups) -19. [NelmioApiDocBundle integration](core/nelmio-api-doc.md) -20. [AngularJS Integration](core/angularjs-integration.md) +21. [NelmioApiDocBundle integration](core/nelmio-api-doc.md) +22. [AngularJS Integration](core/angularjs-integration.md) 1. [Restangular](core/angularjs-integration.md#restangular) 2. [ng-admin](core/angularjs-integration.md#ng-admin)