champs-libres/wopi-bundle

提供在 Symfony 和 WOPI 连接器之间路由和粘合代码的包。

安装次数: 5,251

依赖: 2

建议者: 0

安全性: 0

星级: 7

关注者: 4

分支: 1

开放问题: 4

类型:symfony-bundle

dev-master 2023-01-19 21:29 UTC

README

Latest Stable Version GitHub stars Total Downloads GitHub Workflow Status Scrutinizer code quality Type Coverage Code Coverage License

WOPI 包

一个用于简化 WOPI 端点和协议实现的 Symfony 扩展包。

描述

Web 应用程序开放平台接口(WOPI)协议让您可以将 Office for the web 与您的应用程序集成,还可以与其他软件如 Collabora Online 集成。

目前,此包的目标是集成 Collabora Online。

将来,此包可能能够通过 验证,以便与 Office for the Web 一起使用。

Collabora Online 集成

WOPI 协议概述

Web 级 Office 平台

安装

composer require champs-libres/wopi-bundle

使用方法

此包为 Symfony 提供了协议的基本实现。但是,有许多方法可以

  • 在应用程序中存储文档;
  • 确保协议的安全性
  • 并管理权限,根据您自己的业务逻辑。

因此,此包不提供通过 基本接口champs-libres/wopi-lib 包中描述的 WOPI 协议的具体实现。

因此,此包提供

  • WOPI 协议所需的 路由,该路由以 /wopi 路径开头(WOPI 协议所必需的);
  • 用于 WOPI 路由的 控制器
  • 以及 Wopi 逻辑的实现,该实现将重用您的一些逻辑来管理权限、文档等。

一些术语

  • Wopi 服务器:实现此包的应用程序;
  • Wopi 客户端:Collabora Online(或 Office 365),它将使用您的应用程序(服务器)提供的端点;
  • 编辑器:Collabora Online(或 office 365)。Wopi 客户端的同义词。

以下是集成 wopi 包到您的应用程序的步骤

启动编辑器/您的 wopi 客户端进行开发

您将在 CODE 项目中找到一个免费的 collabora online: CODE

⚠️编辑器必须可以访问您的应用程序,与浏览器打开您的应用程序的域名相同

如果您使用 docker 和 docker-compose,您可以通过修改您的 /etc/hosts 文件来实现这一点

# docker-compose.yaml

services:
    app:
        # your php / symfony application
        # we assume that your app listen **inside the container** on the port 8001 (no port mapping required between inside and
        # outside of the container)
        # ...
    collabora:
        image: collabora/code:latest
        environment:
            - SLEEPFORDEBUGGER=0
            - DONT_GEN_SSL_CERT="True"
            - extra_params=--o:ssl.enable=false --o:ssl.termination=false
            - username=admin
            - password=admin
            - dictionaries=en_US
            - aliasgroup1=http://nginx:8001
        ports:
            - "127.0.0.1:9980:9980"
        cap_add:
            - MKNOD
        links:
            - app
# /etc/hosts

127.0.0.1 app collabora

使用此配置,您应该可以通过 http://collabora:9980 访问 collabora,并通过 http://app:8001 访问您的应用程序。您必须在调试 collabora 功能时使用后者访问您的应用程序。

配置此包

# app/config/package/wopi.yaml

wopi:
    # this is the path to your server.
    # note: the wopi client (Collabora) must be able to your app **using the same domains as your browser**
    server: http://collabora:9980

创建您的文档实体

每个编辑的文档都应该是一个实现 Document 的实体。

创建您的文档管理器

您的经理将实现 DocumentManagerInterface

这个DocumentManager将处理您应用程序中的文档逻辑。它提供写入文档和从中提取一些信息的方法。

您可以在这里找到一个实现。

创建您的访问令牌逻辑

access_token由您的应用程序创建,当它打开编辑页面时(剧透:编辑页面将是一个iframe)。Wopi主机(您的应用程序)将在每次客户端发起请求时接收此访问令牌。每个令牌的有效期为10小时。

您可以选择自己的逻辑。但JWT可以简化您的任务。

使用LexikJWT的一些工作配置

使用JWT (Json Web Token)轻松验证您的请求。这可以通过LexikJWTAuthenticationBundle轻松实现。

创建一个防火墙并配置以/wopi开头的url的访问控制

# config/package/security.yaml
security:
    firewalls:
        wopi:
            pattern: ^/wopi
            stateless: true
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
    access_control:
        # ...
        - { path: ^/wopi, roles: IS_AUTHENTICATED_FULLY }
        # ...

配置lexik

# config/package/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
    # required for wopi - recommended duration for token ttl
    token_ttl: 36000

    # required for wopi: the token is in query, with `?access_token=<your_token>`
    token_extractors:
        query_parameter:
            enabled: true
            name: access_token

查看一个工作实现:https://gitea.champs-libres.be/Chill-project/chill-skeleton-basic

提供有关您的用户的信息

实现UserManagerInterface以提供有关用户的信息。

这些信息应通过访问令牌提取。

一些工作实现

提供有关权限/授权的信息

实现AuthorizationManagerInterface以提供有关给定文档的权限信息。

一些工作实现

绑定所有服务

此捆绑包将需要实现按照接口命名。

一些示例

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface;
use ChampsLibres\WopiBundle\Contracts\UserManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\WopiBundle\Service\Wopi\AuthorizationManager;
use Chill\WopiBundle\Service\Wopi\ChillDocumentManager;
use Chill\WopiBundle\Service\Wopi\UserManager;

return static function (ContainerConfigurator $container) {
    $services = $container
        ->services();

    $services
        ->defaults()
        ->autowire()
        ->autoconfigure();

    $services
        ->set(ChillDocumentManager::class);

    $services
        ->alias(DocumentManagerInterface::class, ChillDocumentManager::class);

    $services
        ->set(AuthorizationManager::class);

    $services->alias(AuthorizationManagerInterface::class, AuthorizationManager::class);

    $services
        ->set(UserManager::class);

    $services->alias(UserManagerInterface::class, UserManager::class);
};

创建一个编辑页面

编辑页面将是一个通过iframe加载编辑器的页面。

这里是一个控制器

<?php

declare(strict_types=1);

namespace App\Controller;

use ChampsLibres\WopiLib\Contract\Service\Configuration\ConfigurationInterface;
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\User;
use Chill\WopiBundle\Service\Controller\ResponderInterface;
use Exception;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use loophp\psr17\Psr17Interface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;

final class Editor
{
    private DocumentManagerInterface $documentManager;

    private JWTTokenManagerInterface $JWTTokenManager;

    private Psr17Interface $psr17;

    private ResponderInterface $responder;

    private RouterInterface $router;

    private Security $security;

    private ConfigurationInterface $wopiConfiguration;

    private DiscoveryInterface $wopiDiscovery;

    public function __construct(
        ConfigurationInterface $wopiConfiguration,
        DiscoveryInterface $wopiDiscovery,
        DocumentManagerInterface $documentManager,
        JWTTokenManagerInterface $JWTTokenManager,
        ResponderInterface $responder,
        Security $security,
        Psr17Interface $psr17,
        RouterInterface $router
    ) {
        $this->documentManager = $documentManager;
        $this->JWTTokenManager = $JWTTokenManager;
        $this->wopiConfiguration = $wopiConfiguration;
        $this->wopiDiscovery = $wopiDiscovery;
        $this->responder = $responder;
        $this->security = $security;
        $this->psr17 = $psr17;
        $this->router = $router;
    }

    public function __invoke(string $fileId): Response
    {
        if (null === $user = $this->security->getUser()) {
            throw new AccessDeniedHttpException('Please authenticate to access this feature');
        }

        $configuration = $this->wopiConfiguration->jsonSerialize();
        $storedObject = $this->documentManager->findByDocumentId($fileId);

        if (null === $storedObject) {
            throw new NotFoundHttpException(sprintf('Unable to find object %s', $fileId));
        }

        if ([] === $discoverExtension = $this->wopiDiscovery->discoverMimeType($storedObject->getType())) {
            throw new Exception(sprintf('Unable to find mime type %s', $storedObject->getType()));
        }

        $configuration['favIconUrl'] = '';
        $configuration['access_token'] = $this->JWTTokenManager->createFromPayload($user, [
            'UserCanWrite' => true,
            'UserCanAttend' => true,
            'UserCanPresent' => true,
            'fileId' => $fileId,
        ]);

        // we parse the jwt to get the access_token_ttl
        // reminder: access_token_ttl is a javascript epoch, not a number of seconds; it is the
        // time when the token will expire, not the time to live:
        // https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/concepts#the-access_token_ttl-property
        $jwt = $this->JWTTokenManager->parse($configuration['access_token']);
        $configuration['access_token_ttl'] = $jwt['exp'] * 1000;

        $configuration['server'] = $this
            ->psr17
            ->createUri($discoverExtension[0]['urlsrc'])
            ->withQuery(
                http_build_query(
                    [
                        'WOPISrc' => $this
                            ->router
                            ->generate(
                                'checkFileInfo',
                                [
                                    'fileId' => $this->documentManager->getDocumentId($storedObject),
                                ],
                                UrlGeneratorInterface::ABSOLUTE_URL
                            ),
                        'closebutton' => 1,
                    ]
                )
            );

        return $this
            ->responder
            ->render(
                '@Wopi/Editor/page.html.twig',
                $configuration
            );
    }
}

故障排除

  • 检查您的collabora / CODE的日志。它们提供了WOPI调用中的错误信息;
  • 使用分析器来调试wopi客户端在幕后对WOPI端点的调用。

文档

代码质量、测试、基准

每次向库引入更改时,Github都会运行测试。

库使用PHPUNIT编写了测试。

在每次提交之前,使用GrumPHP执行一些检查;运行composer grumphp以手动检查。

使用Infection(PHP突变测试框架)测试测试的质量,运行composer infection以尝试。

静态分析器也正在控制代码。已启用PHPStanPSalm的最大级别。

贡献

请随时通过在Github上提交拉取请求来为此项目做出贡献。

更改日志

请参阅CHANGELOG.md以获取基于git提交的更改日志。

如需查看更详细的更新日志,请访问发布更新日志