Merge pull request #10865 from demarches-simplifiees/fix-10799
ETQ Admin / Instructeur je veux être savoir si le jeton api entreprise d'une démarche a expiré ou va expirer prochainement
This commit is contained in:
commit
097074fdc7
25 changed files with 340 additions and 50 deletions
|
@ -1,3 +1,3 @@
|
||||||
---
|
---
|
||||||
fr:
|
fr:
|
||||||
title: Jeton Entreprise
|
title: Jeton API Entreprise
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
.fr-col-6.fr-col-md-4.fr-col-lg-3
|
.fr-col-6.fr-col-md-4.fr-col-lg-3
|
||||||
= link_to jeton_admin_procedure_path(@procedure), class: 'fr-tile fr-enlarge-link' do
|
= link_to jeton_admin_procedure_path(@procedure), class: 'fr-tile fr-enlarge-link' do
|
||||||
.fr-tile__body.flex.column.align-center.justify-between
|
.fr-tile__body.flex.column.align-center.justify-between
|
||||||
- if @procedure.api_entreprise_token.present?
|
- if @procedure.has_api_entreprise_token?
|
||||||
%p.fr-badge.fr-badge--success Validé
|
- if @procedure.api_entreprise_token_expired_or_expires_soon?
|
||||||
|
%p.fr-badge.fr-badge--error À renouveler
|
||||||
|
- else
|
||||||
|
%p.fr-badge.fr-badge--success Validé
|
||||||
- else
|
- else
|
||||||
%p.fr-badge.fr-badge--info À configurer
|
%p.fr-badge.fr-badge--info À configurer
|
||||||
%div
|
%div
|
||||||
%h3.fr-h6.fr-mt-10v= t('.title')
|
%h3.fr-h6.fr-mt-10v= t('.title')
|
||||||
%p.fr-tile-subtitle Configurer le jeton API entreprise
|
%p.fr-tile-subtitle Configurer le jeton API Entreprise
|
||||||
%p.fr-btn.fr-btn--tertiary= t('views.shared.actions.edit')
|
%p.fr-btn.fr-btn--tertiary= t('views.shared.actions.edit')
|
||||||
|
|
|
@ -17,6 +17,10 @@ class APIEntrepriseToken
|
||||||
decoded_token.key?("exp") && decoded_token["exp"] <= Time.zone.now.to_i
|
decoded_token.key?("exp") && decoded_token["exp"] <= Time.zone.now.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def expiration
|
||||||
|
decoded_token.key?("exp") && Time.zone.at(decoded_token["exp"])
|
||||||
|
end
|
||||||
|
|
||||||
def role?(role)
|
def role?(role)
|
||||||
roles.include?(role)
|
roles.include?(role)
|
||||||
end
|
end
|
||||||
|
|
33
app/models/concerns/api_entreprise_token_concern.rb
Normal file
33
app/models/concerns/api_entreprise_token_concern.rb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module APIEntrepriseTokenConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
SOON_TO_EXPIRE_DELAY = 1.month
|
||||||
|
|
||||||
|
included do
|
||||||
|
validates :api_entreprise_token, jwt_token: true, allow_blank: true
|
||||||
|
|
||||||
|
before_save :set_api_entreprise_token_expires_at, if: :will_save_change_to_api_entreprise_token?
|
||||||
|
|
||||||
|
def api_entreprise_role?(role)
|
||||||
|
APIEntrepriseToken.new(api_entreprise_token).role?(role)
|
||||||
|
end
|
||||||
|
|
||||||
|
def api_entreprise_token
|
||||||
|
self[:api_entreprise_token].presence || Rails.application.secrets.api_entreprise[:key]
|
||||||
|
end
|
||||||
|
|
||||||
|
def api_entreprise_token_expired_or_expires_soon?
|
||||||
|
api_entreprise_token_expires_at && api_entreprise_token_expires_at <= SOON_TO_EXPIRE_DELAY.from_now
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_api_entreprise_token?
|
||||||
|
self[:api_entreprise_token].present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_api_entreprise_token_expires_at
|
||||||
|
self.api_entreprise_token_expires_at = has_api_entreprise_token? ? APIEntrepriseToken.new(api_entreprise_token).expiration : nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Procedure < ApplicationRecord
|
class Procedure < ApplicationRecord
|
||||||
|
include APIEntrepriseTokenConcern
|
||||||
include ProcedureStatsConcern
|
include ProcedureStatsConcern
|
||||||
include EncryptableConcern
|
include EncryptableConcern
|
||||||
include InitiationProcedureConcern
|
include InitiationProcedureConcern
|
||||||
|
@ -284,7 +285,6 @@ class Procedure < ApplicationRecord
|
||||||
size: { less_than: LOGO_MAX_SIZE },
|
size: { less_than: LOGO_MAX_SIZE },
|
||||||
if: -> { new_record? || created_at > Date.new(2020, 11, 13) }
|
if: -> { new_record? || created_at > Date.new(2020, 11, 13) }
|
||||||
|
|
||||||
validates :api_entreprise_token, jwt_token: true, allow_blank: true
|
|
||||||
validates :api_particulier_token, format: { with: /\A[A-Za-z0-9\-_=.]{15,}\z/ }, allow_blank: true
|
validates :api_particulier_token, format: { with: /\A[A-Za-z0-9\-_=.]{15,}\z/ }, allow_blank: true
|
||||||
validate :validate_auto_archive_on_in_the_future, if: :will_save_change_to_auto_archive_on?
|
validate :validate_auto_archive_on_in_the_future, if: :will_save_change_to_auto_archive_on?
|
||||||
|
|
||||||
|
@ -762,18 +762,6 @@ class Procedure < ApplicationRecord
|
||||||
"Procedure;#{id}"
|
"Procedure;#{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_entreprise_role?(role)
|
|
||||||
APIEntrepriseToken.new(api_entreprise_token).role?(role)
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_entreprise_token
|
|
||||||
self[:api_entreprise_token].presence || Rails.application.secrets.api_entreprise[:key]
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_entreprise_token_expired?
|
|
||||||
APIEntrepriseToken.new(api_entreprise_token).expired?
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_new_revision(revision = nil)
|
def create_new_revision(revision = nil)
|
||||||
transaction do
|
transaction do
|
||||||
new_revision = (revision || draft_revision)
|
new_revision = (revision || draft_revision)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Maintenance
|
||||||
|
class UpdateAPIEntrepriseTokenExpiresAtTask < MaintenanceTasks::Task
|
||||||
|
def collection
|
||||||
|
Procedure.with_discarded.where.not(api_entreprise_token: nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def process(procedure)
|
||||||
|
procedure.set_api_entreprise_token_expires_at
|
||||||
|
procedure.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -28,6 +28,11 @@
|
||||||
- elsif @procedure.locked?
|
- elsif @procedure.locked?
|
||||||
= link_to commencer_url(@procedure.path), commencer_url(@procedure.path), class: "fr-link"
|
= link_to commencer_url(@procedure.path), commencer_url(@procedure.path), class: "fr-link"
|
||||||
.flex.fr-mt-1w
|
.flex.fr-mt-1w
|
||||||
|
|
||||||
|
- if @procedure.api_entreprise_token_expired_or_expires_soon?
|
||||||
|
%span.fr-badge.fr-badge--error.fr-mr-1w
|
||||||
|
= t('to_modify', scope: [:layouts, :breadcrumb])
|
||||||
|
|
||||||
%span.fr-badge.fr-badge--success.fr-mr-1w
|
%span.fr-badge.fr-badge--success.fr-mr-1w
|
||||||
= t('published', scope: [:layouts, :breadcrumb])
|
= t('published', scope: [:layouts, :breadcrumb])
|
||||||
= t('since', scope: [:layouts, :breadcrumb], number: @procedure.id, date: l(@procedure.published_at.to_date))
|
= t('since', scope: [:layouts, :breadcrumb], number: @procedure.id, date: l(@procedure.published_at.to_date))
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
- if procedure.api_entreprise_token_expires_at.present?
|
||||||
|
- if procedure.api_entreprise_token_expires_at < Time.zone.now
|
||||||
|
= render Dsfr::AlertComponent.new(state: :error, size: :sm, extra_class_names: 'fr-mb-2w') do |c|
|
||||||
|
- c.with_body do
|
||||||
|
%p
|
||||||
|
Votre jeton API Entreprise est expiré.
|
||||||
|
Merci de le renouveler.
|
||||||
|
- elsif procedure.api_entreprise_token_expired_or_expires_soon?
|
||||||
|
= render Dsfr::AlertComponent.new(state: :warning, size: :sm, extra_class_names: 'fr-mb-2w') do |c|
|
||||||
|
- c.with_body do
|
||||||
|
%p
|
||||||
|
Votre jeton API Entreprise expirera le
|
||||||
|
= procedure.api_entreprise_token_expires_at.strftime('%d/%m/%Y à %H:%M.')
|
||||||
|
Merci de le renouveler avant cette date.
|
|
@ -54,11 +54,15 @@
|
||||||
|
|
||||||
.text-right
|
.text-right
|
||||||
%p.fr-mb-0.width-max-content N° #{number_with_html_delimiter(procedure.id)}
|
%p.fr-mb-0.width-max-content N° #{number_with_html_delimiter(procedure.id)}
|
||||||
|
|
||||||
- if procedure.close? || procedure.depubliee?
|
- if procedure.close? || procedure.depubliee?
|
||||||
%span.fr-badge.fr-badge--sm.fr-badge--warning
|
%span.fr-badge.fr-badge--sm.fr-badge--warning
|
||||||
= t('closed', scope: [:layouts, :breadcrumb])
|
= t('closed', scope: [:layouts, :breadcrumb])
|
||||||
|
|
||||||
- elsif procedure.publiee?
|
- elsif procedure.publiee?
|
||||||
|
- if procedure.api_entreprise_token_expired_or_expires_soon?
|
||||||
|
%span.fr-badge.fr-badge--sm.fr-badge--error
|
||||||
|
= t('to_modify', scope: [:layouts, :breadcrumb])
|
||||||
%span.fr-badge.fr-badge--sm.fr-badge--success
|
%span.fr-badge.fr-badge--sm.fr-badge--success
|
||||||
= t('published', scope: [:layouts, :breadcrumb])
|
= t('published', scope: [:layouts, :breadcrumb])
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
= render partial: 'administrateurs/breadcrumbs',
|
= render partial: 'administrateurs/breadcrumbs',
|
||||||
locals: { steps: [['Démarches', admin_procedures_back_path(@procedure)],
|
locals: { steps: [['Démarches', admin_procedures_back_path(@procedure)],
|
||||||
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
|
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
|
||||||
['Jeton Entreprise']] }
|
['Jeton API Entreprise']] }
|
||||||
|
|
||||||
.fr-container
|
.fr-container
|
||||||
%h1.fr-h2 Jeton Entreprise
|
%h1.fr-h2 Jeton API Entreprise
|
||||||
|
|
||||||
= form_with model: @procedure, url: url_for({ controller: 'administrateurs/procedures', action: :update_jeton }) do |f|
|
= form_with model: @procedure, url: url_for({ controller: 'administrateurs/procedures', action: :update_jeton }) do |f|
|
||||||
.fr-container
|
.fr-container
|
||||||
|
@ -14,12 +14,16 @@
|
||||||
Démarches Simplifiées utilise
|
Démarches Simplifiées utilise
|
||||||
= link_to 'API Entreprise', "https://entreprise.api.gouv.fr/"
|
= link_to 'API Entreprise', "https://entreprise.api.gouv.fr/"
|
||||||
qui permet de récupérer les informations administratives des entreprises et des associations.
|
qui permet de récupérer les informations administratives des entreprises et des associations.
|
||||||
Si votre démarche nécessite des autorisations spécifiques que Démarches Simplifiées n’a pas par défaut, merci de renseigner ici le jeton
|
Si votre démarche nécessite des autorisations spécifiques que Démarches Simplifiées n’a pas par défaut, merci de renseigner ci-dessous
|
||||||
= link_to 'API Entreprise', "https://api.gouv.fr/les-api/api-entreprise/demande-acces"
|
%strong le jeton API Entreprise
|
||||||
propre à votre démarche.
|
propre à votre démarche.
|
||||||
|
%p
|
||||||
|
Si besoin, vous pouvez demander une habilitation API Entreprise en cliquant sur le lien suivant :
|
||||||
|
= link_to "https://api.gouv.fr/les-api/api-entreprise/demande-acces.", "https://api.gouv.fr/les-api/api-entreprise/demande-acces"
|
||||||
|
|
||||||
.fr-input-group
|
|
||||||
= f.label :api_entreprise_token, "Jeton", class: 'fr-label'
|
= render partial: 'administrateurs/procedures/api_entreprise_token_expiration_alert', locals: { procedure: @procedure }
|
||||||
= f.password_field :api_entreprise_token, value: @procedure.read_attribute(:api_entreprise_token), class: 'fr-input'
|
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :api_entreprise_token, input_type: :password_field, required: false, opts: { value: @procedure.read_attribute(:api_entreprise_token)})
|
||||||
|
|
||||||
= render Procedure::FixedFooterComponent.new(procedure: @procedure, form: f)
|
= render Procedure::FixedFooterComponent.new(procedure: @procedure, form: f)
|
||||||
|
|
|
@ -27,6 +27,15 @@
|
||||||
= link_to 'Clore', admin_procedure_close_path(procedure_id: @procedure.id), class: 'fr-btn fr-btn--tertiary fr-btn--icon-left fr-icon-calendar-close-fill', id: "close-procedure-link"
|
= link_to 'Clore', admin_procedure_close_path(procedure_id: @procedure.id), class: 'fr-btn fr-btn--tertiary fr-btn--icon-left fr-icon-calendar-close-fill', id: "close-procedure-link"
|
||||||
|
|
||||||
.fr-container
|
.fr-container
|
||||||
|
- if @procedure.api_entreprise_token_expired_or_expires_soon?
|
||||||
|
= render Dsfr::AlertComponent.new(state: :error, title: t(:technical_issues, scope: [:administrateurs, :procedures]), extra_class_names: 'fr-mb-2w') do |c|
|
||||||
|
- c.with_body do
|
||||||
|
%ul.fr-mb-0
|
||||||
|
%li
|
||||||
|
Le
|
||||||
|
= link_to "Jeton API Entreprise", jeton_admin_procedure_path(@procedure), class: 'error-anchor'
|
||||||
|
est expiré ou va expirer prochainement
|
||||||
|
|
||||||
- if @procedure.draft_changed?
|
- if @procedure.draft_changed?
|
||||||
= render Dsfr::CalloutComponent.new(title: t(:has_changes, scope: [:administrateurs, :revision_changes]), icon: "fr-fi-information-line") do |c|
|
= render Dsfr::CalloutComponent.new(title: t(:has_changes, scope: [:administrateurs, :revision_changes]), icon: "fr-fi-information-line") do |c|
|
||||||
- c.with_body do
|
- c.with_body do
|
||||||
|
|
|
@ -7,6 +7,7 @@ en:
|
||||||
attributes:
|
attributes:
|
||||||
procedure:
|
procedure:
|
||||||
hints:
|
hints:
|
||||||
|
api_entreprise_token: 'For example: eyJhbGciOiJIUzI1NiJ9.eyJ1...'
|
||||||
description: Describe in a few lines the context, the aim etc.
|
description: Describe in a few lines the context, the aim etc.
|
||||||
description_target_audience: Describe in a few lines the final recipients of the process, the eligibility criteria if there are any, the prerequisites, etc.
|
description_target_audience: Describe in a few lines the final recipients of the process, the eligibility criteria if there are any, the prerequisites, etc.
|
||||||
description_pj: Describe the required attachments list if there is any
|
description_pj: Describe the required attachments list if there is any
|
||||||
|
@ -48,6 +49,7 @@ en:
|
||||||
personne_morale: 'Legal entity'
|
personne_morale: 'Legal entity'
|
||||||
declarative_with_state/en_instruction: Instruction
|
declarative_with_state/en_instruction: Instruction
|
||||||
declarative_with_state/accepte: Accepted
|
declarative_with_state/accepte: Accepted
|
||||||
|
api_entreprise_token: Token API Entreprise
|
||||||
api_particulier_token: Token API Particulier
|
api_particulier_token: Token API Particulier
|
||||||
initiated_mail: File sorted for processing notification email
|
initiated_mail: File sorted for processing notification email
|
||||||
received_mail: File submitted notification email
|
received_mail: File submitted notification email
|
||||||
|
|
|
@ -7,6 +7,7 @@ fr:
|
||||||
attributes:
|
attributes:
|
||||||
procedure:
|
procedure:
|
||||||
hints:
|
hints:
|
||||||
|
api_entreprise_token: 'Exemple : eyJhbGciOiJIUzI1NiJ9.eyJ1...'
|
||||||
description: Décrivez en quelques lignes le contexte, la finalité, etc.
|
description: Décrivez en quelques lignes le contexte, la finalité, etc.
|
||||||
description_target_audience: Décrivez en quelques lignes les destinataires finaux de la démarche, les conditions d’éligibilité s’il y en a, les pré-requis, etc.
|
description_target_audience: Décrivez en quelques lignes les destinataires finaux de la démarche, les conditions d’éligibilité s’il y en a, les pré-requis, etc.
|
||||||
description_pj: Décrivez la liste des pièces jointes à fournir s’il y en a
|
description_pj: Décrivez la liste des pièces jointes à fournir s’il y en a
|
||||||
|
@ -54,6 +55,7 @@ fr:
|
||||||
personne_morale: 'Personne morale'
|
personne_morale: 'Personne morale'
|
||||||
declarative_with_state/en_instruction: En instruction
|
declarative_with_state/en_instruction: En instruction
|
||||||
declarative_with_state/accepte: Accepté
|
declarative_with_state/accepte: Accepté
|
||||||
|
api_entreprise_token: Jeton API Entreprise
|
||||||
api_particulier_token: Jeton API Particulier
|
api_particulier_token: Jeton API Particulier
|
||||||
initiated_mail: L’email de notification de passage de dossier en instruction
|
initiated_mail: L’email de notification de passage de dossier en instruction
|
||||||
received_mail: L’email de notification de dépôt de dossier
|
received_mail: L’email de notification de dépôt de dossier
|
||||||
|
|
|
@ -67,6 +67,7 @@ en:
|
||||||
dpd_part_4: How to do ? You can either send him the link to the procedure on test stage by email, or name him "administrator". In any case, publish your approach only after having had his opinion.
|
dpd_part_4: How to do ? You can either send him the link to the procedure on test stage by email, or name him "administrator". In any case, publish your approach only after having had his opinion.
|
||||||
back_to_procedure: 'Cancel and return to the procedure page'
|
back_to_procedure: 'Cancel and return to the procedure page'
|
||||||
submit: Publish
|
submit: Publish
|
||||||
|
technical_issues: "Issues are affecting the proper functioning of the process"
|
||||||
check_path:
|
check_path:
|
||||||
path_not_available:
|
path_not_available:
|
||||||
owner: This URL is identical to another of your published procedures. If you publish this procedure, the old one will be unpublished and will no longer be accessible to the public.
|
owner: This URL is identical to another of your published procedures. If you publish this procedure, the old one will be unpublished and will no longer be accessible to the public.
|
||||||
|
|
|
@ -67,6 +67,7 @@ fr:
|
||||||
dpd_part_4: Comment faire ? Vous pouvez soit lui communiquer par email le lien vers la démarche en test, ou bien le nommer « administrateur ». Dans tous les cas, ne publiez votre démarche qu’après avoir eu son avis.
|
dpd_part_4: Comment faire ? Vous pouvez soit lui communiquer par email le lien vers la démarche en test, ou bien le nommer « administrateur ». Dans tous les cas, ne publiez votre démarche qu’après avoir eu son avis.
|
||||||
back_to_procedure: 'Annuler et revenir à la page de la démarche'
|
back_to_procedure: 'Annuler et revenir à la page de la démarche'
|
||||||
submit: Publier
|
submit: Publier
|
||||||
|
technical_issues: Des problèmes impactent le bon fonctionnement de la démarche
|
||||||
check_path:
|
check_path:
|
||||||
path_not_available:
|
path_not_available:
|
||||||
owner: Cette url est identique à celle d’une autre de vos démarches publiées. Si vous publiez cette démarche, l’ancienne sera dépubliée et ne sera plus accessible au public.
|
owner: Cette url est identique à celle d’une autre de vos démarches publiées. Si vous publiez cette démarche, l’ancienne sera dépubliée et ne sera plus accessible au public.
|
||||||
|
|
|
@ -11,6 +11,7 @@ en:
|
||||||
closed: "Closed"
|
closed: "Closed"
|
||||||
published: "Published"
|
published: "Published"
|
||||||
draft: "Draft"
|
draft: "Draft"
|
||||||
|
to_modify: "To modify"
|
||||||
more_info_on_test: "For more information on test stage"
|
more_info_on_test: "For more information on test stage"
|
||||||
go_to_FAQ: "read FAQ"
|
go_to_FAQ: "read FAQ"
|
||||||
url_FAQ: "/faq#accordion-administrateur-2"
|
url_FAQ: "/faq#accordion-administrateur-2"
|
||||||
|
|
|
@ -11,6 +11,7 @@ fr:
|
||||||
closed: "Close"
|
closed: "Close"
|
||||||
published: "Publiée"
|
published: "Publiée"
|
||||||
draft: "En test"
|
draft: "En test"
|
||||||
|
to_modify: "À modifier"
|
||||||
more_info_on_test: "Pour plus d’information sur la phase de test"
|
more_info_on_test: "Pour plus d’information sur la phase de test"
|
||||||
go_to_FAQ: "consulter la FAQ"
|
go_to_FAQ: "consulter la FAQ"
|
||||||
url_FAQ: "/faq#accordion-administrateur-2"
|
url_FAQ: "/faq#accordion-administrateur-2"
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddAPIEntrepriseTokenExpiresAtToProcedures < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :procedures, :api_entreprise_token_expires_at, :datetime, precision: nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -242,8 +242,8 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_29_141825) do
|
||||||
t.integer "dossier_count"
|
t.integer "dossier_count"
|
||||||
t.string "dossier_state"
|
t.string "dossier_state"
|
||||||
t.bigint "instructeur_id", null: false
|
t.bigint "instructeur_id", null: false
|
||||||
t.datetime "sent_at", precision: nil, null: false
|
|
||||||
t.bigint "procedure_id"
|
t.bigint "procedure_id"
|
||||||
|
t.datetime "sent_at", precision: nil, null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -933,6 +933,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_29_141825) do
|
||||||
t.boolean "allow_expert_messaging", default: true, null: false
|
t.boolean "allow_expert_messaging", default: true, null: false
|
||||||
t.boolean "allow_expert_review", default: true, null: false
|
t.boolean "allow_expert_review", default: true, null: false
|
||||||
t.string "api_entreprise_token"
|
t.string "api_entreprise_token"
|
||||||
|
t.datetime "api_entreprise_token_expires_at", precision: nil
|
||||||
t.text "api_particulier_scopes", default: [], array: true
|
t.text "api_particulier_scopes", default: [], array: true
|
||||||
t.jsonb "api_particulier_sources", default: {}
|
t.jsonb "api_particulier_sources", default: {}
|
||||||
t.boolean "ask_birthday", default: false, null: false
|
t.boolean "ask_birthday", default: false, null: false
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe Procedure::Card::APIEntrepriseComponent, type: :component do
|
||||||
|
subject { render_inline(described_class.new(procedure:)) }
|
||||||
|
|
||||||
|
let(:procedure) { create(:procedure, api_entreprise_token:) }
|
||||||
|
|
||||||
|
context "Token is not configured" do
|
||||||
|
let(:api_entreprise_token) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to have_css('p.fr-badge.fr-badge--info', text: "À configurer") }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "Token expires soon" do
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: 2.days.from_now.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it { is_expected.to have_css('p.fr-badge.fr-badge--error', text: "À renouveler") }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "Token expires in a long time" do
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: 2.months.from_now.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it { is_expected.to have_css('p.fr-badge.fr-badge--success', text: "Validé") }
|
||||||
|
end
|
||||||
|
end
|
|
@ -138,4 +138,34 @@ describe APIEntrepriseToken, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#expiration" do
|
||||||
|
subject { api_entreprise_token.expiration }
|
||||||
|
|
||||||
|
context "without token" do
|
||||||
|
let(:token) { nil }
|
||||||
|
|
||||||
|
it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a blank token" do
|
||||||
|
let(:token) { "" }
|
||||||
|
|
||||||
|
it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an invalid token" do
|
||||||
|
let(:token) { "NOT-A-VALID-TOKEN" }
|
||||||
|
|
||||||
|
it { expect { subject }.to raise_exception(APIEntrepriseToken::TokenError) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a valid token" do
|
||||||
|
let(:token) { "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiI2NjRkZWEyMS02YWFlLTQwZmYtYWM0Mi1kZmQ3ZGE4YjQ3NmUiLCJqdGkiOiJhcGktZW50cmVwcmlzZS1zdGFnaW5nIiwicm9sZXMiOlsiY2VydGlmaWNhdF9jbmV0cCIsInByb2J0cCIsImV0YWJsaXNzZW1lbnRzIiwicHJpdmlsZWdlcyIsInVwdGltZSIsImF0dGVzdGF0aW9uc19hZ2VmaXBoIiwiYWN0ZXNfaW5waSIsImJpbGFuc19pbnBpIiwiYWlkZXNfY292aWRfZWZmZWN0aWZzIiwiY2VydGlmaWNhdF9yZ2VfYWRlbWUiLCJhdHRlc3RhdGlvbnNfc29jaWFsZXMiLCJlbnRyZXByaXNlX2FydGlzYW5hbGUiLCJmbnRwX2NhcnRlX3BybyIsImNvbnZlbnRpb25zX2NvbGxlY3RpdmVzIiwiZXh0cmFpdHNfcmNzIiwiZXh0cmFpdF9jb3VydF9pbnBpIiwiY2VydGlmaWNhdF9hZ2VuY2VfYmlvIiwibXNhX2NvdGlzYXRpb25zIiwiZG9jdW1lbnRzX2Fzc29jaWF0aW9uIiwiZW9yaV9kb3VhbmVzIiwiYXNzb2NpYXRpb25zIiwiYmlsYW5zX2VudHJlcHJpc2VfYmRmIiwiZW50cmVwcmlzZXMiLCJxdWFsaWJhdCIsImNlcnRpZmljYXRfb3BxaWJpIiwiZW50cmVwcmlzZSIsImV0YWJsaXNzZW1lbnQiXSwic3ViIjoic3RhZ2luZyBkZXZlbG9wbWVudCIsImlhdCI6MTY0MTMwNDcxNCwidmVyc2lvbiI6IjEuMCIsImV4cCI6MTY4ODQ3NTUxNH0.xID66pIlMnBR5_6nG-GidFBzK4Tuuy5ZsWfkMEVB_Ek" }
|
||||||
|
|
||||||
|
it "returns the correct expiration time" do
|
||||||
|
expect(subject).to eq(Time.zone.at(1688475514))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
84
spec/models/concerns/api_entreprise_token_concern_spec.rb
Normal file
84
spec/models/concerns/api_entreprise_token_concern_spec.rb
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe APIEntrepriseTokenConcern do
|
||||||
|
describe "#api_entreprise_token_expired_or_expires_soon?" do
|
||||||
|
subject { procedure.api_entreprise_token_expired_or_expires_soon? }
|
||||||
|
|
||||||
|
let(:procedure) { create(:procedure, api_entreprise_token:) }
|
||||||
|
|
||||||
|
context "when there is no token" do
|
||||||
|
let(:api_entreprise_token) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_falsey }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the token expires in 2 months" do
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: 2.months.from_now.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it { is_expected.to be_falsey }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the token expires tomorrow" do
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: 1.day.from_now.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the token is expired" do
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: 1.day.ago.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#set_api_entreprise_token_expires_at (before_save)' do
|
||||||
|
let(:procedure) { create(:procedure, api_entreprise_token: initial_api_entreprise_token) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
procedure.api_entreprise_token = api_entreprise_token
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { procedure.save }
|
||||||
|
|
||||||
|
context "when procedure had no api_entreprise_token" do
|
||||||
|
let(:initial_api_entreprise_token) { nil }
|
||||||
|
|
||||||
|
context 'when the api_entreprise_token is nil' do
|
||||||
|
let(:api_entreprise_token) { nil }
|
||||||
|
|
||||||
|
it 'does not set the api_entreprise_token_expires_at' do
|
||||||
|
expect { subject }.not_to change { procedure.api_entreprise_token_expires_at }.from(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the api_entreprise_token is not valid' do
|
||||||
|
let(:api_entreprise_token) { "not a token" }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect { subject }.not_to change { procedure.api_entreprise_token_expires_at }.from(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the api_entreprise_token is valid' do
|
||||||
|
let(:expiration_date) { Time.zone.now.beginning_of_minute }
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: expiration_date.to_i }, nil, 'none') }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect { subject }.to change { procedure.api_entreprise_token_expires_at }.from(nil).to(expiration_date)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when procedure had an api_entreprise_token" do
|
||||||
|
let(:initial_api_entreprise_token) { JWT.encode({ exp: 2.months.from_now.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
context 'when the api_entreprise_token is set to nil' do
|
||||||
|
let(:api_entreprise_token) { nil }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect { subject }.to change { procedure.api_entreprise_token_expires_at }.to(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -621,31 +621,6 @@ describe Procedure do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'api_entreprise_token_expired?' do
|
|
||||||
let(:token) { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
|
|
||||||
let(:procedure) { create(:procedure, api_entreprise_token: token) }
|
|
||||||
let(:payload) {
|
|
||||||
[
|
|
||||||
{ "exp" => expiration_time }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
let(:subject) { procedure.api_entreprise_token_expired? }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(JWT).to receive(:decode).with(token, nil, false).and_return(payload)
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with token expired" do
|
|
||||||
let(:expiration_time) { (1.day.ago).to_i }
|
|
||||||
it { is_expected.to be_truthy }
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with token not expired" do
|
|
||||||
let(:expiration_time) { (1.day.from_now).to_i }
|
|
||||||
it { is_expected.to be_falsey }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'clone' do
|
describe 'clone' do
|
||||||
let(:service) { create(:service) }
|
let(:service) { create(:service) }
|
||||||
let(:procedure) do
|
let(:procedure) do
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Maintenance
|
||||||
|
RSpec.describe UpdateAPIEntrepriseTokenExpiresAtTask do
|
||||||
|
describe '#collection' do
|
||||||
|
subject(:collection) { described_class.collection }
|
||||||
|
|
||||||
|
let!(:procedures_with_token) { create_list(:procedure, 3, api_entreprise_token: JWT.encode({}, nil, 'none')) }
|
||||||
|
let!(:procedure_without_token) { create(:procedure, api_entreprise_token: nil) }
|
||||||
|
|
||||||
|
it 'returns procedures with api_entreprise_token present' do
|
||||||
|
expect(collection).to match_array(procedures_with_token)
|
||||||
|
|
||||||
|
expect(collection).not_to include(procedure_without_token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#process" do
|
||||||
|
subject(:process) { described_class.process(procedure) }
|
||||||
|
|
||||||
|
let(:expiration) { 1.month.from_now.beginning_of_minute }
|
||||||
|
let(:procedure) { create(:procedure) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
procedure.update_column(:api_entreprise_token, JWT.encode({ exp: expiration.to_i }, nil, "none"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect { process }.to change { procedure.reload.api_entreprise_token_expires_at }.from(nil).to(expiration)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,47 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe 'administrateurs/procedures/_api_entreprise_token_expiration_alert', type: :view do
|
||||||
|
let(:procedure) { create(:procedure, api_entreprise_token:) }
|
||||||
|
|
||||||
|
subject { render 'administrateurs/procedures/api_entreprise_token_expiration_alert', procedure: procedure }
|
||||||
|
|
||||||
|
context "when there is no token" do
|
||||||
|
let(:api_entreprise_token) { nil }
|
||||||
|
|
||||||
|
it "does not render anything" do
|
||||||
|
subject
|
||||||
|
expect(rendered).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the token is expired" do
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: 2.days.ago.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it "should display an error" do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(rendered).to have_content("Votre jeton API Entreprise est expiré")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the token expires in few days it should display the expiration date" do
|
||||||
|
let(:expiration) { 2.days.from_now }
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: expiration.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it "should display an error" do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(rendered).to have_content("Votre jeton API Entreprise expirera le\n#{expiration.strftime('%d/%m/%Y à %H:%M')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the token expires in a long time" do
|
||||||
|
let(:expiration) { 2.months.from_now }
|
||||||
|
let(:api_entreprise_token) { JWT.encode({ exp: expiration.to_i }, nil, "none") }
|
||||||
|
|
||||||
|
it "does not render anything" do
|
||||||
|
subject
|
||||||
|
expect(rendered).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue