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 `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)
|
* 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)
|
* 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
|
## 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
|
* [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.
|
* 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
|
## Future development
|
||||||
|
|
||||||
This might get supported in future (maybe):
|
This might get supported in future (maybe):
|
||||||
|
|
|
@ -32,3 +32,10 @@ $port = !in_array($_SERVER['SERVER_PORT'], [80, 443]) ? ':' . $_SERVER['SERVER_P
|
||||||
$root = '/';
|
$root = '/';
|
||||||
|
|
||||||
define('KaraDAV\WWW_URL', sprintf('http%s://%s%s%s', $https, $name, $port, $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;
|
namespace KaraDAV;
|
||||||
|
|
||||||
|
use KD2\WebDAV\WOPI;
|
||||||
|
|
||||||
class Server
|
class Server
|
||||||
{
|
{
|
||||||
public Users $users;
|
public Users $users;
|
||||||
|
@ -26,6 +28,15 @@ class Server
|
||||||
return true;
|
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);
|
$nc = new NextCloud($this->dav, $this->users);
|
||||||
|
|
||||||
if ($r = $nc->route($uri)) {
|
if ($r = $nc->route($uri)) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace KaraDAV;
|
||||||
|
|
||||||
use KD2\WebDAV\AbstractStorage;
|
use KD2\WebDAV\AbstractStorage;
|
||||||
use KD2\WebDAV\Server as WebDAV_Server;
|
use KD2\WebDAV\Server as WebDAV_Server;
|
||||||
|
use KD2\WebDAV\WOPI;
|
||||||
use KD2\WebDAV\Exception as WebDAV_Exception;
|
use KD2\WebDAV\Exception as WebDAV_Exception;
|
||||||
|
|
||||||
class Storage extends AbstractStorage
|
class Storage extends AbstractStorage
|
||||||
|
@ -142,8 +143,8 @@ class Storage extends AbstractStorage
|
||||||
|
|
||||||
return md5_file($target);
|
return md5_file($target);
|
||||||
// NextCloud stuff
|
// NextCloud stuff
|
||||||
case Nextcloud::PROP_NC_HAS_PREVIEW:
|
case NextCloud::PROP_NC_HAS_PREVIEW:
|
||||||
case Nextcloud::PROP_NC_IS_ENCRYPTED:
|
case NextCloud::PROP_NC_IS_ENCRYPTED:
|
||||||
return 'false';
|
return 'false';
|
||||||
case NextCloud::PROP_OC_SHARETYPES:
|
case NextCloud::PROP_OC_SHARETYPES:
|
||||||
return WebDAV::EMPTY_PROP_VALUE;
|
return WebDAV::EMPTY_PROP_VALUE;
|
||||||
|
@ -161,10 +162,10 @@ class Storage extends AbstractStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
case Nextcloud::PROP_OC_ID:
|
case NextCloud::PROP_OC_ID:
|
||||||
$username = $this->users->current()->login;
|
$username = $this->users->current()->login;
|
||||||
return NextCloud::getDirectID($username, $uri);
|
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]);
|
return implode('', [NextCloud::PERM_READ, NextCloud::PERM_WRITE, NextCloud::PERM_CREATE, NextCloud::PERM_DELETE, NextCloud::PERM_RENAME_MOVE]);
|
||||||
case 'DAV::quota-available-bytes':
|
case 'DAV::quota-available-bytes':
|
||||||
return null;
|
return null;
|
||||||
|
@ -178,6 +179,31 @@ class Storage extends AbstractStorage
|
||||||
else {
|
else {
|
||||||
return filesize($target);
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -189,6 +215,13 @@ class Storage extends AbstractStorage
|
||||||
return $this->getResourceProperties($uri)->get($name);
|
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
|
public function properties(string $uri, ?array $properties, int $depth): ?array
|
||||||
{
|
{
|
||||||
$target = $this->users->current()->path . $uri;
|
$target = $this->users->current()->path . $uri;
|
||||||
|
@ -471,4 +504,29 @@ class Storage extends AbstractStorage
|
||||||
|
|
||||||
return $last;
|
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);
|
$out = parent::html_directory($uri, $list);
|
||||||
|
|
||||||
if (null !== $out) {
|
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);
|
$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