cedricziel/canva-extension-helper

Canva.com 扩展辅助工具

v0.0.9 2021-02-07 17:20 UTC

This package is auto-updated.

Last update: 2024-09-08 00:55:00 UTC


README

构建 Canva 扩展的一些实用工具。

使用方法

安装

通过 composer

composer require cedricziel/canva-extension-helper

与 Symfony

Symfony 项目通常附带完全配置的序列化器和配置的 http 服务器层。

以下 Symfony 控制器足以用于具有 basic 布局的 "发布" 扩展

<?php

namespace App\Controller\Canva;

use Canva\Error;
use Canva\HttpHelper;
use Canva\Publish\ErrorResponse;
use Canva\Publish\UploadRequest;
use Canva\Publish\UploadResponse;
use Canva\Request as CanvaRequest;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
 * @Route(path="/canva/extensions/publish", name="canva_publish")
 */
class PublishExtensionController extends AbstractController implements EventSubscriberInterface
{
    private string $canvaSecret;

    public function __construct(string $canvaSecret)
    {
        $this->canvaSecret = $canvaSecret;
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::CONTROLLER => 'onKernelController',
        ];
    }

    /**
     * @Route("/configuration", name="_configuration", methods={"POST"})
     */
    public function configuration(): Response
    {
        return $this->json(new ErrorResponse(Error::CODE_INVALID_REQUEST));
    }

    /**
     * @Route("/resources/find", name="_resources_find", methods={"POST"})
     */
    public function resourcesFind(): Response
    {
        return $this->json(new ErrorResponse(Error::CODE_INVALID_REQUEST));
    }

    /**
     * @Route("/resources/get", name="_resources_get", methods={"POST"})
     */
    public function resourcesGet(): Response
    {
        return $this->json(new ErrorResponse(Error::CODE_INVALID_REQUEST));
    }

    /**
     * @Route("/resources/upload", name="_resources_upload")
     */
    public function resourcesUpload(Request $request, HttpClientInterface $httpClient, SerializerInterface $serializer): Response
    {
        try {
            /** @var UploadRequest $uploadRequest */
            $uploadRequest = $serializer->deserialize($request->getContent(), UploadRequest::class, 'json');

            foreach ($uploadRequest->getAssets() as $asset) {
                // do something with the result
                $httpClient->request('GET', $asset->getUrl());
            }

            return $this->json(new UploadResponse());
        } catch (InvalidArgumentException $exception) {
            return $this->json(new ErrorResponse(Error::CODE_INVALID_REQUEST));
        }
    }

    public function onKernelController(ControllerEvent $event)
    {
        $controller = $event->getController();
        $request = $event->getRequest();

        // when a controller class defines multiple action methods, the controller
        // is returned as [$controllerInstance, 'methodName']
        if (is_array($controller)) {
            $controller = $controller[0];
        }

        /**
         * Every publish extension endpoint is invoked via POST and needs
         * signature AND timestamp checking.
         */
        if ($controller instanceof self) {
            $timestampHeader = $request->headers->get(CanvaRequest::HEADER_TIMESTAMP);
            if ($timestampHeader === null || !HttpHelper::verifyTimestamp($timestampHeader, time())) {
                throw new HttpException(401, 'Timestamp skew is too large.');
            }

            $path = parse_url($request->getUri(), PHP_URL_PATH);
            $operation = '';
            switch (true) {
                case str_ends_with($path, '/configuration'):
                    $operation = '/configuration';
                    break;
                case str_ends_with($path, '/publish/resources/find'):
                    $operation = '/publish/resources/find';
                    break;
                case str_ends_with($path, '/publish/resources/get'):
                    $operation = '/publish/resources/get';
                    break;
                case str_ends_with($path, '/publish/resources/upload'):
                    $operation = '/publish/resources/upload';
                    break;
                default:
                    throw new HttpException(401, 'Unknown operation');
            }

            $signature = HttpHelper::calculatePostSignature(
                $timestampHeader,
                $operation,
                $request->getContent(),
                $this->canvaSecret
            );

            $signatureHeader = $request->headers->get(CanvaRequest::HEADER_SIGNATURES);
            if ($signatureHeader === null || !in_array($signature, explode(',', $signatureHeader), true)) {
                throw new HttpException(401, 'Signatures do not match');
            }
        }
    }
}

然后您可以在 services.yaml 中将构造函数参数 $canvaSecret "绑定" 到您的 Canva.com 密钥

services:
    _defaults:
        # .. other defaults
        bind:
            $canvaSecret: 'my-secret'

序列化

注意:此项目提供模型类。反/序列化必须通过您的应用程序进行。

示例使用 Symfony Serializer 组件

use Canva\Publish\GetResourceRequest;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
use Symfony\Component\Serializer\Serializer;

// the json chunk extracted from the request body
$request = '...';

$encoders = [new JsonEncoder()];
$extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
$normalizers = [new ArrayDenormalizer(), new ObjectNormalizer(null, null, null, $extractor), new PropertyNormalizer(), new GetSetMethodNormalizer()];

/** @var GetResourceRequest $getResourceRequest */
$getResourceRequest = $serializer->deserialize($request, GetResourceRequest::class, 'json');

检查传入请求

Canva 要求扩展检查传入请求的时间戳偏移和匹配的 HMAC 签名。此包提供辅助程序以轻松处理。

检查时间戳

// allow a skew of 300 seconds
$leniency = 300;

// the timestamp at which the request was received
$localTimestamp = time();

// the timestamp at which the request was sent
$sentTimestamp = $_SERVER['HTTP_X_CANVA_TIMESTAMP'];

// returns a boolean whether the timestamps are close enough together
$timestampIsOkay = \Canva\HttpHelper::verifyTimestamp($sentTimestamp, $localTimestamp, $leniency)

有关检查签名的示例,请参阅中间件部分。

中间件

Canva 要求您检查来自其端点的请求。您可以手动使用 Canva\HttpHelper 类验证时间戳标头,或者选择在 Canva 将通信的路径上安装中间件。

Canva\MiddlewareTimestampMiddleware - 检查时间偏移 Canva\Middleware\PostHMACMiddleware - 检查 POST 请求上的签名 Canva\Middleware\GetHMACMiddleware - 检查 GET 请求上的签名

免责声明

本项目与 Canva.com 无关

许可证

MIT