diff --git a/app/components/profile/api_token_component/api_token_component.html.haml b/app/components/profile/api_token_component/api_token_component.html.haml index 279793805c2..c2d28e4b3c6 100644 --- a/app/components/profile/api_token_component/api_token_component.html.haml +++ b/app/components/profile/api_token_component/api_token_component.html.haml @@ -1,4 +1,4 @@ -%li.fr-mt-2w.flex +%li.fr-mt-2w.flex{ id: dom_id(@api_token) } .fr-mr-4w{ class: class_names('fr-text-default--success': recently_used?) } %span.fr-icon-key-line .flex-grow @@ -9,8 +9,14 @@ %div= network_filtering %div= use_and_expiration %div - = link_to 'Supprimer', - admin_api_token_path(@api_token), - method: :delete, - class: 'fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-btn--icon-left fr-icon-delete-line', - data: { confirm: "Confirmez-vous la suppression du jeton « #{@api_token.name} » ?" } + %ul + %li + = link_to 'Modifier', + edit_admin_api_token_path(@api_token), + class: 'fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-btn--icon-left fr-icon-settings-5-line' + %li + = link_to 'Supprimer', + admin_api_token_path(@api_token), + class: 'fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-btn--icon-left fr-icon-delete-line', + data: { turbo_method: :delete, confirm: "Confirmez-vous la suppression du jeton « #{@api_token.name} » ?" } + diff --git a/app/controllers/administrateurs/api_tokens_controller.rb b/app/controllers/administrateurs/api_tokens_controller.rb index 119feae1987..8c178d0effe 100644 --- a/app/controllers/administrateurs/api_tokens_controller.rb +++ b/app/controllers/administrateurs/api_tokens_controller.rb @@ -1,7 +1,9 @@ module Administrateurs class APITokensController < AdministrateurController + include ActionView::RecordIdentifier + before_action :authenticate_administrateur! - before_action :set_api_token, only: [:destroy] + before_action :set_api_token, only: [:edit, :update, :destroy] def nom @name = name @@ -30,10 +32,30 @@ def create allowed_procedure_ids:, authorized_networks:, expires_at:) end + def edit + end + + def update + if invalid_network? + @invalid_network = true + return render :edit + end + + if @api_token.eternal? && networks.empty? + flash[:alert] = "Vous ne pouvez pas supprimer les restrictions d'accès à l'API d'un jeton permanent." + return render :edit + end + + @api_token.update!(name:, authorized_networks: networks) + + flash[:notice] = "Le jeton d'API a été mis à jour." + redirect_to profil_path + end + def destroy @api_token.destroy - redirect_to profil_path + render turbo_stream: turbo_stream.remove(dom_id(@api_token)) end private diff --git a/app/models/api_token.rb b/app/models/api_token.rb index 2e27881dfed..9183c67c70f 100644 --- a/app/models/api_token.rb +++ b/app/models/api_token.rb @@ -89,6 +89,10 @@ def expired? expires_at&.past? end + def eternal? + expires_at.nil? + end + class << self def generate(administrateur) plain_token = generate_unique_secure_token diff --git a/app/views/administrateurs/api_tokens/edit.html.haml b/app/views/administrateurs/api_tokens/edit.html.haml new file mode 100644 index 00000000000..6efa775f59d --- /dev/null +++ b/app/views/administrateurs/api_tokens/edit.html.haml @@ -0,0 +1,46 @@ +- content_for :title, "Modification du jeton d'API « #{@api_token.name} »" + += render partial: 'administrateurs/breadcrumbs', + locals: { steps: [['Tableau de bord', tableau_de_bord_helper_path], + [t('users.profil.show.profile'), profil_path], + ["Jeton d’API : #{@api_token.name}"]] } + +.fr-container.fr-mt-2w + %h1 Modification du jeton d'API « #{@api_token.name} » + + = form_with url: admin_api_token_path(@api_token), method: :patch, html: { class: 'fr-mt-2w' } do |f| + .fr-input-group + = f.label :name, class: 'fr-label' do + = t('name', scope: [:administrateurs, :api_tokens, :nom]) + %span.fr-hint-text= t('name-hint', scope: [:administrateurs, :api_tokens, :nom]) + = f.text_field :name, + class: 'fr-input width-33', + autocomplete: 'off', + autocapitalize: 'off', + autocorrect: 'off', + spellcheck: false, + required: true, + value: @api_token.name + + .fr-input-group.fr-mb-4w{ + class: class_names('fr-input-group--error': @invalid_network) } + = f.label :name, class: 'fr-label' do + = @api_token.eternal? ? "Entrez au moins 1 réseau autorisé" : "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: class_names('fr-input': true, 'fr-input--error': @invalid_network), + autocomplete: 'off', + autocapitalize: 'off', + autocorrect: 'off', + spellcheck: false, + required: @api_token.eternal?, + value: @api_token.authorized_networks_for_ui.gsub(/,/, ' ') + + - if @invalid_network + %p.fr-error-text vous devez entrer des adresses ipv4 ou ipv6 valides + + %ul.fr-btns-group.fr-btns-group--inline + %li + = f.button 'Modifier', type: :submit, class: "fr-btn fr-btn--primary" + %li + = link_to 'Revenir', profil_path, class: "fr-btn fr-btn--secondary" diff --git a/app/views/administrateurs/api_tokens/index.turbo_stream.haml b/app/views/administrateurs/api_tokens/index.turbo_stream.haml deleted file mode 100644 index b6c304eb1b2..00000000000 --- a/app/views/administrateurs/api_tokens/index.turbo_stream.haml +++ /dev/null @@ -1,2 +0,0 @@ -= turbo_stream.replace dom_id(current_administrateur, :profil_api_token) do - = render Profile::APITokenCardComponent.new created_api_token: @api_token, created_packed_token: @packed_token diff --git a/config/routes.rb b/config/routes.rb index 366de80260e..8d6ad929515 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -662,7 +662,7 @@ end end - resources :api_tokens, only: [:create, :destroy] do + resources :api_tokens, only: [:create, :destroy, :edit, :update] do collection do get :nom get :autorisations diff --git a/spec/controllers/administrateurs/api_tokens_controller_spec.rb b/spec/controllers/administrateurs/api_tokens_controller_spec.rb index 5bf4c769e3d..c96f0cbc882 100644 --- a/spec/controllers/administrateurs/api_tokens_controller_spec.rb +++ b/spec/controllers/administrateurs/api_tokens_controller_spec.rb @@ -93,4 +93,50 @@ end end end + + describe 'update' do + let(:token) { APIToken.generate(admin).first } + let(:params) { { name:, networks: } } + let(:name) { 'new name' } + let(:networks) { '118.218.200.200' } + + subject { patch :update, params: params.merge(id: token.id) } + + context 'nominal' do + before { subject; token.reload } + + it 'updates a token' do + expect(token.name).to eq('new name') + expect(token.authorized_networks).to eq([IPAddr.new('118.218.200.200')]) + end + end + + context 'with bad network' do + let(:networks) { 'bad' } + + before { subject; token.reload } + + it 'does not update a token' do + expect(token.name).not_to eq('new name') + expect(assigns(:invalid_network)).to be true + expect(response).to render_template(:edit) + end + end + + context 'with no network and infinite lifetime' do + before do + token.update!(authorized_networks: [IPAddr.new('118.218.200.200')]) + subject + token.reload + end + + let(:networks) { '' } + + it 'does not update a token' do + expect(token.name).not_to eq('new name') + expect(flash[:alert]).to eq("Vous ne pouvez pas supprimer les restrictions d'accès à l'API d'un jeton permanent.") + expect(response).to render_template(:edit) + end + end + end end