scinote-web/app/utilities/wopi_util.rb

155 lines
5.9 KiB
Ruby
Raw Normal View History

2020-09-29 18:58:05 +08:00
# frozen_string_literal: true
2016-08-03 21:31:25 +08:00
module WopiUtil
require 'open-uri'
# Used for timestamp
UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000
CLR_TICKS_PER_SECOND = 10000000
2016-08-03 21:31:25 +08:00
2020-09-29 18:58:05 +08:00
DISCOVERY_TTL = 1.day
2016-08-03 21:31:25 +08:00
DISCOVERY_TTL.freeze
# For more explanation see this:
# http://stackoverflow.com/questions/11888053/
# convert-net-datetime-ticks-property-to-date-in-objective-c
def convert_to_unix_timestamp(timestamp)
2020-09-29 18:58:05 +08:00
Time.zone.at((timestamp - UNIX_EPOCH_IN_CLR_TICKS) / CLR_TICKS_PER_SECOND)
end
2016-08-03 21:31:25 +08:00
2020-09-29 18:58:05 +08:00
def get_action(extension, action)
discovery = current_wopi_discovery
discovery[:actions].find { |i| i[:extension] == extension && i[:action] == action }
end
2016-08-03 21:31:25 +08:00
def current_wopi_discovery
2020-09-29 18:58:05 +08:00
initialize_discovery
end
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
# with this discovery public key (two key possible old/new)
def wopi_verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
discovery = current_wopi_discovery
token_length = [token.length].pack('>N').bytes
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
url_length = [url.length].pack('>N').bytes
expected_proof = token_length + token.bytes +
url_length + url.upcase.bytes +
timestamp_length + timestamp_bytes
key = generate_key(discovery[:proof_key_mod], discovery[:proof_key_exp])
old_key = generate_key(discovery[:proof_key_old_mod], discovery[:proof_key_old_exp])
# Try all possible combiniations
try_verification(expected_proof, signed_proof, key) ||
try_verification(expected_proof, signed_proof_old, key) ||
try_verification(expected_proof, signed_proof, old_key)
2016-08-03 21:31:25 +08:00
end
private
2016-08-03 21:31:25 +08:00
# Currently only saves Excel, Word and PowerPoint view and edit actions
2020-09-29 18:58:05 +08:00
def initialize_discovery
Rails.cache.fetch(:wopi_discovery, expires_in: DISCOVERY_TTL) do
@doc = Nokogiri::XML(Net::HTTP.get(URI(ENV.fetch('WOPI_DISCOVERY_URL', nil))))
2020-09-29 18:58:05 +08:00
discovery_json = {}
key = @doc.xpath('//proof-key')
discovery_json[:proof_key_mod] = key.xpath('@modulus').first.value
discovery_json[:proof_key_exp] = key.xpath('@exponent').first.value
discovery_json[:proof_key_old_mod] = key.xpath('@oldmodulus').first.value
discovery_json[:proof_key_old_exp] = key.xpath('@oldexponent').first.value
discovery_json[:actions] = []
wopi_net_zone_name = ENV.fetch('WOPI_NET_ZONE_NAME', nil)
wopi_apps_path = wopi_net_zone_name.present? ? "//net-zone[@name='#{wopi_net_zone_name}']//app" : '//app'
2020-09-29 18:58:05 +08:00
@doc.xpath(wopi_apps_path).each do |app|
2020-09-29 18:58:05 +08:00
app_name = app.xpath('@name').first.value
next unless %w(Excel Word PowerPoint WopiTest).include?(app_name)
icon = app.xpath('@favIconUrl').first.value
app.xpath('action').each do |action|
action_name = action.xpath('@name').first.value
next unless %w(view edit editnew embedview wopitest).include?(action_name)
action_json = {}
action_json[:icon] = icon
action_json[:action] = action_name
action_json[:extension] = action.xpath('@ext').first.value
action_json[:urlsrc] = action.xpath('@urlsrc').first.value
discovery_json[:actions].push(action_json)
end
2016-08-03 21:31:25 +08:00
end
2020-09-29 18:58:05 +08:00
discovery_json
2016-08-03 21:31:25 +08:00
end
2020-09-29 18:58:05 +08:00
rescue StandardError => e
Rails.logger.warn 'WOPI: initialization failed: ' + e.message
e.backtrace.each { |line| Rails.logger.error line }
2020-09-29 18:58:05 +08:00
end
# Generates a public key from given modulus and exponent
def generate_key(modulus, exponent)
mod = Base64.decode64(modulus).unpack1('H*').to_i(16)
exp = Base64.decode64(exponent).unpack1('H*').to_i(16)
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
OpenSSL::ASN1::Integer.new(exp)])
OpenSSL::PKey::RSA.new(seq.to_der)
end
# Verify if decrypting signed_proof with public_key equals to expected_proof
def try_verification(expected_proof, signed_proof_b64, public_key)
signed_proof = Base64.decode64(signed_proof_b64)
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
expected_proof.pack('c*'))
2016-08-03 21:31:25 +08:00
end
2016-09-29 21:30:55 +08:00
def create_wopi_file_activity(current_user, started_editing)
action = if started_editing
t('activities.wupi_file_editing.started')
else
t('activities.wupi_file_editing.finished')
end
2016-09-29 21:30:55 +08:00
if @assoc.class == Step
2019-03-29 22:09:20 +08:00
default_step_items =
{ step: @asset.step.id,
step_position: { id: @asset.step.id, value_for: 'position_plus_one' },
asset_name: { id: @asset.id, value_for: 'file_name' },
2019-03-29 22:09:20 +08:00
action: action }
2016-09-29 21:30:55 +08:00
if @protocol.in_module?
project = @protocol.my_module.experiment.project
team = project.team
2019-03-29 22:09:20 +08:00
type_of = :edit_wopi_file_on_step
message_items = { my_module: @protocol.my_module.id }
else
type_of = :edit_wopi_file_on_step_in_repository
project = nil
team = @protocol.team
message_items = { protocol: @protocol.id }
2016-09-29 21:30:55 +08:00
end
2019-03-29 22:09:20 +08:00
message_items = default_step_items.merge(message_items)
Activities::CreateActivityService
2019-03-29 22:09:20 +08:00
.call(activity_type: type_of,
owner: current_user,
subject: @protocol,
team: team,
project: project,
2019-03-29 22:09:20 +08:00
message_items: message_items)
elsif @assoc.class == Result
Activities::CreateActivityService
.call(activity_type: :edit_wopi_file_on_result,
owner: current_user,
subject: @asset.result,
team: @my_module.experiment.project.team,
project: @my_module.experiment.project,
message_items: {
result: @asset.result.id,
asset_name: { id: @asset.id, value_for: 'file_name' },
action: action
})
2016-09-29 21:30:55 +08:00
end
end
end