mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-11-10 16:31: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,31 +66,27 @@ class AssetsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
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
|
@token = current_user.get_wopi_token
|
||||||
@ttl = (current_user.wopi_token_ttl*1000).to_s
|
@ttl = (current_user.wopi_token_ttl * 1000).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def view
|
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
|
@token = current_user.get_wopi_token
|
||||||
@ttl = (current_user.wopi_token_ttl*1000).to_s
|
@ttl = (current_user.wopi_token_ttl * 1000).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_vars
|
def load_vars
|
||||||
@asset = Asset.find_by_id(params[:id])
|
@asset = Asset.find_by_id(params[:id])
|
||||||
|
render_404 unless @asset
|
||||||
unless @asset
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
step_assoc = @asset.step
|
step_assoc = @asset.step
|
||||||
result_assoc = @asset.result
|
result_assoc = @asset.result
|
||||||
|
@assoc = step_assoc unless step_assoc.nil?
|
||||||
@assoc = step_assoc if not step_assoc.nil?
|
@assoc = result_assoc unless result_assoc.nil?
|
||||||
@assoc = result_assoc if not result_assoc.nil?
|
|
||||||
|
|
||||||
if @assoc.class == Step
|
if @assoc.class == Step
|
||||||
@protocol = @asset.step.protocol
|
@protocol = @asset.step.protocol
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ class WopiController < ActionController::Base
|
||||||
logger.warn 'WOPI: lock; ' + lock.to_s
|
logger.warn 'WOPI: lock; ' + lock.to_s
|
||||||
render nothing: :true, status: 404 and return if lock.nil? || lock.blank?
|
render nothing: :true, status: 404 and return if lock.nil? || lock.blank?
|
||||||
@asset.with_lock do
|
@asset.with_lock do
|
||||||
if @asset.is_locked
|
if @asset.locked?
|
||||||
if @asset.lock == lock
|
if @asset.lock == lock
|
||||||
@asset.refresh_lock
|
@asset.refresh_lock
|
||||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||||
|
|
@ -117,7 +117,7 @@ class WopiController < ActionController::Base
|
||||||
render nothing: :true, status: 400 and return
|
render nothing: :true, status: 400 and return
|
||||||
end
|
end
|
||||||
@asset.with_lock do
|
@asset.with_lock do
|
||||||
if @asset.is_locked
|
if @asset.locked?
|
||||||
if @asset.lock == old_lock
|
if @asset.lock == old_lock
|
||||||
@asset.unlock
|
@asset.unlock
|
||||||
@asset.lock_asset(lock)
|
@asset.lock_asset(lock)
|
||||||
|
|
@ -138,9 +138,9 @@ class WopiController < ActionController::Base
|
||||||
lock = request.headers['X-WOPI-Lock']
|
lock = request.headers['X-WOPI-Lock']
|
||||||
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
||||||
@asset.with_lock do
|
@asset.with_lock do
|
||||||
if @asset.is_locked
|
if @asset.locked?
|
||||||
logger.warn 'WOPI: current asset lock: #{@asset.lock},
|
logger.warn "WOPI: current asset lock: #{@asset.lock},
|
||||||
unlocking lock #{lock}'
|
unlocking lock #{lock}"
|
||||||
if @asset.lock == lock
|
if @asset.lock == lock
|
||||||
@asset.unlock
|
@asset.unlock
|
||||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||||
|
|
@ -161,7 +161,7 @@ class WopiController < ActionController::Base
|
||||||
lock = request.headers['X-WOPI-Lock']
|
lock = request.headers['X-WOPI-Lock']
|
||||||
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
||||||
@asset.with_lock do
|
@asset.with_lock do
|
||||||
if @asset.is_locked
|
if @asset.locked?
|
||||||
if @asset.lock == lock
|
if @asset.lock == lock
|
||||||
@asset.refresh_lock
|
@asset.refresh_lock
|
||||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||||
|
|
@ -180,7 +180,7 @@ class WopiController < ActionController::Base
|
||||||
|
|
||||||
def get_lock
|
def get_lock
|
||||||
@asset.with_lock do
|
@asset.with_lock do
|
||||||
if @asset.is_locked
|
if @asset.locked?
|
||||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||||
else
|
else
|
||||||
response.headers['X-WOPI-Lock'] = ''
|
response.headers['X-WOPI-Lock'] = ''
|
||||||
|
|
@ -193,7 +193,7 @@ class WopiController < ActionController::Base
|
||||||
def put_file
|
def put_file
|
||||||
@asset.with_lock do
|
@asset.with_lock do
|
||||||
lock = request.headers['X-WOPI-Lock']
|
lock = request.headers['X-WOPI-Lock']
|
||||||
if @asset.is_locked
|
if @asset.locked?
|
||||||
if @asset.lock == lock
|
if @asset.lock == lock
|
||||||
logger.warn 'WOPI: replacing file'
|
logger.warn 'WOPI: replacing file'
|
||||||
@asset.update_contents(request.body)
|
@asset.update_contents(request.body)
|
||||||
|
|
@ -263,8 +263,8 @@ class WopiController < ActionController::Base
|
||||||
url = request.original_url.upcase.encode('utf-8')
|
url = request.original_url.upcase.encode('utf-8')
|
||||||
|
|
||||||
if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now
|
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)
|
signed_proof_old, url)
|
||||||
logger.warn 'WOPI: proof verification: successful'
|
logger.warn 'WOPI: proof verification: successful'
|
||||||
else
|
else
|
||||||
logger.warn 'WOPI: proof verification: not verified'
|
logger.warn 'WOPI: proof verification: not verified'
|
||||||
|
|
|
||||||
|
|
@ -288,9 +288,12 @@ class Asset < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Preserving attachments (on client-side) between failed validations (only usable for small/few files!)
|
# Preserving attachments (on client-side) between failed validations
|
||||||
# Needs to be called before save method and view needs to have :file_content and :file_info hidden field
|
# (only usable for small/few files!).
|
||||||
# If file is an image, it can be viewed on front-end using @preview_cached with image_tag tag
|
# 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)
|
def preserve(file_data)
|
||||||
if file_data[:file_content].present?
|
if file_data[:file_content].present?
|
||||||
restore_cached(file_data[:file_content], file_data[:file_info])
|
restore_cached(file_data[:file_content], file_data[:file_info])
|
||||||
|
|
@ -299,28 +302,31 @@ class Asset < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_perform_action(action)
|
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)
|
action = get_action(file_ext, action)
|
||||||
if action.nil?
|
return false if action.nil?
|
||||||
return false
|
|
||||||
end
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_action_url(user, action, with_tokens = true)
|
||||||
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)
|
||||||
action = get_action(file_ext,action)
|
|
||||||
if !action.nil?
|
if !action.nil?
|
||||||
action_url = action.urlsrc
|
action_url = action.urlsrc
|
||||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/, "IsLicensedUser=0&")
|
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/,
|
||||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER>/, "IsLicensedUser=0")
|
'IsLicensedUser=0&')
|
||||||
action_url = action_url.gsub(/<.*?=.*?>/, "")
|
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)
|
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(
|
||||||
action_url = action_url + "WOPISrc=#{rest_url}"
|
host: ENV['WOPI_ENDPOINT_URL'],
|
||||||
|
id: id
|
||||||
|
)
|
||||||
|
action_url += "WOPISrc=#{rest_url}"
|
||||||
if with_tokens
|
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
|
else
|
||||||
action_url
|
action_url
|
||||||
end
|
end
|
||||||
|
|
@ -329,57 +335,49 @@ class Asset < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#is_locked, lock_asset and refresh_lock rely on the asset being locked in the database to prevent race conditions
|
# locked?, lock_asset and refresh_lock rely on the asset
|
||||||
def is_locked
|
# being locked in the database to prevent race conditions
|
||||||
if lock.nil?
|
def locked?
|
||||||
return false
|
!lock.nil?
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def lock_asset(lock_string)
|
def lock_asset(lock_string)
|
||||||
self.lock = lock_string
|
self.lock = lock_string
|
||||||
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
||||||
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
||||||
self.save!
|
save!
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_lock
|
def refresh_lock
|
||||||
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
||||||
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
||||||
self.save!
|
save!
|
||||||
end
|
end
|
||||||
|
|
||||||
def unlock
|
def unlock
|
||||||
self.lock = nil
|
self.lock = nil
|
||||||
self.lock_ttl = nil
|
self.lock_ttl = nil
|
||||||
self.save!
|
save!
|
||||||
end
|
end
|
||||||
|
|
||||||
def unlock_expired
|
def unlock_expired
|
||||||
self.with_lock do
|
with_lock do
|
||||||
if !self.lock_ttl.nil? and self.lock_ttl>= Time.now.to_i
|
if !lock_ttl.nil? && lock_ttl >= Time.now.to_i
|
||||||
self.lock = nil
|
self.lock = nil
|
||||||
self.lock_ttl = nil
|
self.lock_ttl = nil
|
||||||
self.save!
|
save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_contents(new_file)
|
def update_contents(new_file)
|
||||||
new_file.class.class_eval { attr_accessor :original_filename }
|
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
|
self.file = new_file
|
||||||
if self.version.nil?
|
self.version = version.nil? ? 1 : version + 1
|
||||||
self.version = 1
|
save
|
||||||
else
|
|
||||||
self.version = self.version + 1
|
|
||||||
end
|
|
||||||
self.save
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# Checks if attachments is an image (in post processing imagemagick will
|
# Checks if attachments is an image (in post processing imagemagick will
|
||||||
|
|
|
||||||
|
|
@ -253,33 +253,28 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_by_valid_wopi_token(token)
|
def self.find_by_valid_wopi_token(token)
|
||||||
Rails.logger.warn "Searching by token #{token}"
|
Rails.logger.warn "WOPI: searching by token #{token}"
|
||||||
user = User.where("wopi_token = ?", token).first
|
User.where('wopi_token = ?', token).first
|
||||||
return user
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def token_valid
|
def token_valid
|
||||||
if !self.wopi_token.nil? and (self.wopi_token_ttl==0 or self.wopi_token_ttl > Time.now.to_i)
|
!wopi_token.nil? && (wopi_token_ttl.zero? || wopi_token_ttl > Time.now.to_i)
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_wopi_token
|
def get_wopi_token
|
||||||
unless token_valid
|
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)
|
self.wopi_token = Devise.friendly_token(20)
|
||||||
# WOPI uses millisecond TTLs
|
# WOPI uses millisecond TTLs
|
||||||
self.wopi_token_ttl = Time.now.to_i + 60*60*24
|
self.wopi_token_ttl = Time.now.to_i + 1.days
|
||||||
self.save
|
save
|
||||||
Rails.logger.warn("Generating new token #{self.wopi_token}")
|
Rails.logger.warn("Generating new token #{wopi_token}")
|
||||||
end
|
end
|
||||||
Rails.logger.warn("Returning token #{self.wopi_token}")
|
Rails.logger.warn("Returning token #{wopi_token}")
|
||||||
self.wopi_token
|
wopi_token
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def time_zone_check
|
def time_zone_check
|
||||||
if time_zone.nil? or ActiveSupport::TimeZone.new(time_zone).nil?
|
if time_zone.nil? or ActiveSupport::TimeZone.new(time_zone).nil?
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
class WopiAction < ActiveRecord::Base
|
class WopiAction < ActiveRecord::Base
|
||||||
|
belongs_to :wopi_app, foreign_key: 'wopi_app_id', class_name: 'WopiApp'
|
||||||
|
validates :action, :extension, :urlsrc, :wopi_app, presence: true
|
||||||
|
|
||||||
belongs_to :wopi_app, :foreign_key => 'wopi_app_id', :class_name => 'WopiApp'
|
def self.find_action(extension, activity)
|
||||||
validates :action,:extension,:urlsrc,:wopi_app, presence: true
|
WopiAction.distinct
|
||||||
|
.where('extension = ? and action = ?', extension, activity).first
|
||||||
|
end
|
||||||
def self.find_action(extension,activity)
|
end
|
||||||
WopiAction.distinct
|
|
||||||
.where("extension = ? and action = ?",extension,activity).first
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
class WopiApp < ActiveRecord::Base
|
class WopiApp < ActiveRecord::Base
|
||||||
|
belongs_to :wopi_discovery,
|
||||||
belongs_to :wopi_discovery, :foreign_key => 'wopi_discovery_id', :class_name => 'WopiDiscovery'
|
foreign_key: 'wopi_discovery_id',
|
||||||
has_many :wopi_actions, class_name: 'WopiAction', foreign_key: 'wopi_app_id', :dependent => :destroy
|
class_name: 'WopiDiscovery'
|
||||||
|
has_many :wopi_actions,
|
||||||
validates :name, :icon, :wopi_discovery, presence: true
|
class_name: 'WopiAction',
|
||||||
|
foreign_key: 'wopi_app_id',
|
||||||
end
|
dependent: :destroy
|
||||||
|
validates :name, :icon, :wopi_discovery, presence: true
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,53 @@
|
||||||
class WopiDiscovery < ActiveRecord::Base
|
class WopiDiscovery < ActiveRecord::Base
|
||||||
require 'base64'
|
require 'base64'
|
||||||
|
|
||||||
has_many :wopi_apps, class_name: 'WopiApp', foreign_key: 'wopi_discovery_id', :dependent => :destroy
|
has_many :wopi_apps,
|
||||||
validates :expires, :proof_key_mod, :proof_key_exp, :proof_key_old_mod, :proof_key_old_exp, presence: true
|
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
|
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
|
||||||
# with this discovery public key (two key possible old/new)
|
# with this discovery public key (two key possible old/new)
|
||||||
def verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
|
def verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
|
||||||
token_length = [token.length].pack('>N').bytes
|
token_length = [token.length].pack('>N').bytes
|
||||||
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
|
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
|
||||||
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
|
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
|
||||||
url_length = [url.length].pack('>N').bytes
|
url_length = [url.length].pack('>N').bytes
|
||||||
|
|
||||||
expected_proof = token_length + token.bytes +
|
expected_proof = token_length + token.bytes +
|
||||||
url_length + url.upcase.bytes +
|
url_length + url.upcase.bytes +
|
||||||
timestamp_length + timestamp_bytes
|
timestamp_length + timestamp_bytes
|
||||||
|
|
||||||
key = generate_key(proof_key_mod, proof_key_exp)
|
key = generate_key(proof_key_mod, proof_key_exp)
|
||||||
old_key = generate_key(proof_key_old_mod, proof_key_old_exp)
|
old_key = generate_key(proof_key_old_mod, proof_key_old_exp)
|
||||||
|
|
||||||
# Try all possible combiniations
|
# Try all possible combiniations
|
||||||
try_verification(expected_proof, signed_proof, key) ||
|
try_verification(expected_proof, signed_proof, key) ||
|
||||||
try_verification(expected_proof, signed_proof_old, key) ||
|
try_verification(expected_proof, signed_proof_old, key) ||
|
||||||
try_verification(expected_proof, signed_proof, old_key)
|
try_verification(expected_proof, signed_proof, old_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generates a public key from given modulus and exponent
|
# Generates a public key from given modulus and exponent
|
||||||
def generate_key(modulus, exponent)
|
def generate_key(modulus, exponent)
|
||||||
mod = Base64.decode64(modulus).unpack('H*').first.to_i(16)
|
mod = Base64.decode64(modulus).unpack('H*').first.to_i(16)
|
||||||
exp = Base64.decode64(exponent).unpack('H*').first.to_i(16)
|
exp = Base64.decode64(exponent).unpack('H*').first.to_i(16)
|
||||||
|
|
||||||
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
|
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
|
||||||
OpenSSL::ASN1::Integer.new(exp)])
|
OpenSSL::ASN1::Integer.new(exp)])
|
||||||
OpenSSL::PKey::RSA.new(seq.to_der)
|
OpenSSL::PKey::RSA.new(seq.to_der)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verify if decrypting signed_proof with public_key equals to expected_proof
|
# Verify if decrypting signed_proof with public_key equals to expected_proof
|
||||||
def try_verification(expected_proof, signed_proof_b64, public_key)
|
def try_verification(expected_proof, signed_proof_b64, public_key)
|
||||||
signed_proof = Base64.decode64(signed_proof_b64)
|
signed_proof = Base64.decode64(signed_proof_b64)
|
||||||
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
|
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
|
||||||
expected_proof.pack('c*'))
|
expected_proof.pack('c*'))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,22 @@ module WopiUtil
|
||||||
UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000
|
UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000
|
||||||
CLR_TICKS_PER_SECOND = 10000000
|
CLR_TICKS_PER_SECOND = 10000000
|
||||||
|
|
||||||
DISCOVERY_TTL = 60*60*24
|
DISCOVERY_TTL = 1.days
|
||||||
DISCOVERY_TTL.freeze
|
DISCOVERY_TTL.freeze
|
||||||
|
|
||||||
# For more explanation see this:
|
# For more explanation see this:
|
||||||
# http://stackoverflow.com/questions/11888053/
|
# http://stackoverflow.com/questions/11888053/
|
||||||
# convert-net-datetime-ticks-property-to-date-in-objective-c
|
# convert-net-datetime-ticks-property-to-date-in-objective-c
|
||||||
def convert_to_unix_timestamp(timestamp)
|
def convert_to_unix_timestamp(timestamp)
|
||||||
Time.at((timestamp-UNIX_EPOCH_IN_CLR_TICKS)/CLR_TICKS_PER_SECOND)
|
Time.at((timestamp - UNIX_EPOCH_IN_CLR_TICKS) / CLR_TICKS_PER_SECOND)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_action(extension, activity)
|
def get_action(extension, activity)
|
||||||
get_discovery
|
current_wopi_discovery
|
||||||
action = WopiAction.find_action(extension, activity)
|
WopiAction.find_action(extension, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_discovery
|
def current_wopi_discovery
|
||||||
discovery = WopiDiscovery.first
|
discovery = WopiDiscovery.first
|
||||||
return discovery if discovery && discovery.expires >= Time.now.to_i
|
return discovery if discovery && discovery.expires >= Time.now.to_i
|
||||||
initialize_discovery(discovery)
|
initialize_discovery(discovery)
|
||||||
|
|
@ -30,49 +30,44 @@ module WopiUtil
|
||||||
|
|
||||||
# Currently only saves Excel, Word and PowerPoint view and edit actions
|
# Currently only saves Excel, Word and PowerPoint view and edit actions
|
||||||
def initialize_discovery(discovery)
|
def initialize_discovery(discovery)
|
||||||
begin
|
Rails.logger.warn 'Initializing discovery'
|
||||||
Rails.logger.warn "Initializing discovery"
|
discovery.destroy if 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 = WopiDiscovery.new
|
||||||
discovery.expires = Time.now.to_i + DISCOVERY_TTL
|
discovery.expires = Time.now.to_i + DISCOVERY_TTL
|
||||||
key = @doc.xpath("//proof-key")
|
key = @doc.xpath('//proof-key')
|
||||||
discovery.proof_key_mod = key.xpath("@modulus").first.value
|
discovery.proof_key_mod = key.xpath('@modulus').first.value
|
||||||
discovery.proof_key_exp = key.xpath("@exponent").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_mod = key.xpath('@oldmodulus').first.value
|
||||||
discovery.proof_key_old_exp = key.xpath("@oldexponent").first.value
|
discovery.proof_key_old_exp = key.xpath('@oldexponent').first.value
|
||||||
discovery.save!
|
discovery.save!
|
||||||
|
|
||||||
@doc.xpath("//app").each do |app|
|
@doc.xpath('//app').each do |app|
|
||||||
app_name = app.xpath("@name").first.value
|
app_name = app.xpath('@name').first.value
|
||||||
if ["Excel","Word","PowerPoint","WopiTest"].include?(app_name)
|
next unless %w(Excel Word PowerPoint WopiTest).include?(app_name)
|
||||||
wopi_app = WopiApp.new
|
|
||||||
wopi_app.name = app.xpath("@name").first.value
|
wopi_app = WopiApp.new
|
||||||
wopi_app.icon = app.xpath("@favIconUrl").first.value
|
wopi_app.name = app.xpath('@name').first.value
|
||||||
wopi_app.wopi_discovery_id=discovery.id
|
wopi_app.icon = app.xpath('@favIconUrl').first.value
|
||||||
wopi_app.save!
|
wopi_app.wopi_discovery_id = discovery.id
|
||||||
app.xpath("action").each do |action|
|
wopi_app.save!
|
||||||
name = action.xpath("@name").first.value
|
app.xpath('action').each do |action|
|
||||||
if ["view","edit","wopitest"].include?(name)
|
name = action.xpath('@name').first.value
|
||||||
wopi_action = WopiAction.new
|
next unless %w(view edit wopitest).include?(name)
|
||||||
wopi_action.action = name
|
wopi_action = WopiAction.new
|
||||||
wopi_action.extension = action.xpath("@ext").first.value
|
wopi_action.action = name
|
||||||
wopi_action.urlsrc = action.xpath("@urlsrc").first.value
|
wopi_action.extension = action.xpath('@ext').first.value
|
||||||
wopi_action.wopi_app_id = wopi_app.id
|
wopi_action.urlsrc = action.xpath('@urlsrc').first.value
|
||||||
wopi_action.save!
|
wopi_action.wopi_app_id = wopi_app.id
|
||||||
end
|
wopi_action.save!
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
discovery
|
|
||||||
rescue
|
|
||||||
Rails.logger.warn "Initialization failed"
|
|
||||||
discovery = WopiDiscovery.first
|
|
||||||
discovery.destroy if discovery
|
|
||||||
end
|
end
|
||||||
|
discovery
|
||||||
|
rescue
|
||||||
|
Rails.logger.warn 'Initialization failed'
|
||||||
|
discovery = WopiDiscovery.first
|
||||||
|
discovery.destroy if discovery
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
||||||
<!-- Enable IE Standards mode -->
|
<!-- Enable IE Standards mode -->
|
||||||
|
|
@ -9,56 +9,56 @@
|
||||||
<title></title>
|
<title></title>
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||||
|
|
||||||
<link rel="shortcut icon"
|
<link rel="shortcut icon"
|
||||||
href="" />
|
href="" />
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
-ms-content-zooming: none;
|
-ms-content-zooming: none;
|
||||||
}
|
}
|
||||||
#office_frame {
|
#office_frame {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<form id="office_form" name="office_form" target="office_frame"
|
<form id="office_form" name="office_form" target="office_frame"
|
||||||
action=<%=@action_url %>
|
action=<%=@action_url %>
|
||||||
method="post">
|
method="post">
|
||||||
<input name="access_token" value=<%= @token%> type="hidden"/>
|
<input name="access_token" value=<%= @token%> type="hidden"/>
|
||||||
<input name="access_token_ttl" value= <%= @ttl %> type="hidden"/>
|
<input name="access_token_ttl" value= <%= @ttl %> type="hidden"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<span id="frameholder"></span>
|
<span id="frameholder"></span>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var frameholder = document.getElementById('frameholder');
|
var frameholder = document.getElementById('frameholder');
|
||||||
var office_frame = document.createElement('iframe');
|
var office_frame = document.createElement('iframe');
|
||||||
office_frame.name = 'office_frame';
|
office_frame.name = 'office_frame';
|
||||||
office_frame.id ='office_frame';
|
office_frame.id ='office_frame';
|
||||||
// The title should be set for accessibility
|
// The title should be set for accessibility
|
||||||
office_frame.title = 'Office Online Frame';
|
office_frame.title = 'Office Online Frame';
|
||||||
// This attribute allows true fullscreen mode in slideshow view
|
// This attribute allows true fullscreen mode in slideshow view
|
||||||
// when using PowerPoint Online's 'view' action.
|
// when using PowerPoint Online's 'view' action.
|
||||||
office_frame.setAttribute('allowfullscreen', 'true');
|
office_frame.setAttribute('allowfullscreen', 'true');
|
||||||
frameholder.appendChild(office_frame);
|
frameholder.appendChild(office_frame);
|
||||||
document.getElementById('office_form').submit();
|
document.getElementById('office_form').submit();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
||||||
<!-- Enable IE Standards mode -->
|
<!-- Enable IE Standards mode -->
|
||||||
|
|
@ -9,56 +9,56 @@
|
||||||
<title></title>
|
<title></title>
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||||
|
|
||||||
<link rel="shortcut icon"
|
<link rel="shortcut icon"
|
||||||
href="" />
|
href="" />
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
-ms-content-zooming: none;
|
-ms-content-zooming: none;
|
||||||
}
|
}
|
||||||
#office_frame {
|
#office_frame {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<form id="office_form" name="office_form" target="office_frame"
|
<form id="office_form" name="office_form" target="office_frame"
|
||||||
action=<%=@action_url %>
|
action=<%=@action_url %>
|
||||||
method="post">
|
method="post">
|
||||||
<input name="access_token" value=<%= @token%> type="hidden"/>
|
<input name="access_token" value=<%= @token%> type="hidden"/>
|
||||||
<input name="access_token_ttl" value= <%= @ttl %> type="hidden"/>
|
<input name="access_token_ttl" value= <%= @ttl %> type="hidden"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<span id="frameholder"></span>
|
<span id="frameholder"></span>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var frameholder = document.getElementById('frameholder');
|
var frameholder = document.getElementById('frameholder');
|
||||||
var office_frame = document.createElement('iframe');
|
var office_frame = document.createElement('iframe');
|
||||||
office_frame.name = 'office_frame';
|
office_frame.name = 'office_frame';
|
||||||
office_frame.id ='office_frame';
|
office_frame.id ='office_frame';
|
||||||
// The title should be set for accessibility
|
// The title should be set for accessibility
|
||||||
office_frame.title = 'Office Online Frame';
|
office_frame.title = 'Office Online Frame';
|
||||||
// This attribute allows true fullscreen mode in slideshow view
|
// This attribute allows true fullscreen mode in slideshow view
|
||||||
// when using PowerPoint Online's 'view' action.
|
// when using PowerPoint Online's 'view' action.
|
||||||
office_frame.setAttribute('allowfullscreen', 'true');
|
office_frame.setAttribute('allowfullscreen', 'true');
|
||||||
frameholder.appendChild(office_frame);
|
frameholder.appendChild(office_frame);
|
||||||
document.getElementById('office_form').submit();
|
document.getElementById('office_form').submit();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,10 @@ module Scinote
|
||||||
"[#{datetime}] #{severity}: #{msg}\n"
|
"[#{datetime}] #{severity}: #{msg}\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
#config.action_dispatch.default_headers = {
|
|
||||||
#'X-WOPI-Lock' => "",
|
|
||||||
#'Random-header' => "with value",
|
|
||||||
#'Random-non-special-header' => "a"
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Paperclip spoof checking
|
# 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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
class AddWopi< ActiveRecord::Migration
|
class AddWopi < ActiveRecord::Migration
|
||||||
|
|
||||||
def up
|
def up
|
||||||
add_column :users, :wopi_token, :string
|
add_column :users, :wopi_token, :string
|
||||||
add_column :users, :wopi_token_ttl, :integer
|
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, :lock_ttl, :integer
|
||||||
add_column :assets, :version, :integer, default: 1
|
add_column :assets, :version, :integer, default: 1
|
||||||
|
|
||||||
|
|
@ -32,8 +31,7 @@ class AddWopi< ActiveRecord::Migration
|
||||||
add_foreign_key :wopi_actions, :wopi_apps, column: :wopi_app_id
|
add_foreign_key :wopi_actions, :wopi_apps, column: :wopi_app_id
|
||||||
add_foreign_key :wopi_apps, :wopi_discoveries, column: :wopi_discovery_id
|
add_foreign_key :wopi_apps, :wopi_discoveries, column: :wopi_discovery_id
|
||||||
|
|
||||||
add_index :wopi_actions, [:extension,:action]
|
add_index :wopi_actions, [:extension, :action]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue