Bỏ qua, tới nội dung
Tài liệu APIv1

Tích hợp lưu trữ

Bảo mật WOPI

access_token và thời hạn, danh sách được phép ở máy chủ soạn thảo, Nextcloud, gate và proxy, kênh truyền, và tình trạng khoá proof.

Trong trang này

Trong WOPI, máy chủ soạn thảo gọi vào kho tệp của bạn với quyền của người dùng. Bảo mật dựa trên bốn lớp: token cho từng lần mở, thời hạn của token, danh sách máy được phép ở cả hai phía, và kênh truyền mã hoá. Lớp thứ năm, khoá proof, sắp có.

access_token#

  • Sinh ngẫu nhiên, tối thiểu 128 bit, không đoán được. Không dùng mã người dùng hay mã tệp làm token.
  • Gắn với đúng một tệp, một người dùng và một quyền (đọc hoặc sửa). Mọi lời gọi WOPI đều phải kiểm lại cả ba.
  • Trình duyệt chỉ nhận token trong thân form POST gửi vào iframe. Không đặt token lên URL của trang.
  • Máy chủ soạn thảo gửi token trên chuỗi truy vấn khi gọi WOPI host. Đừng ghi chuỗi truy vấn của các đường /wopi/ vào nhật ký.
PythonCấp và kiểm token
# Cấp access_token: ngẫu nhiên, gắn với đúng một tệp, một người dùng, một quyền, và có hạn
import secrets
import time

TOKENS = {}


def issue_token(file_id: str, user_id: str, can_write: bool, hours: int = 8) -> tuple[str, int]:
    token = secrets.token_urlsafe(32)                       # 256 bit ngẫu nhiên
    ttl_ms = int((time.time() + hours * 3600) * 1000)       # access_token_ttl: mili giây Unix
    TOKENS[token] = {"file": file_id, "user": user_id, "write": can_write, "exp_ms": ttl_ms}
    return token, ttl_ms


def check_token(token: str, file_id: str, need_write: bool = False) -> dict | None:
    g = TOKENS.get(token)
    if not g or g["file"] != file_id or g["exp_ms"] < time.time() * 1000:
        return None                                          # trả 401 cho máy chủ soạn thảo
    if need_write and not g["write"]:
        return None
    return g

Thời hạn#

access_token_ttl là thời điểm token hết hạn, tính bằng mili giây Unix, gửi kèm token trong form. Chọn thời hạn đủ cho một buổi làm việc (vài giờ). Hết hạn giữa chừng thì người dùng phải mở lại tài liệu. Riêng PutFile: vẫn nhận lần lưu cuối của một phiên sửa hợp lệ vừa hết hạn, để không mất dữ liệu.

NơiThời hạn trong v1
Phiên nhúng O3O.Editor (gate là WOPI host)O3O_EMBED_SESSION_TTL_MINUTES, mặc định 720 phút
Nextcloudrichdocuments tự quản lý token của nó
WOPI host của bạnBạn quyết định

Danh sách được phép ở cả hai phía#

LớpCấu hìnhChặn điều gì
Máy chủ soạn thảoO3O_ONLINE_ALIASGROUP1, O3O_ONLINE_ALIASGROUP2 (và nhóm thêm)Chỉ mở tài liệu có WOPISrc thuộc các nhóm này. Nhóm 1 luôn là gate. Lạ thì báo Unauthorized WOPI host.
Nextcloudwopi_allowlistChỉ nhận lời gọi WOPI từ IP/CIDR của máy chủ soạn thảo.
WOPI host của bạntường lửa, allow/deny của proxyChỉ máy chủ soạn thảo gọi được /wopi/.
o3o-gateO3O_WOPI_ALLOWED_HOSTSGate chỉ gọi CheckFileInfo (khi đã chạm trần) tới các gốc liệt kê. Rỗng thì không gọi ra ngoài.
o3o-proxycấu hình cố địnhTrả 404 từ bên ngoài cho /o3o/wopi/, /o3o/auth, bảng quản trị và số liệu của máy chủ soạn thảo.
nginxVí dụ nginx trước WOPI host của bạn
# Máy chủ soạn thảo gửi access_token trên chuỗi truy vấn. Đừng ghi chuỗi truy vấn của /wopi/ vào nhật ký.
log_format wopi_safe '$remote_addr [$time_local] "$request_method $uri" $status $body_bytes_sent';

location /wopi/ {
    access_log /var/log/nginx/wopi.log wopi_safe;
    allow 10.20.0.15;      # IP của máy chủ soạn thảo
    deny all;
    proxy_pass http://127.0.0.1:5000;
}

Kênh truyền#

  • Người dùng vào bằng https: cắt TLS ở proxy phía trước và đặt O3O_ONLINE_SSL_TERMINATION=true.
  • Lời gọi WOPI giữa máy chủ soạn thảo và host đi qua mạng nào thì mạng đó phải tin được. Khác trung tâm dữ liệu thì dùng https.
  • Trang nhúng khác origin: đặt O3O_ONLINE_FRAME_ANCESTORS để chỉ trang của bạn được nhúng khung soạn thảo.
BashCác biến nên đặt khi chạy thật
# online/.env — các khoá liên quan tới WOPI khi chạy thật
O3O_ONLINE_ALIASGROUP1=http://o3o-gate:8070                  # luôn là gate, không đổi
O3O_ONLINE_ALIASGROUP2=https://cloud.example.com             # WOPI host được phép; bí danh thêm sau dấu phẩy là biểu thức chính quy
O3O_WOPI_ALLOWED_HOSTS=https://cloud.example.com             # gate chỉ gọi CheckFileInfo tới đây
O3O_ONLINE_SSL_TERMINATION=true
O3O_ONLINE_FRAME_ANCESTORS=https://cloud.example.com
O3O_COOLWSD_ADMIN_PASSWORD=<chuỗi ngẫu nhiên 32 ký tự>

# Kiểm từ bên ngoài: các đường quản trị và WOPI nội bộ phải trả 404, kể cả dạng có dấu / cuối
# curl -s -o /dev/null --path-as-is -w "%{http_code}\n" https://office.example.com/cool/adminws
# curl -s -o /dev/null --path-as-is -w "%{http_code}\n" https://office.example.com/cool/adminws/
# curl -s -o /dev/null --path-as-is -w "%{http_code}\n" https://office.example.com/cool/getMetrics/
# curl -s -o /dev/null --path-as-is -w "%{http_code}\n" https://office.example.com/o3o/wopi/files/x

Khoá proof#

Khoá proof cho phép WOPI host kiểm rằng lời gọi thật sự đến từ máy chủ soạn thảo (header X-WOPI-Proof, khoá công khai công bố trong discovery). Bản v1 chưa gắn khoá này nên discovery không có phần tử proof-key. Nextcloud 30 (richdocuments 8.5.17) không kiểm chữ ký proof nên không bị ảnh hưởng; oCIS thì bắt buộc, vì vậy oCIS chưa dùng được với v1.

BashKiểm discovery có khoá proof chưa
# Đếm phần tử proof-key trong discovery. v1 trả 0: máy chủ soạn thảo chưa được cấp khoá proof.
curl -s http://localhost:8080/hosting/discovery | grep -c "proof-key"
Sắp có

Khoá proof cho máy chủ soạn thảo, đồng bộ khoá giữa nhiều máy chủ soạn thảo, và mã mẫu kiểm X-WOPI-Proof cho WOPI host tự viết.