Merge branch 'dev'

This commit is contained in:
Frederic Merizen 2018-05-02 11:14:00 +02:00
commit 4494d0ab58
25 changed files with 275 additions and 58 deletions

View file

@ -203,7 +203,7 @@ GEM
ffi (1.9.23) ffi (1.9.23)
fission (0.5.0) fission (0.5.0)
CFPropertyList (~> 2.2) CFPropertyList (~> 2.2)
flipflop (2.3.1) flipflop (2.4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
fog (1.42.0) fog (1.42.0)
fog-aliyun (>= 0.1.0) fog-aliyun (>= 0.1.0)

View file

@ -23,7 +23,7 @@ class Admin::GestionnairesController < AdminController
procedure_id = params[:procedure_id] procedure_id = params[:procedure_id]
if @gestionnaire.nil? if @gestionnaire.nil?
new_gestionnaire! invite_gestionnaire(params[:gestionnaire][:email])
else else
assign_gestionnaire! assign_gestionnaire!
end end
@ -42,22 +42,23 @@ class Admin::GestionnairesController < AdminController
private private
def new_gestionnaire! def invite_gestionnaire(email)
attributes = params.require(:gestionnaire).permit(:email) password = SecureRandom.hex
.merge(password: SecureRandom.hex(5))
@gestionnaire = Gestionnaire.create( @gestionnaire = Gestionnaire.create(
attributes.merge( email: email,
administrateurs: [current_administrateur] password: password,
) password_confirmation: password,
administrateurs: [current_administrateur]
) )
if @gestionnaire.errors.messages.empty? if @gestionnaire.errors.messages.empty?
@gestionnaire.invite!
if User.exists?(email: @gestionnaire.email) if User.exists?(email: @gestionnaire.email)
GestionnaireMailer.user_to_gestionnaire(@gestionnaire.email).deliver_now! GestionnaireMailer.user_to_gestionnaire(@gestionnaire.email).deliver_now!
else else
User.create(attributes) User.create(email: email, password: password)
GestionnaireMailer.new_gestionnaire(@gestionnaire.email, @gestionnaire.password).deliver_now!
end end
flash.notice = 'Accompagnateur ajouté' flash.notice = 'Accompagnateur ajouté'
else else

View file

@ -1,10 +1,13 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
MAINTENANCE_MESSAGE = 'Le site est actuellement en maintenance. Il sera à nouveau disponible dans un court instant.'
# Prevent CSRF attacks by raising an exception. # Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead. # For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception protect_from_forgery with: :exception
before_action :load_navbar_left_pannel_partial_url before_action :load_navbar_left_pannel_partial_url
before_action :set_raven_context before_action :set_raven_context
before_action :authorize_request_for_profiler before_action :authorize_request_for_profiler
before_action :reject, if: -> { Flipflop.maintenance_mode? }
before_action :staging_authenticate before_action :staging_authenticate
@ -135,4 +138,23 @@ class ApplicationController < ActionController::Base
) )
# END OF FIXME # END OF FIXME
end end
def reject
authorized_request =
request.path_info == '/' ||
request.path_info.start_with?('/manager') ||
request.path_info.start_with?('/administrations')
api_request = request.path_info.start_with?('/api/')
if administration_signed_in? || authorized_request
flash.now.alert = MAINTENANCE_MESSAGE
elsif api_request
render json: { error: MAINTENANCE_MESSAGE }.to_json, status: :service_unavailable
else
%i(user gestionnaire administrateur).each { |role| sign_out(role) }
flash[:alert] = MAINTENANCE_MESSAGE
redirect_to root_path
end
end
end end

View file

@ -0,0 +1,47 @@
class Gestionnaires::ActivateController < ApplicationController
layout "new_application"
def new
@gestionnaire = Gestionnaire.with_reset_password_token(params[:token])
if !@gestionnaire
flash.alert = "Le lien de validation du compte accompagnateur a expiré, contactez-nous à contact@demarches-simplifiees.fr pour obtenir un nouveau lien."
redirect_to root_path
end
end
def create
password = create_gestionnaire_params[:password]
gestionnaire = Gestionnaire.reset_password_by_token({
password: password,
password_confirmation: password,
reset_password_token: create_gestionnaire_params[:reset_password_token]
})
if gestionnaire && gestionnaire.errors.empty?
sign_in(gestionnaire, scope: :gestionnaire)
try_to_authenticate(User, gestionnaire.email, password)
try_to_authenticate(Administrateur, gestionnaire.email, password)
flash.notice = "Mot de passe enregistré"
redirect_to gestionnaire_procedures_path
else
flash.alert = gestionnaire.errors.full_messages
redirect_to gestionnaire_activate_path(token: create_gestionnaire_params[:reset_password_token])
end
end
private
def create_gestionnaire_params
params.require(:gestionnaire).permit(:reset_password_token, :password)
end
def try_to_authenticate(klass, email, password)
resource = klass.find_for_database_authentication(email: email)
if resource&.valid_password?(password)
sign_in resource
resource.force_sync_credentials
end
end
end

View file

@ -19,6 +19,20 @@ module Manager
redirect_to manager_administrateur_path(params[:id]) redirect_to manager_administrateur_path(params[:id])
end end
def enable_feature
administrateur = Administrateur.find(params[:id])
params[:features].each do |key, enable|
if enable
administrateur.enable_feature(key.to_sym)
else
administrateur.disable_feature(key.to_sym)
end
end
head :ok
end
private private
def create_administrateur_params def create_administrateur_params

View file

@ -237,9 +237,10 @@ module NewGestionnaire
when 'user', 'etablissement', 'entreprise' when 'user', 'etablissement', 'entreprise'
if filter['column'] == 'date_creation' if filter['column'] == 'date_creation'
date = filter['value'].to_date rescue nil
dossiers dossiers
.includes(filter['table']) .includes(filter['table'])
.where("#{filter['table'].pluralize}.#{filter['column']} = ?", filter['value'].to_date) .where("#{filter['table'].pluralize}.#{filter['column']} = ?", date)
else else
dossiers dossiers
.includes(filter['table']) .includes(filter['table'])

View file

@ -15,6 +15,7 @@ class AdministrateurDashboard < Administrate::BaseDashboard
procedures: Field::HasMany.with_options(limit: 20), procedures: Field::HasMany.with_options(limit: 20),
registration_state: Field::String.with_options(searchable: false), registration_state: Field::String.with_options(searchable: false),
current_sign_in_at: Field::DateTime, current_sign_in_at: Field::DateTime,
features: FeaturesField
}.freeze }.freeze
# COLLECTION_ATTRIBUTES # COLLECTION_ATTRIBUTES
@ -38,6 +39,7 @@ class AdministrateurDashboard < Administrate::BaseDashboard
:updated_at, :updated_at,
:registration_state, :registration_state,
:current_sign_in_at, :current_sign_in_at,
:features,
:procedures, :procedures,
].freeze ].freeze

View file

@ -0,0 +1,4 @@
require "administrate/field/base"
class FeaturesField < Administrate::Field::Base
end

View file

@ -11,29 +11,12 @@ module Flipflop::Strategies
def enabled?(feature) def enabled?(feature)
# Can only check features if we have the user's session. # Can only check features if we have the user's session.
if request? if request?
legacy_enabled?(feature) || find_current_administrateur&.feature_enabled?(feature) find_current_administrateur&.feature_enabled?(feature)
end end
end end
private private
def legacy_enabled?(feature)
if self.class.legacy_features_map.present?
ids = self.class.legacy_features_map["#{feature}_allowed_for_admin_ids"]
ids.present? && find_current_administrateur&.id&.in?(ids)
end
end
LEGACY_CONFIG_FILE = Rails.root.join("config", "initializers", "features.yml")
def self.legacy_features_map
@@legacy_features_map = begin
if File.exist?(LEGACY_CONFIG_FILE)
YAML.load_file(LEGACY_CONFIG_FILE)
end
end
end
def find_current_administrateur def find_current_administrateur
if request.session["warden.user.administrateur.key"] if request.session["warden.user.administrateur.key"]
administrateur_id = request.session["warden.user.administrateur.key"][0][0] administrateur_id = request.session["warden.user.administrateur.key"][0][0]

View file

@ -1,8 +1,12 @@
class GestionnaireMailer < ApplicationMailer class GestionnaireMailer < ApplicationMailer
layout 'mailers/layout' layout 'mailers/layout'
def new_gestionnaire(email, password) def invite_gestionnaire(gestionnaire, reset_password_token)
send_mail(email, password, "Vous avez été nommé accompagnateur sur demarches-simplifiees.fr") @reset_password_token = reset_password_token
@gestionnaire = gestionnaire
mail(to: gestionnaire.email,
subject: "demarches-simplifiees.fr - Activez votre compte accompagnateur",
reply_to: "contact@demarches-simplifiees.fr")
end end
def user_to_gestionnaire(email) def user_to_gestionnaire(email)

View file

@ -144,6 +144,12 @@ class Gestionnaire < ApplicationRecord
Follow.where(gestionnaire: self, dossier: dossier).update_all(attributes) Follow.where(gestionnaire: self, dossier: dossier).update_all(attributes)
end end
def invite!
reset_password_token = set_reset_password_token
GestionnaireMailer.invite_gestionnaire(self, reset_password_token).deliver_now!
end
private private
def valid_couple_table_attr?(table, column) def valid_couple_table_attr?(table, column)

View file

@ -1,5 +1,15 @@
class ChampSerializer < ActiveModel::Serializer class ChampSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :value attributes :value
has_one :type_de_champ has_one :type_de_champ
def value
if object.piece_justificative_file.attached?
url_for(object.piece_justificative_file)
else
object.value
end
end
end end

View file

@ -7,11 +7,11 @@
%span{ "aria-hidden" => "true" } × %span{ "aria-hidden" => "true" } ×
%h4#myModalLabel.modal-title %h4#myModalLabel.modal-title
Transférer la procédure à un autre administrateur Envoyer une copie de cette procédure à un autre administrateur
.modal-body .modal-body
%p %p
Cette fonctionnalité vous permet de transmettre un clone de votre procédure à un autre administrateur. Cette fonctionnalité vous permet de d'envoyer une copie de votre procédure à un autre administrateur.
%div{ style:'margin-top:20px' } %div{ style:'margin-top:20px' }
= text_field_tag :email_admin, '', { class: 'form-control', = text_field_tag :email_admin, '', { class: 'form-control',

View file

@ -15,7 +15,7 @@
%a#transfer.btn.btn-small.btn-default{ "data-target" => "#transferModal", "data-toggle" => "modal", :type => "button", style: 'float: right; margin-top: 10px; margin-right: 10px;' } %a#transfer.btn.btn-small.btn-default{ "data-target" => "#transferModal", "data-toggle" => "modal", :type => "button", style: 'float: right; margin-top: 10px; margin-right: 10px;' }
%i.fa.fa-exchange %i.fa.fa-exchange
Transférer Envoyer une copie
= render partial: '/admin/procedures/modal_transfer' = render partial: '/admin/procedures/modal_transfer'

View file

@ -0,0 +1,26 @@
%table#features
- Flipflop.feature_set.features.each do |feature|
- if !feature.group || feature.group.key != :production
%tr
%td= feature.title
%td
= check_box_tag "enable-feature", "enable", field.data[feature.name], data: { url: enable_feature_manager_administrateur_path(field.resource.id), key: feature.key }
:javascript
window.onload = function() {
$('#features input[type=checkbox]').on('change', function(evt) {
let url = $(evt.target).data('url');
let key = $(evt.target).data('key');
let features = {};
features[key] = $(evt.target).prop('checked');
$.ajax(url, {
method: 'put',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
features: features
})
});
});
};

View file

@ -0,0 +1,19 @@
- content_for(:title, 'Activation de votre compte accompagnateur')
Bonjour,
%br
%br
Vous venez d'être nommé accompagnateur sur demarches-simplifiees.fr.
%br
Votre compte a été créé pour l'adresse email #{@gestionnaire.email}. Pour lactiver, je vous invite à cliquer sur le lien suivant : 
= link_to(gestionnaire_activate_url(token: @reset_password_token), gestionnaire_activate_url(token: @reset_password_token))
%br
%br
Par ailleurs, notre site de documentation qui regroupe l'ensemble des informations relatives à demarches-simplifiees.fr ainsi que des tutoriels dutilisation est à votre disposition : 
= link_to('https://demarches-simplifiees.gitbook.io/demarches-simplifiees/', 'https://demarches-simplifiees.gitbook.io/demarches-simplifiees/')
%br
%br
Bonne journée,
%br
%br
L'équipe demarches-simplifiees.fr

View file

@ -1,11 +0,0 @@
Bienvenue sur demarches-simplifiees.fr,
Vous venez d'être nommé accompagnateur sur demarches-simplifiees.fr. Pour mémoire, voici quelques informations utiles :
URL : <%= new_gestionnaire_session_url %>
Login : <%= @email %>
Mot de passe : <%= @args %>
Bonne journée,
L'équipe demarches-simplifiees.fr

View file

@ -0,0 +1,7 @@
.container
= form_for @gestionnaire, url: { controller: 'gestionnaires/activate', action: :create }, html: { class: "form" } do |f|
%br
%h1= @gestionnaire.email
= f.password_field :password, placeholder: 'Mot de passe'
= f.hidden_field :reset_password_token, value: params[:token]
= f.submit 'Définir le mot de passe', class: 'button large primary expand'

View file

@ -31,6 +31,8 @@ Rails.application.configure do
# ActionMailer::Base.deliveries array. # ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test config.action_mailer.delivery_method = :test
config.active_storage.service = :local
# Randomize the order test cases are executed. # Randomize the order test cases are executed.
config.active_support.test_order = :random config.active_support.test_order = :random

View file

@ -1,18 +1,26 @@
Flipflop.configure do Flipflop.configure do
strategy :cookie strategy :cookie,
secure: Rails.env.production?,
httponly: true
strategy :active_record strategy :active_record
strategy :user_preference strategy :user_preference
strategy :default strategy :default
group :champs do group :champs do
feature :champ_pj feature :champ_pj,
feature :champ_siret title: "Champ pièce justificative"
feature :champ_siret,
title: "Champ SIRET"
end end
feature :web_hook feature :web_hook
group :production do group :production do
feature :remote_storage, feature :remote_storage,
default: Rails.env.production? || Rails.env.staging? default: Rails.env.production? || Rails.env.staging?
feature :weekly_overview, feature :weekly_overview,
default: Rails.env.production? default: Rails.env.production?
end end
feature :maintenance_mode
end end

View file

@ -1,8 +0,0 @@
remote_storage: false
weekly_overview: false
champ_pj_allowed_for_admin_ids:
- 0
champ_siret_allowed_for_admin_ids:
- 0
web_hook_allowed_for_admin_ids:
- 0

View file

@ -8,6 +8,7 @@ Rails.application.routes.draw do
resources :administrateurs, only: [:index, :show, :new, :create] do resources :administrateurs, only: [:index, :show, :new, :create] do
post 'reinvite', on: :member post 'reinvite', on: :member
put 'enable_feature', on: :member
end end
resources :demandes, only: [:index] resources :demandes, only: [:index]
@ -114,6 +115,11 @@ Rails.application.routes.draw do
resource :dossiers resource :dossiers
end end
namespace :gestionnaire do
get 'activate' => '/gestionnaires/activate#new'
patch 'activate' => '/gestionnaires/activate#create'
end
namespace :admin do namespace :admin do
get 'activate' => '/administrateurs/activate#new' get 'activate' => '/administrateurs/activate#new'
patch 'activate' => '/administrateurs/activate#create' patch 'activate' => '/administrateurs/activate#create'

View file

@ -149,8 +149,7 @@ describe Admin::GestionnairesController, type: :controller do
context 'Email notification' do context 'Email notification' do
it 'Notification email is sent when accompagnateur is create' do it 'Notification email is sent when accompagnateur is create' do
expect(GestionnaireMailer).to receive(:new_gestionnaire).and_return(GestionnaireMailer) expect_any_instance_of(Gestionnaire).to receive(:invite!)
expect(GestionnaireMailer).to receive(:deliver_now!)
subject subject
end end
end end

View file

@ -82,4 +82,57 @@ describe ApplicationController, type: :controller do
end end
end end
end end
describe 'reject before action' do
let(:path_info) { '/one_path' }
before do
allow(@controller).to receive(:redirect_to)
allow(@controller).to receive(:sign_out)
allow(@controller).to receive(:render)
@request.path_info = path_info
end
context 'when no administration is logged in' do
before { @controller.send(:reject) }
it { expect(@controller).to have_received(:sign_out).with(:user) }
it { expect(@controller).to have_received(:sign_out).with(:gestionnaire) }
it { expect(@controller).to have_received(:sign_out).with(:administrateur) }
it { expect(flash[:alert]).to eq(ApplicationController::MAINTENANCE_MESSAGE) }
it { expect(@controller).to have_received(:redirect_to).with(root_path) }
context 'when the path is safe' do
%w(/ /manager /administrations).each do |path|
let(:path_info) { path }
it { expect(@controller).not_to have_received(:sign_out) }
it { expect(@controller).not_to have_received(:redirect_to) }
it { expect(flash.alert).to eq(ApplicationController::MAINTENANCE_MESSAGE) }
end
end
context 'when the path is api related' do
let(:path_info) { '/api/some-stuff' }
let(:json_error) { { error: ApplicationController::MAINTENANCE_MESSAGE }.to_json }
it { expect(@controller).not_to have_received(:sign_out) }
it { expect(@controller).not_to have_received(:redirect_to) }
it { expect(flash.alert).to be_nil }
it { expect(@controller).to have_received(:render).with({ json: json_error, status: :service_unavailable }) }
end
end
context 'when a administration is logged in' do
let(:current_administration) { create(:administration) }
before do
sign_in(current_administration)
@controller.send(:reject)
end
it { expect(@controller).not_to have_received(:sign_out) }
it { expect(@controller).not_to have_received(:redirect_to) }
it { expect(flash[:alert]).to eq(ApplicationController::MAINTENANCE_MESSAGE) }
end
end
end end

View file

@ -0,0 +1,22 @@
describe ChampSerializer do
describe '#attributes' do
subject { ChampSerializer.new(champ).serializable_hash }
context 'when type champ is piece justificative' do
include Rails.application.routes.url_helpers
let(:champ) { create(:champ, type_de_champ: create(:type_de_champ_piece_justificative)) }
before { champ.piece_justificative_file.attach({ filename: __FILE__, io: File.open(__FILE__) }) }
after { champ.piece_justificative_file.purge }
it { is_expected.to include(value: url_for(champ.piece_justificative_file)) }
end
context 'when type champ is not piece justificative' do
let(:champ) { create(:champ, value: "blah") }
it { is_expected.to include(value: "blah") }
end
end
end