Merge pull request #6941 from betagouv/main

2022-02-08-02
This commit is contained in:
Paul Chavard 2022-02-08 17:24:32 +01:00 committed by GitHub
commit 7587a9910b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 442 additions and 204 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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('<br>')
@ -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?

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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, dont 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

View file

@ -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)

View file

@ -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

View file

@ -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=""

View file

@ -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',

View file

@ -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

View file

@ -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"

View file

@ -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 <style>
# c'est trop compliqué pour être rectifié immédiatement (et sans valeur ajoutée:
# c'est hardcodé dans les vues, donc pas injectable).
policy.style_src :self, "*.crisp.chat", "crisp.chat", 'cdn.jsdelivr.net', 'maxcdn.bootstrapcdn.com', :unsafe_inline
policy.connect_src :self, "wss://*.crisp.chat", "*.crisp.chat", "*.demarches-simplifiees.fr", "in-automate.sendinblue.com", "app.franceconnect.gouv.fr", "sentry.io", "geo.api.gouv.fr", "api-adresse.data.gouv.fr", "openmaptiles.geo.data.gouv.fr", "openmaptiles.github.io", "tiles.geo.api.gouv.fr", "wxs.ign.fr", "data.education.gouv.fr"
policy.style_src(:self, "*.crisp.chat", "crisp.chat", 'cdn.jsdelivr.net', 'maxcdn.bootstrapcdn.com', :unsafe_inline)
connect_whitelist = ["wss://*.crisp.chat", "*.crisp.chat", "in-automate.sendinblue.com", "app.franceconnect.gouv.fr", "sentry.io", "openmaptiles.geo.data.gouv.fr", "openmaptiles.github.io", "tiles.geo.api.gouv.fr", "wxs.ign.fr"]
connect_whitelist << URI(API_ADRESSE_URL).host if API_ADRESSE_URL.present?
connect_whitelist << URI(API_EDUCATION_URL).host if API_EDUCATION_URL.present?
connect_whitelist << URI(API_GEO_URL).host if API_GEO_URL.present?
connect_whitelist << "*.#{ENV.fetch('APP_HOST', 'localhost:3000')}"
policy.connect_src(:self, *connect_whitelist)
# Pour tout le reste, par défaut on accepte uniquement ce qui vient de chez nous
# et dans la notification on inclue la source de l'erreur
policy.default_src :self, :data, :blob, :report_sample, "fonts.gstatic.com", "in-automate.sendinblue.com", "player.vimeo.com", "app.franceconnect.gouv.fr", "sentry.io", "static.demarches-simplifiees.fr", "*.crisp.chat", "crisp.chat", "*.crisp.help", "*.sibautomation.com", "sibautomation.com", "data"
default_whitelist = ["fonts.gstatic.com", "in-automate.sendinblue.com", "player.vimeo.com", "app.franceconnect.gouv.fr", "sentry.io", "*.crisp.chat", "crisp.chat", "*.crisp.help", "*.sibautomation.com", "sibautomation.com", "data"]
default_whitelist << URI(FOG_OPENSTACK_URL).host if FOG_OPENSTACK_URL.present?
policy.default_src(:self, :data, :blob, :report_sample, *default_whitelist)
if Rails.env.development?
# Les CSP ne sont pas appliquées en dev: on notifie cependant une url quelconque de la violation
# pour détecter les erreurs lors de l'ajout d'une nouvelle brique externe durant le développement
policy.report_uri "http://#{ENV['APP_HOST']}/csp/"
policy.report_uri "http://#{ENV.fetch('APP_HOST', 'localhost:3000')}/csp/"
# En développement, quand bin/webpack-dev-server est utilisé, on autorise les requêtes faites par le live-reload
policy.connect_src(*policy.connect_src, "ws://localhost:3035", "http://localhost:3035")
end
end
# rubocop:enable DS/ApplicationName
# If you are using UJS then enable automatic nonce generation
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }

View file

@ -1,14 +0,0 @@
# We want to register an interceptor, but we can't make the action idempotent
# (because there's no way to peek at the currently registered interceptors).
#
# To make zeitwerk happy, instead signal that we don't want the
# DynamicSmtpSettingsInterceptor constant to be auto-loaded, by:
# - adding it to a non-autoloaded-path (/lib),
# - requiring it explicitely.
#
# See https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoloading-when-the-application-boots
require 'action_mailer/dynamic_smtp_settings_interceptor'
ActiveSupport.on_load(:action_mailer) do
ActionMailer::Base.register_interceptor DynamicSmtpSettingsInterceptor
end

View file

@ -0,0 +1,17 @@
if ENV['HELO_ENABLED'] == 'enabled'
ActiveSupport.on_load(:action_mailer) do
module Helo
class SMTP < ::Mail::SMTP; end
end
ActionMailer::Base.add_delivery_method :helo, Helo::SMTP
ActionMailer::Base.helo_settings = {
user_name: 'demarches-simplifiees',
password: '',
address: '127.0.0.1',
domain: '127.0.0.1',
port: ENV.fetch('HELO_PORT', '2525'),
authentication: :plain
}
end
end

View file

@ -0,0 +1,17 @@
if ENV.fetch('MAILTRAP_ENABLED') == 'enabled'
ActiveSupport.on_load(:action_mailer) do
module Mailtrap
class SMTP < ::Mail::SMTP; end
end
ActionMailer::Base.add_delivery_method :mailtrap, Mailtrap::SMTP
ActionMailer::Base.mailtrap_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
}
end
end

View file

@ -1,5 +1,23 @@
require 'sib-api-v3-sdk'
if ENV.fetch('SENDINBLUE_ENABLED') == 'enabled'
require 'sib-api-v3-sdk'
SibApiV3Sdk.configure do |config|
config.api_key['api-key'] = ENV.fetch('SENDINBLUE_API_V3_KEY', '')
ActiveSupport.on_load(:action_mailer) do
module Sendinblue
class SMTP < ::Mail::SMTP; end
end
ActionMailer::Base.add_delivery_method :sendinblue, Sendinblue::SMTP
ActionMailer::Base.sendinblue_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
}
end
SibApiV3Sdk.configure do |config|
config.api_key['api-key'] = Rails.application.secrets.sendinblue[:api_v3_key]
end
end

View file

@ -0,0 +1,5 @@
class AddAttestationTemplateIdToProcedureRevisions < ActiveRecord::Migration[6.1]
def change
add_reference :procedure_revisions, :attestation_template, foreign_key: { to_table: :attestation_templates }, null: true, index: true
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_01_27_135056) do
ActiveRecord::Schema.define(version: 2022_01_28_135056) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -593,6 +593,8 @@ ActiveRecord::Schema.define(version: 2022_01_27_135056) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "published_at"
t.bigint "attestation_template_id"
t.index ["attestation_template_id"], name: "index_procedure_revisions_on_attestation_template_id"
t.index ["procedure_id"], name: "index_procedure_revisions_on_procedure_id"
end
@ -872,6 +874,7 @@ ActiveRecord::Schema.define(version: 2022_01_27_135056) do
add_foreign_key "procedure_revision_types_de_champ", "procedure_revision_types_de_champ", column: "parent_id"
add_foreign_key "procedure_revision_types_de_champ", "procedure_revisions", column: "revision_id"
add_foreign_key "procedure_revision_types_de_champ", "types_de_champ"
add_foreign_key "procedure_revisions", "attestation_templates"
add_foreign_key "procedure_revisions", "procedures"
add_foreign_key "procedures", "procedure_revisions", column: "draft_revision_id"
add_foreign_key "procedures", "procedure_revisions", column: "published_revision_id"

View file

@ -1,22 +0,0 @@
# Note: this class is instanciated when being added as an interceptor
# during the app initialization.
#
# If you edit this file in development env, you will need to restart
# the app to see the changes.
class DynamicSmtpSettingsInterceptor
def self.delivering_email(message)
if ENV['SENDINBLUE_BALANCING'] == 'enabled'
if rand(0..99) < ENV['SENDINBLUE_BALANCING_VALUE'].to_i
message.delivery_method.settings = {
user_name: ENV['SENDINBLUE_USER_NAME'],
password: ENV['SENDINBLUE_SMTP_KEY'],
address: 'smtp-relay.sendinblue.com',
domain: 'smtp-relay.sendinblue.com',
port: '587',
authentication: :cram_md5
}
end
end
end
end

View file

@ -1,20 +1,26 @@
namespace :after_party do
desc 'Deployment task: backfill_expert_id_on_avis_table'
task backfill_experts_procedure_id_on_avis_table: :environment do
puts "Running deploy task 'backfill_expert_id_on_avis_table'"
puts "Running deploy task 'backfill_experts_procedure_id_on_avis_table'"
# rubocop:disable DS/Unscoped
# rubocop:disable Rails/PluckInWhere
Instructeur.includes(:user)
.where(id: Avis.unscoped.pluck(:instructeur_id))
.where.not(users: { instructeur_id: nil })
.find_each do |instructeur|
instructeurs = Instructeur.includes(:user).where.not(users: { instructeur_id: nil })
instructeurs =
if Avis.column_names.include?("instructeur_id")
instructeurs.where(id: Avis.unscoped.pluck(:instructeur_id))
else
instructeurs.where(id: Avis.unscoped.where(claimant_type: [nil, "Instructeur"]).pluck(:claimant_id))
end
instructeurs.find_each do |instructeur|
user = instructeur.user
User.create_or_promote_to_expert(user.email, SecureRandom.hex)
user.reload
# rubocop:enable DS/Unscoped
# rubocop:enable Rails/PluckInWhere
instructeur.avis.each do |avis|
Avis.where(claimant: instructeur).each do |avis|
experts_procedure = ExpertsProcedure.find_or_create_by(expert: user.expert, procedure: avis.procedure)
avis.update_column(:experts_procedure_id, experts_procedure.id)
end

View file

@ -5,7 +5,8 @@ namespace :after_party do
BATCH_SIZE = 20000
with_dossiers = Avis.where(claimant_type: nil).includes(claimant: :assign_to).where.not(claimant: { assign_tos: { id: nil } })
without_assign_to_ids = Instructeur.includes(:assign_to).where(assign_tos: { id: nil }).pluck(:id)
with_dossiers = Avis.where(claimant_type: nil).where.not(claimant_id: without_assign_to_ids)
((with_dossiers.count / BATCH_SIZE).ceil + 1).times do
with_dossiers
@ -13,10 +14,9 @@ namespace :after_party do
.update_all(claimant_type: 'Instructeur')
end
without_dossiers = Avis.where(claimant_type: nil).includes(claimant: :assign_to).where(claimant: { assign_tos: { id: nil } })
without_dossiers = Avis.where(claimant_type: nil).where(claimant_id: without_assign_to_ids)
without_dossiers.find_each do |avis|
claimant = avis.claimant.user
instructeur = avis.instructeur
instructeur = Instructeur.find(avis.claimant_id)
if instructeur && avis.experts_procedure_id.blank?
User.create_or_promote_to_expert(instructeur.user.email, SecureRandom.hex)
@ -33,7 +33,7 @@ namespace :after_party do
elsif avis.experts_procedure_id.present?
avis.update_column(:claimant_type, 'Expert')
elsif claimant.blank?
elsif instructeur && instructeur.user.nil?
avis.destroy
end
end

View file

@ -3,7 +3,8 @@ namespace :after_party do
task backfill_claimant_id_for_experts_on_avis_table: :environment do
puts "Running deploy task 'backfill_claimant_id_for_experts_on_avis_table'"
avis_experts_claimant = Avis.where(claimant_type: 'Expert', tmp_expert_migrated: false)
avis_experts_claimant = Avis.where(claimant_type: 'Expert')
avis_experts_claimant = avis_experts_claimant.where(tmp_expert_migrated: false) if Avis.column_names.include?("tmp_expert_migrated")
progress = ProgressReport.new(avis_experts_claimant.count)
avis_experts_claimant.find_each do |avis|
@ -15,7 +16,10 @@ namespace :after_party do
claimant_expert = claimant_instructeur.reload.user.expert
ExpertsProcedure.find_or_create_by(procedure: avis.procedure, expert: claimant_expert)
end
avis.update_columns(claimant_id: claimant_expert.id, tmp_expert_migrated: true)
if Avis.column_names.include?("tmp_expert_migrated")
avis.update_columns(claimant_id: claimant_expert.id, tmp_expert_migrated: true)
end
else
# Avis associated to an Instructeur with no user are bad data: delete it
avis.destroy!
@ -23,6 +27,7 @@ namespace :after_party do
progress.inc
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

View file

@ -3,8 +3,19 @@ namespace :after_party do
task backfill_experts_procedure_id_on_avis_table_again: :environment do
puts "Running deploy task 'backfill_experts_procedure_id_on_avis_table_again'"
without_instructeur = Avis.where(experts_procedure_id: nil, instructeur_id: nil).where.not(email: nil)
with_instructeur = Avis.where(experts_procedure_id: nil, email: nil).where.not(instructeur_id: nil)
if Avis.column_names.include?("instructeur_id")
without_instructeur = Avis.where(experts_procedure_id: nil, instructeur_id: nil).where.not(email: nil)
with_instructeur = Avis.where(experts_procedure_id: nil, email: nil).where.not(instructeur_id: nil)
else
without_instructeur = Avis
.where(experts_procedure_id: nil, claimant_type: [nil, "Instructeur"])
.where.not(email: nil)
with_instructeur = Avis
.where(experts_procedure_id: nil, email: nil, claimant_type: [nil, "Instructeur"])
.where.not(claimant_id: nil)
end
progress = ProgressReport.new(without_instructeur.count)
progress2 = ProgressReport.new(with_instructeur.count)
@ -22,7 +33,8 @@ namespace :after_party do
progress.finish
with_instructeur.find_each do |avis|
instructeur = avis.instructeur
instructeur = avis.respond_to?(:instructeur) ? avis.instructeur : avis.claimant
if instructeur && instructeur.user
user = User.create_or_promote_to_expert(instructeur.user.email, SecureRandom.hex)
user.reload

View file

@ -3,14 +3,16 @@ namespace :after_party do
task revise_attestation_templates: :environment do
rake_puts "Running deploy task 'revise_attestation_templates'"
attestation_templates = AttestationTemplate.where.not(procedure_id: nil)
progress = ProgressReport.new(attestation_templates.count)
revisions = ProcedureRevision
.joins(procedure: :attestation_template)
.where(attestation_template_id: nil)
progress = ProgressReport.new(revisions.count)
revisions.find_each do |revision|
attestation_template_id = revision.procedure.attestation_template.id
revision.update_column(:attestation_template_id, attestation_template_id)
attestation_templates.find_each do |attestation_template|
ProcedureRevision
.where(procedure_id: attestation_template.procedure_id, attestation_template_id: nil)
.update_all(attestation_template_id: attestation_template)
attestation_template.update_column(:procedure_id, nil)
progress.inc
end
progress.finish

View file

@ -1,8 +1,8 @@
include ActionDispatch::TestProcess
describe Administrateurs::AttestationTemplatesController, type: :controller do
let!(:attestation_template) { create(:attestation_template) }
let(:admin) { create(:administrateur) }
let(:attestation_template) { build(:attestation_template) }
let!(:procedure) { create :procedure, administrateur: admin, attestation_template: attestation_template }
let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
let(:logo2) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
@ -41,7 +41,7 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
end
context 'if an attestation template exists on the procedure' do
after { procedure.attestation_template.destroy }
after { procedure.draft_revision.attestation_template&.destroy }
context 'with images' do
let!(:attestation_template) do
@ -115,14 +115,14 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
procedure.reload
end
it { expect(procedure.attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.activated).to be true }
it { expect(procedure.attestation_template.logo.download).to eq(logo2.read) }
it { expect(procedure.attestation_template.signature.download).to eq(signature2.read) }
it { expect(procedure.draft_attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.draft_attestation_template.activated).to be true }
it { expect(procedure.draft_attestation_template.logo.download).to eq(logo2.read) }
it { expect(procedure.draft_attestation_template.signature.download).to eq(signature2.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été sauvegardée") }
after { procedure.attestation_template.destroy }
after { procedure.draft_attestation_template.destroy }
end
context 'when something wrong happens in the attestation template creation' do
@ -140,7 +140,7 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.alert).to be_present }
it { expect(procedure.attestation_template).to be nil }
it { expect(procedure.draft_attestation_template).to be nil }
end
end
@ -158,13 +158,13 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
procedure.reload
end
it { expect(procedure.attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.logo.download).to eq(logo2.read) }
it { expect(procedure.attestation_template.signature.download).to eq(signature2.read) }
it { expect(procedure.draft_attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.draft_attestation_template.logo.download).to eq(logo2.read) }
it { expect(procedure.draft_attestation_template.signature.download).to eq(signature2.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été modifiée") }
after { procedure.attestation_template.destroy }
after { procedure.draft_attestation_template&.destroy }
end
context 'when something wrong happens in the attestation template creation' do

View file

@ -4,8 +4,6 @@ FactoryBot.define do
body { 'body' }
footer { 'footer' }
activated { true }
association :procedure
end
trait :with_files do

View file

@ -18,15 +18,16 @@ FactoryBot.define do
administrateurs { administrateur.present? ? [administrateur] : [association(:administrateur)] }
transient do
administrateur { }
administrateur {}
instructeurs { [] }
types_de_champ { [] }
types_de_champ_private { [] }
updated_at { nil }
attestation_template { nil }
end
after(:build) do |procedure, evaluator|
initial_revision = build(:procedure_revision, procedure: procedure)
initial_revision = build(:procedure_revision, procedure: procedure, attestation_template: evaluator.attestation_template)
add_types_de_champs(evaluator.types_de_champ, to: initial_revision, scope: :public)
add_types_de_champs(evaluator.types_de_champ_private, to: initial_revision, scope: :private)

View file

@ -0,0 +1,93 @@
RSpec.describe BalancerDeliveryMethod do
class ExampleMailer < ApplicationMailer
def greet(name)
mail(to: "smtp_to", from: "smtp_from", body: "Hello #{name}")
end
end
class TestMail
def self.deliveries
@deliveries ||= []
end
def self.deliveries=(val)
@deliveries = val
end
attr_accessor :settings
def initialize(values)
@settings = values.dup
end
def deliver!(mail)
Mail::CheckDeliveryParams.check(mail)
self.class.deliveries << mail
end
end
class MockSmtp < TestMail; end
class MockSendmail < TestMail; end
class FixedSequence
def initialize(sequence)
@enumerator = sequence.each
end
def rand(_)
@enumerator.next
end
end
before do
ActionMailer::Base.add_delivery_method :mock_smtp, MockSmtp
ActionMailer::Base.add_delivery_method :mock_sendmail, MockSendmail
ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
ExampleMailer.delivery_method = :balancer
end
context 'when a single delivery method is provided' do
before do
ActionMailer::Base.balancer_settings = { mock_smtp: 10 }
end
it 'sends emails to the selected delivery method' do
mail = ExampleMailer.greet('Joshua').deliver_now
expect(mail).to have_been_delivered_using(MockSmtp)
end
end
context 'when multiple delivery methods are provided' do
before do
ActionMailer::Base.balancer_settings = { mock_smtp: 10, mock_sendmail: 5 }
rng_sequence = [3, 14, 1]
BalancerDeliveryMethod.random = FixedSequence.new(rng_sequence)
end
after do
BalancerDeliveryMethod.random = Random.new
end
it 'sends emails randomly, given the provided weights' do
mail1 = ExampleMailer.greet('Lucia').deliver_now
expect(mail1).to have_been_delivered_using(MockSmtp)
mail2 = ExampleMailer.greet('Damian').deliver_now
expect(mail2).to have_been_delivered_using(MockSendmail)
mail3 = ExampleMailer.greet('Rahwa').deliver_now
expect(mail3).to have_been_delivered_using(MockSmtp)
end
end
# Helpers
def have_been_delivered_using(delivery_class)
satisfy("have been delivered using #{delivery_class}") do |mail|
delivery_class.deliveries.include?(mail)
end
end
end

View file

@ -0,0 +1,22 @@
describe '20220112184331_revise_attestation_templates' do
let(:rake_task) { Rake::Task['after_party:revise_attestation_templates'] }
let(:procedure) { create(:procedure) }
let(:attestation_template) { create(:attestation_template, procedure: procedure) }
subject(:run_task) do
attestation_template
rake_task.invoke
attestation_template.reload
end
after { rake_task.reenable }
describe 'revise_attestation_templates' do
it 'attaches the attestation_template to the latest revision (without removing the link between attestation_template and procedure for now)' do
expect(attestation_template.procedure.revisions.first.attestation_template_id).to be_nil
run_task
expect(attestation_template.procedure_id).to eq(procedure.id)
expect(attestation_template.procedure.revisions.first.attestation_template_id).to eq(attestation_template.id)
end
end
end

View file

@ -4,6 +4,10 @@ describe Universign::API do
let(:digest) { Digest::SHA256.hexdigest("CECI EST UN HASH") }
before do
stub_const("UNIVERSIGN_API_URL", "https://ws.universign.eu/tsa/post/")
end
it { is_expected.not_to be_nil }
end
end

View file

@ -66,16 +66,12 @@ describe Procedure do
end
describe '#closed_mail_template_attestation_inconsistency_state' do
let(:procedure_without_attestation) { create(:procedure, closed_mail: closed_mail) }
let(:procedure_without_attestation) { create(:procedure, closed_mail: closed_mail, attestation_template: nil) }
let(:procedure_with_active_attestation) do
procedure = create(:procedure, closed_mail: closed_mail)
create(:attestation_template, procedure: procedure, activated: true)
procedure
create(:procedure, closed_mail: closed_mail, attestation_template: build(:attestation_template, activated: true))
end
let(:procedure_with_inactive_attestation) do
procedure = create(:procedure, closed_mail: closed_mail)
create(:attestation_template, procedure: procedure, activated: false)
procedure
create(:procedure, closed_mail: closed_mail, attestation_template: build(:attestation_template, activated: false))
end
subject { procedure.closed_mail_template_attestation_inconsistency_state }

View file

@ -9,6 +9,7 @@ end
Capybara.register_driver :headless_chrome do |app|
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--no-sandbox') unless ENV['SANDBOX']
options.add_argument('--headless') unless ENV['NO_HEADLESS']
options.add_argument('--window-size=1440,900')

View file

@ -1,5 +1,6 @@
describe 'admin/_closed_mail_template_attestation_inconsistency_alert.html.haml', type: :view do
let(:procedure) { create(:procedure, closed_mail: closed_mail) }
let(:procedure) { create(:procedure, closed_mail: closed_mail, attestation_template: attestation_template) }
let(:attestation_template) { nil }
def alert
assign(:procedure, procedure)
@ -23,7 +24,7 @@ describe 'admin/_closed_mail_template_attestation_inconsistency_alert.html.haml'
context 'when there is an active attestation but the closed mail template does not mention it' do
let(:closed_mail) { create(:closed_mail) }
let!(:attestation_template) { create(:attestation_template, procedure: procedure, activated: true) }
let(:attestation_template) { build(:attestation_template) }
it { expect(alert).to include("Cette démarche comporte une attestation, mais laccusé dacceptation ne la mentionne pas") }
it { expect(alert).to include(edit_admin_procedure_mail_template_path(procedure, Mails::ClosedMail::SLUG)) }