diff --git a/.rubocop.yml b/.rubocop.yml
index e5eac1a9b..9a84c37d4 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,15 +1,15 @@
require:
- - rubocop/rspec/focused
+ - rubocop-performance
+ - rubocop-rails
+ - rubocop-rspec
- ./lib/cops/add_concurrent_index.rb
- ./lib/cops/application_name.rb
- ./lib/cops/unscoped.rb
-inherit_gem:
- rubocop-rails_config:
- - config/rails.yml
-
AllCops:
- TargetRubyVersion: 2.7
+ TargetRubyVersion: 3.0
+ DisabledByDefault: true
+ SuggestExtensions: false
Exclude:
- "db/schema.rb"
- "db/migrate/20190730153555_recreate_structure.rb"
@@ -644,6 +644,9 @@ Performance/RedundantMatch:
Performance/RedundantMerge:
Enabled: true
+Style/HashTransformValues:
+ Enabled: true
+
Style/RedundantSortBy:
Enabled: true
@@ -874,7 +877,7 @@ Rails/WhereExists:
Rails/WhereNot:
Enabled: true
-RSpec/Focused:
+RSpec/Focus:
Enabled: true
Security/Eval:
@@ -1373,4 +1376,3 @@ Style/YodaCondition:
Style/ZeroLengthPredicate:
Enabled: true
-
diff --git a/Gemfile b/Gemfile
index 3377bc729..b56231cea 100644
--- a/Gemfile
+++ b/Gemfile
@@ -111,8 +111,9 @@ group :development do
gem 'rack-mini-profiler'
gem 'rails-erd', require: false # generates `doc/database_models.pdf`
gem 'rubocop', require: false
- gem 'rubocop-rails_config'
- gem 'rubocop-rspec-focused', require: false
+ gem 'rubocop-performance', require: false
+ gem 'rubocop-rails', require: false
+ gem 'rubocop-rspec', require: false
gem 'scss_lint', require: false
gem 'web-console'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 2ea2134bc..dad5cc90b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -620,8 +620,6 @@ GEM
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.4.1)
parser (>= 2.7.1.5)
- rubocop-packaging (0.5.1)
- rubocop (>= 0.89, < 2.0)
rubocop-performance (1.9.2)
rubocop (>= 0.90.0, < 2.0)
rubocop-ast (>= 0.4.0)
@@ -629,15 +627,9 @@ GEM
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 0.90.0, < 2.0)
- rubocop-rails_config (1.3.1)
- railties (>= 5.0)
- rubocop (>= 1.8)
- rubocop-ast (>= 1.0.1)
- rubocop-packaging (~> 0.5)
- rubocop-performance (~> 1.3)
- rubocop-rails (~> 2.0)
- rubocop-rspec-focused (1.0.0)
- rubocop (>= 0.51)
+ rubocop-rspec (2.4.0)
+ rubocop (~> 1.0)
+ rubocop-ast (>= 1.1.0)
ruby-graphviz (1.2.5)
rexml
ruby-progressbar (1.11.0)
@@ -874,8 +866,9 @@ DEPENDENCIES
rspec-rails
rspec_junit_formatter
rubocop
- rubocop-rails_config
- rubocop-rspec-focused
+ rubocop-performance
+ rubocop-rails
+ rubocop-rspec
ruby-saml-idp
sanitize-url
sassc-rails
diff --git a/app/assets/stylesheets/landing.scss b/app/assets/stylesheets/landing.scss
index 589cdbb01..124e9ee2a 100644
--- a/app/assets/stylesheets/landing.scss
+++ b/app/assets/stylesheets/landing.scss
@@ -295,7 +295,6 @@ $users-breakpoint: 950px;
font-size: 24px;
font-weight: bold;
margin-top: 13px;
- color: #FFFFFF;
&.grey {
color: $g700;
@@ -303,15 +302,19 @@ $users-breakpoint: 950px;
}
.cta-panel-explanation {
- font-size: 24px;
+ font-size: 22px;
margin-bottom: 10px;
- color: #FFFFFF;
&.grey {
color: $g700;
}
}
+.half .cta-panel-title,
+.half .cta-panel-explanation {
+ text-align: center;
+}
+
.role-panel-title {
font-size: 30px;
font-weight: bold;
@@ -357,7 +360,7 @@ $cta-panel-button-border-size: 2px;
@include vertical-padding(15px);
display: block;
border-radius: 100px;
- font-size: 24px;
+ font-size: 22px;
text-align: center;
cursor: pointer;
margin-top: 20px;
diff --git a/app/controllers/administrateurs/attestation_templates_controller.rb b/app/controllers/administrateurs/attestation_templates_controller.rb
index aff0bb793..d1f66ebad 100644
--- a/app/controllers/administrateurs/attestation_templates_controller.rb
+++ b/app/controllers/administrateurs/attestation_templates_controller.rb
@@ -3,12 +3,17 @@ module Administrateurs
before_action :retrieve_procedure
def edit
- @attestation_template = @procedure.attestation_template || AttestationTemplate.new(procedure: @procedure)
+ @attestation_template = build_attestation
end
def update
- attestation_template = @procedure.attestation_template
+ attestation_template = @procedure.draft_attestation_template.revise!
+
if attestation_template.update(activated_attestation_params)
+ AttestationTemplate
+ .where(id: @procedure.revisions.pluck(:attestation_template_id).compact)
+ .update_all(activated: attestation_template.activated?)
+
flash.notice = "L'attestation a bien été modifiée"
else
flash.alert = attestation_template.errors.full_messages.join('
')
@@ -18,7 +23,7 @@ module Administrateurs
end
def create
- attestation_template = AttestationTemplate.new(activated_attestation_params.merge(procedure_id: @procedure.id))
+ attestation_template = build_attestation(activated_attestation_params)
if attestation_template.save
flash.notice = "L'attestation a bien été sauvegardée"
@@ -30,14 +35,19 @@ module Administrateurs
end
def preview
- attestation = @procedure.attestation_template || AttestationTemplate.new
- @attestation = attestation.render_attributes_for({})
+ @attestation = build_attestation.render_attributes_for({})
render 'administrateurs/attestation_templates/show', formats: [:pdf]
end
private
+ def build_attestation(attributes = {})
+ attestation_template = @procedure.draft_attestation_template || @procedure.draft_revision.build_attestation_template
+ attestation_template.attributes = attributes
+ attestation_template
+ end
+
def activated_attestation_params
# cache result to avoid multiple uninterlaced computations
if @activated_attestation_params.nil?
diff --git a/app/lib/balancer_delivery_method.rb b/app/lib/balancer_delivery_method.rb
new file mode 100644
index 000000000..85f468ae3
--- /dev/null
+++ b/app/lib/balancer_delivery_method.rb
@@ -0,0 +1,38 @@
+# A Mail delivery method that randomly balances the actual delivery between different
+# methods.
+#
+# Usage:
+#
+# ```ruby
+# ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
+# config.action_mailer.balancer_settings = {
+# smtp: 25,
+# sendmail: 75
+# }
+# config.action_mailer.delivery_method = :balancer
+# ```
+#
+# Be sure to restart your server when you modify this file.
+class BalancerDeliveryMethod
+ # Allows configuring the random number generator used for selecting a delivery method,
+ # mostly for testing purposes.
+ mattr_accessor :random, default: Random.new
+
+ def initialize(settings)
+ @delivery_methods = settings
+ end
+
+ def deliver!(mail)
+ balanced_delivery_method = delivery_method(mail)
+ ApplicationMailer.wrap_delivery_behavior(mail, balanced_delivery_method)
+ mail.deliver
+ end
+
+ private
+
+ def delivery_method(mail)
+ @delivery_methods
+ .flat_map { |delivery_method, weight| [delivery_method] * weight }
+ .sample(random: self.class.random)
+ end
+end
diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb
index 6062589e8..deab7b0a2 100644
--- a/app/models/attestation_template.rb
+++ b/app/models/attestation_template.rb
@@ -15,7 +15,8 @@ class AttestationTemplate < ApplicationRecord
include ActionView::Helpers::NumberHelper
include TagsSubstitutionConcern
- belongs_to :procedure, optional: false
+ belongs_to :procedure, optional: true
+ has_many :revisions, class_name: 'ProcedureRevision', inverse_of: :attestation_template, dependent: :nullify
has_one_attached :logo
has_one_attached :signature
@@ -103,6 +104,25 @@ class AttestationTemplate < ApplicationRecord
}
end
+ def revise!
+ if revisions.size > 1
+ attestation_template = dup
+ attestation_template.save!
+ revisions
+ .last
+ .procedure
+ .draft_revision
+ .update!(attestation_template: attestation_template)
+ attestation_template
+ else
+ self
+ end
+ end
+
+ def procedure
+ revisions.last&.procedure || super
+ end
+
private
def used_tags
diff --git a/app/models/dossier.rb b/app/models/dossier.rb
index c0f11c577..83a91032a 100644
--- a/app/models/dossier.rb
+++ b/app/models/dossier.rb
@@ -720,9 +720,11 @@ class Dossier < ApplicationRecord
{ lon: lon, lat: lat, zoom: zoom }
end
- def unspecified_attestation_champs
- attestation_template = procedure.attestation_template
+ def attestation_template
+ revision.attestation_template
+ end
+ def unspecified_attestation_champs
if attestation_template&.activated?
attestation_template.unspecified_champs_for_dossier(self)
else
@@ -731,8 +733,8 @@ class Dossier < ApplicationRecord
end
def build_attestation
- if procedure.attestation_template&.activated?
- procedure.attestation_template.attestation_for(self)
+ if attestation_template&.activated?
+ attestation_template.attestation_for(self)
end
end
diff --git a/app/models/procedure.rb b/app/models/procedure.rb
index dec7d8a27..f3894815c 100644
--- a/app/models/procedure.rb
+++ b/app/models/procedure.rb
@@ -77,12 +77,15 @@ class Procedure < ApplicationRecord
has_many :published_types_de_champ_private, through: :published_revision, source: :types_de_champ_private
has_many :draft_types_de_champ, through: :draft_revision, source: :types_de_champ
has_many :draft_types_de_champ_private, through: :draft_revision, source: :types_de_champ_private
+ has_one :draft_attestation_template, through: :draft_revision, source: :attestation_template
+ has_one :published_attestation_template, through: :published_revision, source: :attestation_template
has_many :experts_procedures, dependent: :destroy
has_many :experts, through: :experts_procedures
has_one :module_api_carto, dependent: :destroy
has_one :attestation_template, dependent: :destroy
+ has_many :attestation_templates, through: :revisions, source: :attestation_template
belongs_to :parent_procedure, class_name: 'Procedure', optional: true
belongs_to :canonical_procedure, class_name: 'Procedure', optional: true
@@ -438,7 +441,8 @@ class Procedure < ApplicationRecord
},
revision_types_de_champ_private: {
type_de_champ: :types_de_champ
- }
+ },
+ attestation_template: []
}
}
include_list[:groupe_instructeurs] = :instructeurs if !is_different_admin
@@ -576,13 +580,17 @@ class Procedure < ApplicationRecord
touch(:whitelisted_at)
end
+ def active_attestation_template
+ published_attestation_template || draft_attestation_template
+ end
+
def closed_mail_template_attestation_inconsistency_state
# As an optimization, don’t check the predefined templates (they are presumed correct)
if closed_mail.present?
tag_present = closed_mail.body.to_s.include?("--lien attestation--")
- if attestation_template&.activated? && !tag_present
+ if active_attestation_template&.activated? && !tag_present
:missing_tag
- elsif !attestation_template&.activated? && tag_present
+ elsif !active_attestation_template&.activated? && tag_present
:extraneous_tag
end
end
diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb
index 7c0a19c48..da60bb296 100644
--- a/app/models/procedure_revision.rb
+++ b/app/models/procedure_revision.rb
@@ -2,15 +2,17 @@
#
# Table name: procedure_revisions
#
-# id :bigint not null, primary key
-# published_at :datetime
-# created_at :datetime not null
-# updated_at :datetime not null
-# procedure_id :bigint not null
+# id :bigint not null, primary key
+# published_at :datetime
+# created_at :datetime not null
+# updated_at :datetime not null
+# attestation_template_id :bigint
+# procedure_id :bigint not null
#
class ProcedureRevision < ApplicationRecord
self.implicit_order_column = :created_at
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
+ belongs_to :attestation_template, inverse_of: :revisions, optional: true, dependent: :destroy
has_many :dossiers, inverse_of: :revision, foreign_key: :revision_id
@@ -125,6 +127,10 @@ class ProcedureRevision < ApplicationRecord
)
end
+ def attestation_template
+ super || procedure.attestation_template
+ end
+
private
def compare_types_de_champ(from_tdc, to_tdc)
diff --git a/app/views/administrateurs/procedures/show.html.haml b/app/views/administrateurs/procedures/show.html.haml
index c0985cf4e..04dc0bb42 100644
--- a/app/views/administrateurs/procedures/show.html.haml
+++ b/app/views/administrateurs/procedures/show.html.haml
@@ -138,7 +138,7 @@
.procedure-grid
= link_to edit_admin_procedure_attestation_template_path(@procedure), class: 'card-admin' do
- - if @procedure.attestation_template.present? && @procedure.attestation_template.activated
+ - if @procedure.draft_attestation_template&.activated?
%div
%span.icon.accept
%p.card-admin-status-accept Activée
diff --git a/config/env.example b/config/env.example
index d2f9de017..8f3e332f8 100644
--- a/config/env.example
+++ b/config/env.example
@@ -70,28 +70,33 @@ MATOMO_ENABLED="disabled"
MATOMO_ID=""
MATOMO_HOST="matomo.organisme.fr"
-# SMTP Provider: Send In Blue
+# Default SMTP Provider: Mailjet
+MAILJET_API_KEY=""
+MAILJET_SECRET_KEY=""
+
+# Alternate SMTP Provider: SendInBlue
SENDINBLUE_ENABLED="disabled"
-SENDINBLUE_BALANCING=""
-SENDINBLUE_BALANCING_VALUE=""
SENDINBLUE_CLIENT_KEY=""
SENDINBLUE_SMTP_KEY=""
SENDINBLUE_USER_NAME=""
# SENDINBLUE_LOGIN_URL="https://app.sendinblue.com/account/saml/login/truc"
-# SMTP Provider: Mailjet
-MAILJET_API_KEY=""
-MAILJET_SECRET_KEY=""
+# Ratio of emails sent using SendInBlue
+# When enabled, N % of emails will be sent using SendInBlue
+# (and the others using the default SMTP provider)
+SENDINBLUE_BALANCING="disabled"
+SENDINBLUE_BALANCING_VALUE="50"
+
+# Alternate SMTP Provider: Mailtrap (mail catcher for staging environments)
+# When enabled, all emails will be sent using this provided
+MAILTRAP_ENABLED="disabled"
+MAILTRAP_USERNAME=""
+MAILTRAP_PASSWORD=""
# External service: live chat for admins (specific to démarches-simplifiées.fr)
CRISP_ENABLED="disabled"
CRISP_CLIENT_KEY=""
-# External service: mail catcher for staging environments (specific to démarches-simplifiées.fr)
-MAILTRAP_ENABLED="disabled"
-MAILTRAP_USERNAME=""
-MAILTRAP_PASSWORD=""
-
# API Entreprise credentials
# https://api.gouv.fr/api/api-entreprise.html
API_ENTREPRISE_KEY=""
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 2b0f92cbc..e0be95cc4 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -75,40 +75,13 @@ Rails.application.configure do
config.assets.raise_runtime_errors = true
# Action Mailer settings
+ config.action_mailer.delivery_method = :letter_opener
- if ENV['SENDINBLUE_ENABLED'] == 'enabled'
- config.action_mailer.delivery_method = :smtp
- config.action_mailer.smtp_settings = {
- user_name: Rails.application.secrets.sendinblue[:username],
- password: Rails.application.secrets.sendinblue[:smtp_key],
- address: 'smtp-relay.sendinblue.com',
- domain: 'smtp-relay.sendinblue.com',
- port: '587',
- authentication: :cram_md5
- }
- else
- # https://usehelo.com
- if ENV['HELO_ENABLED'] == 'enabled'
- config.action_mailer.delivery_method = :smtp
- config.action_mailer.smtp_settings = {
- user_name: 'demarches-simplifiees',
- password: '',
- address: '127.0.0.1',
- domain: '127.0.0.1',
- port: ENV.fetch('HELO_PORT', '2525'),
- authentication: :plain
- }
- else
- config.action_mailer.delivery_method = :letter_opener_web
- end
-
- config.action_mailer.default_url_options = {
- host: 'localhost',
- port: 3000
- }
-
- config.action_mailer.asset_host = "http://" + ENV['APP_HOST']
- end
+ config.action_mailer.default_url_options = {
+ host: 'localhost',
+ port: 3000
+ }
+ config.action_mailer.asset_host = "http://" + ENV['APP_HOST']
Rails.application.routes.default_url_options = {
host: 'localhost',
diff --git a/config/environments/production.rb b/config/environments/production.rb
index cdfeaed9b..912426727 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,4 +1,5 @@
require "active_support/core_ext/integer/time"
+require Rails.root.join("app/lib/balancer_delivery_method")
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
@@ -76,25 +77,19 @@ Rails.application.configure do
# config.action_mailer.raise_delivery_errors = false
if ENV['MAILTRAP_ENABLED'] == 'enabled'
- config.action_mailer.delivery_method = :smtp
- config.action_mailer.smtp_settings = {
- user_name: Rails.application.secrets.mailtrap[:username],
- password: Rails.application.secrets.mailtrap[:password],
- address: 'smtp.mailtrap.io',
- domain: 'smtp.mailtrap.io',
- port: '2525',
- authentication: :cram_md5
+ config.action_mailer.delivery_method = :mailtrap
+
+ elsif ENV['SENDINBLUE_ENABLED'] == 'enabled' && ENV['SENDINBLUE_BALANCING'] == 'enabled'
+ ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
+ config.action_mailer.balancer_settings = {
+ sendinblue: ENV.fetch('SENDINBLUE_BALANCING_VALUE').to_i,
+ mailjet: 100 - ENV.fetch('SENDINBLUE_BALANCING_VALUE').to_i
}
+ config.action_mailer.delivery_method = :balancer
+
elsif ENV['SENDINBLUE_ENABLED'] == 'enabled'
- config.action_mailer.delivery_method = :smtp
- config.action_mailer.smtp_settings = {
- user_name: Rails.application.secrets.sendinblue[:username],
- password: Rails.application.secrets.sendinblue[:smtp_key],
- address: 'smtp-relay.sendinblue.com',
- domain: 'smtp-relay.sendinblue.com',
- port: '587',
- authentication: :cram_md5
- }
+ config.action_mailer.delivery_method = :sendinblue
+
else
config.action_mailer.delivery_method = :mailjet
end
diff --git a/config/initializers/urls.rb b/config/initializers/02_urls.rb
similarity index 93%
rename from config/initializers/urls.rb
rename to config/initializers/02_urls.rb
index 732c583f2..6385e38e9 100644
--- a/config/initializers/urls.rb
+++ b/config/initializers/02_urls.rb
@@ -1,7 +1,9 @@
# rubocop:disable DS/ApplicationName
# API URLs
+API_ADRESSE_URL = ENV.fetch("API_ADRESSE_URL", "https://api-adresse.data.gouv.fr")
API_ENTREPRISE_URL = ENV.fetch("API_ENTREPRISE_URL", "https://entreprise.api.gouv.fr/v2")
API_EDUCATION_URL = ENV.fetch("API_EDUCATION_URL", "https://data.education.gouv.fr/api/records/1.0")
+API_GEO_URL = ENV.fetch("API_GEO_URL", "https://geo.api.gouv.fr")
API_PARTICULIER_URL = ENV.fetch("API_PARTICULIER_URL", "https://particulier.api.gouv.fr/api")
HELPSCOUT_API_URL = ENV.fetch("HELPSCOUT_API_URL", "https://api.helpscout.net/v2")
PIPEDRIVE_API_URL = ENV.fetch("PIPEDRIVE_API_URL", "https://api.pipedrive.com/v1")
@@ -11,7 +13,7 @@ UNIVERSIGN_API_URL = ENV.fetch("UNIVERSIGN_API_URL", "https://ws.universign.eu/t
FEATURE_UPVOTE_URL = ENV.fetch("FEATURE_UPVOTE_URL", "https://demarches-simplifiees.featureupvote.com")
# Internal URLs
-FOG_BASE_URL = "https://static.demarches-simplifiees.fr"
+FOG_OPENSTACK_URL = ENV.fetch("FOG_OPENSTACK_URL", "https://static.demarches-simplifiees.fr")
# External services URLs
WEBINAIRE_URL = "https://app.livestorm.co/demarches-simplifiees"
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index 17aa4aef0..73d7177a4 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -4,30 +4,45 @@
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
-# rubocop:disable DS/ApplicationName
Rails.application.config.content_security_policy do |policy|
# Whitelist image
- policy.img_src :self, "*.openstreetmap.org", "static.demarches-simplifiees.fr", "*.cloud.ovh.net", "stats.data.gouv.fr", "*", :data, :blob
+ images_whitelist = ["*.openstreetmap.org", "*.cloud.ovh.net", "*"]
+ images_whitelist << URI(FOG_OPENSTACK_URL).host if FOG_OPENSTACK_URL.present?
+ images_whitelist << URI(MATOMO_IFRAME_URL).host if MATOMO_IFRAME_URL.present?
+ policy.img_src(:self, :data, :blob, *images_whitelist)
+
# Whitelist JS: nous, sendinblue et matomo
# miniprofiler et nous avons quelques boutons inline :(
- policy.script_src :self, "stats.data.gouv.fr", "*.sendinblue.com", "*.crisp.chat", "crisp.chat", "*.sibautomation.com", "sibautomation.com", 'cdn.jsdelivr.net', 'maxcdn.bootstrapcdn.com', 'code.jquery.com', :unsafe_eval, :unsafe_inline, :blob
+ scripts_whitelist = ["*.sendinblue.com", "*.crisp.chat", "crisp.chat", "*.sibautomation.com", "sibautomation.com", "cdn.jsdelivr.net", "maxcdn.bootstrapcdn.com", "code.jquery.com"]
+ scripts_whitelist << URI(MATOMO_IFRAME_URL).host if MATOMO_IFRAME_URL.present?
+ policy.script_src(:self, :unsafe_eval, :unsafe_inline, :blob, *scripts_whitelist)
+
# Pour les CSS, on a beaucoup de style inline et quelques balises