diff --git a/app/assets/stylesheets/new_design/motivation.scss b/app/assets/stylesheets/new_design/motivation.scss
index 5adcbc78b..a8244b1f4 100644
--- a/app/assets/stylesheets/new_design/motivation.scss
+++ b/app/assets/stylesheets/new_design/motivation.scss
@@ -43,4 +43,12 @@
padding-left: $default-spacer * 1.5;
}
}
+
+ .optional-justificatif {
+ margin-bottom: 16px;
+ }
+
+ input[data-direct-upload-url] {
+ width: 100%;
+ }
}
diff --git a/app/controllers/gestionnaires/dossiers_controller.rb b/app/controllers/gestionnaires/dossiers_controller.rb
index ded7aaba6..a96616bfe 100644
--- a/app/controllers/gestionnaires/dossiers_controller.rb
+++ b/app/controllers/gestionnaires/dossiers_controller.rb
@@ -93,16 +93,17 @@ module Gestionnaires
def terminer
motivation = params[:dossier] && params[:dossier][:motivation]
+ justificatif = params[:dossier] && params[:dossier][:justificatif_motivation]
case params[:process_action]
when "refuser"
- dossier.refuser!(current_gestionnaire, motivation)
+ dossier.refuser!(current_gestionnaire, motivation, justificatif)
flash.notice = "Dossier considéré comme refusé."
when "classer_sans_suite"
- dossier.classer_sans_suite!(current_gestionnaire, motivation)
+ dossier.classer_sans_suite!(current_gestionnaire, motivation, justificatif)
flash.notice = "Dossier considéré comme sans suite."
when "accepter"
- dossier.accepter!(current_gestionnaire, motivation)
+ dossier.accepter!(current_gestionnaire, motivation, justificatif)
flash.notice = "Dossier traité avec succès."
end
diff --git a/app/controllers/new_administrateur/procedure_administrateurs_controller.rb b/app/controllers/new_administrateur/procedure_administrateurs_controller.rb
new file mode 100644
index 000000000..b2fa6811d
--- /dev/null
+++ b/app/controllers/new_administrateur/procedure_administrateurs_controller.rb
@@ -0,0 +1,46 @@
+module NewAdministrateur
+ class ProcedureAdministrateursController < AdministrateurController
+ before_action :retrieve_procedure
+
+ def index
+ end
+
+ def create
+ email = params.require(:administrateur)[:email]&.strip&.downcase
+
+ # Find the admin
+ administrateur = Administrateur.find_by(email: email)
+ if administrateur.nil?
+ flash.alert = "L’administrateur « #{email} » n’existe pas. Invitez-le à demander un compte administrateur à l’addresse #{new_demande_url}."
+ return
+ end
+
+ # Prevent duplicates (also enforced in the database in administrateurs_procedures)
+ if @procedure.administrateurs.include?(administrateur)
+ flash.alert = "L’administrateur « #{administrateur.email} » est déjà administrateur de « #{@procedure.libelle} »."
+ return
+ end
+
+ # Actually add the admin
+ @procedure.administrateurs << administrateur
+ @administrateur = administrateur
+ flash.notice = "L’administrateur « #{administrateur.email} » a été ajouté à la démarche « #{@procedure.libelle} »."
+ end
+
+ def destroy
+ administrateur = @procedure.administrateurs.find(params[:id])
+
+ # Prevent self-removal (Also enforced in the UI)
+ if administrateur == current_administrateur
+ flash.error = "Vous ne pouvez pas vous retirer vous-même d’une démarche."
+ end
+
+ # Actually remove the admin
+ @procedure.administrateurs.delete(administrateur)
+ @administrateur = administrateur
+ flash.notice = "L’administrateur \« #{administrateur.email} » a été retiré de la démarche « #{@procedure.libelle} »."
+ rescue ActiveRecord::ActiveRecordError => e
+ flash.alert = e.message
+ end
+ end
+end
diff --git a/app/javascript/new_design/state-button.js b/app/javascript/new_design/state-button.js
index c30fa64a2..01ac323cc 100644
--- a/app/javascript/new_design/state-button.js
+++ b/app/javascript/new_design/state-button.js
@@ -11,3 +11,8 @@ export function motivationCancel() {
document.querySelectorAll('.motivation').forEach(hide);
show(document.querySelector('.dropdown-items'));
}
+
+export function showImportJustificatif(name) {
+ show(document.querySelector('#justificatif_motivation_import_' + name));
+ hide(document.querySelector('#justificatif_motivation_suggest_' + name));
+}
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 7f61db545..ba665e4bb 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -29,7 +29,11 @@ import '../new_design/champs/repetition';
import { toggleCondidentielExplanation } from '../new_design/avis';
import { scrollMessagerie } from '../new_design/messagerie';
-import { showMotivation, motivationCancel } from '../new_design/state-button';
+import {
+ showMotivation,
+ motivationCancel,
+ showImportJustificatif
+} from '../new_design/state-button';
import { toggleChart } from '../new_design/toggle-chart';
import { replaceSemicolonByComma } from '../new_design/avis';
@@ -40,6 +44,7 @@ const DS = {
scrollMessagerie,
showMotivation,
motivationCancel,
+ showImportJustificatif,
toggleChart,
replaceSemicolonByComma
};
diff --git a/app/javascript/shared/activestorage/ujs.js b/app/javascript/shared/activestorage/ujs.js
index 43beef778..55432abf7 100644
--- a/app/javascript/shared/activestorage/ujs.js
+++ b/app/javascript/shared/activestorage/ujs.js
@@ -10,8 +10,12 @@ addEventListener(INITIALIZE_EVENT, ({ target, detail: { id, file } }) => {
ProgressBar.init(target, id, file);
});
-addEventListener(START_EVENT, ({ detail: { id } }) => {
+addEventListener(START_EVENT, ({ target, detail: { id } }) => {
ProgressBar.start(id);
+ const button = target.form.querySelector('button.primary');
+ if (button) {
+ delete button.dataset.confirm;
+ }
});
addEventListener(PROGRESS_EVENT, ({ detail: { id, progress } }) => {
diff --git a/app/models/champs/pays_champ.rb b/app/models/champs/pays_champ.rb
index d088d2880..78de3277f 100644
--- a/app/models/champs/pays_champ.rb
+++ b/app/models/champs/pays_champ.rb
@@ -2,4 +2,8 @@ class Champs::PaysChamp < Champs::TextChamp
def self.pays
ApiGeo::API.pays.pluck(:nom)
end
+
+ def self.disabled_options
+ pays.select { |v| (v =~ /^--.*--$/).present? }
+ end
end
diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb
index 390555775..6be66c76a 100644
--- a/app/models/concerns/tags_substitution_concern.rb
+++ b/app/models/concerns/tags_substitution_concern.rb
@@ -59,7 +59,13 @@ module TagsSubstitutionConcern
{
libelle: 'lien attestation',
description: '',
- lambda: -> (d) { external_link(attestation_dossier_url(d)) },
+ lambda: -> (d) {
+ links = [external_link(attestation_dossier_url(d))]
+ if d.justificatif_motivation.attached?
+ links.push external_link("Télécharger le justificatif", url_for_justificatif_motivation(d))
+ end
+ links.join "
\n"
+ },
available_for_states: [Dossier.states.fetch(:accepte)]
}
]
@@ -138,8 +144,14 @@ module TagsSubstitutionConcern
end
end
- def external_link(url)
- link_to(url, url, target: '_blank', rel: 'noopener')
+ def external_link(url, title = nil)
+ link_to(title || url, url, target: '_blank', rel: 'noopener')
+ end
+
+ def url_for_justificatif_motivation(dossier)
+ if dossier.justificatif_motivation.attached?
+ Rails.application.routes.url_helpers.url_for(dossier.justificatif_motivation)
+ end
end
def dossier_tags
diff --git a/app/models/dossier.rb b/app/models/dossier.rb
index 1b8516399..39dc2c7ae 100644
--- a/app/models/dossier.rb
+++ b/app/models/dossier.rb
@@ -20,6 +20,8 @@ class Dossier < ApplicationRecord
has_one :attestation, dependent: :destroy
has_many :pieces_justificatives, dependent: :destroy
+ has_one_attached :justificatif_motivation
+
has_many :champs, -> { root.public_only.ordered }, dependent: :destroy
has_many :champs_private, -> { root.private_only.ordered }, class_name: 'Champ', dependent: :destroy
has_many :commentaires, dependent: :destroy
@@ -296,10 +298,12 @@ class Dossier < ApplicationRecord
log_dossier_operation(gestionnaire, :repasser_en_construction)
end
- def accepter!(gestionnaire, motivation)
+ def accepter!(gestionnaire, motivation, justificatif = nil)
self.motivation = motivation
self.en_instruction_at ||= Time.zone.now
-
+ if justificatif
+ self.justificatif_motivation.attach(justificatif)
+ end
accepte!
if attestation.nil?
@@ -330,20 +334,24 @@ class Dossier < ApplicationRecord
DeletedDossier.create_from_dossier(self)
end
- def refuser!(gestionnaire, motivation)
+ def refuser!(gestionnaire, motivation, justificatif = nil)
self.motivation = motivation
self.en_instruction_at ||= Time.zone.now
-
+ if justificatif
+ self.justificatif_motivation.attach(justificatif)
+ end
refuse!
NotificationMailer.send_refused_notification(self).deliver_later
log_dossier_operation(gestionnaire, :refuser)
end
- def classer_sans_suite!(gestionnaire, motivation)
+ def classer_sans_suite!(gestionnaire, motivation, justificatif = nil)
self.motivation = motivation
self.en_instruction_at ||= Time.zone.now
-
+ if justificatif
+ self.justificatif_motivation.attach(justificatif)
+ end
sans_suite!
NotificationMailer.send_without_continuation_notification(self).deliver_later
diff --git a/app/models/procedure.rb b/app/models/procedure.rb
index 485ca86c8..0cfe9a71d 100644
--- a/app/models/procedure.rb
+++ b/app/models/procedure.rb
@@ -1,6 +1,8 @@
require Rails.root.join('lib', 'percentile')
class Procedure < ApplicationRecord
+ self.ignored_columns = [:administrateur_id]
+
MAX_DUREE_CONSERVATION = 36
has_many :types_de_piece_justificative, -> { ordered }, dependent: :destroy
@@ -17,7 +19,7 @@ class Procedure < ApplicationRecord
has_many :assign_to, dependent: :destroy
has_many :administrateurs_procedures
- has_many :administrateurs, through: :administrateurs_procedures
+ has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! }
has_many :gestionnaires, through: :assign_to
has_one :initiated_mail, class_name: "Mails::InitiatedMail", dependent: :destroy
@@ -57,6 +59,7 @@ class Procedure < ApplicationRecord
validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :description, presence: true, allow_blank: false, allow_nil: false
+ validates :administrateurs, presence: true
validate :check_juridique
validates :path, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: :aasm_state, case_sensitive: false }, presence: true, allow_blank: false, allow_nil: true
# FIXME: remove duree_conservation_required flag once all procedures are converted to the new style
diff --git a/app/serializers/dossier_serializer.rb b/app/serializers/dossier_serializer.rb
index 536c418cf..876b2dcaf 100644
--- a/app/serializers/dossier_serializer.rb
+++ b/app/serializers/dossier_serializer.rb
@@ -22,6 +22,7 @@ class DossierSerializer < ActiveModel::Serializer
has_many :champs_private
has_many :pieces_justificatives
has_many :types_de_piece_justificative
+ has_one :justificatif_motivation
has_many :champs, serializer: ChampSerializer
@@ -52,6 +53,12 @@ class DossierSerializer < ActiveModel::Serializer
PiecesJustificativesService.serialize_champs_as_pjs(object)
end
+ def justificatif_motivation
+ if object.justificatif_motivation.attached?
+ Rails.application.routes.url_helpers.url_for(object.justificatif_motivation)
+ end
+ end
+
def types_de_piece_justificative
ActiveModelSerializers::SerializableResource.new(object.types_de_piece_justificative).serializable_hash +
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
diff --git a/app/views/gestionnaires/dossiers/_state_button.html.haml b/app/views/gestionnaires/dossiers/_state_button.html.haml
index 2c6580c6b..17986293e 100644
--- a/app/views/gestionnaires/dossiers/_state_button.html.haml
+++ b/app/views/gestionnaires/dossiers/_state_button.html.haml
@@ -60,6 +60,7 @@
- if dossier.motivation.present?
%h4 Motivation
%p.dossier-motivation= dossier.motivation
+ = render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
%h4 Attestation
diff --git a/app/views/gestionnaires/dossiers/_state_button_motivation.html.haml b/app/views/gestionnaires/dossiers/_state_button_motivation.html.haml
index 58e5a1d0f..f0310eb6a 100644
--- a/app/views/gestionnaires/dossiers/_state_button_motivation.html.haml
+++ b/app/views/gestionnaires/dossiers/_state_button_motivation.html.haml
@@ -28,6 +28,10 @@
%li= unspecified_annotations_privee.libelle
- else
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: placeholder, required: true
+ .optional-justificatif{ id: "justificatif_motivation_suggest_#{popup_class}", onclick: "DS.showImportJustificatif('#{popup_class}');" }
+ .button Ajouter un justificatif (optionnel)
+ .hidden{ id: "justificatif_motivation_import_#{popup_class}" }
+ = file_field :dossier, :justificatif_motivation, direct_upload: true
.text-right
- if title == 'Accepter' && dossier.procedure.attestation_template&.activated?
= link_to "Voir l'attestation", apercu_attestation_gestionnaire_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener', class: 'button', title: "Voir l'attestation qui sera envoyée au demandeur"
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index af74cd8c7..5a0486f44 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -19,7 +19,7 @@
= stylesheet_link_tag 'new_design/new_application', media: 'all', 'data-turbolinks-track': 'reload'
= stylesheet_link_tag 'new_design/print', media: 'print', 'data-turbolinks-track': 'reload'
- = Gon::Base.render_data(camel_case: true, init: true)
+ = Gon::Base.render_data(camel_case: true, init: true, nonce: request.content_security_policy_nonce)
- if Rails.env.development?
= stylesheet_link_tag :xray
diff --git a/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml b/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml
index 3fa249331..9a139b589 100644
--- a/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml
+++ b/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml
@@ -27,6 +27,10 @@
- if @procedure.missing_steps.include?(:service)
%p.missing-steps (à compléter)
+ %a#onglet-administrateurs{ href: url_for(procedure_administrateurs_path(@procedure)) }
+ .procedure-list-element{ class: ('active' if active == 'Administrateurs') }
+ Administrateurs
+
%a#onglet-instructeurs{ href: url_for(admin_procedure_instructeurs_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Instructeurs') }
Instructeurs
diff --git a/app/views/new_administrateur/procedure_administrateurs/_administrateur.html.haml b/app/views/new_administrateur/procedure_administrateurs/_administrateur.html.haml
new file mode 100644
index 000000000..65354f432
--- /dev/null
+++ b/app/views/new_administrateur/procedure_administrateurs/_administrateur.html.haml
@@ -0,0 +1,13 @@
+%tr{ id: "procedure-#{@procedure.id}-administrateur-#{administrateur.id}" }
+ %td= administrateur.email
+ %td= administrateur.created_at.strftime('%d/%m/%Y %H:%M')
+ %td= administrateur.registration_state
+ %td
+ - if administrateur == current_administrateur
+ C’est vous !
+ - else
+ = link_to 'Retirer',
+ [@procedure, administrateur],
+ method: :delete,
+ 'data-confirm': "Retirer « #{administrateur.email} » des administrateurs de « #{@procedure.libelle} » ?",
+ remote: true
diff --git a/app/views/new_administrateur/procedure_administrateurs/create.js.haml b/app/views/new_administrateur/procedure_administrateurs/create.js.haml
new file mode 100644
index 000000000..9e4ff843c
--- /dev/null
+++ b/app/views/new_administrateur/procedure_administrateurs/create.js.haml
@@ -0,0 +1,6 @@
+= render_flash(sticky: true)
+- if @administrateur
+ = append_to_element("#procedure-#{@procedure.id}-administrateurs",
+ partial: 'administrateur',
+ locals: { administrateur: @administrateur })
+ document.getElementById('procedure-#{@procedure.id}-new_administrateur').reset()
diff --git a/app/views/new_administrateur/procedure_administrateurs/destroy.js.haml b/app/views/new_administrateur/procedure_administrateurs/destroy.js.haml
new file mode 100644
index 000000000..b56486490
--- /dev/null
+++ b/app/views/new_administrateur/procedure_administrateurs/destroy.js.haml
@@ -0,0 +1,4 @@
+= render_flash(sticky: true)
+- if @administrateur
+ = remove_element("#procedure-#{@procedure.id}-administrateur-#{@administrateur.id}")
+
diff --git a/app/views/new_administrateur/procedure_administrateurs/index.html.haml b/app/views/new_administrateur/procedure_administrateurs/index.html.haml
new file mode 100644
index 000000000..ea54e04c5
--- /dev/null
+++ b/app/views/new_administrateur/procedure_administrateurs/index.html.haml
@@ -0,0 +1,26 @@
+= render partial: 'new_administrateur/breadcrumbs',
+ locals: { steps: [link_to('Démarches', admin_procedures_path),
+ link_to(@procedure.libelle, admin_procedure_path(@procedure)),
+ 'Administrateurs'], preview: false }
+
+.container
+ %h1 Administrateurs de « #{@procedure.libelle} »
+ %table.table
+ %thead
+ %th= 'Adresse email'
+ %th= 'Enregistré le'
+ %th= 'État'
+ %tbody{ id: "procedure-#{@procedure.id}-administrateurs" }
+ = render partial: 'administrateur', collection: @procedure.administrateurs.order(:email)
+ %tfoot
+ %tr
+ %th{ colspan: 4 }
+ = form_for @procedure.administrateurs.new,
+ url: { controller: 'procedure_administrateurs' },
+ html: { class: 'form', id: "procedure-#{@procedure.id}-new_administrateur" } ,
+ remote: true do |f|
+ = f.label :email do
+ Ajouter un administrateur
+ %span.notice= "Renseignez l’email d’un administrateur déjà enregistré sur demarches-simplifiees.fr pour lui permettre de modifier « #{@procedure.libelle} »."
+ = f.email_field :email, placeholder: 'marie.dupont@exemple.fr', required: true
+ = f.submit 'Ajouter comme administrateur', class: 'button primary send'
diff --git a/app/views/new_user/dossiers/show/_download_justificatif.html.haml b/app/views/new_user/dossiers/show/_download_justificatif.html.haml
new file mode 100644
index 000000000..509c594e0
--- /dev/null
+++ b/app/views/new_user/dossiers/show/_download_justificatif.html.haml
@@ -0,0 +1,7 @@
+- if dossier.present?
+ - justificatif = dossier.justificatif_motivation
+ - if dossier.justificatif_motivation.attached? and dossier.justificatif_motivation.virus_scanner.safe?
+ .action
+ = link_to (justificatif), target: '_blank', class: 'button primary' do
+ %span.icon.download
+ Télécharger le justificatif
diff --git a/app/views/shared/dossiers/_demande.html.haml b/app/views/shared/dossiers/_demande.html.haml
index 8eef11616..78ff2fb67 100644
--- a/app/views/shared/dossiers/_demande.html.haml
+++ b/app/views/shared/dossiers/_demande.html.haml
@@ -1,7 +1,7 @@
.container
- if dossier.en_construction_at.present?
.card
- = render partial: "shared/dossiers/horodatage", locals: { dossier: dossier }
+ = render partial: "shared/dossiers/infos_generales", locals: { dossier: dossier }
.tab-title Identité du demandeur
.card
diff --git a/app/views/shared/dossiers/_horodatage.html.haml b/app/views/shared/dossiers/_horodatage.html.haml
deleted file mode 100644
index c97b9c601..000000000
--- a/app/views/shared/dossiers/_horodatage.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-%table.table.vertical.dossier-champs
- %tbody
- %tr
- %th.libelle Déposé le :
- %td= l(dossier.en_construction_at, format: '%d %B %Y')
diff --git a/app/views/shared/dossiers/_infos_generales.html.haml b/app/views/shared/dossiers/_infos_generales.html.haml
new file mode 100644
index 000000000..9d2748899
--- /dev/null
+++ b/app/views/shared/dossiers/_infos_generales.html.haml
@@ -0,0 +1,11 @@
+%table.table.vertical.dossier-champs
+ %tbody
+ %tr
+ %th.libelle Déposé le :
+ %td= l(dossier.en_construction_at, format: '%d %B %Y')
+ - if dossier.justificatif_motivation.attached?
+ %tr
+ %th.libelle Justificatif :
+ %td
+ .action
+ = render partial: 'shared/piece_jointe/pj_link', locals: { object: dossier, pj: dossier.justificatif_motivation }
diff --git a/app/views/shared/dossiers/editable_champs/_pays.html.haml b/app/views/shared/dossiers/editable_champs/_pays.html.haml
index af0ee8b74..13062d8e5 100644
--- a/app/views/shared/dossiers/editable_champs/_pays.html.haml
+++ b/app/views/shared/dossiers/editable_champs/_pays.html.haml
@@ -1,4 +1,5 @@
= form.select :value,
Champs::PaysChamp.pays,
+ disabled: Champs::PaysChamp.disabled_options,
include_blank: true,
required: champ.mandatory?
diff --git a/app/views/users/dossiers/show/_status_overview.html.haml b/app/views/users/dossiers/show/_status_overview.html.haml
index ceb07234c..e2d16b5db 100644
--- a/app/views/users/dossiers/show/_status_overview.html.haml
+++ b/app/views/users/dossiers/show/_status_overview.html.haml
@@ -50,12 +50,15 @@
%h3 Motif de l’acceptation
%blockquote= dossier.motivation
+ = render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
+
- if dossier.attestation.present?
.action
= link_to attestation_dossier_path(dossier), target: '_blank', rel: 'noopener', class: 'button primary' do
%span.icon.download
Télécharger l’attestation
+
- elsif dossier.refuse?
.refuse
%p.decision
@@ -68,6 +71,7 @@
%h3 Motif du refus
%blockquote= dossier.motivation
+ = render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
.action
= link_to 'Envoyer un message à l’administration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
@@ -79,6 +83,8 @@
= succeed '.' do
%strong sans suite
+ = render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
+
- if dossier.motivation.present?
%h3 Motif du classement sans suite
%blockquote= dossier.motivation
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 9a178ab8c..1d51b0cd3 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -59,6 +59,9 @@ Rails.application.configure do
port: 3000
}
+ # Use Content-Security-Policy-Report-Only instead of Content-Security-Policy
+ config.content_security_policy_report_only = true
+
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
diff --git a/config/environments/production.rb b/config/environments/production.rb
index f2474f93d..befbe17aa 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -109,5 +109,7 @@ Rails.application.configure do
host: ENV['APP_HOST']
}
+ config.content_security_policy_report_only = true
+
config.lograge.enabled = ENV['LOGRAGE_ENABLED'] == 'enabled'
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 1b9f30460..2a1fb8e0f 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -45,6 +45,9 @@ Rails.application.configure do
protocol: :http
}
+ # Use Content-Security-Policy-Report-Only instead of Content-Security-Policy
+ config.content_security_policy_report_only = true
+
config.active_job.queue_adapter = :test
config.active_storage.service = :test
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
new file mode 100644
index 000000000..b58d8af0e
--- /dev/null
+++ b/config/initializers/content_security_policy.rb
@@ -0,0 +1,23 @@
+Rails.application.config.content_security_policy do |policy|
+ # En cas de non respect d'une des règles, faire un POST sur cette URL
+ policy.report_uri "https://e30e0ed9c14194254481124271b34a72.report-uri.com/r/d/csp/reportOnly"
+ # Whitelist image
+ policy.img_src :self, "https://*.openstreetmap.org"
+ # Whitelist JS: nous, sendinblue et matomo, et… miniprofiler :(
+ if Rails.env.development?
+ # https://github.com/MiniProfiler/rack-mini-profiler/issues/327
+ policy.script_src :self, "https://sibautomation.com", "//stats.data.gouv.fr", :unsafe_eval, :unsafe_inline
+ else
+ policy.script_src :self, "https://sibautomation.com", "//stats.data.gouv.fr"
+ end
+ # Génération d'un nonce pour les balises script inline qu'on maitrise (Gon)
+ Rails.application.config.content_security_policy_nonce_generator = -> _request { SecureRandom.base64(16) }
+
+ # Pour les CSS, on a beaucoup de style inline et quelques balises