Commit 5e4cc7eb authored by Magdalena Frankiewicz's avatar Magdalena Frankiewicz

Rate limit update username action

This is to mitigate misuse, for example to mass-probe which usernames
are in use.

Changelog: security
parent ba69be0e
...@@ -6,6 +6,9 @@ class ProfilesController < Profiles::ApplicationController ...@@ -6,6 +6,9 @@ class ProfilesController < Profiles::ApplicationController
before_action :user before_action :user
before_action :authorize_change_username!, only: :update_username before_action :authorize_change_username!, only: :update_username
before_action only: :update_username do
check_rate_limit!(:profile_update_username, scope: current_user)
end
skip_before_action :require_email, only: [:show, :update] skip_before_action :require_email, only: [:show, :update]
before_action do before_action do
push_frontend_feature_flag(:webauthn, default_enabled: :yaml) push_frontend_feature_flag(:webauthn, default_enabled: :yaml)
......
...@@ -51,6 +51,7 @@ module Gitlab ...@@ -51,6 +51,7 @@ module Gitlab
web_hook_calls: { interval: 1.minute }, web_hook_calls: { interval: 1.minute },
users_get_by_id: { threshold: 10, interval: 1.minute }, users_get_by_id: { threshold: 10, interval: 1.minute },
profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }, profile_resend_email_confirmation: { threshold: 5, interval: 1.minute },
profile_update_username: { threshold: 10, interval: 1.minute },
update_environment_canary_ingress: { threshold: 1, interval: 1.minute }, update_environment_canary_ingress: { threshold: 1, interval: 1.minute },
auto_rollback_deployment: { threshold: 1, interval: 3.minutes }, auto_rollback_deployment: { threshold: 1, interval: 3.minutes },
user_email_lookup: { threshold: -> { application_settings.user_email_lookup_limit }, interval: 1.minute } user_email_lookup: { threshold: -> { application_settings.user_email_lookup_limit }, interval: 1.minute }
......
...@@ -153,9 +153,12 @@ RSpec.describe ProfilesController, :request_store do ...@@ -153,9 +153,12 @@ RSpec.describe ProfilesController, :request_store do
let(:gitlab_shell) { Gitlab::Shell.new } let(:gitlab_shell) { Gitlab::Shell.new }
let(:new_username) { generate(:username) } let(:new_username) { generate(:username) }
it 'allows username change' do before do
sign_in(user) sign_in(user)
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
end
it 'allows username change' do
put :update_username, put :update_username,
params: { user: { username: new_username } } params: { user: { username: new_username } }
...@@ -166,8 +169,6 @@ RSpec.describe ProfilesController, :request_store do ...@@ -166,8 +169,6 @@ RSpec.describe ProfilesController, :request_store do
end end
it 'updates a username using JSON request' do it 'updates a username using JSON request' do
sign_in(user)
put :update_username, put :update_username,
params: { params: {
user: { username: new_username } user: { username: new_username }
...@@ -179,8 +180,6 @@ RSpec.describe ProfilesController, :request_store do ...@@ -179,8 +180,6 @@ RSpec.describe ProfilesController, :request_store do
end end
it 'renders an error message when the username was not updated' do it 'renders an error message when the username was not updated' do
sign_in(user)
put :update_username, put :update_username,
params: { params: {
user: { username: 'invalid username.git' } user: { username: 'invalid username.git' }
...@@ -192,8 +191,6 @@ RSpec.describe ProfilesController, :request_store do ...@@ -192,8 +191,6 @@ RSpec.describe ProfilesController, :request_store do
end end
it 'raises a correct error when the username is missing' do it 'raises a correct error when the username is missing' do
sign_in(user)
expect { put :update_username, params: { user: { gandalf: 'you shall not pass' } } } expect { put :update_username, params: { user: { gandalf: 'you shall not pass' } } }
.to raise_error(ActionController::ParameterMissing) .to raise_error(ActionController::ParameterMissing)
end end
...@@ -202,8 +199,6 @@ RSpec.describe ProfilesController, :request_store do ...@@ -202,8 +199,6 @@ RSpec.describe ProfilesController, :request_store do
it 'moves dependent projects to new namespace' do it 'moves dependent projects to new namespace' do
project = create(:project_empty_repo, :legacy_storage, namespace: namespace) project = create(:project_empty_repo, :legacy_storage, namespace: namespace)
sign_in(user)
put :update_username, put :update_username,
params: { user: { username: new_username } } params: { user: { username: new_username } }
...@@ -220,8 +215,6 @@ RSpec.describe ProfilesController, :request_store do ...@@ -220,8 +215,6 @@ RSpec.describe ProfilesController, :request_store do
before_disk_path = project.disk_path before_disk_path = project.disk_path
sign_in(user)
put :update_username, put :update_username,
params: { user: { username: new_username } } params: { user: { username: new_username } }
...@@ -232,5 +225,18 @@ RSpec.describe ProfilesController, :request_store do ...@@ -232,5 +225,18 @@ RSpec.describe ProfilesController, :request_store do
expect(before_disk_path).to eq(project.disk_path) expect(before_disk_path).to eq(project.disk_path)
end end
end end
context 'when the rate limit is reached' do
it 'does not update the username and returns status 429 Too Many Requests' do
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:profile_update_username, scope: user).and_return(true)
expect do
put :update_username,
params: { user: { username: new_username } }
end.not_to change { user.reload.username }
expect(response).to have_gitlab_http_status(:too_many_requests)
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment