# Offense count: 26
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
- Max: 286
+ Max: 297
# Offense count: 59
# Configuration parameters: AllowedMethods, AllowedPatterns.
end
def destroy
- current_user.soft_destroy!
+ if current_user.deletion_allowed?
+ current_user.soft_destroy!
- session.delete(:user)
- session_expires_automatically
+ session.delete(:user)
+ session_expires_automatically
- flash[:notice] = t ".success"
- redirect_to root_path
+ flash[:notice] = t ".success"
+ redirect_to root_path
+ else
+ head :bad_request
+ end
end
end
#
# Indexes
#
-# changesets_bbox_idx (min_lat,max_lat,min_lon,max_lon) USING gist
-# changesets_closed_at_idx (closed_at)
-# changesets_created_at_idx (created_at)
-# changesets_user_id_created_at_idx (user_id,created_at)
-# changesets_user_id_id_idx (user_id,id)
+# changesets_bbox_idx (min_lat,max_lat,min_lon,max_lon) USING gist
+# changesets_closed_at_idx (closed_at)
+# changesets_created_at_idx (created_at)
+# changesets_user_id_created_at_idx (user_id,created_at)
+# changesets_user_id_id_idx (user_id,id)
+# index_changesets_on_user_id_and_closed_at (user_id,closed_at)
#
# Foreign Keys
#
end
end
+ def deletion_allowed_at
+ unless Settings.user_account_deletion_delay.nil?
+ last_changeset = changesets.reorder(:closed_at => :desc).first
+ return last_changeset.closed_at.utc + Settings.user_account_deletion_delay.hours if last_changeset
+ end
+ creation_time.utc
+ end
+
+ def deletion_allowed?
+ deletion_allowed_at <= Time.now.utc
+ end
+
private
def encrypt_password
<li><%= t ".retain_email" %></li>
</ul>
-<%= link_to t(".delete_account"), account_path, { :method => :delete, :class => "btn btn-danger", :data => { :confirm => t(".confirm_delete") } } %>
+<% if current_user.deletion_allowed? %>
+ <%= link_to t(".delete_account"), account_path, { :method => :delete, :class => "btn btn-danger", :data => { :confirm => t(".confirm_delete") } } %>
+<% else %>
+ <div class="alert alert-warning">
+ <%= t ".recent_editing_html", :time => friendly_date(current_user.deletion_allowed_at) %>
+ </div>
+ <button class="btn btn-secondary" disabled><%= t(".delete_account") %></button>
+<% end %>
+
<%= link_to t(".cancel"), edit_account_path, :class => "btn btn-link" %>
required(:max_number_of_relation_members).filled(:int?)
required(:max_issues_count).filled(:int?)
required(:api_timeout).filled(:int?)
+ required(:user_account_deletion_delay).maybe(:number?)
required(:imagery_blacklist).maybe(:array?)
required(:status).filled(:str?, :included_in? => ALLOWED_STATUS)
required(:avatar_storage).filled(:str?)
retain_notes: Your map notes and note comments, if any, will be retained but hidden from view.
retain_changeset_discussions: Your changeset discussions, if any, will be retained.
retain_email: Your email address will be retained.
+ recent_editing_html: "As you have edited recently your account cannot currently be deleted. Deletion will be possible in %{time}."
confirm_delete: Are you sure?
cancel: Cancel
accounts:
web_timeout: 30
# Periods (in hours) which are allowed for user blocks
user_block_periods: [0, 1, 3, 6, 12, 24, 48, 96, 168, 336, 731, 4383, 8766, 87660]
+# Account deletion cooldown period (in hours) since last changeset close; null to disable, 0 to make sure there aren't any open changesets when the deletion happens
+user_account_deletion_delay: null
# Rate limit for message sending
max_messages_per_hour: 60
# Rate limit for friending
--- /dev/null
+class AddClosedAtIndexToChangesets < ActiveRecord::Migration[7.1]
+ disable_ddl_transaction!
+
+ def change
+ add_index :changesets, [:user_id, :closed_at], :algorithm => :concurrently
+ end
+end
CREATE INDEX index_changeset_comments_on_created_at ON public.changeset_comments USING btree (created_at);
+--
+-- Name: index_changesets_on_user_id_and_closed_at; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_changesets_on_user_id_and_closed_at ON public.changesets USING btree (user_id, closed_at);
+
+
--
-- Name: index_changesets_subscribers_on_changeset_id; Type: INDEX; Schema: public; Owner: -
--
('23'),
('22'),
('21'),
+('20231117170422'),
('20231101222146'),
('20231029151516'),
('20231010194809'),
# Make sure we have a button to "go public"
assert_select "form.button_to[action='/user/go_public']", true
end
+
+ def test_destroy_allowed
+ user = create(:user)
+ session_for(user)
+
+ delete account_path
+ assert_response :redirect
+ end
+
+ def test_destroy_not_allowed
+ with_user_account_deletion_delay(24) do
+ user = create(:user)
+ create(:changeset, :user => user, :created_at => Time.now.utc)
+ session_for(user)
+
+ delete account_path
+ assert_response :bad_request
+ end
+ end
end
oauth_access_token.reload
assert_predicate oauth_access_token, :revoked?
end
+
+ def test_deletion_allowed_when_no_changesets
+ with_user_account_deletion_delay(10000) do
+ user = create(:user)
+ assert_predicate user, :deletion_allowed?
+ end
+ end
+
+ def test_deletion_allowed_without_delay
+ with_user_account_deletion_delay(nil) do
+ user = create(:user)
+ create(:changeset, :user => user)
+ user.reload
+ assert_predicate user, :deletion_allowed?
+ end
+ end
+
+ def test_deletion_allowed_past_delay
+ with_user_account_deletion_delay(10) do
+ user = create(:user)
+ create(:changeset, :user => user, :created_at => Time.now.utc - 12.hours, :closed_at => Time.now.utc - 10.hours)
+ user.reload
+ assert_predicate user, :deletion_allowed?
+ end
+ end
+
+ def test_deletion_allowed_during_delay
+ with_user_account_deletion_delay(10) do
+ user = create(:user)
+ create(:changeset, :user => user, :created_at => Time.now.utc - 11.hours, :closed_at => Time.now.utc - 9.hours)
+ user.reload
+ assert_not_predicate user, :deletion_allowed?
+ assert_equal Time.now.utc + 1.hour, user.deletion_allowed_at
+ end
+ end
+
+ def test_deletion_allowed_past_zero_delay
+ with_user_account_deletion_delay(0) do
+ user = create(:user)
+ create(:changeset, :user => user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
+ travel 90.minutes do
+ user.reload
+ assert_predicate user, :deletion_allowed?
+ end
+ end
+ end
+
+ def test_deletion_allowed_during_zero_delay
+ with_user_account_deletion_delay(0) do
+ user = create(:user)
+ create(:changeset, :user => user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
+ travel 30.minutes do
+ user.reload
+ assert_not_predicate user, :deletion_allowed?
+ assert_equal Time.now.utc + 30.minutes, user.deletion_allowed_at
+ end
+ end
+ end
end
assert_content "Account Deleted"
end
+
+ test "can delete with any delay setting value if the user has no changesets" do
+ with_user_account_deletion_delay(10000) do
+ travel 1.hour do
+ visit edit_account_path
+
+ click_link "Delete Account..."
+
+ assert_no_content "cannot currently be deleted"
+ end
+ end
+ end
+
+ test "can delete with delay disabled" do
+ with_user_account_deletion_delay(nil) do
+ create(:changeset, :user => @user)
+
+ travel 1.hour do
+ visit edit_account_path
+
+ click_link "Delete Account..."
+
+ assert_no_content "cannot currently be deleted"
+ end
+ end
+ end
+
+ test "can delete when last changeset is old enough" do
+ with_user_account_deletion_delay(10) do
+ create(:changeset, :user => @user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
+
+ travel 12.hours do
+ visit edit_account_path
+
+ click_link "Delete Account..."
+
+ assert_no_content "cannot currently be deleted"
+ end
+ end
+ end
+
+ test "can't delete when last changeset isn't old enough" do
+ with_user_account_deletion_delay(10) do
+ create(:changeset, :user => @user, :created_at => Time.now.utc, :closed_at => Time.now.utc + 1.hour)
+
+ travel 10.hours do
+ visit edit_account_path
+
+ click_link "Delete Account..."
+
+ assert_content "cannot currently be deleted"
+ assert_content "in about 1 hour"
+ end
+ end
+ end
end
el << tag_el
end
end
+
+ def with_user_account_deletion_delay(value)
+ freeze_time
+ default_value = Settings.user_account_deletion_delay
+ Settings.user_account_deletion_delay = value
+
+ yield
+
+ Settings.user_account_deletion_delay = default_value
+ unfreeze_time
+ end
end
end