Add WOPI support, tested with Collabora
This commit is contained in:
parent
b8fd51c750
commit
07c3711d65
86
COLLABORA.md
Normal file
86
COLLABORA.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Setting up Collabora with KaraDAV
|
||||
|
||||
This is entirely optional, but will allow you to edit office documents directly from the browser.
|
||||
|
||||
Note that Collabora has a soft limit of 20
|
||||
|
||||
## With Docker
|
||||
|
||||
First install docker and docker-compose, then run `docker pull collabora/code` to fetch the docker image.
|
||||
|
||||
Now you will have to create a `docker-compose.yml` file containing:
|
||||
|
||||
```
|
||||
collabora:
|
||||
image: collabora/code
|
||||
container_name: collabora
|
||||
environment:
|
||||
domain: "karadav.localhost"
|
||||
extra_params: "--o:ssl.enable=false --o:ssl.termination=false -o:net.frame_ancestors=karadav.localhost:*"
|
||||
expose:
|
||||
- 9980
|
||||
ports:
|
||||
- "9980:9980"
|
||||
extra_hosts:
|
||||
- "karadav.localhost:0.0.0.0"
|
||||
```
|
||||
|
||||
This setup is for a localhost test environment, where `karadav.localhost` is hosting your WebDAV server, and `docs.karadav.localhost` will host the Collabora server. You will have to replace `0.0.0.0` with your computer IP.
|
||||
|
||||
Then create a new Apache virtual host:
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName docs.karadav.localhost
|
||||
|
||||
AllowEncodedSlashes NoDecode
|
||||
ProxyPreserveHost On
|
||||
|
||||
<Location />
|
||||
<Limit OPTIONS>
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
</Limit>
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_METHOD} OPTIONS
|
||||
RewriteRule ^(.*)$ $1 [R=200,L]
|
||||
</Location>
|
||||
|
||||
|
||||
# static html, js, images, etc. served from coolwsd
|
||||
# browser is the client part of Collabora Online
|
||||
ProxyPass /browser http://127.0.0.1:9980/browser retry=0
|
||||
ProxyPassReverse /browser http://127.0.0.1:9980/browser
|
||||
|
||||
|
||||
# WOPI discovery URL
|
||||
ProxyPass /hosting/discovery http://127.0.0.1:9980/hosting/discovery retry=0
|
||||
ProxyPassReverse /hosting/discovery http://127.0.0.1:9980/hosting/discovery
|
||||
|
||||
|
||||
# Capabilities
|
||||
ProxyPass /hosting/capabilities http://127.0.0.1:9980/hosting/capabilities retry=0
|
||||
ProxyPassReverse /hosting/capabilities http://127.0.0.1:9980/hosting/capabilities
|
||||
|
||||
# Main websocket
|
||||
ProxyPassMatch "/cool/(.*)/ws$" ws://127.0.0.1:9980/cool/$1/ws nocanon
|
||||
|
||||
|
||||
# Admin Console websocket
|
||||
ProxyPass /cool/adminws ws://127.0.0.1:9980/cool/adminws
|
||||
|
||||
|
||||
# Download as, Fullscreen presentation and Image upload operations
|
||||
ProxyPass /cool http://127.0.0.1:9980/cool
|
||||
ProxyPassReverse /cool http://127.0.0.1:9980/cool
|
||||
# Compatibility with integrations that use the /lool/convert-to endpoint
|
||||
ProxyPass /lool http://127.0.0.1:9980/cool
|
||||
ProxyPassReverse /lool http://127.0.0.1:9980/cool
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Reload the apache configuration, and launch `docker-compose up`.
|
||||
|
||||
Lastly, in KaraDAV's `config.local.php` set `WOPI_DISCOVERY_URL` to `http://docs.karadav.localhost/hosting/discovery`.
|
||||
|
||||
Now you should be able to edit ODS/ODT/etc. files from the web UI using Collabora.
|
|
@ -25,6 +25,7 @@ This server features:
|
|||
* Support for `Content-MD5` with `PUT` requests, see [dCache documentation for details](https://dcache.org/old/manuals/UserGuide-6.0/webdav.shtml#checksums)
|
||||
* Support for some of the [Microsoft proprietary properties](https://greenbytes.de/tech/webdav/webdavfaq.html)
|
||||
* Passes most of the [Litmus compliance tests](https://github.com/tolsen/litmus)
|
||||
* Supports WOPI, for editing and viewing of documents using OnlyOffice, Collabora Online or MS Office.
|
||||
|
||||
## NextCloud/ownCloud compatibility
|
||||
|
||||
|
@ -52,6 +53,9 @@ The following NextCloud specific features are supported:
|
|||
* [FUSE webdavfs](https://github.com/miquels/webdavfs) is recommended for Linux
|
||||
* davfs2 is NOT recommended: it is very slow, and it is using a local cache, meaning changing a file locally may not be synced to the server for a few minutes, leading to things getting out of sync. If you have to use it, at least disable locks, by setting `use_locks=0` in the config.
|
||||
|
||||
## WOPI clients compatibility
|
||||
|
||||
* Tested successfully with Collabora Development Edition (see [COLLABORA.md](COLLABORA.md))
|
||||
## Future development
|
||||
|
||||
This might get supported in future (maybe):
|
||||
|
|
|
@ -32,3 +32,10 @@ $port = !in_array($_SERVER['SERVER_PORT'], [80, 443]) ? ':' . $_SERVER['SERVER_P
|
|||
$root = '/';
|
||||
|
||||
define('KaraDAV\WWW_URL', sprintf('http%s://%s%s%s', $https, $name, $port, $root));
|
||||
|
||||
/**
|
||||
* WOPI client discovery URL
|
||||
* eg. http://onlyoffice.domain.tld/hosting/discovery for OnlyOffice
|
||||
* If set to NULL, WOPI support is disabled
|
||||
*/
|
||||
const WOPI_DISCOVERY_URL = null;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace KaraDAV;
|
||||
|
||||
use KD2\WebDAV\WOPI;
|
||||
|
||||
class Server
|
||||
{
|
||||
public Users $users;
|
||||
|
@ -26,6 +28,15 @@ class Server
|
|||
return true;
|
||||
}
|
||||
|
||||
if (WOPI_DISCOVERY_URL) {
|
||||
$wopi = new WOPI;
|
||||
$wopi->setServer($this->dav);
|
||||
|
||||
if ($wopi->route($uri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$nc = new NextCloud($this->dav, $this->users);
|
||||
|
||||
if ($r = $nc->route($uri)) {
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace KaraDAV;
|
|||
|
||||
use KD2\WebDAV\AbstractStorage;
|
||||
use KD2\WebDAV\Server as WebDAV_Server;
|
||||
use KD2\WebDAV\WOPI;
|
||||
use KD2\WebDAV\Exception as WebDAV_Exception;
|
||||
|
||||
class Storage extends AbstractStorage
|
||||
|
@ -142,8 +143,8 @@ class Storage extends AbstractStorage
|
|||
|
||||
return md5_file($target);
|
||||
// NextCloud stuff
|
||||
case Nextcloud::PROP_NC_HAS_PREVIEW:
|
||||
case Nextcloud::PROP_NC_IS_ENCRYPTED:
|
||||
case NextCloud::PROP_NC_HAS_PREVIEW:
|
||||
case NextCloud::PROP_NC_IS_ENCRYPTED:
|
||||
return 'false';
|
||||
case NextCloud::PROP_OC_SHARETYPES:
|
||||
return WebDAV::EMPTY_PROP_VALUE;
|
||||
|
@ -161,10 +162,10 @@ class Storage extends AbstractStorage
|
|||
}
|
||||
|
||||
return '';
|
||||
case Nextcloud::PROP_OC_ID:
|
||||
case NextCloud::PROP_OC_ID:
|
||||
$username = $this->users->current()->login;
|
||||
return NextCloud::getDirectID($username, $uri);
|
||||
case Nextcloud::PROP_OC_PERMISSIONS:
|
||||
case NextCloud::PROP_OC_PERMISSIONS:
|
||||
return implode('', [NextCloud::PERM_READ, NextCloud::PERM_WRITE, NextCloud::PERM_CREATE, NextCloud::PERM_DELETE, NextCloud::PERM_RENAME_MOVE]);
|
||||
case 'DAV::quota-available-bytes':
|
||||
return null;
|
||||
|
@ -178,6 +179,31 @@ class Storage extends AbstractStorage
|
|||
else {
|
||||
return filesize($target);
|
||||
}
|
||||
case WOPI::PROP_FILE_URL:
|
||||
$id = gzcompress($uri);
|
||||
$id = WOPI::base64_encode_url_safe($id);
|
||||
return WWW_URL . 'wopi/files/' . $id;
|
||||
case WOPI::PROP_TOKEN:
|
||||
$p = $this->getResourceProperties($uri);
|
||||
$token = $p->get($name)['xml'] ?? null;
|
||||
|
||||
// Check if token has expired, if so, then renew it
|
||||
if ($token) {
|
||||
$expiry = $p->get(WOPI::PROP_TOKEN_TTL);
|
||||
|
||||
if ($expiry < time() * 1000) {
|
||||
$token = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Create token and store it
|
||||
if (!$token) {
|
||||
$token = $this->createWopiToken($uri);
|
||||
$p->set(WOPI::PROP_TOKEN, null, $token);
|
||||
$p->set(WOPI::PROP_TOKEN_TTL, null, (time()+3600)*1000);
|
||||
}
|
||||
|
||||
return $token;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -189,6 +215,13 @@ class Storage extends AbstractStorage
|
|||
return $this->getResourceProperties($uri)->get($name);
|
||||
}
|
||||
|
||||
protected function createWopiToken(string $uri)
|
||||
{
|
||||
$login = $this->users->current()->login;
|
||||
$bytes = substr(md5(random_bytes(10)), 0, 10);
|
||||
return WOPI::base64_encode_url_safe(sprintf('%s:%s', sha1($login . $uri . $bytes), $bytes));
|
||||
}
|
||||
|
||||
public function properties(string $uri, ?array $properties, int $depth): ?array
|
||||
{
|
||||
$target = $this->users->current()->path . $uri;
|
||||
|
@ -471,4 +504,29 @@ class Storage extends AbstractStorage
|
|||
|
||||
return $last;
|
||||
}
|
||||
|
||||
public function getWopiURI(string $id, string $token): ?string
|
||||
{
|
||||
$id = WOPI::base64_decode_url_safe($id);
|
||||
$uri = gzuncompress($id);
|
||||
$token_decode = WOPI::base64_decode_url_safe($token);
|
||||
$hash = strtok($token_decode, ':');
|
||||
$bytes = strtok(false);
|
||||
|
||||
$r = DB::getInstance()->first('SELECT user, uri FROM properties WHERE name = ? AND xml = ?;', WOPI::PROP_TOKEN, $token);
|
||||
|
||||
if (!$r) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hash_equals(sha1($r->user . $r->uri . $bytes), $hash)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->users->setCurrent($r->user)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $r->uri;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@ class WebDAV extends WebDAV_Server
|
|||
$out = parent::html_directory($uri, $list);
|
||||
|
||||
if (null !== $out) {
|
||||
if (WOPI_DISCOVERY_URL) {
|
||||
$out = str_replace('<html', sprintf('<html data-wopi-discovery-url="%s" data-wopi-host-url="%s"', WOPI_DISCOVERY_URL, WWW_URL . 'wopi/'), $out);
|
||||
}
|
||||
|
||||
$out = str_replace('<body>', sprintf('<body style="opacity: 0"><script type="text/javascript" src="%swebdav.js"></script>', WWW_URL), $out);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue