Merge pull request #4206 from betagouv/dev

2019-08-14-01
This commit is contained in:
LeSim 2019-08-14 17:54:50 +02:00 committed by GitHub
commit 6a81006708
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 326 additions and 347 deletions

View file

@ -3,7 +3,9 @@ class Administrateurs::ActivateController < ApplicationController
def new
@token = params[:token]
@administrateur = Administrateur.find_inactive_by_token(@token)
user = User.with_reset_password_token(@token)
@administrateur = user&.administrateur
if @administrateur
# the administrateur activates its account from an email
@ -16,14 +18,16 @@ class Administrateurs::ActivateController < ApplicationController
def create
password = update_administrateur_params[:password]
administrateur = Administrateur.reset_password(
update_administrateur_params[:reset_password_token],
password
)
if administrateur && administrateur.errors.empty?
sign_in(administrateur, scope: :administrateur)
try_to_authenticate(User, administrateur.email, password)
user = User.reset_password_by_token({
password: password,
password_confirmation: password,
reset_password_token: update_administrateur_params[:reset_password_token]
})
if user&.administrateur&.errors&.empty?
sign_in(user, scope: :user)
flash.notice = "Mot de passe enregistré"
redirect_to admin_procedures_path
else

View file

@ -18,7 +18,8 @@ class ApplicationController < ActionController::Base
before_action :set_active_storage_host
before_action :setup_tracking
helper_method :logged_in?, :multiple_devise_profile_connect?, :instructeur_signed_in?, :current_instructeur
helper_method :logged_in?, :multiple_devise_profile_connect?, :instructeur_signed_in?, :current_instructeur,
:administrateur_signed_in?, :current_administrateur
def staging_authenticate
if StagingAuthService.enabled? && !authenticate_with_http_basic { |username, password| StagingAuthService.authenticate(username, password) }
@ -66,6 +67,14 @@ class ApplicationController < ActionController::Base
user_signed_in? && current_user&.instructeur.present?
end
def current_administrateur
current_user&.administrateur
end
def administrateur_signed_in?
current_administrateur.present?
end
protected
def authenticate_logged_user!
@ -85,9 +94,7 @@ class ApplicationController < ActionController::Base
end
def authenticate_administrateur!
if administrateur_signed_in?
super
else
if !administrateur_signed_in?
redirect_to new_user_session_path
end
end
@ -183,7 +190,7 @@ class ApplicationController < ActionController::Base
def redirect_if_untrusted
if instructeur_signed_in? &&
sensitive_path &&
Flipflop.enable_email_login_token? &&
!Flipflop.bypass_email_login_token? &&
!IPService.ip_trusted?(request.headers['X-Forwarded-For']) &&
!trusted_device?
@ -201,6 +208,8 @@ class ApplicationController < ActionController::Base
if path == '/' ||
path == '/users/sign_out' ||
path == '/contact' ||
path == '/contact-admin' ||
path.start_with?('/connexion-par-jeton') ||
path.start_with?('/api/') ||
path.start_with?('/lien-envoye')

View file

@ -2,7 +2,7 @@ module Manager
class InstructeursController < Manager::ApplicationController
def reinvite
instructeur = Instructeur.find(params[:id])
instructeur.invite!
instructeur.user.invite!
flash[:notice] = "Instructeur réinvité."
redirect_to manager_instructeur_path(instructeur)
end

View file

@ -23,7 +23,7 @@ class Users::ActivateController < ApplicationController
if user && user.errors.empty?
sign_in(user, scope: :user)
try_to_authenticate(Administrateur, user.email, password)
flash.notice = "Mot de passe enregistré"
redirect_to instructeur_procedures_path
else

View file

@ -59,7 +59,7 @@ class Users::PasswordsController < Devise::PasswordsController
administrateur = Administrateur.find_by(email: current_user.email)
if administrateur
sign_in administrateur
sign_in(administrateur.user)
end
end
end

View file

@ -16,8 +16,7 @@ class Users::SessionsController < Sessions::SessionsController
def create
remember_me = params[:user][:remember_me] == '1'
if resource_locked?(try_to_authenticate(User, remember_me)) ||
resource_locked?(try_to_authenticate(Administrateur, remember_me))
if resource_locked?(try_to_authenticate(User, remember_me))
flash.alert = 'Votre compte est verrouillé.'
new
return render :new, status: 401
@ -43,14 +42,6 @@ class Users::SessionsController < Sessions::SessionsController
# DELETE /resource/sign_out
def destroy
if instructeur_signed_in?
sign_out :instructeur
end
if administrateur_signed_in?
sign_out :administrateur
end
if user_signed_in?
connected_with_france_connect = current_user.loged_in_with_france_connect
current_user.update(loged_in_with_france_connect: '')

View file

@ -64,8 +64,7 @@ module ProcedureHelper
private
TOGGLES = {
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?,
TypeDeChamp.type_champs.fetch(:repetition) => :champ_repetition?
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?
}
def types_de_champ_types

View file

@ -39,7 +39,7 @@ function diplayMap(element, data, initial = false) {
}
}
addEventListener('turbolinks:load', initialize);
addEventListener('ds:page:update', initialize);
addEventListener('carte:update', ({ detail: { selector, data } }) => {
const element = document.querySelector(selector);

View file

@ -14,4 +14,4 @@ export function scrollMessagerie() {
}
}
addEventListener('turbolinks:load', scrollMessagerie);
addEventListener('ds:page:update', scrollMessagerie);

View file

@ -14,7 +14,7 @@ function expandProcedureDescription() {
descBody.classList.remove('read-more-collapsed');
}
addEventListener('turbolinks:load', updateReadMoreVisibility);
addEventListener('ds:page:update', updateReadMoreVisibility);
addEventListener('resize', updateReadMoreVisibility);
delegate('click', '.read-more-button', expandProcedureDescription);

View file

@ -1,7 +1,7 @@
import $ from 'jquery';
import 'select2';
addEventListener('turbolinks:load', () => {
addEventListener('ds:page:update', () => {
$('select.select2').select2({
language: 'fr',
width: '100%'

View file

@ -12,5 +12,5 @@ function updateContactElementsVisibility() {
}
}
addEventListener('turbolinks:load', updateContactElementsVisibility);
addEventListener('ds:page:update', updateContactElementsVisibility);
delegate('change', '#contact-form #type', updateContactElementsVisibility);

View file

@ -4,6 +4,7 @@ import Rails from '@rails/ujs';
import * as ActiveStorage from '@rails/activestorage';
import jQuery from 'jquery';
import '../shared/page-update-event';
import '../shared/activestorage/ujs';
import '../shared/rails-ujs-fix';
import '../shared/safari-11-file-xhr-workaround';

View file

@ -9,6 +9,7 @@ import Highcharts from 'highcharts';
import ReactUJS from '../shared/react-ujs';
import reactComponents from '../shared/react-components';
import '../shared/page-update-event';
import '../shared/activestorage/ujs';
import '../shared/activestorage/attachment-checker';
import '../shared/rails-ujs-fix';

View file

@ -37,22 +37,14 @@ function source(url) {
};
}
addEventListener('turbolinks:load', function() {
autocompleteSetup();
});
addEventListener('ajax:success', function() {
autocompleteSetup();
});
function autocompleteSetup() {
addEventListener('ds:page:update', function() {
for (let { type, url } of sources) {
for (let element of document.querySelectorAll(selector(type))) {
element.removeAttribute('data-autocomplete');
autocompleteInitializeElement(element, url);
}
}
}
});
function autocompleteInitializeElement(element, url) {
const select = autocomplete(element, options, [source(url)]);

View file

@ -20,8 +20,7 @@ function init() {
}
}
addEventListener('DOMContentLoaded', init);
addEventListener('turbolinks:load', init);
addEventListener('ds:page:update', init);
function toggleElement(event) {
event.preventDefault();

View file

@ -0,0 +1,9 @@
import { fire } from '@utils';
addEventListener('turbolinks:load', function() {
fire(document, 'ds:page:update');
});
addEventListener('ajax:success', function() {
fire(document, 'ds:page:update');
});

View file

@ -56,6 +56,6 @@ export default class ReactUJS {
}
start() {
addEventListener('turbolinks:load', () => this.mountComponents());
addEventListener('ds:page:update', () => this.mountComponents());
}
}

View file

@ -2,6 +2,10 @@ class Administrateurs::ActivateBeforeExpirationJob < ApplicationJob
queue_as :cron
def perform(*args)
Administrateur.inactive.where(created_at: 3.days.ago.all_day).each(&:remind_invitation!)
Administrateur
.includes(:user)
.inactive
.where(created_at: 3.days.ago.all_day)
.each { |a| a.user.remind_invitation! }
end
end

View file

@ -2,13 +2,13 @@
class AdministrateurMailer < ApplicationMailer
layout 'mailers/layout'
def activate_before_expiration(administrateur, reset_password_token)
@administrateur = administrateur
def activate_before_expiration(user, reset_password_token)
@user = user
@reset_password_token = reset_password_token
@expiration_date = @administrateur.reset_password_sent_at + Devise.reset_password_within
@expiration_date = @user.reset_password_sent_at + Devise.reset_password_within
@subject = "N'oubliez pas d'activer votre compte administrateur"
mail(to: administrateur.email,
mail(to: user.email,
subject: @subject,
reply_to: CONTACT_EMAIL)
end

View file

@ -3,21 +3,20 @@ class Administrateur < ApplicationRecord
include EmailSanitizableConcern
include ActiveRecord::SecureToken
devise :database_authenticatable, :registerable, :async,
:recoverable, :rememberable, :trackable, :validatable, :lockable
has_and_belongs_to_many :instructeurs
has_many :administrateurs_procedures
has_many :procedures, through: :administrateurs_procedures
has_many :services
has_many :dossiers, -> { state_not_brouillon }, through: :procedures
has_one :user
before_validation -> { sanitize_email(:email) }
scope :inactive, -> { where(active: false) }
scope :with_publiees_ou_archivees, -> { joins(:procedures).where(procedures: { aasm_state: [:publiee, :archivee] }) }
validate :password_complexity, if: Proc.new { |a| Devise.password_length.include?(a.password.try(:size)) }
# validate :password_complexity, if: Proc.new { |a| Devise.password_length.include?(a.password.try(:size)) }
def password_complexity
if password.present? && ZxcvbnService.new(password).score < PASSWORD_COMPLEXITY_FOR_ADMIN
@ -56,28 +55,6 @@ class Administrateur < ApplicationRecord
end
end
def invite!(administration_id)
if active?
raise "Impossible d'inviter un utilisateur déjà actif !"
end
reset_password_token = set_reset_password_token
AdministrationMailer.invite_admin(self, reset_password_token, administration_id).deliver_later
reset_password_token
end
def remind_invitation!
if active?
raise "Impossible d'envoyer un rappel d'invitation à un utilisateur déjà actif !"
end
reset_password_token = set_reset_password_token
AdministrateurMailer.activate_before_expiration(self, reset_password_token).deliver_later
end
def invitation_expired?
!active && !reset_password_period_valid?
end

View file

@ -9,29 +9,33 @@ class Administration < ApplicationRecord
def invite_admin(email)
password = SecureRandom.hex
administrateur = Administrateur.new({
email: email,
active: false,
password: password,
password_confirmation: password
})
if administrateur.save
AdministrationMailer.new_admin_email(administrateur, self).deliver_later
administrateur.invite!(id)
user = User.find_by(email: email)
user = User.create({
if user.nil?
# set confirmed_at otherwise admin confirmation doesnt work
# we somehow mess up using reset_password logic instead of
# confirmation_logic
# FIXME
user = User.create(
email: email,
password: password,
confirmed_at: Time.zone.now
})
Instructeur.create({
email: email,
user: user
})
)
end
administrateur
if user.errors.empty?
if user.instructeur.nil?
Instructeur.create!(email: email, user: user)
end
if user.administrateur.nil?
administrateur = Administrateur.create!(email: email, active: false, user: user)
AdministrationMailer.new_admin_email(administrateur, self).deliver_later
user.invite_administrateur!(id)
end
end
user
end
end

View file

@ -18,6 +18,7 @@ class User < ApplicationRecord
has_many :feedbacks, dependent: :destroy
has_one :france_connect_information, dependent: :destroy
belongs_to :instructeur
belongs_to :administrateur
accepts_nested_attributes_for :france_connect_information
@ -44,6 +45,23 @@ class User < ApplicationRecord
UserMailer.invite_instructeur(self, set_reset_password_token).deliver_later
end
def invite_administrateur!(administration_id)
if administrateur.active?
raise "Impossible d'inviter un utilisateur déjà actif !"
end
reset_password_token = set_reset_password_token
AdministrationMailer.invite_admin(self, reset_password_token, administration_id).deliver_later
reset_password_token
end
def remind_invitation!
reset_password_token = set_reset_password_token
AdministrateurMailer.activate_before_expiration(self, reset_password_token).deliver_later
end
private
def link_invites!

View file

@ -9,7 +9,7 @@
%p
Afin dactiver votre compte, veuillez cliquer sur le lien ci-dessous :
= link_to(admin_activate_url(token: @reset_password_token), admin_activate_url(token: @reset_password_token))
= link_to(users_activate_url(token: @reset_password_token), users_activate_url(token: @reset_password_token))
%p
Nous restons à votre disposition si vous avez besoin daccompagnement.

View file

@ -4,18 +4,16 @@
Télécharger tous les dossiers
- old_format_limit_date = Date.parse("Oct 31 2019")
- export_v1_enabled = old_format_limit_date > Time.zone.today
- export_v2_enabled = Flipflop.procedure_export_v2_enabled? || !export_v1_enabled
- old_format_message = export_v1_enabled && export_v2_enabled ? "(ancien format, jusquau #{old_format_limit_date.strftime('%d/%m/%Y')})" : ''
.dropdown-content.fade-in-down{ style: export_v1_enabled && export_v2_enabled ? 'width: 330px' : '' }
.dropdown-content.fade-in-down{ style: !export_v1_enabled ? 'width: 330px' : '' }
%ul.dropdown-items
- if export_v2_enabled
%li
= link_to "Au format .xlsx", procedure_dossiers_download_path(procedure, format: :xlsx, version: 'v2'), target: "_blank", rel: "noopener"
%li
= link_to "Au format .ods", procedure_dossiers_download_path(procedure, format: :ods, version: 'v2'), target: "_blank", rel: "noopener"
%li
= link_to "Au format .csv", procedure_dossiers_download_path(procedure, format: :csv, version: 'v2'), target: "_blank", rel: "noopener"
%li
= link_to "Au format .xlsx", procedure_dossiers_download_path(procedure, format: :xlsx, version: 'v2'), target: "_blank", rel: "noopener"
%li
= link_to "Au format .ods", procedure_dossiers_download_path(procedure, format: :ods, version: 'v2'), target: "_blank", rel: "noopener"
%li
= link_to "Au format .csv", procedure_dossiers_download_path(procedure, format: :csv, version: 'v2'), target: "_blank", rel: "noopener"
- if export_v1_enabled
- old_format_message = "(ancien format, jusquau #{old_format_limit_date.strftime('%d/%m/%Y')})"
%li
= link_to "Au format .xlsx #{old_format_message}", procedure_dossiers_download_path(procedure, format: :xlsx, version: 'v1'), target: "_blank", rel: "noopener"
%li

View file

@ -10,16 +10,6 @@
%span.mandatory *
= f.text_field :organisme, placeholder: "mairie de Mours, préfecture de l'Oise, ministère de la Culture", required: true
= f.label :siret do
SIRET
%span.mandatory *
%p
Pour trouver votre numéro SIRET, utilisez
%a{ href: 'https://entreprise.data.gouv.fr/', target: '_blank', rel: 'noopener' }
entreprise.data.gouv.fr
ou renseignez-vous auprès de votre service comptable
= f.number_field :siret, required: true
= f.label :type_organisme do
Type dorganisme
%span.mandatory *

View file

@ -9,17 +9,14 @@ Flipflop.configure do
group :champs do
feature :champ_integer_number,
title: "Champ nombre entier"
feature :champ_repetition,
title: "Bloc répétable"
end
feature :web_hook
feature :enable_email_login_token
feature :procedure_export_v2_enabled
feature :operation_log_serialize_subject
feature :download_as_zip_enabled,
default: false
feature :download_as_zip_enabled
feature :bypass_email_login_token,
default: Rails.env.test?
group :development do
feature :mini_profiler_enabled,

View file

@ -77,8 +77,6 @@ Rails.application.routes.draw do
omniauth_callbacks: 'administrations/omniauth_callbacks'
}
devise_for :administrateurs, skip: :all
devise_for :users, controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations',

View file

@ -0,0 +1,6 @@
class LinkUserAndAdministrateur < ActiveRecord::Migration[5.2]
def change
add_reference :users, :administrateur, index: true
add_foreign_key :users, :administrateurs
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: 2019_08_08_144607) do
ActiveRecord::Schema.define(version: 2019_08_09_073736) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -597,6 +597,8 @@ ActiveRecord::Schema.define(version: 2019_08_08_144607) do
t.datetime "locked_at"
t.text "unconfirmed_email"
t.bigint "instructeur_id"
t.bigint "administrateur_id"
t.index ["administrateur_id"], name: "index_users_on_administrateur_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["instructeur_id"], name: "index_users_on_instructeur_id"
@ -644,6 +646,7 @@ ActiveRecord::Schema.define(version: 2019_08_08_144607) do
add_foreign_key "services", "administrateurs"
add_foreign_key "trusted_device_tokens", "instructeurs"
add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id"
add_foreign_key "users", "administrateurs"
add_foreign_key "users", "instructeurs"
add_foreign_key "without_continuation_mails", "procedures"
end

View file

@ -10,6 +10,12 @@ default_password = "this is a very complicated password !"
puts "Create test user '#{default_user}'"
Administration.create!(email: default_user, password: default_password)
Administrateur.create!(email: default_user, password: default_password)
Instructeur.create!(email: default_user, password: default_password)
User.create!(email: default_user, password: default_password, confirmed_at: Time.zone.now)
administrateur = Administrateur.create!(email: default_user)
instructeur = Instructeur.create!(email: default_user)
User.create!(
email: default_user,
password: default_password,
confirmed_at: Time.zone.now,
administrateur: administrateur,
instructeur: instructeur
)

View file

@ -1,8 +0,0 @@
namespace :'activate_trusted_device_for_a-f' do
task run: :environment do
letters_a_to_f = ('a'..'f').to_a
Gestionnaire
.where("substr(email, 1, 1) IN (?)", letters_a_to_f)
.update_all(features: { "enable_email_login_token" => true })
end
end

View file

@ -0,0 +1,10 @@
namespace :after_party do
desc 'Deployment task: populate_user_administrateur_ids'
task populate_user_administrateur_ids: :environment do
Administrateur.find_each do |administrateur|
User.where(email: administrateur.email).update(administrateur_id: administrateur.id)
end
AfterParty::TaskRecord.create version: '20190809074534'
end
end

View file

@ -6,7 +6,7 @@ describe Admin::AssignsController, type: :controller do
let(:instructeur) { create :instructeur, administrateurs: [admin] }
before do
sign_in admin
sign_in(admin.user)
end
describe 'GET #show' do

View file

@ -11,7 +11,7 @@ describe Admin::AttestationTemplatesController, type: :controller do
let(:uninterlaced_logo) { fixture_file_upload('spec/fixtures/files/uninterlaced-black.png', 'image/png') }
before do
sign_in admin
sign_in(admin.user)
Timecop.freeze(Time.zone.now)
end

View file

@ -6,7 +6,7 @@ describe Admin::InstructeursController, type: :controller do
let(:admin_2) { create :administrateur, email: email_2 }
before do
sign_in admin
sign_in(admin.user)
end
describe 'GET #index' do
@ -117,8 +117,8 @@ describe Admin::InstructeursController, type: :controller do
before do
create :instructeur, email: email, administrateurs: [admin]
sign_out admin
sign_in admin_2
sign_out(admin.user)
sign_in(admin_2.user)
subject
end
@ -138,8 +138,8 @@ describe Admin::InstructeursController, type: :controller do
before do
create :instructeur, email: email, administrateurs: [admin]
sign_out admin
sign_in admin_2
sign_out(admin.user)
sign_in(admin_2.user)
subject
end

View file

@ -5,7 +5,7 @@ describe Admin::MailTemplatesController, type: :controller do
let(:initiated_mail) { Mails::InitiatedMail.default_for_procedure(procedure) }
before do
sign_in procedure.administrateurs.first
sign_in(procedure.administrateurs.first.user)
end
describe 'GET index' do

View file

@ -33,7 +33,7 @@ describe Admin::ProceduresController, type: :controller do
}
before do
sign_in admin
sign_in(admin.user)
end
describe 'GET #index' do
@ -155,7 +155,7 @@ describe Admin::ProceduresController, type: :controller do
context 'when user is not connected' do
before do
sign_out admin
sign_out(admin.user)
end
it { is_expected.to redirect_to new_user_session_path }
@ -252,7 +252,7 @@ describe Admin::ProceduresController, type: :controller do
context 'when administrateur is not connected' do
before do
sign_out admin
sign_out(admin.user)
end
subject { put :update, params: { id: procedure.id } }
@ -411,8 +411,8 @@ describe Admin::ProceduresController, type: :controller do
let(:admin_2) { create(:administrateur) }
before do
sign_out admin
sign_in admin_2
sign_out(admin.user)
sign_in(admin_2.user)
put :publish, params: { procedure_id: procedure.id, path: 'fake_path' }
procedure.reload
@ -471,8 +471,8 @@ describe Admin::ProceduresController, type: :controller do
let(:admin_2) { create(:administrateur) }
before do
sign_out admin
sign_in admin_2
sign_out(admin.user)
sign_in(admin_2.user)
put :archive, params: { procedure_id: procedure.id }
procedure.reload
@ -517,8 +517,8 @@ describe Admin::ProceduresController, type: :controller do
let(:admin_2) { create(:administrateur) }
before do
sign_out admin
sign_in admin_2
sign_out(admin.user)
sign_in(admin_2.user)
subject
end
@ -783,7 +783,7 @@ describe Admin::ProceduresController, type: :controller do
context 'when administrateur is not connected' do
before do
sign_out admin
sign_out(admin.user)
end
subject { patch :update_monavis, params: { procedure_id: procedure.id } }

View file

@ -1,7 +1,7 @@
describe Administrateurs::ActivateController, type: :controller do
describe '#new' do
let(:admin) { create(:administrateur) }
let(:token) { admin.send(:set_reset_password_token) }
let(:token) { admin.user.send(:set_reset_password_token) }
before { allow(controller).to receive(:trust_device) }

View file

@ -166,56 +166,44 @@ describe ApplicationController, type: :controller do
context 'when the path is sensitive' do
let(:sensitive_path) { true }
before do
Flipflop::FeatureSet.current.test!.switch!(:bypass_email_login_token, false)
end
context 'when the instructeur is signed_in' do
let(:instructeur_signed_in) { true }
context 'when the feature is activated' do
before do
Flipflop::FeatureSet.current.test!.switch!(:enable_email_login_token, true)
context 'when the ip is not trusted' do
let(:ip_trusted) { false }
context 'when the device is trusted' do
let(:trusted_device) { true }
before { subject }
it { expect(@controller).not_to have_received(:redirect_to) }
end
context 'when the ip is not trusted' do
let(:ip_trusted) { false }
context 'when the device is not trusted' do
let(:trusted_device) { false }
context 'when the device is trusted' do
let(:trusted_device) { true }
before { subject }
before { subject }
it { expect(@controller).not_to have_received(:redirect_to) }
end
it { expect(@controller).to have_received(:redirect_to) }
it { expect(@controller).to have_received(:send_login_token_or_bufferize) }
it { expect(@controller).to have_received(:store_location_for) }
end
end
context 'when the feature is activated' do
before do
Flipflop::FeatureSet.current.test!.switch!(:enable_email_login_token, true)
end
context 'when the ip is trusted' do
let(:ip_trusted) { true }
context 'when the ip is untrusted' do
let(:ip_trusted) { false }
context 'when the device is not trusted' do
let(:trusted_device) { false }
context 'when the device is not trusted' do
let(:trusted_device) { false }
before { subject }
before { subject }
it { expect(@controller).to have_received(:redirect_to) }
it { expect(@controller).to have_received(:send_login_token_or_bufferize) }
it { expect(@controller).to have_received(:store_location_for) }
end
end
context 'when the ip is trusted' do
let(:ip_trusted) { true }
context 'when the device is not trusted' do
let(:trusted_device) { false }
before { subject }
it { expect(@controller).not_to have_received(:redirect_to) }
end
it { expect(@controller).not_to have_received(:redirect_to) }
end
end
end

View file

@ -184,13 +184,7 @@ describe Instructeurs::DossiersController, type: :controller do
expect(dossier.reload.state).to eq(Dossier.states.fetch(:accepte))
expect(response).to have_http_status(:ok)
end
context 'as administrateur' do
let (:current_user) { administrateur }
it 'it is not possible to go back to en_instruction' do
expect(dossier.reload.state).to eq(Dossier.states.fetch(:accepte))
expect(response).to have_http_status(:ok)
end
end
context 'as superadmin' do
let (:current_user) { administration }
it 'it is not possible to go back to en_instruction' do

View file

@ -7,7 +7,7 @@ describe NewAdministrateur::MailTemplatesController, type: :controller do
let(:procedure) { create(:procedure, :with_logo, :with_service, administrateur: admin) }
before do
sign_in admin
sign_in(admin.user)
get :preview, params: { id: "initiated_mail", procedure_id: procedure.id }
end

View file

@ -5,7 +5,7 @@ describe NewAdministrateur::ProceduresController, type: :controller do
let(:procedure) { create(:procedure) }
before do
sign_in admin
sign_in(admin.user)
get :apercu, params: { id: procedure.id }
end

View file

@ -4,7 +4,7 @@ describe NewAdministrateur::ServicesController, type: :controller do
describe '#create' do
before do
sign_in admin
sign_in(admin.user)
post :create, params: params
end
@ -52,7 +52,7 @@ describe NewAdministrateur::ServicesController, type: :controller do
let(:service_params) { { nom: 'nom', type_organisme: Service.type_organismes.fetch(:association) } }
before do
sign_in admin
sign_in(admin.user)
params = {
id: service.id,
service: service_params,
@ -82,7 +82,7 @@ describe NewAdministrateur::ServicesController, type: :controller do
let!(:service) { create(:service, administrateur: admin) }
def post_add_to_procedure
sign_in admin
sign_in(admin.user)
params = {
procedure: {
id: procedure.id,
@ -114,7 +114,7 @@ describe NewAdministrateur::ServicesController, type: :controller do
context 'when a service has no related procedure' do
before do
sign_in admin
sign_in(admin.user)
delete :destroy, params: { id: service.id, procedure_id: 12 }
end
@ -128,7 +128,7 @@ describe NewAdministrateur::ServicesController, type: :controller do
let!(:procedure) { create(:procedure, service: service) }
before do
sign_in admin
sign_in(admin.user)
delete :destroy, params: { id: service.id, procedure_id: 12 }
end

View file

@ -6,7 +6,7 @@ describe NewAdministrateur::TypesDeChampController, type: :controller do
before do
admin.procedures << procedure
sign_in admin
sign_in(admin.user)
end
let(:type_champ) { TypeDeChamp.type_champs.fetch(:text) }

View file

@ -26,7 +26,7 @@ describe RootController, type: :controller do
context 'when Administrateur is connected' do
before do
sign_in create(:administrateur)
sign_in(create(:administrateur).user)
end
it { expect(subject).to redirect_to(admin_procedures_path) }

View file

@ -8,8 +8,6 @@ describe Sessions::SessionsController, type: :controller do
end
let(:user) { create(:user) }
let(:instructeur) { create(:instructeur) }
let(:administrateur) { create(:administrateur) }
describe '#create' do
before do
@ -34,17 +32,4 @@ describe Sessions::SessionsController, type: :controller do
post :create
end
end
describe '#create with administrateur connected' do
before do
@request.env["devise.mapping"] = Devise.mappings[:administrateur]
allow_any_instance_of(described_class).to receive(:administrateur_signed_in?).and_return(true)
end
it 'calls sign out for administrateur' do
expect_any_instance_of(described_class).to receive(:sign_out).with(:administrateur)
post :create
end
end
end

View file

@ -10,7 +10,7 @@ describe Users::ProfilController, type: :controller do
describe 'POST #renew_api_token' do
let(:administrateur) { create(:administrateur) }
before { sign_in(administrateur) }
before { sign_in(administrateur.user) }
before do
allow(administrateur).to receive(:renew_api_token)

View file

@ -17,7 +17,6 @@ describe Users::SessionsController, type: :controller do
let(:send_password) { password }
before do
Flipflop::FeatureSet.current.test!.switch!(:enable_email_login_token, true)
allow(controller).to receive(:trusted_device?).and_return(trusted_device)
allow(InstructeurMailer).to receive(:send_login_token).and_return(double(deliver_later: true))
end
@ -28,6 +27,9 @@ describe Users::SessionsController, type: :controller do
end
context 'when the device is not trusted' do
before do
Flipflop::FeatureSet.current.test!.switch!(:bypass_email_login_token, false)
end
let(:trusted_device) { false }
it 'redirects to the send_linked_path' do
@ -106,20 +108,6 @@ describe Users::SessionsController, type: :controller do
expect(response).to redirect_to(root_path)
end
end
context "when associated administrateur" do
let(:administrateur) { create(:administrateur, user: user) }
it 'signs user + instructeur + administrateur out' do
sign_in user
sign_in administrateur
delete :destroy
expect(@response.redirect?).to be(true)
expect(subject.current_user).to be(nil)
expect(subject.current_instructeur).to be(nil)
expect(subject.current_administrateur).to be(nil)
end
end
end
describe '#new' do

View file

@ -2,18 +2,20 @@ FactoryBot.define do
sequence(:administrateur_email) { |n| "admin#{n}@admin.com" }
factory :administrateur do
email { generate(:administrateur_email) }
password { 'mon chien aime les bananes' }
transient do
user { nil }
password { 'mon chien aime les bananes' }
end
after(:create) do |admin, evaluator|
after(:create) do |administrateur, evaluator|
if evaluator.user.present?
create(:instructeur, email: admin.email, password: admin.password, user: evaluator.user)
user = evaluator.user
else
create(:instructeur, email: admin.email, password: admin.password)
user = create(:user, email: administrateur.email, password: evaluator.password, administrateur: administrateur)
end
create(:instructeur, email: administrateur.email, user: user)
end
end

View file

@ -7,7 +7,7 @@ feature 'As an administrateur I wanna clone a procedure', js: true do
let(:administrateur) { create(:administrateur) }
before do
login_as administrateur, scope: :administrateur
login_as administrateur.user, scope: :user
visit new_from_existing_admin_procedures_path
end

View file

@ -7,7 +7,7 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
let(:administrateur) { create(:administrateur, :with_procedure) }
before do
login_as administrateur, scope: :administrateur
login_as administrateur.user, scope: :user
visit root_path
end

View file

@ -14,7 +14,7 @@ feature 'Administrateurs can edit procedures', js: true do
end
before do
login_as administrateur, scope: :administrateur
login_as administrateur.user, scope: :user
end
context 'when the procedure is in draft' do

View file

@ -6,7 +6,7 @@ feature 'As an instructeur', js: true do
let(:instructeur_email) { 'new_instructeur@gouv.fr' }
before do
login_as administrateur, scope: :administrateur
login_as administrateur.user, scope: :user
visit admin_procedure_assigns_path(procedure)
fill_in :instructeur_email, with: instructeur_email

View file

@ -9,10 +9,6 @@ feature 'The instructeur part' do
let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
let!(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) }
before do
Flipflop::FeatureSet.current.test!.switch!(:enable_email_login_token, true)
end
context 'the instructeur is also a user' do
scenario 'a instructeur can fill a dossier' do
visit commencer_path(path: procedure.path)

View file

@ -5,8 +5,7 @@ feature 'As an administrateur I can edit types de champ', js: true do
let(:procedure) { create(:procedure) }
before do
Flipflop::FeatureSet.current.test!.switch!(:champ_repetition, true)
login_as administrateur, scope: :administrateur
login_as administrateur.user, scope: :user
visit champs_procedure_path(procedure)
end

View file

@ -0,0 +1,57 @@
require 'spec_helper'
feature 'Managing password:' do
context 'for simple users' do
let(:user) { create(:user) }
let(:new_password) { 'a simple password' }
scenario 'a simple user can reset their password' do
visit root_path
click_on 'Connexion'
click_on 'Mot de passe oublié ?'
expect(page).to have_current_path(new_user_password_path)
fill_in 'Email', with: user.email
perform_enqueued_jobs do
click_on 'Réinitialiser'
end
expect(page).to have_content 'vous allez recevoir un lien de réinitialisation par email'
click_reset_password_link_for user.email
expect(page).to have_content 'Changement de mot de passe'
fill_in 'user_password', with: new_password
fill_in 'user_password_confirmation', with: new_password
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre mot de passe a été changé avec succès')
end
end
context 'for admins' do
let(:user) { create(:user) }
let(:administrateur) { create(:administrateur, user: user) }
let(:new_password) { 'a new, long, and complicated password!' }
scenario 'an admin can reset their password' do
visit root_path
click_on 'Connexion'
click_on 'Mot de passe oublié ?'
expect(page).to have_current_path(new_user_password_path)
fill_in 'Email', with: user.email
perform_enqueued_jobs do
click_on 'Réinitialiser'
end
expect(page).to have_content 'vous allez recevoir un lien de réinitialisation par email'
click_reset_password_link_for user.email
expect(page).to have_content 'Changement de mot de passe'
fill_in 'user_password', with: new_password
fill_in 'user_password_confirmation', with: new_password
click_on 'Changer le mot de passe'
expect(page).to have_content('Votre mot de passe a été changé avec succès')
end
end
end

View file

@ -0,0 +1,17 @@
require 'spec_helper'
feature 'Sign out' do
context 'when a user is logged in' do
let(:user) { create(:administrateur).user }
before { login_as user, scope: :user }
scenario 'he can sign out' do
visit dossiers_path
click_on 'Se déconnecter'
expect(page).to have_current_path(root_path)
end
end
end

View file

@ -38,7 +38,7 @@ RSpec.describe Administrateurs::ActivateBeforeExpirationJob, type: :job do
subject
end
it { expect(AdministrateurMailer).to have_received(:activate_before_expiration).with(administrateur, kind_of(String)) }
it { expect(AdministrateurMailer).to have_received(:activate_before_expiration).with(administrateur.user, kind_of(String)) }
end
end

View file

@ -8,29 +8,6 @@ describe Administrateur, type: :model do
it { is_expected.to have_many(:procedures) }
end
context 'unified login' do
it 'syncs credentials to associated user' do
administrateur = create(:administrateur)
user = administrateur.instructeur.user
administrateur.update(email: 'whoami@plop.com', password: 'voilà un super mdp')
user.reload
expect(user.email).to eq('whoami@plop.com')
expect(user.valid_password?('voilà un super mdp')).to be(true)
end
it 'syncs credentials to associated administrateur' do
administrateur = create(:administrateur)
instructeur = administrateur.instructeur
administrateur.update(email: 'whoami@plop.com', password: 'et encore un autre mdp')
instructeur.reload
expect(instructeur.email).to eq('whoami@plop.com')
end
end
describe "#renew_api_token" do
let!(:administrateur) { create(:administrateur) }
let!(:token) { administrateur.renew_api_token }
@ -44,22 +21,6 @@ describe Administrateur, type: :model do
end
end
describe '#find_inactive_by_token' do
let(:administrateur) { create(:administration).invite_admin('paul@tps.fr') }
let(:reset_password_token) { administrateur.invite!(administration.id) }
it { expect(Administrateur.find_inactive_by_token(reset_password_token)).not_to be_nil }
end
describe '#reset_password' do
let(:administrateur) { create(:administration).invite_admin('paul@tps.fr') }
let(:reset_password_token) { administrateur.invite!(administration.id) }
it { expect(Administrateur.reset_password(reset_password_token, "j'aime manger des radis").errors).to be_empty }
it { expect(Administrateur.reset_password('123', "j'aime manger des radis").errors).not_to be_empty }
it { expect(Administrateur.reset_password(reset_password_token, '').errors).not_to be_empty }
end
describe '#feature_enabled?' do
let(:administrateur) { create(:administrateur) }
@ -71,33 +32,33 @@ describe Administrateur, type: :model do
it { expect(administrateur.feature_enabled?(:test_a)).to be_truthy }
end
describe '#password_complexity' do
let(:email) { 'mail@beta.gouv.fr' }
let(:passwords) { ['pass', '12pass23', 'démarches ', 'démarches-simple', 'démarches-simplifiées-pwd'] }
let(:administrateur) { build(:administrateur, email: email, password: password) }
let(:min_complexity) { PASSWORD_COMPLEXITY_FOR_ADMIN }
# describe '#password_complexity' do
# let(:email) { 'mail@beta.gouv.fr' }
# let(:passwords) { ['pass', '12pass23', 'démarches ', 'démarches-simple', 'démarches-simplifiées-pwd'] }
# let(:administrateur) { build(:administrateur, email: email, password: password) }
# let(:min_complexity) { PASSWORD_COMPLEXITY_FOR_ADMIN }
subject do
administrateur.save
administrateur.errors.full_messages
end
# subject do
# administrateur.save
# administrateur.errors.full_messages
# end
context 'when password is too short' do
let(:password) { 's' * (PASSWORD_MIN_LENGTH - 1) }
# context 'when password is too short' do
# let(:password) { 's' * (PASSWORD_MIN_LENGTH - 1) }
it { expect(subject).to eq(["Le mot de passe est trop court"]) }
end
# it { expect(subject).to eq(["Le mot de passe est trop court"]) }
# end
context 'when password is too simple' do
let(:password) { passwords[min_complexity - 1] }
# context 'when password is too simple' do
# let(:password) { passwords[min_complexity - 1] }
it { expect(subject).to eq(["Le mot de passe n'est pas assez complexe"]) }
end
# it { expect(subject).to eq(["Le mot de passe n'est pas assez complexe"]) }
# end
context 'when password is acceptable' do
let(:password) { passwords[min_complexity] }
# context 'when password is acceptable' do
# let(:password) { passwords[min_complexity] }
it { expect(subject).to eq([]) }
end
end
# it { expect(subject).to eq([]) }
# end
# end
end

View file

@ -4,13 +4,15 @@ describe Administration, type: :model do
describe '#invite_admin' do
let(:administration) { create :administration }
let(:valid_email) { 'paul@tps.fr' }
subject { administration.invite_admin(valid_email) }
it {
expect(subject.errors).to be_empty
expect(subject).to be_persisted
expect(administration.invite_admin(valid_email).errors).not_to be_empty
user = subject
expect(user.errors).to be_empty
expect(user).to be_persisted
}
it { expect(administration.invite_admin(nil).errors).not_to be_empty }
it { expect(administration.invite_admin('toto').errors).not_to be_empty }

View file

@ -12,10 +12,6 @@ describe Instructeur, type: :model do
assign(procedure_2)
end
describe 'default features' do
it { expect(instructeur.features).to eq({ "enable_email_login_token" => true }) }
end
describe '#visible_procedures' do
let(:procedure_not_assigned) { create :procedure, administrateur: admin }
let(:procedure_with_default_path) { create :procedure, administrateur: admin }
@ -141,18 +137,6 @@ describe Instructeur, type: :model do
end
end
context 'unified login' do
it 'syncs credentials to associated administrateur' do
admin = create(:administrateur)
user = admin.instructeur.user
user.update(password: 'démarches-simplifiées-pwd')
admin.reload
expect(admin.valid_password?('démarches-simplifiées-pwd')).to be(true)
end
end
describe 'last_week_overview' do
let!(:instructeur2) { create(:instructeur) }
subject { instructeur2.last_week_overview }

View file

@ -100,18 +100,4 @@ describe User, type: :model do
it { is_expected.to be_falsey }
end
end
context 'unified login' do
it 'syncs credentials to associated administrateur' do
admin = create(:administrateur)
user = admin.instructeur.user
user.update(email: 'whoami@plop.com', password: 'démarches-simplifiées2')
user.confirm
admin.reload
expect(admin.email).to eq('whoami@plop.com')
expect(admin.valid_password?('démarches-simplifiées2')).to be(true)
end
end
end

View file

@ -21,6 +21,10 @@ module FeatureHelpers
fill_in :user_email, with: email
fill_in :user_password, with: password
if sign_in_by_link
Flipflop::FeatureSet.current.test!.switch!(:bypass_email_login_token, false)
end
perform_enqueued_jobs do
click_on 'Se connecter'
end
@ -74,6 +78,13 @@ module FeatureHelpers
value
end
end
def click_reset_password_link_for(email)
reset_password_email = open_email(email)
token_params = reset_password_email.body.match(/reset_password_token=[^"]+/)
visit "/users/password/edit?#{token_params}"
end
end
RSpec.configure do |config|

View file

@ -6,6 +6,7 @@ describe 'layouts/procedure_context.html.haml', type: :view do
before do
allow(view).to receive(:instructeur_signed_in?).and_return(false)
allow(view).to receive(:administrateur_signed_in?).and_return(false)
end
subject do