-
Notifications
You must be signed in to change notification settings - Fork 2
Implemented the file use model with access control for file downloads. #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Actions are decoupled from controller logic so that they may be called from a controller or a background job. | ||
| class FileSetAttachFilesActor < Hyrax::Actors::FileSetActor | ||
| ORIGINAL_FILE = ExtendedContainedFiles::ORIGINAL_FILE | ||
| # Spawns asynchronous IngestJob | ||
| # Called from FileSetsController, AttachFilesToWorkJob, ImportURLJob, IngestLocalFileJob | ||
| # @param [Hyrax::UploadedFile, File, ActionDigest::HTTP::UploadedFile] file the file uploaded by the user | ||
| # @param [Symbol, #to_s] relation | ||
| # @return [IngestJob, FalseClass] false on failure, otherwise the queued job | ||
| def create_content(file, relation = ORIGINAL_FILE) | ||
| # If the file set doesn't have a title or label assigned, set a default. | ||
| file_set.label ||= label_for(file) | ||
| file_set.title = [file_set.label] if file_set.title.blank? | ||
| return false unless file_set.save # Need to save to get an id | ||
| IngestJob.perform_later(wrapper!(file, relation)) | ||
| end | ||
|
|
||
| # Spawns asynchronous IngestJob with user notification afterward | ||
| # @param [Hyrax::UploadedFile, File, ActionDigest::HTTP::UploadedFile] file the file uploaded by the user | ||
| # @param [Symbol, #to_s] relation | ||
| # @return [IngestJob] the queued job | ||
| def update_content(file, relation = ORIGINAL_FILE) | ||
| IngestJob.perform_later(wrapper!(file, relation), notification: true) | ||
| end | ||
|
|
||
| # Spawns async ImportUrlJob to attach remote file to fileset | ||
| # @param [#to_s] url | ||
| # @return [IngestUrlJob] the queued job | ||
| def import_url(url, relation = ORIGINAL_FILE) | ||
| file_set.update(import_url: url.to_s) | ||
| operation = Hyrax::Operation.create!(user: user, operation_type: "Attach File") | ||
| ImportUrlWithFileUseJob.perform_later(file_set, operation, url, relation) | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| module Hyrax | ||
| module Actors | ||
| # Creates a work and attaches files to the work | ||
| class CreateWithFilesActor < Hyrax::Actors::AbstractActor | ||
|
||
| # @param [Hyrax::Actors::Environment] env | ||
| # @return [Boolean] true if create was successful | ||
| def create(env) | ||
| uploaded_file_ids = filter_file_ids(env.attributes.delete(:uploaded_files)) | ||
| files = uploaded_files(uploaded_file_ids) | ||
| file_uses = file_use(env.attributes) | ||
| validate_files(files, env) && next_actor.create(env) && attach_files(files, env, file_uses) | ||
| end | ||
|
|
||
| # @param [Hyrax::Actors::Environment] env | ||
| # @return [Boolean] true if update was successful | ||
| def update(env) | ||
| uploaded_file_ids = filter_file_ids(env.attributes.delete(:uploaded_files)) | ||
| files = uploaded_files(uploaded_file_ids) | ||
| file_uses = file_use(env.attributes) | ||
| validate_files(files, env) && next_actor.update(env) && attach_files(files, env, file_uses) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def filter_file_ids(input) | ||
| Array.wrap(input).select(&:present?) | ||
| end | ||
|
|
||
| # ensure that the files we are given are owned by the depositor of the work | ||
| def validate_files(files, env) | ||
| expected_user_id = env.user.id | ||
| files.each do |file| | ||
| if file.user_id != expected_user_id | ||
| Rails.logger.error "User #{env.user.user_key} attempted to ingest file #{file.id} belongs to other users" | ||
| return false | ||
| end | ||
| end | ||
| true | ||
| end | ||
|
|
||
| # @return [TrueClass] | ||
| def attach_files(files, env, file_uses) | ||
| return true unless files | ||
| AttachFilesToFileSetJob.perform_later(env.curation_concern, | ||
| files, file_uses, env.attributes.to_h.symbolize_keys) | ||
| true | ||
| end | ||
|
|
||
| # Fetch uploaded_files from the database | ||
| def uploaded_files(uploaded_file_ids) | ||
| return [] if uploaded_file_ids.empty? | ||
| UploadedFile.find(uploaded_file_ids) | ||
| end | ||
|
|
||
| # File use for fileset | ||
| def file_use(attributes) | ||
| return [] unless attributes.key? :file_use | ||
| attributes.delete(:file_use) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| module Hyrax | ||
| module Actors | ||
| # Attaches remote files to the work | ||
| class CreateWithRemoteFilesActor < Hyrax::Actors::AbstractActor | ||
|
||
| ORIGINAL_FILE = ExtendedContainedFiles::ORIGINAL_FILE | ||
|
|
||
| # @param [Hyrax::Actors::Environment] env | ||
| # @return [Boolean] true if create was successful | ||
| def create(env) | ||
| remote_files = env.attributes.delete(:remote_files) | ||
| file_uses = file_use(env.attributes) | ||
| next_actor.create(env) && attach_files(env, remote_files, file_uses) | ||
| end | ||
|
|
||
| # @param [Hyrax::Actors::Environment] env | ||
| # @return [Boolean] true if update was successful | ||
| def update(env) | ||
| remote_files = env.attributes.delete(:remote_files) | ||
| file_uses = file_use(env.attributes) | ||
| next_actor.update(env) && attach_files(env, remote_files, file_uses) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| # @param [HashWithIndifferentAccess] remote_files | ||
| # @return [TrueClass] | ||
| def attach_files(env, remote_files, file_uses) | ||
| return true unless remote_files | ||
|
|
||
| ingest_remote_files(env, remote_files, file_uses) | ||
|
|
||
| true | ||
| end | ||
|
|
||
| # Generic utility for creating FileSet from a URL | ||
| # Used in to import files using URLs from a file picker like browse_everything | ||
| def create_file_from_url(env, file_infos, file_uses) | ||
| fs = create_file_set(env) | ||
| file_infos.each_with_index do |file_info, index| | ||
| fs.update(import_url: file_info[:url].to_s, label: file_info[:file_name]) | ||
| uri = URI.parse(URI.encode(file_info[:url])) | ||
| if uri.scheme == 'file' | ||
| IngestLocalFileJob.perform_later(fs, URI.decode(uri.path), env.user, file_uses[index]) | ||
| else | ||
| ImportUrlWithFileUseJob.perform_later(fs, operation_for(user: env.user), uri, file_uses[index]) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def operation_for(user:) | ||
| Hyrax::Operation.create!(user: user, | ||
| operation_type: "Attach Remote File") | ||
| end | ||
|
|
||
| # File use for fileset | ||
| def file_use(attributes) | ||
| return [] unless attributes.key? :file_use | ||
| attributes[:file_use] | ||
| end | ||
|
|
||
| def create_file_set(env) | ||
| work_permissions = env.curation_concern.permissions.map(&:to_hash) | ||
| ::FileSet.new do |fs| | ||
| actor = Hyrax::Actors::FileSetActor.new(fs, env.user) | ||
| actor.create_metadata(visibility: env.curation_concern.visibility) | ||
| actor.attach_to_work(env.curation_concern) | ||
| fs.permissions_attributes = work_permissions | ||
| fs.save! | ||
| end | ||
| end | ||
|
|
||
| # Arrange and attach the remote files basing on file use provided | ||
| # @param [Hyrax::Actors::Environment] env | ||
| # @param [Array] remote_files | ||
| # @param [Array] file_uses | ||
| def ingest_remote_files(env, remote_files, file_uses) | ||
| file_set_urls = [] | ||
| file_set_file_uses = [] | ||
| remote_files.each_with_index do |file_info, index| | ||
| next if file_info.blank? || file_info[:url].blank? | ||
|
|
||
| file_use = file_uses.present? && file_uses.count > index ? file_uses[index] : ORIGINAL_FILE | ||
| attach_remote_files_to_file_set(env, file_set_urls, file_set_file_uses) if file_use == ORIGINAL_FILE | ||
| file_set_file_uses << file_use | ||
| file_set_urls << file_info | ||
| end | ||
|
|
||
| # Files in the last file set that need attached | ||
| attach_remote_files_to_file_set(env, file_set_urls, file_set_file_uses) | ||
| end | ||
|
|
||
| # Attach files with file uses to a FileSet | ||
| # @param [Array] file_set_urls | ||
| # @param [Array] file_set_file_uses | ||
| def attach_remote_files_to_file_set(env, file_set_urls, file_set_file_uses) | ||
| # One original_file one FileSet: multiple files with different file uses will be attached to a FileSet | ||
| return unless file_set_urls.count.positive? | ||
| create_file_from_url(env, file_set_urls.select { |f| f }, file_set_file_uses.select { |u| u }) | ||
| file_set_urls.clear | ||
| file_set_file_uses.clear | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,54 @@ | |
|
|
||
| class FileSetIndexer < Hyrax::FileSetIndexer | ||
| self.thumbnail_path_service = ::ThumbnailPathService | ||
|
|
||
| def generate_solr_document | ||
|
||
| super.tap do |solr_doc| | ||
| file_uses.each do |file_use| | ||
| file_metadata = file_metadata(object.send(file_use)) | ||
| file_metadata = file_metadata.merge(file_use: file_use) | ||
| Solrizer.insert_field(solr_doc, "files_json", file_metadata.to_json, :stored_searchable) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def file_uses | ||
| [].tap do |process| | ||
| process << :original_file if object.original_file | ||
| process << :preservation_master_file if object.preservation_master_file | ||
| process << :transcript if object.transcript | ||
| end | ||
| end | ||
|
|
||
| # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity | ||
| def file_metadata(file) | ||
| {}.tap do |attrs| | ||
| attrs[:uri] = file.uri.to_s | ||
| attrs[:label] = file.label.first if file.label.present? | ||
| attrs[:file_name] = file.file_name.first | ||
| attrs[:file_format] = format(file) | ||
| attrs[:file_size] = file.file_size.present? ? file.file_size.first : file.content.size | ||
| attrs[:height] = Integer(file.height.first) if file.height.present? | ||
| attrs[:width] = Integer(file.width.first) if file.width.present? | ||
| attrs[:mime_type] = file.mime_type | ||
| attrs[:digest] = file.digest.first.to_s | ||
| attrs[:file_title] = file.file_title.first if file.file_title.present? | ||
| attrs[:duration] = file.duration.first if file.duration.present? | ||
| attrs[:sample_rate] = file.sample_rate.first if file.sample_rate.present? | ||
| attrs[:original_checksum] = file.original_checksum.first | ||
| attrs[:date_uploaded] = file.date_modified.present? ? file.date_modified.first : file.create_date | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def format(file) | ||
| if file.mime_type.present? && file.format_label.present? | ||
| "#{file.mime_type.split('/').last} (#{file.format_label.join(', ')})" | ||
| elsif file.mime_type.present? | ||
| file.mime_type.split('/').last | ||
| elsif file.format_label.present? | ||
| file.format_label | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Converts UploadedFiles into FileSets and attaches them to works. | ||
| class AttachFilesToFileSetJob < AttachFilesToWorkJob | ||
|
||
| queue_as Hyrax.config.ingest_queue_name | ||
|
|
||
| ORIGINAL_FILE = ExtendedContainedFiles::ORIGINAL_FILE | ||
|
|
||
| # @param [ActiveFedora::Base] work - the work object | ||
| # @param [Array<Hyrax::UploadedFile>] uploaded_files - an array of files to attach | ||
| def perform(work, uploaded_files, file_uses, **work_attributes) | ||
| user = User.find_by_user_key(work.depositor) | ||
|
|
||
| file_set_files = [] | ||
| file_set_file_uses = [] | ||
| uploaded_files.each_with_index do |uploaded_file, index| | ||
| file_use = file_uses.present? && file_uses.count > index ? file_uses[index] : ORIGINAL_FILE | ||
|
|
||
| # One original_file one FileSet: multiple files with different file uses will be attached to a FileSet | ||
| attach_files(user, work, work_attributes, file_set_files, file_set_file_uses) if file_use == ORIGINAL_FILE | ||
|
|
||
| file_set_file_uses << file_use | ||
| file_set_files << uploaded_file | ||
| end | ||
|
|
||
| # Files in the last file set that need attached | ||
| attach_files(user, work, work_attributes, file_set_files, file_set_file_uses) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| # @param [Hyrax::Actors::FileSetActor] actor | ||
| # @param [Hyrax::UploadedFile] uploaded_file .uploader.file must be a CarrierWave::SanitizedFile or | ||
| # .uploader.url must be present | ||
| def attach_content(actor, uploaded_file, relation = ORIGINAL_FILE) | ||
| file_uploader = uploaded_file.uploader | ||
| if file_uploader.file.is_a? CarrierWave::SanitizedFile | ||
| actor.create_content(file_uploader.file.to_file, relation) | ||
| elsif file_uploader.url.present? | ||
| actor.import_url(file_uploader.url, relation) | ||
| else | ||
| raise ArgumentError, "#{file_uploader.class} received with #{file_uploader.file.class} object and no URL" | ||
| end | ||
| end | ||
|
|
||
| # Create file set | ||
| def create_file_set(user, work, work_attributes, work_permissions) | ||
| ::FileSet.new do |fs| | ||
| actor = Hyrax::Actors::FileSetActor.new(fs, user) | ||
| actor.create_metadata(visibility_attributes(work_attributes)) | ||
| actor.attach_to_work(work) | ||
| fs.permissions_attributes = work_permissions | ||
| fs.save! | ||
| end | ||
| end | ||
|
|
||
| # Attached files to FileSet | ||
| # @param [::FileSet] file_set | ||
| # @param [Hyrax::UploadedFile] file_set_files | ||
| # @param [Array] file_set_file_uses | ||
| def attach_files(user, work, work_attributes, file_set_files, file_uses) | ||
| return unless file_set_files.count.positive? | ||
|
|
||
| work_permissions = work.permissions.map(&:to_hash) | ||
| file_set = create_file_set(user, work, work_attributes, work_permissions) | ||
|
|
||
| file_set_files.each_with_index do |uploaded_file, index| | ||
| file_use = file_uses.present? && file_uses.count > index ? file_uses[index] : ORIGINAL_FILE | ||
|
|
||
| uploaded_file.update(file_set_uri: file_set.uri) | ||
| actor = FileSetAttachFilesActor.new(file_set, user) | ||
| attach_content(actor, uploaded_file, file_use) | ||
| end | ||
| file_set_files.clear | ||
| file_uses.clear | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What change was needed here that required us overriding the (presumably) upstream
FileSetAttachFilesActor?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we just extend/override the Hyrax::Actors::FileSetActor to accept a relation/file use parameter.