Skip to content

Commit 385f48a

Browse files
committed
Add a button for admins to dangerously destroy users, and move user-settings path to be under participants
1 parent c2416e4 commit 385f48a

File tree

6 files changed

+219
-1
lines changed

6 files changed

+219
-1
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# frozen_string_literal: true
2+
3+
class SettingsController < ApplicationController
4+
skip_authorization_check
5+
6+
before_action :set_user
7+
8+
def show
9+
authorize! :read, @user
10+
end
11+
12+
def update
13+
authorize! :update, @user
14+
set_email
15+
password_changed = maybe_update_password(@user, params[:user])
16+
user_field_changes = set_user_fields
17+
18+
begin
19+
log_user_field_changes(user_field_changes)
20+
rescue StandardError
21+
end
22+
23+
if @user.errors.empty? && @user.save
24+
flash[:notice] = if password_changed
25+
'Changes saved and password changed'
26+
else
27+
'Changes saved'
28+
end
29+
redirect_to participant_settings_path(@user)
30+
else
31+
flash.now[:error] = 'Failed to save profile'
32+
render action: :show, status: :forbidden
33+
end
34+
end
35+
36+
def dangerously_destroy_user
37+
im_sure = params[:im_sure]
38+
if im_sure != '1'
39+
redirect_to verify_dangerously_destroying_user_participant_settings_url, notice: 'Please check the checkbox after you have read the instructions.'
40+
return
41+
end
42+
@user = authenticate_current_user_destroy
43+
username = @user.login
44+
sign_out if current_user == @user
45+
email = @user.email
46+
username = @user.login
47+
@user.destroy
48+
RecentlyChangedUserDetail.deleted.create!(old_value: false, new_value: true, email: email, username: username)
49+
redirect_to root_url, notice: "The account #{username} (#{email}) has been permanently destroyed."
50+
end
51+
52+
def verify_dangerously_destroying_user
53+
@user = authenticate_current_user_destroy
54+
end
55+
56+
private
57+
58+
def authenticate_current_user_destroy
59+
user = User.find(params[:participant_id])
60+
authorize! :destroy, user
61+
user
62+
end
63+
64+
def set_user
65+
@user = User.find(params[:participant_id])
66+
end
67+
68+
def set_email
69+
user_params = params[:user]
70+
71+
return if !@user.new_record? && user_params[:email_repeat].blank?
72+
73+
if user_params[:email].blank?
74+
@user.errors.add(:email, 'needed')
75+
elsif user_params[:email] != user_params[:email_repeat]
76+
@user.errors.add(:email_repeat, 'did not match')
77+
else
78+
@user.email = user_params[:email].strip
79+
end
80+
end
81+
82+
def maybe_update_password(user, user_params)
83+
if user_params[:old_password].present? || user_params[:password].present?
84+
if !user.has_password?(user_params[:old_password])
85+
user.errors.add(:old_password, 'incorrect')
86+
elsif user_params[:password] != user_params[:password_repeat]
87+
user.errors.add(:password_repeat, 'did not match')
88+
elsif user_params[:password].blank?
89+
user.errors.add(:password, 'cannot be empty')
90+
else
91+
user.password = user_params[:password]
92+
end
93+
true
94+
else
95+
false
96+
end
97+
end
98+
99+
def set_user_fields
100+
return if params[:user_field].nil?
101+
changes = {}
102+
UserField.all.select { |f| f.visible_to?(current_user) }.each do |field|
103+
value_record = @user.field_value_record(field)
104+
old_value = value_record.ruby_value
105+
value_record.set_from_form(params[:user_field][field.name])
106+
new_value = value_record.ruby_value
107+
changes[field.name] = { from: old_value, to: new_value } unless new_value == old_value
108+
end
109+
changes
110+
end
111+
end

app/views/participants/_nav.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<nav class="nav nav-tabs nav-justified">
22
<%= active_on_current_path_link_to fa_icon("user lg", text: 'Overview'), participant_path(@user), class: 'nav-item nav-link' %>
33
<%= active_on_current_path_link_to fa_icon("certificate lg", text: 'Certificates'), participant_certificates_path(@user), class: 'nav-item nav-link' %>
4-
<%= active_on_current_path_link_to fa_icon("cog lg", text: 'Settings'), user_path, class: 'nav-item nav-link' %>
4+
<%= active_on_current_path_link_to fa_icon("cog lg", text: 'Settings'), participant_settings_path(@user), class: 'nav-item nav-link' %>
55
</nav>
66

77
<br>

app/views/settings/_form.html.erb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<%= form_for @user, url: { :action => :update, :class => "form-horizontal" }, autocomplete: "off" do |f| %>
2+
<%= render 'shared/error_messages', :target => @user %>
3+
4+
<section>
5+
<h2>User account</h2>
6+
<% unless local_assigns[:hide_login] %>
7+
<div class="alert alert-info" role="alert">
8+
Your <i>username</i> will be visible to everyone for example in public scoreboards. <br>
9+
Therefore it should <b>not</b> contain your student number or your
10+
email address.
11+
</div>
12+
<%= bs_labeled_field('Username', f.text_field(:login, :disabled => !@user.new_record?, class: 'form-control')) %>
13+
<% end%>
14+
<%= bs_labeled_field('E-mail:', f.text_field(:email, class: 'form-control')) %>
15+
<%= bs_labeled_field('Confirm e-mail:', f.text_field(:email_repeat, :value => '', class: 'form-control stop-autofilling', autocomplete: 'off', type: 'email')) %>
16+
</section>
17+
18+
<% if @user.new_record? %>
19+
<section>
20+
<h2>Password</h2>
21+
<%= bs_labeled_field('Password:', f.password_field(:password, :value => '', :autocomplete => 'off', class: 'form-control')) %>
22+
<%= bs_labeled_field('Confirm password:', f.password_field(:password_repeat, :value => '', :autocomplete => 'off', class: 'form-control')) %>
23+
</section>
24+
<% else %>
25+
<section>
26+
<h2>Change password</h2>
27+
<%= bs_labeled_field('Old password:', f.password_field(:old_password, :value => '', class: 'form-control stop-autofilling', autocomplete: 'off')) %>
28+
<%= bs_labeled_field('New password:', f.password_field(:password, :value => '', class: 'form-control'), autocomplete: 'off') %>
29+
<%= bs_labeled_field('Confirm new password:', f.password_field(:password_repeat, :value => '', class: 'form-control', autocomplete: 'off')) %>
30+
</section>
31+
<% end %>
32+
33+
<% if !UserField.all.empty? %>
34+
<section>
35+
<% for group in UserField.groups %>
36+
<% if UserField.by_group(group).any? {|field| field.visible_to?(@user) } %>
37+
<h2><%= group %></h2>
38+
<% for field in UserField.by_group(group) %>
39+
<% if field.visible_to?(@user) %>
40+
<%= extra_field(@user.field_value_record(field), :form_scope => 'user_field') %>
41+
<% end %>
42+
<% end %>
43+
<% end %>
44+
<% end %>
45+
</section>
46+
<% end %>
47+
48+
<section>
49+
<% if @user.new_record? %>
50+
<%= f.submit "Sign up", class: "btn btn-primary" %>
51+
<% else %>
52+
<%= f.submit "Save", class: "btn btn-primary" %>
53+
<% end %>
54+
</section>
55+
<% end %>
56+
<br>
57+
58+
59+
<script>
60+
setTimeout(function() {
61+
document.querySelectorAll('.stop-autofilling').forEach(function(e) {
62+
e.value = ""
63+
})
64+
}, 500)
65+
</script>

app/views/settings/show.html.erb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<%= render partial: 'participants/nav' %>
2+
3+
<%= render :partial => 'settings/form', :user => @user, locals: { hide_login: true } %>
4+
5+
<% if can? :destroy, @user %>
6+
<%= button_to 'Request deleting account', send_destroy_email_path(@user.id), method: :post, class: 'btn btn-danger' %>
7+
<% end %>
8+
9+
<br>
10+
11+
<% if current_user.administrator? %>
12+
<%= button_to 'Dangerously destroy account', dangerously_destroy_user_participant_settings_path(@user.id), method: :post, class: 'btn btn-danger' %>
13+
<% end %>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<h1>Confirm deleting account: <%= @user.username %> (<%= @user.email %>)</h1>
2+
3+
<div class="jumbotron hero-unit-alert alert-danger">
4+
Before deleting this user, make sure they don't have any submissions. If they have submissions, advice them to delete their own account by pressing "Request deleting account" -button themselves from their User settings.
5+
</div>
6+
7+
8+
<%= form_tag(dangerously_destroy_user_participant_settings_path, method: :delete, class: 'form-horizontal') do %>
9+
<div class="form-group">
10+
<%= check_box_tag :im_sure %>
11+
<%= label_tag "I've read the above and I'm sure I understand the consequences", nil, class: "control-label", for: :im_sure %>
12+
</div>
13+
<p><strong>Pressing the button below will permanently destroy this mooc.fi account.</strong></p>
14+
<%= submit_tag("Destroy this account permanently", class: "btn btn-danger", data: { confirm: "Are you sure you want to delete the account #{@user.login}?"}, disabled: "disabled", id: :destroy_button) %>
15+
<% end %>
16+
17+
18+
<script>
19+
var checkBox = document.getElementById('im_sure')
20+
var destroyButton = document.getElementById('destroy_button')
21+
checkBox.addEventListener("click", function() {
22+
destroyButton.disabled = !checkBox.checked
23+
})
24+
</script>

config/routes.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@
236236
resource :user
237237

238238
resources :participants do
239+
resource :settings, only: [:show, :update] do
240+
post 'dangerously_destroy_user', to: 'settings#dangerously_destroy_user'
241+
get 'verify_dangerously_destroying_user', to: 'settings#verify_dangerously_destroying_user'
242+
delete 'dangerously_destroy_user', to: 'settings#dangerously_destroy_user'
243+
end
239244
resources :certificates, only: [:index]
240245
collection do
241246
get 'me', to: 'participants#me'

0 commit comments

Comments
 (0)