Skip to content

Commit 4ff6614

Browse files
authored
Merge pull request #13357 from mfo/harden-api-token
ETQ admin, je dois fournir un filtrage réseau pour avoir un jeton API
2 parents 950e396 + 5e7caff commit 4ff6614

16 files changed

Lines changed: 196 additions & 48 deletions

File tree

app/components/profile/api_token_component.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def autorizations
2020
def network_filtering
2121
if @api_token.authorized_networks.present?
2222
"filtrage : #{@api_token.authorized_networks_for_ui}"
23+
elsif @api_token.pending_auto_ip?
24+
tag.span('en attente de détection IP (1er appel)', class: 'fr-badge fr-badge--sm fr-badge--info')
2325
else
2426
tag.span('aucun filtrage réseau', class: 'fr-text-default--warning')
2527
end

app/controllers/administrateurs/api_tokens_controller.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ def create
2626
@api_token, @packed_token = APIToken.generate(current_administrateur)
2727

2828
@api_token.update!(name:, write_access:,
29-
allowed_procedure_ids:, authorized_networks:, expires_at:)
29+
allowed_procedure_ids:, authorized_networks:,
30+
expires_at:, requires_ip_filtering: true)
3031

3132
@curl_command = curl_command(@packed_token, @api_token.procedure_ids.first)
3233
end
@@ -176,7 +177,7 @@ def expires_at
176177
Date.parse(params[:customLifetime]),
177178
1.year.from_now,
178179
].min
179-
in 'infinite' if authorized_networks.present?
180+
in 'infinite'
180181
nil
181182
else
182183
1.week.from_now.to_date

app/controllers/api/v2/base_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def authenticate_from_token
5656

5757
if @api_token.present?
5858
@api_token.touch(:last_v2_authenticated_at)
59+
@api_token.assign_first_ip!(request.remote_ip)
5960
@api_token.store_new_ip(request.remote_ip)
6061
@current_user = @api_token.administrateur.user
6162
Current.user = @current_user

app/controllers/api_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def authenticate_from_token
3333

3434
if @api_token.present?
3535
@api_token.touch(:last_v1_authenticated_at)
36+
@api_token.assign_first_ip!(request.remote_ip)
3637
@api_token.store_new_ip(request.remote_ip)
3738
@current_user = @api_token.administrateur.user
3839
end

app/javascript/controllers/api_token_securite_controller.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ export class ApiTokenSecuriteController extends ApplicationController {
44
static targets = [
55
'continueButton',
66
'networkFiltering',
7-
'infiniteLifetime',
87
'customLifetime',
98
'customLifetimeInput',
109
'networks'
1110
];
1211

1312
declare readonly continueButtonTarget: HTMLButtonElement;
1413
declare readonly networkFilteringTarget: HTMLElement;
15-
declare readonly infiniteLifetimeTarget: HTMLInputElement;
1614
declare readonly customLifetimeTarget: HTMLElement;
1715
declare readonly customLifetimeInputTarget: HTMLInputElement;
1816
declare readonly networksTarget: HTMLInputElement;
@@ -24,14 +22,11 @@ export class ApiTokenSecuriteController extends ApplicationController {
2422
showNetworkFiltering() {
2523
this.networkFilteringTarget.classList.remove('hidden');
2624
this.setContinueButtonState();
27-
this.infiniteLifetimeTarget.disabled = false;
2825
}
2926

3027
hideNetworkFiltering() {
3128
this.networkFilteringTarget.classList.add('hidden');
3229
this.setContinueButtonState();
33-
this.infiniteLifetimeTarget.checked = false;
34-
this.infiniteLifetimeTarget.disabled = true;
3530
}
3631

3732
showCustomLifetime() {
@@ -45,34 +40,13 @@ export class ApiTokenSecuriteController extends ApplicationController {
4540
}
4641

4742
setContinueButtonState() {
48-
if (this.networkDefined() && this.lifetimeDefined()) {
43+
if (this.lifetimeDefined()) {
4944
this.continueButtonTarget.disabled = false;
5045
} else {
5146
this.continueButtonTarget.disabled = true;
5247
}
5348
}
5449

55-
networkDefined() {
56-
if (
57-
this.element.querySelectorAll(
58-
"[name='networkFiltering'][value='none']:checked"
59-
).length > 0
60-
) {
61-
return true;
62-
}
63-
64-
if (
65-
this.element.querySelectorAll(
66-
"[name='networkFiltering'][value='customNetworks']:checked"
67-
).length > 0 &&
68-
this.networksTarget.value.trim() != ''
69-
) {
70-
return true;
71-
}
72-
73-
return false;
74-
}
75-
7650
lifetimeDefined() {
7751
if (
7852
this.element.querySelectorAll(

app/models/api_token.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ def forbidden_network?(ip)
8787
authorized_networks.none? { |range| range.include?(ip) }
8888
end
8989

90+
def assign_first_ip!(ip)
91+
return if !requires_ip_filtering? || authorized_networks.any?
92+
93+
update!(authorized_networks: [IPAddr.new(ip)])
94+
end
95+
96+
def pending_auto_ip?
97+
requires_ip_filtering? && authorized_networks.empty?
98+
end
99+
90100
def expired?
91101
expires_at&.past?
92102
end

app/views/administrateurs/api_tokens/edit.html.haml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
- if @invalid_network_message.present?
4848
%p.fr-error-text= @invalid_network_message
4949

50+
- if @api_token.pending_auto_ip?
51+
.fr-messages-group{ aria: { live: 'polite' } }
52+
%p.fr-message.fr-message--info
53+
Si aucun réseau n'est configuré, l'adresse IP sera automatiquement verrouillée lors du premier appel API.
54+
5055
= form_with url: admin_api_token_path(@api_token), method: :patch, html: { class: 'fr-mt-2w' } do |f|
5156
.fr-mb-4w
5257
- if @api_token.full_access?

app/views/administrateurs/api_tokens/securite.html.haml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@
2828
2929
= render Dsfr::RadioButtonListComponent.new(form: f,
3030
target: :networkFiltering,
31-
buttons: [ { label: 'Je veux spécifier les réseaux autorisées à utiliser mon jeton',
31+
buttons: [ { label: 'Détection automatique au 1er appel API',
32+
hint: "L'adresse IP de votre premier appel sera automatiquement autorisée",
33+
value: :autoAssign,
34+
checked: params[:networkFiltering] != 'customNetworks',
35+
'data-action': 'click->api-token-securite#hideNetworkFiltering' },
36+
{ label: 'Saisie manuelle des réseaux autorisés',
3237
value: :customNetworks,
3338
checked: params[:networkFiltering] == 'customNetworks',
34-
'data-action': 'click->api-token-securite#showNetworkFiltering' },
35-
{ label: 'Mon jeton peut être utilisé depuis nʼimporte quelle adresse IP dans le monde',
36-
hint: 'dangereux',
37-
value: :none,
38-
checked: params[:networkFiltering] == 'none',
39-
'data-action': 'click->api-token-securite#hideNetworkFiltering' }]) do
39+
'data-action': 'click->api-token-securite#showNetworkFiltering' }]) do
4040
Filtrage réseau :
4141
4242
.fr-input-group.fr-mb-4w{
@@ -68,11 +68,9 @@
6868
value: :custom,
6969
checked: params[:lifetime] == 'custom',
7070
'data-action': 'click->api-token-securite#showCustomLifetime'},
71-
{ label: 'Infini (le filtrage réseau doit être activé)',
71+
{ label: 'Infini',
7272
value: :infinite,
7373
checked: params[:lifetime] == 'infinite',
74-
disabled: true,
75-
'data-api-token-securite-target': 'infiniteLifetime',
7674
'data-action': 'click->api-token-securite#hideCustomLifetime' }]) do
7775
Durée de vie du jeton :
7876
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class AddRequiresIPFilteringToAPITokens < ActiveRecord::Migration[8.0]
4+
def change
5+
add_column :api_tokens, :requires_ip_filtering, :boolean, default: false, null: false
6+
end
7+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class ChangeRequiresIPFilteringDefault < ActiveRecord::Migration[8.0]
4+
def change
5+
change_column_default :api_tokens, :requires_ip_filtering, from: false, to: true
6+
end
7+
end

0 commit comments

Comments
 (0)