Use active storage load hook to extend blob

This commit is contained in:
Paul Chavard 2019-05-16 18:59:34 +02:00
parent d5f54de0a0
commit 42235e81b1
7 changed files with 58 additions and 40 deletions

View file

@ -23,28 +23,21 @@ class ActiveStorage::VirusScanner
blob.metadata[:virus_scan_result] == SAFE blob.metadata[:virus_scan_result] == SAFE
end end
def analyzed? def done?
started? && blob.metadata[:virus_scan_result] != PENDING
end
def started?
blob.metadata[:virus_scan_result].present? blob.metadata[:virus_scan_result].present?
end end
def analyze_later
if !analyzed?
blob.update!(metadata: blob.metadata.merge(virus_scan_result: PENDING))
VirusScannerJob.perform_later(blob)
end
end
def metadata def metadata
begin download_blob_to_tempfile do |file|
download_blob_to_tempfile do |file| if ClamavService.safe_file?(file.path)
if ClamavService.safe_file?(file.path) { virus_scan_result: SAFE, scanned_at: Time.zone.now }
{ virus_scan_result: SAFE, scanned_at: Time.zone.now } else
else { virus_scan_result: INFECTED, scanned_at: Time.zone.now }
{ virus_scan_result: INFECTED, scanned_at: Time.zone.now }
end
end end
rescue StandardError => e
Raven.capture_exception(e)
end end
end end
end end

View file

@ -0,0 +1,24 @@
module BlobVirusScanner
extend ActiveSupport::Concern
included do
before_create :set_pending
after_update_commit :enqueue_virus_scan
end
def virus_scanner
ActiveStorage::VirusScanner.new(self)
end
private
def set_pending
self.metadata[:virus_scan_result] ||= ActiveStorage::VirusScanner::PENDING
end
def enqueue_virus_scan
if analyzed? && !virus_scanner.done?
VirusScannerJob.perform_later(self)
end
end
end

View file

@ -1,10 +1,10 @@
- if pj.attached? - if pj.attached?
.pj-link .pj-link
- if pj.virus_scanner.safe? || !pj.virus_scanner.analyzed? - if pj.virus_scanner.safe? || !pj.virus_scanner.started?
= link_to url_for(pj), target: '_blank', rel: 'noopener', title: "Télécharger la pièce jointe" do = link_to url_for(pj), target: '_blank', rel: 'noopener', title: "Télécharger la pièce jointe" do
%span.icon.attachment %span.icon.attachment
= pj.filename.to_s = pj.filename.to_s
- if !pj.virus_scanner.analyzed? - if !pj.virus_scanner.started?
(ce fichier na pas été analysé par notre antivirus, téléchargez-le avec précaution) (ce fichier na pas été analysé par notre antivirus, téléchargez-le avec précaution)
- else - else

View file

@ -10,16 +10,6 @@ ActiveStorage::Service.url_expires_in = 1.hour
# The way analyzable blob interface work is by running the first accepted analyzer. # The way analyzable blob interface work is by running the first accepted analyzer.
# This is not what we want for the virus scan. Using analyzer interface is still beneficial # This is not what we want for the virus scan. Using analyzer interface is still beneficial
# as it takes care of downloading the blob. # as it takes care of downloading the blob.
ActiveStorage::Attachment.class_eval do
after_create_commit :virus_scan
private
def virus_scan
ActiveStorage::VirusScanner.new(blob).analyze_later
end
end
ActiveStorage::Attached::One.class_eval do ActiveStorage::Attached::One.class_eval do
def virus_scanner def virus_scanner
if attached? if attached?
@ -27,3 +17,5 @@ ActiveStorage::Attached::One.class_eval do
end end
end end
end end
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }

View file

@ -59,23 +59,32 @@ describe Gestionnaires::AvisController, type: :controller do
avis_without_answer.reload avis_without_answer.reload
end end
it { expect(response).to redirect_to(instruction_gestionnaire_avis_path(avis_without_answer)) } it 'should be ok' do
it { expect(avis_without_answer.answer).to eq('answer') } expect(response).to redirect_to(instruction_gestionnaire_avis_path(avis_without_answer))
it { expect(avis_without_answer.piece_justificative_file).to_not be_attached } expect(avis_without_answer.answer).to eq('answer')
it { expect(flash.notice).to eq('Votre réponse est enregistrée.') } expect(avis_without_answer.piece_justificative_file).to_not be_attached
expect(flash.notice).to eq('Votre réponse est enregistrée.')
end
end end
describe 'with attachment' do describe 'with attachment' do
include ActiveJob::TestHelper
let(:file) { Rack::Test::UploadedFile.new("./spec/fixtures/files/piece_justificative_0.pdf", 'application/pdf') } let(:file) { Rack::Test::UploadedFile.new("./spec/fixtures/files/piece_justificative_0.pdf", 'application/pdf') }
before do before do
post :update, params: { id: avis_without_answer.id, avis: { answer: 'answer', piece_justificative_file: file } } expect(ClamavService).to receive(:safe_file?).and_return(true)
perform_enqueued_jobs do
post :update, params: { id: avis_without_answer.id, avis: { answer: 'answer', piece_justificative_file: file } }
end
avis_without_answer.reload avis_without_answer.reload
end end
it { expect(response).to redirect_to(instruction_gestionnaire_avis_path(avis_without_answer)) } it 'should be ok' do
it { expect(avis_without_answer.answer).to eq('answer') } expect(response).to redirect_to(instruction_gestionnaire_avis_path(avis_without_answer))
it { expect(avis_without_answer.piece_justificative_file).to be_attached } expect(avis_without_answer.answer).to eq('answer')
it { expect(flash.notice).to eq('Votre réponse est enregistrée.') } expect(avis_without_answer.piece_justificative_file).to be_attached
expect(flash.notice).to eq('Votre réponse est enregistrée.')
end
end end
end end

View file

@ -388,7 +388,7 @@ describe Champ do
champ.save champ.save
end end
it { expect(champ.piece_justificative_file.virus_scanner.analyzed?).to be_truthy } it { expect(champ.piece_justificative_file.virus_scanner.started?).to be_truthy }
end end
context 'and there is no blob' do context 'and there is no blob' do

View file

@ -10,7 +10,7 @@ describe ChampSerializer do
before do before do
champ.piece_justificative_file.attach({ filename: __FILE__, io: File.open(__FILE__) }) champ.piece_justificative_file.attach({ filename: __FILE__, io: File.open(__FILE__) })
champ.piece_justificative_file.virus_scanner.analyze_later champ.piece_justificative_file.blob.send(:enqueue_virus_scan)
end end
after { champ.piece_justificative_file.purge } after { champ.piece_justificative_file.purge }