@@ -21,6 +21,7 @@ class UploadsController < ApplicationController
2121 def download
2222 sanitize_filepath
2323 parse_upload_path
24+ ensure_valid_db_record
2425 track_download if trackable_request?
2526 send_file @sanitized_filepath , disposition : :inline
2627 end
@@ -47,9 +48,33 @@ def parse_upload_path
4748
4849 model_key = path_parts [ 0 ] . downcase
4950 @model_name = MODELS_OVERRIDES [ model_key ] || model_key
51+ @uploader_name = path_parts [ 1 ] . downcase
52+ @record_id = path_parts [ 2 ] . to_i
5053 @filename = path_parts [ 3 ..] . join ( "/" )
5154 end
5255
56+ # extra security step, do not let download antything for removed records or not saved in DB
57+ # we already have sanitized file path that should be enough, but this also will ensure no protected file is downloaded
58+ def ensure_valid_db_record
59+ model_class = @model_name . classify . constantize
60+ record = model_class . find ( @record_id )
61+ uploader = record . public_send ( @uploader_name )
62+ db_filenames = [ uploader . file . file , *uploader . versions . values . map { |v | v . file . file } ] . map { |f | File . basename ( f ) }
63+
64+ unless db_filenames . include? ( File . basename ( @sanitized_filepath ) )
65+ raise_not_found_exception
66+ end
67+ rescue NameError , ActiveRecord ::RecordNotFound
68+ raise_not_found_exception
69+ end
70+
71+ def allowed_models
72+ Dir . entries ( Rails . root . join ( "uploads" ) )
73+ . select { |entry | File . directory? ( Rails . root . join ( "uploads" , entry ) ) }
74+ . reject { |entry | entry . start_with? ( "." ) || entry == "tmp" }
75+ . map { |entry | ALLOWED_MODELS_OVERRIDES [ entry . downcase ] || entry }
76+ end
77+
5378 def track_download
5479 TrackFileDownloadJob . perform_later ( client_id , request . url , @filename , @model_name )
5580 end
0 commit comments