diff --git a/app/assets/images/spider-128.png b/app/assets/images/spider-128.png new file mode 100644 index 000000000..3dfdd3380 Binary files /dev/null and b/app/assets/images/spider-128.png differ diff --git a/app/controllers/api_tokens_controller.rb b/app/controllers/api_tokens_controller.rb index 50ae9664c..986106252 100644 --- a/app/controllers/api_tokens_controller.rb +++ b/app/controllers/api_tokens_controller.rb @@ -15,6 +15,9 @@ class APITokensController < ApplicationController .map { |libelle, id| ["#{id} - #{libelle}", id] } end + def securite + end + def create @api_token, @packed_token = APIToken.generate(current_administrateur) diff --git a/app/javascript/controllers/api_token_securite_controller.ts b/app/javascript/controllers/api_token_securite_controller.ts new file mode 100644 index 000000000..da306797a --- /dev/null +++ b/app/javascript/controllers/api_token_securite_controller.ts @@ -0,0 +1,99 @@ +import { ApplicationController } from './application_controller'; + +export class ApiTokenSecuriteController extends ApplicationController { + static targets = [ + 'continueButton', + 'networkFiltering', + 'infiniteLifetime', + 'customLifetime', + 'customLifetimeInput', + 'networks' + ]; + + declare readonly continueButtonTarget: HTMLButtonElement; + declare readonly networkFilteringTarget: HTMLElement; + declare readonly infiniteLifetimeTarget: HTMLInputElement; + declare readonly customLifetimeTarget: HTMLElement; + declare readonly customLifetimeInputTarget: HTMLInputElement; + declare readonly networksTarget: HTMLInputElement; + + showNetworkFiltering() { + this.networkFilteringTarget.classList.remove('hidden'); + this.setContinueButtonState(); + this.infiniteLifetimeTarget.disabled = false; + } + + hideNetworkFiltering() { + this.networkFilteringTarget.classList.add('hidden'); + this.setContinueButtonState(); + this.infiniteLifetimeTarget.checked = false; + this.infiniteLifetimeTarget.disabled = true; + } + + showCustomLifetime() { + this.customLifetimeTarget.classList.remove('hidden'); + this.setContinueButtonState(); + } + + hideCustomLifetime() { + this.customLifetimeTarget.classList.add('hidden'); + this.setContinueButtonState(); + } + + setContinueButtonState() { + if (this.networkDefined() && this.lifetimeDefined()) { + this.continueButtonTarget.disabled = false; + } else { + this.continueButtonTarget.disabled = true; + } + } + + networkDefined() { + if ( + this.element.querySelectorAll( + "[name='networkFiltering'][value='none']:checked" + ).length > 0 + ) { + return true; + } + + if ( + this.element.querySelectorAll( + "[name='networkFiltering'][value='customNetworks']:checked" + ).length > 0 && + this.networksTarget.value.trim() != '' + ) { + return true; + } + + return false; + } + + lifetimeDefined() { + if ( + this.element.querySelectorAll( + "[name='lifetime'][value='oneWeek']:checked" + ).length > 0 + ) { + return true; + } + + if ( + this.element.querySelectorAll( + "[name='lifetime'][value='infinite']:checked" + ).length > 0 + ) { + return true; + } + + if ( + this.element.querySelectorAll("[name='lifetime'][value='custom']:checked") + .length > 0 && + this.customLifetimeInputTarget.value.trim() != '' + ) { + return true; + } + + return false; + } +} diff --git a/app/views/api_tokens/securite.html.haml b/app/views/api_tokens/securite.html.haml new file mode 100644 index 000000000..87a2de560 --- /dev/null +++ b/app/views/api_tokens/securite.html.haml @@ -0,0 +1,93 @@ +- content_for :title, "Sécurité du jeton « #{@name} »" + += render partial: 'administrateurs/breadcrumbs', + locals: { steps: [['Tableau de bord', tableau_de_bord_helper_path], + [t('users.profil.show.profile'), profil_path], + [t('api_tokens.nom.new_token')]] } + +.fr-container.fr-mt-2w + %h1 Sécurité + + .flex.align-center + %div + = image_tag("spider-128.png", width: '128px', style: 'display: block;') + %div + %blockquote{ cite: "https://fr.wikipedia.org/wiki/Un_grand_pouvoir,_grandes_responsabilit%C3%A9s#Utilisation_dans_Spider-Man" } + %p « Avec un grand pouvoir vient une grande responsabilité » + %p.fr-text--sm Oncle Ben dans Spider-Man + + %p.fr-mt-2w + Votre jeton va proprablement vous permettre de manipuler des données confidentielles, voir personnelles.
+ %b Il est de votre responsabilité de le conserver en sécurité et d'en limiter l'utilisation aux seules personnes habilitées. + %p Pour vous aider, nous vous proposons des fonctionnalités de filtrage réseau et de durée de vie du jeton. + + = form_with url: api_tokens_path, + method: :post, + html: { class: 'fr-mt-2w' }, + data: { controller: 'api-token-securite' } do |f| + + = render Dsfr::RadioButtonListComponent.new(form: f, + target: :networkFiltering, + buttons: [ { label: 'Je veux spécifier les réseaux autorisées à utiliser mon jeton', + value: :customNetworks, + 'data-action': 'click->api-token-securite#showNetworkFiltering' }, + { label: 'Mon jeton peut être utilisé depuis nʼimporte quelle adresse IP dans le monde', + hint: 'dangereux', + value: :none, + 'data-action': 'click->api-token-securite#hideNetworkFiltering' }]) do + Filtrage réseau : + + .fr-input-group.fr-mb-4w.hidden{ 'data-api-token-securite-target': 'networkFiltering' } + = f.label :name, class: 'fr-label' do + Entrez les adresses IP autorisées + %span.fr-hint-text adresses réseaux séparées par des espaces. Ex: 176.31.79.200 192.168.33.0/24 2001:41d0:304:400::52f/128 + = f.text_field :networks, + class: 'fr-input', + autocomplete: 'off', + autocapitalize: 'off', + autocorrect: 'off', + spellcheck: false, + 'data-action': 'input->api-token-securite#setContinueButtonState' + + = render Dsfr::RadioButtonListComponent.new(form: f, + target: :lifetime, + buttons: [ { label: '1 semaine', + value: :oneWeek, + 'data-action': 'click->api-token-securite#hideCustomLifetime' }, + { label: 'durée personnalisée inférieure à 1 an', + value: :custom, + 'data-action': 'click->api-token-securite#showCustomLifetime'}, + { label: 'Infini (le filtrage réseau doit être activé)', + value: :infinite, + disabled: true, + 'data-api-token-securite-target': 'infiniteLifetime', + 'data-action': 'click->api-token-securite#hideCustomLifetime' }]) do + Durée de vie du jeton : + + .fr-input-group.fr-mb-4w.hidden{ 'data-api-token-securite-target': 'customLifetime' } + = f.label :name, class: 'fr-label' do + Entrez la date de fin de validité du jeton + + %input{ type: 'date', + class: 'fr-input width-33 fr-mb-4w', + name: 'customLifetime', + 'data-action': 'input->api-token-securite#setContinueButtonState', + 'data-api-token-securite-target': 'customLifetimeInput', + min: Date.tomorrow.iso8601, + max: 1.year.from_now.to_date.iso8601 } + + = f.hidden_field :name, value: params[:name] + = f.hidden_field :access, value: params[:access] + = f.hidden_field :target, value: params[:target] + - params[:targets]&.each do |target| + = f.hidden_field 'targets[]', value: target + + %ul.fr-btns-group.fr-btns-group--inline + %li + = f.button type: :submit, + class: "fr-btn fr-btn--primary", + disabled: true, + 'data-api-token-securite-target': 'continueButton' do + créer le jeton + %li + = link_to 'retour', autorisations_api_tokens_path(name: params[:name], access: params[:access], target: params[:target], targets: params[:targets]), class: "fr-btn fr-btn--secondary" diff --git a/config/routes.rb b/config/routes.rb index 7ee1f1980..bdaed2d31 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -212,6 +212,7 @@ Rails.application.routes.draw do collection do get :nom get :autorisations + get :securite end end