mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
[#65875] Refactor collaborative editing.
- Use published docker image for local dev setup. - Handle a case when Setting.collaborative_editing_hocuspocus_url is set to invalid URI. - Remove unneedd CSS. - Add some tests.
This commit is contained in:
@@ -54,15 +54,22 @@ module DynamicContentSecurityPolicy
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def add_hocuspocus_host_to_csp
|
||||
hocuspocus_url = Setting.collaborative_editing_hocuspocus_url
|
||||
if hocuspocus_url.present?
|
||||
uri = URI.parse(hocuspocus_url)
|
||||
base_url = "#{uri.scheme}://#{uri.host}"
|
||||
|
||||
append_content_security_policy_directives(connect_src: [base_url])
|
||||
uri = begin
|
||||
URI.parse(hocuspocus_url)
|
||||
rescue URI::InvalidURIError
|
||||
OpenProject.logger.info do
|
||||
"Setting.collaborative_editing_hocuspocus_url is set to an invalid URI: #{hocuspocus_url}"
|
||||
end
|
||||
nil
|
||||
end
|
||||
if uri.present?
|
||||
append_content_security_policy_directives(connect_src: ["#{uri.scheme}://#{uri.host}"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -566,7 +566,7 @@ module Settings
|
||||
description: "Additional allowed host names for the application.",
|
||||
default: []
|
||||
},
|
||||
collaborative_editing_hocuspocus_url: {
|
||||
collaborative_editing_hocuspocus_url: {
|
||||
format: :string,
|
||||
default: nil,
|
||||
description: "The URL of the hocuspocus server used by BlockNoteJS editor to enable collaborative editing.",
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Server } from "@hocuspocus/server";
|
||||
import { createVerifier } from 'fast-jwt'
|
||||
import { ServerBlockNoteEditor } from "@blocknote/server-util";
|
||||
import {
|
||||
BlockNoteSchema,
|
||||
defaultBlockSpecs,
|
||||
defaultInlineContentSpecs,
|
||||
defaultStyleSpecs,
|
||||
} from "@blocknote/core";
|
||||
import * as Y from "yjs";
|
||||
import { SQLite } from "@hocuspocus/extension-sqlite";
|
||||
|
||||
const secret = "secret12345"
|
||||
const verifyToken = createVerifier({
|
||||
key: async () => secret,
|
||||
algorithms: ['HS256'],
|
||||
})
|
||||
|
||||
const server = new Server({
|
||||
port: 1234,
|
||||
quite: false,
|
||||
extensions: [
|
||||
new SQLite({
|
||||
database: "db.sqlite",
|
||||
}),
|
||||
],
|
||||
onConnect(data) {
|
||||
console.log('CONNECTED: documentName: %0, socketId %0', data.documentName, data.socketId);
|
||||
},
|
||||
async afterUnloadDocument(data) {
|
||||
console.log(`Document ${data.documentName} was closed`);
|
||||
},
|
||||
async onChange(data) {
|
||||
console.log(`Document ${data.documentName} was changed`);
|
||||
},
|
||||
async onLoadDocument({ context, documentName, document }) {
|
||||
const fragment = document.getXmlFragment('document-store');
|
||||
if (fragment.length === 0) {
|
||||
const schema = BlockNoteSchema.create({
|
||||
blockSpecs: defaultBlockSpecs,
|
||||
});
|
||||
const editor = ServerBlockNoteEditor.create({schema});
|
||||
const blocks = await editor.tryParseMarkdownToBlocks(context.document_text);
|
||||
const doc = editor.blocksToYDoc(blocks, "document-store");
|
||||
return doc;
|
||||
}
|
||||
},
|
||||
|
||||
async onAuthenticate(data) {
|
||||
const { token, documentName } = data;
|
||||
if (!token) {
|
||||
throw new Error('Unauthorized: Token missing.')
|
||||
}
|
||||
let tokenPayload;
|
||||
try {
|
||||
tokenPayload = await verifyToken(token)
|
||||
} catch (err) {
|
||||
throw new Error('Unauthorized: Invalid token.')
|
||||
}
|
||||
console.log('Token payload:', tokenPayload);
|
||||
if(documentName != tokenPayload.document_id) {
|
||||
throw new Error('Unauthorized: Invalid token. This document cannot be accessed with this token.')
|
||||
}
|
||||
data.context.document_text = tokenPayload.document_text;
|
||||
},
|
||||
});
|
||||
|
||||
server.listen();
|
||||
-4991
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "openproject-hocuspocus",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@blocknote/server-util": "^0.33.0",
|
||||
"@hocuspocus/extension-sqlite": "^3.2.2",
|
||||
"@hocuspocus/server": "^3.2.0",
|
||||
"fast-jwt": "^6.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
services:
|
||||
hocuspocus:
|
||||
command: sh -c "npm install && node index.js"
|
||||
image: node:20
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- ./app:/app
|
||||
- node_modules:/app/node_modules
|
||||
image: openproject/hocuspocus:main-88913ad0
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.hocuspocus.rule=Host(`hocuspocus.local`)"
|
||||
@@ -13,10 +8,10 @@ services:
|
||||
- "traefik.http.routers.hocuspocus.tls=true"
|
||||
- "traefik.http.services.hocuspocus-service.loadbalancer.server.port=1234"
|
||||
- "traefik.http.routers.hocuspocus.tls.certresolver=step"
|
||||
# - "traefik.http.serversTransports.insecureTransport.insecureSkipVerify=true"
|
||||
# - "traefik.http.services.my-wss-service.loadBalancer.serversTransport=insecureTransport"
|
||||
networks:
|
||||
- gateway
|
||||
environment:
|
||||
- SECRET=secret12345
|
||||
networks:
|
||||
gateway:
|
||||
external: true
|
||||
|
||||
Generated
+131
-5
@@ -43,6 +43,7 @@
|
||||
"@fullcalendar/resource-timeline": "^6.1.11",
|
||||
"@fullcalendar/timegrid": "^6.1.11",
|
||||
"@github/webauthn-json": "^2.1.1",
|
||||
"@hocuspocus/provider": "^3.2.0",
|
||||
"@hotwired/stimulus": "^3.2.2",
|
||||
"@hotwired/turbo": "^8.0.10",
|
||||
"@hotwired/turbo-rails": "^8.0.10",
|
||||
@@ -176,9 +177,10 @@
|
||||
"source-map-explorer": "^2.5.2",
|
||||
"theo": "^8.1.5",
|
||||
"ts-node": "~8.3.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"webpack-bundle-analyzer": "^4.4.2"
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"wscat": "^6.1.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "*"
|
||||
@@ -4182,6 +4184,31 @@
|
||||
"webauthn-json": "dist/bin/main.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@hocuspocus/common": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-3.2.3.tgz",
|
||||
"integrity": "sha512-P8COsx2HVXS7NbDEKe9KSt5Hd1A95hZhyTabiNPlU/Pi+7K1RJuHqIkIRr4oIxGzujvpLq/LSwBHP4NYUk+cGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.87"
|
||||
}
|
||||
},
|
||||
"node_modules/@hocuspocus/provider": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-3.2.3.tgz",
|
||||
"integrity": "sha512-M62Fly7s6sKKGGFze46fypjV3jv2Y2l8PA6/+IgbQRWH275p8NjGP1U7yMOxi52PLFrgaEoy+fQyW93iJ2jqaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hocuspocus/common": "^3.2.3",
|
||||
"@lifeomic/attempt": "^3.0.2",
|
||||
"lib0": "^0.2.87",
|
||||
"ws": "^8.17.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"y-protocols": "^1.0.6",
|
||||
"yjs": "^13.6.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@hotwired/stimulus": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz",
|
||||
@@ -4962,6 +4989,12 @@
|
||||
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lifeomic/attempt": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lifeomic/attempt/-/attempt-3.1.0.tgz",
|
||||
"integrity": "sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@listr2/prompt-adapter-inquirer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz",
|
||||
@@ -21147,6 +21180,19 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read/-/read-4.1.0.tgz",
|
||||
"integrity": "sha512-uRfX6K+f+R8OOrYScaM3ixPY4erg69f8DN6pgTvMcA9iRc8iDhwrA4m3Yu8YYKsXJgVvum+m8PkRboZwwuLzYA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"mute-stream": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -26164,7 +26210,6 @@
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -26182,6 +26227,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/wscat": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wscat/-/wscat-6.1.0.tgz",
|
||||
"integrity": "sha512-x6gEZvITvqWslR38DoBfnMi37ZBUGsG9rTkGc/200sEfSs1JwgKLZYQeqa0vlu3bxXQV7hEHI4NF7KQmYIzB2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^12.1.0",
|
||||
"https-proxy-agent": "^7.0.5",
|
||||
"read": "^4.0.0",
|
||||
"ws": "^8.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"wscat": "bin/wscat"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/wscat/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/wsl-utils": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
|
||||
@@ -28933,6 +29007,25 @@
|
||||
"resolved": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-2.1.1.tgz",
|
||||
"integrity": "sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ=="
|
||||
},
|
||||
"@hocuspocus/common": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-3.2.3.tgz",
|
||||
"integrity": "sha512-P8COsx2HVXS7NbDEKe9KSt5Hd1A95hZhyTabiNPlU/Pi+7K1RJuHqIkIRr4oIxGzujvpLq/LSwBHP4NYUk+cGA==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.87"
|
||||
}
|
||||
},
|
||||
"@hocuspocus/provider": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-3.2.3.tgz",
|
||||
"integrity": "sha512-M62Fly7s6sKKGGFze46fypjV3jv2Y2l8PA6/+IgbQRWH275p8NjGP1U7yMOxi52PLFrgaEoy+fQyW93iJ2jqaw==",
|
||||
"requires": {
|
||||
"@hocuspocus/common": "^3.2.3",
|
||||
"@lifeomic/attempt": "^3.0.2",
|
||||
"lib0": "^0.2.87",
|
||||
"ws": "^8.17.1"
|
||||
}
|
||||
},
|
||||
"@hotwired/stimulus": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz",
|
||||
@@ -29399,6 +29492,11 @@
|
||||
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
||||
"dev": true
|
||||
},
|
||||
"@lifeomic/attempt": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lifeomic/attempt/-/attempt-3.1.0.tgz",
|
||||
"integrity": "sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw=="
|
||||
},
|
||||
"@listr2/prompt-adapter-inquirer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz",
|
||||
@@ -40682,6 +40780,15 @@
|
||||
"use-latest": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"read": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read/-/read-4.1.0.tgz",
|
||||
"integrity": "sha512-uRfX6K+f+R8OOrYScaM3ixPY4erg69f8DN6pgTvMcA9iRc8iDhwrA4m3Yu8YYKsXJgVvum+m8PkRboZwwuLzYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mute-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -44171,8 +44278,27 @@
|
||||
"ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="
|
||||
},
|
||||
"wscat": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wscat/-/wscat-6.1.0.tgz",
|
||||
"integrity": "sha512-x6gEZvITvqWslR38DoBfnMi37ZBUGsG9rTkGc/200sEfSs1JwgKLZYQeqa0vlu3bxXQV7hEHI4NF7KQmYIzB2A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^12.1.0",
|
||||
"https-proxy-agent": "^7.0.5",
|
||||
"read": "^4.0.0",
|
||||
"ws": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"wsl-utils": {
|
||||
"version": "0.1.0",
|
||||
|
||||
@@ -29,22 +29,3 @@
|
||||
// Component specific Styles
|
||||
@import "../../../app/components/_index.sass"
|
||||
@import "../../../app/forms/_index.sass"
|
||||
|
||||
.comments-main-container
|
||||
align-items: center
|
||||
display: flex
|
||||
flex-direction: column-reverse
|
||||
gap: 10px
|
||||
height: 100%
|
||||
max-width: none
|
||||
padding: 10px
|
||||
width: 100%
|
||||
.bn-editor
|
||||
height: 100%
|
||||
max-width: 700px
|
||||
min-height: 80vh
|
||||
overflow: auto
|
||||
width: 100%
|
||||
.bn-thread-comments
|
||||
.bn-editor
|
||||
min-height: 3vh
|
||||
|
||||
@@ -32,8 +32,7 @@ import { Controller } from '@hotwired/stimulus';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import OpBlockNoteContainer from '../../../react/OpBlockNoteContainer';
|
||||
// import OpBlockNoteContainer from 'react/OpBlockNoteContainer';
|
||||
import { User } from "@blocknote/core/comments";
|
||||
import { User } from '@blocknote/core/comments';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [
|
||||
@@ -53,7 +52,7 @@ export default class extends Controller {
|
||||
declare readonly blockNoteEditorTarget:HTMLElement;
|
||||
declare readonly blockNoteInputFieldTarget:HTMLInputElement;
|
||||
declare readonly inputTextValue:string;
|
||||
declare readonly usersValue:Array<User>;
|
||||
declare readonly usersValue:User[];
|
||||
declare readonly activeUserValue:User;
|
||||
declare readonly hocuspocusUrlValue:string;
|
||||
declare readonly hocuspocusAccessTokenValue:string;
|
||||
|
||||
@@ -32,12 +32,7 @@ require "spec_helper"
|
||||
require_module_spec_helper
|
||||
|
||||
RSpec.describe "Appendix of default CSP for external file storage hosts" do
|
||||
def parse_csp(csp_string)
|
||||
csp_string
|
||||
.split("; ")
|
||||
.map(&:split)
|
||||
.each_with_object({}) { |csp_part, csp_hash_map| csp_hash_map[csp_part[0]] = csp_part[1..] }
|
||||
end
|
||||
include CspHelper
|
||||
|
||||
shared_let(:project) { create(:project) }
|
||||
shared_let(:storage) { create(:nextcloud_storage) }
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "" do
|
||||
include CspHelper
|
||||
|
||||
current_user { create(:user) }
|
||||
|
||||
describe "GET /" do
|
||||
context "when collaborative_editing_hocuspocus_url is set as a valid URI" do
|
||||
it "responds with 200 and appends storage host to the connect-src CSP",
|
||||
with_settings: { collaborative_editing_hocuspocus_url: "wss://hocuspocus.local" } do
|
||||
get "/"
|
||||
|
||||
expect(last_response).to have_http_status(200)
|
||||
csp = parse_csp(last_response.headers["Content-Security-Policy"])
|
||||
expect(csp["connect-src"]).to include("wss://hocuspocus.local")
|
||||
end
|
||||
end
|
||||
|
||||
context "when collaborative_editing_hocuspocus_url is set to an invalid URI" do
|
||||
it "responds with 200 and logs the problem",
|
||||
with_settings: { collaborative_editing_hocuspocus_url: "://hocuspocus.local" } do
|
||||
allow(OpenProject.logger).to receive(:info)
|
||||
|
||||
get "/"
|
||||
|
||||
expect(last_response).to have_http_status(200)
|
||||
expect(OpenProject.logger).to have_received(:info) do |&blk|
|
||||
expect(blk.call).to eq "Setting.collaborative_editing_hocuspocus_url is set to an invalid URI: ://hocuspocus.local"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe CollaborativeEditing::DocumentIdGenerator do
|
||||
describe ".call" do
|
||||
let(:secret_key_base) { "test_secret" }
|
||||
|
||||
before do
|
||||
allow(Rails.application).to receive(:secret_key_base).and_return(secret_key_base)
|
||||
end
|
||||
|
||||
it "returns a SHA256 HMAC hex digest of category and id" do
|
||||
result = described_class.call("documents", 123)
|
||||
|
||||
expect(result).to eq("a809f02491b92e3addef5bc78319f788ca0d9c8e56c9a67532f6f8d76e5b54cc")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe CollaborativeEditing::DocumentAccessTokenGenerator do
|
||||
describe ".call" do
|
||||
let(:document_id) { "a809f02491b92e3addef5bc78319f788ca0d9c8e56c9a67532f6f8d76e5b54cc" }
|
||||
let(:document_text) { "Some text" }
|
||||
let(:secret) { "jwt_secret" }
|
||||
|
||||
context "when Setting.collaborative_editing_hocuspocus_secret is present" do
|
||||
before do
|
||||
allow(Setting).to receive(:collaborative_editing_hocuspocus_secret).and_return(secret)
|
||||
end
|
||||
|
||||
it "returns a JWT token" do
|
||||
token = described_class.call(document_id, document_text)
|
||||
|
||||
expect(token).to be_a(String)
|
||||
|
||||
payload, header = JWT.decode(token, secret, true, algorithm: "HS256")
|
||||
|
||||
expect(payload["document_id"]).to eq(document_id)
|
||||
expect(payload["document_text"]).to eq(document_text)
|
||||
expect(payload["exp"]).to be_within(5).of(20.minutes.from_now.to_i)
|
||||
expect(header["alg"]).to eq("HS256")
|
||||
end
|
||||
end
|
||||
|
||||
context "when Setting.collaborative_editing_hocuspocus_secret is not present" do
|
||||
before do
|
||||
allow(Setting).to receive(:collaborative_editing_hocuspocus_secret).and_return(nil)
|
||||
end
|
||||
|
||||
it "returns nil" do
|
||||
expect(described_class.call(document_id, document_text)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module CspHelper
|
||||
def parse_csp(csp_string)
|
||||
csp_string
|
||||
.split("; ")
|
||||
.map(&:split)
|
||||
.each_with_object({}) { |csp_part, csp_hash_map| csp_hash_map[csp_part[0]] = csp_part[1..] }
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user