Commit ff3703f5 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'sh-fix-destroy-user-race' into 'master'

Fix race condition where a namespace would be deleted before a project was deleted

Closes #30334 and #30306

See merge request !10389
parents dcbd090c 6a2d022d
...@@ -25,12 +25,12 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -25,12 +25,12 @@ class RegistrationsController < Devise::RegistrationsController
end end
def destroy def destroy
Users::DestroyService.new(current_user).execute(current_user) DeleteUserWorker.perform_async(current_user.id, current_user.id)
respond_to do |format| respond_to do |format|
format.html do format.html do
session.try(:destroy) session.try(:destroy)
redirect_to new_user_session_path, notice: "Account successfully removed." redirect_to new_user_session_path, notice: "Account scheduled for removal."
end end
end end
end end
......
...@@ -20,10 +20,10 @@ module Users ...@@ -20,10 +20,10 @@ module Users
Groups::DestroyService.new(group, current_user).execute Groups::DestroyService.new(group, current_user).execute
end end
user.personal_projects.each do |project| user.personal_projects.with_deleted.each do |project|
# Skip repository removal because we remove directory with namespace # Skip repository removal because we remove directory with namespace
# that contain all this repositories # that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end end
move_issues_to_ghost_user(user) move_issues_to_ghost_user(user)
......
---
title: Fix race condition where a namespace would be deleted before a project was
deleted
merge_request:
author:
...@@ -293,7 +293,7 @@ module API ...@@ -293,7 +293,7 @@ module API
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
::Users::DestroyService.new(current_user).execute(user) DeleteUserWorker.perform_async(current_user.id, user.id)
end end
desc 'Block a user. Available only for admins.' desc 'Block a user. Available only for admins.'
......
...@@ -68,4 +68,20 @@ describe RegistrationsController do ...@@ -68,4 +68,20 @@ describe RegistrationsController do
end end
end end
end end
describe '#destroy' do
let(:user) { create(:user) }
before do
sign_in(user)
end
it 'schedules the user for destruction' do
expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id)
post(:destroy)
expect(response.status).to eq(302)
end
end
end end
...@@ -676,7 +676,7 @@ describe API::Users, api: true do ...@@ -676,7 +676,7 @@ describe API::Users, api: true do
before { admin } before { admin }
it "deletes user" do it "deletes user" do
delete api("/users/#{user.id}", admin) Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) }
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound
...@@ -684,23 +684,23 @@ describe API::Users, api: true do ...@@ -684,23 +684,23 @@ describe API::Users, api: true do
end end
it "does not delete for unauthenticated user" do it "does not delete for unauthenticated user" do
delete api("/users/#{user.id}") Sidekiq::Testing.inline! { delete api("/users/#{user.id}") }
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
it "is not available for non admin users" do it "is not available for non admin users" do
delete api("/users/#{user.id}", user) Sidekiq::Testing.inline! { delete api("/users/#{user.id}", user) }
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
it "returns 404 for non-existing user" do it "returns 404 for non-existing user" do
delete api("/users/999999", admin) Sidekiq::Testing.inline! { delete api("/users/999999", admin) }
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found') expect(json_response['message']).to eq('404 User Not Found')
end end
it "returns a 404 for invalid ID" do it "returns a 404 for invalid ID" do
delete api("/users/ASDF", admin) Sidekiq::Testing.inline! { delete api("/users/ASDF", admin) }
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
......
...@@ -17,13 +17,28 @@ describe Users::DestroyService, services: true do ...@@ -17,13 +17,28 @@ describe Users::DestroyService, services: true do
expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound) expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
end end
it 'will delete the project in the near future' do it 'will delete the project' do
expect_any_instance_of(Projects::DestroyService).to receive(:async_execute).once expect_any_instance_of(Projects::DestroyService).to receive(:execute).once
service.execute(user) service.execute(user)
end end
end end
context 'projects in pending_delete' do
before do
project.pending_delete = true
project.save
end
it 'destroys a project in pending_delete' do
expect_any_instance_of(Projects::DestroyService).to receive(:execute).once
service.execute(user)
expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context "a deleted user's issues" do context "a deleted user's issues" do
let(:project) { create(:project) } let(:project) { create(:project) }
......
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