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*Je donne mon avis\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 + + Je donne mon avis + + 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 + + Je donne mon avis + + 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