Skip to content

Commit f974d7f

Browse files
committed
encrypt oauth token for usage with hocuspocus
1 parent 3daf394 commit f974d7f

File tree

5 files changed

+174
-7
lines changed

5 files changed

+174
-7
lines changed

docker/dev/hocuspocus/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ services:
1515
environment:
1616
- ALLOWED_DOMAINS=openproject.local,localhost
1717
- NODE_TLS_REJECT_UNAUTHORIZED=0
18+
- SECRET=secret12345
1819
networks:
1920
gateway:
2021
external: true

modules/documents/app/controllers/documents_controller.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def show
6161
@attachments = @document.attachments.order(Arel.sql("created_at DESC"))
6262

6363
if @document.collaborative?
64-
generate_oauth_token
64+
generate_encrypted_oauth_token
6565
derive_show_edit_state_from_params
6666
end
6767
end
@@ -200,8 +200,8 @@ def create_collaborative_document
200200
redirect_to document_path(call.result, state: :edit)
201201
end
202202

203-
def generate_oauth_token
204-
# do not generate a token if the user is not allowed to manage documents
203+
# rubocop:disable Metrics/AbcSize
204+
def generate_encrypted_oauth_token
205205
if !current_user.allowed_in_project?(:view_documents, @project)
206206
return
207207
end
@@ -210,12 +210,23 @@ def generate_oauth_token
210210
.new(user: current_user)
211211
.call
212212

213-
if result.success?
214-
@oauth_token = result.result.plaintext_token
215-
else
213+
if result.failure?
216214
Rails.logger.error("Failed to generate OAuth token for document #{@document.id}: #{result.errors}")
215+
return
216+
end
217+
218+
result = Documents::OAuth::EncryptTokenService
219+
.new(token: result.result.plaintext_token)
220+
.call
221+
222+
if result.failure?
223+
Rails.logger.error("Failed to encrypt OAuth token for document #{@document.id}: #{result.errors}")
224+
return
217225
end
226+
227+
@oauth_token = result.result
218228
end
229+
# rubocop:enable Metrics/AbcSize
219230

220231
def update_header_component_via_turbo_stream(state: :show)
221232
update_via_turbo_stream(
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
module Documents
32+
module OAuth
33+
class EncryptTokenService < BaseServices::BaseCallable
34+
ALGORITHM = "aes-256-gcm"
35+
36+
def initialize(token:)
37+
super()
38+
39+
@token = token
40+
end
41+
42+
def perform
43+
encryptor = ActiveSupport::MessageEncryptor.new(
44+
key,
45+
cipher: ALGORITHM,
46+
serializer: ActiveSupport::MessageEncryptor::NullSerializer
47+
)
48+
encrypted = encryptor.encrypt_and_sign(token)
49+
50+
ServiceResult.success(result: encrypted)
51+
rescue StandardError => e
52+
ServiceResult.failure(errors: e.message)
53+
end
54+
55+
private
56+
57+
attr_reader :token
58+
59+
def key
60+
@key ||= begin
61+
secret = Setting.collaborative_editing_hocuspocus_secret
62+
raise "Collaborative editing secret is not set. Cannot encrypt token." if secret.blank?
63+
64+
Digest::SHA256.digest(secret)
65+
end
66+
end
67+
end
68+
end
69+
end

modules/documents/spec/controllers/documents_controller_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@
186186
end
187187
end
188188

189-
describe "generate_oauth_token" do
189+
describe "generate_oauth_token",
190+
with_config: { collaborative_editing_hocuspocus_secret: "secret1234" } do
190191
let(:manage_role) { create(:project_role, permissions: %i[view_documents manage_documents]) }
191192
let(:view_only_role) { create(:project_role, permissions: [:view_documents]) }
192193
let(:user_with_manage) { create(:user) }
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
require "spec_helper"
32+
33+
RSpec.describe Documents::OAuth::EncryptTokenService do
34+
subject(:service_call) { described_class.new(token:).call }
35+
36+
before do
37+
allow(Setting)
38+
.to receive(:collaborative_editing_hocuspocus_secret)
39+
.and_return(secret)
40+
end
41+
42+
describe "#call" do
43+
context "when the secret is not defined" do
44+
let(:secret) { nil }
45+
let(:token) { "anything" }
46+
47+
it "return an failure" do
48+
result = service_call
49+
expect(result).to be_failure
50+
end
51+
end
52+
53+
context "when the secret is short" do
54+
let(:secret) { "short_secret" }
55+
let(:token) { "sensitive_token_value" }
56+
57+
it "returns a successful result with the encrypted token" do
58+
result = service_call
59+
expect(result).to be_success
60+
61+
encrypted_token = result.result
62+
63+
expect(encrypted_token).not_to eq(token)
64+
expect(encrypted_token).to be_a(String)
65+
expect(encrypted_token.length).to be > token.length
66+
end
67+
end
68+
69+
context "when the secret is long" do
70+
let(:secret) { "this_is_a_very_long_and_secure_secret_for_encryption_purposes_123456" }
71+
let(:token) { "sensitive_token_value" }
72+
73+
it "returns a success result with the encrypted token" do
74+
result = service_call
75+
expect(result).to be_success
76+
77+
encrypted_token = result.result
78+
79+
expect(encrypted_token).not_to eq(token)
80+
expect(encrypted_token).to be_a(String)
81+
expect(encrypted_token.length).to be > token.length
82+
end
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)