mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-26 17:51:47 +08:00
Initial commit of WOPI integration
This commit is contained in:
parent
908f85f6c0
commit
b2b1d5a8f5
12 changed files with 370 additions and 5 deletions
2
Gemfile
2
Gemfile
|
@ -57,6 +57,8 @@ gem 'delayed_job_active_record'
|
|||
gem 'devise-async'
|
||||
gem 'ruby-graphviz', '~> 1.2' # Graphviz for rails
|
||||
|
||||
gem 'nokogiri' # XML parser
|
||||
|
||||
group :development, :test do
|
||||
gem 'byebug'
|
||||
gem 'web-console', '~> 2.0'
|
||||
|
|
|
@ -321,6 +321,7 @@ DEPENDENCIES
|
|||
minitest-reporters (~> 1.1)
|
||||
momentjs-rails (>= 2.9.0)
|
||||
nested_form_fields
|
||||
nokogiri
|
||||
paperclip (~> 4.3)
|
||||
pg
|
||||
puma
|
||||
|
|
130
app/controllers/wopi_controller.rb
Normal file
130
app/controllers/wopi_controller.rb
Normal file
|
@ -0,0 +1,130 @@
|
|||
class WopiController < ActionController::Base
|
||||
before_action :authenticate_user_from_token!, :load_vars
|
||||
#before_action :verify_proof!
|
||||
|
||||
def get_file
|
||||
Rails.logger.warn "get_file called"
|
||||
#Only used for checkfileinfo
|
||||
check_file_info
|
||||
end
|
||||
|
||||
def get_file_contents
|
||||
Rails.logger.warn "get_file_contents called"
|
||||
#Only used for getfile
|
||||
get_file
|
||||
|
||||
end
|
||||
|
||||
def post_file
|
||||
Rails.logger.warn "post_file called"
|
||||
override = request.headers["X-WOPI-Override"]
|
||||
case override
|
||||
when "PUT_RELATIVE"
|
||||
put_relative
|
||||
when "LOCK"
|
||||
lock
|
||||
when "UNLOCK"
|
||||
unlock
|
||||
when "REFRESH_LOCK"
|
||||
refresh_lock
|
||||
else
|
||||
render :nothing => true, :status => 404 and return
|
||||
end
|
||||
end
|
||||
|
||||
def post_file_contents
|
||||
Rails.logger.warn "post_file_contents called"
|
||||
#Only used for putfile
|
||||
put_file
|
||||
end
|
||||
|
||||
def check_file_info
|
||||
msg = { :BaseFileName => @asset.file_file_name,
|
||||
:OwnerId => @asset.created_by_id.to_s,
|
||||
:Size => @asset.file_file_size,
|
||||
:UserId => @user.id,
|
||||
:Version => @asset.version,
|
||||
:SupportsExtendedLockLength => true,
|
||||
:SupportsGetLock => true,
|
||||
:SupportsLocks => true,
|
||||
:SupportsUpdate => true,
|
||||
#Setting all users to business until we figure out which should NOT be business
|
||||
:LicenseCheckForEditIsEnabled => true,
|
||||
:UserFriendlyName => @user.name,
|
||||
#TODO Check user permisisons
|
||||
:ReadOnly => false,
|
||||
:UserCanWrite => true,
|
||||
#TODO decide what to put here
|
||||
:CloseUrl => "",
|
||||
:DownloadUrl => url_for(controller: 'assets',action: 'download',id: @asset.id),
|
||||
:HostEditUrl => url_for(controller: 'assets',action: 'edit',id: @asset.id),
|
||||
:HostViewUrl => url_for(controller: 'assets',action: 'view',id: @asset.id)
|
||||
#TODO breadcrumbs?
|
||||
#:FileExtension
|
||||
}
|
||||
render json:msg
|
||||
end
|
||||
|
||||
|
||||
|
||||
def load_vars
|
||||
@asset = Asset.find_by_id(params[:id])
|
||||
if @asset.nil?
|
||||
render :nothing => true, :status => 404 and return
|
||||
else
|
||||
Rails.logger.warn "Found asset with id: #{params[:id]}, (id: #{@asset.id})"
|
||||
step_assoc = @asset.step
|
||||
result_assoc = @asset.result
|
||||
@assoc = step_assoc if not step_assoc.nil?
|
||||
@assoc = result_assoc if not result_assoc.nil?
|
||||
|
||||
if @assoc.class == Step
|
||||
@protocol = @asset.step.protocol
|
||||
else
|
||||
@my_module = @assoc.my_module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def authenticate_user_from_token!
|
||||
wopi_token = params[:access_token]
|
||||
if wopi_token.nil?
|
||||
Rails.logger.warn "nil wopi token"
|
||||
render :nothing => true, :status => 401 and return
|
||||
end
|
||||
|
||||
@user = User.find_by_valid_wopi_token(wopi_token)
|
||||
if @user.nil?
|
||||
Rails.logger.warn "no user with this token found"
|
||||
render :nothing => true, :status => 401 and return
|
||||
end
|
||||
|
||||
#TODO check if the user can do anything with the file
|
||||
end
|
||||
|
||||
def verify_proof!
|
||||
begin
|
||||
token = params[:access_token]
|
||||
timestamp = request.headers["X-WOPI-TimeStamp"]
|
||||
url = request.original_url
|
||||
|
||||
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
|
||||
|
||||
proof = token_length + token.bytes + url_length + url.bytes + timestamp_length + timestamp_bytes
|
||||
|
||||
Rails.logger.warn "PROOF: #{proof}"
|
||||
|
||||
xml_doc = Nokogiri::XML("<root><aliens><alien><name>Alf</name></alien></aliens></root>")
|
||||
rescue
|
||||
Rails.logger.warn "proof verification failed"
|
||||
render :nothing => true, :status => 401 and return
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -2,6 +2,7 @@ class Asset < ActiveRecord::Base
|
|||
include SearchableModel
|
||||
include DatabaseHelper
|
||||
include Encryptor
|
||||
include WopiUtil
|
||||
|
||||
require 'tempfile'
|
||||
|
||||
|
@ -295,6 +296,23 @@ class Asset < ActiveRecord::Base
|
|||
cache
|
||||
end
|
||||
|
||||
|
||||
def get_action_path(user,action)
|
||||
file_ext = file_file_name.split(".").last
|
||||
action = get_action(file_ext,action)
|
||||
if !action.nil?
|
||||
edit_url = action.urlsrc
|
||||
edit_url = edit_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/, "IsLicensedUser=1&")
|
||||
edit_url = edit_url.gsub(/<IsLicensedUser=BUSINESS_USER>/, "IsLicensedUser=1")
|
||||
edit_url = edit_url.gsub(/<.*?=.*?>/, "")
|
||||
#This does not work yet - provides path instead of absolute url
|
||||
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(host: ENV["WOPI_ENDPOINT_URL"],id: id)
|
||||
edit_url = edit_url + "WOPISrc=#{rest_url}&access_token=#{user.get_wopi_token}&access_token_ttl=#{user.wopi_token_ttl.to_s}"
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Checks if attachments is an image (in post processing imagemagick will
|
||||
|
|
|
@ -252,12 +252,40 @@ class User < ActiveRecord::Base
|
|||
.uniq
|
||||
end
|
||||
|
||||
protected
|
||||
def self.find_by_valid_wopi_token(token)
|
||||
Rails.logger.warn "Searching by token #{token}"
|
||||
user = User.where("wopi_token = ?", token).first
|
||||
return user
|
||||
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
|
||||
end
|
||||
|
||||
def get_wopi_token
|
||||
unless token_valid
|
||||
# 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_ttl = Time.now.to_i + 60*60*24
|
||||
self.save
|
||||
Rails.logger.warn("Generating new token #{self.wopi_token}")
|
||||
end
|
||||
Rails.logger.warn("Returning token #{self.wopi_token}")
|
||||
self.wopi_token
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def time_zone_check
|
||||
if time_zone.nil? or ActiveSupport::TimeZone.new(time_zone).nil?
|
||||
errors.add(:time_zone)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
|
12
app/models/wopi_action.rb
Normal file
12
app/models/wopi_action.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class WopiAction < ActiveRecord::Base
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
end
|
8
app/models/wopi_app.rb
Normal file
8
app/models/wopi_app.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
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 => :delete_all
|
||||
|
||||
validates :name, :icon, :wopi_discovery, presence: true
|
||||
|
||||
end
|
6
app/models/wopi_discovery.rb
Normal file
6
app/models/wopi_discovery.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class WopiDiscovery < ActiveRecord::Base
|
||||
|
||||
has_many :wopi_apps, class_name: 'WopiApp', foreign_key: 'wopi_discovery_id', :dependent => :delete_all
|
||||
validates :expires, :proof_key_mod, :proof_key_exp, :proof_key_old_mod, :proof_key_old_exp, presence: true
|
||||
|
||||
end
|
67
app/utilities/wopi_util.rb
Normal file
67
app/utilities/wopi_util.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
module WopiUtil
|
||||
require 'open-uri'
|
||||
|
||||
|
||||
DISCOVERY_TTL = 60*60*24
|
||||
DISCOVERY_TTL.freeze
|
||||
|
||||
def get_action(extension, activity)
|
||||
discovery = WopiDiscovery.first
|
||||
if discovery.nil? || discovery.expires < Time.now.to_i
|
||||
initializeDiscovery(discovery)
|
||||
end
|
||||
|
||||
action = WopiAction.find_action(extension,activity)
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
# Currently only saves Excel, Word and PowerPoint view and edit actions
|
||||
def initializeDiscovery(discovery)
|
||||
begin
|
||||
Rails.logger.warn "Initializing discovery"
|
||||
unless discovery.nil?
|
||||
discovery.destroy
|
||||
end
|
||||
|
||||
@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
|
||||
discovery.save!
|
||||
|
||||
@doc.xpath("//app").each do |app|
|
||||
app_name = app.xpath("@name").first.value
|
||||
if ["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.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)
|
||||
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.wopi_app_id = wopi_app.id
|
||||
wopi_action.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue
|
||||
Rails.logger.warn "Initialization failed"
|
||||
WopiDiscovery.first.destroy
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
|
@ -32,6 +32,7 @@ Rails.application.routes.draw do
|
|||
get "users/settings/user_organizations/:user_organization_id/destroy_html", to: "users/settings#destroy_user_organization_html", as: "destroy_user_organization_html"
|
||||
delete "users/settings/user_organizations/:user_organization_id", to: "users/settings#destroy_user_organization", as: "destroy_user_organization"
|
||||
|
||||
|
||||
resources :organizations, only: [] do
|
||||
resources :samples, only: [:new, :create]
|
||||
resources :sample_types, only: [:new, :create]
|
||||
|
@ -256,10 +257,21 @@ Rails.application.routes.draw do
|
|||
get "files/:id/present", to: "assets#file_present", as: "file_present_asset"
|
||||
get "files/:id/download", to: "assets#download", as: "download_asset"
|
||||
get "files/:id/preview", to: "assets#preview", as: "preview_asset"
|
||||
get "files/:id/view", to: "assets#view", as: "view_asset"
|
||||
get "files/:id/edit", to: "assets#edit", as: "edit_asset"
|
||||
post 'asset_signature' => 'assets#signature'
|
||||
|
||||
devise_scope :user do
|
||||
get 'avatar/:id/:style' => 'users/registrations#avatar', as: 'avatar'
|
||||
post 'avatar_signature' => 'users/registrations#signature'
|
||||
end
|
||||
|
||||
# Office integration
|
||||
get "wopi/files/:id/contents", to: "wopi#get_file_contents"
|
||||
post "wopi/files/:id/contents", to: "wopi#post_file_contents"
|
||||
|
||||
get "wopi/files/:id", to: "wopi#get_file", as: 'wopi_rest_endpoint'
|
||||
post "wopi/files/:id", to: "wopi#post_file"
|
||||
|
||||
|
||||
end
|
||||
|
|
51
db/migrate/20160728145000_add_wopi.rb
Normal file
51
db/migrate/20160728145000_add_wopi.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
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_ttl, :integer
|
||||
add_column :assets, :version, :integer
|
||||
|
||||
create_table :wopi_discoveries do |t|
|
||||
t.integer :expires, null: false
|
||||
t.string :proof_key_mod, null: false
|
||||
t.string :proof_key_exp, null: false
|
||||
t.string :proof_key_old_mod, null: false
|
||||
t.string :proof_key_old_exp, null: false
|
||||
end
|
||||
|
||||
create_table :wopi_apps do |t|
|
||||
t.string :name, null: false
|
||||
t.string :icon, null: false
|
||||
t.integer :wopi_discovery_id, null: false
|
||||
end
|
||||
|
||||
create_table :wopi_actions do |t|
|
||||
t.string :action, null: false
|
||||
t.string :extension, null: false
|
||||
t.string :urlsrc, null: false
|
||||
t.integer :wopi_app_id, null: false
|
||||
end
|
||||
|
||||
add_foreign_key :wopi_actions, :wopi_apps, column: :wopi_app_id
|
||||
add_foreign_key :wopi_apps, :wopi_discoveries, column: :wopi_discovery_id
|
||||
|
||||
add_index :wopi_actions, [:extension,:action]
|
||||
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :users, :wopi_token
|
||||
remove_column :users, :wopi_token_ttl
|
||||
|
||||
remove_column :assets, :lock
|
||||
remove_column :assets, :lock_ttl
|
||||
remove_column :assets, :version
|
||||
|
||||
drop_table :wopi_actions
|
||||
drop_table :wopi_apps
|
||||
drop_table :wopi_discoveries
|
||||
end
|
||||
end
|
38
db/schema.rb
38
db/schema.rb
|
@ -46,16 +46,19 @@ ActiveRecord::Schema.define(version: 20160809074757) do
|
|||
add_index "asset_text_data", ["data_vector"], name: "index_asset_text_data_on_data_vector", using: :gin
|
||||
|
||||
create_table "assets", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "file_file_name"
|
||||
t.string "file_content_type"
|
||||
t.integer "file_file_size"
|
||||
t.datetime "file_updated_at"
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "estimated_size", default: 0, null: false
|
||||
t.boolean "file_present", default: false, null: false
|
||||
t.integer "estimated_size", default: 0, null: false
|
||||
t.boolean "file_present", default: false, null: false
|
||||
t.string "lock", limit: 1024
|
||||
t.integer "lock_ttl"
|
||||
t.integer "version"
|
||||
end
|
||||
|
||||
add_index "assets", ["created_at"], name: "index_assets_on_created_at", using: :btree
|
||||
|
@ -637,6 +640,8 @@ ActiveRecord::Schema.define(version: 20160809074757) do
|
|||
t.string "invited_by_type"
|
||||
t.integer "invitations_count", default: 0
|
||||
t.integer "tutorial_status", default: 0, null: false
|
||||
t.string "wopi_token"
|
||||
t.integer "wopi_token_ttl"
|
||||
end
|
||||
|
||||
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
|
||||
|
@ -646,6 +651,29 @@ ActiveRecord::Schema.define(version: 20160809074757) do
|
|||
add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id", using: :btree
|
||||
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
|
||||
|
||||
create_table "wopi_actions", force: :cascade do |t|
|
||||
t.string "action", null: false
|
||||
t.string "extension", null: false
|
||||
t.string "urlsrc", null: false
|
||||
t.integer "wopi_app_id", null: false
|
||||
end
|
||||
|
||||
add_index "wopi_actions", ["extension", "action"], name: "index_wopi_actions_on_extension_and_action", using: :btree
|
||||
|
||||
create_table "wopi_apps", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "icon", null: false
|
||||
t.integer "wopi_discovery_id", null: false
|
||||
end
|
||||
|
||||
create_table "wopi_discoveries", force: :cascade do |t|
|
||||
t.integer "expires", null: false
|
||||
t.string "proof_key_mod", null: false
|
||||
t.string "proof_key_exp", null: false
|
||||
t.string "proof_key_old_mod", null: false
|
||||
t.string "proof_key_old_exp", null: false
|
||||
end
|
||||
|
||||
add_foreign_key "activities", "my_modules"
|
||||
add_foreign_key "activities", "projects"
|
||||
add_foreign_key "activities", "users"
|
||||
|
@ -764,4 +792,6 @@ ActiveRecord::Schema.define(version: 20160809074757) do
|
|||
add_foreign_key "user_projects", "projects"
|
||||
add_foreign_key "user_projects", "users"
|
||||
add_foreign_key "user_projects", "users", column: "assigned_by_id"
|
||||
add_foreign_key "wopi_actions", "wopi_apps"
|
||||
add_foreign_key "wopi_apps", "wopi_discoveries"
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue