Posted over 3 years ago. Visible to the public.

OroPlatform API - Deleting Entity - API Testing - WSSE Authentication

In the Creating a Simple CRUD Archive cookbook, it uses REST API to delete entities Archive . However, the description isn't enough to properly implement the delete operation.

However, there are examples on how to do it in the \vendor\oro\platform codebase. Reference vendor\oro\platform\src\Oro\Bundle\OrganizationBundle\ on coding for REST API. The files of interest in the bundle are:

  • Resources\config\oro\routing.yml
  • Controller\Api\Rest\BusinessUnitController.php
  • Resources\config\services.yml
  • Handler\BusinessUnitDeleteHandler.php

So, I can continue with what I have done in Cookbook: Creating a Simple CRUD.

Reinstate the Delete Button

In the twig files:

  • src\InventoryBundle\Resources\views\Vehicle\update.html.twig
  • src\InventoryBundle\Resources\views\Vehicle\view.html.twig

Remove 0 and from the if statement, eg: {% if 0 and form.vars.value.id and is_granted('DELETE', form.vars.value) %}.

In src\InventoryBundle\Resources\config\oro\datagrids.yml, uncomment the data_link property.

Register the route for API

In the config file src\InventoryBundle\Resources\config\oro\routing.yml, add the API routing info inventory_api:

Copy
inventory_bundle: resource: "@InventoryBundle/Controller" type: annotation prefix: /inventory inventory_api: resource: "@InventoryBundle/Controller/Api/Rest/VehicleController.php" type: rest prefix: api/rest/{version}/ requirements: version: latest|v1 _format: json defaults: version: latest

Create the API Controller

Add the file src\InventoryBundle\Controller\Api\Rest\VehicleController.php:

Copy
<?php namespace InventoryBundle\Controller\Api\Rest; use FOS\RestBundle\Controller\Annotations\NamePrefix; use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\RouteResource; use FOS\RestBundle\Routing\ClassResourceInterface; use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Oro\Bundle\SecurityBundle\Annotation\Acl; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Oro\Bundle\SoapBundle\Controller\Api\Rest\RestController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use InventoryBundle\Entity\Vehicle; /** * @RouteResource("vehicle") * @NamePrefix("inventory_api_") */ class VehicleController extends RestController implements ClassResourceInterface { /** * REST GET list * * @QueryParam( * name="page", * requirements="\d+", * nullable=true, * description="Page number, starting from 1. Defaults to 1." * ) * @QueryParam( * name="limit", * requirements="\d+", * nullable=true, * description="Number of items per page. defaults to 10." * ) * @ApiDoc( * description="Get all vehicles items", * resource=true, * filters={ * {"name"="page", "dataType"="integer"}, * {"name"="limit", "dataType"="integer"} * } * ) * @AclAncestor("inventory.vehicle_view") * @param Request $request * @return Response */ public function cgetAction(Request $request) { $page = (int)$request->get('page', 1); $limit = (int)$request->get('limit', self::ITEMS_PER_PAGE); return $this->handleGetListRequest($page, $limit); } /** * Create new vehicle * * @ApiDoc( * description="Create new vehicle", * resource=true * ) * @AclAncestor("inventory.vehicle_create") */ public function postAction() { return $this->handleCreateRequest(); } /** * REST PUT * * @param int $id Business unit item id * * @ApiDoc( * description="Update vehicle", * resource=true * ) * @AclAncestor("inventory.vehicle_update") * @return Response */ public function putAction($id) { return $this->handleUpdateRequest($id); } /** * REST GET item * * @param string $id * * @ApiDoc( * description="Get vehicle item", * resource=true * ) * @AclAncestor("inventory.vehicle_view") * @return Response */ public function getAction($id) { return $this->handleGetRequest($id); } /** * {@inheritdoc} */ protected function transformEntityField($field, &$value) { switch ($field) { case 'vehicle': if ($value) { /** @var Vehicle $value */ $value = array( 'id' => $value->getId(), 'name' => $value->getName(), ); } break; case 'car': if ($value) { $value = array( 'id' => $value->getId(), 'name' => $value->getName() ); } break; default: parent::transformEntityField($field, $value); } } /** * {@inheritdoc} */ protected function getPreparedItem($entity, $resultFields = []) { $result = parent::getPreparedItem($entity); //unset($result['users']); return $result; } /** * REST DELETE * * @param int $id * * @ApiDoc( * description="Delete vehicle", * resource=true * ) * @Acl( * id="inventory.vehicle_delete", * type="entity", * class="InventoryBundle:Vehicle", * permission="DELETE" * ) * @return Response */ public function deleteAction($id) { return $this->handleDeleteRequest($id); } /** * {@inheritdoc} */ public function getManager() { return $this->get('inventory.vehicle.manager.api'); } /** * {@inheritdoc} */ public function getForm() { return $this->get('inventory.form.vehicle.api'); } /** * {@inheritdoc} */ public function getFormHandler() { return $this->get('inventory.form.handler.vehicle.api'); } /** * {@inheritdoc} */ protected function getDeleteHandler() { return $this->get('inventory.vehicle.handler.delete'); } }

Register the Services to Handle API and Deletion

Create the config file src\InventoryBundle\Resources\config\services.yml:

Copy
parameters: inventory.vehicle.entity.class: InventoryBundle\Entity\Vehicle inventory.vehicle.manager.api.class: Oro\Bundle\SoapBundle\Entity\Manager\ApiEntityManager services: # # Vehicle API # inventory.vehicle.manager.api: class: '%inventory.vehicle.manager.api.class%' parent: oro_soap.manager.entity_manager.abstract arguments: - '%inventory.vehicle.entity.class%' - '@doctrine.orm.entity_manager' # # Entity config dumper extension # inventory.vehicle.handler.delete: class: InventoryBundle\Handler\VehicleDeleteHandler parent: oro_soap.handler.delete.abstract

And we also need to load it in src\InventoryBundle\DependencyInjection\InventoryExtension.php:

Copy
<?php namespace InventoryBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class InventoryExtension extends Extension { /** * {@inheritDoc} */ public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('form.yml'); $loader->load('services.yml'); } }

Create the Delete Handler

Create the handler file src\InventoryBundle\Handler\VehicleDeleteHandler.php:

Copy
<?php namespace InventoryBundle\Handler; use Doctrine\Common\Persistence\ObjectManager; use Oro\Bundle\SecurityBundle\Exception\ForbiddenException; use Oro\Bundle\SoapBundle\Handler\DeleteHandler; class VehicleDeleteHandler extends DeleteHandler { /** * {@inheritdoc} */ protected function checkPermissions($entity, ObjectManager $em) { parent::checkPermissions($entity, $em); $repository = $em->getRepository('InventoryBundle:Vehicle'); if (0&&$repository->getVehiclesCount() <= 1) { throw new ForbiddenException('Unable to remove the last vehicle'); } } }

Note that I disable the ability the delete the last vehicle.

That's all. It's important to refresh the cache in bash shell: php bin/console cache:clear

Delete button is rendered:

Image

Confirmation before deletion:

Image

Testing the REST API

Generate API Key

Image

Generate WSSE Header in Bash Shell

The command to generate WSSE header in bash is

php bin/console oro:wsse:generate-header {api_key}

Example:

Copy
kiat@win10 MINGW64 /d/Work/wamp64/www/oro/platform $ php bin/console oro:wsse:generate-header 39cc1cadcd7baccd5cc899b1f3b2edf0fe0c2c0f To use WSSE authentication add following headers to the request: Authorization: WSSE profile="UsernameToken" X-WSSE: UsernameToken Username="admin", PasswordDigest="4fUg5ttcXQJ1ytT23JrhP6GqYEQ=", Nonce="NGY5ZWVmMDdlZmM2OWNjMg==", Created="2019-02-27T10:12:13+00:00"

Install Chrome Extension Restlet Client in Chrome

Restlet Client is designed and developed by developers for developers to make REST API testing and automation easy. Our mission is to bring you the best visual tool to increase your productivity by letting you focus on what to do instead of how to do it.

Image

WSSE Authentication Archive

API Rest Server

Copy
# src\Google\Bundle\VisionBundle\Resources\config\oro\bundles.yml bundles: - Google\Bundle\VisionBundle\GoogleVisionBundle
Copy
# src\Google\Bundle\VisionBundle\Resources\config\services.yml services: Google\Bundle\VisionBundle\Util\Ocr: autowire: true Google\Bundle\VisionBundle\Controller\OcrController: tags: ['controller.service_arguments'] Google\Bundle\VisionBundle\Controller\Api\Rest\OcrController: tags: ['controller.service_arguments']
Copy
# src\Google\Bundle\VisionBundle\Resources\config\oro\routing.yml google_vision_bundle: resource: "@GoogleVisionBundle/Controller" type: annotation google_vision_api: resource: "@GoogleVisionBundle/Controller/Api/Rest" # to specify specific file, append '/OcrController.php' type: rest prefix: api/rest/{version}/ requirements: version: latest|v1 _format: json defaults: version: latest
Copy
# src\Google\Bundle\VisionBundle\Controller\Api\Rest\OcrController.php <?php namespace Google\Bundle\VisionBundle\Controller\Api\Rest; use FOS\RestBundle\Controller\Annotations\NamePrefix; use FOS\RestBundle\Controller\Annotations\RouteResource; use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Symfony\Component\HttpFoundation\JsonResponse; use Google\Bundle\VisionBundle\Util\Ocr; /** * @RouteResource("googleocr", pluralize=false) * @NamePrefix("google_vision_ocr_api_") */ class OcrController { /** * REST GET text from an image using Google Vision * * @param string $base64Path filename of the image file or URL to the image file * @ApiDoc( * description="Get text from an image using Google Vision", * resource=true * ) * @AclAncestor("google_vision_ocr_api.get") * @return JsonResponse */ public function getAction($base64Path, Ocr $ocr) { $t2 = microtime(true); $path = base64_decode($base64Path); $output = $ocr->image($path); $response = [ 'path' => $path, 'tat' => microtime(true) - $t2, 'output' => $output ]; return new JsonResponse($response); } }
Copy
[kiat@reporting misoro]$ sudo -u nginx php bin/console cache:clear // Clearing the cache for the dev environment with debug // true [OK] Cache for the "dev" environment (debug=true) was successfully cleared. [kiat@reporting misoro]$ sudo -unginx php bin/console debug:router google Select one of the matching routes: [0] oro_user_google_login [1] oro_sso_google_login [2] google_vision_ocr_pdf [3] google_vision_ocr_image [4] google_vision_ocr_detect [5] google_vision_ocr_api_get_googleocr > 5 +--------------+---------------------------------------------------------------------------------------------------+ | Property | Value | +--------------+---------------------------------------------------------------------------------------------------+ | Route Name | google_vision_ocr_api_get_googleocr | | Path | /api/rest/{version}/googleocr/{base64Path}.{_format} | | Path Regex | #^/api/rest/(?P<version>latest|v1)/googleocr/(?P<base64Path>[^/\.]++)(?:\.(?P<_format>json))?$#sD | | Host | ANY | | Host Regex | | | Scheme | ANY | | Method | GET | | Requirements | _format: json | | | version: latest|v1 | | Class | Symfony\Component\Routing\Route | | Defaults | _controller: Google\Bundle\VisionBundle\Controller\Api\Rest\OcrController:getAction | | | _format: json | | | version: latest | | Options | compiler_class: Symfony\Component\Routing\RouteCompiler | +--------------+---------------------------------------------------------------------------------------------------+ [kiat@reporting misoro]$

API Rest Client

Copy
<?php /** * ocr Module * * @category Sxxm * @package Sxxm_Ocr * @copyright Copyright (c) 2019 Ng Kiat Siong * @author Ng Kiat Siong, Celera eShop, kiatsiong.ng@gmail.com * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Sxxm_Ocr_Helper_Data extends Mage_Core_Helper_Abstract { /** * @param string eg: index_dev.php/api/rest/latest/googleocr/aGVsbG8=.json * @return string */ public function curl($uri) { $url = 'http://oro.mis.sc/'.$uri; $ch = curl_init($url); curl_setopt($ch, CURLOPT_HTTPHEADER, $this->_getWsseHeader()); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FAILONERROR, true); //curl_setopt($ch, CURLOPT_VERBOSE, 1); $result = curl_exec($ch); if ($result === false) { $result = curl_error($ch); } curl_close($ch); return $result; } /** * The generated header has a lifetime of 3600s and it expires if not used during this time. * Each nonce might be used only once in specific time for generation of the password digest. * By default, the nonce cooldown time is also set to 3600s. * This rule is aimed to improve safety of the application and prevent “replay” attacks. * * @return string */ protected function _getWsseHeader() { $userName = 'kiat'; $userApiKey = 'some_string'; // generate from Oro Platform $nonce = base64_encode(substr(md5(uniqid()), 0, 16)); $created = date('c'); $digest = base64_encode(sha1(base64_decode($nonce) . $created . $userApiKey, true)); $wsseProfile = sprintf( 'X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"', $userName, $digest, $nonce, $created ); return [ 'Authorization: WSSE profile="UsernameToken"', $wsseProfile ]; } }
Copy
# src\Google\Bundle\VisionBundle\Util\Ocr.php <?php namespace Google\Bundle\VisionBundle\Util; use Google\Cloud\Vision\V1\ImageAnnotatorClient; use Google\Cloud\Vision\V1\EntityAnnotation; class Ocr { /** * Detect text in image * */ public function image(string $filename): array { $content = file_get_contents($filename); if (!$content) { return ['error' => 'invalid path: '.$filename]; } putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/keyfile.json'); return $this->textDetect($content); } /** * Feature TEXT_DETECTION */ protected function textDetect($content): array { $imageAnnotator = new ImageAnnotatorClient(); $response = $imageAnnotator->textDetection($content); $texts = $response->getTextAnnotations(); $imageAnnotator->close(); $output = []; /** @var EntityAnnotation $text */ foreach ($texts as $text) { $output[] = $text->getDescription(); } return $output; } }

Owner of this card:

Avatar
kiatng
Last edit:
almost 3 years ago
by kiatng
Attachments:
rclient.png, user-api-key.png, vehicle-delete-button.png, vehicle-delete-confrim.png
Posted by kiatng to Oro
This website uses short-lived cookies to improve usability.
Accept or learn more