mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-11-10 00:11:22 +08:00
Refactor the rest of WOPI logic from Nejc
This commit is contained in:
parent
c1ae4360a3
commit
5aea0fbb19
12 changed files with 247 additions and 261 deletions
|
|
@ -66,13 +66,13 @@ class AssetsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
@action_url = @asset.get_action_url(current_user,"edit",false)
|
||||
@action_url = @asset.get_action_url(current_user, 'edit', false)
|
||||
@token = current_user.get_wopi_token
|
||||
@ttl = (current_user.wopi_token_ttl * 1000).to_s
|
||||
end
|
||||
|
||||
def view
|
||||
@action_url = @asset.get_action_url(current_user,"view",false)
|
||||
@action_url = @asset.get_action_url(current_user, 'view', false)
|
||||
@token = current_user.get_wopi_token
|
||||
@ttl = (current_user.wopi_token_ttl * 1000).to_s
|
||||
end
|
||||
|
|
@ -81,16 +81,12 @@ class AssetsController < ApplicationController
|
|||
|
||||
def load_vars
|
||||
@asset = Asset.find_by_id(params[:id])
|
||||
|
||||
unless @asset
|
||||
render_404
|
||||
end
|
||||
render_404 unless @asset
|
||||
|
||||
step_assoc = @asset.step
|
||||
result_assoc = @asset.result
|
||||
|
||||
@assoc = step_assoc if not step_assoc.nil?
|
||||
@assoc = result_assoc if not result_assoc.nil?
|
||||
@assoc = step_assoc unless step_assoc.nil?
|
||||
@assoc = result_assoc unless result_assoc.nil?
|
||||
|
||||
if @assoc.class == Step
|
||||
@protocol = @asset.step.protocol
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class WopiController < ActionController::Base
|
|||
logger.warn 'WOPI: lock; ' + lock.to_s
|
||||
render nothing: :true, status: 404 and return if lock.nil? || lock.blank?
|
||||
@asset.with_lock do
|
||||
if @asset.is_locked
|
||||
if @asset.locked?
|
||||
if @asset.lock == lock
|
||||
@asset.refresh_lock
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
|
|
@ -117,7 +117,7 @@ class WopiController < ActionController::Base
|
|||
render nothing: :true, status: 400 and return
|
||||
end
|
||||
@asset.with_lock do
|
||||
if @asset.is_locked
|
||||
if @asset.locked?
|
||||
if @asset.lock == old_lock
|
||||
@asset.unlock
|
||||
@asset.lock_asset(lock)
|
||||
|
|
@ -138,9 +138,9 @@ class WopiController < ActionController::Base
|
|||
lock = request.headers['X-WOPI-Lock']
|
||||
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
||||
@asset.with_lock do
|
||||
if @asset.is_locked
|
||||
logger.warn 'WOPI: current asset lock: #{@asset.lock},
|
||||
unlocking lock #{lock}'
|
||||
if @asset.locked?
|
||||
logger.warn "WOPI: current asset lock: #{@asset.lock},
|
||||
unlocking lock #{lock}"
|
||||
if @asset.lock == lock
|
||||
@asset.unlock
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
|
|
@ -161,7 +161,7 @@ class WopiController < ActionController::Base
|
|||
lock = request.headers['X-WOPI-Lock']
|
||||
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
||||
@asset.with_lock do
|
||||
if @asset.is_locked
|
||||
if @asset.locked?
|
||||
if @asset.lock == lock
|
||||
@asset.refresh_lock
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
|
|
@ -180,7 +180,7 @@ class WopiController < ActionController::Base
|
|||
|
||||
def get_lock
|
||||
@asset.with_lock do
|
||||
if @asset.is_locked
|
||||
if @asset.locked?
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = ''
|
||||
|
|
@ -193,7 +193,7 @@ class WopiController < ActionController::Base
|
|||
def put_file
|
||||
@asset.with_lock do
|
||||
lock = request.headers['X-WOPI-Lock']
|
||||
if @asset.is_locked
|
||||
if @asset.locked?
|
||||
if @asset.lock == lock
|
||||
logger.warn 'WOPI: replacing file'
|
||||
@asset.update_contents(request.body)
|
||||
|
|
@ -263,7 +263,7 @@ class WopiController < ActionController::Base
|
|||
url = request.original_url.upcase.encode('utf-8')
|
||||
|
||||
if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now
|
||||
if get_discovery.verify_proof(token, timestamp, signed_proof,
|
||||
if current_wopi_discovery.verify_proof(token, timestamp, signed_proof,
|
||||
signed_proof_old, url)
|
||||
logger.warn 'WOPI: proof verification: successful'
|
||||
else
|
||||
|
|
|
|||
|
|
@ -288,9 +288,12 @@ class Asset < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Preserving attachments (on client-side) between failed validations (only usable for small/few files!)
|
||||
# Needs to be called before save method and view needs to have :file_content and :file_info hidden field
|
||||
# If file is an image, it can be viewed on front-end using @preview_cached with image_tag tag
|
||||
# Preserving attachments (on client-side) between failed validations
|
||||
# (only usable for small/few files!).
|
||||
# Needs to be called before save method and view needs to have
|
||||
# :file_content and :file_info hidden field.
|
||||
# If file is an image, it can be viewed on front-end
|
||||
# using @preview_cached with image_tag tag.
|
||||
def preserve(file_data)
|
||||
if file_data[:file_content].present?
|
||||
restore_cached(file_data[:file_content], file_data[:file_info])
|
||||
|
|
@ -299,28 +302,31 @@ class Asset < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def can_perform_action(action)
|
||||
file_ext = file_file_name.split(".").last
|
||||
file_ext = file_file_name.split('.').last
|
||||
action = get_action(file_ext, action)
|
||||
if action.nil?
|
||||
return false
|
||||
end
|
||||
return false if action.nil?
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def get_action_url(user, action, with_tokens = true)
|
||||
file_ext = file_file_name.split(".").last
|
||||
file_ext = file_file_name.split('.').last
|
||||
action = get_action(file_ext, action)
|
||||
if !action.nil?
|
||||
action_url = action.urlsrc
|
||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/, "IsLicensedUser=0&")
|
||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER>/, "IsLicensedUser=0")
|
||||
action_url = action_url.gsub(/<.*?=.*?>/, "")
|
||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/,
|
||||
'IsLicensedUser=0&')
|
||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER>/,
|
||||
'IsLicensedUser=0')
|
||||
action_url = action_url.gsub(/<.*?=.*?>/, '')
|
||||
|
||||
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(host: ENV["WOPI_ENDPOINT_URL"],id: id)
|
||||
action_url = action_url + "WOPISrc=#{rest_url}"
|
||||
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(
|
||||
host: ENV['WOPI_ENDPOINT_URL'],
|
||||
id: id
|
||||
)
|
||||
action_url += "WOPISrc=#{rest_url}"
|
||||
if with_tokens
|
||||
action_url = action_url + "&access_token=#{user.get_wopi_token}&access_token_ttl=#{(user.wopi_token_ttl*1000).to_s}"
|
||||
action_url + "&access_token=#{user.get_wopi_token}"\
|
||||
"&access_token_ttl=#{(user.wopi_token_ttl * 1000)}"
|
||||
else
|
||||
action_url
|
||||
end
|
||||
|
|
@ -329,56 +335,48 @@ class Asset < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
#is_locked, lock_asset and refresh_lock rely on the asset being locked in the database to prevent race conditions
|
||||
def is_locked
|
||||
if lock.nil?
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
# locked?, lock_asset and refresh_lock rely on the asset
|
||||
# being locked in the database to prevent race conditions
|
||||
def locked?
|
||||
!lock.nil?
|
||||
end
|
||||
|
||||
def lock_asset(lock_string)
|
||||
self.lock = lock_string
|
||||
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
||||
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
||||
self.save!
|
||||
save!
|
||||
end
|
||||
|
||||
def refresh_lock
|
||||
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
||||
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
||||
self.save!
|
||||
save!
|
||||
end
|
||||
|
||||
def unlock
|
||||
self.lock = nil
|
||||
self.lock_ttl = nil
|
||||
self.save!
|
||||
save!
|
||||
end
|
||||
|
||||
def unlock_expired
|
||||
self.with_lock do
|
||||
if !self.lock_ttl.nil? and self.lock_ttl>= Time.now.to_i
|
||||
with_lock do
|
||||
if !lock_ttl.nil? && lock_ttl >= Time.now.to_i
|
||||
self.lock = nil
|
||||
self.lock_ttl = nil
|
||||
self.save!
|
||||
save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_contents(new_file)
|
||||
new_file.class.class_eval { attr_accessor :original_filename }
|
||||
new_file.original_filename = self.file_file_name
|
||||
new_file.original_filename = file_file_name
|
||||
self.file = new_file
|
||||
if self.version.nil?
|
||||
self.version = 1
|
||||
else
|
||||
self.version = self.version + 1
|
||||
self.version = version.nil? ? 1 : version + 1
|
||||
save
|
||||
end
|
||||
self.save
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
|
|
|
|||
|
|
@ -253,30 +253,25 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.find_by_valid_wopi_token(token)
|
||||
Rails.logger.warn "Searching by token #{token}"
|
||||
user = User.where("wopi_token = ?", token).first
|
||||
return user
|
||||
Rails.logger.warn "WOPI: searching by token #{token}"
|
||||
User.where('wopi_token = ?', token).first
|
||||
end
|
||||
|
||||
def token_valid
|
||||
if !self.wopi_token.nil? and (self.wopi_token_ttl==0 or self.wopi_token_ttl > Time.now.to_i)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
!wopi_token.nil? && (wopi_token_ttl.zero? || wopi_token_ttl > Time.now.to_i)
|
||||
end
|
||||
|
||||
def get_wopi_token
|
||||
unless token_valid
|
||||
# if current token is not valid generate a new one with a one day TTL
|
||||
# If current token is not valid generate a new one with a one day TTL
|
||||
self.wopi_token = Devise.friendly_token(20)
|
||||
# WOPI uses millisecond TTLs
|
||||
self.wopi_token_ttl = Time.now.to_i + 60*60*24
|
||||
self.save
|
||||
Rails.logger.warn("Generating new token #{self.wopi_token}")
|
||||
self.wopi_token_ttl = Time.now.to_i + 1.days
|
||||
save
|
||||
Rails.logger.warn("Generating new token #{wopi_token}")
|
||||
end
|
||||
Rails.logger.warn("Returning token #{self.wopi_token}")
|
||||
self.wopi_token
|
||||
Rails.logger.warn("Returning token #{wopi_token}")
|
||||
wopi_token
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
class WopiAction < ActiveRecord::Base
|
||||
|
||||
belongs_to :wopi_app, :foreign_key => 'wopi_app_id', :class_name => 'WopiApp'
|
||||
belongs_to :wopi_app, foreign_key: 'wopi_app_id', class_name: 'WopiApp'
|
||||
validates :action, :extension, :urlsrc, :wopi_app, presence: true
|
||||
|
||||
|
||||
def self.find_action(extension, activity)
|
||||
WopiAction.distinct
|
||||
.where("extension = ? and action = ?",extension,activity).first
|
||||
.where('extension = ? and action = ?', extension, activity).first
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
class WopiApp < ActiveRecord::Base
|
||||
|
||||
belongs_to :wopi_discovery, :foreign_key => 'wopi_discovery_id', :class_name => 'WopiDiscovery'
|
||||
has_many :wopi_actions, class_name: 'WopiAction', foreign_key: 'wopi_app_id', :dependent => :destroy
|
||||
|
||||
belongs_to :wopi_discovery,
|
||||
foreign_key: 'wopi_discovery_id',
|
||||
class_name: 'WopiDiscovery'
|
||||
has_many :wopi_actions,
|
||||
class_name: 'WopiAction',
|
||||
foreign_key: 'wopi_app_id',
|
||||
dependent: :destroy
|
||||
validates :name, :icon, :wopi_discovery, presence: true
|
||||
|
||||
end
|
||||
|
|
@ -1,8 +1,16 @@
|
|||
class WopiDiscovery < ActiveRecord::Base
|
||||
require 'base64'
|
||||
|
||||
has_many :wopi_apps, class_name: 'WopiApp', foreign_key: 'wopi_discovery_id', :dependent => :destroy
|
||||
validates :expires, :proof_key_mod, :proof_key_exp, :proof_key_old_mod, :proof_key_old_exp, presence: true
|
||||
has_many :wopi_apps,
|
||||
class_name: 'WopiApp',
|
||||
foreign_key: 'wopi_discovery_id',
|
||||
dependent: :destroy
|
||||
validates :expires,
|
||||
:proof_key_mod,
|
||||
:proof_key_exp,
|
||||
:proof_key_old_mod,
|
||||
:proof_key_old_exp,
|
||||
presence: true
|
||||
|
||||
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
|
||||
# with this discovery public key (two key possible old/new)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module WopiUtil
|
|||
UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000
|
||||
CLR_TICKS_PER_SECOND = 10000000
|
||||
|
||||
DISCOVERY_TTL = 60*60*24
|
||||
DISCOVERY_TTL = 1.days
|
||||
DISCOVERY_TTL.freeze
|
||||
|
||||
# For more explanation see this:
|
||||
|
|
@ -16,11 +16,11 @@ module WopiUtil
|
|||
end
|
||||
|
||||
def get_action(extension, activity)
|
||||
get_discovery
|
||||
action = WopiAction.find_action(extension, activity)
|
||||
current_wopi_discovery
|
||||
WopiAction.find_action(extension, activity)
|
||||
end
|
||||
|
||||
def get_discovery
|
||||
def current_wopi_discovery
|
||||
discovery = WopiDiscovery.first
|
||||
return discovery if discovery && discovery.expires >= Time.now.to_i
|
||||
initialize_discovery(discovery)
|
||||
|
|
@ -30,49 +30,44 @@ module WopiUtil
|
|||
|
||||
# Currently only saves Excel, Word and PowerPoint view and edit actions
|
||||
def initialize_discovery(discovery)
|
||||
begin
|
||||
Rails.logger.warn "Initializing discovery"
|
||||
Rails.logger.warn 'Initializing discovery'
|
||||
discovery.destroy if discovery
|
||||
|
||||
@doc = Nokogiri::XML(open(ENV["WOPI_DISCOVERY_URL"]))
|
||||
@doc = Nokogiri::XML(open(ENV['WOPI_DISCOVERY_URL']))
|
||||
|
||||
discovery = WopiDiscovery.new
|
||||
discovery.expires = Time.now.to_i + DISCOVERY_TTL
|
||||
key = @doc.xpath("//proof-key")
|
||||
discovery.proof_key_mod = key.xpath("@modulus").first.value
|
||||
discovery.proof_key_exp = key.xpath("@exponent").first.value
|
||||
discovery.proof_key_old_mod = key.xpath("@oldmodulus").first.value
|
||||
discovery.proof_key_old_exp = key.xpath("@oldexponent").first.value
|
||||
key = @doc.xpath('//proof-key')
|
||||
discovery.proof_key_mod = key.xpath('@modulus').first.value
|
||||
discovery.proof_key_exp = key.xpath('@exponent').first.value
|
||||
discovery.proof_key_old_mod = key.xpath('@oldmodulus').first.value
|
||||
discovery.proof_key_old_exp = key.xpath('@oldexponent').first.value
|
||||
discovery.save!
|
||||
|
||||
@doc.xpath("//app").each do |app|
|
||||
app_name = app.xpath("@name").first.value
|
||||
if ["Excel","Word","PowerPoint","WopiTest"].include?(app_name)
|
||||
@doc.xpath('//app').each do |app|
|
||||
app_name = app.xpath('@name').first.value
|
||||
next unless %w(Excel Word PowerPoint WopiTest).include?(app_name)
|
||||
|
||||
wopi_app = WopiApp.new
|
||||
wopi_app.name = app.xpath("@name").first.value
|
||||
wopi_app.icon = app.xpath("@favIconUrl").first.value
|
||||
wopi_app.name = app.xpath('@name').first.value
|
||||
wopi_app.icon = app.xpath('@favIconUrl').first.value
|
||||
wopi_app.wopi_discovery_id = discovery.id
|
||||
wopi_app.save!
|
||||
app.xpath("action").each do |action|
|
||||
name = action.xpath("@name").first.value
|
||||
if ["view","edit","wopitest"].include?(name)
|
||||
app.xpath('action').each do |action|
|
||||
name = action.xpath('@name').first.value
|
||||
next unless %w(view edit wopitest).include?(name)
|
||||
wopi_action = WopiAction.new
|
||||
wopi_action.action = name
|
||||
wopi_action.extension = action.xpath("@ext").first.value
|
||||
wopi_action.urlsrc = action.xpath("@urlsrc").first.value
|
||||
wopi_action.extension = action.xpath('@ext').first.value
|
||||
wopi_action.urlsrc = action.xpath('@urlsrc').first.value
|
||||
wopi_action.wopi_app_id = wopi_app.id
|
||||
wopi_action.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
discovery
|
||||
rescue
|
||||
Rails.logger.warn "Initialization failed"
|
||||
Rails.logger.warn 'Initialization failed'
|
||||
discovery = WopiDiscovery.first
|
||||
discovery.destroy if discovery
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,13 +30,10 @@ module Scinote
|
|||
"[#{datetime}] #{severity}: #{msg}\n"
|
||||
end
|
||||
|
||||
#config.action_dispatch.default_headers = {
|
||||
#'X-WOPI-Lock' => "",
|
||||
#'Random-header' => "with value",
|
||||
#'Random-non-special-header' => "a"
|
||||
#}
|
||||
|
||||
# Paperclip spoof checking
|
||||
Paperclip.options[:content_type_mappings] = {:csv => "text/plain", wopitest: ['text/plain', 'inode/x-empty'] }
|
||||
Paperclip.options[:content_type_mappings] = {
|
||||
csv: 'text/plain',
|
||||
wopitest: ['text/plain', 'inode/x-empty']
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
class AddWopi < ActiveRecord::Migration
|
||||
|
||||
def up
|
||||
add_column :users, :wopi_token, :string
|
||||
add_column :users, :wopi_token_ttl, :integer
|
||||
|
||||
add_column :assets, :lock, :string, :limit => 1024
|
||||
add_column :assets, :lock, :string, limit: 1024
|
||||
add_column :assets, :lock_ttl, :integer
|
||||
add_column :assets, :version, :integer, default: 1
|
||||
|
||||
|
|
@ -33,7 +32,6 @@ class AddWopi< ActiveRecord::Migration
|
|||
add_foreign_key :wopi_apps, :wopi_discoveries, column: :wopi_discovery_id
|
||||
|
||||
add_index :wopi_actions, [:extension, :action]
|
||||
|
||||
end
|
||||
|
||||
def down
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue