Merge pull request #7140 from betagouv/only_safe_pj_in_zip

fix(archive): exporte uniquement les fichiers sans virus
This commit is contained in:
LeSim 2022-04-12 12:10:14 +02:00 committed by GitHub
commit 9b1f9f9a21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 13 deletions

View file

@ -165,6 +165,7 @@ class PiecesJustificativesService
ActiveStorage::Attachment ActiveStorage::Attachment
.includes(:blob) .includes(:blob)
.where(record_type: "Champ", record_id: champ_id_dossier_id.keys) .where(record_type: "Champ", record_id: champ_id_dossier_id.keys)
.filter { |a| safe_attachment(a) }
.map do |a| .map do |a|
dossier_id = champ_id_dossier_id[a.record_id] dossier_id = champ_id_dossier_id[a.record_id]
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a) ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
@ -181,6 +182,7 @@ class PiecesJustificativesService
ActiveStorage::Attachment ActiveStorage::Attachment
.includes(:blob) .includes(:blob)
.where(record_type: "Commentaire", record_id: commentaire_id_dossier_id.keys) .where(record_type: "Commentaire", record_id: commentaire_id_dossier_id.keys)
.filter { |a| safe_attachment(a) }
.map do |a| .map do |a|
dossier_id = commentaire_id_dossier_id[a.record_id] dossier_id = commentaire_id_dossier_id[a.record_id]
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a) ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
@ -212,6 +214,7 @@ class PiecesJustificativesService
ActiveStorage::Attachment ActiveStorage::Attachment
.includes(:blob) .includes(:blob)
.where(record_type: "Dossier", name: "justificatif_motivation", record_id: dossiers) .where(record_type: "Dossier", name: "justificatif_motivation", record_id: dossiers)
.filter { |a| safe_attachment(a) }
.map do |a| .map do |a|
dossier_id = a.record_id dossier_id = a.record_id
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a) ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
@ -262,4 +265,10 @@ class PiecesJustificativesService
.where(record_type: "BillSignature", record_id: bill_ids) .where(record_type: "BillSignature", record_id: bill_ids)
.map { |bill| ActiveStorage::DownloadableFile.bill_and_path(bill) } .map { |bill| ActiveStorage::DownloadableFile.bill_and_path(bill) }
end end
def self.safe_attachment(attachment)
attachment
.blob
.metadata[:virus_scan_result] == ActiveStorage::VirusScanner::SAFE
end
end end

View file

@ -23,6 +23,16 @@ describe PiecesJustificativesService do
it { expect(subject).to match_array([pj_champ.call(dossier).piece_justificative_file.attachment]) } it { expect(subject).to match_array([pj_champ.call(dossier).piece_justificative_file.attachment]) }
end end
context 'with a pj not safe on a champ' do
let(:procedure) { create(:procedure, :with_piece_justificative) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:pj_champ) { -> (d) { d.champs.find { |c| c.type == 'Champs::PieceJustificativeChamp' } } }
before { attach_file_to_champ(pj_champ.call(dossier), safe = false) }
it { expect(subject).to be_empty }
end
context 'with a private pj champ' do context 'with a private pj champ' do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure: procedure) }
@ -64,12 +74,26 @@ describe PiecesJustificativesService do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier) }
let(:witness) { create(:dossier) } let(:witness) { create(:dossier) }
let!(:commentaire) { create(:commentaire, :with_file, dossier: dossier) } let!(:commentaire) { create(:commentaire, dossier: dossier) }
let!(:witness_commentaire) { create(:commentaire, :with_file, dossier: witness) } let!(:witness_commentaire) { create(:commentaire, dossier: witness) }
before do
attach_file(commentaire.piece_jointe)
attach_file(witness_commentaire.piece_jointe)
end
it { expect(subject).to match_array(dossier.commentaires.first.piece_jointe.attachment) } it { expect(subject).to match_array(dossier.commentaires.first.piece_jointe.attachment) }
end end
context 'with a pj not safe on a commentaire' do
let(:dossier) { create(:dossier) }
let!(:commentaire) { create(:commentaire, dossier: dossier) }
before { attach_file(commentaire.piece_jointe, safe = false) }
it { expect(subject).to be_empty }
end
context 'with a motivation' do context 'with a motivation' do
let(:dossier) { create(:dossier, :with_justificatif) } let(:dossier) { create(:dossier, :with_justificatif) }
let!(:witness) { create(:dossier, :with_justificatif) } let!(:witness) { create(:dossier, :with_justificatif) }
@ -77,6 +101,14 @@ describe PiecesJustificativesService do
it { expect(subject).to match_array(dossier.justificatif_motivation.attachment) } it { expect(subject).to match_array(dossier.justificatif_motivation.attachment) }
end end
context 'with a motivation not safe' do
let(:dossier) { create(:dossier) }
before { attach_file(dossier.justificatif_motivation, safe = false) }
it { expect(subject).to be_empty }
end
context 'with an attestation' do context 'with an attestation' do
let(:dossier) { create(:dossier, :with_attestation) } let(:dossier) { create(:dossier, :with_attestation) }
let!(:witness) { create(:dossier, :with_attestation) } let!(:witness) { create(:dossier, :with_attestation) }
@ -167,12 +199,20 @@ describe PiecesJustificativesService do
end end
end end
def attach_file_to_champ(champ) def attach_file_to_champ(champ, safe = true)
attach_file(champ.piece_justificative_file) attach_file(champ.piece_justificative_file, safe)
end end
def attach_file(attachable) def attach_file(attachable, safe = true)
attachable to_be_attached = {
.attach(io: StringIO.new("toto"), filename: "toto.png", content_type: "image/png") io: StringIO.new("toto"),
filename: "toto.png", content_type: "image/png"
}
if safe
to_be_attached[:metadata] = { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
end
attachable.attach(to_be_attached)
end end
end end

View file

@ -90,8 +90,22 @@ describe 'Inviting an expert:' do
let(:commentaire) { create(:commentaire, instructeur: instructeur, dossier: dossier) } let(:commentaire) { create(:commentaire, instructeur: instructeur, dossier: dossier) }
before do before do
champ.piece_justificative_file.attach(io: File.open(path), filename: "piece_justificative_0.pdf", content_type: "application/pdf") champ
dossier.champs_private << create(:champ_piece_justificative, :with_piece_justificative_file, private: true, dossier: dossier) .piece_justificative_file
.attach(io: File.open(path),
filename: "piece_justificative_0.pdf",
content_type: "application/pdf",
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE })
dossier.champs_private << create(:champ_piece_justificative, private: true, dossier: dossier)
dossier.champs_private
.first
.piece_justificative_file
.attach(io: File.open(path),
filename: "piece_justificative_0.pdf",
content_type: "application/pdf",
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE })
end end
scenario 'An Expert can download an archive containing attachments without any private champ, bill signature and operations logs' do scenario 'An Expert can download an archive containing attachments without any private champ, bill signature and operations logs' do
@ -102,7 +116,7 @@ describe 'Inviting an expert:' do
click_on '1 avis à donner' click_on '1 avis à donner'
click_on avis.dossier.user.email click_on avis.dossier.user.email
find(:css, '.attached').click find(:css, '[aria-controls=print-pj-menu]').click
click_on 'Télécharger le dossier et toutes ses pièces jointes' click_on 'Télécharger le dossier et toutes ses pièces jointes'
# For some reason, clicking the download link does not trigger the download in the headless browser ; # For some reason, clicking the download link does not trigger the download in the headless browser ;
# So we need to go to the download link directly # So we need to go to the download link directly

View file

@ -194,14 +194,18 @@ describe 'Instructing a dossier:', js: true do
before do before do
dossier.passer_en_instruction!(instructeur: instructeur) dossier.passer_en_instruction!(instructeur: instructeur)
champ.piece_justificative_file.attach(io: File.open(path), filename: "piece_justificative_0.pdf", content_type: "application/pdf") champ.piece_justificative_file
.attach(io: File.open(path),
filename: "piece_justificative_0.pdf",
content_type: "application/pdf",
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE })
log_in(instructeur.email, password) log_in(instructeur.email, password)
visit instructeur_dossier_path(procedure, dossier) visit instructeur_dossier_path(procedure, dossier)
end end
scenario 'A instructeur can download an archive containing a single attachment' do scenario 'A instructeur can download an archive containing a single attachment' do
find(:css, '.attached').click find(:css, '[aria-controls=print-pj-menu]').click
click_on 'Télécharger le dossier et toutes ses pièces jointes' click_on 'Télécharger le dossier et toutes ses pièces jointes'
# For some reason, clicking the download link does not trigger the download in the headless browser ; # For some reason, clicking the download link does not trigger the download in the headless browser ;
# So we need to go to the download link directly # So we need to go to the download link directly
@ -219,7 +223,12 @@ describe 'Instructing a dossier:', js: true do
end end
scenario 'A instructeur can download an archive containing several identical attachments' do scenario 'A instructeur can download an archive containing several identical attachments' do
commentaire.piece_jointe.attach(io: File.open(path), filename: "piece_justificative_0.pdf", content_type: "application/pdf") commentaire
.piece_jointe
.attach(io: File.open(path),
filename: "piece_justificative_0.pdf",
content_type: "application/pdf",
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE })
visit telecharger_pjs_instructeur_dossier_path(procedure, dossier) visit telecharger_pjs_instructeur_dossier_path(procedure, dossier)
DownloadHelpers.wait_for_download DownloadHelpers.wait_for_download