From 781db39bc06c25a94b96c4ec295f0f23dfe2fb9c Mon Sep 17 00:00:00 2001 From: Peter Mangiafico Date: Mon, 24 Jul 2017 12:50:08 -0700 Subject: [PATCH 1/4] initial commit to protect file uploads and change their location --- app/controllers/object_files_controller.rb | 16 ++++++++++++++++ app/models/hydrus/contentable.rb | 4 ++-- app/models/hydrus/object_file.rb | 5 +++-- app/uploaders/file_uploader.rb | 11 ++++++++--- config/environment.rb | 2 -- config/routes.rb | 9 +++++++-- config/settings.yml | 1 + lib/tasks/fixtures.rake | 8 ++++---- spec/controllers/object_files_controller_spec.rb | 5 +++++ spec/models/hydrus/contentable_spec.rb | 2 +- uploads/.keep | 0 11 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 app/controllers/object_files_controller.rb create mode 100644 spec/controllers/object_files_controller_spec.rb create mode 100644 uploads/.keep diff --git a/app/controllers/object_files_controller.rb b/app/controllers/object_files_controller.rb new file mode 100644 index 000000000..fdf262450 --- /dev/null +++ b/app/controllers/object_files_controller.rb @@ -0,0 +1,16 @@ +class ObjectFilesController < ApplicationController + + before_filter :authenticate_user! + + def show + @fobj = Hydrus::Item.find(params[:id]) + @fobj.current_user = current_user + authorize! :read, @fobj # only users who are authorized to view this object and download the files + filename = params[:filename] + object_file = Hydrus::ObjectFile.new(:pid=>DruidTools::Druid.new(@fobj.pid).druid) + file_location = File.join(object_file.file.store_dir,filename) + file = File.new(file_location) + send_file file + end + +end diff --git a/app/models/hydrus/contentable.rb b/app/models/hydrus/contentable.rb index be0eb9011..116fed90c 100644 --- a/app/models/hydrus/contentable.rb +++ b/app/models/hydrus/contentable.rb @@ -67,8 +67,8 @@ def parent_directory end def base_file_directory - f = File.join(Rails.root, 'public', Hydrus::Application.config.file_upload_path) - DruidTools::Druid.new(pid, f).path + file_object = Hydrus::ObjectFile.new(:pid=>DruidTools::Druid.new(pid).druid) + file_location = File.join(file_object.file.base_dir) end def content_directory diff --git a/app/models/hydrus/object_file.rb b/app/models/hydrus/object_file.rb index 468168c63..857aa9d15 100644 --- a/app/models/hydrus/object_file.rb +++ b/app/models/hydrus/object_file.rb @@ -7,9 +7,10 @@ class Hydrus::ObjectFile < ActiveRecord::Base def size file.size end - + + # Override the URL supplied by CarrierWave def url - file.url + Rails.application.routes.url_helpers.file_upload_path(:id=>pid,:filename=>filename) end def current_path diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index b235605ae..5126aab69 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -43,14 +43,19 @@ def delete_tmp_dir(new_file) end end - # Override the directory where uploaded files will be stored. + # Set the base object directory name + def base_dir + File.join(Rails.root, Settings.hydrus.file_upload_path, DruidTools::Druid.new(model.pid).path) + end + + # Set the directory where uploaded files will be stored. def store_dir - DruidTools::Druid.new(model.pid, Hydrus::Application.config.file_upload_path).path('content') + File.join(base_dir, 'content') end # temp directory where files are stored before they are uploaded def cache_dir - File.join(root, Hydrus::Application.config.file_upload_path, 'tmp') + File.join(Rails.root,'tmp') end # Provide a default URL as a default if there hasn't been a file uploaded: diff --git a/config/environment.rb b/config/environment.rb index 1dd9f6d95..264d23f18 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -5,8 +5,6 @@ Rails.application.initialize! Hydrus::Application.configure do - # this is the path from the root of the public folder into which file uploads will be stored - config.file_upload_path = 'uploads' # file attributes by mimetype, including defaults, to use when generating content metadata config.cm_file_attributes = { diff --git a/config/routes.rb b/config/routes.rb index 49fb68a80..9326d55d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -54,6 +54,11 @@ match 'dor/reindex/:id' => 'hydrus_solr#reindex', :as => 'reindex', via: [:get, :post, :put] match 'dor/delete_from_index/:id' => 'hydrus_solr#delete_from_index', :as => 'delete_from_index', via: [:get, :post] - get '/404', to: 'exceptions#render_404' - get '/500', to: 'exceptions#render_500' + # Action to get an uploaded file + constraints filename: %r{[^/]+} do + get '/file/:id/:filename' => 'object_files#show', :as => 'file_upload' + end + + get '/404', :to => "exceptions#render_404" + get '/500', :to => "exceptions#render_500" end diff --git a/config/settings.yml b/config/settings.yml index d8335a595..1b8bff711 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -3,6 +3,7 @@ hydrus: exception_error_page: true # if true, a generic error page will be shown with no exception messages; if false, standard Rails exceptions are shown directly to the user exception_error_panel: false host: 'hydrus-test.stanford.edu' + file_upload_path: 'uploads' exception_recipients: '' ur_apo_druid: 'druid:bb000bb0000' project_tag: 'Project : Hydrus' diff --git a/lib/tasks/fixtures.rake b/lib/tasks/fixtures.rake index aacfc216e..f05f46ca3 100644 --- a/lib/tasks/fixtures.rake +++ b/lib/tasks/fixtures.rake @@ -97,7 +97,7 @@ namespace :hydrus do end end - desc 'reload test uploaded files to public/upload directory' + desc "reload test uploaded files to upload directory" task :refresh_upload_files do # Copies fixture files from source control to the app's public area: # source: spec/fixtures/files/DRUID/* @@ -106,7 +106,7 @@ namespace :hydrus do require File.expand_path('config/environment') app_base = File.expand_path('../../../', __FILE__) src_base = File.join(app_base, 'spec/fixtures/files') - dst_base = File.join(app_base, 'public', Hydrus::Application.config.file_upload_path) + dst_base = File.join(app_base, Settings.hydrus.file_upload_path) FIXTURE_PIDS.each do |pid| pid.gsub!('druid:', '') src = File.join(src_base, pid) @@ -119,12 +119,12 @@ namespace :hydrus do end end - desc 'clear uploaded files [public/upload] directory' + desc "clear uploaded files upload directory" task :clear_upload_files do puts 'clearing upload files directory' require File.expand_path('config/environment') app_base = File.expand_path('../../../', __FILE__) - dst_base = File.join(app_base, 'public', Hydrus::Application.config.file_upload_path) + dst_base = File.join(app_base, Settings.hydrus.file_upload_path) puts "Removing all folders in #{dst_base}" all_folders = Dir.glob("#{dst_base}/*") all_folders.each do |folder| diff --git a/spec/controllers/object_files_controller_spec.rb b/spec/controllers/object_files_controller_spec.rb new file mode 100644 index 000000000..adf53a483 --- /dev/null +++ b/spec/controllers/object_files_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe ObjectFilesController, :type => :controller do + +end diff --git a/spec/models/hydrus/contentable_spec.rb b/spec/models/hydrus/contentable_spec.rb index 215c1d2ae..ab2dce42b 100644 --- a/spec/models/hydrus/contentable_spec.rb +++ b/spec/models/hydrus/contentable_spec.rb @@ -4,7 +4,7 @@ before(:each) do @go = Hydrus::GenericObject.new @pid = 'oo000oo9999' - @base_dir = '/oo/000/oo/9999/oo000oo9999' + @base_dir = File.join(Rails.root,Settings.hydrus.file_upload_path,'./oo/000/oo/9999/oo000oo9999') allow(@go).to receive(:pid).and_return(@pid) end diff --git a/uploads/.keep b/uploads/.keep new file mode 100644 index 000000000..e69de29bb From 156dd0cd5d3867ebbaf4916e6bda7da7da780233 Mon Sep 17 00:00:00 2001 From: Peter Mangiafico Date: Mon, 24 Jul 2017 12:51:43 -0700 Subject: [PATCH 2/4] updates for new upload folder location --- .gitignore | 1 + config/deploy.rb | 2 +- spec/features/item_edit_spec.rb | 6 +++--- spec/features/models/object_file_spec.rb | 3 +-- spec/support/helpers.rb | 12 +++++------- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index d0f88d664..63de86b7a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ coverage/* ## PROJECT::GENERAL rdoc public/uploads +uploads /public/assets doc pkg diff --git a/config/deploy.rb b/config/deploy.rb index b6889989d..cd5b2bb01 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -25,7 +25,7 @@ ) # Default value for linked_dirs is [] -set :linked_dirs, %w{log config/certs config/settings tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads} +set :linked_dirs, %w{log config/certs config/settings tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads uploads} # Default value for default_env is {} # set :default_env, { path: "/opt/ruby/bin:$PATH" } diff --git a/spec/features/item_edit_spec.rb b/spec/features/item_edit_spec.rb index 5ab6ec57e..20e16f8f1 100644 --- a/spec/features/item_edit_spec.rb +++ b/spec/features/item_edit_spec.rb @@ -653,7 +653,7 @@ # Visit edit page: delete a file. # Make corresponding changes in the exp hash. i = 1 - file_url = Hydrus::ObjectFile.find(i).url + object_file = Hydrus::ObjectFile.find(i) should_visit_edit_page(@hi) css_del = "delete_file_#{i}" click_link(css_del) @@ -668,8 +668,8 @@ check_file_info.call(exp) # Restore the deleted file. - restore_upload_file(file_url) - expect(File.exists?('public' + file_url)).to eq(true) + restore_upload_file(object_file) + expect(File.exists?(object_file.current_path)).to eq(true) end end end diff --git a/spec/features/models/object_file_spec.rb b/spec/features/models/object_file_spec.rb index 2ded07d93..904869960 100644 --- a/spec/features/models/object_file_spec.rb +++ b/spec/features/models/object_file_spec.rb @@ -24,7 +24,6 @@ files = @hi.files expect(files.size).to eq(4) file = files.first - file_url = file.url full_file_path = file.current_path expect(File.exists?(full_file_path)).to be_truthy @@ -36,7 +35,7 @@ expect(File.exists?(full_file_path)).to be_falsey # restore original file and stream from fixtures - restore_upload_file(file_url) + restore_upload_file(file) expect(File.exists?(full_file_path)).to be_truthy end end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index 4af73745c..9338b9fe8 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -216,13 +216,11 @@ def create_new_item(opts = {}) Hydrus::Item.find(druid) end -# Takes the file_url of an Item's uploaded file. +# Takes an objet_file object. # Helper method to restore a file to the uploads directory # after it was deleted in a integration test. -def restore_upload_file(file_url) - parts = file_url.split /\// - parts[0] = 'public' - dst = File.join(*parts) - src = File.join('spec/fixtures/files', parts[-3], parts[-1]) - FileUtils.cp(src, dst) +def restore_upload_file(object_file) + src = File.join('spec/fixtures/files', DruidTools::Druid.new(object_file.pid).id, object_file.filename) + dst = object_file.current_path + FileUtils.cp(src, dst) end From 2b1e96e7e399360fbfe4d4f1f326828da33f10b8 Mon Sep 17 00:00:00 2001 From: Peter Mangiafico Date: Tue, 25 Jul 2017 12:14:11 -0700 Subject: [PATCH 3/4] add tests for new mediated download links --- app/controllers/object_files_controller.rb | 10 +++--- app/models/hydrus/contentable.rb | 4 +-- app/models/hydrus/object_file.rb | 4 +-- app/uploaders/file_uploader.rb | 4 +-- config/environment.rb | 1 - config/routes.rb | 4 +-- lib/tasks/fixtures.rake | 4 +-- .../object_files_controller_spec.rb | 5 --- spec/features/home_page_spec.rb | 2 +- spec/features/item_create_spec.rb | 1 - spec/features/models/object_file_spec.rb | 5 ++- spec/features/object_files_controller_spec.rb | 31 +++++++++++++++++++ spec/models/hydrus/contentable_spec.rb | 2 +- spec/support/helpers.rb | 6 ++-- 14 files changed, 52 insertions(+), 31 deletions(-) delete mode 100644 spec/controllers/object_files_controller_spec.rb create mode 100644 spec/features/object_files_controller_spec.rb diff --git a/app/controllers/object_files_controller.rb b/app/controllers/object_files_controller.rb index fdf262450..f32bbf075 100644 --- a/app/controllers/object_files_controller.rb +++ b/app/controllers/object_files_controller.rb @@ -1,16 +1,14 @@ class ObjectFilesController < ApplicationController - - before_filter :authenticate_user! + before_action :authenticate_user! def show @fobj = Hydrus::Item.find(params[:id]) @fobj.current_user = current_user authorize! :read, @fobj # only users who are authorized to view this object and download the files - filename = params[:filename] - object_file = Hydrus::ObjectFile.new(:pid=>DruidTools::Druid.new(@fobj.pid).druid) - file_location = File.join(object_file.file.store_dir,filename) + filename = params[:filename] + object_file = Hydrus::ObjectFile.new(pid: DruidTools::Druid.new(@fobj.pid).druid) + file_location = File.join(object_file.file.store_dir, filename) file = File.new(file_location) send_file file end - end diff --git a/app/models/hydrus/contentable.rb b/app/models/hydrus/contentable.rb index 116fed90c..9d09a183d 100644 --- a/app/models/hydrus/contentable.rb +++ b/app/models/hydrus/contentable.rb @@ -67,8 +67,8 @@ def parent_directory end def base_file_directory - file_object = Hydrus::ObjectFile.new(:pid=>DruidTools::Druid.new(pid).druid) - file_location = File.join(file_object.file.base_dir) + file_object = Hydrus::ObjectFile.new(pid: DruidTools::Druid.new(pid).druid) + File.join(file_object.file.base_dir) end def content_directory diff --git a/app/models/hydrus/object_file.rb b/app/models/hydrus/object_file.rb index 857aa9d15..c5feb08e5 100644 --- a/app/models/hydrus/object_file.rb +++ b/app/models/hydrus/object_file.rb @@ -7,10 +7,10 @@ class Hydrus::ObjectFile < ActiveRecord::Base def size file.size end - + # Override the URL supplied by CarrierWave def url - Rails.application.routes.url_helpers.file_upload_path(:id=>pid,:filename=>filename) + Rails.application.routes.url_helpers.file_upload_path(id: pid, filename: filename) end def current_path diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 5126aab69..7f6df08e2 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -47,7 +47,7 @@ def delete_tmp_dir(new_file) def base_dir File.join(Rails.root, Settings.hydrus.file_upload_path, DruidTools::Druid.new(model.pid).path) end - + # Set the directory where uploaded files will be stored. def store_dir File.join(base_dir, 'content') @@ -55,7 +55,7 @@ def store_dir # temp directory where files are stored before they are uploaded def cache_dir - File.join(Rails.root,'tmp') + File.join(Rails.root, Settings.hydrus.file_upload_path, 'tmp') end # Provide a default URL as a default if there hasn't been a file uploaded: diff --git a/config/environment.rb b/config/environment.rb index 264d23f18..f0b909956 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -5,7 +5,6 @@ Rails.application.initialize! Hydrus::Application.configure do - # file attributes by mimetype, including defaults, to use when generating content metadata config.cm_file_attributes = { 'default' => { publish: 'yes', preserve: 'yes', shelve: 'yes' } diff --git a/config/routes.rb b/config/routes.rb index 9326d55d1..6221c9420 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -59,6 +59,6 @@ get '/file/:id/:filename' => 'object_files#show', :as => 'file_upload' end - get '/404', :to => "exceptions#render_404" - get '/500', :to => "exceptions#render_500" + get '/404', to: 'exceptions#render_404' + get '/500', to: 'exceptions#render_500' end diff --git a/lib/tasks/fixtures.rake b/lib/tasks/fixtures.rake index f05f46ca3..45aa6548a 100644 --- a/lib/tasks/fixtures.rake +++ b/lib/tasks/fixtures.rake @@ -97,7 +97,7 @@ namespace :hydrus do end end - desc "reload test uploaded files to upload directory" + desc 'reload test uploaded files to upload directory' task :refresh_upload_files do # Copies fixture files from source control to the app's public area: # source: spec/fixtures/files/DRUID/* @@ -119,7 +119,7 @@ namespace :hydrus do end end - desc "clear uploaded files upload directory" + desc 'clear uploaded files upload directory' task :clear_upload_files do puts 'clearing upload files directory' require File.expand_path('config/environment') diff --git a/spec/controllers/object_files_controller_spec.rb b/spec/controllers/object_files_controller_spec.rb deleted file mode 100644 index adf53a483..000000000 --- a/spec/controllers/object_files_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe ObjectFilesController, :type => :controller do - -end diff --git a/spec/features/home_page_spec.rb b/spec/features/home_page_spec.rb index 09355ee50..e03713cb3 100644 --- a/spec/features/home_page_spec.rb +++ b/spec/features/home_page_spec.rb @@ -69,7 +69,7 @@ it 'breadcrumbs should not be displayed' do # Logged out - logout + sign_out visit root_path expect(page).not_to have_css(@breadcrumbs) end diff --git a/spec/features/item_create_spec.rb b/spec/features/item_create_spec.rb index 6760837c8..599998c13 100644 --- a/spec/features/item_create_spec.rb +++ b/spec/features/item_create_spec.rb @@ -125,7 +125,6 @@ f.file = Tempfile.new('mock_HydrusObjectFile_') f.save click_button(@buttons[:save]) - # confirm validation message is shown and publish button is not available expect(find(@div_alert)).to have_content(@notices[:save]) expect(find(@div_alert)).to have_content('Contributors must be entered') diff --git a/spec/features/models/object_file_spec.rb b/spec/features/models/object_file_spec.rb index 904869960..9efa4ae39 100644 --- a/spec/features/models/object_file_spec.rb +++ b/spec/features/models/object_file_spec.rb @@ -10,9 +10,9 @@ it 'finds four files associated with the first item and it grabs the url of a given file' do expect(files.size).to eq(4) f = files[0] - exp_url = '/uploads/bb/123/bb/1234/bb123bb1234/content/pinocchio.htm' + exp_url = '/file/druid:bb123bb1234/pinocchio.htm' expect(f.url).to eq(exp_url) - expect(f.current_path).to eq("#{Rails.root}/public#{exp_url}") + expect(f.current_path).to eq("#{Rails.root}/uploads/bb/123/bb/1234/bb123bb1234/content/pinocchio.htm") expect(files[1].filename).to eq(%q{pinocchio characters tc in file name.pdf}) expect(files[1].size).to be > 0 end @@ -24,7 +24,6 @@ files = @hi.files expect(files.size).to eq(4) file = files.first - file_url = file.url full_file_path = file.current_path expect(File.exists?(full_file_path)).to be_truthy diff --git a/spec/features/object_files_controller_spec.rb b/spec/features/object_files_controller_spec.rb new file mode 100644 index 000000000..eea21658f --- /dev/null +++ b/spec/features/object_files_controller_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe('Object Files Download', type: :request, integration: true) do + fixtures :object_files + let(:archivist1) { create :archivist1 } + let(:archivist99) { create :archivist99 } + + before :each do + @druid = 'druid:bb123bb1234' + @file = Hydrus::ObjectFile.find(3) # this txt file defined in the fixtures belongs to archivist1 + end + + it 'allows the owner of the file to download it' do + sign_in(archivist1) # owner of the item can download the file + visit @file.url + expect(page.status_code).to eq(200) + expect(page.response_headers['Content-Type']).to eq('text/plain') + logout(archivist1) + end + + it 'redirects to home page with not authorized message when accessing a file URL when no access is allowed' do + sign_in(archivist99) # no view access on the item, no access + visit @file.url + expect(page.response_headers['Content-Type']).not_to eq('text/plain') + expect(current_path).to eq(root_path) + expect(find('#flash-notices div.alert')).to have_content('You are not authorized to access this page.') + logout(archivist99) + visit @file.url + expect(current_path).to eq(root_path) # logged out, still can't get the file + end +end diff --git a/spec/models/hydrus/contentable_spec.rb b/spec/models/hydrus/contentable_spec.rb index ab2dce42b..463503720 100644 --- a/spec/models/hydrus/contentable_spec.rb +++ b/spec/models/hydrus/contentable_spec.rb @@ -4,7 +4,7 @@ before(:each) do @go = Hydrus::GenericObject.new @pid = 'oo000oo9999' - @base_dir = File.join(Rails.root,Settings.hydrus.file_upload_path,'./oo/000/oo/9999/oo000oo9999') + @base_dir = File.join(Rails.root, Settings.hydrus.file_upload_path, './oo/000/oo/9999/oo000oo9999') allow(@go).to receive(:pid).and_return(@pid) end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index 9338b9fe8..7024781b7 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -220,7 +220,7 @@ def create_new_item(opts = {}) # Helper method to restore a file to the uploads directory # after it was deleted in a integration test. def restore_upload_file(object_file) - src = File.join('spec/fixtures/files', DruidTools::Druid.new(object_file.pid).id, object_file.filename) - dst = object_file.current_path - FileUtils.cp(src, dst) + src = File.join('spec/fixtures/files', DruidTools::Druid.new(object_file.pid).id, object_file.filename) + dst = object_file.current_path + FileUtils.cp(src, dst) end From cd3a59869e1b4855d7b21b7afcb0a90f40d8c077 Mon Sep 17 00:00:00 2001 From: Peter Mangiafico Date: Thu, 23 Jan 2020 15:41:02 -0800 Subject: [PATCH 4/4] remove public/uploads dir from symlinked directories --- config/deploy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deploy.rb b/config/deploy.rb index cd5b2bb01..8457be4c7 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -25,7 +25,7 @@ ) # Default value for linked_dirs is [] -set :linked_dirs, %w{log config/certs config/settings tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads uploads} +set :linked_dirs, %w{log config/certs config/settings tmp/pids tmp/cache tmp/sockets vendor/bundle public/system uploads} # Default value for default_env is {} # set :default_env, { path: "/opt/ruby/bin:$PATH" }