Merge pull request #3892 from tchak/refresh-virus-scanner-checks

Refresh attachments with virus scan result
This commit is contained in:
Paul Chavard 2019-05-22 14:46:02 +02:00 committed by GitHub
commit 64009a5e10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 144 additions and 69 deletions

View file

@ -0,0 +1,9 @@
class AttachmentsController < ApplicationController
before_action :authenticate_logged_user!
include ActiveStorage::SetBlob
def show
@attachment = @blob.attachments.find(params[:id])
@user_can_upload = params[:user_can_upload]
end
end

View file

@ -9,6 +9,7 @@ import ReactUJS from '../shared/react-ujs';
import reactComponents from '../shared/react-components';
import '../shared/activestorage/ujs';
import '../shared/activestorage/attachment-checker';
import '../shared/rails-ujs-fix';
import '../shared/safari-11-file-xhr-workaround';
import '../shared/autocomplete';

View file

@ -0,0 +1,77 @@
import { ajax, delegate } from '@utils';
addEventListener('turbolinks:load', () => {
checker.deactivate();
const attachments = document.querySelectorAll('[data-attachment-check-url]');
for (let attachment of attachments) {
checker.add(attachment.dataset.attachmentCheckUrl);
}
});
addEventListener('attachment:update', ({ detail: { url } }) => {
checker.add(url);
});
delegate('click', '[data-attachment-refresh]', event => {
event.preventDefault();
checker.check();
});
class AttachmentChecker {
urls = new Set();
timeout;
checks = 0;
constructor(settings = {}) {
this.interval = settings.interval || 5000;
this.maxChecks = settings.maxChecks || 5;
}
get isEnabled() {
return this.checks <= this.maxChecks;
}
get isActive() {
return this.timeout !== undefined;
}
add(url) {
if (this.isEnabled) {
if (!this.isActive) {
this.activate();
}
this.urls.add(url);
}
}
check() {
let urls = this.urls;
this.reset();
for (let url of urls) {
ajax({ url, type: 'get' });
}
}
activate() {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.checks++;
this.check();
}, this.interval);
}
deactivate() {
this.checks = 0;
this.reset();
}
reset() {
clearTimeout(this.timeout);
this.urls = new Set();
this.timeout = undefined;
}
}
const checker = new AttachmentChecker();

View file

@ -3,7 +3,7 @@ import $ from 'jquery';
import debounce from 'debounce';
export { debounce };
export const { fire } = Rails;
export const { fire, ajax } = Rails;
export function show({ classList }) {
classList.remove('hidden');

View file

@ -0,0 +1,8 @@
<%= render_to_element(".pj-link[data-attachment-id=\"#{@attachment.id}\"]",
partial: 'shared/attachment/show',
outer: true,
locals: { attachment: @attachment, user_can_upload: @user_can_upload }) %>
<% if @attachment.virus_scanner.pending? %>
<%= fire_event('attachment:update', { url: attachment_url(@attachment.id, { signed_id: @attachment.blob.signed_id, user_can_upload: @user_can_upload }) }.to_json ) %>
<% end %>

View file

@ -13,7 +13,7 @@
= form_for @avis, url: gestionnaire_avis_path(@avis), html: { class: 'form' } do |f|
= f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true
= render partial: "shared/piece_jointe/pj_upload_field", locals: { pj: @avis.piece_justificative_file, object: @avis, form: f }
= render partial: "shared/attachment/update", locals: { pj: @avis.piece_justificative_file, object: @avis, form: f }
.flex.justify-between.align-baseline
%p.confidentiel.flex

View file

@ -60,7 +60,7 @@
- if dossier.motivation.present?
%h4 Motivation
%p.dossier-motivation= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
%h4 Attestation

View file

@ -1,2 +1,7 @@
<%= render_flash %>
<%= render_to_element('.state-button', partial: "state_button", locals: { dossier: dossier }) %>
<% attachment = dossier.justificatif_motivation.attachment %>
<% if attachment && attachment.virus_scanner.pending? %>
<%= fire_event('attachment:update', { url: attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }) }.to_json ) %>
<% end %>

View file

@ -28,6 +28,7 @@
Réponse donnée le #{l(avis.updated_at, format: '%d/%m/%y à %H:%M')}
- else
%span.waiting En attente de réponse
= render partial: 'shared/piece_jointe/pj_link', locals: { pj: avis.piece_justificative_file, user_can_upload: false }
- if avis.piece_justificative_file.attached?
= render partial: 'shared/attachment/show', locals: { attachment: avis.piece_justificative_file.attachment }
.answer-body
= simple_format(avis.answer)

View file

@ -1,12 +0,0 @@
- if dossier.present?
- justificatif = dossier.justificatif_motivation
- if justificatif.attached?
- if justificatif.virus_scanner.safe?
.action
= link_to (justificatif), target: '_blank', class: 'button primary' do
%span.icon.download
Télécharger le justificatif
- elsif justificatif.virus_scanner.pending?
%p
Un justificatif a été joint. L'analyse antivirus de ce document est en cours.
= link_to "rafraichir", request.path

View file

@ -0,0 +1,26 @@
- should_display_link = attachment.virus_scanner.safe? || !attachment.virus_scanner.started?
- user_can_upload = defined?(user_can_upload) ? user_can_upload : false
- if should_display_link
- attachment_check_url = false
- else
- attachment_check_url = attachment_url(attachment.id, { signed_id: attachment.blob.signed_id, user_can_upload: user_can_upload })
.pj-link{ 'data-attachment-id': attachment.id, 'data-attachment-check-url': attachment_check_url }
- if should_display_link
= link_to url_for(attachment.blob), target: '_blank', rel: 'noopener', title: "Télécharger la pièce jointe" do
%span.icon.attachment
= attachment.filename.to_s
- if !attachment.virus_scanner.started?
(ce fichier na pas été analysé par notre antivirus, téléchargez-le avec précaution)
- else
= attachment.filename.to_s
- if attachment.virus_scanner.pending?
(analyse antivirus en cours
= link_to "rafraichir", request.path, data: { 'attachment-refresh': true }
)
- elsif attachment.virus_scanner.infected?
- if user_can_upload
(virus détecté, merci denvoyer un autre fichier)
- else
(virus détecté, le téléchargement de ce fichier est bloqué)

View file

@ -2,7 +2,7 @@
- if pj.attached?
.piece-justificative-actions{ id: "piece_justificative_#{object.id}" }
.piece-justificative-action
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: true }
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true }
.piece-justificative-action
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{object.id}" }

View file

@ -1,5 +1,5 @@
- pj = champ.piece_justificative_file
- if pj.attached?
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: false }
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment }
- else
Pièce justificative non fournie

View file

@ -8,4 +8,4 @@
%th.libelle Justificatif :
%td
.action
= render partial: 'shared/piece_jointe/pj_link', locals: { object: dossier, pj: dossier.justificatif_motivation }
= render partial: 'shared/attachment/show', locals: { attachment: dossier.justificatif_motivation.attachment }

View file

@ -9,7 +9,7 @@
- if pj.attached?
.piece-justificative-actions{ id: "piece_justificative_#{champ.id}" }
.piece-justificative-action
= render partial: "shared/piece_jointe/pj_link", locals: { pj: pj, user_can_upload: true }
= render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true }
.piece-justificative-action
- if champ.private?
= link_to 'Supprimer', gestionnaire_champ_purge_champ_piece_justificative_path(procedure_id: champ.dossier.procedure_id, dossier_id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete, class: 'button small danger'

View file

@ -1,20 +0,0 @@
- if pj.attached?
.pj-link
- 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
%span.icon.attachment
= pj.filename.to_s
- if !pj.virus_scanner.started?
(ce fichier na pas été analysé par notre antivirus, téléchargez-le avec précaution)
- else
= pj.filename.to_s
- if pj.virus_scanner.pending?
(analyse antivirus en cours
= link_to "rafraichir", request.path
)
- elsif pj.virus_scanner.infected?
- if user_can_upload
(virus détecté, merci denvoyer un autre fichier)
- else
(virus détecté, le téléchargement de ce fichier est bloqué)

View file

@ -0,0 +1,2 @@
- if dossier.present? && dossier.justificatif_motivation.attached?
= render partial: "shared/attachment/show", locals: { attachment: dossier.justificatif_motivation.attachment }

View file

@ -50,7 +50,7 @@
%h3 Motif de lacceptation
%blockquote= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
.action
@ -71,7 +71,7 @@
%h3 Motif du refus
%blockquote= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
.action
= link_to 'Envoyer un message à ladministration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
@ -83,7 +83,7 @@
= succeed '.' do
%strong sans suite
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.motivation.present?
%h3 Motif du classement sans suite

View file

@ -1,21 +1,3 @@
ActiveStorage::Service.url_expires_in = 1.hour
# We want to run the virus scan on every ActiveStorage attachment,
# regardless of its type (user-uploaded document, instructeur-uploaded attestation, form template, etc.)
#
# To do this, the best place to work on is the ActiveStorage::Attachment
# objects themselves.
#
# We have to monkey patch ActiveStorage in order to always run an 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
# as it takes care of downloading the blob.
ActiveStorage::Attached::One.class_eval do
def virus_scanner
if attached?
ActiveStorage::VirusScanner.new(attachment.blob)
end
end
end
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }

View file

@ -133,6 +133,8 @@ Rails.application.routes.draw do
post ':position/repetition', to: 'repetition#show', as: :repetition
end
get 'attachments/:id', to: 'attachments#show', as: :attachment
get 'tour-de-france' => 'root#tour_de_france'
get "patron" => "root#patron"
get "accessibilite" => "root#accessibilite"

View file

@ -390,12 +390,6 @@ describe Champ do
it { expect(champ.piece_justificative_file.virus_scanner.started?).to be_truthy }
end
context 'and there is no blob' do
before { champ.save }
it { expect(champ.piece_justificative_file.virus_scanner).to be_nil }
end
end
end

View file

@ -1,6 +1,6 @@
require 'rails_helper'
describe 'shared/piece_jointe/_pj_link.html.haml', type: :view do
describe 'shared/attachment/_show.html.haml', type: :view do
let(:champ) { create(:champ_piece_justificative) }
let(:virus_scan_result) { nil }
@ -8,7 +8,7 @@ describe 'shared/piece_jointe/_pj_link.html.haml', type: :view do
champ.piece_justificative_file.blob.update(metadata: champ.piece_justificative_file.blob.metadata.merge(virus_scan_result: virus_scan_result))
end
subject { render 'shared/piece_jointe/pj_link', pj: champ.piece_justificative_file, user_can_upload: false }
subject { render 'shared/attachment/show', attachment: champ.piece_justificative_file.attachment }
context 'when there is no anti-virus scan' do
let(:virus_scan_result) { nil }