From 034929632bf9bbf4cb8d69cfcd9db151ae5a5366 Mon Sep 17 00:00:00 2001 From: pedong Date: Thu, 14 Feb 2019 15:24:12 +0100 Subject: [PATCH 01/21] [fix #3417] add emtpy value for departement --- .../shared/dossiers/editable_champs/_departements.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/shared/dossiers/editable_champs/_departements.html.haml b/app/views/shared/dossiers/editable_champs/_departements.html.haml index 9f7cc8a8b..e3084d148 100644 --- a/app/views/shared/dossiers/editable_champs/_departements.html.haml +++ b/app/views/shared/dossiers/editable_champs/_departements.html.haml @@ -1,3 +1,4 @@ = form.select :value, Champs::DepartementChamp.departements, + include_blank: true, required: champ.mandatory? From 235310bcf6115d0be2a79a56ba37a8d4c9e8464f Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 12 Feb 2019 17:55:42 +0100 Subject: [PATCH 02/21] Fix upload progress bar --- app/javascript/shared/activestorage/progress.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/shared/activestorage/progress.js b/app/javascript/shared/activestorage/progress.js index efaf9349c..aa428a4ac 100644 --- a/app/javascript/shared/activestorage/progress.js +++ b/app/javascript/shared/activestorage/progress.js @@ -28,7 +28,7 @@ addEventListener('direct-upload:progress', event => { progress = event.detail.progress, progressElement = document.getElementById('direct-upload-progress-' + id); - progressElement.style.width = `${progress} %`; + progressElement.style.width = `${progress}%`; }); addEventListener('direct-upload:error', event => { From 1e8bc3e14ce0a3fed832d415c6ff86f57a1ac83e Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 12 Feb 2019 17:56:06 +0100 Subject: [PATCH 03/21] Refactor upload progress bar --- .../shared/activestorage/progress.js | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/app/javascript/shared/activestorage/progress.js b/app/javascript/shared/activestorage/progress.js index aa428a4ac..996fb8c11 100644 --- a/app/javascript/shared/activestorage/progress.js +++ b/app/javascript/shared/activestorage/progress.js @@ -1,49 +1,64 @@ addEventListener('direct-upload:initialize', event => { - const target = event.target, - detail = event.detail, - id = detail.id, - file = detail.file; + const { + target, + detail: { + id, + file: { name: filename } + } + } = event; - target.insertAdjacentHTML( - 'beforebegin', - '\n
\n
\n' + - file.name + - '\n
\n' + const errorElements = target.parentElement.querySelectorAll( + '.direct-upload--error' ); + for (let element of errorElements) { + element.remove(); + } + target.insertAdjacentHTML('beforebegin', template(id, filename)); }); addEventListener('direct-upload:start', event => { const id = event.detail.id, - element = document.getElementById('direct-upload-' + id); + element = getDirectUploadElement(id); element.classList.remove('direct-upload--pending'); + return false; }); addEventListener('direct-upload:progress', event => { - const id = event.detail.id, - progress = event.detail.progress, - progressElement = document.getElementById('direct-upload-progress-' + id); + const { id, progress } = event.detail, + progressElement = getDirectUploadProgressElement(id); progressElement.style.width = `${progress}%`; }); addEventListener('direct-upload:error', event => { - event.preventDefault(); - const id = event.detail.id, - error = event.detail.error, - element = document.getElementById('direct-upload-' + id); + const { id, error } = event.detail, + element = getDirectUploadElement(id); element.classList.add('direct-upload--error'); element.setAttribute('title', error); }); addEventListener('direct-upload:end', event => { - const id = event.detail.id, - element = document.getElementById('direct-upload-' + id); + const { id } = event.detail, + element = getDirectUploadElement(id); element.classList.add('direct-upload--complete'); }); + +function template(id, filename) { + return `
+
+ ${filename} +
`; +} + +function getDirectUploadElement(id) { + return document.getElementById(`direct-upload-${id}`); +} + +function getDirectUploadProgressElement(id) { + return document.querySelector( + `#direct-upload-${id} .direct-upload__progress` + ); +} From 5e806aa39e47cae65dbcaf17ba96442e70c0e39f Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 13 Feb 2019 14:16:22 +0100 Subject: [PATCH 04/21] Add progress bar to model uploads --- .../administrateur/DraggableItem.js | 23 ++--- app/javascript/packs/application-old.js | 2 +- app/javascript/packs/application.js | 2 +- .../shared/activestorage/progress-bar.js | 92 +++++++++++++++++++ .../shared/activestorage/progress.js | 64 ------------- app/javascript/shared/activestorage/ujs.js | 27 ++++++ .../shared/activestorage/uploader.js | 43 +++++++++ 7 files changed, 171 insertions(+), 82 deletions(-) create mode 100644 app/javascript/shared/activestorage/progress-bar.js delete mode 100644 app/javascript/shared/activestorage/progress.js create mode 100644 app/javascript/shared/activestorage/ujs.js create mode 100644 app/javascript/shared/activestorage/uploader.js diff --git a/app/javascript/new_design/administrateur/DraggableItem.js b/app/javascript/new_design/administrateur/DraggableItem.js index ea6c1361b..df94177a5 100644 --- a/app/javascript/new_design/administrateur/DraggableItem.js +++ b/app/javascript/new_design/administrateur/DraggableItem.js @@ -1,5 +1,5 @@ import { getJSON, debounce } from '@utils'; -import { DirectUpload } from 'activestorage'; +import Uploader from '../../shared/activestorage/uploader'; export default { props: ['state', 'index', 'item'], @@ -181,7 +181,12 @@ export default { const file = input.files[0]; if (file) { this.isUploading = true; - uploadFile(this.state.directUploadUrl, file).then(({ signed_id }) => { + const controller = new Uploader( + input, + file, + this.state.directUploadUrl + ); + controller.start().then(signed_id => { this.pieceJustificativeTemplate = signed_id; this.isUploading = false; this.debouncedSave(); @@ -247,17 +252,3 @@ const EXCLUDE_FROM_REPETITION = [ function castBoolean(value) { return value && value != 0; } - -function uploadFile(directUploadUrl, file) { - const upload = new DirectUpload(file, directUploadUrl); - - return new Promise((resolve, reject) => { - upload.create((error, blob) => { - if (error) { - reject(error); - } else { - resolve(blob); - } - }); - }); -} diff --git a/app/javascript/packs/application-old.js b/app/javascript/packs/application-old.js index 4a984df96..4e327c228 100644 --- a/app/javascript/packs/application-old.js +++ b/app/javascript/packs/application-old.js @@ -4,7 +4,7 @@ import Rails from 'rails-ujs'; import * as ActiveStorage from 'activestorage'; import jQuery from 'jquery'; -import '../shared/activestorage/progress'; +import '../shared/activestorage/ujs'; import '../shared/sentry'; import '../shared/rails-ujs-fix'; import '../shared/safari-11-file-xhr-workaround'; diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 128b7c1b1..df838bc5a 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -5,7 +5,7 @@ import * as ActiveStorage from 'activestorage'; import Chartkick from 'chartkick'; import Highcharts from 'highcharts'; -import '../shared/activestorage/progress'; +import '../shared/activestorage/ujs'; import '../shared/sentry'; import '../shared/rails-ujs-fix'; import '../shared/safari-11-file-xhr-workaround'; diff --git a/app/javascript/shared/activestorage/progress-bar.js b/app/javascript/shared/activestorage/progress-bar.js new file mode 100644 index 000000000..e08df104d --- /dev/null +++ b/app/javascript/shared/activestorage/progress-bar.js @@ -0,0 +1,92 @@ +const PENDING_CLASS = 'direct-upload--pending'; +const ERROR_CLASS = 'direct-upload--error'; +const COMPLETE_CLASS = 'direct-upload--complete'; + +/** + ProgressBar is and utility class responsible for + rendering upload progress bar. It is used to handle + direct-upload form ujs events but also in the + Uploader delegate used with uploads on json api. + */ +export default class ProgressBar { + static init(input, id, file) { + clearErrors(input); + const html = this.render(id, file.name); + input.insertAdjacentHTML('beforebegin', html); + } + + static start(id) { + const element = getDirectUploadElement(id); + + element.classList.remove(PENDING_CLASS); + } + + static progress(id, progress) { + const element = getDirectUploadProgressElement(id); + + element.style.width = `${progress}%`; + } + + static error(id, error) { + const element = getDirectUploadElement(id); + + element.classList.add(ERROR_CLASS); + element.setAttribute('title', error); + } + + static end(id) { + const element = getDirectUploadElement(id); + + element.classList.add(COMPLETE_CLASS); + } + + static render(id, filename) { + return `
+
+ ${filename} +
`; + } + + constructor(input, id, file) { + this.constructor.init(input, id, file); + this.id = id; + } + + start() { + this.constructor.start(this.id); + } + + progress(progress) { + this.constructor.progress(this.id, progress); + } + + error(error) { + this.constructor.error(this.id, error); + } + + end() { + this.constructor.end(this.id); + } + + destroy() { + const element = getDirectUploadElement(this.id); + element.remove(); + } +} + +function clearErrors(input) { + const errorElements = input.parentElement.querySelectorAll(`.${ERROR_CLASS}`); + for (let element of errorElements) { + element.remove(); + } +} + +function getDirectUploadElement(id) { + return document.getElementById(`direct-upload-${id}`); +} + +function getDirectUploadProgressElement(id) { + return document.querySelector( + `#direct-upload-${id} .direct-upload__progress` + ); +} diff --git a/app/javascript/shared/activestorage/progress.js b/app/javascript/shared/activestorage/progress.js deleted file mode 100644 index 996fb8c11..000000000 --- a/app/javascript/shared/activestorage/progress.js +++ /dev/null @@ -1,64 +0,0 @@ -addEventListener('direct-upload:initialize', event => { - const { - target, - detail: { - id, - file: { name: filename } - } - } = event; - - const errorElements = target.parentElement.querySelectorAll( - '.direct-upload--error' - ); - for (let element of errorElements) { - element.remove(); - } - target.insertAdjacentHTML('beforebegin', template(id, filename)); -}); - -addEventListener('direct-upload:start', event => { - const id = event.detail.id, - element = getDirectUploadElement(id); - - element.classList.remove('direct-upload--pending'); - return false; -}); - -addEventListener('direct-upload:progress', event => { - const { id, progress } = event.detail, - progressElement = getDirectUploadProgressElement(id); - - progressElement.style.width = `${progress}%`; -}); - -addEventListener('direct-upload:error', event => { - const { id, error } = event.detail, - element = getDirectUploadElement(id); - - element.classList.add('direct-upload--error'); - element.setAttribute('title', error); -}); - -addEventListener('direct-upload:end', event => { - const { id } = event.detail, - element = getDirectUploadElement(id); - - element.classList.add('direct-upload--complete'); -}); - -function template(id, filename) { - return `
-
- ${filename} -
`; -} - -function getDirectUploadElement(id) { - return document.getElementById(`direct-upload-${id}`); -} - -function getDirectUploadProgressElement(id) { - return document.querySelector( - `#direct-upload-${id} .direct-upload__progress` - ); -} diff --git a/app/javascript/shared/activestorage/ujs.js b/app/javascript/shared/activestorage/ujs.js new file mode 100644 index 000000000..43beef778 --- /dev/null +++ b/app/javascript/shared/activestorage/ujs.js @@ -0,0 +1,27 @@ +import ProgressBar from './progress-bar'; + +const INITIALIZE_EVENT = 'direct-upload:initialize'; +const START_EVENT = 'direct-upload:start'; +const PROGRESS_EVENT = 'direct-upload:progress'; +const ERROR_EVENT = 'direct-upload:error'; +const END_EVENT = 'direct-upload:end'; + +addEventListener(INITIALIZE_EVENT, ({ target, detail: { id, file } }) => { + ProgressBar.init(target, id, file); +}); + +addEventListener(START_EVENT, ({ detail: { id } }) => { + ProgressBar.start(id); +}); + +addEventListener(PROGRESS_EVENT, ({ detail: { id, progress } }) => { + ProgressBar.progress(id, progress); +}); + +addEventListener(ERROR_EVENT, ({ detail: { id, error } }) => { + ProgressBar.error(id, error); +}); + +addEventListener(END_EVENT, ({ detail: { id } }) => { + ProgressBar.end(id); +}); diff --git a/app/javascript/shared/activestorage/uploader.js b/app/javascript/shared/activestorage/uploader.js new file mode 100644 index 000000000..e2ebf5b6e --- /dev/null +++ b/app/javascript/shared/activestorage/uploader.js @@ -0,0 +1,43 @@ +import { DirectUpload } from 'activestorage'; +import ProgressBar from './progress-bar'; + +/** + Uploader class is a delegate for DirectUpload instance + used to track lifecycle and progress of un upload. + */ +export default class Uploader { + constructor(input, file, directUploadUrl) { + this.directUpload = new DirectUpload(file, directUploadUrl, this); + this.progressBar = new ProgressBar(input, this.directUpload.id, file); + } + + start() { + this.progressBar.start(); + + return new Promise((resolve, reject) => { + this.directUpload.create((error, attributes) => { + if (error) { + this.progressBar.error(error); + reject(error); + } else { + resolve(attributes.signed_id); + } + this.progressBar.end(); + this.progressBar.destroy(); + }); + }); + } + + uploadRequestDidProgress(event) { + const progress = (event.loaded / event.total) * 100; + if (progress) { + this.progressBar.progress(progress); + } + } + + directUploadWillStoreFileWithXHR(xhr) { + xhr.upload.addEventListener('progress', event => + this.uploadRequestDidProgress(event) + ); + } +} From 6aa2bf64caf6f4b07d1da659dad8c26fb2465544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chai=CC=88b=20Martinez?= Date: Thu, 14 Feb 2019 12:27:57 +0100 Subject: [PATCH 05/21] Texte update --- app/views/administration_mailer/refuse_admin.html.haml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/views/administration_mailer/refuse_admin.html.haml b/app/views/administration_mailer/refuse_admin.html.haml index 014875d10..fefcdce29 100644 --- a/app/views/administration_mailer/refuse_admin.html.haml +++ b/app/views/administration_mailer/refuse_admin.html.haml @@ -4,14 +4,20 @@ Bonjour, %p - Les comptes administrateurs sont destinés aux administrations publiques souhaitant mettre en place des démarches dématérialisées avec demarches-simplifiees.fr. Il ne semble pas que ce soit votre cas. + Les comptes administrateurs sont destinés aux administrations publiques souhaitant mettre en place sur leurs sites internet des démarches dématérialisées pour leurs usagers. Il ne semble pas que ce soit votre cas. + %p - Pour les usagers qui souhaitent remplir une démarche, l’entrée dans demarches-simplifiees.fr se fait via un lien fourni par l’administration responsable, sur son propre site web. Ce lien vous permettra de créer un compte et de remplir le formulaire dans la foulée. + Pour les usagers ou les administrations publiques (collectivités, etc.) qui souhaitent remplir une démarche ou un déposer un dossier en ligne, l’entrée dans demarches-simplifiees.fr se fait via un lien fourni par l’administration responsable, sur son propre site web. Ce lien vous permettra de créer un compte et de remplir le formulaire dans la foulée. %p Si par contre vous rencontrez des problèmes lors de l'utilisation de demarches-simplifiees.fr en tant qu'usager, merci d'expliciter le problème rencontré sur notre = link_to("formulaire de contact", contact_url) \. +%p + Si vous avez fait une demande de compte administrateur légitime avec une adresse email grand public (Orange, Wanadoo etc), merci de nous contacter sur notre + = link_to("formulaire de contact administrateur", contact_admin_url) + \. + = render partial: "layouts/mailers/signature" From a7e068003aec97948b81220cadca6da004e53e5a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 13 Feb 2019 16:13:37 +0100 Subject: [PATCH 06/21] [fix #3427] Administration can soft delete a dossier --- .../manager/dossiers_controller.rb | 11 +++++++++++ app/models/deleted_dossier.rb | 4 ++++ app/models/dossier.rb | 19 ++++++++++++++++--- app/models/dossier_operation_log.rb | 4 +++- app/views/manager/dossiers/show.html.erb | 5 ++++- config/routes.rb | 1 + ...tration_column_to_log_dossier_operation.rb | 5 +++++ db/schema.rb | 5 ++++- .../manager/dossiers_controller_spec.rb | 14 ++++++++++++++ spec/models/dossier_spec.rb | 17 +++++++++++++++++ 10 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20190213144145_add_administration_column_to_log_dossier_operation.rb create mode 100644 spec/controllers/manager/dossiers_controller_spec.rb diff --git a/app/controllers/manager/dossiers_controller.rb b/app/controllers/manager/dossiers_controller.rb index 72858f2a0..4407d2323 100644 --- a/app/controllers/manager/dossiers_controller.rb +++ b/app/controllers/manager/dossiers_controller.rb @@ -29,6 +29,17 @@ module Manager redirect_to manager_dossier_path(dossier) end + def hide + dossier = Dossier.find(params[:id]) + deleted_dossier = dossier.hide!(current_administration) + + DossierMailer.notify_deletion_to_user(deleted_dossier, dossier.user.email).deliver_later + logger.info("Le dossier #{dossier.id} est supprimé par #{current_administration.email}") + flash[:notice] = "Le dossier #{dossier.id} est supprimé" + + redirect_to manager_dossier_path(dossier) + end + private def unfiltered_list? diff --git a/app/models/deleted_dossier.rb b/app/models/deleted_dossier.rb index 31cffa793..9fba98749 100644 --- a/app/models/deleted_dossier.rb +++ b/app/models/deleted_dossier.rb @@ -1,3 +1,7 @@ class DeletedDossier < ApplicationRecord belongs_to :procedure + + def self.create_from_dossier(dossier) + DeletedDossier.create!(dossier_id: dossier.id, procedure: dossier.procedure, state: dossier.state, deleted_at: Time.now.utc) + end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 70f023e26..4c5833b30 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -258,9 +258,8 @@ class Dossier < ApplicationRecord end def delete_and_keep_track - now = Time.zone.now - deleted_dossier = DeletedDossier.create!(dossier_id: id, procedure: procedure, state: state, deleted_at: now) - update(hidden_at: now) + deleted_dossier = DeletedDossier.create_from_dossier(self) + update(hidden_at: deleted_dossier.deleted_at) if en_construction? administration_emails = followers_gestionnaires.present? ? followers_gestionnaires.pluck(:email) : [procedure.administrateur.email] @@ -318,6 +317,13 @@ class Dossier < ApplicationRecord log_dossier_operation(nil, :accepter, automatic_operation: true) end + def hide!(administration) + update(hidden_at: Time.zone.now) + + log_administration_dossier_operation(administration, :supprimer) + DeletedDossier.create_from_dossier(self) + end + def refuser!(gestionnaire, motivation) self.motivation = motivation self.en_instruction_at ||= Time.zone.now @@ -356,6 +362,13 @@ class Dossier < ApplicationRecord ) end + def log_administration_dossier_operation(administration, operation) + dossier_operation_logs.create( + administration: administration, + operation: DossierOperationLog.operations.fetch(operation) + ) + end + def update_state_dates if en_construction? && !self.en_construction_at self.en_construction_at = Time.zone.now diff --git a/app/models/dossier_operation_log.rb b/app/models/dossier_operation_log.rb index fd4e770a4..6dced86b6 100644 --- a/app/models/dossier_operation_log.rb +++ b/app/models/dossier_operation_log.rb @@ -4,9 +4,11 @@ class DossierOperationLog < ApplicationRecord repasser_en_construction: 'repasser_en_construction', accepter: 'accepter', refuser: 'refuser', - classer_sans_suite: 'classer_sans_suite' + classer_sans_suite: 'classer_sans_suite', + supprimer: 'supprimer' } belongs_to :dossier belongs_to :gestionnaire + belongs_to :administration end diff --git a/app/views/manager/dossiers/show.html.erb b/app/views/manager/dossiers/show.html.erb index 9da620ca8..636962cd6 100644 --- a/app/views/manager/dossiers/show.html.erb +++ b/app/views/manager/dossiers/show.html.erb @@ -23,7 +23,7 @@ as well as a link to its edit page.

<%= content_for(:title) %> <% if dossier.hidden_at %> - (SUPPRIMÉ) + (Supprimé) <% end %>

@@ -31,6 +31,9 @@ as well as a link to its edit page. <% if dossier.termine? %> <%= link_to 'Repasser en instruction', change_state_to_instruction_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Repasser en instruction ?" } %> <% end %> + <% if dossier.hidden_at.nil? %> + <%= link_to 'Supprimer le dossier', hide_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %> + <% end %>
diff --git a/config/routes.rb b/config/routes.rb index 6e0f65c98..ba28c63cd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,6 +14,7 @@ Rails.application.routes.draw do resources :dossiers, only: [:index, :show] do post 'change_state_to_instruction', on: :member + post 'hide', on: :member end resources :administrateurs, only: [:index, :show, :new, :create] do diff --git a/db/migrate/20190213144145_add_administration_column_to_log_dossier_operation.rb b/db/migrate/20190213144145_add_administration_column_to_log_dossier_operation.rb new file mode 100644 index 000000000..cfaf6aa1f --- /dev/null +++ b/db/migrate/20190213144145_add_administration_column_to_log_dossier_operation.rb @@ -0,0 +1,5 @@ +class AddAdministrationColumnToLogDossierOperation < ActiveRecord::Migration[5.2] + def change + add_reference :dossier_operation_logs, :administration, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index b710d1d37..74265b893 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_01_10_163655) do +ActiveRecord::Schema.define(version: 2019_02_13_144145) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -219,6 +219,8 @@ ActiveRecord::Schema.define(version: 2019_01_10_163655) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "automatic_operation", default: false, null: false + t.bigint "administration_id" + t.index ["administration_id"], name: "index_dossier_operation_logs_on_administration_id" t.index ["dossier_id"], name: "index_dossier_operation_logs_on_dossier_id" t.index ["gestionnaire_id"], name: "index_dossier_operation_logs_on_gestionnaire_id" end @@ -597,6 +599,7 @@ ActiveRecord::Schema.define(version: 2019_01_10_163655) do add_foreign_key "champs", "champs", column: "parent_id" add_foreign_key "closed_mails", "procedures" add_foreign_key "commentaires", "dossiers" + add_foreign_key "dossier_operation_logs", "administrations" add_foreign_key "dossier_operation_logs", "dossiers" add_foreign_key "dossier_operation_logs", "gestionnaires" add_foreign_key "dossiers", "users" diff --git a/spec/controllers/manager/dossiers_controller_spec.rb b/spec/controllers/manager/dossiers_controller_spec.rb new file mode 100644 index 000000000..e3c5f3cad --- /dev/null +++ b/spec/controllers/manager/dossiers_controller_spec.rb @@ -0,0 +1,14 @@ +describe Manager::DossiersController, type: :controller do + describe '#hide' do + let(:administration) { create :administration } + let!(:dossier) { create(:dossier) } + + before do + sign_in administration + post :hide, params: { id: dossier.id } + dossier.reload + end + + it { expect(dossier.hidden_at).not_to be_nil } + end +end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 60e8dab8c..78a39d7e0 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -892,4 +892,21 @@ describe Dossier do end end end + + describe '#hide!' do + let(:dossier) { create(:dossier) } + let(:administration) { create(:administration) } + let(:last_operation) { dossier.dossier_operation_logs.last } + + before do + Timecop.freeze + dossier.hide!(administration) + end + + after { Timecop.return } + + it { expect(dossier.hidden_at).to eq(Time.zone.now) } + it { expect(last_operation.operation).to eq('supprimer') } + it { expect(last_operation.administration).to eq(administration) } + end end From e653e5876bffe24d1c9054a59ead6dc98cd9836b Mon Sep 17 00:00:00 2001 From: clemkeirua Date: Mon, 18 Feb 2019 11:59:16 +0100 Subject: [PATCH 07/21] =?UTF-8?q?fix:=20ne=20pas=20afficher=20de=20date=20?= =?UTF-8?q?de=20d=C3=A9pot=20si=20celle-ci=20n'est=20pas=20dispo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/new_user/dossiers/show/_header.html.haml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/new_user/dossiers/show/_header.html.haml b/app/views/new_user/dossiers/show/_header.html.haml index b49179440..e1d488610 100644 --- a/app/views/new_user/dossiers/show/_header.html.haml +++ b/app/views/new_user/dossiers/show/_header.html.haml @@ -5,7 +5,10 @@ .title-container %span.icon.folder %h1= dossier.procedure.libelle - %h2 Dossier nº #{dossier.id} - Déposé le #{l(dossier.en_construction_at, format: '%d %B %Y')} + %h2 + Dossier nº #{dossier.id} + - if dossier.en_construction_at.present? + = "- Déposé le #{l(dossier.en_construction_at, format: '%d %B %Y')}" - if current_user.owns?(dossier) .header-actions From 1ca64e3f28a1122ad6abba2e1d66cf6c9abeada6 Mon Sep 17 00:00:00 2001 From: Frederic Merizen Date: Fri, 15 Feb 2019 19:35:50 +0100 Subject: [PATCH 08/21] [Fix #1140] Let's close this stupid issue already --- app/views/admin/attestation_templates/edit.html.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/admin/attestation_templates/edit.html.haml b/app/views/admin/attestation_templates/edit.html.haml index 34b4572fe..ce16b0873 100644 --- a/app/views/admin/attestation_templates/edit.html.haml +++ b/app/views/admin/attestation_templates/edit.html.haml @@ -10,7 +10,11 @@ - else %small Désactivée - %p.notice Les attestations, si elles sont activées, sont délivrées par email aux usagers lorsque leurs dossiers sont acceptés, et sont également disponibles au téléchargement sur leur espace personnel. + %p.notice + L’attestation, si elle est activée, est émise au moment où un dossier est accepté. + %br + L’email d’accusé d’acceptation envoyé à l’usager comporte alors un lien vers l’attestation ; + celle-ci est également disponible au téléchargement depuis l’espace personnel de l’usager. .image-upload - if @attestation_template.logo.present? From b152025c5f80459a6d73a95b4b815f8cd2111023 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 18 Feb 2019 14:42:33 +0100 Subject: [PATCH 09/21] Manager: add procedure preview link --- app/views/manager/procedures/show.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/manager/procedures/show.html.erb b/app/views/manager/procedures/show.html.erb index e4a00a4d3..908b0e2bf 100644 --- a/app/views/manager/procedures/show.html.erb +++ b/app/views/manager/procedures/show.html.erb @@ -33,6 +33,8 @@ as well as a link to its edit page.
+ <%= link_to 'aperçu', apercu_procedure_path(procedure), class: 'button' %> + <% if !procedure.whitelisted? %> <%= link_to 'whitelister', whitelist_manager_procedure_path(procedure), method: :post, class: 'button' %> <% end %> From ffe0ceaaa27254096c718d5735dd82c13b8e1ab3 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Mon, 18 Feb 2019 12:04:35 +0100 Subject: [PATCH 10/21] manager: allow search procedures by path By default the Field::String type is searchable. --- app/fields/procedure_link_field.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/fields/procedure_link_field.rb b/app/fields/procedure_link_field.rb index a8669a15c..2862bab0e 100644 --- a/app/fields/procedure_link_field.rb +++ b/app/fields/procedure_link_field.rb @@ -1,6 +1,6 @@ require "administrate/field/base" -class ProcedureLinkField < Administrate::Field::Base +class ProcedureLinkField < Administrate::Field::String def name "Lien démarche" end From e1a2a8c0d1b45f660ceb5341eda88c0e00d5a0c3 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 13 Feb 2019 16:16:45 +0100 Subject: [PATCH 11/21] stylesheet: improve upload progress bar appearance --- app/assets/stylesheets/new_design/direct_uploads.scss | 9 +++++---- app/assets/stylesheets/new_design/forms.scss | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/new_design/direct_uploads.scss b/app/assets/stylesheets/new_design/direct_uploads.scss index eda8e75c7..febde1bd0 100644 --- a/app/assets/stylesheets/new_design/direct_uploads.scss +++ b/app/assets/stylesheets/new_design/direct_uploads.scss @@ -1,12 +1,13 @@ +@import "colors"; + .direct-upload { display: inline-block; position: relative; - padding: 2px 4px; + padding: 4px 15px; margin: 0 3px 3px 0; - border: 1px solid rgba(0, 0, 0, 0.3); + border: 1px solid $border-grey; border-radius: 3px; - font-size: 11px; - line-height: 13px; + font-size: 14px; } .direct-upload--pending { diff --git a/app/assets/stylesheets/new_design/forms.scss b/app/assets/stylesheets/new_design/forms.scss index e2202ecad..0e6954664 100644 --- a/app/assets/stylesheets/new_design/forms.scss +++ b/app/assets/stylesheets/new_design/forms.scss @@ -111,6 +111,10 @@ } } + .direct-upload { + margin-bottom: 2 * $default-padding; + } + .add-row { margin-bottom: 2 * $default-padding; } From d1f514c7b083dba93531dce061edad0e07f1e6b7 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 13 Feb 2019 15:24:09 +0000 Subject: [PATCH 12/21] dossier: make clear when files are being uploaded --- app/views/shared/dossiers/_edit.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/dossiers/_edit.html.haml b/app/views/shared/dossiers/_edit.html.haml index 3d52cd697..1b8dbadc0 100644 --- a/app/views/shared/dossiers/_edit.html.haml +++ b/app/views/shared/dossiers/_edit.html.haml @@ -75,18 +75,18 @@ name: :save_draft, value: true, class: 'button send secondary', - data: { disable: true } + data: { 'disable-with': "Envoi en cours…" } - if dossier.can_transition_to_en_construction? = f.button 'Soumettre le dossier', class: 'button send primary', disabled: !current_user.owns?(dossier), - data: { disable: true } + data: { 'disable-with': "Envoi en cours…" } - else = f.button 'Enregistrer les modifications du dossier', class: 'button send primary', - data: { disable: true } + data: { 'disable-with': "Envoi en cours…" } - if dossier.brouillon? && !current_user.owns?(dossier) .send-notice.invite-cannot-submit From 9d92e43d8d8fde3e6d691a270b75878f33941f88 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 18 Feb 2019 16:18:09 +0100 Subject: [PATCH 13/21] [fix #3315] Migrate service organisme --- app/models/service.rb | 7 +++---- config/locales/models/service/fr.yml | 15 +++++++-------- .../2019_02_18_migrate_service_organisme.rake | 17 +++++++++++++++++ .../services_controller_spec.rb | 10 +++++----- spec/factories/service.rb | 2 +- spec/models/service_spec.rb | 2 +- 6 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 lib/tasks/2019_02_18_migrate_service_organisme.rake diff --git a/app/models/service.rb b/app/models/service.rb index 5305c2989..4f8cab66c 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -7,11 +7,10 @@ class Service < ApplicationRecord enum type_organisme: { administration_centrale: 'administration_centrale', association: 'association', - commune: 'commune', - departement: 'departement', + collectivite_territoriale: 'collectivite_territoriale', etablissement_enseignement: 'etablissement_enseignement', - prefecture: 'prefecture', - region: 'region', + operateur_d_etat: "operateur_d_etat", + service_deconcentre_de_l_etat: 'service_deconcentre_de_l_etat', autre: 'autre' } diff --git a/config/locales/models/service/fr.yml b/config/locales/models/service/fr.yml index 4a573ecc4..68e032c8d 100644 --- a/config/locales/models/service/fr.yml +++ b/config/locales/models/service/fr.yml @@ -1,10 +1,9 @@ fr: type_organisme: - administration_centrale: 'administration centrale' - association: 'association' - commune: 'commune' - departement: 'département' - etablissement_enseignement: 'établissement d’enseignement' - prefecture: 'préfecture' - region: 'région' - autre: 'autre' + administration_centrale: 'Administration centrale' + association: 'Association' + collectivite_territoriale: 'Collectivité territoriale' + etablissement_enseignement: 'Établissement d’enseignement' + operateur_d_etat: "Opérateur d'État" + service_deconcentre_de_l_etat: "Service déconcentré de l'État" + autre: 'Autre' diff --git a/lib/tasks/2019_02_18_migrate_service_organisme.rake b/lib/tasks/2019_02_18_migrate_service_organisme.rake new file mode 100644 index 000000000..369a85a5f --- /dev/null +++ b/lib/tasks/2019_02_18_migrate_service_organisme.rake @@ -0,0 +1,17 @@ +namespace :after_party do + desc 'Deployment task: migrate service organisme' + task migrate_service_organisme: :environment do + table = { + 'commune': 'collectivite_territoriale', + 'departement': 'collectivite_territoriale', + 'region': 'collectivite_territoriale', + 'prefecture': 'service_deconcentre_de_l_etat' + } + + table.each do |(old_name, new_name)| + Service.where(type_organisme: old_name).update_all(type_organisme: new_name) + end + + AfterParty::TaskRecord.create version: '20190201121252' + end +end diff --git a/spec/controllers/new_administrateur/services_controller_spec.rb b/spec/controllers/new_administrateur/services_controller_spec.rb index 081603342..e020d9751 100644 --- a/spec/controllers/new_administrateur/services_controller_spec.rb +++ b/spec/controllers/new_administrateur/services_controller_spec.rb @@ -15,7 +15,7 @@ describe NewAdministrateur::ServicesController, type: :controller do nom: 'super service', organisme: 'organisme', siret: '01234567891234', - type_organisme: 'region', + type_organisme: 'association', email: 'email@toto.com', telephone: '1234', horaires: 'horaires', @@ -30,7 +30,7 @@ describe NewAdministrateur::ServicesController, type: :controller do it { expect(Service.last.nom).to eq('super service') } it { expect(Service.last.organisme).to eq('organisme') } it { expect(Service.last.siret).to eq('01234567891234') } - it { expect(Service.last.type_organisme).to eq(Service.type_organismes.fetch(:region)) } + it { expect(Service.last.type_organisme).to eq(Service.type_organismes.fetch(:association)) } it { expect(Service.last.email).to eq('email@toto.com') } it { expect(Service.last.telephone).to eq('1234') } it { expect(Service.last.horaires).to eq('horaires') } @@ -49,7 +49,7 @@ describe NewAdministrateur::ServicesController, type: :controller do describe '#update' do let!(:service) { create(:service, administrateur: admin) } - let(:service_params) { { nom: 'nom', type_organisme: Service.type_organismes.fetch(:region) } } + let(:service_params) { { nom: 'nom', type_organisme: Service.type_organismes.fetch(:association) } } before do sign_in admin @@ -65,12 +65,12 @@ describe NewAdministrateur::ServicesController, type: :controller do it { expect(flash.alert).to be_nil } it { expect(flash.notice).to eq('nom modifié') } it { expect(Service.last.nom).to eq('nom') } - it { expect(Service.last.type_organisme).to eq(Service.type_organismes.fetch(:region)) } + it { expect(Service.last.type_organisme).to eq(Service.type_organismes.fetch(:association)) } it { expect(response).to redirect_to(services_path(procedure_id: procedure.id)) } end context 'when updating a service with invalid data' do - let(:service_params) { { nom: '', type_organisme: Service.type_organismes.fetch(:region) } } + let(:service_params) { { nom: '', type_organisme: Service.type_organismes.fetch(:association) } } it { expect(flash.alert).not_to be_nil } it { expect(response).to render_template(:edit) } diff --git a/spec/factories/service.rb b/spec/factories/service.rb index 710721e7c..889a80240 100644 --- a/spec/factories/service.rb +++ b/spec/factories/service.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :service do nom { 'service' } organisme { 'organisme' } - type_organisme { Service.type_organismes.fetch(:commune) } + type_organisme { Service.type_organismes.fetch(:association) } administrateur { create(:administrateur) } email { 'email@toto.com' } telephone { '1234' } diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 2a24cc89f..e34f56dac 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -5,7 +5,7 @@ describe Service, type: :model do { nom: 'service des jardins', organisme: 'mairie des iles', - type_organisme: Service.type_organismes.fetch(:commune), + type_organisme: Service.type_organismes.fetch(:association), email: 'super@email.com', telephone: '1212202', horaires: 'du lundi au vendredi', From bee9a108c5963d5b1d5993c8aed6358180ff41af Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 1 Feb 2019 17:17:10 +0100 Subject: [PATCH 14/21] split login and trusted_device logic --- app/controllers/application_controller.rb | 29 ++++++ app/controllers/users/sessions_controller.rb | 39 +++----- app/models/concerns/trusted_device_concern.rb | 7 ++ .../application_controller_spec.rb | 51 ++++++++++ .../sessions/sessions_controller_spec.rb | 1 + .../users/sessions_controller_spec.rb | 98 +++++++++---------- 6 files changed, 143 insertions(+), 82 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3b1243d20..175cb05a9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,6 @@ class ApplicationController < ActionController::Base + include TrustedDeviceConcern + MAINTENANCE_MESSAGE = 'Le site est actuellement en maintenance. Il sera à nouveau disponible dans un court instant.' # Prevent CSRF attacks by raising an exception. @@ -6,6 +8,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception, if: -> { !Rails.env.test? } before_action :load_navbar_left_pannel_partial_url before_action :set_raven_context + before_action :redirect_if_untrusted before_action :authorize_request_for_profiler before_action :reject, if: -> { Flipflop.maintenance_mode? } @@ -151,4 +154,30 @@ class ApplicationController < ActionController::Base redirect_to root_path end end + + def redirect_if_untrusted + if gestionnaire_signed_in? && + sensitive_path && + current_gestionnaire.feature_enabled?(:enable_email_login_token) && + !trusted_device? + + send_login_token_or_bufferize(current_gestionnaire) + redirect_to link_sent_path(email: current_gestionnaire.email) + end + end + + def sensitive_path + path = request.path_info + + if path == '/' || + path == '/users/sign_out' || + path.start_with?('/connexion-par-jeton') || + path.start_with?('/api/') || + path.start_with?('/lien-envoye') + + false + else + true + end + end end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index ee94aed4a..b02263499 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -23,20 +23,7 @@ class Users::SessionsController < Sessions::SessionsController current_user.update(loged_in_with_france_connect: nil) end - if gestionnaire_signed_in? - if trusted_device? || !current_gestionnaire.feature_enabled?(:enable_email_login_token) - set_flash_message :notice, :signed_in - redirect_to after_sign_in_path_for(:user) - else - gestionnaire = current_gestionnaire - - send_login_token_or_bufferize(gestionnaire) - - [:user, :gestionnaire, :administrateur].each { |role| sign_out(role) } - - redirect_to link_sent_path(email: gestionnaire.email) - end - elsif user_signed_in? + if gestionnaire_signed_in? || user_signed_in? set_flash_message :notice, :signed_in redirect_to after_sign_in_path_for(:user) else @@ -87,28 +74,24 @@ class Users::SessionsController < Sessions::SessionsController trust_device flash.notice = "Merci d’avoir confirmé votre connexion. Votre navigateur est maintenant authentifié pour #{TRUSTED_DEVICE_PERIOD.to_i / ActiveSupport::Duration::SECONDS_PER_DAY} jours." - user = User.find_by(email: gestionnaire.email) - administrateur = Administrateur.find_by(email: gestionnaire.email) - [user, gestionnaire, administrateur].compact.each { |resource| sign_in(resource) } - # redirect to procedure'url if stored by store_location_for(:user) in dossiers_controller # redirect to root_path otherwise - redirect_to after_sign_in_path_for(:user) + + if gestionnaire_signed_in? + redirect_to after_sign_in_path_for(:user) + else + redirect_to new_user_session_path + end else - flash[:alert] = 'Votre lien est invalide ou expiré, veuillez-vous reconnecter.' - redirect_to new_user_session_path + flash[:alert] = 'Votre lien est invalide ou expiré, un nouveau vient de vous être envoyé.' + + send_login_token_or_bufferize(gestionnaire) + redirect_to link_sent_path(email: gestionnaire.email) end end private - def send_login_token_or_bufferize(gestionnaire) - if !gestionnaire.young_login_token? - login_token = gestionnaire.login_token! - GestionnaireMailer.send_login_token(gestionnaire, login_token).deliver_later - end - end - def try_to_authenticate(klass, remember_me = false) resource = klass.find_for_database_authentication(email: params[:user][:email]) diff --git a/app/models/concerns/trusted_device_concern.rb b/app/models/concerns/trusted_device_concern.rb index 9928adfa1..258fc15fe 100644 --- a/app/models/concerns/trusted_device_concern.rb +++ b/app/models/concerns/trusted_device_concern.rb @@ -17,6 +17,13 @@ module TrustedDeviceConcern (Time.zone.now - TRUSTED_DEVICE_PERIOD) < trusted_device_cookie_created_at end + def send_login_token_or_bufferize(gestionnaire) + if !gestionnaire.young_login_token? + login_token = gestionnaire.login_token! + GestionnaireMailer.send_login_token(gestionnaire, login_token).deliver_later + end + end + private def trusted_device_cookie_created_at diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 0545d786b..cd8dbf75d 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -9,6 +9,7 @@ describe ApplicationController, type: :controller do .map(&:filter) expect(before_actions).to include(:set_raven_context) + expect(before_actions).to include(:redirect_if_untrusted) end end @@ -145,4 +146,54 @@ describe ApplicationController, type: :controller do it { expect(flash[:alert]).to eq(ApplicationController::MAINTENANCE_MESSAGE) } end end + + describe '#redirect_if_unstrusted' do + let(:current_gestionnaire) { create(:gestionnaire) } + + before do + allow(current_gestionnaire).to receive(:feature_enabled?).and_return(feature_enabled) + allow(@controller).to receive(:current_gestionnaire).and_return(current_gestionnaire) + + allow(@controller).to receive(:redirect_to) + allow(@controller).to receive(:trusted_device?).and_return(trusted_device) + allow(@controller).to receive(:gestionnaire_signed_in?).and_return(gestionnaire_signed_in) + allow(@controller).to receive(:sensitive_path).and_return(sensitive_path) + allow(@controller).to receive(:send_login_token_or_bufferize) + end + + subject { @controller.send(:redirect_if_untrusted) } + + context 'when the path is sensitive' do + let(:sensitive_path) { true } + + context 'when the gestionnaire is signed_in' do + let(:gestionnaire_signed_in) { true } + + context 'when the feature is activated' do + let(:feature_enabled) { true } + + context 'when the device is trusted' do + let(:trusted_device) { true } + + before { subject } + + it { expect(@controller).not_to have_received(:redirect_to) } + end + end + + context 'when the feature is activated' do + let(:feature_enabled) { true } + + context 'when the device is not trusted' do + let(:trusted_device) { false } + + before { subject } + + it { expect(@controller).to have_received(:redirect_to) } + it { expect(@controller).to have_received(:send_login_token_or_bufferize) } + end + end + end + end + end end diff --git a/spec/controllers/sessions/sessions_controller_spec.rb b/spec/controllers/sessions/sessions_controller_spec.rb index c0f5efc14..5d8e5839d 100644 --- a/spec/controllers/sessions/sessions_controller_spec.rb +++ b/spec/controllers/sessions/sessions_controller_spec.rb @@ -40,6 +40,7 @@ describe Sessions::SessionsController, type: :controller do @request.env["devise.mapping"] = Devise.mappings[:gestionnaire] allow_any_instance_of(described_class).to receive(:gestionnaire_signed_in?).and_return(true) + allow_any_instance_of(described_class).to receive(:current_gestionnaire).and_return(gestionnaire) end it 'calls sign out for gestionnaire' do diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index efa388790..cdab44c91 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -28,31 +28,15 @@ describe Users::SessionsController, type: :controller do context 'when the device is not trusted' do let(:trusted_device) { false } - it 'redirects to the confirmation link path' do + it 'redirects to the root path' do subject - expect(controller).to redirect_to link_sent_path(email: email) + expect(controller).to redirect_to(root_path) - # do not know why, should be test related expect(controller.current_user).to eq(user) - - expect(controller.current_gestionnaire).to be(nil) - expect(controller.current_administrateur).to be(nil) - expect(user.loged_in_with_france_connect).to be(nil) - expect(GestionnaireMailer).to have_received(:send_login_token) - end - - context 'and the user try to connect multiple times in a short period' do - before do - allow_any_instance_of(Gestionnaire).to receive(:young_login_token?).and_return(true) - allow(GestionnaireMailer).to receive(:send_login_token) - end - - it 'does not renew nor send a new login token' do - subject - - expect(GestionnaireMailer).not_to have_received(:send_login_token) - end + expect(controller.current_gestionnaire).to eq(gestionnaire) + expect(controller.current_administrateur).to eq(administrateur) + expect(user.loged_in_with_france_connect).to eq(nil) end end @@ -69,7 +53,6 @@ describe Users::SessionsController, type: :controller do expect(controller.current_gestionnaire).to eq(gestionnaire) expect(controller.current_administrateur).to eq(administrateur) expect(user.loged_in_with_france_connect).to be(nil) - expect(GestionnaireMailer).not_to have_received(:send_login_token) end end @@ -193,49 +176,56 @@ describe Users::SessionsController, type: :controller do context 'when the gestionnaire has non other account' do let(:gestionnaire) { create(:gestionnaire) } let!(:good_jeton) { gestionnaire.login_token! } + let(:logged) { false } before do + if logged + sign_in gestionnaire + end allow(controller).to receive(:trust_device) + allow(controller).to receive(:send_login_token_or_bufferize) post :sign_in_by_link, params: { id: gestionnaire.id, jeton: jeton } end - context 'when the token is valid' do - let(:jeton) { good_jeton } + context 'when the gestionnaire is not logged in' do + context 'when the token is valid' do + let(:jeton) { good_jeton } - # TODO when the gestionnaire has no other account, and the token is valid, and the user signing in was not starting a demarche, - # redirect to root_path, then redirect to gestionnaire_procedures_path (see root_controller) - it { is_expected.to redirect_to root_path } - it { expect(controller.current_gestionnaire).to eq(gestionnaire) } - it { expect(controller).to have_received(:trust_device) } + it { is_expected.to redirect_to new_user_session_path } + it { expect(controller.current_gestionnaire).to be_nil } + it { expect(controller).to have_received(:trust_device) } + end + + context 'when the token is invalid' do + let(:jeton) { 'invalid_token' } + + it { is_expected.to redirect_to link_sent_path(email: gestionnaire.email) } + it { expect(controller.current_gestionnaire).to be_nil } + it { expect(controller).not_to have_received(:trust_device) } + it { expect(controller).to have_received(:send_login_token_or_bufferize) } + end end - context 'when the token is invalid' do - let(:jeton) { 'invalid_token' } + context 'when the gestionnaire is logged in' do + let(:logged) { true } - it { is_expected.to redirect_to new_user_session_path } - it { expect(controller.current_gestionnaire).to be_nil } - it { expect(controller).not_to have_received(:trust_device) } - end - end + context 'when the token is valid' do + let(:jeton) { good_jeton } - context 'when the gestionnaire has an user and admin account' do - let(:email) { 'unique@plop.com' } - let(:password) { 'un super mot de passe' } + # redirect to root_path, then redirect to gestionnaire_procedures_path (see root_controller) + it { is_expected.to redirect_to root_path } + it { expect(controller.current_gestionnaire).to eq(gestionnaire) } + it { expect(controller).to have_received(:trust_device) } + end - let!(:user) { create(:user, email: email, password: password) } - let!(:administrateur) { create(:administrateur, email: email, password: password) } - let(:gestionnaire) { administrateur.gestionnaire } + context 'when the token is invalid' do + let(:jeton) { 'invalid_token' } - before do - post :sign_in_by_link, params: { id: gestionnaire.id, jeton: jeton } - end - - context 'when the token is valid' do - let(:jeton) { gestionnaire.login_token! } - - it { expect(controller.current_gestionnaire).to eq(gestionnaire) } - it { expect(controller.current_administrateur).to eq(administrateur) } - it { expect(controller.current_user).to eq(user) } + it { is_expected.to redirect_to link_sent_path(email: gestionnaire.email) } + it { expect(controller.current_gestionnaire).to eq(gestionnaire) } + it { expect(controller).not_to have_received(:trust_device) } + it { expect(controller).to have_received(:send_login_token_or_bufferize) } + end end end end @@ -250,7 +240,7 @@ describe Users::SessionsController, type: :controller do context 'when the cookie is outdated' do before do Timecop.freeze(Time.zone.now - TrustedDeviceConcern::TRUSTED_DEVICE_PERIOD - 1.minute) - controller.trust_device + controller.trust_device(Time.zone.now) Timecop.return end @@ -258,7 +248,7 @@ describe Users::SessionsController, type: :controller do end context 'when the cookie is ok' do - before { controller.trust_device } + before { controller.trust_device(Time.zone.now) } it { is_expected.to be true } end From b9b83cca3a7f9153b115e47232144614b3c8c055 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 1 Feb 2019 18:11:55 +0100 Subject: [PATCH 15/21] use multiple trusted_device_token --- app/models/gestionnaire.rb | 21 +++++++++---------- app/models/trusted_device_token.rb | 4 ++++ ...0201164951_create_trusted_device_tokens.rb | 11 ++++++++++ db/schema.rb | 10 +++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 app/models/trusted_device_token.rb create mode 100644 db/migrate/20190201164951_create_trusted_device_tokens.rb diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index 7ac39c9e5..df3284403 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -1,7 +1,6 @@ class Gestionnaire < ApplicationRecord include CredentialsSyncableConcern include EmailSanitizableConcern - include ActiveRecord::SecureToken LOGIN_TOKEN_VALIDITY = 45.minutes LOGIN_TOKEN_YOUTH = 15.minutes @@ -20,6 +19,7 @@ class Gestionnaire < ApplicationRecord has_many :followed_dossiers, through: :follows, source: :dossier has_many :avis has_many :dossiers_from_avis, through: :avis, source: :dossier + has_many :trusted_device_tokens def visible_procedures procedures.merge(Procedure.avec_lien.or(Procedure.archivees)) @@ -136,17 +136,15 @@ class Gestionnaire < ApplicationRecord end def login_token! - login_token = Gestionnaire.generate_unique_secure_token - encrypted_login_token = BCrypt::Password.create(login_token) - update(encrypted_login_token: encrypted_login_token, login_token_created_at: Time.zone.now) - login_token + trusted_device_token = trusted_device_tokens.create + trusted_device_token.token end def login_token_valid?(login_token) - BCrypt::Password.new(encrypted_login_token) == login_token && - LOGIN_TOKEN_VALIDITY.ago < login_token_created_at - rescue BCrypt::Errors::InvalidHash - false + trusted_device_token = trusted_device_tokens.find_by(token: login_token) + + trusted_device_token.present? && + LOGIN_TOKEN_VALIDITY.ago < trusted_device_token.created_at end def dossiers_id_with_notifications(dossiers) @@ -213,8 +211,9 @@ class Gestionnaire < ApplicationRecord end def young_login_token? - login_token_created_at.present? && - LOGIN_TOKEN_YOUTH.ago < login_token_created_at + trusted_device_token = trusted_device_tokens.order(created_at: :desc).first + trusted_device_token.present? && + LOGIN_TOKEN_YOUTH.ago < trusted_device_token.created_at end private diff --git a/app/models/trusted_device_token.rb b/app/models/trusted_device_token.rb new file mode 100644 index 000000000..848a9c689 --- /dev/null +++ b/app/models/trusted_device_token.rb @@ -0,0 +1,4 @@ +class TrustedDeviceToken < ApplicationRecord + belongs_to :gestionnaire + has_secure_token +end diff --git a/db/migrate/20190201164951_create_trusted_device_tokens.rb b/db/migrate/20190201164951_create_trusted_device_tokens.rb new file mode 100644 index 000000000..c53bec685 --- /dev/null +++ b/db/migrate/20190201164951_create_trusted_device_tokens.rb @@ -0,0 +1,11 @@ +class CreateTrustedDeviceTokens < ActiveRecord::Migration[5.2] + def change + create_table :trusted_device_tokens do |t| + t.string :token, null: false + t.references :gestionnaire, foreign_key: true + + t.timestamps + end + add_index :trusted_device_tokens, :token, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 74265b893..05ee04c74 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -520,6 +520,15 @@ ActiveRecord::Schema.define(version: 2019_02_13_144145) do t.string "version", null: false end + create_table "trusted_device_tokens", force: :cascade do |t| + t.string "token", null: false + t.bigint "gestionnaire_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["gestionnaire_id"], name: "index_trusted_device_tokens_on_gestionnaire_id" + t.index ["token"], name: "index_trusted_device_tokens_on_token", unique: true + end + create_table "types_de_champ", id: :serial, force: :cascade do |t| t.string "libelle" t.string "type_champ" @@ -611,6 +620,7 @@ ActiveRecord::Schema.define(version: 2019_02_13_144145) do add_foreign_key "received_mails", "procedures" add_foreign_key "refused_mails", "procedures" add_foreign_key "services", "administrateurs" + add_foreign_key "trusted_device_tokens", "gestionnaires" add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id" add_foreign_key "without_continuation_mails", "procedures" end From 23db8a160c41c94b443e3f8a934c891829e08416 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Sat, 2 Feb 2019 22:16:11 +0100 Subject: [PATCH 16/21] move token validity to trusted_device_token --- app/controllers/users/sessions_controller.rb | 6 +++++- app/models/gestionnaire.rb | 8 -------- app/models/trusted_device_token.rb | 6 ++++++ spec/models/gestionnaire_spec.rb | 20 -------------------- spec/models/trusted_device_token_spec.rb | 15 +++++++++++++++ 5 files changed, 26 insertions(+), 29 deletions(-) create mode 100644 spec/models/trusted_device_token_spec.rb diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index b02263499..bd7dcd879 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -70,7 +70,11 @@ class Users::SessionsController < Sessions::SessionsController def sign_in_by_link gestionnaire = Gestionnaire.find(params[:id]) - if gestionnaire&.login_token_valid?(params[:jeton]) + trusted_device_token = gestionnaire + .trusted_device_tokens + .find_by(token: params[:jeton]) + + if trusted_device_token&.token_valid? trust_device flash.notice = "Merci d’avoir confirmé votre connexion. Votre navigateur est maintenant authentifié pour #{TRUSTED_DEVICE_PERIOD.to_i / ActiveSupport::Duration::SECONDS_PER_DAY} jours." diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index df3284403..7b35b5f92 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -2,7 +2,6 @@ class Gestionnaire < ApplicationRecord include CredentialsSyncableConcern include EmailSanitizableConcern - LOGIN_TOKEN_VALIDITY = 45.minutes LOGIN_TOKEN_YOUTH = 15.minutes devise :database_authenticatable, :registerable, :async, @@ -140,13 +139,6 @@ class Gestionnaire < ApplicationRecord trusted_device_token.token end - def login_token_valid?(login_token) - trusted_device_token = trusted_device_tokens.find_by(token: login_token) - - trusted_device_token.present? && - LOGIN_TOKEN_VALIDITY.ago < trusted_device_token.created_at - end - def dossiers_id_with_notifications(dossiers) dossiers = dossiers.followed_by(self) diff --git a/app/models/trusted_device_token.rb b/app/models/trusted_device_token.rb index 848a9c689..01071ead9 100644 --- a/app/models/trusted_device_token.rb +++ b/app/models/trusted_device_token.rb @@ -1,4 +1,10 @@ class TrustedDeviceToken < ApplicationRecord + LOGIN_TOKEN_VALIDITY = 45.minutes + belongs_to :gestionnaire has_secure_token + + def token_valid? + LOGIN_TOKEN_VALIDITY.ago < created_at + end end diff --git a/spec/models/gestionnaire_spec.rb b/spec/models/gestionnaire_spec.rb index 344ee1683..493d4a3a1 100644 --- a/spec/models/gestionnaire_spec.rb +++ b/spec/models/gestionnaire_spec.rb @@ -392,26 +392,6 @@ describe Gestionnaire, type: :model do end end - describe '#login_token_valid?' do - let!(:gestionnaire) { create(:gestionnaire) } - let!(:good_token) { gestionnaire.login_token! } - - it { expect(gestionnaire.login_token_valid?(good_token)).to be true } - it { expect(gestionnaire.login_token_valid?('bad_token')).to be false } - - context 'when the token as expired' do - before { gestionnaire.update(login_token_created_at: (Gestionnaire::LOGIN_TOKEN_VALIDITY + 1.minute).ago) } - - it { expect(gestionnaire.login_token_valid?(good_token)).to be false } - end - - context 'when the gestionnaire does not have a token' do - before { gestionnaire.update(encrypted_login_token: nil) } - - it { expect(gestionnaire.login_token_valid?(nil)).to be false } - end - end - describe '#young_login_token?' do let!(:gestionnaire) { create(:gestionnaire) } diff --git a/spec/models/trusted_device_token_spec.rb b/spec/models/trusted_device_token_spec.rb new file mode 100644 index 000000000..4557bf439 --- /dev/null +++ b/spec/models/trusted_device_token_spec.rb @@ -0,0 +1,15 @@ +RSpec.describe TrustedDeviceToken, type: :model do + describe '#token_valid?' do + let(:token) { TrustedDeviceToken.create } + + context 'when the token is create after login_token_validity' do + it { expect(token.token_valid?).to be true } + end + + context 'when the token is create before login_token_validity' do + before { token.update(created_at: (TrustedDeviceToken::LOGIN_TOKEN_VALIDITY + 1.minute).ago) } + + it { expect(token.token_valid?).to be false } + end + end +end From d664f130fd60aa178480465a3f78c8ecbfc2e75b Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 4 Feb 2019 11:04:55 +0100 Subject: [PATCH 17/21] trustedDeviceToken: move token youth --- app/models/gestionnaire.rb | 5 +---- app/models/trusted_device_token.rb | 5 +++++ spec/models/gestionnaire_spec.rb | 4 ++-- spec/models/trusted_device_token_spec.rb | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index 7b35b5f92..9a038586d 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -2,8 +2,6 @@ class Gestionnaire < ApplicationRecord include CredentialsSyncableConcern include EmailSanitizableConcern - LOGIN_TOKEN_YOUTH = 15.minutes - devise :database_authenticatable, :registerable, :async, :recoverable, :rememberable, :trackable, :validatable @@ -204,8 +202,7 @@ class Gestionnaire < ApplicationRecord def young_login_token? trusted_device_token = trusted_device_tokens.order(created_at: :desc).first - trusted_device_token.present? && - LOGIN_TOKEN_YOUTH.ago < trusted_device_token.created_at + trusted_device_token&.token_young? end private diff --git a/app/models/trusted_device_token.rb b/app/models/trusted_device_token.rb index 01071ead9..4b485f832 100644 --- a/app/models/trusted_device_token.rb +++ b/app/models/trusted_device_token.rb @@ -1,5 +1,6 @@ class TrustedDeviceToken < ApplicationRecord LOGIN_TOKEN_VALIDITY = 45.minutes + LOGIN_TOKEN_YOUTH = 15.minutes belongs_to :gestionnaire has_secure_token @@ -7,4 +8,8 @@ class TrustedDeviceToken < ApplicationRecord def token_valid? LOGIN_TOKEN_VALIDITY.ago < created_at end + + def token_young? + LOGIN_TOKEN_YOUTH.ago < created_at + end end diff --git a/spec/models/gestionnaire_spec.rb b/spec/models/gestionnaire_spec.rb index 493d4a3a1..5385e246c 100644 --- a/spec/models/gestionnaire_spec.rb +++ b/spec/models/gestionnaire_spec.rb @@ -403,13 +403,13 @@ describe Gestionnaire, type: :model do end context 'when the token is a bit old' do - before { gestionnaire.update(login_token_created_at: (Gestionnaire::LOGIN_TOKEN_YOUTH + 1.minute).ago) } + before { gestionnaire.trusted_device_tokens.first.update(created_at: (TrustedDeviceToken::LOGIN_TOKEN_YOUTH + 1.minute).ago) } it { expect(gestionnaire.young_login_token?).to be false } end end context 'when there are no token' do - it { expect(gestionnaire.young_login_token?).to be false } + it { expect(gestionnaire.young_login_token?).to be_falsey } end end diff --git a/spec/models/trusted_device_token_spec.rb b/spec/models/trusted_device_token_spec.rb index 4557bf439..ed5ae1e51 100644 --- a/spec/models/trusted_device_token_spec.rb +++ b/spec/models/trusted_device_token_spec.rb @@ -12,4 +12,18 @@ RSpec.describe TrustedDeviceToken, type: :model do it { expect(token.token_valid?).to be false } end end + + describe '#token_young?' do + let(:token) { TrustedDeviceToken.create } + + context 'when the token is create after login_token_youth' do + it { expect(token.token_young?).to be true } + end + + context 'when the token is create before login_token_youth' do + before { token.update(created_at: (TrustedDeviceToken::LOGIN_TOKEN_YOUTH + 1.minute).ago) } + + it { expect(token.token_young?).to be false } + end + end end From 7de3a18fd1c1e25d2212b34a37076c72f06b1e9e Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 4 Feb 2019 11:57:50 +0100 Subject: [PATCH 18/21] valid period depend on trusted_device_token.created_at --- app/controllers/users/sessions_controller.rb | 7 +++++-- app/models/concerns/trusted_device_concern.rb | 6 +++--- spec/controllers/users/sessions_controller_spec.rb | 5 ++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index bd7dcd879..783043bcf 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -75,8 +75,11 @@ class Users::SessionsController < Sessions::SessionsController .find_by(token: params[:jeton]) if trusted_device_token&.token_valid? - trust_device - flash.notice = "Merci d’avoir confirmé votre connexion. Votre navigateur est maintenant authentifié pour #{TRUSTED_DEVICE_PERIOD.to_i / ActiveSupport::Duration::SECONDS_PER_DAY} jours." + trust_device(trusted_device_token.created_at) + + period = ((trusted_device_token.created_at + TRUSTED_DEVICE_PERIOD) - Time.zone.now).to_i / ActiveSupport::Duration::SECONDS_PER_DAY + + flash.notice = "Merci d’avoir confirmé votre connexion. Votre navigateur est maintenant authentifié pour #{period} jours." # redirect to procedure'url if stored by store_location_for(:user) in dossiers_controller # redirect to root_path otherwise diff --git a/app/models/concerns/trusted_device_concern.rb b/app/models/concerns/trusted_device_concern.rb index 258fc15fe..800c09353 100644 --- a/app/models/concerns/trusted_device_concern.rb +++ b/app/models/concerns/trusted_device_concern.rb @@ -4,10 +4,10 @@ module TrustedDeviceConcern TRUSTED_DEVICE_COOKIE_NAME = :trusted_device TRUSTED_DEVICE_PERIOD = 1.month - def trust_device + def trust_device(start_at) cookies.encrypted[TRUSTED_DEVICE_COOKIE_NAME] = { - value: JSON.generate({ created_at: Time.zone.now }), - expires: TRUSTED_DEVICE_PERIOD, + value: JSON.generate({ created_at: start_at }), + expires: start_at + TRUSTED_DEVICE_PERIOD, httponly: true } end diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index cdab44c91..c84415207 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -239,9 +239,8 @@ describe Users::SessionsController, type: :controller do context 'when the cookie is outdated' do before do - Timecop.freeze(Time.zone.now - TrustedDeviceConcern::TRUSTED_DEVICE_PERIOD - 1.minute) - controller.trust_device(Time.zone.now) - Timecop.return + emission_date = Time.zone.now - TrustedDeviceConcern::TRUSTED_DEVICE_PERIOD - 1.minute + controller.trust_device(emission_date) end it { is_expected.to be false } From c16e30442aaed2dc06b54b8fbcae1c9c7ec51c9d Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Feb 2019 20:51:04 +0100 Subject: [PATCH 19/21] save path before redirect to link_sent_path --- app/controllers/application_controller.rb | 4 ++++ spec/controllers/application_controller_spec.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 175cb05a9..4bcaa901a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -161,6 +161,10 @@ class ApplicationController < ActionController::Base current_gestionnaire.feature_enabled?(:enable_email_login_token) && !trusted_device? + # return at this location + # after the device is trusted + store_location_for(:user, request.fullpath) + send_login_token_or_bufferize(current_gestionnaire) redirect_to link_sent_path(email: current_gestionnaire.email) end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index cd8dbf75d..ce5777b40 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -159,6 +159,7 @@ describe ApplicationController, type: :controller do allow(@controller).to receive(:gestionnaire_signed_in?).and_return(gestionnaire_signed_in) allow(@controller).to receive(:sensitive_path).and_return(sensitive_path) allow(@controller).to receive(:send_login_token_or_bufferize) + allow(@controller).to receive(:store_location_for) end subject { @controller.send(:redirect_if_untrusted) } @@ -191,6 +192,7 @@ describe ApplicationController, type: :controller do 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 end From 47e3b57e81813496bddd58c3bcc926577713afd3 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 12 Feb 2019 17:35:19 +0100 Subject: [PATCH 20/21] TrustedDeviceToken: valid for one week --- app/models/trusted_device_token.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/trusted_device_token.rb b/app/models/trusted_device_token.rb index 4b485f832..951ccbe26 100644 --- a/app/models/trusted_device_token.rb +++ b/app/models/trusted_device_token.rb @@ -1,5 +1,5 @@ class TrustedDeviceToken < ApplicationRecord - LOGIN_TOKEN_VALIDITY = 45.minutes + LOGIN_TOKEN_VALIDITY = 1.week LOGIN_TOKEN_YOUTH = 15.minutes belongs_to :gestionnaire From 0b8619be77c017a694758a063c5be09e1b55e6a8 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 18 Feb 2019 17:05:30 +0100 Subject: [PATCH 21/21] Gestionnaire: login_token! -> create_trusted_device_token --- app/models/concerns/trusted_device_concern.rb | 2 +- app/models/gestionnaire.rb | 2 +- spec/controllers/users/sessions_controller_spec.rb | 2 +- spec/models/gestionnaire_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/trusted_device_concern.rb b/app/models/concerns/trusted_device_concern.rb index 800c09353..f7dfb8e92 100644 --- a/app/models/concerns/trusted_device_concern.rb +++ b/app/models/concerns/trusted_device_concern.rb @@ -19,7 +19,7 @@ module TrustedDeviceConcern def send_login_token_or_bufferize(gestionnaire) if !gestionnaire.young_login_token? - login_token = gestionnaire.login_token! + login_token = gestionnaire.create_trusted_device_token GestionnaireMailer.send_login_token(gestionnaire, login_token).deliver_later end end diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index 9a038586d..82516d4de 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -132,7 +132,7 @@ class Gestionnaire < ApplicationRecord Dossier.where(id: dossiers_id_with_notifications(dossiers)).group(:procedure_id).count end - def login_token! + def create_trusted_device_token trusted_device_token = trusted_device_tokens.create trusted_device_token.token end diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index c84415207..e644fa74a 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -175,7 +175,7 @@ describe Users::SessionsController, type: :controller do describe '#sign_in_by_link' do context 'when the gestionnaire has non other account' do let(:gestionnaire) { create(:gestionnaire) } - let!(:good_jeton) { gestionnaire.login_token! } + let!(:good_jeton) { gestionnaire.create_trusted_device_token } let(:logged) { false } before do diff --git a/spec/models/gestionnaire_spec.rb b/spec/models/gestionnaire_spec.rb index 5385e246c..2a28e5f6e 100644 --- a/spec/models/gestionnaire_spec.rb +++ b/spec/models/gestionnaire_spec.rb @@ -396,7 +396,7 @@ describe Gestionnaire, type: :model do let!(:gestionnaire) { create(:gestionnaire) } context 'when there is a token' do - let!(:good_token) { gestionnaire.login_token! } + let!(:good_token) { gestionnaire.create_trusted_device_token } context 'when the token has just been created' do it { expect(gestionnaire.young_login_token?).to be true }