diff --git a/app/validators/mon_avis_embed_validator.rb b/app/validators/mon_avis_embed_validator.rb
index b4fb33f59..3fb87b456 100644
--- a/app/validators/mon_avis_embed_validator.rb
+++ b/app/validators/mon_avis_embed_validator.rb
@@ -1,9 +1,31 @@
+# We need to ensure the embed code is not any random string in order to avoid injections
class MonAvisEmbedValidator < ActiveModel::Validator
+ class MonAvisEmbedError < StandardError; end
+ # from time to time, they decide to change domain just for fun. if it breaks, check the new subdomain
+ KNOWN_SUBDOMAIN = ['jedonnemonavis', 'monavis', 'voxusagers']
+ HREF_CHECKER = /https:\/\/#{KNOWN_SUBDOMAIN.join('|')}.numerique.gouv.fr\/Demarches\/\d+.*key=[[:alnum:]]+.*/
+ IMG_CHECKER = /https:\/\/#{KNOWN_SUBDOMAIN.join('|')}.numerique.gouv.fr\/(monavis-)?static\/bouton-blanc|bleu.png|svg/
+
def validate(record)
- # We need to ensure the embed code is not any random string in order to avoid injections
- r = Regexp.new('\s*\s*', Regexp::MULTILINE)
- if record.monavis_embed.present? && !r.match?(record.monavis_embed)
- record.errors.add :base, :invalid, message: "Le code fourni ne correspond pas au format des codes MonAvis reconnus par la plateforme."
+ if record.monavis_embed.present?
+ embed = Nokogiri::HTML(record.monavis_embed)
+ check_link(embed.css('a'))
+ check_img(embed.css('img'))
end
+ rescue MonAvisEmbedError => e
+ record.errors.add :base, :invalid, message: "Le code fourni ne correspond pas au format des codes MonAvis reconnus par la plateforme. #{e.message}"
+ rescue # nokogiri
+ record.errors.add :base, :invalid, message: "Le code fourni ne correspond pas au format des codes MonAvis reconnus par la plateforme."
+ end
+
+ def check_link(links)
+ raise MonAvisEmbedError.new("le code monavis doit comporter un seul lien") if links.size != 1
+ raise MonAvisEmbedError.new("le lien du bouton mon avis doit pointer vers le bon domaine") if !HREF_CHECKER.match?(links.first['href'])
+ end
+
+ def check_img(imgs)
+ raise MonAvisEmbedError.new("le code monavis doit comporter une seule image") if imgs.size != 1
+ raise MonAvisEmbedError.new("l'image du bouton mon avis ne pointe pas vers le bon domaine") if !IMG_CHECKER.match?(imgs.first['src'])
+ raise MonAvisEmbedError.new("l'image du bouton mon avis n'a pas d'attribut alt") if imgs.first['alt'].blank?
end
end
diff --git a/lib/tasks/deployment/20240220104123_disable_invalid_monavis.rake b/lib/tasks/deployment/20240220104123_disable_invalid_monavis.rake
new file mode 100644
index 000000000..9e587f58c
--- /dev/null
+++ b/lib/tasks/deployment/20240220104123_disable_invalid_monavis.rake
@@ -0,0 +1,23 @@
+namespace :after_party do
+ desc 'Deployment task: disable_invalid_monavis'
+ task disable_invalid_monavis: :environment do
+ puts "Running deploy task 'disable_invalid_monavis'"
+ # rubocop:disable DS/Unscoped
+ all_procedures = Procedure.unscoped.where.not(monavis_embed: nil)
+ # rubocop:enable DS/Unscoped
+ progress = ProgressReport.new(all_procedures.count)
+
+ all_procedures.find_each do |procedure|
+ if !procedure.valid? && procedure.errors.key?(:monavis_embed)
+ procedure.update_column(:monavis_embed, '')
+ rake_puts "fix: #{procedure.id}"
+ end
+ progress.inc(1)
+ end
+ progress.finish
+ # Update task as completed. If you remove the line below, the task will
+ # run with every deploy (or every time you call after_party:run).
+ AfterParty::TaskRecord
+ .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
+ end
+end
diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb
index e6361eaa9..9a7899c8d 100644
--- a/spec/models/procedure_spec.rb
+++ b/spec/models/procedure_spec.rb
@@ -294,6 +294,26 @@ describe Procedure do
let(:procedure) { build(:procedure, monavis_embed: monavis_issue_bouchra) }
it { expect(procedure.valid?).to eq(true) }
end
+
+ context 'Monavis embed code with jedonnemonavis' do
+ monavis_jedonnemonavis = <<-MSG
+
+
+
+ MSG
+ let(:procedure) { build(:procedure, monavis_embed: monavis_jedonnemonavis) }
+ it { expect(procedure.valid?).to eq(true) }
+ end
+
+ context 'Monavis embed code with kthxbye' do
+ monavis_jedonnemonavis = <<-MSG
+
+
+
+ MSG
+ let(:procedure) { build(:procedure, monavis_embed: monavis_jedonnemonavis) }
+ it { expect(procedure.valid?).to eq(false) }
+ end
end
describe 'duree de conservation dans ds' do