Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AO3-6672 Allow some admins to set a generic username #4994

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ class UsersController < ApplicationController

before_action :check_user_status, only: [:edit, :update, :change_username, :changed_username]
before_action :load_user, except: [:activate, :delete_confirmation, :index]
before_action :check_ownership, except: [:activate, :delete_confirmation, :edit, :index, :show, :update]
before_action :check_ownership_or_admin, only: [:edit, :update]
before_action :check_ownership, except: [:activate, :change_username, :changed_username, :delete_confirmation, :edit, :index, :show, :update]
before_action :check_ownership_or_admin, only: [:change_username, :changed_username, :edit, :update]
skip_before_action :store_location, only: [:end_first_login]

def load_user
Expand Down Expand Up @@ -48,6 +48,7 @@ def change_password
end

def change_username
authorize @user if logged_in_as_admin?
@page_subtitle = t(".browser_title")
end

Expand All @@ -70,20 +71,27 @@ def changed_password
end

def changed_username
render(:change_username) && return unless params[:new_login].present?
authorize @user if logged_in_as_admin?
render(:change_username) && return if params[:new_login].blank?

@new_login = params[:new_login]

unless @user.valid_password?(params[:password])
flash[:error] = ts('Your password was incorrect')
unless logged_in_as_admin? || @user.valid_password?(params[:password])
flash[:error] = t(".user.incorrect_password")
render(:change_username) && return
end

@user.login = @new_login
@user.ticket_number = params[:ticket_id]

if @user.save
flash[:notice] = ts('Your user name has been successfully updated.')
redirect_to @user
if logged_in_as_admin?
flash[:notice] = t(".admin.successfully_updated")
redirect_to admin_user_path(@user)
else
flash[:notice] = t(".user.successfully_updated")
redirect_to @user
end
else
@user.reload
render :change_username
Expand Down
6 changes: 3 additions & 3 deletions app/models/concerns/justifiable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ module Justifiable
validates :ticket_number,
presence: true,
numericality: { only_integer: true },
if: :enabled?
if: :justification_enabled?

validate :ticket_number_exists_in_tracker, if: :enabled?
validate :ticket_number_exists_in_tracker, if: :justification_enabled?
end

private

def enabled?
def justification_enabled?
# Only require a ticket if the record has been changed by an admin.
User.current_user.is_a?(Admin) && changed?
end
Expand Down
32 changes: 31 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class User < ApplicationRecord
audited redacted: [:encrypted_password, :password_salt]
include Justifiable
include WorksOwner
include PasswordResetsLimitable
include UserLoggable
Expand Down Expand Up @@ -198,6 +199,7 @@ def unread_inbox_comments_count
uniqueness: true,
not_forbidden_name: { if: :will_save_change_to_login? }
validate :username_is_not_recently_changed, if: :will_save_change_to_login?
validate :admin_username_generic, if: :will_save_change_to_login?

# allow nil so can save existing users
validates_length_of :password,
Expand Down Expand Up @@ -525,6 +527,12 @@ def reindex_user_creations

private

# Override the default Justifiable enabled check, because we only need to justify
# username changes at the moment.
def justification_enabled?
User.current_user.is_a?(Admin) && login_changed?
end

# Create and/or return a user account for holding orphaned works
def self.fetch_orphan_account
orphan_account = User.find_or_create_by(login: "orphan_account")
Expand Down Expand Up @@ -564,11 +572,25 @@ def reindex_user_creations_after_rename
end

def add_renamed_at
return if User.current_user.is_a?(Admin)

self.renamed_at = Time.current
end

def log_change_if_login_was_edited
create_log_item(action: ArchiveConfig.ACTION_RENAME, note: "Old Username: #{login_before_last_save}; New Username: #{login}") if saved_change_to_login?
return unless saved_change_to_login?

current_admin = User.current_user if User.current_user.is_a?(Admin)
options = {
action: ArchiveConfig.ACTION_RENAME,
admin: current_admin
}
options[:note] = if current_admin
"Old Username: #{login_before_last_save}, New Username: #{login}, Changed by: #{User.current_user&.login}, Ticket ID: ##{ticket_number}"
else
"Old Username: #{login_before_last_save}; New Username: #{login}"
end
create_log_item(options)
end

def send_wrangler_username_change_notification
Expand All @@ -592,6 +614,8 @@ def remove_stale_from_autocomplete
end

def username_is_not_recently_changed
return if User.current_user.is_a?(Admin)

change_interval_days = ArchiveConfig.USER_RENAME_LIMIT_DAYS
return unless renamed_at && change_interval_days.days.ago <= renamed_at

Expand All @@ -601,6 +625,12 @@ def username_is_not_recently_changed
renamed_at: I18n.l(renamed_at, format: :long))
end

def admin_username_generic
return unless User.current_user.is_a?(Admin)

errors.add(:login, :admin_must_use_default) unless login == "user#{id}"
end

# Extra callback to make sure readings are deleted in an order consistent
# with the ReadingsJob.
#
Expand Down
12 changes: 10 additions & 2 deletions app/policies/user_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ class UserPolicy < ApplicationPolicy
# This is further restricted using ALLOWED_ATTRIBUTES_BY_ROLES.
MANAGE_ROLES = %w[superadmin legal policy_and_abuse open_doors support tag_wrangling].freeze

# Roles that are allowed to set a generic username for users.
CHANGE_USERNAME_ROLES = %w[superadmin policy_and_abuse].freeze

# Roles that allow updating the Fannish Next Of Kin of a user.
MANAGE_NEXT_OF_KIN_ROLES = %w[superadmin policy_and_abuse support].freeze

Expand All @@ -18,8 +21,8 @@ class UserPolicy < ApplicationPolicy
# Define which roles can update which attributes.
ALLOWED_ATTRIBUTES_BY_ROLES = {
"open_doors" => [roles: []],
"policy_and_abuse" => [:email, roles: []],
"superadmin" => [:email, roles: []],
"policy_and_abuse" => [:email, { roles: [] }],
"superadmin" => [:email, { roles: [] }],
"support" => %i[email],
"tag_wrangling" => [roles: []]
}.freeze
Expand Down Expand Up @@ -48,6 +51,10 @@ def can_access_creation_summary?
user_has_roles?(REVIEW_CREATIONS_ROLES)
end

def change_username?
user_has_roles?(CHANGE_USERNAME_ROLES)
end

def permitted_attributes
ALLOWED_ATTRIBUTES_BY_ROLES.values_at(*user.roles).compact.flatten
end
Expand All @@ -60,6 +67,7 @@ def can_edit_user_role?(role)
alias bulk_search? can_manage_users?
alias show? can_manage_users?
alias update? can_manage_users?
alias changed_username? change_username?

alias update_next_of_kin? can_manage_next_of_kin?

Expand Down
3 changes: 3 additions & 0 deletions app/views/admin/admin_users/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<li>
<%= button_to t(".navigation.activate"), { action: "activate", id: @user }, disabled: @user.active? %>
</li>
<li>
<%= link_to t(".navigation.rename"), change_username_user_path(@user) %>
</li>
<li>
<%= link_to t(".navigation.roles"), { action: "index", user_id: @user.id } %>
</li>
Expand Down
27 changes: 27 additions & 0 deletions app/views/users/_admin_change_username.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<h2 class="heading"><%= t(".page_heading") %></h2>
<%= error_messages_for :user %>
<p class="note"><%= t(".description") %></p>

<ul class="navigation actions" role="navigation">
<li><%= link_to t(".navigation.manage_user"), admin_user_path(@user) %></li>
</ul>

<%= form_tag changed_username_user_path(@user) do %>
<dl>
<dt><%= t("users.change_username.current_user_name") %></dt>
<dd><p class="important informational"><%= @user.login %></p></dd>
<dt><%= label_tag :new_login, t("users.change_username.new_user_name") %></dt>
<dd>
<%= text_field_tag :new_login, "user#{@user.id}", readonly: true, autocomplete: "off" %>
<p class="footnote" id="new-login-field-description">
<%= t(".new_username_footnote") %>
</p>
</dd>
<dt><%= label_tag :ticket_id, t(".ticket_id") %></dt>
<dd><%= text_field_tag :ticket_id, nil, autocomplete: "disabled" %></dd>
<dt class="landmark"><%= t("users.change_username.submit_landmark") %></dt>
<dd class="submit actions">
<%= submit_tag t("users.change_username.change_user_name") %>
</dd>
</dl>
<% end %>
93 changes: 48 additions & 45 deletions app/views/users/change_username.html.erb
Original file line number Diff line number Diff line change
@@ -1,50 +1,53 @@
<!--Descriptive page name, messages and instructions-->
<h2 class="heading"><%= ts("Change My User Name") %></h2>
<%= error_messages_for :user %>
<div class="caution">
<p>
<strong><%= t(".caution") %></strong>
<%= t(".change_window", count: ArchiveConfig.USER_RENAME_LIMIT_DAYS) %>
<% if @user.renamed_at %>
<%= t(".last_renamed", renamed_at: l(@user.renamed_at, format: :long)) %>
<% end %>
<% if policy(@user).change_username? %>
<%= render "admin_change_username" %>
<% else %>
<!--Descriptive page name, messages and instructions-->
<h2 class="heading"><%= t(".page_heading") %></h2>
<%= error_messages_for :user %>
<div class="caution">
<p>
<strong><%= t(".caution") %></strong>
<%= t(".change_window", count: ArchiveConfig.USER_RENAME_LIMIT_DAYS) %>
<% if @user.renamed_at %>
<%= t(".last_renamed", renamed_at: l(@user.renamed_at, format: :long)) %>
<% end %>
</p>
<p>
<%= t(".more_info_html",
account_faq_link: link_to(t(".account_faq"), archive_faq_path("your-account", anchor: "namechange")),
contact_support_link: link_to(t(".contact_support"), new_feedback_report_path)) %>
</p>
</div>
<p class="note">
<%= t(".new_pseud_instead_html", create_a_new_pseud_link: link_to(t(".create_a_new_pseud"), new_user_pseud_path(@user))) %>
</p>
<p>
<%= t(".more_info",
account_faq_link: (link_to t(".account_faq"), archive_faq_path("your-account", anchor: "namechange")),
contact_support_link: (link_to t(".contact_support"), new_feedback_report_path)
).html_safe %>
</p>
</div>
<p class="note">
<%= ts("If that is not what you want, you can <a href=\"#{new_user_pseud_path(@user)}\">create a new Pseud</a> instead.").html_safe %>
</p>
<!--/descriptions-->
<!--/descriptions-->

<!--subnav-->
<%= render "edit_header_navigation" %>
<!--/subnav-->
<!--subnav-->
<%= render "edit_header_navigation" %>
<!--/subnav-->

<!--main content-->
<!--main content-->

<%= form_tag changed_username_user_path(@user) do %>
<dl>
<dt><%= ts("Current user name") %></dt>
<dd><p class="important informational"><%= @user.login %></p></dd>
<dt><%= label_tag :new_login, ts("New user name") %></dt>
<dd>
<%= text_field_tag :new_login, @new_login, autocomplete: "off", "aria-describedby" => "new-login-field-description" %>
<p class="footnote" id="new-login-field-description">
<%= ts("%{minimum} to %{maximum} characters (A-Z, a-z, _, 0-9 only), no spaces, cannot begin or end with underscore (_)",
minimum: ArchiveConfig.LOGIN_LENGTH_MIN,
maximum: ArchiveConfig.LOGIN_LENGTH_MAX) %>
</p>
</dd>
<dt><%= label_tag :password, ts("Password") %></dt>
<dd><%= password_field_tag :password %></dd>
<dt class="landmark"><%= ts("Submit") %></dt>
<dd class="submit actions">
<%= submit_tag ts("Change User Name"), data: { confirm: ts("Are you sure you want to change your user name?") } %>
</dd>
</dl>
<%= form_tag changed_username_user_path(@user) do %>
<dl>
<dt><%= t(".current_user_name") %></dt>
<dd><p class="important informational"><%= @user.login %></p></dd>
<dt><%= label_tag :new_login, t(".new_user_name") %></dt>
<dd>
<%= text_field_tag :new_login, @new_login, autocomplete: "off", "aria-describedby": "new-login-field-description" %>
<p class="footnote" id="new-login-field-description">
<%= t(".username_requirements",
minimum: ArchiveConfig.LOGIN_LENGTH_MIN,
maximum: ArchiveConfig.LOGIN_LENGTH_MAX) %>
</p>
</dd>
<dt><%= label_tag :password, t(".password") %></dt>
<dd><%= password_field_tag :password, nil, autocomplete: "disabled" %></dd>
<dt class="landmark"><%= t(".submit_landmark") %></dt>
<dd class="submit actions">
<%= submit_tag t(".change_user_name"), data: { confirm: t(".confirm") } %>
</dd>
</dl>
<% end %>
<% end %>
6 changes: 6 additions & 0 deletions config/locales/controllers/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ en:
index:
page_subtitle: fandoms
users:
changed_username:
admin:
successfully_updated: User name has been successfully updated.
user:
incorrect_password: Your password was incorrect
successfully_updated: Your user name has been successfully updated.
contact_abuse: contact Policy & Abuse
passwords:
create:
Expand Down
1 change: 1 addition & 0 deletions config/locales/models/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ en:
accepted: Sorry, you need to consent to the processing of your personal data in order to sign up.
format: "%{message}"
login:
admin_must_use_default: must use the default, please contact your chairs to use something else.
changed_too_recently:
one: can only be changed once per day. You last changed your user name on %{renamed_at}.
other: can only be changed once every %{count} days. You last changed your user name on %{renamed_at}.
Expand Down
20 changes: 19 additions & 1 deletion config/locales/views/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ en:
invitations:
add: Add Invitations
manage: Manage Invitations
rename: Rename
roles: Manage Roles
troubleshoot: Troubleshoot
note: To fix common errors with this user's Subscriptions and Stats pages, and to reindex their works and bookmarks, choose "Troubleshoot."
Expand Down Expand Up @@ -1697,6 +1698,13 @@ en:
description: Recalculate the filters for this work based on the current set of tags. Use this option if wrangling seems to have gone wrong. (e.g. If you synned one of this work's tags to a canonical but it's not showing up in the tag listings, or if the wrong tags are listed in the sidebar when you drill down to a search that includes only this work.)
title: Update Work Filters
users:
admin_change_username:
description: Change the name of this user to a generic name in the format user + their account ID.
navigation:
manage_user: Manage User
new_username_footnote: This username cannot be edited. If you need to change it to a different name, contact your chair.
page_heading: Change Username
ticket_id: Ticket ID
change_email:
browser_title: Change Email
change_password:
Expand All @@ -1705,12 +1713,22 @@ en:
account_faq: Account FAQ
browser_title: Change Username
caution: Please use this feature with caution.
change_user_name: Change User Name
change_window:
one: You can change your user name once per day.
other: You can change your user name once every %{count} days.
confirm: Are you sure you want to change your user name?
contact_support: contact Support
create_a_new_pseud: create a new Pseud
current_user_name: Current user name
last_renamed: You last changed your user name on %{renamed_at}.
more_info: For information on how changing your user name will affect your account, please check out the %{account_faq_link}. Note that changes to your user name may take several days or longer to appear. If you are still seeing your old user name on your works, bookmarks, series, or collections after a week, please %{contact_support_link}.
more_info_html: For information on how changing your user name will affect your account, please check out the %{account_faq_link}. Note that changes to your user name may take several days or longer to appear. If you are still seeing your old user name on your works, bookmarks, series, or collections after a week, please %{contact_support_link}.
new_pseud_instead_html: If that is not what you want, you can %{create_a_new_pseud_link} instead.
new_user_name: New user name
page_heading: Change My User Name
password: Password
submit_landmark: Submit
username_requirements: "%{minimum} to %{maximum} characters (A-Z, a-z, _, 0-9 only), no spaces, cannot begin or end with underscore (_)"
confirmation:
contact_support: contact Support
go_back: Return to Archive front page
Expand Down
Loading
Loading