Merge pull request #3892 from tchak/refresh-virus-scanner-checks
Refresh attachments with virus scan result
This commit is contained in:
commit
64009a5e10
22 changed files with 144 additions and 69 deletions
9
app/controllers/attachments_controller.rb
Normal file
9
app/controllers/attachments_controller.rb
Normal 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
|
|
@ -9,6 +9,7 @@ import ReactUJS from '../shared/react-ujs';
|
||||||
import reactComponents from '../shared/react-components';
|
import reactComponents from '../shared/react-components';
|
||||||
|
|
||||||
import '../shared/activestorage/ujs';
|
import '../shared/activestorage/ujs';
|
||||||
|
import '../shared/activestorage/attachment-checker';
|
||||||
import '../shared/rails-ujs-fix';
|
import '../shared/rails-ujs-fix';
|
||||||
import '../shared/safari-11-file-xhr-workaround';
|
import '../shared/safari-11-file-xhr-workaround';
|
||||||
import '../shared/autocomplete';
|
import '../shared/autocomplete';
|
||||||
|
|
77
app/javascript/shared/activestorage/attachment-checker.js
Normal file
77
app/javascript/shared/activestorage/attachment-checker.js
Normal 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();
|
|
@ -3,7 +3,7 @@ import $ from 'jquery';
|
||||||
import debounce from 'debounce';
|
import debounce from 'debounce';
|
||||||
|
|
||||||
export { debounce };
|
export { debounce };
|
||||||
export const { fire } = Rails;
|
export const { fire, ajax } = Rails;
|
||||||
|
|
||||||
export function show({ classList }) {
|
export function show({ classList }) {
|
||||||
classList.remove('hidden');
|
classList.remove('hidden');
|
||||||
|
|
8
app/views/attachments/show.js.erb
Normal file
8
app/views/attachments/show.js.erb
Normal 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 %>
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
= form_for @avis, url: gestionnaire_avis_path(@avis), html: { class: 'form' } do |f|
|
= form_for @avis, url: gestionnaire_avis_path(@avis), html: { class: 'form' } do |f|
|
||||||
= f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true
|
= 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
|
.flex.justify-between.align-baseline
|
||||||
%p.confidentiel.flex
|
%p.confidentiel.flex
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
- if dossier.motivation.present?
|
- if dossier.motivation.present?
|
||||||
%h4 Motivation
|
%h4 Motivation
|
||||||
%p.dossier-motivation= dossier.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?
|
- if dossier.attestation.present?
|
||||||
%h4 Attestation
|
%h4 Attestation
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
<%= render_flash %>
|
<%= render_flash %>
|
||||||
<%= render_to_element('.state-button', partial: "state_button", locals: { dossier: dossier }) %>
|
<%= 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 %>
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
Réponse donnée le #{l(avis.updated_at, format: '%d/%m/%y à %H:%M')}
|
Réponse donnée le #{l(avis.updated_at, format: '%d/%m/%y à %H:%M')}
|
||||||
- else
|
- else
|
||||||
%span.waiting En attente de réponse
|
%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
|
.answer-body
|
||||||
= simple_format(avis.answer)
|
= simple_format(avis.answer)
|
||||||
|
|
|
@ -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
|
|
26
app/views/shared/attachment/_show.html.haml
Normal file
26
app/views/shared/attachment/_show.html.haml
Normal 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 n’a 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 d’envoyer un autre fichier)
|
||||||
|
- else
|
||||||
|
(virus détecté, le téléchargement de ce fichier est bloqué)
|
|
@ -2,7 +2,7 @@
|
||||||
- if pj.attached?
|
- if pj.attached?
|
||||||
.piece-justificative-actions{ id: "piece_justificative_#{object.id}" }
|
.piece-justificative-actions{ id: "piece_justificative_#{object.id}" }
|
||||||
.piece-justificative-action
|
.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
|
.piece-justificative-action
|
||||||
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{object.id}" }
|
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{object.id}" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- pj = champ.piece_justificative_file
|
- pj = champ.piece_justificative_file
|
||||||
- if pj.attached?
|
- 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
|
- else
|
||||||
Pièce justificative non fournie
|
Pièce justificative non fournie
|
||||||
|
|
|
@ -8,4 +8,4 @@
|
||||||
%th.libelle Justificatif :
|
%th.libelle Justificatif :
|
||||||
%td
|
%td
|
||||||
.action
|
.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 }
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
- if pj.attached?
|
- if pj.attached?
|
||||||
.piece-justificative-actions{ id: "piece_justificative_#{champ.id}" }
|
.piece-justificative-actions{ id: "piece_justificative_#{champ.id}" }
|
||||||
.piece-justificative-action
|
.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
|
.piece-justificative-action
|
||||||
- if champ.private?
|
- 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'
|
= 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'
|
||||||
|
|
|
@ -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 n’a 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 d’envoyer un autre fichier)
|
|
||||||
- else
|
|
||||||
(virus détecté, le téléchargement de ce fichier est bloqué)
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
- if dossier.present? && dossier.justificatif_motivation.attached?
|
||||||
|
= render partial: "shared/attachment/show", locals: { attachment: dossier.justificatif_motivation.attachment }
|
|
@ -50,7 +50,7 @@
|
||||||
%h3 Motif de l’acceptation
|
%h3 Motif de l’acceptation
|
||||||
%blockquote= dossier.motivation
|
%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?
|
- if dossier.attestation.present?
|
||||||
.action
|
.action
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
%h3 Motif du refus
|
%h3 Motif du refus
|
||||||
%blockquote= dossier.motivation
|
%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
|
.action
|
||||||
= link_to 'Envoyer un message à l’administration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
|
= link_to 'Envoyer un message à l’administration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
= succeed '.' do
|
= succeed '.' do
|
||||||
%strong sans suite
|
%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?
|
- if dossier.motivation.present?
|
||||||
%h3 Motif du classement sans suite
|
%h3 Motif du classement sans suite
|
||||||
|
|
|
@ -1,21 +1,3 @@
|
||||||
ActiveStorage::Service.url_expires_in = 1.hour
|
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 }
|
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }
|
||||||
|
|
|
@ -133,6 +133,8 @@ Rails.application.routes.draw do
|
||||||
post ':position/repetition', to: 'repetition#show', as: :repetition
|
post ':position/repetition', to: 'repetition#show', as: :repetition
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get 'attachments/:id', to: 'attachments#show', as: :attachment
|
||||||
|
|
||||||
get 'tour-de-france' => 'root#tour_de_france'
|
get 'tour-de-france' => 'root#tour_de_france'
|
||||||
get "patron" => "root#patron"
|
get "patron" => "root#patron"
|
||||||
get "accessibilite" => "root#accessibilite"
|
get "accessibilite" => "root#accessibilite"
|
||||||
|
|
|
@ -390,12 +390,6 @@ describe Champ do
|
||||||
|
|
||||||
it { expect(champ.piece_justificative_file.virus_scanner.started?).to be_truthy }
|
it { expect(champ.piece_justificative_file.virus_scanner.started?).to be_truthy }
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'rails_helper'
|
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(:champ) { create(:champ_piece_justificative) }
|
||||||
let(:virus_scan_result) { nil }
|
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))
|
champ.piece_justificative_file.blob.update(metadata: champ.piece_justificative_file.blob.metadata.merge(virus_scan_result: virus_scan_result))
|
||||||
end
|
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
|
context 'when there is no anti-virus scan' do
|
||||||
let(:virus_scan_result) { nil }
|
let(:virus_scan_result) { nil }
|
Loading…
Reference in a new issue