(wopi) save woopi client proof key in the configuration

The proof-key feature is not yet implemented. The first step is to save
the proof-key given by the client in its discovery document.
This commit is contained in:
Manuel Raynaud
2026-08-13 09:22:04 +02:00
committed by Nicolas Clerc
parent 2117f0dbf1
commit 8cafbd56c7
5 changed files with 133 additions and 8 deletions
+1 -1
View File
@@ -1788,7 +1788,7 @@ class ItemViewSet(
if request.user.is_authenticated and request.user.language
else settings.LANGUAGE_CODE
)
launch_url = compute_wopi_launch_url(wopi_client["url"], get_file_info, language)
launch_url = compute_wopi_launch_url(wopi_client.get("launch_url"), get_file_info, language)
return drf.response.Response(
{
@@ -40,11 +40,14 @@ def configure_wopi_settings(valid_mimetype, valid_wopi_launch_url):
{
"mimetypes": {
valid_mimetype: {
"url": valid_wopi_launch_url,
"launch_url": valid_wopi_launch_url,
"client": "vendorA",
},
},
"extensions": {},
"vendorA": {
"proof_keys": {},
},
},
)
+40 -2
View File
@@ -1,11 +1,15 @@
"""Task configuring WOPI using discovery url."""
from base64 import b64decode
from django.conf import settings
from django.core.cache import cache
import requests
from celery import Celery
from celery.schedules import crontab
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from defusedxml.ElementTree import fromstring
from drive.celery_app import app as celery_app
@@ -44,6 +48,19 @@ def configure_wopi_clients():
)
def build_rsa_public_key(modulus, exponent):
"""Build RSA public key from modulus and exponent."""
mod = int(b64decode(modulus).hex(), 16)
exp = int(b64decode(exponent).hex(), 16)
rsa_public_key = RSAPublicNumbers(exp, mod).public_key()
return rsa_public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
def _configure_wopi_client_from_discovery(client, discovery_url):
"""Configure wopi client from discovery url."""
@@ -67,6 +84,27 @@ def _configure_wopi_client_from_discovery(client, discovery_url):
if net_zone is None:
raise RuntimeError(f"net-zone element not found in discovery url for wopi client {client}")
proof_key_node = root.find(".//proof-key")
proof_keys = {}
if proof_key_node is not None:
# build current and old public key
current_public_key = build_rsa_public_key(
proof_key_node.get("modulus"), proof_key_node.get("exponent")
)
old_public_key = build_rsa_public_key(
proof_key_node.get("oldmodulus"), proof_key_node.get("oldexponent")
)
proof_keys = {
"public_key": current_public_key,
"old_public_key": old_public_key,
}
wopi_configuration[client] = {
"proof_keys": proof_keys,
}
# Iterate through all app elements
for app in net_zone.findall(".//app"):
app_name = app.get("name")
@@ -85,7 +123,7 @@ def _configure_wopi_client_from_discovery(client, discovery_url):
continue
wopi_configuration["mimetypes"][mimetype] = {
"url": action.get("urlsrc"),
"launch_url": action.get("urlsrc"),
"client": client,
}
@@ -96,7 +134,7 @@ def _configure_wopi_client_from_discovery(client, discovery_url):
continue
wopi_configuration["extensions"][extension] = {
"url": action.get("urlsrc"),
"launch_url": action.get("urlsrc"),
"client": client,
}
@@ -49,16 +49,83 @@ def test_configure_wopi_clients(settings):
assert cache.get(WOPI_CONFIGURATION_CACHE_KEY) == {
"mimetypes": {
"application/vnd.oasis.opendocument.text": {
"url": "http://localhost:9980/browser/0968141f2c/cool.html?",
"launch_url": "http://localhost:9980/browser/0968141f2c/cool.html?",
"client": "vendorA",
},
},
"extensions": {
"odt": {
"url": "http://localhost:9980/browser/0968141f2c/cool.html?",
"launch_url": "http://localhost:9980/browser/0968141f2c/cool.html?",
"client": "vendorA",
},
},
"vendorA": {
"proof_keys": {},
},
}
@responses.activate
def test_configure_wopi_clients_with_proof_keys(settings):
"""Test the configure_wopi celery task with client using proof key."""
settings.WOPI_CLIENTS = ["vendorA"]
settings.WOPI_CLIENTS_CONFIGURATION = {
"vendorA": {
"discovery_url": "https://vendorA.com/hosting/discovery",
}
}
# pylint: disable=line-too-long
responses.add(
responses.GET,
"https://vendorA.com/hosting/discovery",
body="""
<wopi-discovery>
<net-zone name="external-http">
<app favIconUrl="http://localhost:9980/browser/0968141f2c/images/x-office-document.svg" name="writer">
<action default="true" ext="sxw" name="view" urlsrc="http://localhost:9980/browser/0968141f2c/cool.html?"/>
<action default="true" ext="odt" name="edit" urlsrc="http://localhost:9980/browser/0968141f2c/cool.html?"/>
</app>
<app name="application/vnd.oasis.opendocument.text">
<action default="true" ext="" name="edit" urlsrc="http://localhost:9980/browser/0968141f2c/cool.html?"/>
</app>
</net-zone>
<proof-key
oldvalue="BgIAAACkAABSU0ExAAgAAAEAAQD75uIhulOrvFWdgiI3BUqZWj3Zxlii/oCz4CQpH72jYZgbPkKpFC0D0zXznEvn8jgkekLjTD4Q3Dj5UoqN7atjGqhcCuLyiKqkjbklrOS/PS/nhKNJjMWgEUksRKu2vHXJmUhO1FECEgwHM7TOQtDnVJuMV0TK5MsjuhV7cU4uLe42gnzrrLbmpLM6UroNkwOTw723AwrzUSlNVecwMOEijfZj9hhvPzsTuMkIf48OfCQZk5VUJh4/D4lLlvqwFd8Lu48KXvEstWgRF+13pieinmEmQXSgBLzqMi//I9BbpPQQAl1AIy5o0HayDthDDYx5mis3sVEBwEs8dIQJgoTT"
oldmodulus="04SCCYR0PEvAAVGxNyuaeYwNQ9gOsnbQaC4jQF0CEPSkW9Aj/y8y6rwEoHRBJmGeoiemd+0XEWi1LPFeCo+7C98VsPqWS4kPPx4mVJWTGSR8Do9/CMm4Ezs/bxj2Y/aNIuEwMOdVTSlR8woDt73DkwOTDbpSOrOk5ras63yCNu4tLk5xexW6I8vkykRXjJtU59BCzrQzBwwSAlHUTkiZyXW8tqtELEkRoMWMSaOE5y89v+SsJbmNpKqI8uIKXKgaY6vtjYpS+TjcED5M40J6JDjy50uc8zXTAy0UqUI+G5hho70fKSTgs4D+oljG2T1amUoFNyKCnVW8q1O6IeLm+w=="
oldexponent="AQAB"
value="BgIAAACkAABSU0ExAAgAAAEAAQD75uIhulOrvFWdgiI3BUqZWj3Zxlii/oCz4CQpH72jYZgbPkKpFC0D0zXznEvn8jgkekLjTD4Q3Dj5UoqN7atjGqhcCuLyiKqkjbklrOS/PS/nhKNJjMWgEUksRKu2vHXJmUhO1FECEgwHM7TOQtDnVJuMV0TK5MsjuhV7cU4uLe42gnzrrLbmpLM6UroNkwOTw723AwrzUSlNVecwMOEijfZj9hhvPzsTuMkIf48OfCQZk5VUJh4/D4lLlvqwFd8Lu48KXvEstWgRF+13pieinmEmQXSgBLzqMi//I9BbpPQQAl1AIy5o0HayDthDDYx5mis3sVEBwEs8dIQJgoTT"
modulus="04SCCYR0PEvAAVGxNyuaeYwNQ9gOsnbQaC4jQF0CEPSkW9Aj/y8y6rwEoHRBJmGeoiemd+0XEWi1LPFeCo+7C98VsPqWS4kPPx4mVJWTGSR8Do9/CMm4Ezs/bxj2Y/aNIuEwMOdVTSlR8woDt73DkwOTDbpSOrOk5ras63yCNu4tLk5xexW6I8vkykRXjJtU59BCzrQzBwwSAlHUTkiZyXW8tqtELEkRoMWMSaOE5y89v+SsJbmNpKqI8uIKXKgaY6vtjYpS+TjcED5M40J6JDjy50uc8zXTAy0UqUI+G5hho70fKSTgs4D+oljG2T1amUoFNyKCnVW8q1O6IeLm+w=="
exponent="AQAB"/>
</wopi-discovery>
""",
)
assert cache.get(WOPI_CONFIGURATION_CACHE_KEY) is None
configure_wopi_clients()
# pylint: disable=line-too-long
assert cache.get(WOPI_CONFIGURATION_CACHE_KEY) == {
"mimetypes": {
"application/vnd.oasis.opendocument.text": {
"launch_url": "http://localhost:9980/browser/0968141f2c/cool.html?",
"client": "vendorA",
},
},
"extensions": {
"odt": {
"launch_url": "http://localhost:9980/browser/0968141f2c/cool.html?",
"client": "vendorA",
},
},
"vendorA": {
"proof_keys": {
"old_public_key": b"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA04SCCYR0PEvAAVGxNyua\neYwNQ9gOsnbQaC4jQF0CEPSkW9Aj/y8y6rwEoHRBJmGeoiemd+0XEWi1LPFeCo+7\nC98VsPqWS4kPPx4mVJWTGSR8Do9/CMm4Ezs/bxj2Y/aNIuEwMOdVTSlR8woDt73D\nkwOTDbpSOrOk5ras63yCNu4tLk5xexW6I8vkykRXjJtU59BCzrQzBwwSAlHUTkiZ\nyXW8tqtELEkRoMWMSaOE5y89v+SsJbmNpKqI8uIKXKgaY6vtjYpS+TjcED5M40J6\nJDjy50uc8zXTAy0UqUI+G5hho70fKSTgs4D+oljG2T1amUoFNyKCnVW8q1O6IeLm\n+wIDAQAB\n-----END PUBLIC KEY-----\n",
"public_key": b"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA04SCCYR0PEvAAVGxNyua\neYwNQ9gOsnbQaC4jQF0CEPSkW9Aj/y8y6rwEoHRBJmGeoiemd+0XEWi1LPFeCo+7\nC98VsPqWS4kPPx4mVJWTGSR8Do9/CMm4Ezs/bxj2Y/aNIuEwMOdVTSlR8woDt73D\nkwOTDbpSOrOk5ras63yCNu4tLk5xexW6I8vkykRXjJtU59BCzrQzBwwSAlHUTkiZ\nyXW8tqtELEkRoMWMSaOE5y89v+SsJbmNpKqI8uIKXKgaY6vtjYpS+TjcED5M40J6\nJDjy50uc8zXTAy0UqUI+G5hho70fKSTgs4D+oljG2T1amUoFNyKCnVW8q1O6IeLm\n+wIDAQAB\n-----END PUBLIC KEY-----\n",
},
},
}
+19 -2
View File
@@ -23,7 +23,7 @@ def is_item_wopi_supported(item, user):
def get_wopi_client_config(item, user):
"""make
"""
Get the WOPI client configuration for an item.
"""
if (
@@ -33,7 +33,7 @@ def get_wopi_client_config(item, user):
):
return None
wopi_configuration = cache.get(WOPI_CONFIGURATION_CACHE_KEY, default=WOPI_DEFAULT_CONFIGURATION)
wopi_configuration = get_wopi_configuration()
if not wopi_configuration:
return None
@@ -50,6 +50,23 @@ def get_wopi_client_config(item, user):
return result
def get_wopi_client_proof_keys(item, user):
"""get the wopi proof keys for an item"""
wopi_client_config = get_wopi_client_config(item, user)
if not wopi_client_config:
return None
wopi_configuration = get_wopi_configuration()
return wopi_configuration[wopi_client_config["client"]]["proof_keys"]
def get_wopi_configuration():
"""get the wopi configuration"""
return cache.get(WOPI_CONFIGURATION_CACHE_KEY, default=WOPI_DEFAULT_CONFIGURATION)
def compute_wopi_launch_url(launch_url, get_file_info_path, lang=None):
"""
Compute the WOPI launch URL for an item.