From b2b1d5a8f518c9f659794cd976e811eeed94af23 Mon Sep 17 00:00:00 2001 From: Nejc Bernot Date: Wed, 3 Aug 2016 15:31:25 +0200 Subject: [PATCH 01/54] Initial commit of WOPI integration --- Gemfile | 2 + Gemfile.lock | 1 + app/controllers/wopi_controller.rb | 130 ++++++++++++++++++++++++++ app/models/asset.rb | 18 ++++ app/models/user.rb | 30 +++++- app/models/wopi_action.rb | 12 +++ app/models/wopi_app.rb | 8 ++ app/models/wopi_discovery.rb | 6 ++ app/utilities/wopi_util.rb | 67 +++++++++++++ config/routes.rb | 12 +++ db/migrate/20160728145000_add_wopi.rb | 51 ++++++++++ db/schema.rb | 38 +++++++- 12 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 app/controllers/wopi_controller.rb create mode 100644 app/models/wopi_action.rb create mode 100644 app/models/wopi_app.rb create mode 100644 app/models/wopi_discovery.rb create mode 100644 app/utilities/wopi_util.rb create mode 100644 db/migrate/20160728145000_add_wopi.rb diff --git a/Gemfile b/Gemfile index 0d3e8260b..8c5a5d5ba 100644 --- a/Gemfile +++ b/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' diff --git a/Gemfile.lock b/Gemfile.lock index 32f598656..35f1f7c91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -321,6 +321,7 @@ DEPENDENCIES minitest-reporters (~> 1.1) momentjs-rails (>= 2.9.0) nested_form_fields + nokogiri paperclip (~> 4.3) pg puma diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb new file mode 100644 index 000000000..d18ec7562 --- /dev/null +++ b/app/controllers/wopi_controller.rb @@ -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("Alf") + rescue + Rails.logger.warn "proof verification failed" + render :nothing => true, :status => 401 and return + end + + + end + +end \ No newline at end of file diff --git a/app/models/asset.rb b/app/models/asset.rb index d7436ed69..838739ff2 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -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=1&") + edit_url = edit_url.gsub(//, "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 diff --git a/app/models/user.rb b/app/models/user.rb index f6ab98e29..f2953592c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/models/wopi_action.rb b/app/models/wopi_action.rb new file mode 100644 index 000000000..3c9efa432 --- /dev/null +++ b/app/models/wopi_action.rb @@ -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 \ No newline at end of file diff --git a/app/models/wopi_app.rb b/app/models/wopi_app.rb new file mode 100644 index 000000000..19f8d227f --- /dev/null +++ b/app/models/wopi_app.rb @@ -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 \ No newline at end of file diff --git a/app/models/wopi_discovery.rb b/app/models/wopi_discovery.rb new file mode 100644 index 000000000..9b72793a5 --- /dev/null +++ b/app/models/wopi_discovery.rb @@ -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 diff --git a/app/utilities/wopi_util.rb b/app/utilities/wopi_util.rb new file mode 100644 index 000000000..ad16dd00d --- /dev/null +++ b/app/utilities/wopi_util.rb @@ -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 \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index b45b67444..bfa77a62e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/db/migrate/20160728145000_add_wopi.rb b/db/migrate/20160728145000_add_wopi.rb new file mode 100644 index 000000000..471dc1872 --- /dev/null +++ b/db/migrate/20160728145000_add_wopi.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 4bb3282f7..64c4c79c4 100644 --- a/db/schema.rb +++ b/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 From 9a8d49d5136581a0176a405c6dc81471305794f5 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Wed, 3 Aug 2016 15:41:49 +0200 Subject: [PATCH 02/54] Update Gemfile.lock --- Gemfile.lock | 110 ++++++++++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 35f1f7c91..1296fb027 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -43,16 +43,15 @@ GEM arel (6.0.3) aspector (0.14.0) ast (2.3.0) - autoprefixer-rails (6.1.2) + autoprefixer-rails (6.4.0) execjs - json awesome_print (1.7.0) - aws-sdk (2.2.8) - aws-sdk-resources (= 2.2.8) - aws-sdk-core (2.2.8) + aws-sdk (2.2.37) + aws-sdk-resources (= 2.2.37) + aws-sdk-core (2.2.37) jmespath (~> 1.0) - aws-sdk-resources (2.2.8) - aws-sdk-core (= 2.2.8) + aws-sdk-resources (2.2.37) + aws-sdk-core (= 2.2.37) aws-sdk-v1 (1.66.0) json (~> 1.4) nokogiri (>= 1.4.4) @@ -63,34 +62,34 @@ GEM rack (>= 0.9.0) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootstrap-sass (3.3.6) + bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) bootstrap-select-rails (1.6.3) bootstrap3-datetimepicker-rails (4.15.35) momentjs-rails (>= 2.8.1) - bootstrap_form (2.3.0) + bootstrap_form (2.4.0) builder (3.2.2) - byebug (8.2.1) + byebug (9.0.5) climate_control (0.0.3) activesupport (>= 3.0) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) coderay (1.1.1) - coffee-rails (4.1.0) + coffee-rails (4.2.1) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.0) + railties (>= 4.0.0, < 5.2.x) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.10.0) commit_param_routing (0.0.1) - concurrent-ruby (1.0.0) + concurrent-ruby (1.0.2) debug_inspector (0.0.2) - delayed_job (4.1.1) - activesupport (>= 3.0, < 5.0) - delayed_job_active_record (4.1.0) - activerecord (>= 3.0, < 5) + delayed_job (4.1.2) + activesupport (>= 3.0, < 5.1) + delayed_job_active_record (4.1.1) + activerecord (>= 3.0, < 5.1) delayed_job (>= 3.0, < 5) devise (3.5.6) bcrypt (~> 3.0) @@ -99,14 +98,14 @@ GEM responders thread_safe (~> 0.1) warden (~> 1.2.3) - devise-async (0.10.1) - devise (~> 3.2) - devise_invitable (1.5.5) - actionmailer (>= 3.2.6, < 5) + devise-async (0.10.2) + devise (>= 3.2, < 4.0) + devise_invitable (1.6.0) + actionmailer (>= 3.2.6) devise (>= 3.2.0) erubis (2.7.0) - execjs (2.6.0) - faker (1.6.1) + execjs (2.7.0) + faker (1.6.6) i18n (~> 0.5) figaro (1.1.1) thor (~> 0.14) @@ -116,14 +115,14 @@ GEM activesupport (>= 4.1.0) hammerjs-rails (2.0.4) i18n (0.7.0) - i18n-js (3.0.0.rc11) - i18n (~> 0.6) + i18n-js (3.0.0.rc13) + i18n (~> 0.6, >= 0.6.6) introjs-rails (1.0.0) sass-rails (>= 3.2) thor (~> 0.14) - jmespath (1.1.3) - jquery-rails (4.0.5) - rails-dom-testing (~> 1.0) + jmespath (1.3.1) + jquery-rails (4.1.1) + rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-scrollto-rails (1.4.3) @@ -133,21 +132,23 @@ GEM turbolinks jquery-ui-rails (5.0.5) railties (>= 3.2.16) - js_cookie_rails (1.0.1) + js_cookie_rails (2.1.2) railties (>= 3.1) json (1.8.3) - kaminari (0.16.3) + kaminari (0.17.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) + lazy_priority_queue (0.1.1) little-plugger (1.1.4) logging (2.0.0) little-plugger (~> 1.1) multi_json (~> 1.10) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) + mail (2.6.4) + mime-types (>= 1.16, < 4) mime-types (1.25.1) + mime-types-data (3.2016.0521) mimemagic (0.3.0) mini_portile2 (2.1.0) minitest (5.9.0) @@ -158,8 +159,8 @@ GEM ruby-progressbar momentjs-rails (2.10.6) railties (>= 3.1) - multi_json (1.11.2) - nested_form_fields (0.7.4) + multi_json (1.12.1) + nested_form_fields (0.7.8) coffee-rails (>= 3.2.1) jquery-rails rails (>= 3.2.0) @@ -167,7 +168,7 @@ GEM mini_portile2 (~> 2.1.0) pkg-config (~> 1.1.7) orm_adapter (0.5.0) - paperclip (4.3.2) + paperclip (4.3.7) activemodel (>= 3.2.0) activesupport (>= 3.2.0) cocaine (~> 0.5.5) @@ -204,8 +205,8 @@ GEM rails_12factor (0.0.3) rails_serve_static_assets rails_stdout_logging - rails_serve_static_assets (0.0.4) - rails_stdout_logging (0.0.4) + rails_serve_static_assets (0.0.5) + rails_stdout_logging (0.0.5) railties (4.2.5) actionpack (= 4.2.5) activesupport (= 4.2.5) @@ -213,13 +214,14 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (2.1.0) rake (11.2.2) - rdoc (4.2.0) - redcarpet (3.3.3) + rdoc (4.2.2) + json (~> 1.4) + redcarpet (3.3.4) remotipart (1.2.1) responders (2.2.0) railties (>= 4.2.0, < 5.1) - rgl (0.5.1) - algorithms (~> 0.6.1) + rgl (0.5.2) + lazy_priority_queue (~> 0.1.0) stream (~> 0.5.0) roo (2.1.1) nokogiri (~> 1) @@ -250,34 +252,34 @@ GEM activesupport (>= 3.0.0) spinjs-rails (1.4) rails (>= 3.1) - sprockets (3.5.2) + sprockets (3.7.0) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (2.3.3) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (>= 2.8, < 4.0) + sprockets-rails (3.1.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) stream (0.5) thor (0.19.1) thread_safe (0.3.5) - tilt (2.0.1) - turbolinks (2.5.3) - coffee-rails + tilt (2.0.5) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.0) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.7.2) - execjs (>= 0.3.0) - json (>= 1.8.0) + uglifier (3.0.1) + execjs (>= 0.3.0, < 3) underscore-rails (1.8.3) unicode-display_width (1.1.0) warden (1.2.6) rack (>= 1.0) - web-console (2.2.1) + web-console (2.3.0) activemodel (>= 4.0) binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) - wicked_pdf (1.0.3) + wicked_pdf (1.0.6) wkhtmltopdf-heroku (2.12.3.0) yomu (0.2.4) json (~> 1.8) From 1b260785ec646ed8d8f2f97ba69393df27173d5d Mon Sep 17 00:00:00 2001 From: Nejc Bernot Date: Wed, 10 Aug 2016 17:49:25 +0200 Subject: [PATCH 03/54] First working version of office integration Only works on scinote-preview --- app/controllers/assets_controller.rb | 12 ++ app/controllers/wopi_controller.rb | 185 ++++++++++++++++++++++++-- app/models/asset.rb | 81 ++++++++++- app/models/user.rb | 1 + app/models/wopi_app.rb | 2 +- app/models/wopi_discovery.rb | 2 +- app/utilities/wopi_util.rb | 5 +- app/views/assets/edit.erb | 64 +++++++++ app/views/assets/view.erb | 64 +++++++++ app/views/steps/_step.html.erb | 8 ++ config/application.rb | 8 +- config/routes.rb | 9 +- db/migrate/20160728145000_add_wopi.rb | 2 +- db/schema.rb | 2 +- 14 files changed, 417 insertions(+), 28 deletions(-) create mode 100644 app/views/assets/edit.erb create mode 100644 app/views/assets/view.erb diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 7a7dd169a..640d07ebe 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -65,6 +65,18 @@ class AssetsController < ApplicationController end end + def edit + @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) + @token = current_user.get_wopi_token + @ttl = (current_user.wopi_token_ttl*1000).to_s + end + private def load_vars diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index d18ec7562..eb0c95fa6 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -1,28 +1,35 @@ class WopiController < ActionController::Base - before_action :authenticate_user_from_token!, :load_vars + before_action :load_vars,:authenticate_user_from_token! #before_action :verify_proof! - def get_file + def get_file_endpoint Rails.logger.warn "get_file called" #Only used for checkfileinfo check_file_info end - def get_file_contents + def get_file_contents_endpoint Rails.logger.warn "get_file_contents called" #Only used for getfile get_file end - def post_file + def post_file_endpoint Rails.logger.warn "post_file called" override = request.headers["X-WOPI-Override"] case override + when "GET_LOCK" + get_lock when "PUT_RELATIVE" put_relative when "LOCK" - lock + old_lock = request.headers["X-WOPI-OldLock"] + if old_lock.nil? + lock + else + unlock_and_relock + end when "UNLOCK" unlock when "REFRESH_LOCK" @@ -32,13 +39,14 @@ class WopiController < ActionController::Base end end - def post_file_contents + def post_file_contents_endpoint Rails.logger.warn "post_file_contents called" #Only used for putfile put_file end def check_file_info + Rails.logger.warn "Check file info started" msg = { :BaseFileName => @asset.file_file_name, :OwnerId => @asset.created_by_id.to_s, :Size => @asset.file_file_size, @@ -53,18 +61,175 @@ class WopiController < ActionController::Base :UserFriendlyName => @user.name, #TODO Check user permisisons :ReadOnly => false, + :UserCanNotWriteRelative => true, :UserCanWrite => true, #TODO decide what to put here - :CloseUrl => "", + :CloseUrl => "https://scinote-preview.herokuapp.com", :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 + response.headers['X-WOPI-HostEndpoint'] = ENV["WOPI_ENDPOINT_URL"] + response.headers['X-WOPI-MachineName'] = ENV["WOPI_ENDPOINT_URL"] + response.headers['X-WOPI-ServerVersion'] = APP_VERSION + render json:msg and return + end + def get_file + Rails.logger.warn "getting file" + response.headers["X-WOPI-ItemVersion"] = @asset.version + response.body = Paperclip.io_adapters.for(@asset.file).read + send_data response.body, disposition: "inline", :content_type => 'text/plain' + end + + def put_relative + Rails.logger.warn "put relative" + render :nothing => true, :status => 501 and return + end + + def lock + Rails.logger.warn "lock" + lock = request.headers["X-WOPI-Lock"] + if lock.nil? || lock.blank? + render :nothing => true, :status => 400 and return + end + @asset.with_lock do + if @asset.is_locked + if @asset.lock == lock + @asset.refresh_lock + response.headers["X-WOPI-ItemVersion"] = @asset.version + render :nothing => true, :status => 200 and return + else + response.headers["X-WOPI-Lock"] = @asset.lock + render :nothing => true, :status => 409 and return + end + else + @asset.lock_asset(lock) + response.headers["X-WOPI-ItemVersion"] = @asset.version + render :nothing => true, :status => 200 and return + end + end + end + + def unlock_and_relock + Rails.logger.warn "lock and relock" + lock = request.headers["X-WOPI-Lock"] + old_lock = request.headers["X-WOPI-OldLock"] + if lock.nil? || lock.blank? || old_lock.blank? + render :nothing => true, :status => 400 and return + end + @asset.with_lock do + if @asset.is_locked + if @asset.lock == old_lock + @asset.unlock + @asset.lock_asset(lock) + response.headers["X-WOPI-ItemVersion"] = @asset.version + render :nothing => true, :status => 200 and return + else + response.headers["X-WOPI-Lock"] = @asset.lock + render :nothing => true, :status => 409 and return + end + else + response.headers["X-WOPI-Lock"] = "" + render :nothing => true, :status => 409 and return + end + end + end + + def unlock + Rails.logger.warn "unlock" + lock = request.headers["X-WOPI-Lock"] + if lock.nil? || lock.blank? + render :nothing => true, :status => 400 and return + end + @asset.with_lock do + if @asset.is_locked + Rails.logger.warn "Current asset lock: #{@asset.lock}, unlocking lock #{lock}" + if @asset.lock == lock + @asset.unlock + response.headers["X-WOPI-ItemVersion"] = @asset.version + render :nothing => true, :status => 200 and return + else + response.headers["X-WOPI-Lock"] = @asset.lock + render :nothing => true, :status => 409 and return + end + else + Rails.logger.warn "Tried to unlock non-locked file" + response.headers["X-WOPI-Lock"] = "" + render :nothing => true, :status => 409 and return + end + end + end + + def refresh_lock + Rails.logger.warn "refresh lock" + lock = request.headers["X-WOPI-Lock"] + if lock.nil? || lock.blank? + render :nothing => true, :status => 400 and return + end + @asset.with_lock do + if @asset.is_locked + if @asset.lock == lock + @asset.refresh_lock + response.headers["X-WOPI-ItemVersion"] = @asset.version + response.headers["X-WOPI-ItemVersion"] = @asset.version + render :nothing => true, :status => 200 and return + else + response.headers["X-WOPI-Lock"] = @asset.lock + render :nothing => true, :status => 409 and return + end + else + response.headers["X-WOPI-Lock"] = "" + render :nothing => true, :status => 409 and return + end + end + end + + def get_lock + Rails.logger.warn "get lock" + @asset.with_lock do + if @asset.is_locked + response.headers["X-WOPI-Lock"] = @asset.lock + render :nothing => true, :status => 200 and return + else + response.headers["X-WOPI-Lock"] = "" + render :nothing => true, :status => 200 and return + end + end + end + # TODO When should we extract file text? + def put_file + Rails.logger.warn "put file" + @asset.with_lock do + lock = request.headers["X-WOPI-Lock"] + if @asset.is_locked + if @asset.lock == lock + Rails.logger.warn "replacing file" + @asset.update_contents(request.body) + response.headers["X-WOPI-ItemVersion"] = @asset.version + render :nothing => true, :status => 200 and return + else + Rails.logger.warn "wrong lock used to try and modify file" + response.headers["X-WOPI-Lock"] = @asset.lock + render :nothing => true, :status => 409 and return + end + else + if !@asset.file_file_size.nil? and @asset.file_file_size==0 + Rails.logger.warn "initializing empty file" + @asset.update_contents(request.body) + response.headers["X-WOPI-ItemVersion"] = @asset.version + render :nothing => true, :status => 200 and return + else + Rails.logger.warn "trying to modify unlocked file" + response.headers["X-WOPI-Lock"] = "" + render :nothing => true, :status => 409 and return + end + end + end + end def load_vars @@ -72,7 +237,7 @@ class WopiController < ActionController::Base if @asset.nil? render :nothing => true, :status => 404 and return else - Rails.logger.warn "Found asset with id: #{params[:id]}, (id: #{@asset.id})" + Rails.logger.warn "Found asset" step_assoc = @asset.step result_assoc = @asset.result @assoc = step_assoc if not step_assoc.nil? @@ -99,6 +264,7 @@ class WopiController < ActionController::Base Rails.logger.warn "no user with this token found" render :nothing => true, :status => 401 and return end + Rails.logger.warn "user found by token" #TODO check if the user can do anything with the file end @@ -118,7 +284,6 @@ class WopiController < ActionController::Base Rails.logger.warn "PROOF: #{proof}" - xml_doc = Nokogiri::XML("Alf") rescue Rails.logger.warn "proof verification failed" render :nothing => true, :status => 401 and return diff --git a/app/models/asset.rb b/app/models/asset.rb index 838739ff2..44eae8002 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -5,6 +5,8 @@ class Asset < ActiveRecord::Base include WopiUtil require 'tempfile' + # Lock duration set to 30 minutes + LOCK_DURATION = 60*30 # Paperclip validation has_attached_file :file, { @@ -296,23 +298,88 @@ class Asset < ActiveRecord::Base cache end + def can_perform_action(action) + file_ext = file_file_name.split(".").last + action = get_action(file_ext,action) + if action.nil? + return false + end + true + end - def get_action_path(user,action) + + def get_action_url(user,action,with_tokens = true) 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=1&") - edit_url = edit_url.gsub(//, "IsLicensedUser=1") - edit_url = edit_url.gsub(/<.*?=.*?>/, "") - #This does not work yet - provides path instead of absolute url + action_url = action.urlsrc + action_url = action_url.gsub(//, "IsLicensedUser=0&") + action_url = action_url.gsub(//, "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) - edit_url = edit_url + "WOPISrc=#{rest_url}&access_token=#{user.get_wopi_token}&access_token_ttl=#{user.wopi_token_ttl.to_s}" + action_url = 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}" + else + action_url + end else return nil 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 + 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! + 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! + end + + def unlock + self.lock = nil + self.lock_ttl = nil + self.save! + end + + def unlock_expired + self.with_lock do + if !self.lock_ttl.nil? and self.lock_ttl>= Time.now.to_i + self.lock = nil + self.lock_ttl = nil + self.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 + self.file = new_file + if self.version.nil? + self.version = 1 + else + self.version = self.version + 1 + end + self.save + end + + protected # Checks if attachments is an image (in post processing imagemagick will diff --git a/app/models/user.rb b/app/models/user.rb index f2953592c..b3d1dc20b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -270,6 +270,7 @@ class User < ActiveRecord::Base 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) + # 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}") diff --git a/app/models/wopi_app.rb b/app/models/wopi_app.rb index 19f8d227f..3150a03df 100644 --- a/app/models/wopi_app.rb +++ b/app/models/wopi_app.rb @@ -1,7 +1,7 @@ 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 + has_many :wopi_actions, class_name: 'WopiAction', foreign_key: 'wopi_app_id', :dependent => :destroy validates :name, :icon, :wopi_discovery, presence: true diff --git a/app/models/wopi_discovery.rb b/app/models/wopi_discovery.rb index 9b72793a5..47f6f9d43 100644 --- a/app/models/wopi_discovery.rb +++ b/app/models/wopi_discovery.rb @@ -1,6 +1,6 @@ class WopiDiscovery < ActiveRecord::Base - has_many :wopi_apps, class_name: 'WopiApp', foreign_key: 'wopi_discovery_id', :dependent => :delete_all + 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 end diff --git a/app/utilities/wopi_util.rb b/app/utilities/wopi_util.rb index ad16dd00d..4ffe19950 100644 --- a/app/utilities/wopi_util.rb +++ b/app/utilities/wopi_util.rb @@ -58,7 +58,10 @@ module WopiUtil end rescue Rails.logger.warn "Initialization failed" - WopiDiscovery.first.destroy + discovery = WopiDiscovery.first + unless discovery.nil? + discovery.destroy + end end end diff --git a/app/views/assets/edit.erb b/app/views/assets/edit.erb new file mode 100644 index 000000000..41fa8eaf5 --- /dev/null +++ b/app/views/assets/edit.erb @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + +
+ method="post"> + type="hidden"/> + type="hidden"/> +
+ + + + + + + \ No newline at end of file diff --git a/app/views/assets/view.erb b/app/views/assets/view.erb new file mode 100644 index 000000000..41fa8eaf5 --- /dev/null +++ b/app/views/assets/view.erb @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + +
+ method="post"> + type="hidden"/> + type="hidden"/> +
+ + + + + + + \ No newline at end of file diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index b360b722b..95be5bcf5 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -72,6 +72,14 @@ <%= link_to download_asset_path(asset), data: {no_turbolink: true, id: true, status: "asset-present"} do %> <%= image_tag preview_asset_path(asset) if asset.is_image? %>

<%= truncate(asset.file_file_name, length: 50) %>

+ <% if asset.can_perform_action("view") %> + <%= link_to "View", view_asset_url(id: asset) %> + <% end %> + <% if asset.can_perform_action("edit") %> + <%= link_to "Edit", edit_asset_url(id: asset) %> + <% end %> + <% else %> + <%= asset_loading_span(asset) %> <% end %> <% else %> <%= asset_loading_span(asset) %> diff --git a/config/application.rb b/config/application.rb index 605efe45d..a95eb3598 100644 --- a/config/application.rb +++ b/config/application.rb @@ -30,7 +30,13 @@ 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"} + Paperclip.options[:content_type_mappings] = {:csv => "text/plain", wopitest: ['text/plain', 'inode/x-empty'] } end end diff --git a/config/routes.rb b/config/routes.rb index bfa77a62e..47c65e092 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -267,11 +267,10 @@ Rails.application.routes.draw do 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" + get "wopi/files/:id/contents", to: "wopi#get_file_contents_endpoint" + post "wopi/files/:id/contents", to: "wopi#post_file_contents_endpoint" + get "wopi/files/:id", to: "wopi#get_file_endpoint", as: 'wopi_rest_endpoint' + post "wopi/files/:id", to: "wopi#post_file_endpoint" end diff --git a/db/migrate/20160728145000_add_wopi.rb b/db/migrate/20160728145000_add_wopi.rb index 471dc1872..01c7fc70b 100644 --- a/db/migrate/20160728145000_add_wopi.rb +++ b/db/migrate/20160728145000_add_wopi.rb @@ -6,7 +6,7 @@ class AddWopi< ActiveRecord::Migration add_column :assets, :lock, :string, :limit => 1024 add_column :assets, :lock_ttl, :integer - add_column :assets, :version, :integer + add_column :assets, :version, :integer, default: 1 create_table :wopi_discoveries do |t| t.integer :expires, null: false diff --git a/db/schema.rb b/db/schema.rb index 64c4c79c4..3151280b9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -58,7 +58,7 @@ ActiveRecord::Schema.define(version: 20160809074757) do t.boolean "file_present", default: false, null: false t.string "lock", limit: 1024 t.integer "lock_ttl" - t.integer "version" + t.integer "version", default: 1 end add_index "assets", ["created_at"], name: "index_assets_on_created_at", using: :btree From 3217271efea12fc7bf32486a61bc3b272aaddad9 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 18 Aug 2016 14:35:18 +0200 Subject: [PATCH 04/54] Remove default headers --- config/application.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/application.rb b/config/application.rb index a95eb3598..06d0fd78d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -30,11 +30,11 @@ module Scinote "[#{datetime}] #{severity}: #{msg}\n" end - config.action_dispatch.default_headers = { - 'X-WOPI-Lock' => "", - 'Random-header' => "with value", - 'Random-non-special-header' => "a" - } + #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'] } From 0e9756ad94624ca00697ca4565cece24f944d702 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 1 Sep 2016 12:51:30 +0200 Subject: [PATCH 05/54] Fix bad step view rebase --- Gemfile.lock | 2 -- app/views/steps/_step.html.erb | 14 ++++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1296fb027..25a3dca4f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,7 +38,6 @@ GEM tzinfo (~> 1.1) ajax-datatables-rails (0.3.1) railties (>= 3.1) - algorithms (0.6.1) ansi (1.5.0) arel (6.0.3) aspector (0.14.0) @@ -148,7 +147,6 @@ GEM mail (2.6.4) mime-types (>= 1.16, < 4) mime-types (1.25.1) - mime-types-data (3.2016.0521) mimemagic (0.3.0) mini_portile2 (2.1.0) minitest (5.9.0) diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index 95be5bcf5..26a24e6ae 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -72,14 +72,12 @@ <%= link_to download_asset_path(asset), data: {no_turbolink: true, id: true, status: "asset-present"} do %> <%= image_tag preview_asset_path(asset) if asset.is_image? %>

<%= truncate(asset.file_file_name, length: 50) %>

- <% if asset.can_perform_action("view") %> - <%= link_to "View", view_asset_url(id: asset) %> - <% end %> - <% if asset.can_perform_action("edit") %> - <%= link_to "Edit", edit_asset_url(id: asset) %> - <% end %> - <% else %> - <%= asset_loading_span(asset) %> + <% end %> + <% if asset.can_perform_action("view") %> + <%= link_to "View", view_asset_url(id: asset) %> + <% end %> + <% if asset.can_perform_action("edit") %> + <%= link_to "Edit", edit_asset_url(id: asset) %> <% end %> <% else %> <%= asset_loading_span(asset) %> From b51f2a3027538d993599a4a108261e3f541c4762 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 22 Sep 2016 13:19:35 +0200 Subject: [PATCH 06/54] Add basic WOPI proof checking Still missing validation of timestamp. Remove web_console gem for testing. Add relevant tests from manual for WOPI proof. --- Gemfile | 1 - Gemfile.lock | 6 --- app/controllers/wopi_controller.rb | 26 ++++----- app/models/wopi_discovery.rb | 39 ++++++++++++++ test/fixtures/wopi_discoveries.yml | 6 +++ test/models/wopi_discovery_test.rb | 85 ++++++++++++++++++++++++++++++ 6 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 test/fixtures/wopi_discoveries.yml create mode 100644 test/models/wopi_discovery_test.rb diff --git a/Gemfile b/Gemfile index 8c5a5d5ba..41e23a0ad 100644 --- a/Gemfile +++ b/Gemfile @@ -61,7 +61,6 @@ gem 'nokogiri' # XML parser group :development, :test do gem 'byebug' - gem 'web-console', '~> 2.0' gem 'better_errors' gem 'binding_of_caller' gem 'awesome_print' diff --git a/Gemfile.lock b/Gemfile.lock index 25a3dca4f..927c2221e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -272,11 +272,6 @@ GEM unicode-display_width (1.1.0) warden (1.2.6) rack (>= 1.0) - web-console (2.3.0) - activemodel (>= 4.0) - binding_of_caller (>= 0.7.2) - railties (>= 4.0) - sprockets-rails (>= 2.0, < 4.0) wicked_pdf (1.0.6) wkhtmltopdf-heroku (2.12.3.0) yomu (0.2.4) @@ -343,7 +338,6 @@ DEPENDENCIES tzinfo-data uglifier (>= 1.3.0) underscore-rails - web-console (~> 2.0) wicked_pdf wkhtmltopdf-heroku yomu diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index eb0c95fa6..e0f022768 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -1,6 +1,6 @@ class WopiController < ActionController::Base - before_action :load_vars,:authenticate_user_from_token! - #before_action :verify_proof! + before_action :load_vars,:authenticate_user_from_token! + before_action :verify_proof! def get_file_endpoint Rails.logger.warn "get_file called" @@ -273,23 +273,19 @@ class WopiController < ActionController::Base 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}" + signed_proof = request.headers["X-WOPI-Proof"] + signed_proof_old = request.headers["X-WOPI-ProofOld"] + url = request.original_url.upcase + unless WopiDiscovery.first.verify_proof(token, timestamp, signed_proof, + signed_proof_old, url) + render :nothing => true, :status => 401 and return + end rescue Rails.logger.warn "proof verification failed" render :nothing => true, :status => 401 and return end - - end -end \ No newline at end of file + +end diff --git a/app/models/wopi_discovery.rb b/app/models/wopi_discovery.rb index 47f6f9d43..3ecea072e 100644 --- a/app/models/wopi_discovery.rb +++ b/app/models/wopi_discovery.rb @@ -1,6 +1,45 @@ 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 + # Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted + # with this discovery public key (two key possible old/new) + def verify_proof(token, timestamp, signed_proof, signed_proof_old, 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 + + expected_proof = token_length + token.bytes + + url_length + url.upcase.bytes + + timestamp_length + timestamp_bytes + + key = generate_key(proof_key_mod, proof_key_exp) + old_key = generate_key(proof_key_old_mod, 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) + end + + # Generates a public key from given modulus and exponent + def generate_key(modulus, exponent) + mod = Base64.decode64(modulus).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), + 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*')) + end + end diff --git a/test/fixtures/wopi_discoveries.yml b/test/fixtures/wopi_discoveries.yml new file mode 100644 index 000000000..7aeddfbad --- /dev/null +++ b/test/fixtures/wopi_discoveries.yml @@ -0,0 +1,6 @@ +first: + expires: <%= Time.now + 1.days %> + proof_key_mod: '0HOWUPFFgmSYHbLZZzdWO/HUOr8YNfx5NAl7GUytooHZ7B9QxQKTJpj0NIJ4XEskQW8e4dLzRrPbNOOJ+KpWHttXz8HoQXkkZV/gYNxaNHJ8/pRXGMZzfVM5vchhx/2C7ULPTrpBsSpmfWQ6ShaVoQzfThFUd0MsBvIN7HVtqzPx9jbSV04wAqyNjcro7F3iu9w7AEsMejHbFlWoN+J05dP5ixryF7+2U5RVmjMt7/dYUdCoiXvCMt2CaVr0XEG6udHU4iDKVKZjmUBc7cTWRzhqEL7lZ1yQfylp38Nd2xxVJ0sSU7OkC1bBDlePcYGaF3JjJgsmp/H5BNnlW9gSxQ==' + proof_key_exp: 'AQAB' + proof_key_old_mod: 'u/ppb/da4jeKQ+XzKr69VJTqR7wgQp2jzDIaEPQVzfwod+pc1zvO7cwjNgfzF/KQGkltoOi9KdtMzR0qmX8C5wZI6wGpS8S4pTFAZPhXg5w4EpyR8fAagrnlOgaVLs0oX5UuBqKndCQyM7Vj5nFd+r53giS0ch7zDW0uB1G+ZWqTZ1TwbtV6dmlpVuJYeIPonOJgo2iuh455KuS2gvxZKOKR27Uq7W949oM8sqRjvfaVf4xDmyor++98XX0zadnf4pMWfPr3XE+bCXtB9jIPAxxMrALf5ncNRhnx0Wyf8zfM7Rfq+omp/HxCgusF5MC2/Ffnn7me/628zzioAMy5pQ==' + proof_key_old_exp: 'AQAB' diff --git a/test/models/wopi_discovery_test.rb b/test/models/wopi_discovery_test.rb new file mode 100644 index 000000000..829d9263f --- /dev/null +++ b/test/models/wopi_discovery_test.rb @@ -0,0 +1,85 @@ +class WopiDiscoveryTest < ActiveSupport::TestCase + def setup + @discovery = wopi_discoveries(:first) + end + + test 'Verify X-WOPI-Proof with current discovery proof key1.' do + assert @discovery.verify_proof( + 'yZhdN1qgywcOQWhyEMVpB6NE3pvBksvcLXsrFKXNtBeDTPW%2fu62g2t%2fOCWSlb3jUGaz1zc%2fzOzbNgAredLdhQI1Q7sPPqUv2owO78olmN74DV%2fv52OZIkBG%2b8jqjwmUobcjXVIC1BG9g%2fynMN0itZklL2x27Z2imCF6xELcQUuGdkoXBj%2bI%2bTlKM', + 635655897610773532, + 'IflL8OWCOCmws5qnDD5kYMraMGI3o+T+hojoDREbjZSkxbbx7XIS1Av85lohPKjyksocpeVwqEYm9nVWfnq05uhDNGp2MsNyhPO9unZ6w25Rjs1hDFM0dmvYx8wlQBNZ/CFPaz3inCMaaP4PtU85YepaDccAjNc1gikdy3kSMeG1XZuaDixHvMKzF/60DMfLMBIu5xP4Nt8i8Gi2oZs4REuxi6yxOv2vQJQ5+8Wu2Olm8qZvT4FEIQT9oZAXebn/CxyvyQv+RVpoU2gb4BreXAdfKthWF67GpJyhr+ibEVDoIIolUvviycyEtjsaEBpOf6Ne/OLRNu98un7WNDzMTQ==', + 'lWBTpWW8q80WC1eJEH5HMnGka4/LUF7zjUPqBwRMO0JzVcnjICvMP2TZPB2lJfy/4ctIstCN6P1t38NCTTbLWlXuE', + 'https://contoso.com/wopi/files/vHxYyRGM8VfmSGwGYDBMIQPzuE+sSC6kw+zWZw2Nyg?access_token=yZhdN1qgywcOQWhyEMVpB6NE3pvBksvcLXsrFKXNtBeDTPW%2fu62g2t%2fOCWSlb3jUGaz1zc%2fzOzbNgAredLdhQI1Q7sPPqUv2owO78olmN74DV%2fv52OZIkBG%2b8jqjwmUobcjXVIC1BG9g%2fynMN0itZklL2x27Z2imCF6xELcQUuGdkoXBj%2bI%2bTlKM' + ) + end + + test 'Verify X-WOPI-Proof with current discovery proof key2.' do + assert @discovery.verify_proof( + 'RLoY%2f3D73%2fjwt6IQqR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD6bHCI0BQrvacjNBL8ok%2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2n5PkTBh6GEYi2afuCIQ8mjXAUdvEDg3um2GjJKtA%3d%3d', + 635655897361394523, + 'x0IeSOjUQNH2pvjMPkP4Jotzs5Weeqms4AlPxMQ5CipssUJbyKFjLWnwPg1Ac0XtSTiPD177BmQ1+KtmYvDTWQ1FmBuvpvKZDSKzXoT6Qj4LCYYQ0TxnN/OT231+qd50sOD8zAxhfXP56qND9tj5xqoHMa+lbuvNCqiOBTZw5f/dklSK7Wgdx7ST3Dq6S9xxDUfsLC4Tjq+EsvcdSNIWL/W6NRZdyWqlgRgE6X8t/2iyyMypURdOW2Rztc6w/iYhbuh22Ul6Jfu14KaDo6YkvBr8iHlK4CcQST9i0u044y1Jnh34UK4EPdVRZrvTmeJ/5DFLWOqEwvBlW2bpoYF+9A==', + 'etYRI9UT6q8jA6PHMMmuGa8NbyIlbTHMHmJZqaCOh2GCpv7um2q7+7oaDFqAV/UP+2N85yZXvZgt9kTOUCwIdggUQVeJluNCwf1B5NsN/7n2aQF9LnWyYK8kK/3xvQKQrj4n24jk2MnHcX1tk8H/qLxq2DbPzi6ROoSgs2ZK8nmzhSPF74jnLCrwiwGgnVZV6gIhlAKCcUGtdrT60sgD/wpJGQQ0M59VDQhf1aDj5bUotf8RXovY8Gl0lpguvN4+EsEjpbVjdV9hxWs7c03JDdoz7mzFUWErSly9IzYXNfuFZMnSXpF3lGiprVJvW34Bu2gTo/cLq4LQoABkNCmd4g==', + 'https://contoso.com/wopi/files/JIB9h+LJpZWBDwvoIiQ5p3115zJWDecpGF9aCm1vOa5UMllgC7w?access_token=RLoY%2f3D73%2fjwt6IQqR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD6bHCI0BQrvacjNBL8ok%2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2n5PkTBh6GEYi2afuCIQ8mjXAUdvEDg3um2GjJKtA%3d%3d' + ) + end + + test 'Verify X-WOPI-ProofOld with current discovery proof key1.' do + assert @discovery.verify_proof( + 'zo7pjAbo%2fyof%2bvtUJn5gXpYcSl7TSSx0TbQGJbWJSll9PTjRRsAbG%2fSNL7FM2n5Ei3jQ8UJsW4RidT5R4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1wZk9uFkWEeGzbtGByTkaGJkBqgKV%2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG', + 635655898374047766, + 'qQhMjQf9Zohj+S/wvhe+RD6W5TIEqJwDWO3zX9DB85yRe3Ide7EPQDCY9dAZtJpWkIDDzU+8FEwnexF0EhPimfCkmAyoPpkl2YYvQvvwUK2gdlk3WboWOVszm17p4dSDA0TDMPYsjaAGHKM/nPnTyIMzRyArEzoy2vNkLEP6qdBIuMP2aCtGsciwMjYifHYRIRenB7H7I+FkwH0UaoTUCoo2PJkyZjy1nK6OwGVWaWG0G8g7Zy+K3bRYV+7cNaM5SB720ezhmYYJJvsIdRvO7pLsjAuTo4KJhvmVFCipwyCdllVHY83GjuGOsAbHIIohl0Ttq59o0jp4w2wUs8U+mQ==', + 'PjKR1BTNNnfOrUzfo27cLIhlrbSiOVZaANadDyHxKij/77ZYId+liyXoawvvQQPgnBH1dW6jqpr6fh5ZxZ9IOtaV+cTSUGnGdRSn7FyKs1ClpApKsZBO/iRBLXw3HDWOLc0jnA2bnxY8yqbEPmH5IBC9taYzxnf7aGjc6AWFHfs6AEQ8lMio6UoASNzjy3VVNzUX+CK+e5Z45coT0X60mjaJmidGfPdWIfyUw8sSuUwxQa1uNXAd8IceRUL7j5s9/kk7EwsihCw1Y3L+XJGG5zMsGhM9bTK5mvxj30UdmZORouNHdywOfdHaB1iOeKOk+yvWFMW3JsYShWbUhZUOEQ==', + 'https://contoso.com/wopi/files/RVQ29k8tf3h8cJ/Endy+aAMPy0iGhLatGNrhvKofPY9p2w?access_token=zo7pjAbo%2fyof%2bvtUJn5gXpYcSl7TSSx0TbQGJbWJSll9PTjRRsAbG%2fSNL7FM2n5Ei3jQ8UJsW4RidT5R4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1wZk9uFkWEeGzbtGByTkaGJkBqgKV%2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG' + ) + end + + test 'Verify X-WOPI-ProofOld with current discovery proof key2.' do + assert @discovery.verify_proof( +'2rZvm60097vFfXK01cylEhaYU26CbpsoEdVdk%2fDK2JzBjg185nE69CCklY36u2Thrx9DyLdNZGsbRorX12TK%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2bTQhhDlcymhYZ%2fnYx282ZrnI8gdIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8cHsiMvftBkQQ%2bJgqOU1le3OimjHOiDg%3d%3d', + 635655899172093398, + 'nLwR4nbFq8F/RLuAQ0Yby/avSJcpktZh+JlWonkXuVunTw+BZ+F84c3i1+0o7QoPQRkYmy/HEYhFoYVghkhQJzFXf98sYxb52SvDz/pl3G/gTgCcdcqkfRSMtaslkRX1VKC3YX42ksIL0LzM4wiCOJfT70i+2Yr5EQRWi6b2JELrAqPuNkxpuUUZ3DtEXsO9r6a4WkEwJesmqcqaQpMUvQ6cAkShgY7p0gAQeL//GG6wSpDaaA3QqKBcmBgzAatCaqg9NXiPjviZdoHgnbIRjBEhtH/WG00py/tsGMoIh6R9VfnC4jEWWymB5mIiXxW1gBuL63QvQqiL1S8FQ2Xo+g==', + 'M7R/iGyWInpWKl1+aAPuB2yVAwWaHTi504c87V0p35PzDyTh+K6fn20ygKmxf9suN9pq+/rMtUXhvM/zCyam0dZFuWpFOI22U0AjQxIJ4ZBH+zT+e/QeNmS7w4ctxUnDJ0zGsJ/DxaC0/DAkiIMOfXgb8FMPA/Z/HY+e1C6rRsoMXy0XGoTwzIDiI6wzPVr6FJpkktwO+eCNMyxnAODhlK+nubIhvmVEzWtenMXX8a2eJ3mFquNC7Le6shiUxZA03MgqknwjasNaGPrdpJYjxCwzH3LnHPOHxVY2HcjjTQTNHO8HEPCoSw9bi5a7XFS2loQM/tm0WknBe9+q4fpMMw==', + 'https://contoso.com/wopi/files/s/3BPN8mUjJJAQS+aOFtFm6CU+YNpRICIR2M7mQLeQ90BA?access_token=2rZvm60097vFfXK01cylEhaYU26CbpsoEdVdk%2fDK2JzBjg185nE69CCklY36u2Thrx9DyLdNZGsbRorX12TK%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2bTQhhDlcymhYZ%2fnYx282ZrnI8gdIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8cHsiMvftBkQQ%2bJgqOU1le3OimjHOiDg%3d%3d' + ) + end + + test 'Verify X-WOPI-Proof with old discovery proof key1.' do + assert @discovery.verify_proof( + 'pbocsujrb9BafFujWh%2fuh7Y6S5nBnonddEzDzV0zEFrBwhiu5lzjXRezXDC9N4acvJeGVB5CWAcxPz6cJ6FzJmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrbw8f2kRfaceRjV1PzXEvFXulnz2K%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4', + 635655898062751632, + 'qF15pAAnOATqpUTLHIS/Z5K7OYFVjWcgKGbHPa0eHRayXsb6JKTelGQhvs74gEFgg1mIgcCORwAtMzLmEFmOHgrdvkGvRzT3jtVVtwkxEhQt8aQL20N0Nwn4wNah0HeBHskdvmA1G/qcaFp8uTgHpRYFoBaSHEP3AZVNFg5y2jyYR34nNj359gktc2ZyLel3J3j7XtyjpRPHvvYVQfh7RsArLQ0VGp8sL4/BDHdSsUyJ8FXe67TSrz6TMZPwhEUR8dYHYek9qbQjC+wxPpo3G/yusucm1gHo0BjW/l36cI8FRmNs1Fqaeppxqu31FhR8dEl7w5dwefa9wOUKcChF6A==', + 'KmYzHJ9tu4SXfoiWzOkUIc0Bh8H3eJrA3OnDSbu2hT68EuLTp2vmvvFcHyHIiO8DuKj7u13MxkpuUER6VSIJp3nYfm91uEE/3g61V3SzaeRXdnkcKUa5x+ulKViECL2n4mpHzNnymxojFW5Y4lKUU4qEGzjE71K1DSFTU/CBkdqycsuy/Oct8G4GhA3O4MynlCf64B9LIhlWe4G+hxZgxIO0pq7w/1SH27nvScWiljVqgOAKr0Oidk/7sEfyBcOlerLgS/A00nJYYJk23DjrKGTKz1YY0CMEsROJCMiW11caxr0aKseOYlfmb6K1RXxtmiDpJ2T4y8jintjEdzEWDA==', + 'https://contoso.com/wopi/files/DJNj59eQlM6BvwzAHkykiB1vNOWRuxT487+guv3v7HexfA?access_token=pbocsujrb9BafFujWh%2fuh7Y6S5nBnonddEzDzV0zEFrBwhiu5lzjXRezXDC9N4acvJeGVB5CWAcxPz6cJ6FzJmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrbw8f2kRfaceRjV1PzXEvFXulnz2K%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4', + ) + end + + test 'Verify X-WOPI-Proof with old discovery proof key2.' do + assert @discovery.verify_proof( + '%2foBUJRjT2EVSVaB0Bjl2BCC7bXkN674bwppkzj2H9Elj2G%2bVGl06EzVgv62BqIW17gZZUweK%2fBPCOWN%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr982LaFC2Nxb0%2bH6u6MrWfRMK5dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzhRbQwIyMQ%3d', + 635655898147049273, + 'egEcu5FlU9q+u+d5WQ/Z1nYbttR+0iWsLh2BgiyzAq93sQFyvQ5XSCW5i/V7rt9HDewnmvIXsak5ayeoX2aDNgsVM8gSI533tO9dv/33vBM6MAzNLXP3v5rNI/TaTXcfm4Q1bIoE5RFVZUSPH73B3p1Oon0Hhhd1CysFGv6ue00CT1Dgpu/w5cVElQxZmWmguD5hEFgmg9IGKuSJ1CdVeJLcTbdKWhJnTl/G4+SpXfWBnvTBXSzLK+PdtfVCxh/bPO/FJNRWNAg+UgdVAXnLN79faiioG27lXGVrodpVgW8qdANvDkjQbn7z7jXmQmv8ksVQgHfZel963NXRty5pDg==', + 'AQUuboqy0JFnMgWfaz8o6V3YDCd6fwQlmkM5cNxCK1ikLK03W4R3xjazBevNja70i3y0JYZ3GvTlJy4ZsEfYf+DRhfzgSuxExpTvNB5RMUvi/kNRTlrMNm6JQrLr2rTY5A+FvQ8YErnKSl5gqh9vb4b47sRdQNvYL5hiVI+Qd4AGAwN9dSq/IJo3jHtivPSGMEfuqozoX3/GQEYfRf5dv58Rjeo5XRiDvuSDhPOacdoL/DFc/ksC0u9vN4qmoTSvt313p/FhFZ9T3NZYi/dv63XP8DJLWs3HMauzGiKFbvKaxDYb2K2llM1CRM2PbKproARMUR6oYYzwNKtg/Q7qIw==', + 'https://contoso.com/wopi/files/UzfEboK8w9Eal86Vk4xzIkGwUeMAiFaNF14FfQETzQ72LPw?access_token=%2foBUJRjT2EVSVaB0Bjl2BCC7bXkN674bwppkzj2H9Elj2G%2bVGl06EzVgv62BqIW17gZZUweK%2fBPCOWN%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr982LaFC2Nxb0%2bH6u6MrWfRMK5dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzhRbQwIyMQ%3d' + ) + end + + test 'Invalid proof key1.' do + assert_not @discovery.verify_proof( + '7DHGrfxYtgXxfaXF%2benHF3PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJRnJm5QIMXS7WbIxMy0LXaova2h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJHtSXfKANUetLyAjFWq6egtMZJSHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03kwAvBrdVQmjFdFryKw', + 635655899260461032, + 'Y/wVmPuvWJ5Q/Gl/a5mCkTrCKbfHWYiMG6cxmJgD+M/yYFzTsfcgbK2IRAlCR2eqGx3a5wh5bQzlC6Y/sKo2IE9Irz/NHFpV55zYfdwDi5ccwXSd34jWVUgkM3uL1r6KVmHcQH/ew10p54FVatXatuGp2Y+cq9BScYV1a45U8fs9zYoZcTAYvdWeYXmJbRGMOLLxab3fOiulPmbw+gOqpYPbuInc0yut6eGxAUmY1ENxpN7nbUqI3LbJvmE3PuX8Ifgg3RCsaFJEqC6JR36MjG+VqoKaFI6hh/ZWLR4y7FBarSmQBr2VlAHrcWCJIMXoOOKaHdsDfNCb3A24LFvdAQ==', + 'Pdbk1FoAB4zhxaDptfVOwTCmrNgWO6zdokoI3VYO8eshE9nJR1Rzr9K2666za29IfT050jJX0EBanIXAawL4rFA6swPHYQAzf3pWJqwvqIbaLYvi4104IBWhm9XdZ7C1jDUmG8DgwbKrXZfg7xxZ/hzPlwEp5Y9ZijD/ReAgRs0Va8/ytWc3AJ+121Q1Ss9U8vD08K5+wg1PVYyNa2YGBpVJbt2ZSt8dvuciWZujFDTzgLvRr6w17kg6+jkiwJyz2ZIL6ytyiUE1oJzsbslIZN3yGHEcmXZZ8Xz5q8fzrLUVmRx1kX6FE2QzRe4+6Q+qNeI8Ct7dj7JBBdbK2Jq+6A==', + 'https://contoso.com/wopi/files/Dy07US/uXVeOMwgyEqGqeVNnyOoaRxR+es2atR08tZPMatf0gf0?access_token=7DHGrfxYtgXxfaXF%2benHF3PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJRnJm5QIMXS7WbIxMy0LXaova2h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJHtSXfKANUetLyAjFWq6egtMZJSHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03kwAvBrdVQmjFdFryKw' + ) + end + + test 'Invalid proof key2.' do + assert_not @discovery.verify_proof( + 'itKqYThsIRPrrctbu%2bh7%2fMcYvNZiVKc5Un50ZqjKtvhVwz0XcObcEVm5rghR4PmrUG0P28QnQTYrcQ65%2bLtrIfbTBferY4zhPKt9qKaf6HKEyRU7utszLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZiQi4icNS6mMGYboNZ1pHaNCRmR31oZjmqWgcknWow8', + 635655897692506269, + 'Bu+myEy45g0hyKJycD7BYoDH+s10yvuH79Iq3OLz4w/o7gT8orKRrZ4KggJUWMzRPPSgg+CqEf6zdNm6kFrxf/zXCXTqa/bEzmJPNBby+r0jlyz2KFsOkxJ/mzVMKJltG0le56NJim21ZU4AwN0KiB6TZ56ruu/ma014proZWJt/H0qye/pNz5ZyYdyc1khgzKf/8o6sCgh6+VEZx93BjAUyMJr6OS9nYq1B8XGei/sE4xdgbrIGW0Jwjl2hxqBQTn1VL0jyljbjuG+vy83QaeBSB4TGLljHZFgYp6ARCX7v3NHO0MicJtTpLdpsB9dlLkbms/BcJd1gK7m5dDJK1A==', + 'CDU9l+cyr82vrUnyhdTh1P4jDwL82c//XrTWU/rPeTyB9Ko6z2aNCs893GEB3jICY7mDZ4t0sA5z/lpN83UyfH1uVqnEB92zCQsJTkXMdNG9dsK64G6RxIVbmhJkV5xiJvERCRnJJTcQg4ymeXQ3cuXAcN9XhStK8FCxBhxGMJsmlY2tahxdTEIHXHVagvdyB1UY0V1rv+OAOnCGaTs0yyBA0Mo5nmTSNF8VnYmqsmbXyyDPJJTsqallyJPxvdrLPeiBq1JHMr3b8dgel1jfweDR4mD9pHn3O9eaHN8b6xlLc+dIpwvcflzyhhEL5JKEUtO6EKbfakZTUeS86BuBZA==', + 'https://contoso.com/wopi/files/6XZhdjvt6/cdJvXdBI1bdlIkhIjJPnb9HgWzZX7V2d8?access_token=itKqYThsIRPrrctbu%2bh7%2fMcYvNZiVKc5Un50ZqjKtvhVwz0XcObcEVm5rghR4PmrUG0P28QnQTYrcQ65%2bLtrIfbTBferY4zhPKt9qKaf6HKEyRU7utszLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZiQi4icNS6mMGYboNZ1pHaNCRmR31oZjmqWgcknWow8' + ) + end +end From 674326e761bcd892660f673d98b73677922d529e Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 22 Sep 2016 16:18:57 +0200 Subject: [PATCH 07/54] Add timestamp WOPI checking Change status error code to 500. --- app/controllers/wopi_controller.rb | 79 +++++++++++++++++------------- app/utilities/wopi_util.rb | 38 ++++++++------ test/models/wopi_discovery_test.rb | 3 ++ 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index e0f022768..b16e0a4a9 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -1,22 +1,24 @@ class WopiController < ActionController::Base + include WopiUtil + before_action :load_vars,:authenticate_user_from_token! before_action :verify_proof! def get_file_endpoint - Rails.logger.warn "get_file called" + logger.warn "get_file called" #Only used for checkfileinfo check_file_info end def get_file_contents_endpoint - Rails.logger.warn "get_file_contents called" + logger.warn "get_file_contents called" #Only used for getfile get_file end def post_file_endpoint - Rails.logger.warn "post_file called" + logger.warn "post_file called" override = request.headers["X-WOPI-Override"] case override when "GET_LOCK" @@ -40,13 +42,13 @@ class WopiController < ActionController::Base end def post_file_contents_endpoint - Rails.logger.warn "post_file_contents called" + logger.warn "post_file_contents called" #Only used for putfile put_file end def check_file_info - Rails.logger.warn "Check file info started" + logger.warn "Check file info started" msg = { :BaseFileName => @asset.file_file_name, :OwnerId => @asset.created_by_id.to_s, :Size => @asset.file_file_size, @@ -79,19 +81,19 @@ class WopiController < ActionController::Base end def get_file - Rails.logger.warn "getting file" + logger.warn "getting file" response.headers["X-WOPI-ItemVersion"] = @asset.version response.body = Paperclip.io_adapters.for(@asset.file).read send_data response.body, disposition: "inline", :content_type => 'text/plain' end def put_relative - Rails.logger.warn "put relative" + logger.warn "put relative" render :nothing => true, :status => 501 and return end def lock - Rails.logger.warn "lock" + logger.warn "lock" lock = request.headers["X-WOPI-Lock"] if lock.nil? || lock.blank? render :nothing => true, :status => 400 and return @@ -115,7 +117,7 @@ class WopiController < ActionController::Base end def unlock_and_relock - Rails.logger.warn "lock and relock" + logger.warn "lock and relock" lock = request.headers["X-WOPI-Lock"] old_lock = request.headers["X-WOPI-OldLock"] if lock.nil? || lock.blank? || old_lock.blank? @@ -140,14 +142,14 @@ class WopiController < ActionController::Base end def unlock - Rails.logger.warn "unlock" + logger.warn "unlock" lock = request.headers["X-WOPI-Lock"] if lock.nil? || lock.blank? render :nothing => true, :status => 400 and return end @asset.with_lock do if @asset.is_locked - Rails.logger.warn "Current asset lock: #{@asset.lock}, unlocking lock #{lock}" + logger.warn "Current asset lock: #{@asset.lock}, unlocking lock #{lock}" if @asset.lock == lock @asset.unlock response.headers["X-WOPI-ItemVersion"] = @asset.version @@ -157,7 +159,7 @@ class WopiController < ActionController::Base render :nothing => true, :status => 409 and return end else - Rails.logger.warn "Tried to unlock non-locked file" + logger.warn "Tried to unlock non-locked file" response.headers["X-WOPI-Lock"] = "" render :nothing => true, :status => 409 and return end @@ -165,7 +167,7 @@ class WopiController < ActionController::Base end def refresh_lock - Rails.logger.warn "refresh lock" + logger.warn "refresh lock" lock = request.headers["X-WOPI-Lock"] if lock.nil? || lock.blank? render :nothing => true, :status => 400 and return @@ -189,7 +191,7 @@ class WopiController < ActionController::Base end def get_lock - Rails.logger.warn "get lock" + logger.warn "get lock" @asset.with_lock do if @asset.is_locked response.headers["X-WOPI-Lock"] = @asset.lock @@ -202,28 +204,28 @@ class WopiController < ActionController::Base end # TODO When should we extract file text? def put_file - Rails.logger.warn "put file" + logger.warn "put file" @asset.with_lock do lock = request.headers["X-WOPI-Lock"] if @asset.is_locked if @asset.lock == lock - Rails.logger.warn "replacing file" + logger.warn "replacing file" @asset.update_contents(request.body) response.headers["X-WOPI-ItemVersion"] = @asset.version render :nothing => true, :status => 200 and return else - Rails.logger.warn "wrong lock used to try and modify file" + logger.warn "wrong lock used to try and modify file" response.headers["X-WOPI-Lock"] = @asset.lock render :nothing => true, :status => 409 and return end else if !@asset.file_file_size.nil? and @asset.file_file_size==0 - Rails.logger.warn "initializing empty file" + logger.warn "initializing empty file" @asset.update_contents(request.body) response.headers["X-WOPI-ItemVersion"] = @asset.version render :nothing => true, :status => 200 and return else - Rails.logger.warn "trying to modify unlocked file" + logger.warn "trying to modify unlocked file" response.headers["X-WOPI-Lock"] = "" render :nothing => true, :status => 409 and return end @@ -237,7 +239,7 @@ class WopiController < ActionController::Base if @asset.nil? render :nothing => true, :status => 404 and return else - Rails.logger.warn "Found asset" + logger.warn "Found asset" step_assoc = @asset.step result_assoc = @asset.result @assoc = step_assoc if not step_assoc.nil? @@ -255,37 +257,44 @@ class WopiController < ActionController::Base def authenticate_user_from_token! wopi_token = params[:access_token] if wopi_token.nil? - Rails.logger.warn "nil wopi token" + 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" + logger.warn "no user with this token found" render :nothing => true, :status => 401 and return end - Rails.logger.warn "user found by token" + logger.warn "user found by token" #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"] - signed_proof = request.headers["X-WOPI-Proof"] - signed_proof_old = request.headers["X-WOPI-ProofOld"] - url = request.original_url.upcase + token = params[:access_token].encode('utf-8') + timestamp = request.headers['X-WOPI-TimeStamp'].to_i + signed_proof = request.headers['X-WOPI-Proof'] + signed_proof_old = request.headers['X-WOPI-ProofOld'] + url = request.original_url.upcase.encode('utf-8') - unless WopiDiscovery.first.verify_proof(token, timestamp, signed_proof, - signed_proof_old, url) - render :nothing => true, :status => 401 and return + if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now + if get_discovery.verify_proof(token, timestamp, signed_proof, + signed_proof_old, url) + logger.warn 'Proof verification: successful' + else + logger.warn 'Proof verification: not verified' + render :nothing => true, :status => 500 and return + end + else + logger.warn 'Proof verification: timestamp too old; ' + timestamp.to_s + render :nothing => true, :status => 500 and return end - rescue - Rails.logger.warn "proof verification failed" - render :nothing => true, :status => 401 and return + rescue => e + logger.warn 'Proof verification: failed; ' + e.message + render :nothing => true, :status => 500 and return end end - end diff --git a/app/utilities/wopi_util.rb b/app/utilities/wopi_util.rb index 4ffe19950..328a7ea4c 100644 --- a/app/utilities/wopi_util.rb +++ b/app/utilities/wopi_util.rb @@ -1,28 +1,38 @@ module WopiUtil require 'open-uri' + # Used for timestamp + UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000 + CLR_TICKS_PER_SECOND = 10000000 DISCOVERY_TTL = 60*60*24 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) + Time.at((timestamp-UNIX_EPOCH_IN_CLR_TICKS)/CLR_TICKS_PER_SECOND) + end + def get_action(extension, activity) + get_discovery + action = WopiAction.find_action(extension, activity) + end + + def get_discovery discovery = WopiDiscovery.first - if discovery.nil? || discovery.expires < Time.now.to_i - initializeDiscovery(discovery) - end - - action = WopiAction.find_action(extension,activity) - + return discovery if discovery && discovery.expires >= Time.now.to_i + initialize_discovery(discovery) end private + # Currently only saves Excel, Word and PowerPoint view and edit actions - def initializeDiscovery(discovery) + def initialize_discovery(discovery) begin Rails.logger.warn "Initializing discovery" - unless discovery.nil? - discovery.destroy - end + discovery.destroy if discovery @doc = Nokogiri::XML(open(ENV["WOPI_DISCOVERY_URL"])) @@ -56,15 +66,13 @@ module WopiUtil end end end + discovery rescue Rails.logger.warn "Initialization failed" discovery = WopiDiscovery.first - unless discovery.nil? - discovery.destroy - end + discovery.destroy if discovery end end - -end \ No newline at end of file +end diff --git a/test/models/wopi_discovery_test.rb b/test/models/wopi_discovery_test.rb index 829d9263f..a3a8f02e7 100644 --- a/test/models/wopi_discovery_test.rb +++ b/test/models/wopi_discovery_test.rb @@ -1,4 +1,7 @@ class WopiDiscoveryTest < ActiveSupport::TestCase + # These tests are taken from WOPI official documentation + # https://github.com/Microsoft/Office-Online-Test-Tools-and-Documentation/ + # blob/master/samples/python/proof_keys/tests.py def setup @discovery = wopi_discoveries(:first) end From 3600fbfe64b84b5c2bde43997d718623ffcb9589 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 22 Sep 2016 18:23:57 +0200 Subject: [PATCH 08/54] Fix UnlockUnlockedFile action The time was nigh! --- app/controllers/wopi_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index b16e0a4a9..cddbce3c7 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -160,7 +160,7 @@ class WopiController < ActionController::Base end else logger.warn "Tried to unlock non-locked file" - response.headers["X-WOPI-Lock"] = "" + response.headers["X-WOPI-Lock"] = " " render :nothing => true, :status => 409 and return end end From 0c9292e4fcc247fb847e732d77911b6086b08947 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 22 Sep 2016 18:47:13 +0200 Subject: [PATCH 09/54] Catch GET_SHARE_URL post file action --- app/controllers/wopi_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index cddbce3c7..e63ff2472 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -36,6 +36,8 @@ class WopiController < ActionController::Base unlock when "REFRESH_LOCK" refresh_lock + when "GET_SHARE_URL" + render :nothing => true, :status => 501 and return else render :nothing => true, :status => 404 and return end From c1ae4360a33fbaeb7bef8bc31bc7ccca4beb7dd8 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Fri, 23 Sep 2016 10:27:30 +0200 Subject: [PATCH 10/54] Refactor WopiController --- app/controllers/wopi_controller.rb | 368 ++++++++++++++--------------- config/routes.rb | 9 +- 2 files changed, 178 insertions(+), 199 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index e63ff2472..b18e234ea 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -1,251 +1,232 @@ class WopiController < ActionController::Base include WopiUtil - before_action :load_vars,:authenticate_user_from_token! + before_action :load_vars, :authenticate_user_from_token! before_action :verify_proof! - def get_file_endpoint - logger.warn "get_file called" - #Only used for checkfileinfo + # Only used for checkfileinfo + def file_get_endpoint check_file_info end - def get_file_contents_endpoint - logger.warn "get_file_contents called" - #Only used for getfile - get_file - + def file_contents_get_endpoint + # get_file + response.headers['X-WOPI-ItemVersion'] = @asset.version + response.body = Paperclip.io_adapters.for(@asset.file).read + send_data response.body, disposition: 'inline', content_type: 'text/plain' end def post_file_endpoint - logger.warn "post_file called" - override = request.headers["X-WOPI-Override"] + override = request.headers['X-WOPI-Override'] case override - when "GET_LOCK" - get_lock - when "PUT_RELATIVE" - put_relative - when "LOCK" - old_lock = request.headers["X-WOPI-OldLock"] - if old_lock.nil? - lock - else - unlock_and_relock - end - when "UNLOCK" - unlock - when "REFRESH_LOCK" - refresh_lock - when "GET_SHARE_URL" - render :nothing => true, :status => 501 and return + when 'GET_LOCK' + get_lock + when 'PUT_RELATIVE' + put_relative + when 'LOCK' + old_lock = request.headers['X-WOPI-OldLock'] + if old_lock.nil? + lock else - render :nothing => true, :status => 404 and return + unlock_and_relock end + when 'UNLOCK' + unlock + when 'REFRESH_LOCK' + refresh_lock + when 'GET_SHARE_URL' + render nothing: :true, status: 501 and return + else + render nothing: :true, status: 404 and return + end end - def post_file_contents_endpoint - logger.warn "post_file_contents called" - #Only used for putfile + # Only used for putfile + def file_contents_post_endpoint + logger.warn 'WOPI: post_file_contents called' put_file end def check_file_info - logger.warn "Check file info started" - 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, - :UserCanNotWriteRelative => true, - :UserCanWrite => true, - #TODO decide what to put here - :CloseUrl => "https://scinote-preview.herokuapp.com", - :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 - } - response.headers['X-WOPI-HostEndpoint'] = ENV["WOPI_ENDPOINT_URL"] - response.headers['X-WOPI-MachineName'] = ENV["WOPI_ENDPOINT_URL"] - response.headers['X-WOPI-ServerVersion'] = APP_VERSION - render json:msg and return - - end - - def get_file - logger.warn "getting file" - response.headers["X-WOPI-ItemVersion"] = @asset.version - response.body = Paperclip.io_adapters.for(@asset.file).read - send_data response.body, disposition: "inline", :content_type => 'text/plain' + 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, + UserCanNotWriteRelative: true, + UserCanWrite: true, + # TODO: decide what to put here + CloseUrl: 'https://scinote-preview.herokuapp.com', + 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 + } + response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL'] + response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL'] + response.headers['X-WOPI-ServerVersion'] = APP_VERSION + render json: msg and return end def put_relative - logger.warn "put relative" - render :nothing => true, :status => 501 and return + render nothing: :true, status: 501 and return end def lock - logger.warn "lock" - lock = request.headers["X-WOPI-Lock"] - if lock.nil? || lock.blank? - render :nothing => true, :status => 400 and return - end + lock = request.headers['X-WOPI-Lock'] + 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.lock == lock @asset.refresh_lock - response.headers["X-WOPI-ItemVersion"] = @asset.version - render :nothing => true, :status => 200 and return + response.headers['X-WOPI-ItemVersion'] = @asset.version + render nothing: :true, status: 200 and return else - response.headers["X-WOPI-Lock"] = @asset.lock - render :nothing => true, :status => 409 and return + response.headers['X-WOPI-Lock'] = @asset.lock + render nothing: :true, status: 409 and return end else @asset.lock_asset(lock) - response.headers["X-WOPI-ItemVersion"] = @asset.version - render :nothing => true, :status => 200 and return + response.headers['X-WOPI-ItemVersion'] = @asset.version + render nothing: :true, status: 200 and return end end end def unlock_and_relock - logger.warn "lock and relock" - lock = request.headers["X-WOPI-Lock"] - old_lock = request.headers["X-WOPI-OldLock"] + logger.warn 'lock and relock' + lock = request.headers['X-WOPI-Lock'] + old_lock = request.headers['X-WOPI-OldLock'] if lock.nil? || lock.blank? || old_lock.blank? - render :nothing => true, :status => 400 and return + render nothing: :true, status: 400 and return end @asset.with_lock do if @asset.is_locked if @asset.lock == old_lock @asset.unlock @asset.lock_asset(lock) - response.headers["X-WOPI-ItemVersion"] = @asset.version - render :nothing => true, :status => 200 and return + response.headers['X-WOPI-ItemVersion'] = @asset.version + render nothing: :true, status: 200 and return else - response.headers["X-WOPI-Lock"] = @asset.lock - render :nothing => true, :status => 409 and return + response.headers['X-WOPI-Lock'] = @asset.lock + render nothing: :true, status: 409 and return end else - response.headers["X-WOPI-Lock"] = "" - render :nothing => true, :status => 409 and return + response.headers['X-WOPI-Lock'] = '' + render nothing: :true, status: 409 and return end end end def unlock - logger.warn "unlock" - lock = request.headers["X-WOPI-Lock"] - if lock.nil? || lock.blank? - render :nothing => true, :status => 400 and return - end + 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 "Current asset lock: #{@asset.lock}, unlocking lock #{lock}" + logger.warn 'WOPI: current asset lock: #{@asset.lock}, + unlocking lock #{lock}' if @asset.lock == lock @asset.unlock - response.headers["X-WOPI-ItemVersion"] = @asset.version - render :nothing => true, :status => 200 and return + response.headers['X-WOPI-ItemVersion'] = @asset.version + render nothing: :true, status: 200 and return else - response.headers["X-WOPI-Lock"] = @asset.lock - render :nothing => true, :status => 409 and return + response.headers['X-WOPI-Lock'] = @asset.lock + render nothing: :true, status: 409 and return end else - logger.warn "Tried to unlock non-locked file" - response.headers["X-WOPI-Lock"] = " " - render :nothing => true, :status => 409 and return + logger.warn 'WOPI: tried to unlock non-locked file' + response.headers['X-WOPI-Lock'] = ' ' + render nothing: :true, status: 409 and return end end end def refresh_lock - logger.warn "refresh lock" - lock = request.headers["X-WOPI-Lock"] - if lock.nil? || lock.blank? - render :nothing => true, :status => 400 and return - end + 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.lock == lock @asset.refresh_lock - response.headers["X-WOPI-ItemVersion"] = @asset.version - response.headers["X-WOPI-ItemVersion"] = @asset.version - render :nothing => true, :status => 200 and return + response.headers['X-WOPI-ItemVersion'] = @asset.version + response.headers['X-WOPI-ItemVersion'] = @asset.version + render nothing: :true, status: 200 and return else - response.headers["X-WOPI-Lock"] = @asset.lock - render :nothing => true, :status => 409 and return + response.headers['X-WOPI-Lock'] = @asset.lock + render nothing: :true, status: 409 and return end else - response.headers["X-WOPI-Lock"] = "" - render :nothing => true, :status => 409 and return + response.headers['X-WOPI-Lock'] = '' + render nothing: :true, status: 409 and return end end end def get_lock - logger.warn "get lock" @asset.with_lock do if @asset.is_locked - response.headers["X-WOPI-Lock"] = @asset.lock - render :nothing => true, :status => 200 and return + response.headers['X-WOPI-Lock'] = @asset.lock else - response.headers["X-WOPI-Lock"] = "" - render :nothing => true, :status => 200 and return - end - end - end - # TODO When should we extract file text? - def put_file - logger.warn "put file" - @asset.with_lock do - lock = request.headers["X-WOPI-Lock"] - if @asset.is_locked - if @asset.lock == lock - logger.warn "replacing file" - @asset.update_contents(request.body) - response.headers["X-WOPI-ItemVersion"] = @asset.version - render :nothing => true, :status => 200 and return - else - logger.warn "wrong lock used to try and modify file" - response.headers["X-WOPI-Lock"] = @asset.lock - render :nothing => true, :status => 409 and return - end - else - if !@asset.file_file_size.nil? and @asset.file_file_size==0 - logger.warn "initializing empty file" - @asset.update_contents(request.body) - response.headers["X-WOPI-ItemVersion"] = @asset.version - render :nothing => true, :status => 200 and return - else - logger.warn "trying to modify unlocked file" - response.headers["X-WOPI-Lock"] = "" - render :nothing => true, :status => 409 and return - end + response.headers['X-WOPI-Lock'] = '' end + render nothing: :true, status: 200 and return end end + # TODO: When should we extract file text? + def put_file + @asset.with_lock do + lock = request.headers['X-WOPI-Lock'] + if @asset.is_locked + if @asset.lock == lock + logger.warn 'WOPI: replacing file' + @asset.update_contents(request.body) + response.headers['X-WOPI-ItemVersion'] = @asset.version + render nothing: :true, status: 200 and return + else + logger.warn 'WOPI: wrong lock used to try and modify file' + response.headers['X-WOPI-Lock'] = @asset.lock + render nothing: :true, status: 409 and return + end + elsif !@asset.file_file_size.nil? && @asset.file_file_size.zero? + logger.warn 'WOPI: initializing empty file' + @asset.update_contents(request.body) + response.headers['X-WOPI-ItemVersion'] = @asset.version + render nothing: :true, status: 200 and return + else + logger.warn 'WOPI: trying to modify unlocked file' + response.headers['X-WOPI-Lock'] = '' + render nothing: :true, status: 409 and return + end + end + end def load_vars @asset = Asset.find_by_id(params[:id]) if @asset.nil? - render :nothing => true, :status => 404 and return + render nothing: :true, status: 404 and return else - logger.warn "Found asset" + logger.warn 'Found 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 @@ -256,47 +237,46 @@ class WopiController < ActionController::Base end private - def authenticate_user_from_token! - wopi_token = params[:access_token] - if wopi_token.nil? - 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? - logger.warn "no user with this token found" - render :nothing => true, :status => 401 and return - end - logger.warn "user found by token" - - #TODO check if the user can do anything with the file + def authenticate_user_from_token! + wopi_token = params[:access_token] + if wopi_token.nil? + logger.warn 'WOPI: nil wopi token' + render nothing: :true, status: 401 and return end - def verify_proof! - begin - token = params[:access_token].encode('utf-8') - timestamp = request.headers['X-WOPI-TimeStamp'].to_i - signed_proof = request.headers['X-WOPI-Proof'] - signed_proof_old = request.headers['X-WOPI-ProofOld'] - 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, - signed_proof_old, url) - logger.warn 'Proof verification: successful' - else - logger.warn 'Proof verification: not verified' - render :nothing => true, :status => 500 and return - end - else - logger.warn 'Proof verification: timestamp too old; ' + timestamp.to_s - render :nothing => true, :status => 500 and return - end - rescue => e - logger.warn 'Proof verification: failed; ' + e.message - render :nothing => true, :status => 500 and return - end + @user = User.find_by_valid_wopi_token(wopi_token) + if @user.nil? + logger.warn 'WOPI: no user with this token found' + render nothing: :true, status: 401 and return end + logger.warn 'WOPI: user found by token' + # TODO: check if the user can do anything with the file + end + + def verify_proof! + token = params[:access_token].encode('utf-8') + timestamp = request.headers['X-WOPI-TimeStamp'].to_i + signed_proof = request.headers['X-WOPI-Proof'] + signed_proof_old = request.headers['X-WOPI-ProofOld'] + 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, + signed_proof_old, url) + logger.warn 'WOPI: proof verification: successful' + else + logger.warn 'WOPI: proof verification: not verified' + render nothing: :true, status: 500 and return + end + else + logger.warn 'WOPI: proof verification: timestamp too old; ' + + timestamp.to_s + render nothing: :true, status: 500 and return + end + rescue => e + logger.warn 'WOPI: proof verification: failed; ' + e.message + render nothing: :true, status: 500 and return + end end diff --git a/config/routes.rb b/config/routes.rb index 47c65e092..acf7f6c54 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -267,10 +267,9 @@ Rails.application.routes.draw do end # Office integration - get "wopi/files/:id/contents", to: "wopi#get_file_contents_endpoint" - post "wopi/files/:id/contents", to: "wopi#post_file_contents_endpoint" - - get "wopi/files/:id", to: "wopi#get_file_endpoint", as: 'wopi_rest_endpoint' - post "wopi/files/:id", to: "wopi#post_file_endpoint" + get 'wopi/files/:id/contents', to: 'wopi#file_contents_get_endpoint' + post 'wopi/files/:id/contents', to: 'wopi#file_contents_post_endpoint' + get 'wopi/files/:id', to: 'wopi#file_get_endpoint', as: 'wopi_rest_endpoint' + post 'wopi/files/:id', to: 'wopi#post_file_endpoint' end From 5aea0fbb196c20f32ec20afccaab5211d029c143 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Fri, 23 Sep 2016 11:42:12 +0200 Subject: [PATCH 11/54] Refactor the rest of WOPI logic from Nejc --- app/controllers/assets_controller.rb | 18 +++--- app/controllers/wopi_controller.rb | 20 +++---- app/models/asset.rb | 76 ++++++++++++------------ app/models/user.rb | 25 ++++---- app/models/wopi_action.rb | 17 +++--- app/models/wopi_app.rb | 16 +++--- app/models/wopi_discovery.rb | 76 +++++++++++++----------- app/utilities/wopi_util.rb | 83 +++++++++++++-------------- app/views/assets/edit.erb | 78 ++++++++++++------------- app/views/assets/view.erb | 78 ++++++++++++------------- config/application.rb | 11 ++-- db/migrate/20160728145000_add_wopi.rb | 10 ++-- 12 files changed, 247 insertions(+), 261 deletions(-) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 640d07ebe..9671bfa87 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -66,31 +66,27 @@ 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 + @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 + @ttl = (current_user.wopi_token_ttl * 1000).to_s end private 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 diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index b18e234ea..9c2a472f9 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -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,8 +263,8 @@ 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, - signed_proof_old, url) + if current_wopi_discovery.verify_proof(token, timestamp, signed_proof, + signed_proof_old, url) logger.warn 'WOPI: proof verification: successful' else logger.warn 'WOPI: proof verification: not verified' diff --git a/app/models/asset.rb b/app/models/asset.rb index 44eae8002..e82467a10 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -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 - action = get_action(file_ext,action) - if action.nil? - return false - end + file_ext = file_file_name.split('.').last + action = get_action(file_ext, action) + return false if action.nil? true end - - def get_action_url(user,action,with_tokens = true) - file_ext = file_file_name.split(".").last - action = get_action(file_ext,action) + def get_action_url(user, action, with_tokens = true) + 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=0&") - action_url = action_url.gsub(//, "IsLicensedUser=0") - action_url = action_url.gsub(/<.*?=.*?>/, "") + action_url = action_url.gsub(//, + 'IsLicensedUser=0&') + action_url = action_url.gsub(//, + '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,57 +335,49 @@ 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 - end - self.save + self.version = version.nil? ? 1 : version + 1 + save end - protected # Checks if attachments is an image (in post processing imagemagick will diff --git a/app/models/user.rb b/app/models/user.rb index b3d1dc20b..c34060677 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -253,33 +253,28 @@ 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 + protected def time_zone_check if time_zone.nil? or ActiveSupport::TimeZone.new(time_zone).nil? diff --git a/app/models/wopi_action.rb b/app/models/wopi_action.rb index 3c9efa432..aa58df170 100644 --- a/app/models/wopi_action.rb +++ b/app/models/wopi_action.rb @@ -1,12 +1,9 @@ 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' - 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 \ No newline at end of file + def self.find_action(extension, activity) + WopiAction.distinct + .where('extension = ? and action = ?', extension, activity).first + end +end diff --git a/app/models/wopi_app.rb b/app/models/wopi_app.rb index 3150a03df..81d9a6116 100644 --- a/app/models/wopi_app.rb +++ b/app/models/wopi_app.rb @@ -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 - - validates :name, :icon, :wopi_discovery, presence: true - -end \ No newline at end of file + 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 diff --git a/app/models/wopi_discovery.rb b/app/models/wopi_discovery.rb index 3ecea072e..3564ebe4a 100644 --- a/app/models/wopi_discovery.rb +++ b/app/models/wopi_discovery.rb @@ -1,45 +1,53 @@ class WopiDiscovery < ActiveRecord::Base - require 'base64' + 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) - def verify_proof(token, timestamp, signed_proof, signed_proof_old, 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 + # Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted + # with this discovery public key (two key possible old/new) + def verify_proof(token, timestamp, signed_proof, signed_proof_old, 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 - expected_proof = token_length + token.bytes + - url_length + url.upcase.bytes + - timestamp_length + timestamp_bytes + expected_proof = token_length + token.bytes + + url_length + url.upcase.bytes + + timestamp_length + timestamp_bytes - key = generate_key(proof_key_mod, proof_key_exp) - old_key = generate_key(proof_key_old_mod, proof_key_old_exp) + key = generate_key(proof_key_mod, proof_key_exp) + old_key = generate_key(proof_key_old_mod, 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) - end + # 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) + end - # Generates a public key from given modulus and exponent - def generate_key(modulus, exponent) - mod = Base64.decode64(modulus).unpack('H*').first.to_i(16) - exp = Base64.decode64(exponent).unpack('H*').first.to_i(16) + # Generates a public key from given modulus and exponent + def generate_key(modulus, exponent) + mod = Base64.decode64(modulus).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), - OpenSSL::ASN1::Integer.new(exp)]) - OpenSSL::PKey::RSA.new(seq.to_der) - end + 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*')) - 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*')) + end end diff --git a/app/utilities/wopi_util.rb b/app/utilities/wopi_util.rb index 328a7ea4c..01b4fa272 100644 --- a/app/utilities/wopi_util.rb +++ b/app/utilities/wopi_util.rb @@ -5,22 +5,22 @@ 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: # http://stackoverflow.com/questions/11888053/ # convert-net-datetime-ticks-property-to-date-in-objective-c 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 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" - discovery.destroy if 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 - discovery.save! + 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 + @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.wopi_discovery_id = discovery.id + wopi_app.save! + 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.wopi_app_id = wopi_app.id + wopi_action.save! end - discovery - rescue - Rails.logger.warn "Initialization failed" - discovery = WopiDiscovery.first - discovery.destroy if discovery end - + discovery + rescue + Rails.logger.warn 'Initialization failed' + discovery = WopiDiscovery.first + discovery.destroy if discovery end - end diff --git a/app/views/assets/edit.erb b/app/views/assets/edit.erb index 41fa8eaf5..72f39b1f8 100644 --- a/app/views/assets/edit.erb +++ b/app/views/assets/edit.erb @@ -1,6 +1,6 @@ - + @@ -9,56 +9,56 @@ + content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> + href="" /> - - + + -
- method="post"> + method="post"> type="hidden"/> type="hidden"/> -
+ - + - - \ No newline at end of file + diff --git a/app/views/assets/view.erb b/app/views/assets/view.erb index 41fa8eaf5..72f39b1f8 100644 --- a/app/views/assets/view.erb +++ b/app/views/assets/view.erb @@ -1,6 +1,6 @@ - + @@ -9,56 +9,56 @@ + content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> + href="" /> - - + + -
- method="post"> + method="post"> type="hidden"/> type="hidden"/> -
+ - + - - \ No newline at end of file + diff --git a/config/application.rb b/config/application.rb index 06d0fd78d..d8d3e4693 100644 --- a/config/application.rb +++ b/config/application.rb @@ -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 diff --git a/db/migrate/20160728145000_add_wopi.rb b/db/migrate/20160728145000_add_wopi.rb index 01c7fc70b..f368bb254 100644 --- a/db/migrate/20160728145000_add_wopi.rb +++ b/db/migrate/20160728145000_add_wopi.rb @@ -1,10 +1,9 @@ -class AddWopi< ActiveRecord::Migration - +class AddWopi < ActiveRecord::Migration def up - add_column :users, :wopi_token, :string + 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 @@ -32,8 +31,7 @@ class AddWopi< ActiveRecord::Migration 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] - + add_index :wopi_actions, [:extension, :action] end def down From 25c9ec67b34046f3bd5a839407eb225b684c6774 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Mon, 26 Sep 2016 19:23:27 +0200 Subject: [PATCH 12/54] Check permissions in wopi_controller --- app/controllers/wopi_controller.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 9c2a472f9..c5092970c 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -1,5 +1,6 @@ class WopiController < ActionController::Base include WopiUtil + include PermissionHelper before_action :load_vars, :authenticate_user_from_token! before_action :verify_proof! @@ -62,10 +63,8 @@ class WopiController < ActionController::Base # which should NOT be business LicenseCheckForEditIsEnabled: true, UserFriendlyName: @user.name, - # TODO: Check user permisisons - ReadOnly: false, + UserCanWrite: @can_write, UserCanNotWriteRelative: true, - UserCanWrite: true, # TODO: decide what to put here CloseUrl: 'https://scinote-preview.herokuapp.com', DownloadUrl: url_for(controller: 'assets', action: 'download', @@ -252,7 +251,18 @@ class WopiController < ActionController::Base end logger.warn 'WOPI: user found by token' - # TODO: check if the user can do anything with the file + # This is what we get for settings permission methods with + # current_user + @current_user = @user + if @assoc.class == Step + @can_read = can_view_steps_in_protocol(@protocol) + @can_write = can_edit_step_in_protocol(@protocol) + else + @can_read = can_view_or_download_result_assets(@module) + @can_write = can_edit_result_asset_in_module(@module) + end + + render nothing: :true, status: 404 and return unless @can_read end def verify_proof! From e96e8486ea691c94446e69740e517cb70cf86151 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Tue, 27 Sep 2016 18:14:19 +0200 Subject: [PATCH 13/54] Add check file permissions to assets controller SCI-502 #close --- app/controllers/assets_controller.rb | 13 +++++++++++++ app/views/steps/_step.html.erb | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 9671bfa87..c56cacdd9 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -1,6 +1,7 @@ class AssetsController < ApplicationController before_action :load_vars, except: [:signature] before_action :check_read_permission, except: [:signature, :file_present] + before_action :check_edit_permission, only: [ :edit ] # Validates asset and then generates S3 upload posts, because # otherwise untracked files could be uploaded to S3 @@ -107,6 +108,18 @@ class AssetsController < ApplicationController end end + def check_edit_permission + if @assoc.class == Step + unless can_edit_step_in_protocol(@protocol) + render_403 and return + end + elsif @assoc.class == Result + unless can_edit_result_asset_in_module(@my_module) + render_403 and return + end + end + end + def generate_upload_posts(asset) posts = [] s3_post = S3_BUCKET.presigned_post( diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index 26a24e6ae..2dc3a5e68 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -76,7 +76,8 @@ <% if asset.can_perform_action("view") %> <%= link_to "View", view_asset_url(id: asset) %> <% end %> - <% if asset.can_perform_action("edit") %> + <% if can_edit_step_in_protocol(@protocol) && + asset.can_perform_action("edit") %> <%= link_to "Edit", edit_asset_url(id: asset) %> <% end %> <% else %> From 1bdbc00e8c7841d64300bcc0155c4876ba3a057e Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Wed, 28 Sep 2016 09:38:17 +0200 Subject: [PATCH 14/54] Update Gemfile.lock --- Gemfile.lock | 126 +++++++++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 54 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 927c2221e..84f392832 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,22 +38,30 @@ GEM tzinfo (~> 1.1) ajax-datatables-rails (0.3.1) railties (>= 3.1) + algorithms (0.6.1) ansi (1.5.0) arel (6.0.3) aspector (0.14.0) ast (2.3.0) - autoprefixer-rails (6.4.0) + auto_strip_attributes (2.1.0) + activerecord (>= 3.0) + autoprefixer-rails (6.1.2) execjs + json awesome_print (1.7.0) - aws-sdk (2.2.37) - aws-sdk-resources (= 2.2.37) - aws-sdk-core (2.2.37) + aws-sdk (2.2.8) + aws-sdk-resources (= 2.2.8) + aws-sdk-core (2.2.8) jmespath (~> 1.0) - aws-sdk-resources (2.2.37) - aws-sdk-core (= 2.2.37) + aws-sdk-resources (2.2.8) + aws-sdk-core (= 2.2.8) aws-sdk-v1 (1.66.0) json (~> 1.4) nokogiri (>= 1.4.4) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) bcrypt (3.1.11) better_errors (2.1.1) coderay (>= 1.0.0) @@ -61,34 +69,34 @@ GEM rack (>= 0.9.0) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootstrap-sass (3.3.7) + bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) bootstrap-select-rails (1.6.3) bootstrap3-datetimepicker-rails (4.15.35) momentjs-rails (>= 2.8.1) - bootstrap_form (2.4.0) + bootstrap_form (2.3.0) builder (3.2.2) - byebug (9.0.5) + byebug (8.2.1) climate_control (0.0.3) activesupport (>= 3.0) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) coderay (1.1.1) - coffee-rails (4.2.1) + coffee-rails (4.1.0) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.2.x) + railties (>= 4.0.0, < 5.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.10.0) commit_param_routing (0.0.1) - concurrent-ruby (1.0.2) + concurrent-ruby (1.0.0) debug_inspector (0.0.2) - delayed_job (4.1.2) - activesupport (>= 3.0, < 5.1) - delayed_job_active_record (4.1.1) - activerecord (>= 3.0, < 5.1) + delayed_job (4.1.1) + activesupport (>= 3.0, < 5.0) + delayed_job_active_record (4.1.0) + activerecord (>= 3.0, < 5) delayed_job (>= 3.0, < 5) devise (3.5.6) bcrypt (~> 3.0) @@ -97,14 +105,14 @@ GEM responders thread_safe (~> 0.1) warden (~> 1.2.3) - devise-async (0.10.2) - devise (>= 3.2, < 4.0) - devise_invitable (1.6.0) - actionmailer (>= 3.2.6) + devise-async (0.10.1) + devise (~> 3.2) + devise_invitable (1.5.5) + actionmailer (>= 3.2.6, < 5) devise (>= 3.2.0) erubis (2.7.0) - execjs (2.7.0) - faker (1.6.6) + execjs (2.6.0) + faker (1.6.1) i18n (~> 0.5) figaro (1.1.1) thor (~> 0.14) @@ -114,14 +122,14 @@ GEM activesupport (>= 4.1.0) hammerjs-rails (2.0.4) i18n (0.7.0) - i18n-js (3.0.0.rc13) - i18n (~> 0.6, >= 0.6.6) + i18n-js (3.0.0.rc11) + i18n (~> 0.6) introjs-rails (1.0.0) sass-rails (>= 3.2) thor (~> 0.14) - jmespath (1.3.1) - jquery-rails (4.1.1) - rails-dom-testing (>= 1, < 3) + jmespath (1.1.3) + jquery-rails (4.0.5) + rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-scrollto-rails (1.4.3) @@ -131,21 +139,20 @@ GEM turbolinks jquery-ui-rails (5.0.5) railties (>= 3.2.16) - js_cookie_rails (2.1.2) + js_cookie_rails (1.0.1) railties (>= 3.1) json (1.8.3) - kaminari (0.17.0) + kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - lazy_priority_queue (0.1.1) little-plugger (1.1.4) logging (2.0.0) little-plugger (~> 1.1) multi_json (~> 1.10) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.4) - mime-types (>= 1.16, < 4) + mail (2.6.3) + mime-types (>= 1.16, < 3) mime-types (1.25.1) mimemagic (0.3.0) mini_portile2 (2.1.0) @@ -157,16 +164,17 @@ GEM ruby-progressbar momentjs-rails (2.10.6) railties (>= 3.1) - multi_json (1.12.1) - nested_form_fields (0.7.8) + multi_json (1.11.2) + nested_form_fields (0.7.4) coffee-rails (>= 3.2.1) jquery-rails rails (>= 3.2.0) nokogiri (1.6.8) mini_portile2 (~> 2.1.0) pkg-config (~> 1.1.7) + oj (2.17.4) orm_adapter (0.5.0) - paperclip (4.3.7) + paperclip (4.3.2) activemodel (>= 3.2.0) activesupport (>= 3.2.0) cocaine (~> 0.5.5) @@ -203,8 +211,8 @@ GEM rails_12factor (0.0.3) rails_serve_static_assets rails_stdout_logging - rails_serve_static_assets (0.0.5) - rails_stdout_logging (0.0.5) + rails_serve_static_assets (0.0.4) + rails_stdout_logging (0.0.4) railties (4.2.5) actionpack (= 4.2.5) activesupport (= 4.2.5) @@ -212,15 +220,15 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (2.1.0) rake (11.2.2) - rdoc (4.2.2) - json (~> 1.4) - redcarpet (3.3.4) + rdoc (4.2.0) + redcarpet (3.3.3) remotipart (1.2.1) responders (2.2.0) railties (>= 4.2.0, < 5.1) - rgl (0.5.2) - lazy_priority_queue (~> 0.1.0) + rgl (0.5.1) + algorithms (~> 0.6.1) stream (~> 0.5.0) + rkelly-remix (0.0.7) roo (2.1.1) nokogiri (~> 1) rubyzip (~> 1.1, < 2.0.0) @@ -248,31 +256,39 @@ GEM activesupport (>= 4.0.0) skylight (0.10.0) activesupport (>= 3.0.0) + sourcemap (0.1.1) spinjs-rails (1.4) rails (>= 3.1) - sprockets (3.7.0) + sprockets (3.5.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.1.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) + sprockets-rails (2.3.3) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (>= 2.8, < 4.0) + starscope (1.5.3) + babel-transpiler (~> 0.7) + oj (~> 2.9) + parser (>= 2.2.2) + rkelly-remix (~> 0.0.7) + ruby-progressbar (~> 1.5) + sourcemap (~> 0.1) stream (0.5) thor (0.19.1) thread_safe (0.3.5) - tilt (2.0.5) - turbolinks (5.0.1) - turbolinks-source (~> 5) - turbolinks-source (5.0.0) + tilt (2.0.1) + turbolinks (2.5.3) + coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (3.0.1) - execjs (>= 0.3.0, < 3) + uglifier (2.7.2) + execjs (>= 0.3.0) + json (>= 1.8.0) underscore-rails (1.8.3) unicode-display_width (1.1.0) warden (1.2.6) rack (>= 1.0) - wicked_pdf (1.0.6) + wicked_pdf (1.0.3) wkhtmltopdf-heroku (2.12.3.0) yomu (0.2.4) json (~> 1.8) @@ -284,6 +300,7 @@ PLATFORMS DEPENDENCIES ajax-datatables-rails (~> 0.3.1) aspector + auto_strip_attributes (~> 2.1) awesome_print aws-sdk (~> 2.2.8) aws-sdk-v1 @@ -334,6 +351,7 @@ DEPENDENCIES shoulda-matchers (>= 3.0.1) skylight spinjs-rails + starscope turbolinks tzinfo-data uglifier (>= 1.3.0) From 02ddd64aaa4db6bbd6ee9e6a941bce61a35f6e43 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Wed, 28 Sep 2016 16:22:25 +0200 Subject: [PATCH 15/54] Set last_modified_by on put_file --- app/controllers/wopi_controller.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index c5092970c..f39010f01 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -196,6 +196,8 @@ class WopiController < ActionController::Base if @asset.lock == lock logger.warn 'WOPI: replacing file' @asset.update_contents(request.body) + @asset.last_modified_by = @user + @asset.save response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else @@ -206,6 +208,8 @@ class WopiController < ActionController::Base elsif !@asset.file_file_size.nil? && @asset.file_file_size.zero? logger.warn 'WOPI: initializing empty file' @asset.update_contents(request.body) + @asset.last_modified_by = @user + @asset.save response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else @@ -221,7 +225,7 @@ class WopiController < ActionController::Base if @asset.nil? render nothing: :true, status: 404 and return else - logger.warn 'Found asset' + logger.warn 'Found asset: ' + @asset.id.to_s step_assoc = @asset.step result_assoc = @asset.result @assoc = step_assoc unless step_assoc.nil? @@ -249,7 +253,8 @@ class WopiController < ActionController::Base logger.warn 'WOPI: no user with this token found' render nothing: :true, status: 401 and return end - logger.warn 'WOPI: user found by token' + logger.warn 'WOPI: user found by token ' + wopi_token + + ' ID: ' + @user.id.to_s # This is what we get for settings permission methods with # current_user From 5e5f4c5ed4a8a54cc1d7a3ccfa255591e1a90fce Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Wed, 28 Sep 2016 17:36:53 +0200 Subject: [PATCH 16/54] Add text extraction on unlock --- app/controllers/wopi_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index f39010f01..89bf68280 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -142,6 +142,7 @@ class WopiController < ActionController::Base unlocking lock #{lock}" if @asset.lock == lock @asset.unlock + @asset.post_process_file(@organization) response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else @@ -188,7 +189,6 @@ class WopiController < ActionController::Base end end - # TODO: When should we extract file text? def put_file @asset.with_lock do lock = request.headers['X-WOPI-Lock'] @@ -233,8 +233,10 @@ class WopiController < ActionController::Base if @assoc.class == Step @protocol = @asset.step.protocol + @organization = @protocol.organization else @my_module = @assoc.my_module + @organization = @my_module.experiment.project.organization end end end From bc22697ebe045a80350bb8c42fc13de33d1738b3 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Wed, 28 Sep 2016 18:02:47 +0200 Subject: [PATCH 17/54] Take/release space in putfile --- app/controllers/wopi_controller.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 89bf68280..348b314a3 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -142,7 +142,7 @@ class WopiController < ActionController::Base unlocking lock #{lock}" if @asset.lock == lock @asset.unlock - @asset.post_process_file(@organization) + @asset.post_process_file # Space is already taken in put_file response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else @@ -195,9 +195,15 @@ class WopiController < ActionController::Base if @asset.locked? if @asset.lock == lock logger.warn 'WOPI: replacing file' + + @organization.release_space(@asset.estimated_size) @asset.update_contents(request.body) @asset.last_modified_by = @user @asset.save + + @organization.take_space(@asset.estimated_size) + @organization.save + response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else @@ -207,9 +213,13 @@ class WopiController < ActionController::Base end elsif !@asset.file_file_size.nil? && @asset.file_file_size.zero? logger.warn 'WOPI: initializing empty file' + + @organization.release_space(@asset.estimated_size) @asset.update_contents(request.body) @asset.last_modified_by = @user @asset.save + @organization.save + response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else From 3bc683be8f9c983161d24ca22076ab74af60a644 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 29 Sep 2016 10:57:08 +0200 Subject: [PATCH 18/54] Fix access_token_ttl calculation --- app/models/user.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index c34060677..5e67ee19e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -266,11 +266,11 @@ class User < ActiveRecord::Base # 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 + 1.days + self.wopi_token_ttl = (Time.now + 1.days).to_i save - Rails.logger.warn("Generating new token #{wopi_token}") + Rails.logger.warn("WOPI: generating new token #{wopi_token}") end - Rails.logger.warn("Returning token #{wopi_token}") + Rails.logger.warn("WOPI: returning token #{wopi_token}") wopi_token end From ed8df3cc62689e06d78becc62147485a2b45ac07 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 29 Sep 2016 12:19:29 +0200 Subject: [PATCH 19/54] Add favicon url to iframe --- app/controllers/assets_controller.rb | 2 ++ app/models/asset.rb | 6 ++++ app/views/assets/edit.erb | 46 +++++++++++++--------------- app/views/assets/view.erb | 45 +++++++++++++-------------- 4 files changed, 50 insertions(+), 49 deletions(-) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index c56cacdd9..94492948c 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -68,12 +68,14 @@ class AssetsController < ApplicationController def edit @action_url = @asset.get_action_url(current_user, 'edit', false) + @favicon_url = @asset.favicon_url('edit') @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) + @favicon_url = @asset.favicon_url('view') @token = current_user.get_wopi_token @ttl = (current_user.wopi_token_ttl * 1000).to_s end diff --git a/app/models/asset.rb b/app/models/asset.rb index e82467a10..6ec1964b2 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -335,6 +335,12 @@ class Asset < ActiveRecord::Base end end + def favicon_url(action) + file_ext = file_file_name.split('.').last + action = get_action(file_ext, action) + action.wopi_app.icon if action.wopi_app + end + # locked?, lock_asset and refresh_lock rely on the asset # being locked in the database to prevent race conditions def locked? diff --git a/app/views/assets/edit.erb b/app/views/assets/edit.erb index 72f39b1f8..ea03dede1 100644 --- a/app/views/assets/edit.erb +++ b/app/views/assets/edit.erb @@ -11,8 +11,7 @@ - + +
+ + +
-
- method="post"> - type="hidden"/> - type="hidden"/> -
+ - - - - - + + diff --git a/app/views/assets/view.erb b/app/views/assets/view.erb index 72f39b1f8..00ece94e3 100644 --- a/app/views/assets/view.erb +++ b/app/views/assets/view.erb @@ -11,8 +11,7 @@ - + +
+ + +
-
- method="post"> - type="hidden"/> - type="hidden"/> -
+ - + - - - + From ff47f29dc22795b924bdb7ea418dcd2424f1e1ec Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 29 Sep 2016 15:30:55 +0200 Subject: [PATCH 20/54] Add wopi file activity SCI-393 #close --- app/controllers/assets_controller.rb | 3 +++ app/controllers/wopi_controller.rb | 2 ++ app/models/activity.rb | 4 ++- app/utilities/wopi_util.rb | 38 ++++++++++++++++++++++++++++ config/locales/en.yml | 4 +++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 94492948c..87e084eac 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -1,4 +1,6 @@ class AssetsController < ApplicationController + include WopiUtil + before_action :load_vars, except: [:signature] before_action :check_read_permission, except: [:signature, :file_present] before_action :check_edit_permission, only: [ :edit ] @@ -71,6 +73,7 @@ class AssetsController < ApplicationController @favicon_url = @asset.favicon_url('edit') @token = current_user.get_wopi_token @ttl = (current_user.wopi_token_ttl * 1000).to_s + create_wopi_file_activity(current_user, true) end def view diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 348b314a3..edc6c6359 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -143,6 +143,8 @@ class WopiController < ActionController::Base if @asset.lock == lock @asset.unlock @asset.post_process_file # Space is already taken in put_file + create_wopi_file_activity(@user, false) + response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else diff --git a/app/models/activity.rb b/app/models/activity.rb index bc2c91212..3a52b5d24 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -39,7 +39,9 @@ class Activity < ActiveRecord::Base :delete_step_comment, :edit_result_comment, :delete_result_comment, - :destroy_result + :destroy_result, + :start_edit_wopi_file, + :unlock_wopi_file ] validates :type_of, presence: true diff --git a/app/utilities/wopi_util.rb b/app/utilities/wopi_util.rb index 01b4fa272..844936efa 100644 --- a/app/utilities/wopi_util.rb +++ b/app/utilities/wopi_util.rb @@ -70,4 +70,42 @@ module WopiUtil discovery = WopiDiscovery.first discovery.destroy if discovery end + + def create_wopi_file_activity(current_user, started_editing) + if @assoc.class == Step + activity = Activity.new( + type_of: :start_edit_wopi_file, + user: current_user, + message: t( + started_editing ? 'activities.start_edit_wopi_file_step' : + 'activities.unlock_wopi_file_step', + user: current_user.full_name, + file: @asset.file_file_name, + step: @asset.step.position + 1, + step_name: @asset.step.name + ) + ) + + if @protocol.in_module? + activity.my_module = @protocol.my_module + activity.project = @protocol.my_module.experiment.project + end + + activity.save + else + Activity.create( + type_of: :start_edit_wopi_file, + user: current_user, + project: @my_module.experiment.project, + my_module: @my_module, + message: t( + started_editing ? 'activities.start_edit_wopi_file_result' : + 'activities.unlock_wopi_file_result', + user: current_user.full_name, + file: @asset.file_file_name, + result: @asset.result.name + ) + ) + end + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 2a91459c1..5eaf22013 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1052,6 +1052,10 @@ en: delete_step_comment: "%{user} deleted comment on Step %{step} %{step_name}." edit_result_comment: "%{user} edited comment on result %{result}." delete_result_comment: "%{user} deleted comment on result %{result}." + start_edit_wopi_file_step: "%{user} started editing File %{file} on Step %{step} %{step_name}." + start_edit_wopi_file_result: "%{user} started editing File %{file} on Result %{result}." + unlock_wopi_file_step: "%{user} closed File %{file} for editing on Step %{step} %{step_name}." + unlock_wopi_file_result: "%{user} started editing File %{file} on Result %{result}." user_my_modules: new: From a6ebff06a7968c8455276f132ea9afc93c687257 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Fri, 30 Sep 2016 09:59:03 +0200 Subject: [PATCH 21/54] Add error message to wopi discovery Fix ambigious open call --- app/models/asset.rb | 2 +- app/utilities/wopi_util.rb | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/models/asset.rb b/app/models/asset.rb index 6ec1964b2..aa583e160 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -338,7 +338,7 @@ class Asset < ActiveRecord::Base def favicon_url(action) file_ext = file_file_name.split('.').last action = get_action(file_ext, action) - action.wopi_app.icon if action.wopi_app + action.wopi_app.icon if action.try(:wopi_app) end # locked?, lock_asset and refresh_lock rely on the asset diff --git a/app/utilities/wopi_util.rb b/app/utilities/wopi_util.rb index 844936efa..b508856c7 100644 --- a/app/utilities/wopi_util.rb +++ b/app/utilities/wopi_util.rb @@ -33,7 +33,7 @@ module WopiUtil Rails.logger.warn 'Initializing discovery' discovery.destroy if discovery - @doc = Nokogiri::XML(open(ENV['WOPI_DISCOVERY_URL'])) + @doc = Nokogiri::XML(Kernel.open(ENV['WOPI_DISCOVERY_URL'])) discovery = WopiDiscovery.new discovery.expires = Time.now.to_i + DISCOVERY_TTL @@ -65,8 +65,9 @@ module WopiUtil end end discovery - rescue - Rails.logger.warn 'Initialization failed' + rescue => e + Rails.logger.warn 'WOPI: initialization failed: ' + e.message + e.backtrace.each { |line| Rails.logger.error line } discovery = WopiDiscovery.first discovery.destroy if discovery end From a2d6d9a29ac07bf3bb99e12f0fe93265cbf35b5b Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Sat, 1 Oct 2016 11:04:43 +0200 Subject: [PATCH 22/54] Add WOPI controlls to result --- app/controllers/wopi_controller.rb | 4 ++-- app/views/results/_result_asset.html.erb | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index edc6c6359..418af521d 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -277,8 +277,8 @@ class WopiController < ActionController::Base @can_read = can_view_steps_in_protocol(@protocol) @can_write = can_edit_step_in_protocol(@protocol) else - @can_read = can_view_or_download_result_assets(@module) - @can_write = can_edit_result_asset_in_module(@module) + @can_read = can_view_or_download_result_assets(@my_module) + @can_write = can_edit_result_asset_in_module(@my_module) end render nothing: :true, status: 404 and return unless @can_read diff --git a/app/views/results/_result_asset.html.erb b/app/views/results/_result_asset.html.erb index 528342b3d..9231879c7 100644 --- a/app/views/results/_result_asset.html.erb +++ b/app/views/results/_result_asset.html.erb @@ -3,6 +3,13 @@ <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %>

<%= truncate(result.asset.file_file_name, length: 50) %>

<% end %> + <% if result.asset.can_perform_action("view") %> + <%= link_to "View", view_asset_url(id: result.asset) %> + <% end %> + <% if can_edit_result_asset_in_module(result.my_module) && + result.asset.can_perform_action("edit") %> + <%= link_to "Edit", edit_asset_url(id: result.asset) %> + <% end %> <% else %> <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %>

<%= result.asset.file_file_name %>

From 5c9e995e56bfea00774e062e56de68121e64feaf Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Sat, 1 Oct 2016 13:23:04 +0200 Subject: [PATCH 23/54] Don't delete asset on locked --- app/models/asset.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/asset.rb b/app/models/asset.rb index aa583e160..f4f27ab6d 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -25,6 +25,7 @@ class Asset < ActiveRecord::Base do_not_validate_attachment_file_type :file before_file_post_process :allow_styles_on_images + before_destroy :check_if_locked # Asset validation # This could cause some problems if you create empty asset and want to @@ -452,4 +453,8 @@ class Asset < ActiveRecord::Base self.file = data end + + def check_if_locked + return false if locked? + end end From d9fa882d6ad0d497b13cf094888b60471001b26e Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Sat, 1 Oct 2016 14:51:55 +0200 Subject: [PATCH 24/54] Add check for locked files on step delete --- app/controllers/steps_controller.rb | 51 +++++++++++++++++------------ app/models/step.rb | 4 +++ app/views/steps/_step.html.erb | 2 +- config/locales/en.yml | 1 + 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb index b0d46a564..aa658bd7d 100644 --- a/app/controllers/steps_controller.rb +++ b/app/controllers/steps_controller.rb @@ -227,29 +227,38 @@ class StepsController < ApplicationController end def destroy - # Update position on other steps of this module - @protocol.steps.where("position > ?", @step.position).each do |step| - step.position = step.position - 1 - step.save + if @step.can_destroy? + # Update position on other steps of this module + @protocol.steps.where('position > ?', @step.position).each do |step| + step.position = step.position - 1 + step.save + end + + # Calculate space taken by this step + org = @protocol.organization + previous_size = @step.space_taken + + # Destroy the step + @step.destroy(current_user) + + # Release space taken by the step + org.release_space(previous_size) + org.save + + # Update protocol timestamp + update_protocol_ts(@step) + + flash[:success] = t( + 'protocols.steps.destroy.success_flash', + step: (@step.position + 1).to_s + ) + else + flash[:error] = t( + 'protocols.steps.destroy.error_flash', + step: (@step.position + 1).to_s + ) end - # Calculate space taken by this step - org = @protocol.organization - previous_size = @step.space_taken - - # Destroy the step - @step.destroy(current_user) - - # Release space taken by the step - org.release_space(previous_size) - org.save - - # Update protocol timestamp - update_protocol_ts(@step) - - flash[:success] = t( - "protocols.steps.destroy.success_flash", - step: (@step.position + 1).to_s) if @protocol.in_module? redirect_to protocols_my_module_path(@step.my_module) else diff --git a/app/models/step.rb b/app/models/step.rb index 21323b6df..2ade330ae 100644 --- a/app/models/step.rb +++ b/app/models/step.rb @@ -82,6 +82,10 @@ class Step < ActiveRecord::Base super() end + def can_destroy? + !assets.map(&:locked?).any? + end + def my_module protocol.present? ? protocol.my_module : nil end diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index 2dc3a5e68..07d5ce2e1 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -16,7 +16,7 @@ <% end %> - <% if can_delete_step_in_protocol(@protocol) %> + <% if can_delete_step_in_protocol(@protocol) && step.can_destroy? %> <%= link_to(step_path(step), title: t("protocols.steps.options.delete_title"), method: "delete", class: "btn btn-link", data: {action: "delete-step", confirm: t("protocols.steps.destroy.confirm", step: step.name)}) do %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 5eaf22013..da60b9203 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1444,6 +1444,7 @@ en: destroy: confirm: "Are you sure you want to delete step %{step}?" success_flash: "Step %{step} successfully deleted." + error_flash: "Step %{step} couldn't be deleted. One or more files are locked." edit: head_title: "Edit protocol" no_keywords: "No keywords" From 1e14955ade4fa2493698f113a2bdb97671f3dc4b Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Sat, 1 Oct 2016 18:47:05 +0200 Subject: [PATCH 25/54] Prevent deleting asset on step update --- app/controllers/steps_controller.rb | 7 ++++++- app/models/asset.rb | 5 ----- app/views/steps/_form_assets.html.erb | 8 +++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb index aa658bd7d..7c5e4b0c4 100644 --- a/app/controllers/steps_controller.rb +++ b/app/controllers/steps_controller.rb @@ -563,7 +563,12 @@ class StepsController < ApplicationController for pos, attrs in params[key] do if attrs[:_destroy] == '1' if attrs[:id].present? - attr_params[pos] = { id: attrs[:id], _destroy: '1' } + asset = Asset.find_by_id(attrs[:id]) + if asset.try(&:locked?) + asset.errors.add(:base, 'This file is locked.') + else + attr_params[pos] = { id: attrs[:id], _destroy: '1' } + end end params[key].delete(pos) elsif has_destroy_params(params[key][pos]) diff --git a/app/models/asset.rb b/app/models/asset.rb index f4f27ab6d..aa583e160 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -25,7 +25,6 @@ class Asset < ActiveRecord::Base do_not_validate_attachment_file_type :file before_file_post_process :allow_styles_on_images - before_destroy :check_if_locked # Asset validation # This could cause some problems if you create empty asset and want to @@ -453,8 +452,4 @@ class Asset < ActiveRecord::Base self.file = data end - - def check_if_locked - return false if locked? - end end diff --git a/app/views/steps/_form_assets.html.erb b/app/views/steps/_form_assets.html.erb index 34ad3656a..9792e1da5 100644 --- a/app/views/steps/_form_assets.html.erb +++ b/app/views/steps/_form_assets.html.erb @@ -3,8 +3,10 @@ <%= t("protocols.steps.new.asset_panel_title") %>
- <%= ff.remove_nested_fields_link do %> - + <% unless ff.object.file.exists? && ff.object.locked? %> + <%= ff.remove_nested_fields_link do %> + + <% end %> <% end %>
@@ -23,4 +25,4 @@ <%= ff.file_field :file %> <% end %> - \ No newline at end of file + From f727289935f0f050fc123ca3c59e3e4c2eb96950 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Mon, 3 Oct 2016 13:43:35 +0200 Subject: [PATCH 26/54] Add lock check for editing result --- app/assets/javascripts/results/result_assets.js | 9 ++++++++- app/controllers/result_assets_controller.rb | 14 ++++++++++++++ config/locales/en.yml | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/results/result_assets.js b/app/assets/javascripts/results/result_assets.js index 73aade08e..8639efd60 100644 --- a/app/assets/javascripts/results/result_assets.js +++ b/app/assets/javascripts/results/result_assets.js @@ -67,7 +67,14 @@ function formAjaxResultAsset($form) { reloadImages($imgs); }) .on("ajax:error", function(e, data) { - $form.renderFormErrors("result", data.errors, true, e); + // This check is here only because of remotipart bug, which returns + // HTML instead of JSON, go figure + var errors = ''; + if (data.errors) + errors = data.errors; + else + errors = data.responseJSON.errors; + $form.renderFormErrors("result", errors, true, e); }); } diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb index 80adafce1..f9d25b8af 100644 --- a/app/controllers/result_assets_controller.rb +++ b/app/controllers/result_assets_controller.rb @@ -103,6 +103,7 @@ class ResultAssetsController < ApplicationController def update update_params = result_params previous_size = @result.space_taken + previous_asset = @result.asset if update_params.key? :asset_attributes asset = Asset.find_by_id(update_params[:asset_attributes][:id]) @@ -136,6 +137,19 @@ class ResultAssetsController < ApplicationController elsif @result.archived_changed?(from: true, to: false) render_403 else + if previous_asset.locked? + @result.errors.add(:asset_attributes, + I18n.t('result_assets.edit.locked_file_error')) + respond_to do |format| + format.json do + render json: { + status: 'error', + errors: @result.errors + }, status: :bad_request + return + end + end + end # Asset (file) and/or name has been changed saved = @result.save diff --git a/config/locales/en.yml b/config/locales/en.yml index da60b9203..2e8c8725f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -837,6 +837,7 @@ en: title: "Edit result from task %{module}" uploaded_asset: "Uploaded file" update: "Update file result" + locked_file_error: 'This file is being edited by someone else.' create: success_flash: "Successfully added file result to task %{module}" update: From 03567d620852bdef6b68d9a9da84d7c5de360515 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Mon, 3 Oct 2016 16:49:41 +0200 Subject: [PATCH 27/54] Prevent archive of locked result asset --- app/controllers/result_assets_controller.rb | 10 ++++++++++ app/helpers/results_helper.rb | 8 ++++++++ app/views/my_modules/_result.html.erb | 2 +- app/views/results/_result_asset.html.erb | 14 +++++++------- config/locales/en.yml | 1 + 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb index f9d25b8af..41c4a52bd 100644 --- a/app/controllers/result_assets_controller.rb +++ b/app/controllers/result_assets_controller.rb @@ -118,6 +118,16 @@ class ResultAssetsController < ApplicationController module: @my_module.name) if @result.archived_changed?(from: false, to: true) + if previous_asset.locked? + respond_to do |format| + format.html { + flash[:error] = t('result_assets.archive.error_flash') + redirect_to results_my_module_path(@my_module) + return + } + end + end + saved = @result.archive(current_user) success_flash = t("result_assets.archive.success_flash", module: @my_module.name) diff --git a/app/helpers/results_helper.rb b/app/helpers/results_helper.rb index 9a718ae4c..939c71586 100644 --- a/app/helpers/results_helper.rb +++ b/app/helpers/results_helper.rb @@ -39,6 +39,14 @@ module ResultsHelper end end + def result_unlocked?(result) + if result.is_asset + !result.asset.locked? + else + true + end + end + def result_path_of_type(result) if result.is_asset result_asset_path(result.result_asset) diff --git a/app/views/my_modules/_result.html.erb b/app/views/my_modules/_result.html.erb index 29da2a114..b18c605be 100644 --- a/app/views/my_modules/_result.html.erb +++ b/app/views/my_modules/_result.html.erb @@ -19,7 +19,7 @@ <% end %> - <% if can_archive_result(result) and not result.archived %> + <% if can_archive_result(result) && !result.archived && result_unlocked?(result) %> diff --git a/app/views/results/_result_asset.html.erb b/app/views/results/_result_asset.html.erb index 9231879c7..3391b61ab 100644 --- a/app/views/results/_result_asset.html.erb +++ b/app/views/results/_result_asset.html.erb @@ -3,13 +3,13 @@ <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %>

<%= truncate(result.asset.file_file_name, length: 50) %>

<% end %> - <% if result.asset.can_perform_action("view") %> - <%= link_to "View", view_asset_url(id: result.asset) %> - <% end %> - <% if can_edit_result_asset_in_module(result.my_module) && - result.asset.can_perform_action("edit") %> - <%= link_to "Edit", edit_asset_url(id: result.asset) %> - <% end %> + <% if result.asset.can_perform_action("view") %> + <%= link_to "View", view_asset_url(id: result.asset) %> + <% end %> + <% if can_edit_result_asset_in_module(result.my_module) && + result.asset.can_perform_action("edit") %> + <%= link_to "Edit", edit_asset_url(id: result.asset) %> + <% end %> <% else %> <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %>

<%= result.asset.file_file_name %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 2e8c8725f..e67728e30 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -844,6 +844,7 @@ en: success_flash: "Successfully updated file result in task %{module}" archive: success_flash: "Successfully archived file result in task %{module}" + error_flash: "Couldn't archive file result. Someone is editing that file." destroy: success_flash: "File result successfully deleted." From 1949b1fe8864126f25ea9170f97a76a8153574f3 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Mon, 3 Oct 2016 18:25:01 +0200 Subject: [PATCH 28/54] Add CloseUrl to WOPI --- app/controllers/wopi_controller.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 418af521d..80c606b39 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -63,10 +63,9 @@ class WopiController < ActionController::Base # which should NOT be business LicenseCheckForEditIsEnabled: true, UserFriendlyName: @user.name, - UserCanWrite: @can_write, - UserCanNotWriteRelative: true, - # TODO: decide what to put here - CloseUrl: 'https://scinote-preview.herokuapp.com', + UserCanWrite: @can_write, + UserCanNotWriteRelative: true, + CloseUrl: @close_url, DownloadUrl: url_for(controller: 'assets', action: 'download', id: @asset.id), HostEditUrl: url_for(controller: 'assets', action: 'edit', @@ -276,9 +275,18 @@ class WopiController < ActionController::Base if @assoc.class == Step @can_read = can_view_steps_in_protocol(@protocol) @can_write = can_edit_step_in_protocol(@protocol) + + if @protocol.in_module? + @close_url = protocols_my_module_path(@protocol.my_module, + only_path: false) + else + @close_url = protocols_path(only_path: false) + end else @can_read = can_view_or_download_result_assets(@my_module) @can_write = can_edit_result_asset_in_module(@my_module) + + @close_url = results_my_module_path(@my_module, only_path: false) end render nothing: :true, status: 404 and return unless @can_read From ec82f64df2f660cfabc2f16dac94acac9df12a17 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Mon, 3 Oct 2016 19:45:39 +0200 Subject: [PATCH 29/54] Add WOPI breadcrumbs --- app/controllers/wopi_controller.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 80c606b39..35577c4ec 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -71,9 +71,11 @@ class WopiController < ActionController::Base HostEditUrl: url_for(controller: 'assets', action: 'edit', id: @asset.id), HostViewUrl: url_for(controller: 'assets', action: 'view', - id: @asset.id) - # TODO: breadcrumbs? - #:FileExtension + id: @asset.id), + BreadcrumbBrandName: @breadcrumb_brand_name, + BreadcrumbBrandUrl: @breadcrumb_brand_url, + BreadcrumbFolderName: @breadcrumb_folder_name, + BreadcrumbFolderUrl: @breadcrumb_folder_url } response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL'] response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL'] @@ -279,14 +281,30 @@ class WopiController < ActionController::Base if @protocol.in_module? @close_url = protocols_my_module_path(@protocol.my_module, only_path: false) + + project = @protocol.my_module.experiment.project + @breadcrumb_brand_name = project.name + @breadcrumb_brand_url = project_path(project, only_path: false) + @breadcrumb_folder_name = @protocol.my_module.name else @close_url = protocols_path(only_path: false) + + @breadcrump_brand_name = 'Projects' + @breadcrumb_brand_url = root_path(only_path: false) + @breadcrumb_folder_name = 'Protocol managament' end + @breadcrumb_folder_url = @close_url else @can_read = can_view_or_download_result_assets(@my_module) @can_write = can_edit_result_asset_in_module(@my_module) @close_url = results_my_module_path(@my_module, only_path: false) + + @breadcrumb_brand_name = @my_module.experiment.project.name + @breadcrumb_brand_url = project_path(@my_module.experiment.project, + only_path: false) + @breadcrumb_folder_name = @my_module.name + @breadcrumb_folder_url = @close_url end render nothing: :true, status: 404 and return unless @can_read From 72c5824e67fa46c8479b99da8ef634b076e684b7 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Mon, 3 Oct 2016 20:02:13 +0200 Subject: [PATCH 30/54] Remove navbar for viewing WOPI files --- app/controllers/assets_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 87e084eac..56affc3a6 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -74,6 +74,8 @@ class AssetsController < ApplicationController @token = current_user.get_wopi_token @ttl = (current_user.wopi_token_ttl * 1000).to_s create_wopi_file_activity(current_user, true) + + render layout: false end def view @@ -81,6 +83,8 @@ class AssetsController < ApplicationController @favicon_url = @asset.favicon_url('view') @token = current_user.get_wopi_token @ttl = (current_user.wopi_token_ttl * 1000).to_s + + render layout: false end private From 0c7cd52dba6746b96f0836fd361a27db06761973 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Tue, 4 Oct 2016 12:34:43 +0200 Subject: [PATCH 31/54] Add WOPI icons/buttons to files --- app/helpers/file_icons_helper.rb | 60 ++++++++++++++++++ app/views/result_assets/_edit.html.erb | 5 +- app/views/results/_result_asset.html.erb | 28 ++++++-- app/views/steps/_form_assets.html.erb | 10 ++- app/views/steps/_step.html.erb | 28 ++++++-- config/locales/en.yml | 2 + public/images/office/Excel-color_16x16x32.png | Bin 0 -> 1490 bytes public/images/office/Excel-xlsx_20x20x32.png | Bin 0 -> 1543 bytes .../office/PowerPoint-Color_16x16x32.png | Bin 0 -> 1488 bytes .../office/PowerPoint-pptx_20x20x32.png | Bin 0 -> 1499 bytes public/images/office/Word-color_16x16x32.png | Bin 0 -> 1448 bytes public/images/office/Word-docx_20x20x32.png | Bin 0 -> 1531 bytes 12 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 app/helpers/file_icons_helper.rb create mode 100644 public/images/office/Excel-color_16x16x32.png create mode 100644 public/images/office/Excel-xlsx_20x20x32.png create mode 100644 public/images/office/PowerPoint-Color_16x16x32.png create mode 100644 public/images/office/PowerPoint-pptx_20x20x32.png create mode 100644 public/images/office/Word-color_16x16x32.png create mode 100644 public/images/office/Word-docx_20x20x32.png diff --git a/app/helpers/file_icons_helper.rb b/app/helpers/file_icons_helper.rb new file mode 100644 index 000000000..01721e38b --- /dev/null +++ b/app/helpers/file_icons_helper.rb @@ -0,0 +1,60 @@ +module FileIconsHelper + def wopi_file?(asset) + file_ext = asset.file_file_name.split('.').last + %w(doc docx xls xlsx ppt pptx).include?(file_ext) + end + + # For showing next to file + def file_extension_icon(asset) + file_ext = asset.file_file_name.split('.').last + if %w(doc docx).include?(file_ext) + image_link = 'office/Word-docx_20x20x32.png' + elsif %w(xls xlsx).include?(file_ext) + image_link = 'office/Excel-xlsx_20x20x32.png' + elsif %w(ppt pptx).include?(file_ext) + image_link = 'office/PowerPoint-pptx_20x20x32.png' + end + + if image_link + image_tag image_link + else + '' + end + end + + # For showing in view/edit buttons (WOPI) + def file_application_icon(asset) + file_ext = asset.file_file_name.split('.').last + if %w(doc docx).include?(file_ext) + image_link = 'office/Word-color_16x16x32.png' + elsif %w(xls xlsx).include?(file_ext) + image_link = 'office/Excel-color_16x16x32.png' + elsif %w(ppt pptx).include?(file_ext) + image_link = 'office/PowerPoint-Color_16x16x32.png' + end + + if image_link + image_tag image_link + else + '' + end + end + + # Shows correct WOPI application text (Word Online/Excel ..) + def wopi_button_text(asset, action) + file_ext = asset.file_file_name.split('.').last + if %w(doc docx).include?(file_ext) + app = 'Word Online' + elsif %w(xls xlsx).include?(file_ext) + app = 'Excel Online' + elsif %w(ppt pptx).include?(file_ext) + app = 'PowerPoint Online' + end + + if action == 'view' + t('result_assets.wopi_open_file', app: app) + elsif action == 'edit' + t('result_assets.wopi_edit_file', app: app) + end + end +end diff --git a/app/views/result_assets/_edit.html.erb b/app/views/result_assets/_edit.html.erb index b1bf833fd..68158d989 100644 --- a/app/views/result_assets/_edit.html.erb +++ b/app/views/result_assets/_edit.html.erb @@ -3,7 +3,10 @@ <%= f.text_field :name, style: "margin-top: 10px;" %>
<%= f.fields_for :asset do |ff| %> <%=t "result_assets.edit.uploaded_asset" %> -

<%= ff.object.file_file_name %>

+

+ <%= file_extension_icon(ff.object) %> + <%= ff.object.file_file_name %> +

<%= ff.file_field :file %> <% end %>
diff --git a/app/views/results/_result_asset.html.erb b/app/views/results/_result_asset.html.erb index 3391b61ab..81506ebf5 100644 --- a/app/views/results/_result_asset.html.erb +++ b/app/views/results/_result_asset.html.erb @@ -1,16 +1,36 @@ <% if can_view_or_download_result_assets(result.my_module) %> <%= link_to download_asset_path(result.asset), data: {no_turbolink: true} do %> <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %> -

<%= truncate(result.asset.file_file_name, length: 50) %>

+ <% if wopi_file?(result.asset) %> +

+ <%= file_extension_icon(result.asset) %> + <%= truncate(result.asset.file_file_name, length: 50) %> +

+ <% else %> +

<%= truncate(result.asset.file_file_name, length: 50) %>

+ <% end %> <% end %> <% if result.asset.can_perform_action("view") %> - <%= link_to "View", view_asset_url(id: result.asset) %> + <%= link_to view_asset_url(id: result.asset), + class: 'btn btn-default btn-sm', + style: 'display: inline-block' do %> + <%= file_application_icon(result.asset) %> + <%= wopi_button_text(result.asset, 'view') %> + <% end %> <% end %> <% if can_edit_result_asset_in_module(result.my_module) && result.asset.can_perform_action("edit") %> - <%= link_to "Edit", edit_asset_url(id: result.asset) %> + <%= link_to edit_asset_url(id: result.asset), + class: 'btn btn-default btn-sm', + style: 'display: inline-block' do %> + <%= file_application_icon(result.asset) %> + <%= wopi_button_text(result.asset, 'edit') %> + <% end %> <% end %> <% else %> <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %> -

<%= result.asset.file_file_name %>

+

+ <%= file_extension_icon(result.asset) %> + <%= truncate(result.asset.file_file_name, length: 50) %> +

<% end %> diff --git a/app/views/steps/_form_assets.html.erb b/app/views/steps/_form_assets.html.erb index 9792e1da5..8dc1cd0d8 100644 --- a/app/views/steps/_form_assets.html.erb +++ b/app/views/steps/_form_assets.html.erb @@ -15,11 +15,17 @@ <% if can_view_or_download_step_assets(@protocol) %> <%= link_to download_asset_path(ff.object), data: {no_turbolink: true} do %> <%= image_tag(preview_asset_path ff.object) if ff.object.is_image? %> -

<%= ff.object.file_file_name %>

+

+ <%= file_extension_icon(ff.object) %> + <%= ff.object.file_file_name %> +

<% end %> <% else %> <%= image_tag(preview_asset_path ff.object) if ff.object.is_image? %> -

<%= ff.object.file_file_name %>

+

+ <%= file_extension_icon(ff.object) %> + <%= ff.object.file_file_name %> +

<% end %> <% else %> <%= ff.file_field :file %> diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index 07d5ce2e1..483ab23da 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -71,21 +71,41 @@ <% if asset.file_present %> <%= link_to download_asset_path(asset), data: {no_turbolink: true, id: true, status: "asset-present"} do %> <%= image_tag preview_asset_path(asset) if asset.is_image? %> -

<%= truncate(asset.file_file_name, length: 50) %>

+ <% if wopi_file?(asset) %> +

+ <%= file_extension_icon(asset) %> + <%= truncate(asset.file_file_name, length: 50) %> +

+ <% else %> +

<%= truncate(asset.file_file_name, length: 50) %>

+ <% end %> <% end %> <% if asset.can_perform_action("view") %> - <%= link_to "View", view_asset_url(id: asset) %> + <%= link_to view_asset_url(id: asset), + class: 'btn btn-default btn-sm', + style: 'display: inline-block' do %> + <%= file_application_icon(asset) %> + <%= wopi_button_text(asset, 'view') %> + <% end %> <% end %> <% if can_edit_step_in_protocol(@protocol) && asset.can_perform_action("edit") %> - <%= link_to "Edit", edit_asset_url(id: asset) %> + <%= link_to edit_asset_url(id: asset), + class: 'btn btn-default btn-sm', + style: 'display: inline-block' do %> + <%= file_application_icon(asset) %> + <%= wopi_button_text(asset, 'edit') %> + <% end %> <% end %> <% else %> <%= asset_loading_span(asset) %> <% end %> <% else %> <%= image_tag preview_asset_path(asset) if asset.is_image? %> -

<%= truncate(asset.file_file_name, length: 50) %>

+

+ <%= file_extension_icon(asset) %> + <%= truncate(asset.file_file_name, length: 50) %> +

<% end %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index e67728e30..f1484e517 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -847,6 +847,8 @@ en: error_flash: "Couldn't archive file result. Someone is editing that file." destroy: success_flash: "File result successfully deleted." + wopi_open_file: "Open in %{app}" + wopi_edit_file: "Edit in %{app}" result_tables: new: diff --git a/public/images/office/Excel-color_16x16x32.png b/public/images/office/Excel-color_16x16x32.png new file mode 100644 index 0000000000000000000000000000000000000000..5d4f705c4029814368d624189d9c0a97c254e492 GIT binary patch literal 1490 zcmaJ>ZEO=|9KQxLfo)+944eC~^9Ukw?Y*?!dY2W}URxNU+uW9{(a3ted)i)h?VWem zt;~e2H0TzG3_>E1&_pwX`=FUAi83W@7LDKw24gS=c~Rp`)XcZANH*$oT|4%HaLIG` z+;hL*@Betf0%ly>X0V zXaS=4Ik9D@0{#w^l~sV+ErgjT8VR(Kw2)R?Q&Up|N)aT16BJHTX0nN~QVc<$#S4Sh zR3XfCdVED&@Xd)ubzNa_d|+U}GGMjHY6K@~nl>~j$_x=^ZGT+nlIFO!rKI2i8n23q zF3NG#P~<{#LU&@&)8!Ciir>E?9M_78f+@q3oPv`U0*}RvxC&@Z?*#YVcpzHq-md_> z6KHZm@L_!o8yN4niG-ac$ZlA|Zk~C>^IqWpyv66Pe!8#0MhQMkfJh2Fn>$Z}PMwiXX z(moqWdP%pViFA8mwS2VOkSuXqy zvVyW*cC<%~3-W+wRI%tMP{n>A_*6ND7Rtbg53ugFu@0Y|rM(1PB#heUV`+zvg~8ix zByGcrTwztZN>Njk&F*%)NGr+O4X%r#S%=F8L$^C@Fmo6Voe_9O7j+2~`_m=q#hoM? zJ;F#_JOYzp7C@LwNIJHoM^=Sif^5c(=95H-F6@?x_$s-=@v3euA>jxZ+m+daFDugv z;&5bDIGZ2bRYwrSlxp?3x|83Bq-ZpVq`WuV6>2f^_1*mZ1u~b^m<8OSt?U1XtEq(jPT;0WUE#AGaaZBgUS6s{d z00IX>b-CZJ%-8JssCw=6@U!QxmY+D5n%p#&`n7&+ss3^p$TUD9 I>kc0L9~hPn1poj5 literal 0 HcmV?d00001 diff --git a/public/images/office/Excel-xlsx_20x20x32.png b/public/images/office/Excel-xlsx_20x20x32.png new file mode 100644 index 0000000000000000000000000000000000000000..b6f6833725fb08328b0091223398c0dfd52d296b GIT binary patch literal 1543 zcmaJ>eQXnD7{5}$xfCNGF&oNu90t+R-pBfNXS?m%>!4;GrIf4)=&pBfyUW_UdUv(m zBALUuWTN80WI?8b1S2ugp!gZbVDe!J3jT{*rXv{q7~w0DAQ*hFTgU!DFS&c4_s#SB z{hsG}pXYkR&1)8vFD*w9WI=E(7lG>%<1L#Hzi;#|TL70zJrL73C<#5ss{mm|B@WPF znooiV;Khy?P6H2un17R^F+CP)Vgw~^;|(8MKAnMR1o718GrZ6abTkfXC(l5FG|+i8pH9gdllNjpUIyC6F^-`{ z7rot!O$8MTg;Bqv0@Pt6tODUAP$y|4Ywa$VYc)y{B!LqYPEuCV#ne&^L7>wQ2GvwC z!9=*mX)U<(Vr{yfVQ@T`%h__ZHbqV1Bu&!>2Sr(-hgIv4bv|#EwG|}>4rqcZWpqiA zQG=0>D_Px(!APf4NM}N!8Dd$RE)*;op64?-X(RA-+Q_Tut?3bPFO2(oYtfDjz#~9Y zvZ?^@Be9|c7KIs|Ojb=5!xse{q(BB_yVk>gbm7?&dUNYL=CKT4@>+nmhr2A*A+FYD5+AbgxeHd(b|*@ z>fhi%TO?Uja+-0AQ;Bjwm0kp5qpGCQ;x#bRebikAg)nVJLtJTa$f^3S8D=w9T+ zw}rp=R21gMFI@QJ625hEF14+Dcu`O9VYDW)`r41l$l}P}L+uY9-<`uN-y7;Wow@w` z)}C|q!_?T7^WFR^yn3*r5cYewg~Q!P&K(R-4p*=G06FP46^?B>oEW+Fl4r=WaD89v z&5t&JSur#=wwUG1)&1tL$IqVqe*57Y*KgFU3=I?zb)qY77j94t()skVs-C__p0lKv zcUW#-?zQY3uS>dP^dLX_{&CB@n=g$xKC6zEC0m#i!c`WjI&)@S%KYft6BXsofx#X9 z{HfuMk3V33W_3>9`yJu<;+>JDZA1Z7AMIk-TeNw$x}NS)TU8=|@vi~4Iksprt~c!J z=O@apKUDygZ}Q?v&)BPX20WHu$AiCQ%#~&FSGM#{rgseuOe{Hd_1b8%r}Fmlg{IdY zdu#v3_LY4X!B^e;);2U_`!4$X`g=_$I&UGLoZopcHb3#?8ZZA=?w96#0sH&*6YmLR%~^QLGb^zLcz=-o*NeWB2VG6E>X5$W~rfnK=wy6aWC z#SAtg?nRxtshBN_8Cjw(!X^o3xG5Qf>7vf)mn|_kjM)@3e8c&nSf48`TB}z9?ZiqEZQ=o;?a;dg4Ps774MN-( z7CK=g6r^1{zkn4OX1XCax9Y9_I$l)67Qw(+65%LfV^~FHA}WYoP{)I?Q;s-@`SH&P zT$UU}3*)E!Q8x_9wYxRgw7b4p+}$N|5>dGguSoDnK^W=+o(Ljcby? z(-6JOK`c4d>Tkf^ss?e!LY0YBIfa)43$W34yS)^*Qh*{UD+#P+z|PyOJVoJ|hd|ae zse^Cycr&)p$w7p4J<5}0cXzj?+h$RarTa4UIxjjq45qdAbxrIO_K=D~`l6i9#tO6GD^(7K#jqjkwb2nBEBQxN%o>ta(=y zl8rE?#x)Vuqho89jBJys$^TGA*AYR zETl$pcN2rR$Pr2Hju}?9sht89q|tnejL>OmiAb(cm%d)ntyv@*0YhA#J#<)} zUKl|mtD)K4KDTZUhUN75JkI9CwV)CT1+cxZ9=Xh=Y*Yv&Q))6**=Jd8x_Y(>JdA%e zGEvh1TF%jod40NX zhq+GKgbVd0bW_ng{tv`|wOfz(^`0y2>peoY-SWP)`9i9vr|A5%+b;e-G`6Z>|E1hy zYVp|hAO3m6JTpHN{1Ke#2)y-r&CYV0o$PO+T>US!@84S-dUAF0lfwlvKX7W@hdF~)XWO@!%!g(IYSrw9 zRiov{O%LtcG%_Gy=ArqqVPdY&6gVop{Pdaca=iHYwky+7$M;8XK9Wp*qpn{#6e{cu zV)@B^kN&)X7jF2nZm#si$c-uHWPx+!H#2hyI~ad&p!nShEGHLR_sp?^uV1WLWBg-% M)%Bi_T!DT61HnKC!~g&Q literal 0 HcmV?d00001 diff --git a/public/images/office/PowerPoint-pptx_20x20x32.png b/public/images/office/PowerPoint-pptx_20x20x32.png new file mode 100644 index 0000000000000000000000000000000000000000..2fdb3cb5fdf88330e627eafdfbd92db59fbbb82f GIT binary patch literal 1499 zcmaJ>eM}Q)7(XoOn1Z0P4Hlf2BN-!<-bZ`1ccDPrE7*-cv%W=X)04ak5VxpA z0P2tO?H~krapy}Dpc+9;*QIc?-W+IT1tn_b4U9DvjYBqqRM)2BywC-7Gy>YCmcJ14JP11s&4H9u;iqX$lKHT>>LwwKZsUiiHNEK2C0^aWQgtV7r1ERH9WNo&$(Rn&HzWEjS3pg(rj=$aa6l7O zDXvRO3^gqB5hbC!FsO7PglIevSTY>b783VF?#csB$_^QNV!=q9CSgP!yR~1eLR!R0$p+FLR2J zFuwXZ7nVp!B1>}=;bbT~WAoH=9+G58yW2@K1ZN{@!s&Gy&I|*~iEzg%LayCM+o&oZ z$8emJBz>gQ#}RI~*F!s;1m|X$4A&pibUr2k!>PfQ?sL5l<+5HC@VcUg6-CaJO0YxG z6|F;wquwnvx=o6SN>VdUaZx8wrEVbBt4b729|J2rz`KvK*W0~bilZ6MN5ZIW9&f$f zMiO3!kM!7~@&&HAEM1wXIm$y)l-tYr7>~hqdkA{%5y=Lw9KUj_=;fcj{O0tTC(6y#_rXmY3d60l z$B?{>`QZq3;ep)FH>aqaAA5*h&XcCnlA$b1BabMb-?=kGZdY#2&8_J^6#BQCxZ8O_?wdXS z*$n;d^z{2q{MC_x!83Jz<>lpTstPvL<>T@zH;*)gEha5*``AHC!!yN0SHZ5|@-Ln? zjkeWBTE=rKT6?E|c_d^$Q8OQS{oKSR(Q*i?T^1# eHS{R5A{Y7di=1cwDCn9pez$&K6Zfg7b?^VjGYGH% literal 0 HcmV?d00001 diff --git a/public/images/office/Word-color_16x16x32.png b/public/images/office/Word-color_16x16x32.png new file mode 100644 index 0000000000000000000000000000000000000000..a6b7889c868d53c0fe571e7b2f0f01c3ff23111c GIT binary patch literal 1448 zcmaJ>ZDtTQf z;$jr^0#P1%>pEyb5ZgVaKWGNKIyp&?J46fP$ix$njUX+1GYL@|1ST2=QAP7$e_olv zP(}7&156jumEb{4=@>RZ-*9)oG(0G=GPbuBZOL#@K^&MOnu)7glFN9ooL&yf)-{fy zIfyyv!PcD$cJ-jVZUB^V5O#@ZCeUWmK{{!d%k?r!5hQ^V6i!lh(#1I`jv&zdgF$PC z9N~Hef8G{6d9aviCO8~Vr_+wK)1eztoMc(n(m_#nh_EMzG*isjwPaI4g8-6}p(IR2 z*HBBN7}is!2ZNrjhY(M6b!{ltlKDizl;IgMfs+mbkH@XJa_FSl3m&`iM0B!$C;{+Z zkknI#1nUuLDv)9Bo^GfBl%)+^B4wz#IAsY3Du@HkOhTu~oJY`e?tr1d0*b1jODXGX zK=5Fxlp=GCKoM@1qFJZUFZf83C26mlVF|%WGK8CVTgEH}n~CtoAzNJ9D>S=kfoJ^; zNw$-2N)WsZ?ep_4H^Dah3*3N~G(}AUmQjnVJm&IG<#N0ML{m5VbzLpAN>5BT^<+#> zpnM;L4k((er;}C{^L_$Dc^k-nLyx1mGH}WhthY0~+t2W9I{_C7qxSoG*6rtE@C;3| zG?wSeo6=Q?nxbgN=ktqLB$y4>}qZY?0;2w39A?7_pv^a2fz ztN~}UsIB%sf)ot|1aE)l=dc=!g^ur;( z$0v%mgkC%J`nx-9wjRc8pl57XxlwzZro=_sbQ6-{31XkW~(J^bK1BBJf7 zv3)#ps^rqC6YmA9xo1ARJYA|*)zmc2Ubt{9G&(oeSAX~Bvki3Z(T2&%{rheg&%QWX zj;%c!JG!;5?4{GqcP7H2sI=!sNdjMPT^af2R?kO^H&^aeH9q*Z>g>!~ICdso8%4wQ ziC-6ADc!nd_xM*AcT!g~G3Tc@D#&Fvbv9FXx47}-_BXFi#eN6lR}TE*{Biqra_R5o z7ZytH{4p~+y=0&L;gCMEFk!pKcyy5s+PADsVWeku|jL2O9>>W6Q1 T-hS?c_3sR{cMFrg(2@TEuifxU literal 0 HcmV?d00001 diff --git a/public/images/office/Word-docx_20x20x32.png b/public/images/office/Word-docx_20x20x32.png new file mode 100644 index 0000000000000000000000000000000000000000..69e8ab3d179832311f8601dde0d912721be544b1 GIT binary patch literal 1531 zcmaJ>eQXnD7{86N0Bd2!i5m(WhsYn$-bZ`4-euir?^@Sn-O4uIAPBqOz1z;Uz4Lmd zEioAxUrzZL1_govF)Ds#86jfK5}9tua6w31TvYs+5rxDE_(5i1!S}j$><_{vcklDQ z_xnBH&)XfUeabp-(L4k}tbuBw9#EY&Y%~XJI7yA#MH6FBMK#Dqkcz65CQ2Q^K*OL-iMg@s{bw*# zk=5y0%tExmRQXEte1%`;GqLEmfOS!R(UJlCUF^-`b zh|%H3rkrXHhEQJB0ZQ8mn?x`K%8+)_;VdsNUyV`(N#F#9la!4t=NuGA5NP(npfz1? z&Xy1Q3^WMKcsN zhMF41u$nO381!^1gs2t_PAiVZvx$N!!&9P$lXe1+M$Nb~=(tf2?zwSabi6UG0lXf> z)r2m=dbBRjkzwv0Y$ykmrD>d&&?A{RWeEon5Ct(K4xJ)19zoB!I$ePU6eEHvCCtA8 z!Hp#nipGZg0mJl2yO}KcMY0OkGlL$}jGUPhFg3C)e8P@Az zNzzAp7!Sn~w4W9jlID4DjvI)@4KXGG)2PW+?s4e{ayecHqM_=Isv60)N~m2m)Ofq9 zp?m|4HYqV#O~%bCX8i=ZvKh#JU5%odGH}X$tb4q?&p|VMxxjjTBuTnFq)*@(pNDpl zK9=!dS*|=IUAd@v%I7F2d?f3i;(BQ(<71o*LkNDBr)Ug^&d3sHC`JTi`_mIKvQk7b zdxVRKu{M|tn+#gTM8v=JZgyWpf$)qp#l&am%N);W*BlbgfGJK7AKXlj zFNndJ)!}fiJ@*p4De~S62%g5&ci}sC?^eyCLTf`c5<34*{x27jOD=c5jLoBp%T|r7 zdTVlJzVn;0pJu-wM8cvxS!0p&^YZfE{`;VPbN=B^*KC?QIJB=-!)UwLX+h?_Onj8FZ-Kn3SyLi++($f?|3#rji-O`fAmg}bw>$eLI zG;IwRK9gR#^IFHnaN_Cpi?CGZ3%3j3N&hzyvVhU+loj!Sz9Ne{F z>7TaseSL?Ym^C-=jiRqV6Nk@Tn7?r1pMO`@GIP-W;-=76Yt1c3%cc4fP#yjL^Gipr ze>re&@QdP&AMHI}d3Nvk`48$WlOuKE_MzDWg-g!-wSC#px`+S3Y%3n>+rGPW>9!rc zPX6Xlhjv!zzcF;>goQZRwV@#Y_?n`gE7kh4n&rcn6J$cu~Lz2bec<@Jp zP!ktNii)Uhd&(XUmXy{wR|N2}VOz_|yq=8 Date: Tue, 4 Oct 2016 18:00:08 +0200 Subject: [PATCH 32/54] Add wd* params to iframe action url --- app/controllers/assets_controller.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 56affc3a6..1d917ae53 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -69,7 +69,8 @@ class AssetsController < ApplicationController end def edit - @action_url = @asset.get_action_url(current_user, 'edit', false) + @action_url = append_wd_params(@asset + .get_action_url(current_user, 'edit', false)) @favicon_url = @asset.favicon_url('edit') @token = current_user.get_wopi_token @ttl = (current_user.wopi_token_ttl * 1000).to_s @@ -79,7 +80,8 @@ class AssetsController < ApplicationController end def view - @action_url = @asset.get_action_url(current_user, 'view', false) + @action_url = append_wd_params(@asset + .get_action_url(current_user, 'view', false)) @favicon_url = @asset.favicon_url('view') @token = current_user.get_wopi_token @ttl = (current_user.wopi_token_ttl * 1000).to_s @@ -166,6 +168,15 @@ class AssetsController < ApplicationController posts end + def append_wd_params(url) + wd_params = '' + params.keys.select { |i| i[/^wd.*/] }.each do |wd| + next if wd == 'wdPreviousSession' || wd == 'wdPreviousCorrelation' + wd_params += "&#{wd}=#{params[wd]}" + end + url + wd_params + end + def asset_params params.permit( :file From 85d2370d286f31b57e190f7c15dea4cc37e16610 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Tue, 4 Oct 2016 18:19:36 +0200 Subject: [PATCH 33/54] Change some values to string in CheckFileInfo --- app/controllers/wopi_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 35577c4ec..9c1007bdf 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -53,8 +53,8 @@ class WopiController < ActionController::Base BaseFileName: @asset.file_file_name, OwnerId: @asset.created_by_id.to_s, Size: @asset.file_file_size, - UserId: @user.id, - Version: @asset.version, + UserId: @user.id.to_s, + Version: @asset.version.to_s, SupportsExtendedLockLength: true, SupportsGetLock: true, SupportsLocks: true, From 62356c36a1e4dd38e24ee26161b83dd0a1540897 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Tue, 4 Oct 2016 19:04:08 +0200 Subject: [PATCH 34/54] Add file extension to global search --- app/views/search/results/_assets.html.erb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/search/results/_assets.html.erb b/app/views/search/results/_assets.html.erb index e8589e92b..21da59d6f 100644 --- a/app/views/search/results/_assets.html.erb +++ b/app/views/search/results/_assets.html.erb @@ -3,7 +3,11 @@ <% if asset.is_image? %> <% else %> - + <% if wopi_file?(asset) %> + <%= file_extension_icon(asset) %> + <% else %> + + <% end %> <% end %> <%= render partial: "search/results/partials/asset_text.html.erb", locals: { asset: asset, query: search_query } %> From b52e596209b848558f258f7bc17774135332adc0 Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 6 Oct 2016 14:05:02 +0200 Subject: [PATCH 35/54] Update protocol timestamp on put_file SCI-551 #close --- app/controllers/wopi_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 9c1007bdf..3bc46e148 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -207,6 +207,8 @@ class WopiController < ActionController::Base @organization.take_space(@asset.estimated_size) @organization.save + @protocol.update(updated_at: Time.now) if @protocol + response.headers['X-WOPI-ItemVersion'] = @asset.version render nothing: :true, status: 200 and return else From 1f67aec258a2963f8fe6d869470cdccd21ba57cb Mon Sep 17 00:00:00 2001 From: Jure Grabnar Date: Thu, 6 Oct 2016 16:13:35 +0200 Subject: [PATCH 36/54] Prevent destroying protocols when file is edited SCI-552 #close --- .../javascripts/my_modules/protocols.js | 2 + app/controllers/protocols_controller.rb | 256 ++++++++++-------- app/models/protocol.rb | 6 + config/locales/en.yml | 5 + 4 files changed, 160 insertions(+), 109 deletions(-) diff --git a/app/assets/javascripts/my_modules/protocols.js b/app/assets/javascripts/my_modules/protocols.js index ee4c41e55..d023c2883 100644 --- a/app/assets/javascripts/my_modules/protocols.js +++ b/app/assets/javascripts/my_modules/protocols.js @@ -528,6 +528,8 @@ function initImport() { if (data.status === "ok") { // Simply reload page location.reload(); + } else if (data.status === 'locked') { + alert(I18n.t("my_modules.protocols.load_from_file_error_locked")); } else { alert(I18n.t("my_modules.protocols.load_from_file_error")); } diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index 29705c8ba..3f25ee639 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -313,125 +313,157 @@ class ProtocolsController < ApplicationController def revert respond_to do |format| - transaction_error = false - Protocol.transaction do - begin - # Revert is basically update from parent - @protocol.update_from_parent(current_user) - rescue Exception - transaction_error = true - raise ActiveRecord:: Rollback + if @protocol.can_destroy? + transaction_error = false + Protocol.transaction do + begin + # Revert is basically update from parent + @protocol.update_from_parent(current_user) + rescue Exception + transaction_error = true + raise ActiveRecord:: Rollback + end end - end - if transaction_error - # Bad request error - format.json { - render json: { - message: t("my_modules.protocols.revert_error") - }, - status: :bad_request - } + if transaction_error + # Bad request error + format.json do + render json: { + message: t('my_modules.protocols.revert_error') + }, + status: :bad_request + end + else + # Everything good, display flash & render 200 + flash[:success] = t( + 'my_modules.protocols.revert_flash' + ) + flash.keep(:success) + format.json { render json: {}, status: :ok } + end else - # Everything good, display flash & render 200 - flash[:success] = t( - "my_modules.protocols.revert_flash", - ) - flash.keep(:success) - format.json { render json: {}, status: :ok } + format.json do + render json: { + message: t('my_modules.protocols.revert_error_locked') + }, status: :bad_request + end end end end def update_parent respond_to do |format| - transaction_error = false - Protocol.transaction do - begin - @protocol.update_parent(current_user) - rescue Exception - transaction_error = true - raise ActiveRecord:: Rollback + if @protocol.parent.can_destroy? + transaction_error = false + Protocol.transaction do + begin + @protocol.update_parent(current_user) + rescue Exception + transaction_error = true + raise ActiveRecord:: Rollback + end end - end - if transaction_error - # Bad request error - format.json { - render json: { - message: t("my_modules.protocols.update_parent_error") - }, - status: :bad_request - } + if transaction_error + # Bad request error + format.json { + render json: { + message: t("my_modules.protocols.update_parent_error") + }, + status: :bad_request + } + else + # Everything good, display flash & render 200 + flash[:success] = t( + "my_modules.protocols.update_parent_flash", + ) + flash.keep(:success) + format.json { render json: {}, status: :ok } + end else - # Everything good, display flash & render 200 - flash[:success] = t( - "my_modules.protocols.update_parent_flash", - ) - flash.keep(:success) - format.json { render json: {}, status: :ok } + format.json do + render json: { + message: t('my_modules.protocols.update_parent_error_locked') + }, status: :bad_request + end end end end def update_from_parent respond_to do |format| - transaction_error = false - Protocol.transaction do - begin - @protocol.update_from_parent(current_user) - rescue Exception - transaction_error = true - raise ActiveRecord:: Rollback + if @protocol.can_destroy? + transaction_error = false + Protocol.transaction do + begin + @protocol.update_from_parent(current_user) + rescue Exception + transaction_error = true + raise ActiveRecord:: Rollback + end end - end - if transaction_error - # Bad request error - format.json { - render json: { - message: t("my_modules.protocols.update_from_parent_error") - }, - status: :bad_request - } + if transaction_error + # Bad request error + format.json { + render json: { + message: t("my_modules.protocols.update_from_parent_error") + }, + status: :bad_request + } + else + # Everything good, display flash & render 200 + flash[:success] = t( + "my_modules.protocols.update_from_parent_flash", + ) + flash.keep(:success) + format.json { render json: {}, status: :ok } + end else - # Everything good, display flash & render 200 - flash[:success] = t( - "my_modules.protocols.update_from_parent_flash", - ) - flash.keep(:success) - format.json { render json: {}, status: :ok } + format.json do + render json: { + message: t('my_modules.protocols.update_from_parent_error_locked') + }, status: :bad_request + end end end end def load_from_repository respond_to do |format| - transaction_error = false - Protocol.transaction do - begin - @protocol.load_from_repository(@source, current_user) - rescue Exception - transaction_error = true - raise ActiveRecord:: Rollback + if @protocol.can_destroy? + transaction_error = false + Protocol.transaction do + begin + @protocol.load_from_repository(@source, current_user) + rescue Exception + transaction_error = true + raise ActiveRecord:: Rollback + end end - end - if transaction_error - # Bad request error - format.json { - render json: { - message: t("my_modules.protocols.load_from_repository_error") - }, - status: :bad_request - } + if transaction_error + # Bad request error + format.json { + render json: { + message: t("my_modules.protocols.load_from_repository_error") + }, + status: :bad_request + } + else + # Everything good, display flash & render 200 + flash[:success] = t( + "my_modules.protocols.load_from_repository_flash", + ) + flash.keep(:success) + format.json { render json: {}, status: :ok } + end else - # Everything good, display flash & render 200 - flash[:success] = t( - "my_modules.protocols.load_from_repository_flash", - ) - flash.keep(:success) - format.json { render json: {}, status: :ok } + format.json do + render json: { + message: t('my_modules.protocols.load_from_repository_error_locked') + }, status: :bad_request + end end end end @@ -439,29 +471,35 @@ class ProtocolsController < ApplicationController def load_from_file # This is actually very similar to import respond_to do |format| - transaction_error = false - Protocol.transaction do - begin - import_into_existing(@protocol, @protocol_json, current_user) - rescue Exception - transaction_error = true - raise ActiveRecord:: Rollback + if @protocol.can_destroy? + transaction_error = false + Protocol.transaction do + begin + import_into_existing(@protocol, @protocol_json, current_user) + rescue Exception + transaction_error = true + raise ActiveRecord:: Rollback + end end - end - if transaction_error - format.json { - render json: { status: :error }, status: :bad_request - } + if transaction_error + format.json { + render json: { status: :error }, status: :bad_request + } + else + # Everything good, display flash & render 200 + flash[:success] = t( + "my_modules.protocols.load_from_file_flash", + ) + flash.keep(:success) + format.json { + render json: { status: :ok }, status: :ok + } + end else - # Everything good, display flash & render 200 - flash[:success] = t( - "my_modules.protocols.load_from_file_flash", - ) - flash.keep(:success) - format.json { - render json: { status: :ok }, status: :ok - } + format.json do + render json: { status: :locked }, status: :bad_request + end end end end diff --git a/app/models/protocol.rb b/app/models/protocol.rb index 9fc7230a3..30c302941 100644 --- a/app/models/protocol.rb +++ b/app/models/protocol.rb @@ -368,6 +368,8 @@ class Protocol < ActiveRecord::Base end def archive(user) + return nil unless can_destroy? + # Don't update "updated_at" timestamp self.record_timestamps = false @@ -588,6 +590,10 @@ class Protocol < ActiveRecord::Base self.reload end + def can_destroy? + steps.map(&:can_destroy?).all? + end + private def deep_clone(clone, current_user) diff --git a/config/locales/en.yml b/config/locales/en.yml index f1484e517..5586625c1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -545,12 +545,16 @@ en: unlink_error: "Failed to unlink protocol." revert_flash: "Protocol was successfully reverted to protocol version." revert_error: "Failed to revert protocol." + revert_error_locked: "Failed to revert protocol. One or more files in the protocol are currently being edited." update_parent_flash: "Protocol in repository was successfully updated with the version from the task." update_parent_error: "Failed to update repository version of the protocol." + update_parent_error_locked: "Failed to update repository version of the protocol. One or more files in the protocol are currently being edited." update_from_parent_flash: "Version in the repository was successfully updated." update_from_parent_error: "Failed to update the protocol with the version in the repository." + update_from_parent_error_locked: "Failed to update the protocol with the version in the repository. One or more files in the protocol are currently being edited." load_from_repository_flash: "Successfully loaded the protocol from the repository." load_from_repository_error: "Failed to load the protocol from the repository." + load_from_repository_error_locked: "Failed to load the protocol from the repository. One or more files in the protocol are currently being edited." load_from_repository_modal: title: "Load protocol from repository" text: "Choose the protocol to be loaded to the task." @@ -582,6 +586,7 @@ en: confirm: "Copy to repository" load_from_file_flash: "Successfully loaded the protocol from the file." load_from_file_error: "Failed to load the protocol from file." + load_from_file_error_locked: "Failed to load the protocol from file. One or more files are currently being edited." results: head_title: "%{project} | %{module} | Results" add_label: "Add new result:" From 495c648f416e7b356eef11de92fe232517e549d9 Mon Sep 17 00:00:00 2001 From: Nejc Bernot Date: Wed, 30 Nov 2016 16:43:33 +0100 Subject: [PATCH 37/54] Added subdomain based routing constraints Non-wopi routes will only work with the subdomain set by the env variable USER_SUBDOMAIN (if it is set). Likewise, wopi routes will only work with the WOPI_SUBDOMAIN if it is set, but they also require the WOPI_ENABLED to be true. --- app/utilities/subdomain.rb | 23 +++++++++++++++++++++++ config/routes.rb | 8 ++++++++ 2 files changed, 31 insertions(+) create mode 100644 app/utilities/subdomain.rb diff --git a/app/utilities/subdomain.rb b/app/utilities/subdomain.rb new file mode 100644 index 000000000..d44662acb --- /dev/null +++ b/app/utilities/subdomain.rb @@ -0,0 +1,23 @@ +class UserSubdomain + def self.matches?(request) + if ENV['USER_SUBDOMAIN'] + return request.subdomain.present? && request.subdomain = ENV['USER_SUBDOMAIN'] + else + return true + end + end +end + +class WopiSubdomain + def self.matches?(request) + if ENV['WOPI_ENABLED'] == "true" + if ENV['WOPI_SUBDOMAIN'] + return request.subdomain.present? && request.subdomain = ENV['WOPI_SUBDOMAIN'] + else + return true + end + else + return false + end + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index acf7f6c54..bd33047cc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,8 @@ Rails.application.routes.draw do + + require 'subdomain' + + constraints (UserSubdomain) do devise_for :users, controllers: { registrations: "users/registrations", sessions: "users/sessions", invitations: "users/invitations", confirmations: "users/confirmations" } @@ -266,10 +270,14 @@ Rails.application.routes.draw do post 'avatar_signature' => 'users/registrations#signature' end + end + constraints (WopiSubdomain) do + # Office integration get 'wopi/files/:id/contents', to: 'wopi#file_contents_get_endpoint' post 'wopi/files/:id/contents', to: 'wopi#file_contents_post_endpoint' get 'wopi/files/:id', to: 'wopi#file_get_endpoint', as: 'wopi_rest_endpoint' post 'wopi/files/:id', to: 'wopi#post_file_endpoint' + end end From cb03ed8384ccb078acc50c71bf9371ea8013b653 Mon Sep 17 00:00:00 2001 From: Nejc Bernot Date: Wed, 30 Nov 2016 16:48:42 +0100 Subject: [PATCH 38/54] Users can now have multiple tokens A new token is generated whenever an edit or view file is called. Tokens have a TTL of one day. Also enabled the toggling of both wopi and wopi test mode, using environmental variables. --- app/controllers/assets_controller.rb | 10 +++--- app/models/asset.rb | 21 ++++++++---- app/models/token.rb | 8 +++++ app/models/user.rb | 34 ++++++++++++------- ...add_wopi.rb => 20161129111100_add_wopi.rb} | 12 ++++--- db/schema.rb | 11 ++++-- 6 files changed, 66 insertions(+), 30 deletions(-) create mode 100644 app/models/token.rb rename db/migrate/{20160728145000_add_wopi.rb => 20161129111100_add_wopi.rb} (84%) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 1d917ae53..664f116a4 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -72,8 +72,9 @@ class AssetsController < ApplicationController @action_url = append_wd_params(@asset .get_action_url(current_user, 'edit', false)) @favicon_url = @asset.favicon_url('edit') - @token = current_user.get_wopi_token - @ttl = (current_user.wopi_token_ttl * 1000).to_s + tkn = current_user.get_wopi_token + @token = tkn.token + @ttl = (tkn.ttl * 1000).to_s create_wopi_file_activity(current_user, true) render layout: false @@ -83,8 +84,9 @@ class AssetsController < ApplicationController @action_url = append_wd_params(@asset .get_action_url(current_user, 'view', false)) @favicon_url = @asset.favicon_url('view') - @token = current_user.get_wopi_token - @ttl = (current_user.wopi_token_ttl * 1000).to_s + tkn = current_user.get_wopi_token + @token = tkn.token + @ttl = (tkn.ttl * 1000).to_s render layout: false end diff --git a/app/models/asset.rb b/app/models/asset.rb index aa583e160..49b09465b 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -302,10 +302,18 @@ class Asset < ActiveRecord::Base end def can_perform_action(action) - file_ext = file_file_name.split('.').last - action = get_action(file_ext, action) - return false if action.nil? - true + if (ENV['WOPI_ENABLED'] == "true") + file_ext = file_file_name.split('.').last + + if (file_ext=="wopitest" && (!ENV['WOPI_TEST_ENABLED'] || ENV['WOPI_TEST_ENABLED'] == "false")) + return false + end + action = get_action(file_ext, action) + return false if action.nil? + true + else + false + end end def get_action_url(user, action, with_tokens = true) @@ -325,8 +333,9 @@ class Asset < ActiveRecord::Base ) action_url += "WOPISrc=#{rest_url}" if with_tokens - action_url + "&access_token=#{user.get_wopi_token}"\ - "&access_token_ttl=#{(user.wopi_token_ttl * 1000)}" + token = user.get_wopi_token + action_url + "&access_token=#{token.token}"\ + "&access_token_ttl=#{(token.ttl * 1000)}" else action_url end diff --git a/app/models/token.rb b/app/models/token.rb new file mode 100644 index 000000000..c5c22d5e1 --- /dev/null +++ b/app/models/token.rb @@ -0,0 +1,8 @@ +class Token < ActiveRecord::Base + + validates :token, presence: true + validates :ttl, presence: true + + belongs_to :user, foreign_key: 'user_id', class_name: 'User', inverse_of: :tokens + +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 5e67ee19e..7608f6d76 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -84,6 +84,7 @@ class User < ActiveRecord::Base has_many :added_protocols, class_name: 'Protocol', foreign_key: 'added_by_id', inverse_of: :added_by has_many :archived_protocols, class_name: 'Protocol', foreign_key: 'archived_by_id', inverse_of: :archived_by has_many :restored_protocols, class_name: 'Protocol', foreign_key: 'restored_by_id', inverse_of: :restored_by + has_many :tokens, class_name: 'Token', foreign_key: 'user_id', inverse_of: :user # If other errors besides parameter "avatar" exist, # they will propagate to "avatar" also, so remove them @@ -254,23 +255,30 @@ class User < ActiveRecord::Base def self.find_by_valid_wopi_token(token) Rails.logger.warn "WOPI: searching by token #{token}" - User.where('wopi_token = ?', token).first - end - - def token_valid - !wopi_token.nil? && (wopi_token_ttl.zero? || wopi_token_ttl > Time.now.to_i) + User + .joins("LEFT OUTER JOIN tokens ON user_id = users.id") + .where('tokens.token = ?', token) + .where('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i) + .first 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) + # WOPI does not have a good way to request a new token, so a new token should be provided each time this is called, while keeping any old tokens + # as long as they have not yet expired + + tokens = Token.where("user_id = ?", id).distinct + + for token in tokens + if (token.ttl < Time.now.to_i) + token.delete + end + end + + token_string = Devise.friendly_token(20) # WOPI uses millisecond TTLs - self.wopi_token_ttl = (Time.now + 1.days).to_i - save - Rails.logger.warn("WOPI: generating new token #{wopi_token}") - end - Rails.logger.warn("WOPI: returning token #{wopi_token}") + ttl = (Time.now + 1.days).to_i + wopi_token = Token.create(token: token_string, ttl: ttl, user_id: id) + Rails.logger.warn("WOPI: generating new token #{wopi_token.token}") wopi_token end diff --git a/db/migrate/20160728145000_add_wopi.rb b/db/migrate/20161129111100_add_wopi.rb similarity index 84% rename from db/migrate/20160728145000_add_wopi.rb rename to db/migrate/20161129111100_add_wopi.rb index f368bb254..8c89d7efd 100644 --- a/db/migrate/20160728145000_add_wopi.rb +++ b/db/migrate/20161129111100_add_wopi.rb @@ -1,7 +1,5 @@ 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 @@ -28,15 +26,20 @@ class AddWopi < ActiveRecord::Migration t.integer :wopi_app_id, null: false end + create_table :tokens do |t| + t.string :token, null: false + t.integer :ttl, null: false + t.integer :user_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_foreign_key :tokens, :users, column: :user_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 @@ -45,5 +48,6 @@ class AddWopi < ActiveRecord::Migration drop_table :wopi_actions drop_table :wopi_apps drop_table :wopi_discoveries + drop_table :tokens end end diff --git a/db/schema.rb b/db/schema.rb index 3151280b9..4f187fadf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160809074757) do +ActiveRecord::Schema.define(version: 20161129111100) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -569,6 +569,12 @@ ActiveRecord::Schema.define(version: 20160809074757) do t.datetime "file_updated_at" end + create_table "tokens", force: :cascade do |t| + t.string "token", null: false + t.integer "ttl", null: false + t.integer "user_id", null: false + end + create_table "user_my_modules", force: :cascade do |t| t.integer "user_id", null: false t.integer "my_module_id", null: false @@ -640,8 +646,6 @@ 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 @@ -783,6 +787,7 @@ ActiveRecord::Schema.define(version: 20160809074757) do add_foreign_key "tags", "projects" add_foreign_key "tags", "users", column: "created_by_id" add_foreign_key "tags", "users", column: "last_modified_by_id" + add_foreign_key "tokens", "users" add_foreign_key "user_my_modules", "my_modules" add_foreign_key "user_my_modules", "users" add_foreign_key "user_my_modules", "users", column: "assigned_by_id" From c54bd6df82bf14d6eeda90dbdf36180ccbf67f33 Mon Sep 17 00:00:00 2001 From: Nejc Bernot Date: Thu, 1 Dec 2016 13:48:11 +0100 Subject: [PATCH 39/54] Added user ids to wopi tokens --- app/models/user.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7608f6d76..d728c3f8c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -265,16 +265,16 @@ class User < ActiveRecord::Base def get_wopi_token # WOPI does not have a good way to request a new token, so a new token should be provided each time this is called, while keeping any old tokens # as long as they have not yet expired - + tokens = Token.where("user_id = ?", id).distinct for token in tokens if (token.ttl < Time.now.to_i) token.delete end - end - - token_string = Devise.friendly_token(20) + end + + token_string = Devise.friendly_token(20) + "-" + id.to_s # WOPI uses millisecond TTLs ttl = (Time.now + 1.days).to_i wopi_token = Token.create(token: token_string, ttl: ttl, user_id: id) From ef609829132d25e3e5bf948e99dafc663a779b8f Mon Sep 17 00:00:00 2001 From: Nejc Bernot Date: Thu, 1 Dec 2016 14:20:43 +0100 Subject: [PATCH 40/54] Subdomain checks fixed --- app/utilities/subdomain.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/utilities/subdomain.rb b/app/utilities/subdomain.rb index d44662acb..0e4fd1fec 100644 --- a/app/utilities/subdomain.rb +++ b/app/utilities/subdomain.rb @@ -1,7 +1,7 @@ class UserSubdomain def self.matches?(request) if ENV['USER_SUBDOMAIN'] - return request.subdomain.present? && request.subdomain = ENV['USER_SUBDOMAIN'] + return (request.subdomain.present? && request.subdomain == ENV['USER_SUBDOMAIN']) else return true end @@ -12,8 +12,8 @@ class WopiSubdomain def self.matches?(request) if ENV['WOPI_ENABLED'] == "true" if ENV['WOPI_SUBDOMAIN'] - return request.subdomain.present? && request.subdomain = ENV['WOPI_SUBDOMAIN'] - else + return (request.subdomain.present? && request.subdomain == ENV['WOPI_SUBDOMAIN']) + else return true end else From 62353b6c3b4ea1d308e00c03064469fee9515302 Mon Sep 17 00:00:00 2001 From: Luka Murn Date: Tue, 6 Dec 2016 16:33:32 +0100 Subject: [PATCH 41/54] Temporary change token expiration --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index d728c3f8c..84f36ed7a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -276,7 +276,7 @@ class User < ActiveRecord::Base token_string = Devise.friendly_token(20) + "-" + id.to_s # WOPI uses millisecond TTLs - ttl = (Time.now + 1.days).to_i + ttl = (Time.now + 1.hour).to_i wopi_token = Token.create(token: token_string, ttl: ttl, user_id: id) Rails.logger.warn("WOPI: generating new token #{wopi_token.token}") wopi_token From 1eb3ddee5340ce0a6c82c018b6d17077a3f3dc02 Mon Sep 17 00:00:00 2001 From: Nbernot Date: Thu, 8 Dec 2016 11:12:24 +0100 Subject: [PATCH 42/54] Token expiration restored to 1 day. --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 84f36ed7a..2d0249317 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -276,7 +276,7 @@ class User < ActiveRecord::Base token_string = Devise.friendly_token(20) + "-" + id.to_s # WOPI uses millisecond TTLs - ttl = (Time.now + 1.hour).to_i + ttl = (Time.now + 1.day).to_i wopi_token = Token.create(token: token_string, ttl: ttl, user_id: id) Rails.logger.warn("WOPI: generating new token #{wopi_token.token}") wopi_token From e554fdf80597cc61e0df1489eca25b3a916647b6 Mon Sep 17 00:00:00 2001 From: zmagod Date: Wed, 21 Dec 2016 16:52:15 +0100 Subject: [PATCH 43/54] code refactor --- app/controllers/assets_controller.rb | 4 +- app/controllers/protocols_controller.rb | 10 +- app/controllers/result_assets_controller.rb | 2 +- app/models/user.rb | 29 ++- app/utilities/subdomain.rb | 32 ++-- app/views/results/_result_asset.html.erb | 42 ++--- db/migrate/20161129111100_add_wopi.rb | 2 - test/models/wopi_discovery_test.rb | 196 ++++++++++++++++---- 8 files changed, 221 insertions(+), 96 deletions(-) diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 664f116a4..f4c6ed924 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -1,9 +1,9 @@ class AssetsController < ApplicationController include WopiUtil - before_action :load_vars, except: [:signature] + before_action :load_vars, except: :signature before_action :check_read_permission, except: [:signature, :file_present] - before_action :check_edit_permission, only: [ :edit ] + before_action :check_edit_permission, only: :edit # Validates asset and then generates S3 upload posts, because # otherwise untracked files could be uploaded to S3 diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index 3f25ee639..e6f78b938 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -444,17 +444,15 @@ class ProtocolsController < ApplicationController if transaction_error # Bad request error - format.json { + format.json do render json: { - message: t("my_modules.protocols.load_from_repository_error") + message: t('my_modules.protocols.load_from_repository_error') }, status: :bad_request - } + end else # Everything good, display flash & render 200 - flash[:success] = t( - "my_modules.protocols.load_from_repository_flash", - ) + flash[:success] = t('my_modules.protocols.load_from_repository_flash') flash.keep(:success) format.json { render json: {}, status: :ok } end diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb index 41c4a52bd..de672e192 100644 --- a/app/controllers/result_assets_controller.rb +++ b/app/controllers/result_assets_controller.rb @@ -149,7 +149,7 @@ class ResultAssetsController < ApplicationController else if previous_asset.locked? @result.errors.add(:asset_attributes, - I18n.t('result_assets.edit.locked_file_error')) + t('result_assets.edit.locked_file_error')) respond_to do |format| format.json do render json: { diff --git a/app/models/user.rb b/app/models/user.rb index 2d0249317..c3e0bc3cd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -256,26 +256,24 @@ class User < ActiveRecord::Base def self.find_by_valid_wopi_token(token) Rails.logger.warn "WOPI: searching by token #{token}" User - .joins("LEFT OUTER JOIN tokens ON user_id = users.id") - .where('tokens.token = ?', token) - .where('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i) - .first + .joins('LEFT OUTER JOIN tokens ON user_id = users.id') + .where(tokens: { token: token }) + .where('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i) + .first end def get_wopi_token - # WOPI does not have a good way to request a new token, so a new token should be provided each time this is called, while keeping any old tokens - # as long as they have not yet expired + # WOPI does not have a good way to request a new token, + # so a new token should be provided each time this is called, + # while keeping any old tokens as long as they have not yet expired + tokens = Token.where(user_id: id).distinct - tokens = Token.where("user_id = ?", id).distinct + tokens.each do |token| + token.delete if token.ttl < Time.now.to_i + end - for token in tokens - if (token.ttl < Time.now.to_i) - token.delete - end - end - - token_string = Devise.friendly_token(20) + "-" + id.to_s - # WOPI uses millisecond TTLs + token_string = "#{Devise.friendly_token(20)}-#{id}" + # WOPI uses millisecond TTLs ttl = (Time.now + 1.day).to_i wopi_token = Token.create(token: token_string, ttl: ttl, user_id: id) Rails.logger.warn("WOPI: generating new token #{wopi_token.token}") @@ -292,4 +290,3 @@ class User < ActiveRecord::Base end - diff --git a/app/utilities/subdomain.rb b/app/utilities/subdomain.rb index 0e4fd1fec..f7317d446 100644 --- a/app/utilities/subdomain.rb +++ b/app/utilities/subdomain.rb @@ -1,23 +1,25 @@ class UserSubdomain def self.matches?(request) - if ENV['USER_SUBDOMAIN'] - return (request.subdomain.present? && request.subdomain == ENV['USER_SUBDOMAIN']) - else - return true - end + if ENV['USER_SUBDOMAIN'] + return (request.subdomain.present? && + request.subdomain == ENV['USER_SUBDOMAIN']) + else + return true + end end end class WopiSubdomain def self.matches?(request) - if ENV['WOPI_ENABLED'] == "true" - if ENV['WOPI_SUBDOMAIN'] - return (request.subdomain.present? && request.subdomain == ENV['WOPI_SUBDOMAIN']) - else - return true - end - else - return false - end + if ENV['WOPI_ENABLED'] == 'true' + if ENV['WOPI_SUBDOMAIN'] + return (request.subdomain.present? && + request.subdomain == ENV['WOPI_SUBDOMAIN']) + else + return true + end + else + return false + end end -end \ No newline at end of file +end diff --git a/app/views/results/_result_asset.html.erb b/app/views/results/_result_asset.html.erb index 81506ebf5..45ee3d50b 100644 --- a/app/views/results/_result_asset.html.erb +++ b/app/views/results/_result_asset.html.erb @@ -1,31 +1,31 @@ <% if can_view_or_download_result_assets(result.my_module) %> <%= link_to download_asset_path(result.asset), data: {no_turbolink: true} do %> <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %> - <% if wopi_file?(result.asset) %> -

- <%= file_extension_icon(result.asset) %> - <%= truncate(result.asset.file_file_name, length: 50) %> -

- <% else %> -

<%= truncate(result.asset.file_file_name, length: 50) %>

- <% end %> + <% if wopi_file?(result.asset) %> +

+ <%= file_extension_icon(result.asset) %> + <%= truncate(result.asset.file_file_name, length: 50) %> +

+ <% else %> +

<%= truncate(result.asset.file_file_name, length: 50) %>

+ <% end %> <% end %> - <% if result.asset.can_perform_action("view") %> - <%= link_to view_asset_url(id: result.asset), - class: 'btn btn-default btn-sm', - style: 'display: inline-block' do %> + <% if result.asset.can_perform_action('view') %> + <%= link_to view_asset_url(id: result.asset), + class: 'btn btn-default btn-sm', + style: 'display: inline-block' do %> <%= file_application_icon(result.asset) %> - <%= wopi_button_text(result.asset, 'view') %> - <% end %> + <%= wopi_button_text(result.asset, 'view') %> + <% end %> <% end %> <% if can_edit_result_asset_in_module(result.my_module) && - result.asset.can_perform_action("edit") %> - <%= link_to edit_asset_url(id: result.asset), - class: 'btn btn-default btn-sm', - style: 'display: inline-block' do %> - <%= file_application_icon(result.asset) %> - <%= wopi_button_text(result.asset, 'edit') %> - <% end %> + result.asset.can_perform_action('edit') %> + <%= link_to edit_asset_url(id: result.asset), + class: 'btn btn-default btn-sm', + style: 'display: inline-block' do %> + <%= file_application_icon(result.asset) %> + <%= wopi_button_text(result.asset, 'edit') %> + <% end %> <% end %> <% else %> <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %> diff --git a/db/migrate/20161129111100_add_wopi.rb b/db/migrate/20161129111100_add_wopi.rb index 8c89d7efd..c3c15f419 100644 --- a/db/migrate/20161129111100_add_wopi.rb +++ b/db/migrate/20161129111100_add_wopi.rb @@ -1,6 +1,5 @@ class AddWopi < ActiveRecord::Migration def up - add_column :assets, :lock, :string, limit: 1024 add_column :assets, :lock_ttl, :integer add_column :assets, :version, :integer, default: 1 @@ -40,7 +39,6 @@ class AddWopi < ActiveRecord::Migration end def down - remove_column :assets, :lock remove_column :assets, :lock_ttl remove_column :assets, :version diff --git a/test/models/wopi_discovery_test.rb b/test/models/wopi_discovery_test.rb index a3a8f02e7..260a335d9 100644 --- a/test/models/wopi_discovery_test.rb +++ b/test/models/wopi_discovery_test.rb @@ -8,81 +8,211 @@ class WopiDiscoveryTest < ActiveSupport::TestCase test 'Verify X-WOPI-Proof with current discovery proof key1.' do assert @discovery.verify_proof( - 'yZhdN1qgywcOQWhyEMVpB6NE3pvBksvcLXsrFKXNtBeDTPW%2fu62g2t%2fOCWSlb3jUGaz1zc%2fzOzbNgAredLdhQI1Q7sPPqUv2owO78olmN74DV%2fv52OZIkBG%2b8jqjwmUobcjXVIC1BG9g%2fynMN0itZklL2x27Z2imCF6xELcQUuGdkoXBj%2bI%2bTlKM', + 'yZhdN1qgywcOQWhyEMVpB6NE3pvBksvcLXsrFKXNtBeDTPW%2fu62g2t%' \ + '2fOCWSlb3jUGaz1zc%2fzOzbNgAredLdhQI1Q7sPPqUv2owO78olmN74D' \ + 'V%2fv52OZIkBG%2b8jqjwmUobcjXVIC1BG9g%2fynMN0itZklL2x27Z2i' \ + 'mCF6xELcQUuGdkoXBj%2bI%2bTlKM', 635655897610773532, - 'IflL8OWCOCmws5qnDD5kYMraMGI3o+T+hojoDREbjZSkxbbx7XIS1Av85lohPKjyksocpeVwqEYm9nVWfnq05uhDNGp2MsNyhPO9unZ6w25Rjs1hDFM0dmvYx8wlQBNZ/CFPaz3inCMaaP4PtU85YepaDccAjNc1gikdy3kSMeG1XZuaDixHvMKzF/60DMfLMBIu5xP4Nt8i8Gi2oZs4REuxi6yxOv2vQJQ5+8Wu2Olm8qZvT4FEIQT9oZAXebn/CxyvyQv+RVpoU2gb4BreXAdfKthWF67GpJyhr+ibEVDoIIolUvviycyEtjsaEBpOf6Ne/OLRNu98un7WNDzMTQ==', - 'lWBTpWW8q80WC1eJEH5HMnGka4/LUF7zjUPqBwRMO0JzVcnjICvMP2TZPB2lJfy/4ctIstCN6P1t38NCTTbLWlXuE', - 'https://contoso.com/wopi/files/vHxYyRGM8VfmSGwGYDBMIQPzuE+sSC6kw+zWZw2Nyg?access_token=yZhdN1qgywcOQWhyEMVpB6NE3pvBksvcLXsrFKXNtBeDTPW%2fu62g2t%2fOCWSlb3jUGaz1zc%2fzOzbNgAredLdhQI1Q7sPPqUv2owO78olmN74DV%2fv52OZIkBG%2b8jqjwmUobcjXVIC1BG9g%2fynMN0itZklL2x27Z2imCF6xELcQUuGdkoXBj%2bI%2bTlKM' + 'IflL8OWCOCmws5qnDD5kYMraMGI3o+T+hojoDREbjZSkxbbx7XIS1Av85' \ + 'lohPKjyksocpeVwqEYm9nVWfnq05uhDNGp2MsNyhPO9unZ6w25Rjs1hDF' \ + 'M0dmvYx8wlQBNZ/CFPaz3inCMaaP4PtU85YepaDccAjNc1gikdy3kSMeG' \ + '1XZuaDixHvMKzF/60DMfLMBIu5xP4Nt8i8Gi2oZs4REuxi6yxOv2vQJQ5' \ + '+8Wu2Olm8qZvT4FEIQT9oZAXebn/CxyvyQv+RVpoU2gb4BreXAdfKthWF' \ + '67GpJyhr+ibEVDoIIolUvviycyEtjsaEBpOf6Ne/OLRNu98un7WNDzMTQ==', + 'lWBTpWW8q80WC1eJEH5HMnGka4/LUF7zjUPqBwRMO0JzVcnjICvMP2TZP' \ + 'B2lJfy/4ctIstCN6P1t38NCTTbLWlXuE', + 'https://contoso.com/wopi/files/vHxYyRGM8VfmSGwGYDBMIQPzuE' \ + '+sSC6kw+zWZw2Nyg?access_token=yZhdN1qgywcOQWhyEMVpB6NE3pv' \ + 'BksvcLXsrFKXNtBeDTPW%2fu62g2t%2fOCWSlb3jUGaz1zc%2fzOzbNgA' \ + 'redLdhQI1Q7sPPqUv2owO78olmN74DV%2fv52OZIkBG%2b8jqjwmUobcj' \ + 'XVIC1BG9g%2fynMN0itZklL2x27Z2imCF6xELcQUuGdkoXBj%2bI%2bTlKM' ) end test 'Verify X-WOPI-Proof with current discovery proof key2.' do assert @discovery.verify_proof( - 'RLoY%2f3D73%2fjwt6IQqR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD6bHCI0BQrvacjNBL8ok%2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2n5PkTBh6GEYi2afuCIQ8mjXAUdvEDg3um2GjJKtA%3d%3d', - 635655897361394523, - 'x0IeSOjUQNH2pvjMPkP4Jotzs5Weeqms4AlPxMQ5CipssUJbyKFjLWnwPg1Ac0XtSTiPD177BmQ1+KtmYvDTWQ1FmBuvpvKZDSKzXoT6Qj4LCYYQ0TxnN/OT231+qd50sOD8zAxhfXP56qND9tj5xqoHMa+lbuvNCqiOBTZw5f/dklSK7Wgdx7ST3Dq6S9xxDUfsLC4Tjq+EsvcdSNIWL/W6NRZdyWqlgRgE6X8t/2iyyMypURdOW2Rztc6w/iYhbuh22Ul6Jfu14KaDo6YkvBr8iHlK4CcQST9i0u044y1Jnh34UK4EPdVRZrvTmeJ/5DFLWOqEwvBlW2bpoYF+9A==', - 'etYRI9UT6q8jA6PHMMmuGa8NbyIlbTHMHmJZqaCOh2GCpv7um2q7+7oaDFqAV/UP+2N85yZXvZgt9kTOUCwIdggUQVeJluNCwf1B5NsN/7n2aQF9LnWyYK8kK/3xvQKQrj4n24jk2MnHcX1tk8H/qLxq2DbPzi6ROoSgs2ZK8nmzhSPF74jnLCrwiwGgnVZV6gIhlAKCcUGtdrT60sgD/wpJGQQ0M59VDQhf1aDj5bUotf8RXovY8Gl0lpguvN4+EsEjpbVjdV9hxWs7c03JDdoz7mzFUWErSly9IzYXNfuFZMnSXpF3lGiprVJvW34Bu2gTo/cLq4LQoABkNCmd4g==', - 'https://contoso.com/wopi/files/JIB9h+LJpZWBDwvoIiQ5p3115zJWDecpGF9aCm1vOa5UMllgC7w?access_token=RLoY%2f3D73%2fjwt6IQqR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD6bHCI0BQrvacjNBL8ok%2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2n5PkTBh6GEYi2afuCIQ8mjXAUdvEDg3um2GjJKtA%3d%3d' + 'RLoY%2f3D73%2fjwt6IQqR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD' \ + '6bHCI0BQrvacjNBL8ok%2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2' \ + 'n5PkTBh6GEYi2afuCIQ8mjXAUdvEDg3um2GjJKtA%3d%3d', + 635655897361394523, + 'x0IeSOjUQNH2pvjMPkP4Jotzs5Weeqms4AlPxMQ5CipssUJbyKFjLWnwPg' \ + '1Ac0XtSTiPD177BmQ1+KtmYvDTWQ1FmBuvpvKZDSKzXoT6Qj4LCYYQ0Txn' \ + 'N/OT231+qd50sOD8zAxhfXP56qND9tj5xqoHMa+lbuvNCqiOBTZw5f/dkl' \ + 'SK7Wgdx7ST3Dq6S9xxDUfsLC4Tjq+EsvcdSNIWL/W6NRZdyWqlgRgE6X8t' \ + '/2iyyMypURdOW2Rztc6w/iYhbuh22Ul6Jfu14KaDo6YkvBr8iHlK4CcQST' \ + '9i0u044y1Jnh34UK4EPdVRZrvTmeJ/5DFLWOqEwvBlW2bpoYF+9A==', + 'etYRI9UT6q8jA6PHMMmuGa8NbyIlbTHMHmJZqaCOh2GCpv7um2q7+7oaDF' \ + 'qAV/UP+2N85yZXvZgt9kTOUCwIdggUQVeJluNCwf1B5NsN/7n2aQF9LnWy' \ + 'YK8kK/3xvQKQrj4n24jk2MnHcX1tk8H/qLxq2DbPzi6ROoSgs2ZK8nmzhS' \ + 'PF74jnLCrwiwGgnVZV6gIhlAKCcUGtdrT60sgD/wpJGQQ0M59VDQhf1aDj' \ + '5bUotf8RXovY8Gl0lpguvN4+EsEjpbVjdV9hxWs7c03JDdoz7mzFUWErSl' \ + 'y9IzYXNfuFZMnSXpF3lGiprVJvW34Bu2gTo/cLq4LQoABkNCmd4g==', + 'https://contoso.com/wopi/files/JIB9h+LJpZWBDwvoIiQ5p3115zJ' \ + 'WDecpGF9aCm1vOa5UMllgC7w?access_token=RLoY%2f3D73%2fjwt6IQ' \ + 'qR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD6bHCI0BQrvacjNBL8ok%' \ + '2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2n5PkTBh6GEYi2afuCIQ8' \ + 'mjXAUdvEDg3um2GjJKtA%3d%3d' ) end test 'Verify X-WOPI-ProofOld with current discovery proof key1.' do assert @discovery.verify_proof( - 'zo7pjAbo%2fyof%2bvtUJn5gXpYcSl7TSSx0TbQGJbWJSll9PTjRRsAbG%2fSNL7FM2n5Ei3jQ8UJsW4RidT5R4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1wZk9uFkWEeGzbtGByTkaGJkBqgKV%2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG', + 'zo7pjAbo%2fyof%2bvtUJn5gXpYcSl7TSSx0TbQGJbWJSll9PTjRRsAbG%' \ + '2fSNL7FM2n5Ei3jQ8UJsW4RidT5R4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1' \ + 'wZk9uFkWEeGzbtGByTkaGJkBqgKV%2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG', 635655898374047766, - 'qQhMjQf9Zohj+S/wvhe+RD6W5TIEqJwDWO3zX9DB85yRe3Ide7EPQDCY9dAZtJpWkIDDzU+8FEwnexF0EhPimfCkmAyoPpkl2YYvQvvwUK2gdlk3WboWOVszm17p4dSDA0TDMPYsjaAGHKM/nPnTyIMzRyArEzoy2vNkLEP6qdBIuMP2aCtGsciwMjYifHYRIRenB7H7I+FkwH0UaoTUCoo2PJkyZjy1nK6OwGVWaWG0G8g7Zy+K3bRYV+7cNaM5SB720ezhmYYJJvsIdRvO7pLsjAuTo4KJhvmVFCipwyCdllVHY83GjuGOsAbHIIohl0Ttq59o0jp4w2wUs8U+mQ==', - 'PjKR1BTNNnfOrUzfo27cLIhlrbSiOVZaANadDyHxKij/77ZYId+liyXoawvvQQPgnBH1dW6jqpr6fh5ZxZ9IOtaV+cTSUGnGdRSn7FyKs1ClpApKsZBO/iRBLXw3HDWOLc0jnA2bnxY8yqbEPmH5IBC9taYzxnf7aGjc6AWFHfs6AEQ8lMio6UoASNzjy3VVNzUX+CK+e5Z45coT0X60mjaJmidGfPdWIfyUw8sSuUwxQa1uNXAd8IceRUL7j5s9/kk7EwsihCw1Y3L+XJGG5zMsGhM9bTK5mvxj30UdmZORouNHdywOfdHaB1iOeKOk+yvWFMW3JsYShWbUhZUOEQ==', - 'https://contoso.com/wopi/files/RVQ29k8tf3h8cJ/Endy+aAMPy0iGhLatGNrhvKofPY9p2w?access_token=zo7pjAbo%2fyof%2bvtUJn5gXpYcSl7TSSx0TbQGJbWJSll9PTjRRsAbG%2fSNL7FM2n5Ei3jQ8UJsW4RidT5R4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1wZk9uFkWEeGzbtGByTkaGJkBqgKV%2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG' + 'qQhMjQf9Zohj+S/wvhe+RD6W5TIEqJwDWO3zX9DB85yRe3Ide7EPQDCY9dA' \ + 'ZtJpWkIDDzU+8FEwnexF0EhPimfCkmAyoPpkl2YYvQvvwUK2gdlk3WboWOV' \ + 'szm17p4dSDA0TDMPYsjaAGHKM/nPnTyIMzRyArEzoy2vNkLEP6qdBIuMP2a' \ + 'CtGsciwMjYifHYRIRenB7H7I+FkwH0UaoTUCoo2PJkyZjy1nK6OwGVWaWG0' \ + 'G8g7Zy+K3bRYV+7cNaM5SB720ezhmYYJJvsIdRvO7pLsjAuTo4KJhvmVFCi' \ + 'pwyCdllVHY83GjuGOsAbHIIohl0Ttq59o0jp4w2wUs8U+mQ==', + 'PjKR1BTNNnfOrUzfo27cLIhlrbSiOVZaANadDyHxKij/77ZYId+liyXoawv' \ + 'vQQPgnBH1dW6jqpr6fh5ZxZ9IOtaV+cTSUGnGdRSn7FyKs1ClpApKsZBO/i' \ + 'RBLXw3HDWOLc0jnA2bnxY8yqbEPmH5IBC9taYzxnf7aGjc6AWFHfs6AEQ8l' \ + 'Mio6UoASNzjy3VVNzUX+CK+e5Z45coT0X60mjaJmidGfPdWIfyUw8sSuUwx' \ + 'Qa1uNXAd8IceRUL7j5s9/kk7EwsihCw1Y3L+XJGG5zMsGhM9bTK5mvxj30U' \ + 'dmZORouNHdywOfdHaB1iOeKOk+yvWFMW3JsYShWbUhZUOEQ==', + 'https://contoso.com/wopi/files/RVQ29k8tf3h8cJ/Endy+aAMPy0iG' \ + 'hLatGNrhvKofPY9p2w?access_token=zo7pjAbo%2fyof%2bvtUJn5gXpY' \ + 'cSl7TSSx0TbQGJbWJSll9PTjRRsAbG%2fSNL7FM2n5Ei3jQ8UJsW4RidT5R' \ + '4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1wZk9uFkWEeGzbtGByTkaGJkBqgKV%' \ + '2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG' ) end test 'Verify X-WOPI-ProofOld with current discovery proof key2.' do assert @discovery.verify_proof( -'2rZvm60097vFfXK01cylEhaYU26CbpsoEdVdk%2fDK2JzBjg185nE69CCklY36u2Thrx9DyLdNZGsbRorX12TK%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2bTQhhDlcymhYZ%2fnYx282ZrnI8gdIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8cHsiMvftBkQQ%2bJgqOU1le3OimjHOiDg%3d%3d', + '2rZvm60097vFfXK01cylEhaYU26CbpsoEdVdk%2fDK2JzBjg185nE69CCkl' \ + 'Y36u2Thrx9DyLdNZGsbRorX12TK%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2b' \ + 'TQhhDlcymhYZ%2fnYx282ZrnI8gdIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8' \ + 'cHsiMvftBkQQ%2bJgqOU1le3OimjHOiDg%3d%3d', 635655899172093398, - 'nLwR4nbFq8F/RLuAQ0Yby/avSJcpktZh+JlWonkXuVunTw+BZ+F84c3i1+0o7QoPQRkYmy/HEYhFoYVghkhQJzFXf98sYxb52SvDz/pl3G/gTgCcdcqkfRSMtaslkRX1VKC3YX42ksIL0LzM4wiCOJfT70i+2Yr5EQRWi6b2JELrAqPuNkxpuUUZ3DtEXsO9r6a4WkEwJesmqcqaQpMUvQ6cAkShgY7p0gAQeL//GG6wSpDaaA3QqKBcmBgzAatCaqg9NXiPjviZdoHgnbIRjBEhtH/WG00py/tsGMoIh6R9VfnC4jEWWymB5mIiXxW1gBuL63QvQqiL1S8FQ2Xo+g==', - 'M7R/iGyWInpWKl1+aAPuB2yVAwWaHTi504c87V0p35PzDyTh+K6fn20ygKmxf9suN9pq+/rMtUXhvM/zCyam0dZFuWpFOI22U0AjQxIJ4ZBH+zT+e/QeNmS7w4ctxUnDJ0zGsJ/DxaC0/DAkiIMOfXgb8FMPA/Z/HY+e1C6rRsoMXy0XGoTwzIDiI6wzPVr6FJpkktwO+eCNMyxnAODhlK+nubIhvmVEzWtenMXX8a2eJ3mFquNC7Le6shiUxZA03MgqknwjasNaGPrdpJYjxCwzH3LnHPOHxVY2HcjjTQTNHO8HEPCoSw9bi5a7XFS2loQM/tm0WknBe9+q4fpMMw==', - 'https://contoso.com/wopi/files/s/3BPN8mUjJJAQS+aOFtFm6CU+YNpRICIR2M7mQLeQ90BA?access_token=2rZvm60097vFfXK01cylEhaYU26CbpsoEdVdk%2fDK2JzBjg185nE69CCklY36u2Thrx9DyLdNZGsbRorX12TK%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2bTQhhDlcymhYZ%2fnYx282ZrnI8gdIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8cHsiMvftBkQQ%2bJgqOU1le3OimjHOiDg%3d%3d' + 'nLwR4nbFq8F/RLuAQ0Yby/avSJcpktZh+JlWonkXuVunTw+BZ+F84c3i1+0' \ + 'o7QoPQRkYmy/HEYhFoYVghkhQJzFXf98sYxb52SvDz/pl3G/gTgCcdcqkfR' \ + 'SMtaslkRX1VKC3YX42ksIL0LzM4wiCOJfT70i+2Yr5EQRWi6b2JELrAqPuN' \ + 'kxpuUUZ3DtEXsO9r6a4WkEwJesmqcqaQpMUvQ6cAkShgY7p0gAQeL//GG6w' \ + 'SpDaaA3QqKBcmBgzAatCaqg9NXiPjviZdoHgnbIRjBEhtH/WG00py/tsGMo' \ + 'Ih6R9VfnC4jEWWymB5mIiXxW1gBuL63QvQqiL1S8FQ2Xo+g==', + 'M7R/iGyWInpWKl1+aAPuB2yVAwWaHTi504c87V0p35PzDyTh+K6fn20ygKm' \ + 'xf9suN9pq+/rMtUXhvM/zCyam0dZFuWpFOI22U0AjQxIJ4ZBH+zT+e/QeNm' \ + 'S7w4ctxUnDJ0zGsJ/DxaC0/DAkiIMOfXgb8FMPA/Z/HY+e1C6rRsoMXy0XG' \ + 'oTwzIDiI6wzPVr6FJpkktwO+eCNMyxnAODhlK+nubIhvmVEzWtenMXX8a2e' \ + 'J3mFquNC7Le6shiUxZA03MgqknwjasNaGPrdpJYjxCwzH3LnHPOHxVY2Hcj' \ + 'jTQTNHO8HEPCoSw9bi5a7XFS2loQM/tm0WknBe9+q4fpMMw==', + 'https://contoso.com/wopi/files/s/3BPN8mUjJJAQS+aOFtFm6CU+YN' \ + 'pRICIR2M7mQLeQ90BA?access_token=2rZvm60097vFfXK01cylEhaYU26' \ + 'CbpsoEdVdk%2fDK2JzBjg185nE69CCklY36u2Thrx9DyLdNZGsbRorX12TK' \ + '%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2bTQhhDlcymhYZ%2fnYx282ZrnI8g' \ + 'dIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8cHsiMvftBkQQ%2bJgqOU1le3Oim' \ + 'jHOiDg%3d%3d' ) end test 'Verify X-WOPI-Proof with old discovery proof key1.' do assert @discovery.verify_proof( - 'pbocsujrb9BafFujWh%2fuh7Y6S5nBnonddEzDzV0zEFrBwhiu5lzjXRezXDC9N4acvJeGVB5CWAcxPz6cJ6FzJmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrbw8f2kRfaceRjV1PzXEvFXulnz2K%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4', + 'pbocsujrb9BafFujWh%2fuh7Y6S5nBnonddEzDzV0zEFrBwhiu5lzjXRezX' \ + 'DC9N4acvJeGVB5CWAcxPz6cJ6FzJmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrb' \ + 'w8f2kRfaceRjV1PzXEvFXulnz2K%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4', 635655898062751632, - 'qF15pAAnOATqpUTLHIS/Z5K7OYFVjWcgKGbHPa0eHRayXsb6JKTelGQhvs74gEFgg1mIgcCORwAtMzLmEFmOHgrdvkGvRzT3jtVVtwkxEhQt8aQL20N0Nwn4wNah0HeBHskdvmA1G/qcaFp8uTgHpRYFoBaSHEP3AZVNFg5y2jyYR34nNj359gktc2ZyLel3J3j7XtyjpRPHvvYVQfh7RsArLQ0VGp8sL4/BDHdSsUyJ8FXe67TSrz6TMZPwhEUR8dYHYek9qbQjC+wxPpo3G/yusucm1gHo0BjW/l36cI8FRmNs1Fqaeppxqu31FhR8dEl7w5dwefa9wOUKcChF6A==', - 'KmYzHJ9tu4SXfoiWzOkUIc0Bh8H3eJrA3OnDSbu2hT68EuLTp2vmvvFcHyHIiO8DuKj7u13MxkpuUER6VSIJp3nYfm91uEE/3g61V3SzaeRXdnkcKUa5x+ulKViECL2n4mpHzNnymxojFW5Y4lKUU4qEGzjE71K1DSFTU/CBkdqycsuy/Oct8G4GhA3O4MynlCf64B9LIhlWe4G+hxZgxIO0pq7w/1SH27nvScWiljVqgOAKr0Oidk/7sEfyBcOlerLgS/A00nJYYJk23DjrKGTKz1YY0CMEsROJCMiW11caxr0aKseOYlfmb6K1RXxtmiDpJ2T4y8jintjEdzEWDA==', - 'https://contoso.com/wopi/files/DJNj59eQlM6BvwzAHkykiB1vNOWRuxT487+guv3v7HexfA?access_token=pbocsujrb9BafFujWh%2fuh7Y6S5nBnonddEzDzV0zEFrBwhiu5lzjXRezXDC9N4acvJeGVB5CWAcxPz6cJ6FzJmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrbw8f2kRfaceRjV1PzXEvFXulnz2K%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4', + 'qF15pAAnOATqpUTLHIS/Z5K7OYFVjWcgKGbHPa0eHRayXsb6JKTelGQhvs7' \ + '4gEFgg1mIgcCORwAtMzLmEFmOHgrdvkGvRzT3jtVVtwkxEhQt8aQL20N0Nw' \ + 'n4wNah0HeBHskdvmA1G/qcaFp8uTgHpRYFoBaSHEP3AZVNFg5y2jyYR34nN' \ + 'j359gktc2ZyLel3J3j7XtyjpRPHvvYVQfh7RsArLQ0VGp8sL4/BDHdSsUyJ' \ + '8FXe67TSrz6TMZPwhEUR8dYHYek9qbQjC+wxPpo3G/yusucm1gHo0BjW/l3' \ + '6cI8FRmNs1Fqaeppxqu31FhR8dEl7w5dwefa9wOUKcChF6A==', + 'KmYzHJ9tu4SXfoiWzOkUIc0Bh8H3eJrA3OnDSbu2hT68EuLTp2vmvvFcHyH' \ + 'IiO8DuKj7u13MxkpuUER6VSIJp3nYfm91uEE/3g61V3SzaeRXdnkcKUa5x+' \ + 'ulKViECL2n4mpHzNnymxojFW5Y4lKUU4qEGzjE71K1DSFTU/CBkdqycsuy/' \ + 'Oct8G4GhA3O4MynlCf64B9LIhlWe4G+hxZgxIO0pq7w/1SH27nvScWiljVq' \ + 'gOAKr0Oidk/7sEfyBcOlerLgS/A00nJYYJk23DjrKGTKz1YY0CMEsROJCMi' \ + 'W11caxr0aKseOYlfmb6K1RXxtmiDpJ2T4y8jintjEdzEWDA==', + 'https://contoso.com/wopi/files/DJNj59eQlM6BvwzAHkykiB1vNOWR' \ + 'uxT487+guv3v7HexfA?access_token=pbocsujrb9BafFujWh%2fuh7Y6S' \ + '5nBnonddEzDzV0zEFrBwhiu5lzjXRezXDC9N4acvJeGVB5CWAcxPz6cJ6Fz' \ + 'JmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrbw8f2kRfaceRjV1PzXEvFXulnz2K' \ + '%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4', ) end test 'Verify X-WOPI-Proof with old discovery proof key2.' do assert @discovery.verify_proof( - '%2foBUJRjT2EVSVaB0Bjl2BCC7bXkN674bwppkzj2H9Elj2G%2bVGl06EzVgv62BqIW17gZZUweK%2fBPCOWN%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr982LaFC2Nxb0%2bH6u6MrWfRMK5dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzhRbQwIyMQ%3d', + '%2foBUJRjT2EVSVaB0Bjl2BCC7bXkN674bwppkzj2H9Elj2G%2bVGl06EzV' \ + 'gv62BqIW17gZZUweK%2fBPCOWN%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr9' \ + '82LaFC2Nxb0%2bH6u6MrWfRMK5dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzh' \ + 'RbQwIyMQ%3d', 635655898147049273, - 'egEcu5FlU9q+u+d5WQ/Z1nYbttR+0iWsLh2BgiyzAq93sQFyvQ5XSCW5i/V7rt9HDewnmvIXsak5ayeoX2aDNgsVM8gSI533tO9dv/33vBM6MAzNLXP3v5rNI/TaTXcfm4Q1bIoE5RFVZUSPH73B3p1Oon0Hhhd1CysFGv6ue00CT1Dgpu/w5cVElQxZmWmguD5hEFgmg9IGKuSJ1CdVeJLcTbdKWhJnTl/G4+SpXfWBnvTBXSzLK+PdtfVCxh/bPO/FJNRWNAg+UgdVAXnLN79faiioG27lXGVrodpVgW8qdANvDkjQbn7z7jXmQmv8ksVQgHfZel963NXRty5pDg==', - 'AQUuboqy0JFnMgWfaz8o6V3YDCd6fwQlmkM5cNxCK1ikLK03W4R3xjazBevNja70i3y0JYZ3GvTlJy4ZsEfYf+DRhfzgSuxExpTvNB5RMUvi/kNRTlrMNm6JQrLr2rTY5A+FvQ8YErnKSl5gqh9vb4b47sRdQNvYL5hiVI+Qd4AGAwN9dSq/IJo3jHtivPSGMEfuqozoX3/GQEYfRf5dv58Rjeo5XRiDvuSDhPOacdoL/DFc/ksC0u9vN4qmoTSvt313p/FhFZ9T3NZYi/dv63XP8DJLWs3HMauzGiKFbvKaxDYb2K2llM1CRM2PbKproARMUR6oYYzwNKtg/Q7qIw==', - 'https://contoso.com/wopi/files/UzfEboK8w9Eal86Vk4xzIkGwUeMAiFaNF14FfQETzQ72LPw?access_token=%2foBUJRjT2EVSVaB0Bjl2BCC7bXkN674bwppkzj2H9Elj2G%2bVGl06EzVgv62BqIW17gZZUweK%2fBPCOWN%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr982LaFC2Nxb0%2bH6u6MrWfRMK5dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzhRbQwIyMQ%3d' + 'egEcu5FlU9q+u+d5WQ/Z1nYbttR+0iWsLh2BgiyzAq93sQFyvQ5XSCW5i/V' \ + '7rt9HDewnmvIXsak5ayeoX2aDNgsVM8gSI533tO9dv/33vBM6MAzNLXP3v5' \ + 'rNI/TaTXcfm4Q1bIoE5RFVZUSPH73B3p1Oon0Hhhd1CysFGv6ue00CT1Dgp' \ + 'u/w5cVElQxZmWmguD5hEFgmg9IGKuSJ1CdVeJLcTbdKWhJnTl/G4+SpXfWB' \ + 'nvTBXSzLK+PdtfVCxh/bPO/FJNRWNAg+UgdVAXnLN79faiioG27lXGVrodp' \ + 'VgW8qdANvDkjQbn7z7jXmQmv8ksVQgHfZel963NXRty5pDg==', + 'AQUuboqy0JFnMgWfaz8o6V3YDCd6fwQlmkM5cNxCK1ikLK03W4R3xjazBev' \ + 'Nja70i3y0JYZ3GvTlJy4ZsEfYf+DRhfzgSuxExpTvNB5RMUvi/kNRTlrMNm' \ + '6JQrLr2rTY5A+FvQ8YErnKSl5gqh9vb4b47sRdQNvYL5hiVI+Qd4AGAwN9d' \ + 'Sq/IJo3jHtivPSGMEfuqozoX3/GQEYfRf5dv58Rjeo5XRiDvuSDhPOacdoL' \ + '/DFc/ksC0u9vN4qmoTSvt313p/FhFZ9T3NZYi/dv63XP8DJLWs3HMauzGiK' \ + 'FbvKaxDYb2K2llM1CRM2PbKproARMUR6oYYzwNKtg/Q7qIw==', + 'https://contoso.com/wopi/files/UzfEboK8w9Eal86Vk4xzIkGwUeMA' \ + 'iFaNF14FfQETzQ72LPw?access_token=%2foBUJRjT2EVSVaB0Bjl2BCC7' \ + 'bXkN674bwppkzj2H9Elj2G%2bVGl06EzVgv62BqIW17gZZUweK%2fBPCOWN' \ + '%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr982LaFC2Nxb0%2bH6u6MrWfRMK5' \ + 'dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzhRbQwIyMQ%3d' ) end test 'Invalid proof key1.' do assert_not @discovery.verify_proof( - '7DHGrfxYtgXxfaXF%2benHF3PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJRnJm5QIMXS7WbIxMy0LXaova2h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJHtSXfKANUetLyAjFWq6egtMZJSHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03kwAvBrdVQmjFdFryKw', + '7DHGrfxYtgXxfaXF%2benHF3PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJR' \ + 'nJm5QIMXS7WbIxMy0LXaova2h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJH' \ + 'tSXfKANUetLyAjFWq6egtMZJSHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03' \ + 'kwAvBrdVQmjFdFryKw', 635655899260461032, - 'Y/wVmPuvWJ5Q/Gl/a5mCkTrCKbfHWYiMG6cxmJgD+M/yYFzTsfcgbK2IRAlCR2eqGx3a5wh5bQzlC6Y/sKo2IE9Irz/NHFpV55zYfdwDi5ccwXSd34jWVUgkM3uL1r6KVmHcQH/ew10p54FVatXatuGp2Y+cq9BScYV1a45U8fs9zYoZcTAYvdWeYXmJbRGMOLLxab3fOiulPmbw+gOqpYPbuInc0yut6eGxAUmY1ENxpN7nbUqI3LbJvmE3PuX8Ifgg3RCsaFJEqC6JR36MjG+VqoKaFI6hh/ZWLR4y7FBarSmQBr2VlAHrcWCJIMXoOOKaHdsDfNCb3A24LFvdAQ==', - 'Pdbk1FoAB4zhxaDptfVOwTCmrNgWO6zdokoI3VYO8eshE9nJR1Rzr9K2666za29IfT050jJX0EBanIXAawL4rFA6swPHYQAzf3pWJqwvqIbaLYvi4104IBWhm9XdZ7C1jDUmG8DgwbKrXZfg7xxZ/hzPlwEp5Y9ZijD/ReAgRs0Va8/ytWc3AJ+121Q1Ss9U8vD08K5+wg1PVYyNa2YGBpVJbt2ZSt8dvuciWZujFDTzgLvRr6w17kg6+jkiwJyz2ZIL6ytyiUE1oJzsbslIZN3yGHEcmXZZ8Xz5q8fzrLUVmRx1kX6FE2QzRe4+6Q+qNeI8Ct7dj7JBBdbK2Jq+6A==', - 'https://contoso.com/wopi/files/Dy07US/uXVeOMwgyEqGqeVNnyOoaRxR+es2atR08tZPMatf0gf0?access_token=7DHGrfxYtgXxfaXF%2benHF3PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJRnJm5QIMXS7WbIxMy0LXaova2h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJHtSXfKANUetLyAjFWq6egtMZJSHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03kwAvBrdVQmjFdFryKw' + 'Y/wVmPuvWJ5Q/Gl/a5mCkTrCKbfHWYiMG6cxmJgD+M/yYFzTsfcgbK2IRAlC' \ + 'R2eqGx3a5wh5bQzlC6Y/sKo2IE9Irz/NHFpV55zYfdwDi5ccwXSd34jWVUgk' \ + 'M3uL1r6KVmHcQH/ew10p54FVatXatuGp2Y+cq9BScYV1a45U8fs9zYoZcTAY' \ + 'vdWeYXmJbRGMOLLxab3fOiulPmbw+gOqpYPbuInc0yut6eGxAUmY1ENxpN7n' \ + 'bUqI3LbJvmE3PuX8Ifgg3RCsaFJEqC6JR36MjG+VqoKaFI6hh/ZWLR4y7FBa' \ + 'rSmQBr2VlAHrcWCJIMXoOOKaHdsDfNCb3A24LFvdAQ==', + 'Pdbk1FoAB4zhxaDptfVOwTCmrNgWO6zdokoI3VYO8eshE9nJR1Rzr9K2666z' \ + 'a29IfT050jJX0EBanIXAawL4rFA6swPHYQAzf3pWJqwvqIbaLYvi4104IBWh' \ + 'm9XdZ7C1jDUmG8DgwbKrXZfg7xxZ/hzPlwEp5Y9ZijD/ReAgRs0Va8/ytWc3' \ + 'AJ+121Q1Ss9U8vD08K5+wg1PVYyNa2YGBpVJbt2ZSt8dvuciWZujFDTzgLvR' \ + 'r6w17kg6+jkiwJyz2ZIL6ytyiUE1oJzsbslIZN3yGHEcmXZZ8Xz5q8fzrLUV' \ + 'mRx1kX6FE2QzRe4+6Q+qNeI8Ct7dj7JBBdbK2Jq+6A==', + 'https://contoso.com/wopi/files/Dy07US/uXVeOMwgyEqGqeVNnyOoaR' \ + 'xR+es2atR08tZPMatf0gf0?access_token=7DHGrfxYtgXxfaXF%2benHF3' \ + 'PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJRnJm5QIMXS7WbIxMy0LXaova2' \ + 'h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJHtSXfKANUetLyAjFWq6egtMZJ' \ + 'SHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03kwAvBrdVQmjFdFryKw' ) end test 'Invalid proof key2.' do assert_not @discovery.verify_proof( - 'itKqYThsIRPrrctbu%2bh7%2fMcYvNZiVKc5Un50ZqjKtvhVwz0XcObcEVm5rghR4PmrUG0P28QnQTYrcQ65%2bLtrIfbTBferY4zhPKt9qKaf6HKEyRU7utszLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZiQi4icNS6mMGYboNZ1pHaNCRmR31oZjmqWgcknWow8', + 'itKqYThsIRPrrctbu%2bh7%2fMcYvNZiVKc5Un50ZqjKtvhVwz0XcObcEVm5' \ + 'rghR4PmrUG0P28QnQTYrcQ65%2bLtrIfbTBferY4zhPKt9qKaf6HKEyRU7ut' \ + 'szLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZiQi4icNS6mMGYboNZ1pHaNCRmR31' \ + 'oZjmqWgcknWow8', 635655897692506269, - 'Bu+myEy45g0hyKJycD7BYoDH+s10yvuH79Iq3OLz4w/o7gT8orKRrZ4KggJUWMzRPPSgg+CqEf6zdNm6kFrxf/zXCXTqa/bEzmJPNBby+r0jlyz2KFsOkxJ/mzVMKJltG0le56NJim21ZU4AwN0KiB6TZ56ruu/ma014proZWJt/H0qye/pNz5ZyYdyc1khgzKf/8o6sCgh6+VEZx93BjAUyMJr6OS9nYq1B8XGei/sE4xdgbrIGW0Jwjl2hxqBQTn1VL0jyljbjuG+vy83QaeBSB4TGLljHZFgYp6ARCX7v3NHO0MicJtTpLdpsB9dlLkbms/BcJd1gK7m5dDJK1A==', - 'CDU9l+cyr82vrUnyhdTh1P4jDwL82c//XrTWU/rPeTyB9Ko6z2aNCs893GEB3jICY7mDZ4t0sA5z/lpN83UyfH1uVqnEB92zCQsJTkXMdNG9dsK64G6RxIVbmhJkV5xiJvERCRnJJTcQg4ymeXQ3cuXAcN9XhStK8FCxBhxGMJsmlY2tahxdTEIHXHVagvdyB1UY0V1rv+OAOnCGaTs0yyBA0Mo5nmTSNF8VnYmqsmbXyyDPJJTsqallyJPxvdrLPeiBq1JHMr3b8dgel1jfweDR4mD9pHn3O9eaHN8b6xlLc+dIpwvcflzyhhEL5JKEUtO6EKbfakZTUeS86BuBZA==', - 'https://contoso.com/wopi/files/6XZhdjvt6/cdJvXdBI1bdlIkhIjJPnb9HgWzZX7V2d8?access_token=itKqYThsIRPrrctbu%2bh7%2fMcYvNZiVKc5Un50ZqjKtvhVwz0XcObcEVm5rghR4PmrUG0P28QnQTYrcQ65%2bLtrIfbTBferY4zhPKt9qKaf6HKEyRU7utszLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZiQi4icNS6mMGYboNZ1pHaNCRmR31oZjmqWgcknWow8' + 'Bu+myEy45g0hyKJycD7BYoDH+s10yvuH79Iq3OLz4w/o7gT8orKRrZ4KggJU' \ + 'WMzRPPSgg+CqEf6zdNm6kFrxf/zXCXTqa/bEzmJPNBby+r0jlyz2KFsOkxJ/' \ + 'mzVMKJltG0le56NJim21ZU4AwN0KiB6TZ56ruu/ma014proZWJt/H0qye/pN' \ + 'z5ZyYdyc1khgzKf/8o6sCgh6+VEZx93BjAUyMJr6OS9nYq1B8XGei/sE4xdg' \ + 'brIGW0Jwjl2hxqBQTn1VL0jyljbjuG+vy83QaeBSB4TGLljHZFgYp6ARCX7v' \ + '3NHO0MicJtTpLdpsB9dlLkbms/BcJd1gK7m5dDJK1A==', + 'CDU9l+cyr82vrUnyhdTh1P4jDwL82c//XrTWU/rPeTyB9Ko6z2aNCs893GEB' \ + '3jICY7mDZ4t0sA5z/lpN83UyfH1uVqnEB92zCQsJTkXMdNG9dsK64G6RxIVb' \ + 'mhJkV5xiJvERCRnJJTcQg4ymeXQ3cuXAcN9XhStK8FCxBhxGMJsmlY2tahxd' \ + 'TEIHXHVagvdyB1UY0V1rv+OAOnCGaTs0yyBA0Mo5nmTSNF8VnYmqsmbXyyDP' \ + 'JJTsqallyJPxvdrLPeiBq1JHMr3b8dgel1jfweDR4mD9pHn3O9eaHN8b6xlL' \ + 'c+dIpwvcflzyhhEL5JKEUtO6EKbfakZTUeS86BuBZA==', + 'https://contoso.com/wopi/files/6XZhdjvt6/cdJvXdBI1bdlIkhIjJP' \ + 'nb9HgWzZX7V2d8?access_token=itKqYThsIRPrrctbu%2bh7%2fMcYvNZi' \ + 'VKc5Un50ZqjKtvhVwz0XcObcEVm5rghR4PmrUG0P28QnQTYrcQ65%2bLtrIf' \ + 'bTBferY4zhPKt9qKaf6HKEyRU7utszLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZ' \ + 'iQi4icNS6mMGYboNZ1pHaNCRmR31oZjmqWgcknWow8' ) end end From 01c086b667bd835fb61d7b5204b781d1d82966ff Mon Sep 17 00:00:00 2001 From: zmagod Date: Tue, 3 Jan 2017 09:49:18 +0100 Subject: [PATCH 44/54] fixes routes.rb indentation --- config/routes.rb | 602 ++++++++++++++++++++++++++--------------------- 1 file changed, 337 insertions(+), 265 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index bd33047cc..04b263ea1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,283 +1,355 @@ Rails.application.routes.draw do + require 'subdomain' - require 'subdomain' + constraints UserSubdomain do + devise_for :users, controllers: { registrations: 'users/registrations', + sessions: 'users/sessions', + invitations: 'users/invitations', + confirmations: 'users/confirmations' } + root 'projects#index' - constraints (UserSubdomain) do - devise_for :users, controllers: { registrations: "users/registrations", - sessions: "users/sessions", invitations: "users/invitations", - confirmations: "users/confirmations" } + resources :activities, only: [:index] + get 'forbidden', to: 'application#forbidden', as: 'forbidden' + get 'not_found', to: 'application#not_found', as: 'not_found' - root 'projects#index' + # Settings + get 'users/settings/preferences', + to: 'users/settings#preferences', + as: 'preferences' + put 'users/settings/preferences', + to: 'users/settings#update_preferences', + as: 'update_preferences' + get 'users/settings/preferences/tutorial', + to: 'users/settings#tutorial', + as: 'tutorial' + post 'users/settings/preferences/reset_tutorial/', + to: 'users/settings#reset_tutorial', + as: 'reset_tutorial' + get 'users/settings/organizations', + to: 'users/settings#organizations', + as: 'organizations' + get 'users/settings/organizations/new', + to: 'users/settings#new_organization', + as: 'new_organization' + post 'users/settings/organizations/new', + to: 'users/settings#create_organization', + as: 'create_organization' + get 'users/settings/organizations/:organization_id', + to: 'users/settings#organization', + as: 'organization' + put 'users/settings/organizations/:organization_id', + to: 'users/settings#update_organization', + as: 'update_organization' + get 'users/settings/organizations/:organization_id/name', + to: 'users/settings#organization_name', + as: 'organization_name' + get 'users/settings/organizations/:organization_id/description', + to: 'users/settings#organization_description', + as: 'organization_description' + get 'users/settings/organizations/:organization_id/search', + to: 'users/settings#search_organization_users', + as: 'search_organization_users' + post 'users/settings/organizations/:organization_id/users_datatable', + to: 'users/settings#organization_users_datatable', + as: 'organization_users_datatable' + delete 'users/settings/organizations/:organization_id', + to: 'users/settings#destroy_organization', + as: 'destroy_organization' + post 'users/settings/user_organizations/new', + to: 'users/settings#create_user_organization', + as: 'create_user_organization' + post 'users/settings/users_organizations/new_user', + to: 'users/settings#create_user_and_user_organization', + as: 'create_user_and_user_organization' + put 'users/settings/user_organizations/:user_organization_id', + to: 'users/settings#update_user_organization', + as: 'update_user_organization' + get 'users/settings/user_organizations/:user_organization_id/leave_html', + to: 'users/settings#leave_user_organization_html', + as: 'leave_user_organization_html' + 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 :activities, only: [:index] - - get "forbidden", :to => "application#forbidden", as: "forbidden" - get "not_found", :to => "application#not_found", as: "not_found" - - # Settings - get "users/settings/preferences", to: "users/settings#preferences", as: "preferences" - put "users/settings/preferences", to: "users/settings#update_preferences", as: "update_preferences" - get "users/settings/preferences/tutorial", to: "users/settings#tutorial", as: "tutorial" - post "users/settings/preferences/reset_tutorial/", to: "users/settings#reset_tutorial", as: "reset_tutorial" - get "users/settings/organizations", to: "users/settings#organizations", as: "organizations" - get "users/settings/organizations/new", to: "users/settings#new_organization", as: "new_organization" - post "users/settings/organizations/new", to: "users/settings#create_organization", as: "create_organization" - get "users/settings/organizations/:organization_id", to: "users/settings#organization", as: "organization" - put "users/settings/organizations/:organization_id", to: "users/settings#update_organization", as: "update_organization" - get "users/settings/organizations/:organization_id/name", to: "users/settings#organization_name", as: "organization_name" - get "users/settings/organizations/:organization_id/description", to: "users/settings#organization_description", as: "organization_description" - get "users/settings/organizations/:organization_id/search", to: "users/settings#search_organization_users", as: "search_organization_users" - post "users/settings/organizations/:organization_id/users_datatable", to: "users/settings#organization_users_datatable", as: "organization_users_datatable" - delete "users/settings/organizations/:organization_id", to: "users/settings#destroy_organization", as: "destroy_organization" - post "users/settings/user_organizations/new", to: "users/settings#create_user_organization", as: "create_user_organization" - post "users/settings/users_organizations/new_user", to: "users/settings#create_user_and_user_organization", as: "create_user_and_user_organization" - put "users/settings/user_organizations/:user_organization_id", to: "users/settings#update_user_organization", as: "update_user_organization" - get "users/settings/user_organizations/:user_organization_id/leave_html", to: "users/settings#leave_user_organization_html", as: "leave_user_organization_html" - 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] - resources :sample_groups, only: [:new, :create] - resources :custom_fields, only: [:create] - member do - post 'parse_sheet' - post 'import_samples' - post 'export_samples' + resources :organizations, only: [] do + resources :samples, only: [:new, :create] + resources :sample_types, only: [:new, :create] + resources :sample_groups, only: [:new, :create] + resources :custom_fields, only: [:create] + member do + post 'parse_sheet' + post 'import_samples' + post 'export_samples' + end + match '*path', + to: 'organizations#routing_error', + via: [:get, :post, :put, :patch] end - match '*path', :to => 'organizations#routing_error', via: [:get, :post, :put, :patch] - end - get 'projects/archive', to: 'projects#archive', as: 'projects_archive' + get 'projects/archive', to: 'projects#archive', as: 'projects_archive' - resources :projects, except: [:new, :destroy] do - resources :user_projects, path: "/users", only: [:new, :create, :index, :edit, :update, :destroy] - resources :project_comments, - path: '/comments', - only: [:new, :create, :index, :edit, :update, :destroy] - # Activities popup (JSON) for individual project in projects index, - # as well as all activities page for single project (HTML) - resources :project_activities, path: "/activities", only: [:index] - resources :tags, only: [:create, :update, :destroy] - resources :reports, path: "/reports", only: [:index, :new, :create, :edit, :update] do - collection do - # The posts following here should in theory be gets, - # but are posts because of parameters payload - post 'generate', to: 'reports#generate' - get 'new/', to: 'reports#new' - get 'new/project_contents_modal', - to: 'reports#project_contents_modal', - as: :project_contents_modal - post 'new/project_contents', - to: 'reports#project_contents', - as: :project_contents - get 'new/experiment_contents_modal', - to: 'reports#experiment_contents_modal', - as: :experiment_contents_modal - post 'new/experiment_contents', - to: 'reports#experiment_contents', - as: :experiment_contents - get 'new/module_contents_modal', - to: 'reports#module_contents_modal', - as: :module_contents_modal - post 'new/module_contents', - to: 'reports#module_contents', - as: :module_contents - get 'new/step_contents_modal', - to: 'reports#step_contents_modal', - as: :step_contents_modal - post 'new/step_contents', - to: 'reports#step_contents', - as: :step_contents - get 'new/result_contents_modal', - to: 'reports#result_contents_modal', - as: :result_contents_modal - post 'new/result_contents', - to: 'reports#result_contents', - as: :result_contents - post '_save', - to: 'reports#save_modal', - as: :save_modal - post 'destroy', as: :destroy # Destroy multiple entries at once + resources :projects, except: [:new, :destroy] do + resources :user_projects, + path: '/users', + only: [:new, :create, :index, :edit, :update, :destroy] + resources :project_comments, + path: '/comments', + only: [:new, :create, :index, :edit, :update, :destroy] + # Activities popup (JSON) for individual project in projects index, + # as well as all activities page for single project (HTML) + resources :project_activities, path: '/activities', only: :index + resources :tags, only: [:create, :update, :destroy] + resources :reports, + path: '/reports', + only: [:index, :new, :create, :edit, :update] do + collection do + # The posts following here should in theory be gets, + # but are posts because of parameters payload + post 'generate', to: 'reports#generate' + get 'new/', to: 'reports#new' + get 'new/project_contents_modal', + to: 'reports#project_contents_modal', + as: :project_contents_modal + post 'new/project_contents', + to: 'reports#project_contents', + as: :project_contents + get 'new/experiment_contents_modal', + to: 'reports#experiment_contents_modal', + as: :experiment_contents_modal + post 'new/experiment_contents', + to: 'reports#experiment_contents', + as: :experiment_contents + get 'new/module_contents_modal', + to: 'reports#module_contents_modal', + as: :module_contents_modal + post 'new/module_contents', + to: 'reports#module_contents', + as: :module_contents + get 'new/step_contents_modal', + to: 'reports#step_contents_modal', + as: :step_contents_modal + post 'new/step_contents', + to: 'reports#step_contents', + as: :step_contents + get 'new/result_contents_modal', + to: 'reports#result_contents_modal', + as: :result_contents_modal + post 'new/result_contents', + to: 'reports#result_contents', + as: :result_contents + post '_save', + to: 'reports#save_modal', + as: :save_modal + post 'destroy', as: :destroy # Destroy multiple entries at once + end + end + resources :experiments, + only: [:new, :create, :edit, :update], + defaults: { format: 'json' } + member do + # Notifications popup for individual project in projects index + get 'notifications' + get 'samples' # Samples for single project + # Renders sample datatable for single project (ajax action) + post 'samples_index' + get 'experiment_archive' # Experiment archive for single project + post :delete_samples, + constraints: CommitParamRouting.new( + MyModulesController::DELETE_SAMPLES + ), + action: :delete_samples + end + + # This route is defined outside of member block to preserve original + # :project_id parameter in URL. + get 'users/edit', to: 'user_projects#index_edit' + end + + resources :experiments do + member do + get 'canvas' # Overview/structure for single experiment + # AJAX-loaded canvas edit mode (from canvas) + get 'canvas/edit', to: 'canvas#edit' + get 'canvas/full_zoom', to: 'canvas#full_zoom' # AJAX-loaded canvas zoom + # AJAX-loaded canvas zoom + get 'canvas/medium_zoom', to: 'canvas#medium_zoom' + get 'canvas/small_zoom', + to: 'canvas#small_zoom' # AJAX-loaded canvas zoom + post 'canvas', to: 'canvas#update' # Save updated canvas action + get 'module_archive' # Module archive for single experiment + get 'archive' # archive experiment + get 'clone_modal' # return modal with clone options + post 'clone' # clone experiment + get 'move_modal' # return modal with move options + post 'move' # move experiment + get 'samples' # Samples for single project + get 'updated_img' # Checks if the workflow image is updated + get 'fetch_workflow_img' # Get udated workflow img + # Renders sample datatable for single project (ajax action) + post 'samples_index' + post :delete_samples, + constraints: CommitParamRouting.new( + MyModulesController::DELETE_SAMPLES + ), + action: :delete_samples end end - resources :experiments, - only: [:new, :create, :edit, :update], - defaults: { format: 'json' } - member do - get 'notifications' # Notifications popup for individual project in projects index - get 'samples' # Samples for single project - post 'samples_index' # Renders sample datatable for single project (ajax action) - get 'experiment_archive' # Experiment archive for single project - post :delete_samples, constraints: CommitParamRouting.new(MyModulesController::DELETE_SAMPLES), action: :delete_samples + + # Show action is a popup (JSON) for individual module in full-zoom canvas, + # as well as "module info" page for single module (HTML) + resources :my_modules, + path: '/modules', + only: [:show, :edit, :update, :destroy] do + resources :my_module_tags, + path: '/tags', + only: [:index, :create, :update, :destroy] + resources :user_my_modules, + path: '/users', + only: [:index, :new, :create, :destroy] + resources :my_module_comments, + path: '/comments', + only: [:index, :new, :create, :edit, :update, :destroy] + resources :sample_my_modules, path: '/samples_index', only: [:index] + resources :result_texts, only: [:new, :create] + resources :result_assets, only: [:new, :create] + resources :result_tables, only: [:new, :create] + member do + # AJAX popup accessed from full-zoom canvas for single module, + # as well as full activities view (HTML) for single module + get 'description' + get 'activities' + get 'activities_tab' # Activities in tab view for single module + get 'due_date' + get 'protocols' # Protocols view for single module + get 'results' # Results view for single module + get 'samples' # Samples view for single module + get 'archive' # Archive view for single module + # Renders sample datatable for single module (ajax action) + post 'samples_index' + post :assign_samples, + constraints: CommitParamRouting.new( + MyModulesController::ASSIGN_SAMPLES + ), + action: :assign_samples + post :assign_samples, + constraints: CommitParamRouting.new( + MyModulesController::UNASSIGN_SAMPLES + ), + action: :unassign_samples + post :assign_samples, + constraints: CommitParamRouting.new( + MyModulesController::DELETE_SAMPLES + ), + action: :delete_samples + end + + # Those routes are defined outside of member block to + # preserve original id parameters in URL. + get 'tags/edit', to: 'my_module_tags#index_edit' + get 'users/edit', to: 'user_my_modules#index_edit' end - # This route is defined outside of member block to preserve original :project_id parameter in URL. - get 'users/edit', to: 'user_projects#index_edit' - end + resources :steps, only: [:edit, :update, :destroy, :show] do + resources :step_comments, + path: '/comments', + only: [:new, :create, :index, :edit, :update, :destroy] + member do + post 'checklistitem_state' + post 'toggle_step_state' + get 'move_down' + get 'move_up' + end + end - resources :experiments do - member do - get 'canvas' # Overview/structure for single experiment - # AJAX-loaded canvas edit mode (from canvas) - get 'canvas/edit', to: 'canvas#edit' - get 'canvas/full_zoom', to: 'canvas#full_zoom' # AJAX-loaded canvas zoom - # AJAX-loaded canvas zoom - get 'canvas/medium_zoom', to: 'canvas#medium_zoom' - get 'canvas/small_zoom', to: 'canvas#small_zoom' # AJAX-loaded canvas zoom - post 'canvas', to: 'canvas#update' # Save updated canvas action - get 'module_archive' # Module archive for single experiment - get 'archive' # archive experiment - get 'clone_modal' # return modal with clone options - post 'clone' # clone experiment - get 'move_modal' # return modal with move options - post 'move' # move experiment - get 'samples' # Samples for single project - get 'updated_img' # Checks if the workflow image is updated - get 'fetch_workflow_img' # Get udated workflow img - # Renders sample datatable for single project (ajax action) - post 'samples_index' - post :delete_samples, - constraints: CommitParamRouting.new( - MyModulesController::DELETE_SAMPLES - ), - action: :delete_samples + resources :results, only: [:update, :destroy] do + resources :result_comments, + path: '/comments', + only: [:new, :create, :index, :edit, :update, :destroy] + end + + resources :samples, only: [:edit, :update, :destroy] + resources :sample_types, only: [:edit, :update] + resources :sample_groups, only: [:edit, :update] + resources :result_texts, only: [:edit, :update, :destroy] + get 'result_texts/:id/download' => 'result_texts#download', + as: :result_text_download + resources :result_assets, only: [:edit, :update, :destroy] + get 'result_assets/:id/download' => 'result_assets#download', + as: :result_asset_download + resources :result_tables, only: [:edit, :update, :destroy] + get 'result_tables/:id/download' => 'result_tables#download', + as: :result_table_download + + resources :protocols, only: [:index, :edit, :create] do + resources :steps, only: [:new, :create] + member do + get 'linked_children', to: 'protocols#linked_children' + post 'linked_children_datatable', + to: 'protocols#linked_children_datatable' + patch 'metadata', to: 'protocols#update_metadata' + patch 'keywords', to: 'protocols#update_keywords' + post 'clone', to: 'protocols#clone' + get 'unlink_modal', to: 'protocols#unlink_modal' + post 'unlink', to: 'protocols#unlink' + get 'revert_modal', to: 'protocols#revert_modal' + post 'revert', to: 'protocols#revert' + get 'update_parent_modal', to: 'protocols#update_parent_modal' + post 'update_parent', to: 'protocols#update_parent' + get 'update_from_parent_modal', to: 'protocols#update_from_parent_modal' + post 'update_from_parent', to: 'protocols#update_from_parent' + post 'load_from_repository_datatable', + to: 'protocols#load_from_repository_datatable' + get 'load_from_repository_modal', + to: 'protocols#load_from_repository_modal' + post 'load_from_repository', to: 'protocols#load_from_repository' + post 'load_from_file', to: 'protocols#load_from_file' + get 'copy_to_repository_modal', to: 'protocols#copy_to_repository_modal' + post 'copy_to_repository', to: 'protocols#copy_to_repository' + get 'protocol_status_bar', to: 'protocols#protocol_status_bar' + get 'updated_at_label', to: 'protocols#updated_at_label' + get 'edit_name_modal', to: 'protocols#edit_name_modal' + get 'edit_keywords_modal', to: 'protocols#edit_keywords_modal' + get 'edit_authors_modal', to: 'protocols#edit_authors_modal' + get 'edit_description_modal', to: 'protocols#edit_description_modal' + end + collection do + get 'create_new_modal', to: 'protocols#create_new_modal' + post 'datatable', to: 'protocols#datatable' + post 'make_private', to: 'protocols#make_private' + post 'publish', to: 'protocols#publish' + post 'archive', to: 'protocols#archive' + post 'restore', to: 'protocols#restore' + post 'import', to: 'protocols#import' + get 'export', to: 'protocols#export' + end + end + + get 'search' => 'search#index' + get 'search/new' => 'search#new', as: :new_search + + # We cannot use 'resources :assets' because assets is a reserved route + # in Rails (assets pipeline) and causes funky behavior + 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 end - # Show action is a popup (JSON) for individual module in full-zoom canvas, - # as well as "module info" page for single module (HTML) - resources :my_modules, path: "/modules", only: [:show, :edit, :update, :destroy] do - resources :my_module_tags, path: "/tags", only: [:index, :create, :update, :destroy] - resources :user_my_modules, path: "/users", only: [:index, :new, :create, :destroy] - resources :my_module_comments, - path: '/comments', - only: [:index, :new, :create, :edit, :update, :destroy] - resources :sample_my_modules, path: "/samples_index", only: [:index] - resources :result_texts, only: [:new, :create] - resources :result_assets, only: [:new, :create] - resources :result_tables, only: [:new, :create] - member do - # AJAX popup accessed from full-zoom canvas for single module, - # as well as full activities view (HTML) for single module - get 'description' - get 'activities' - get 'activities_tab' # Activities in tab view for single module - get 'due_date' - get 'protocols' # Protocols view for single module - get 'results' # Results view for single module - get 'samples' # Samples view for single module - get 'archive' # Archive view for single module - post 'samples_index' # Renders sample datatable for single module (ajax action) - post :assign_samples, constraints: CommitParamRouting.new(MyModulesController::ASSIGN_SAMPLES), action: :assign_samples - post :assign_samples, constraints: CommitParamRouting.new(MyModulesController::UNASSIGN_SAMPLES), action: :unassign_samples - post :assign_samples, constraints: CommitParamRouting.new(MyModulesController::DELETE_SAMPLES), action: :delete_samples - end - - # Those routes are defined outside of member block to preserve original id parameters in URL. - get 'tags/edit', to: 'my_module_tags#index_edit' - get 'users/edit', to: 'user_my_modules#index_edit' + constraints WopiSubdomain do + # Office integration + get 'wopi/files/:id/contents', to: 'wopi#file_contents_get_endpoint' + post 'wopi/files/:id/contents', to: 'wopi#file_contents_post_endpoint' + get 'wopi/files/:id', to: 'wopi#file_get_endpoint', as: 'wopi_rest_endpoint' + post 'wopi/files/:id', to: 'wopi#post_file_endpoint' end - - resources :steps, only: [:edit, :update, :destroy, :show] do - resources :step_comments, - path: '/comments', - only: [:new, :create, :index, :edit, :update, :destroy] - member do - post 'checklistitem_state' - post 'toggle_step_state' - get 'move_down' - get 'move_up' - end - end - - resources :results, only: [:update, :destroy] do - resources :result_comments, - path: '/comments', - only: [:new, :create, :index, :edit, :update, :destroy] - end - - resources :samples, only: [:edit, :update, :destroy] - resources :sample_types, only: [:edit, :update] - resources :sample_groups, only: [:edit, :update] - resources :result_texts, only: [:edit, :update, :destroy] - get 'result_texts/:id/download' => 'result_texts#download', - as: :result_text_download - resources :result_assets, only: [:edit, :update, :destroy] - get 'result_assets/:id/download' => 'result_assets#download', - as: :result_asset_download - resources :result_tables, only: [:edit, :update, :destroy] - get 'result_tables/:id/download' => 'result_tables#download', - as: :result_table_download - - resources :protocols, only: [:index, :edit, :create] do - resources :steps, only: [:new, :create] - member do - get "linked_children", to: "protocols#linked_children" - post "linked_children_datatable", to: "protocols#linked_children_datatable" - patch "metadata", to: "protocols#update_metadata" - patch "keywords", to: "protocols#update_keywords" - post "clone", to: "protocols#clone" - get "unlink_modal", to: "protocols#unlink_modal" - post "unlink", to: "protocols#unlink" - get "revert_modal", to: "protocols#revert_modal" - post "revert", to: "protocols#revert" - get "update_parent_modal", to: "protocols#update_parent_modal" - post "update_parent", to: "protocols#update_parent" - get "update_from_parent_modal", to: "protocols#update_from_parent_modal" - post "update_from_parent", to: "protocols#update_from_parent" - post "load_from_repository_datatable", to: "protocols#load_from_repository_datatable" - get "load_from_repository_modal", to: "protocols#load_from_repository_modal" - post "load_from_repository", to: "protocols#load_from_repository" - post "load_from_file", to: "protocols#load_from_file" - get "copy_to_repository_modal", to: "protocols#copy_to_repository_modal" - post "copy_to_repository", to: "protocols#copy_to_repository" - get "protocol_status_bar", to: "protocols#protocol_status_bar" - get "updated_at_label", to: "protocols#updated_at_label" - get "edit_name_modal", to: "protocols#edit_name_modal" - get "edit_keywords_modal", to: "protocols#edit_keywords_modal" - get "edit_authors_modal", to: "protocols#edit_authors_modal" - get "edit_description_modal", to: "protocols#edit_description_modal" - end - collection do - get "create_new_modal", to: "protocols#create_new_modal" - post "datatable", to: "protocols#datatable" - post "make_private", to: "protocols#make_private" - post "publish", to: "protocols#publish" - post "archive", to: "protocols#archive" - post "restore", to: "protocols#restore" - post "import", to: "protocols#import" - get "export", to: "protocols#export" - end - end - - get 'search' => 'search#index' - get 'search/new' => 'search#new', as: :new_search - - # We cannot use 'resources :assets' because assets is a reserved route - # in Rails (assets pipeline) and causes funky behavior - 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 - - end - constraints (WopiSubdomain) do - - # Office integration - get 'wopi/files/:id/contents', to: 'wopi#file_contents_get_endpoint' - post 'wopi/files/:id/contents', to: 'wopi#file_contents_post_endpoint' - - get 'wopi/files/:id', to: 'wopi#file_get_endpoint', as: 'wopi_rest_endpoint' - post 'wopi/files/:id', to: 'wopi#post_file_endpoint' - end end From 8cd092b521c4f516bebe13482833f9558a1a1d0d Mon Sep 17 00:00:00 2001 From: zmagod Date: Tue, 3 Jan 2017 16:51:02 +0100 Subject: [PATCH 45/54] update schema --- db/schema.rb | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index e3f771f60..57d98a8f5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,8 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 20161129111100) do +ActiveRecord::Schema.define(version: 20161129171012) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -649,20 +648,20 @@ ActiveRecord::Schema.define(version: 20161129111100) do add_index "user_projects", ["user_id"], name: "index_user_projects_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "full_name", null: false - t.string "initials", null: false - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "full_name", null: false + t.string "initials", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - 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 "avatar_file_name" t.string "avatar_content_type" t.integer "avatar_file_size" @@ -671,7 +670,7 @@ ActiveRecord::Schema.define(version: 20161129111100) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.string "time_zone", default: "UTC" + t.string "time_zone", default: "UTC" t.string "invitation_token" t.datetime "invitation_created_at" t.datetime "invitation_sent_at" @@ -679,16 +678,18 @@ ActiveRecord::Schema.define(version: 20161129111100) do t.integer "invitation_limit" t.integer "invited_by_id" t.string "invited_by_type" - t.integer "invitations_count", default: 0 - t.integer "tutorial_status", default: 0, null: false - t.boolean "assignments_notification", default: true - t.boolean "recent_notification", default: true - t.boolean "assignments_notification_email", default: false - t.boolean "recent_notification_email", default: false + t.integer "invitations_count", default: 0 + t.integer "tutorial_status", default: 0, null: false + t.boolean "assignments_notification", default: true + t.boolean "recent_notification", default: true + t.boolean "assignments_notification_email", default: false + t.boolean "recent_notification_email", default: false t.integer "current_organization_id" - t.boolean "system_message_notification_email", default: false + t.boolean "system_message_notification_email", default: false + t.string "authentication_token", limit: 30 end + add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", unique: true, using: :btree @@ -841,7 +842,7 @@ ActiveRecord::Schema.define(version: 20161129111100) 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 "users", "organizations", column: "current_organization_id" add_foreign_key "wopi_actions", "wopi_apps" add_foreign_key "wopi_apps", "wopi_discoveries" - add_foreign_key "users", "organizations", column: "current_organization_id" end From 87ffa7f03b73c9da64962bca7a34e64c352bc5a7 Mon Sep 17 00:00:00 2001 From: zmagod Date: Wed, 4 Jan 2017 10:02:08 +0100 Subject: [PATCH 46/54] fixes X-WOPI-ServerVersion bug --- app/controllers/wopi_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 3bc46e148..e61ccbadb 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -79,7 +79,7 @@ class WopiController < ActionController::Base } response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL'] response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL'] - response.headers['X-WOPI-ServerVersion'] = APP_VERSION + response.headers['X-WOPI-ServerVersion'] = Constants::APP_VERSION render json: msg and return end From 226d5d8247018948bc81c5d87e4a66f34555bce6 Mon Sep 17 00:00:00 2001 From: zmagod Date: Mon, 9 Jan 2017 11:51:18 +0100 Subject: [PATCH 47/54] added blank string to wopi-lock header param --- app/controllers/wopi_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index e61ccbadb..2f61f1aa8 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -128,7 +128,7 @@ class WopiController < ActionController::Base render nothing: :true, status: 409 and return end else - response.headers['X-WOPI-Lock'] = '' + response.headers['X-WOPI-Lock'] = ' ' render nothing: :true, status: 409 and return end end @@ -175,7 +175,7 @@ class WopiController < ActionController::Base render nothing: :true, status: 409 and return end else - response.headers['X-WOPI-Lock'] = '' + response.headers['X-WOPI-Lock'] = ' ' render nothing: :true, status: 409 and return end end @@ -186,7 +186,7 @@ class WopiController < ActionController::Base if @asset.locked? response.headers['X-WOPI-Lock'] = @asset.lock else - response.headers['X-WOPI-Lock'] = '' + response.headers['X-WOPI-Lock'] = ' ' end render nothing: :true, status: 200 and return end @@ -229,7 +229,7 @@ class WopiController < ActionController::Base render nothing: :true, status: 200 and return else logger.warn 'WOPI: trying to modify unlocked file' - response.headers['X-WOPI-Lock'] = '' + response.headers['X-WOPI-Lock'] = ' ' render nothing: :true, status: 409 and return end end From 97584aabb3f86070ece6b362b20e1f6b41aceeb2 Mon Sep 17 00:00:00 2001 From: Nbernot Date: Tue, 14 Feb 2017 16:44:03 +0100 Subject: [PATCH 48/54] Updated the lists of office online file extensions Extended the list of file extensions that receive office online icons and 'open in' text. --- app/helpers/file_icons_helper.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/helpers/file_icons_helper.rb b/app/helpers/file_icons_helper.rb index 01721e38b..e12c51fd7 100644 --- a/app/helpers/file_icons_helper.rb +++ b/app/helpers/file_icons_helper.rb @@ -1,17 +1,17 @@ module FileIconsHelper def wopi_file?(asset) file_ext = asset.file_file_name.split('.').last - %w(doc docx xls xlsx ppt pptx).include?(file_ext) + %w(csv ods xls xlsb xlsm xlsx odp pot potm potx pps ppsm ppsx ppt pptm pptx doc docm docx dot dotm dotx odt rtf).include?(file_ext) end # For showing next to file def file_extension_icon(asset) file_ext = asset.file_file_name.split('.').last - if %w(doc docx).include?(file_ext) + if %w(doc docm docx dot dotm dotx odt rtf).include?(file_ext) image_link = 'office/Word-docx_20x20x32.png' - elsif %w(xls xlsx).include?(file_ext) + elsif %w(csv ods xls xlsb xlsm xlsx).include?(file_ext) image_link = 'office/Excel-xlsx_20x20x32.png' - elsif %w(ppt pptx).include?(file_ext) + elsif %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).include?(file_ext) image_link = 'office/PowerPoint-pptx_20x20x32.png' end @@ -25,11 +25,11 @@ module FileIconsHelper # For showing in view/edit buttons (WOPI) def file_application_icon(asset) file_ext = asset.file_file_name.split('.').last - if %w(doc docx).include?(file_ext) + if %w(doc docm docx dot dotm dotx odt rtf).include?(file_ext) image_link = 'office/Word-color_16x16x32.png' - elsif %w(xls xlsx).include?(file_ext) + elsif %w(csv ods xls xlsb xlsm xlsx).include?(file_ext) image_link = 'office/Excel-color_16x16x32.png' - elsif %w(ppt pptx).include?(file_ext) + elsif %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).include?(file_ext) image_link = 'office/PowerPoint-Color_16x16x32.png' end @@ -43,11 +43,11 @@ module FileIconsHelper # Shows correct WOPI application text (Word Online/Excel ..) def wopi_button_text(asset, action) file_ext = asset.file_file_name.split('.').last - if %w(doc docx).include?(file_ext) + if %w(doc docm docx dot dotm dotx odt rtf).include?(file_ext) app = 'Word Online' - elsif %w(xls xlsx).include?(file_ext) + elsif %w(csv ods xls xlsb xlsm xlsx).include?(file_ext) app = 'Excel Online' - elsif %w(ppt pptx).include?(file_ext) + elsif %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).include?(file_ext) app = 'PowerPoint Online' end From 473487faec2bfe5c0b30ba4ea0029b8e546f9aec Mon Sep 17 00:00:00 2001 From: Luka Murn Date: Mon, 6 Mar 2017 13:55:07 +0100 Subject: [PATCH 49/54] Add HTML to edit, view file pages Closes SCI-1049. --- app/views/assets/edit.erb | 2 +- app/views/assets/view.erb | 2 +- config/locales/en.yml | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/views/assets/edit.erb b/app/views/assets/edit.erb index ea03dede1..9211a4220 100644 --- a/app/views/assets/edit.erb +++ b/app/views/assets/edit.erb @@ -6,7 +6,7 @@ <!-- Enable IE Standards mode --> <meta http-equiv="x-ua-compatible" content="ie=edge"> - <title> + <%= t('assets.head_title.edit', file_name: @asset.file_file_name) %> diff --git a/app/views/assets/view.erb b/app/views/assets/view.erb index 00ece94e3..798caa5b8 100644 --- a/app/views/assets/view.erb +++ b/app/views/assets/view.erb @@ -6,7 +6,7 @@ - + <%= t('assets.head_title.view', file_name: @asset.file_file_name) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 64c8ca7c2..ca6ee1a07 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1496,6 +1496,11 @@ en: assign_user_to_organization: "%{assigned_user} was added as %{role} to team %{organization} by %{assigned_by_user}." unassign_user_from_organization: "%{unassigned_user} was removed from team %{organization} by %{unassigned_by_user}." + assets: + head_title: + edit: "sciNote | %{file_name} | Edit" + view: "sciNote | %{file_name} | View" + # This section contains general words that can be used in any parts of # application. From 86a800a84e14fe6c10f9719f0b764aa8c8a9c3d8 Mon Sep 17 00:00:00 2001 From: Luka Murn Date: Mon, 6 Mar 2017 14:12:18 +0100 Subject: [PATCH 50/54] Open edit/view Office files in new browser tab/window Closes SCI-838. --- app/views/results/_result_asset.html.erb | 6 ++++-- app/views/steps/_step.html.erb | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/results/_result_asset.html.erb b/app/views/results/_result_asset.html.erb index 275e786d1..80e658b3f 100644 --- a/app/views/results/_result_asset.html.erb +++ b/app/views/results/_result_asset.html.erb @@ -15,7 +15,8 @@ <% if result.asset.can_perform_action('view') %> <%= link_to view_asset_url(id: result.asset), class: 'btn btn-default btn-sm', - style: 'display: inline-block' do %> + target: '_blank', + style: 'display: inline-block' do %> <%= file_application_icon(result.asset) %> <%= wopi_button_text(result.asset, 'view') %> <% end %> @@ -24,7 +25,8 @@ result.asset.can_perform_action('edit') %> <%= link_to edit_asset_url(id: result.asset), class: 'btn btn-default btn-sm', - style: 'display: inline-block' do %> + target: '_blank', + style: 'display: inline-block' do %> <%= file_application_icon(result.asset) %> <%= wopi_button_text(result.asset, 'edit') %> <% end %> diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index 0fb7bc54e..7612ceb19 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -102,7 +102,8 @@ <% if asset.can_perform_action('view') %> <%= link_to view_asset_url(id: asset), class: 'btn btn-default btn-sm', - style: 'display: inline-block' do %> + target: '_blank', + style: 'display: inline-block' do %> <%= file_application_icon(asset) %> <%= wopi_button_text(asset, 'view') %> <% end %> @@ -111,7 +112,8 @@ asset.can_perform_action('edit') %> <%= link_to edit_asset_url(id: asset), class: 'btn btn-default btn-sm', - style: 'display: inline-block' do %> + target: '_blank', + style: 'display: inline-block' do %> <%= file_application_icon(asset) %> <%= wopi_button_text(asset, 'edit') %> <% end %> From 0916e7e40dfb62306095c9a80b665f60692f7579 Mon Sep 17 00:00:00 2001 From: Luka Murn Date: Mon, 6 Mar 2017 15:01:32 +0100 Subject: [PATCH 51/54] Allow upload of .dot, .pot, .rtf files Closes SCI-1052. --- config/initializers/paperclip.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index d10217540..18b84cedc 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -175,10 +175,16 @@ module Paperclip # Types taken from: http://filext.com/faq/office_mime_types.php and # https://www.openoffice.org/framework/documentation/mimetypes/mimetypes.html # + # Generic application + (Set[content_type, content_types_from_name].subset? Set.new %w( + text/rtf + application/rtf + )) || # Word processor application (Set[content_type, content_types_from_name].subset? Set.new %w( application/vnd.ms-office application/msword + application/msword-template application/vnd.openxmlformats-officedocument.wordprocessingml.document application/vnd.openxmlformats-officedocument.wordprocessingml.template application/vnd.ms-word.document.macroEnabled.12 @@ -229,6 +235,7 @@ module Paperclip application/vnd.stardivision.impress application/vnd.stardivision.impress-packed application/x-starimpress + text/x-gettext-translation-template )) || # Graphics application (Set[content_type, content_types_from_name].subset? Set.new %w( From 7b16e5643ec0513795daf00bdae49861d0a46746 Mon Sep 17 00:00:00 2001 From: zmagod Date: Mon, 13 Mar 2017 13:20:49 +0100 Subject: [PATCH 52/54] fix assets preprocesor for wopi files --- app/assets/javascripts/assets.js | 4 + app/controllers/assets_controller.rb | 18 +++- app/controllers/result_assets_controller.rb | 4 +- app/helpers/wopi_helper.rb | 63 ++++++++++++++ app/views/results/_result_asset.html.erb | 61 ++----------- app/views/steps/_form_assets.html.erb | 2 +- app/views/steps/_step.html.erb | 2 +- app/views/steps/_wopi_controlls.html.erb | 28 ++---- db/schema.rb | 94 ++++++--------------- 9 files changed, 128 insertions(+), 148 deletions(-) create mode 100644 app/helpers/wopi_helper.rb diff --git a/app/assets/javascripts/assets.js b/app/assets/javascripts/assets.js index c0fa1e265..acffc50a5 100644 --- a/app/assets/javascripts/assets.js +++ b/app/assets/javascripts/assets.js @@ -33,6 +33,10 @@ function setupAssetsLoading() { data['preview-url'] + "'>

" + data.filename + '

' ); + } else if(data.type === "wopi") { + $el.html(data['wopi-file-name'] + + data['wopi-view'] + + data['wopi-edit']); } else { $el.html( "

" + diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 484d50424..fe1d0748c 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -1,6 +1,13 @@ class AssetsController < ApplicationController include WopiUtil + # include ActionView::Helpers + include ActionView::Helpers::AssetTagHelper include ActionView::Helpers::TextHelper + include ActionView::Helpers::UrlHelper + include ActionView::Context + include InputSanitizeHelper + include FileIconsHelper + include WopiHelper before_action :load_vars before_action :check_read_permission, except: :file_present @@ -52,7 +59,10 @@ class AssetsController < ApplicationController length: Constants::FILENAME_TRUNCATION_LENGTH), 'download-url' => download_asset_path(@asset), - 'type' => (@asset.is_image? ? 'image' : 'file') + 'type' => asset_data_type(@asset), + 'wopi-file-name' => wopi_asset_file_name(@asset), + 'wopi-edit' => (wopi_asset_edit_button(@asset) if wopi_file?(@asset)), + 'wopi-view' => (wopi_asset_view_button(@asset) if wopi_file?(@asset)) }, status: 200 end end @@ -203,4 +213,10 @@ class AssetsController < ApplicationController :file ) end + + def asset_data_type(asset) + return 'wopi' if wopi_file?(asset) + return 'image' if asset.is_image? + 'file' + end end diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb index 1e76d1bff..3ba66f263 100644 --- a/app/controllers/result_assets_controller.rb +++ b/app/controllers/result_assets_controller.rb @@ -72,14 +72,14 @@ class ResultAssetsController < ApplicationController else # This response is sent as 200 OK due to IE security error when # accessing iframe content. - format.json { + format.json { controller render json: {status: 'error', errors: @result.errors} } end end end - def edit + def controller controller respond_to do |format| format.json do render json: { diff --git a/app/helpers/wopi_helper.rb b/app/helpers/wopi_helper.rb new file mode 100644 index 000000000..ac8465195 --- /dev/null +++ b/app/helpers/wopi_helper.rb @@ -0,0 +1,63 @@ +module WopiHelper + #include FileIconsHelper + def wopi_result_view_file_button(result) + if result.asset.can_perform_action('view') + link_to view_asset_url(id: result.asset), + class: 'btn btn-default btn-sm', + target: '_blank', + style: 'display: inline-block' do + "#{file_application_icon( + result.asset + )} #{wopi_button_text(result.asset, 'view')}".html_safe + end + end + end + + def wopi_result_edit_file_button(result) + if can_edit_result_asset_in_module(result.my_module) && + result.asset.can_perform_action('edit') + link_to edit_asset_url(id: result.asset), + class: 'btn btn-default btn-sm', + target: '_blank', + style: 'display: inline-block' do + "#{file_application_icon( + result.asset + )} #{wopi_button_text(result.asset, 'edit')}".html_safe + end + end + end + + def wopi_asset_view_button(asset) + if asset.can_perform_action('view') + link_to view_asset_url(id: asset), + class: 'btn btn-default btn-sm', + target: '_blank', + style: 'display: inline-block' do + "#{file_application_icon(asset)} #{wopi_button_text(asset, 'view')}" + .html_safe + end + end + end + + def wopi_asset_edit_button(asset) + if asset.can_perform_action('edit') + link_to edit_asset_url(id: asset), + class: 'btn btn-default btn-sm', + target: '_blank', + style: 'display: inline-block' do + "#{file_application_icon( + asset + )} #{wopi_button_text(asset, 'edit')}".html_safe + end + end + end + + def wopi_asset_file_name(asset) + html = '

' + html += "#{file_extension_icon(asset)} " + html += truncate(asset.file_file_name, + length: Constants::FILENAME_TRUNCATION_LENGTH) + html += '

' + sanitize_input(html, %w(img)) + end +end diff --git a/app/views/results/_result_asset.html.erb b/app/views/results/_result_asset.html.erb index 138e554f2..b38409131 100644 --- a/app/views/results/_result_asset.html.erb +++ b/app/views/results/_result_asset.html.erb @@ -1,44 +1,4 @@ <% if can_view_or_download_result_assets(result.my_module) %> -<<<<<<< HEAD - <%= link_to download_asset_path(result.asset), data: {no_turbolink: true} do %> - <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %> - <% if wopi_file?(result.asset) %> -

- <%= file_extension_icon(result.asset) %> - <%= truncate(result.asset.file_file_name, - length: Constants::FILENAME_TRUNCATION_LENGTH) %> -

- <% else %> -

<%= truncate(result.asset.file_file_name, - length: Constants::FILENAME_TRUNCATION_LENGTH) %>

- <% end %> - <% end %> - <% if result.asset.can_perform_action('view') %> - <%= link_to view_asset_url(id: result.asset), - class: 'btn btn-default btn-sm', - target: '_blank', - style: 'display: inline-block' do %> - <%= file_application_icon(result.asset) %> - <%= wopi_button_text(result.asset, 'view') %> - <% end %> - <% end %> - <% if can_edit_result_asset_in_module(result.my_module) && - result.asset.can_perform_action('edit') %> - <%= link_to edit_asset_url(id: result.asset), - class: 'btn btn-default btn-sm', - target: '_blank', - style: 'display: inline-block' do %> - <%= file_application_icon(result.asset) %> - <%= wopi_button_text(result.asset, 'edit') %> - <% end %> - <% end %> -<% else %> - <%= image_tag(preview_asset_path result.asset) if result.asset.is_image? %> -

- <%= file_extension_icon(result.asset) %> - <%= truncate(result.asset.file_file_name, length: 50) %> -

-======= <% if result.asset.file.processing? %> @@ -57,20 +17,15 @@ <% end %> <% else %> <%= link_to download_asset_path(result.asset), data: {no_turbolink: true} do %> -

<%= truncate(result.asset.file_file_name, - length: Constants::FILENAME_TRUNCATION_LENGTH) %>

+ <% if wopi_file?(result.asset) %> + <%= wopi_asset_file_name(result.asset) %> + <% else %> +

<%= truncate(result.asset.file_file_name, + length: Constants::FILENAME_TRUNCATION_LENGTH) %>

+ <% end %> <% end %> + <%= wopi_result_view_file_button(result) %> + <%= wopi_result_edit_file_button(result) %> <% end %> <% end %> -<% else %> - <% if result.asset.file.processing? %> - - <%= image_tag 'medium/processing.gif' %> - - <% else %> - <%= image_tag result.asset.url(:medium) if result.asset.is_image? %> -

<%= result.asset.file_file_name %>

- <% end %> ->>>>>>> 39e1ac4b26f8c8bfb7f5a2f020db13f07d312433 <% end %> diff --git a/app/views/steps/_form_assets.html.erb b/app/views/steps/_form_assets.html.erb index 39197fcc6..1b3cae417 100644 --- a/app/views/steps/_form_assets.html.erb +++ b/app/views/steps/_form_assets.html.erb @@ -50,6 +50,6 @@ <% end %> <% else %> <%= ff.file_field :file %> -<% end %> + <% end %> diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index 8d9f734be..e03e0d6ac 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -103,7 +103,7 @@ length: Constants::FILENAME_TRUNCATION_LENGTH) %>

<% end %> <% else %> - <% render partial: 'wopi_controlls', locals: { asset: asset} %> + <%= render partial: 'steps/wopi_controlls.html.erb', locals: { asset: asset } %> <% end %> <% end %> <% else %> diff --git a/app/views/steps/_wopi_controlls.html.erb b/app/views/steps/_wopi_controlls.html.erb index 90b26bdd9..a04780b98 100644 --- a/app/views/steps/_wopi_controlls.html.erb +++ b/app/views/steps/_wopi_controlls.html.erb @@ -3,33 +3,15 @@ id: true, status: 'asset-present' } do %> <%= image_tag preview_asset_path(asset) if asset.is_image? %> + <% if wopi_file?(asset) %> -

- <%= file_extension_icon(asset) %> - <%= truncate(asset.file_file_name, - length: Constants::FILENAME_TRUNCATION_LENGTH) %> -

+ <%= wopi_asset_file_name(asset) %> <% else %>

<%= truncate(asset.file_file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>

<% end %> <% end %> -<% if asset.can_perform_action('view') %> - <%= link_to view_asset_url(id: asset), - class: 'btn btn-default btn-sm', - target: '_blank', - style: 'display: inline-block' do %> - <%= file_application_icon(asset) %> - <%= wopi_button_text(asset, 'view') %> - <% end %> -<% end %> -<% if can_edit_step_in_protocol(@protocol) && - asset.can_perform_action('edit') %> - <%= link_to edit_asset_url(id: asset), - class: 'btn btn-default btn-sm', - target: '_blank', - style: 'display: inline-block' do %> - <%= file_application_icon(asset) %> - <%= wopi_button_text(asset, 'edit') %> - <% end %> +<%= wopi_asset_view_button(asset) %> +<% if can_edit_step_in_protocol(@protocol) %> + <%= wopi_asset_edit_button(asset) %> <% end %> diff --git a/db/schema.rb b/db/schema.rb index b5070ae3f..130b67a88 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170124135736) do +ActiveRecord::Schema.define(version: 20170306121855) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -54,11 +54,11 @@ ActiveRecord::Schema.define(version: 20170124135736) do 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.string "lock", limit: 1024 t.integer "lock_ttl" t.integer "version", default: 1 - t.integer "estimated_size", default: 0, null: false - t.boolean "file_present", default: false, null: false t.boolean "file_processing" end @@ -100,8 +100,11 @@ ActiveRecord::Schema.define(version: 20170124135736) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "last_modified_by_id" + t.string "type" + t.integer "associated_id" end + add_index "comments", ["associated_id"], name: "index_comments_on_associated_id", using: :btree add_index "comments", ["created_at"], name: "index_comments_on_created_at", using: :btree add_index "comments", ["last_modified_by_id"], name: "index_comments_on_last_modified_by_id", using: :btree add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree @@ -172,13 +175,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do t.string "message", null: false end - create_table "my_module_comments", force: :cascade do |t| - t.integer "my_module_id", null: false - t.integer "comment_id", null: false - end - - add_index "my_module_comments", ["my_module_id", "comment_id"], name: "index_my_module_comments_on_my_module_id_and_comment_id", using: :btree - create_table "my_module_groups", force: :cascade do |t| t.string "name", null: false t.datetime "created_at", null: false @@ -202,24 +198,26 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_index "my_module_tags", ["tag_id"], name: "index_my_module_tags_on_tag_id", using: :btree create_table "my_modules", force: :cascade do |t| - t.string "name", null: false + t.string "name", null: false t.datetime "due_date" t.string "description" - t.integer "x", default: 0, null: false - t.integer "y", default: 0, null: false + t.integer "x", default: 0, null: false + t.integer "y", default: 0, null: false t.integer "my_module_group_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "archived", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "archived", default: false, null: false t.datetime "archived_on" t.integer "created_by_id" t.integer "last_modified_by_id" t.integer "archived_by_id" t.integer "restored_by_id" t.datetime "restored_on" - t.integer "nr_of_assigned_samples", default: 0 - t.integer "workflow_order", default: -1, null: false - t.integer "experiment_id", default: 0, null: false + t.integer "nr_of_assigned_samples", default: 0 + t.integer "workflow_order", default: -1, null: false + t.integer "experiment_id", default: 0, null: false + t.integer "state", limit: 2, default: 0 + t.datetime "completed_on" end add_index "my_modules", ["archived_by_id"], name: "index_my_modules_on_archived_by_id", using: :btree @@ -241,13 +239,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_index "notifications", ["created_at"], name: "index_notifications_on_created_at", using: :btree - create_table "project_comments", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "comment_id", null: false - end - - add_index "project_comments", ["project_id", "comment_id"], name: "index_project_comments_on_project_id_and_comment_id", using: :btree - create_table "projects", force: :cascade do |t| t.string "name", null: false t.integer "visibility", default: 0, null: false @@ -370,13 +361,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_index "result_assets", ["result_id", "asset_id"], name: "index_result_assets_on_result_id_and_asset_id", using: :btree - create_table "result_comments", force: :cascade do |t| - t.integer "result_id", null: false - t.integer "comment_id", null: false - end - - add_index "result_comments", ["result_id", "comment_id"], name: "index_result_comments_on_result_id_and_comment_id", using: :btree - create_table "result_tables", force: :cascade do |t| t.integer "result_id", null: false t.integer "table_id", null: false @@ -413,13 +397,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_index "results", ["restored_by_id"], name: "index_results_on_restored_by_id", using: :btree add_index "results", ["user_id"], name: "index_results_on_user_id", using: :btree - create_table "sample_comments", force: :cascade do |t| - t.integer "sample_id", null: false - t.integer "comment_id", null: false - end - - add_index "sample_comments", ["sample_id", "comment_id"], name: "index_sample_comments_on_sample_id_and_comment_id", using: :btree - create_table "sample_custom_fields", force: :cascade do |t| t.string "value", null: false t.integer "custom_field_id", null: false @@ -498,6 +475,13 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_index "samples_tables", ["team_id"], name: "index_samples_tables_on_team_id", using: :btree add_index "samples_tables", ["user_id"], name: "index_samples_tables_on_user_id", using: :btree + create_table "settings", force: :cascade do |t| + t.text "type", null: false + t.jsonb "values", default: {}, null: false + end + + add_index "settings", ["type"], name: "index_settings_on_type", unique: true, using: :btree + create_table "step_assets", force: :cascade do |t| t.integer "step_id", null: false t.integer "asset_id", null: false @@ -505,13 +489,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_index "step_assets", ["step_id", "asset_id"], name: "index_step_assets_on_step_id_and_asset_id", using: :btree - create_table "step_comments", force: :cascade do |t| - t.integer "step_id", null: false - t.integer "comment_id", null: false - end - - add_index "step_comments", ["step_id", "comment_id"], name: "index_step_comments_on_step_id_and_comment_id", using: :btree - create_table "step_tables", force: :cascade do |t| t.integer "step_id", null: false t.integer "table_id", null: false @@ -747,8 +724,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_foreign_key "experiments", "users", column: "last_modified_by_id" add_foreign_key "experiments", "users", column: "restored_by_id" add_foreign_key "logs", "teams" - add_foreign_key "my_module_comments", "comments" - add_foreign_key "my_module_comments", "my_modules" add_foreign_key "my_module_groups", "experiments" add_foreign_key "my_module_groups", "users", column: "created_by_id" add_foreign_key "my_module_tags", "users", column: "created_by_id" @@ -759,8 +734,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_foreign_key "my_modules", "users", column: "last_modified_by_id" add_foreign_key "my_modules", "users", column: "restored_by_id" add_foreign_key "notifications", "users", column: "generator_user_id" - add_foreign_key "project_comments", "comments" - add_foreign_key "project_comments", "projects" add_foreign_key "projects", "teams" add_foreign_key "projects", "users", column: "archived_by_id" add_foreign_key "projects", "users", column: "created_by_id" @@ -789,8 +762,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_foreign_key "reports", "users", column: "last_modified_by_id" add_foreign_key "result_assets", "assets" add_foreign_key "result_assets", "results" - add_foreign_key "result_comments", "comments" - add_foreign_key "result_comments", "results" add_foreign_key "result_tables", "results" add_foreign_key "result_tables", "tables" add_foreign_key "result_texts", "results" @@ -799,8 +770,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_foreign_key "results", "users", column: "archived_by_id" add_foreign_key "results", "users", column: "last_modified_by_id" add_foreign_key "results", "users", column: "restored_by_id" - add_foreign_key "sample_comments", "comments" - add_foreign_key "sample_comments", "samples" add_foreign_key "sample_custom_fields", "custom_fields" add_foreign_key "sample_custom_fields", "samples" add_foreign_key "sample_groups", "teams" @@ -819,8 +788,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_foreign_key "samples", "users", column: "last_modified_by_id" add_foreign_key "step_assets", "assets" add_foreign_key "step_assets", "steps" - add_foreign_key "step_comments", "comments" - add_foreign_key "step_comments", "steps" add_foreign_key "step_tables", "steps" add_foreign_key "step_tables", "tables" add_foreign_key "steps", "protocols" @@ -831,12 +798,9 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_foreign_key "tags", "projects" add_foreign_key "tags", "users", column: "created_by_id" add_foreign_key "tags", "users", column: "last_modified_by_id" -<<<<<<< HEAD - add_foreign_key "tokens", "users" -======= add_foreign_key "teams", "users", column: "created_by_id" add_foreign_key "teams", "users", column: "last_modified_by_id" ->>>>>>> 39e1ac4b26f8c8bfb7f5a2f020db13f07d312433 + add_foreign_key "tokens", "users" add_foreign_key "user_my_modules", "my_modules" add_foreign_key "user_my_modules", "users" add_foreign_key "user_my_modules", "users", column: "assigned_by_id" @@ -845,14 +809,10 @@ ActiveRecord::Schema.define(version: 20170124135736) do add_foreign_key "user_projects", "projects" add_foreign_key "user_projects", "users" add_foreign_key "user_projects", "users", column: "assigned_by_id" -<<<<<<< HEAD - add_foreign_key "users", "organizations", column: "current_organization_id" - add_foreign_key "wopi_actions", "wopi_apps" - add_foreign_key "wopi_apps", "wopi_discoveries" -======= add_foreign_key "user_teams", "teams" add_foreign_key "user_teams", "users" add_foreign_key "user_teams", "users", column: "assigned_by_id" add_foreign_key "users", "teams", column: "current_team_id" ->>>>>>> 39e1ac4b26f8c8bfb7f5a2f020db13f07d312433 + add_foreign_key "wopi_actions", "wopi_apps" + add_foreign_key "wopi_apps", "wopi_discoveries" end From df2abe5d17e8b9a2be4a48388bdafefe513e2338 Mon Sep 17 00:00:00 2001 From: zmagod Date: Mon, 13 Mar 2017 13:51:31 +0100 Subject: [PATCH 53/54] fixed typo --- app/controllers/result_assets_controller.rb | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb index 3ba66f263..6cdea4b86 100644 --- a/app/controllers/result_assets_controller.rb +++ b/app/controllers/result_assets_controller.rb @@ -55,31 +55,31 @@ class ResultAssetsController < ApplicationController ) ) - format.html { + format.html do flash[:success] = t( "result_assets.create.success_flash", module: @my_module.name) redirect_to results_my_module_path(@my_module) - } - format.json { + end + format.json do render json: { status: 'ok', - html: render_to_string({ - partial: "my_modules/result.html.erb", locals: {result: @result} - }) + html: render_to_string( + partial: 'my_modules/result.html.erb', locals: { result: @result } + ) }, status: :ok - } + end else # This response is sent as 200 OK due to IE security error when # accessing iframe content. - format.json { controller - render json: {status: 'error', errors: @result.errors} - } + format.json do + render json: { status: 'error', errors: @result.errors } + end end end end - def controller controller + def edit respond_to do |format| format.json do render json: { From b821fff4a57a14d2c9488c2dc4a723e8eeb18da8 Mon Sep 17 00:00:00 2001 From: zmagod Date: Mon, 13 Mar 2017 14:14:44 +0100 Subject: [PATCH 54/54] adds non breaking space between file name and buttons --- app/helpers/wopi_helper.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/helpers/wopi_helper.rb b/app/helpers/wopi_helper.rb index ac8465195..eaaeec08d 100644 --- a/app/helpers/wopi_helper.rb +++ b/app/helpers/wopi_helper.rb @@ -1,5 +1,4 @@ module WopiHelper - #include FileIconsHelper def wopi_result_view_file_button(result) if result.asset.can_perform_action('view') link_to view_asset_url(id: result.asset), @@ -57,7 +56,7 @@ module WopiHelper html += "#{file_extension_icon(asset)} " html += truncate(asset.file_file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) - html += '

' + html += ' 

' sanitize_input(html, %w(img)) end end