diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1eced5531a..275c6ee656 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -269,13 +269,17 @@ def access_denied(options ={})
def admin_only_access_denied
respond_to do |format|
format.html do
- flash[:error] = ts("Sorry, only an authorized admin can access the page you were trying to reach.")
+ flash[:error] = t("admin.access.page_access_denied")
redirect_to root_path
end
format.json do
- errors = [ts("Sorry, only an authorized admin can do that.")]
+ errors = [t("admin.access.action_access_denied")]
render json: { errors: errors }, status: :forbidden
end
+ format.js do
+ flash[:error] = t("admin.access.page_access_denied")
+ render js: "window.location.href = '#{root_path}';"
+ end
end
end
diff --git a/app/controllers/inbox_controller.rb b/app/controllers/inbox_controller.rb
index fb7a710f7e..4191691060 100644
--- a/app/controllers/inbox_controller.rb
+++ b/app/controllers/inbox_controller.rb
@@ -2,7 +2,7 @@ class InboxController < ApplicationController
include BlockHelper
before_action :load_user
- before_action :check_ownership
+ before_action :check_ownership_or_admin
before_action :load_commentable, only: :reply
before_action :check_blocked, only: :reply
@@ -13,6 +13,7 @@ def load_user
end
def show
+ authorize InboxComment if logged_in_as_admin?
@inbox_total = @user.inbox_comments.with_bad_comments_removed.count
@unread = @user.inbox_comments.with_bad_comments_removed.count_unread
@filters = filter_params[:filters] || {}
@@ -30,6 +31,7 @@ def reply
end
def update
+ authorize InboxComment if logged_in_as_admin?
begin
@inbox_comments = InboxComment.find(params[:inbox_comments])
if params[:read]
diff --git a/app/policies/inbox_comment_policy.rb b/app/policies/inbox_comment_policy.rb
new file mode 100644
index 0000000000..8ec7d755b3
--- /dev/null
+++ b/app/policies/inbox_comment_policy.rb
@@ -0,0 +1,7 @@
+class InboxCommentPolicy < ApplicationPolicy
+ VIEW_INBOX_ROLES = %w[superadmin policy_and_abuse].freeze
+
+ def show?
+ user_has_roles?(VIEW_INBOX_ROLES)
+ end
+end
diff --git a/app/views/users/_sidebar.html.erb b/app/views/users/_sidebar.html.erb
index 429c8ebcc9..a6a8a0295d 100644
--- a/app/views/users/_sidebar.html.erb
+++ b/app/views/users/_sidebar.html.erb
@@ -1,30 +1,30 @@
" role="navigation region">
-
<%= ts("Choices")%>
+
<%= t(".landmark.choices") %>
- - <%= span_if_current ts("Dashboard"), @user %>
- - <%= span_if_current ts("Profile"), user_profile_path(@user) %>
+ - <%= span_if_current t(".choices.dashboard"), @user %>
+ - <%= span_if_current t(".choices.profile"), user_profile_path(@user) %>
<% if @user.pseuds.size > 1 %>
-
- "><%= ts("Pseuds") %>
+ "><%= t(".choices.pseuds") %>
<%= pseud_selector(pseuds_for_sidebar(@user, @pseud)) %>
- - <%= span_if_current ts("All Pseuds (%{pseud_number})", :pseud_number => @user.pseuds.count), user_pseuds_path(@user) %>
+ - <%= span_if_current t(".choices.all_pseuds", pseud_number: @user.pseuds.count), user_pseuds_path(@user) %>
<% end %>
<% if @user == current_user %>
- - <%= span_if_current ts("Preferences"), user_preferences_path(@user) %>
- - <%= span_if_current ts("Skins"), user_skins_path(@user) %>
+ - <%= span_if_current t(".choices.preferences"), user_preferences_path(@user) %>
+ - <%= span_if_current t(".choices.skins"), user_skins_path(@user) %>
<% end %>
-
<%= ts("Pitch")%>
+
<%= t(".landmark.pitch") %>
- <%= works_link(@user, @pseud) %>
<% if @user == current_user || logged_in_as_admin? %>
- - <%= span_if_current ts("Drafts") + " (#{@user.unposted_works.size})", drafts_user_works_path(@user) %>
+ - <%= span_if_current t(".pitch.drafts", drafts_number: @user.unposted_works.size), drafts_user_works_path(@user) %>
<% end %>
- <%= series_link(@user, @pseud) %>
@@ -32,31 +32,33 @@
- <%= bookmarks_link(@user, @pseud) %>
<% end %>
- - <%= span_if_current ts("Collections (%{coll_number})", :coll_number => @user.maintained_collections.count), user_collections_path(@user) %>
+ - <%= span_if_current t(".pitch.collections", coll_number: @user.maintained_collections.count), user_collections_path(@user) %>
-<% if @user == current_user %>
-
<%= ts("Catch")%>
+<% if @user == current_user || policy(:inbox_comment).show? %>
+
<%= t(".landmark.catch") %>
- - <%= span_if_current ts("Inbox (%{inbox_number})", :inbox_number => @user.unread_inbox_comments_count.to_s), user_inbox_path(@user) %>
- - <%= span_if_current ts("Statistics"), user_stats_path(@user) %>
- <% if @user.preference.try(:history_enabled?) %>
- - <%= span_if_current ts("History"), user_readings_path(@user) %>
+ - <%= span_if_current t(".catch.inbox", inbox_number: @user.unread_inbox_comments_count.to_s), user_inbox_path(@user) %>
+ <% if @user == current_user %>
+ - <%= span_if_current t(".catch.statistics"), user_stats_path(@user) %>
+ <% if @user.preference.try(:history_enabled?) %>
+ - <%= span_if_current t(".catch.history"), user_readings_path(@user) %>
+ <% end %>
+ - <%= span_if_current t(".catch.subscriptions"), user_subscriptions_path(@user) %>
<% end %>
- - <%= span_if_current ts("Subscriptions"), user_subscriptions_path(@user) %>
<% end %>
-
<%= ts("Switch")%>
+
<%= t(".landmark.switch") %>
<% if @user == current_user %>
<% if @user.preference.allow_cocreator %>
- - <%= span_if_current ts("Co-Creator Requests (%{count})", count: Creatorship.unapproved.for_user(@user).count), user_creatorships_path(@user) %>
+ - <%= span_if_current t(".switch.co_creator_requests", count: Creatorship.unapproved.for_user(@user).count), user_creatorships_path(@user) %>
<% end %>
- - <%= span_if_current ts("Sign-ups (%{signup_number})", :signup_number =>@user.challenge_signups.count), user_signups_path(@user) %>
- - <%= span_if_current ts("Assignments (%{assignment_number})", :assignment_number => (@user.offer_assignments.undefaulted.count + @user.pinch_hit_assignments.undefaulted.count)), user_assignments_path(@user) %>
- - <%= span_if_current ts("Claims (%{claim_number})", :claim_number => (@user.request_claims.unposted.count)), user_claims_path(@user) %>
- - <%= span_if_current ts("Related Works (%{related_works_number})", :related_works_number => (@user.related_works.posted.count + @user.parent_work_relationships.count)), user_related_works_path(@user) %>
+ - <%= span_if_current t(".switch.sign_ups", signup_number: @user.challenge_signups.count), user_signups_path(@user) %>
+ - <%= span_if_current t(".switch.assignments", assignment_number: (@user.offer_assignments.undefaulted.count + @user.pinch_hit_assignments.undefaulted.count)), user_assignments_path(@user) %>
+ - <%= span_if_current t(".switch.claims", claim_number: @user.request_claims.unposted.count), user_claims_path(@user) %>
+ - <%= span_if_current t(".switch.related_works", related_works_number: (@user.related_works.posted.count + @user.parent_work_relationships.count)), user_related_works_path(@user) %>
<% end %>
- <%= gifts_link(@user) %>
diff --git a/config/locales/controllers/en.yml b/config/locales/controllers/en.yml
index a79a3898f1..c230736aea 100644
--- a/config/locales/controllers/en.yml
+++ b/config/locales/controllers/en.yml
@@ -1,6 +1,9 @@
---
en:
admin:
+ access:
+ action_access_denied: Sorry, only an authorized admin can do that.
+ page_access_denied: Sorry, only an authorized admin can access the page you were trying to reach.
admin_invitations:
find:
user_not_found: No results were found. Try another search.
diff --git a/config/locales/views/en.yml b/config/locales/views/en.yml
index 6dd5765b1b..bea8faef48 100644
--- a/config/locales/views/en.yml
+++ b/config/locales/views/en.yml
@@ -1788,6 +1788,34 @@ en:
privacy_policy: Privacy Policy
tos: Terms of Service
welcome_html: Hi! It looks like you've just logged in to AO3 for the first time. For help getting started on AO3, check out some %{new_user_tips_link} or browse through %{our_faqs_link}.
+ sidebar:
+ catch:
+ history: History
+ inbox: Inbox (%{inbox_number})
+ statistics: Statistics
+ subscriptions: Subscriptions
+ choices:
+ all_pseuds: All Pseuds (%{pseud_number})
+ dashboard: Dashboard
+ preferences: Preferences
+ profile: Profile
+ pseud_switcher: Pseud Switcher
+ pseuds: Pseuds
+ skins: Skins
+ landmark:
+ catch: Catch
+ choices: Choices
+ pitch: Pitch
+ switch: Switch
+ pitch:
+ collections: Collections (%{coll_number})
+ drafts: Drafts (%{drafts_number})
+ switch:
+ assignments: Assignments (%{assignment_number})
+ claims: Claims (%{claim_number})
+ co_creator_requests: Co-Creator Requests (%{count})
+ related_works: Related Works (%{related_works_number})
+ sign_ups: Sign-ups (%{signup_number})
works:
adult:
caution: This work could have adult content. If you continue, you have agreed that you are willing to see such content.
diff --git a/spec/controllers/inbox_controller_spec.rb b/spec/controllers/inbox_controller_spec.rb
index 21815b1bbd..9e29317db9 100644
--- a/spec/controllers/inbox_controller_spec.rb
+++ b/spec/controllers/inbox_controller_spec.rb
@@ -19,6 +19,111 @@
"Sorry, you don't have permission to access the page you were trying to reach.")
end
+ context "when logged in as an admin" do
+ context "when the admin does not have the correct authorization" do
+ context "when the admin has no role" do
+ let(:admin) { create(:admin, roles: []) }
+
+ before { fake_login_admin(admin) }
+
+ it "redirects with error" do
+ get :show, params: { user_id: user.login }
+
+ it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
+ end
+ end
+
+ (Admin::VALID_ROLES - %w[superadmin policy_and_abuse]).each do |role|
+ context "when the admin has the #{role} role" do
+ let(:admin) { create(:admin, roles: [role]) }
+
+ before { fake_login_admin(admin) }
+
+ it "redirects with error" do
+ get :show, params: { user_id: user.login }
+
+ it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
+ end
+ end
+ end
+ end
+
+ %w[superadmin policy_and_abuse].each do |role|
+ context "when the admin is authorized with the #{role} role" do
+ let(:admin) { create(:admin, roles: [role]) }
+
+ before { fake_login_admin(admin) }
+
+ it "renders the user inbox" do
+ get :show, params: { user_id: user.login }
+ expect(response).to render_template("show")
+ expect(assigns(:inbox_total)).to eq(0)
+ expect(assigns(:unread)).to eq(0)
+ end
+
+ context "with unread comments" do
+ let!(:inbox_comments) do
+ Array.new(3) do |i|
+ create(:inbox_comment, user: user, created_at: Time.now.utc + i.days)
+ end
+ end
+
+ it "renders non-zero unread count" do
+ get :show, params: { user_id: user.login }
+ expect(assigns(:inbox_comments)).to eq(inbox_comments.reverse)
+ expect(assigns(:inbox_total)).to eq(3)
+ expect(assigns(:unread)).to eq(3)
+ end
+
+ it "renders oldest first" do
+ get :show, params: { user_id: user.login, filters: { date: "asc" } }
+ expect(assigns(:filters)[:date]).to eq("asc")
+ expect(assigns(:inbox_comments)).to eq(inbox_comments)
+ expect(assigns(:inbox_total)).to eq(3)
+ expect(assigns(:unread)).to eq(3)
+ end
+ end
+
+ context "with 1 read and 1 unread" do
+ let!(:read_comment) { create(:inbox_comment, user: user, read: true) }
+ let!(:unread_comment) { create(:inbox_comment, user: user) }
+
+ it "renders only unread" do
+ get :show, params: { user_id: user.login, filters: { read: "false" } }
+ expect(assigns(:filters)[:read]).to eq("false")
+ expect(assigns(:inbox_comments)).to eq([unread_comment])
+ expect(assigns(:inbox_total)).to eq(2)
+ expect(assigns(:unread)).to eq(1)
+ end
+ end
+
+ context "with 1 replied and 1 unreplied" do
+ let!(:replied_comment) { create(:inbox_comment, user: user, replied_to: true) }
+ let!(:unreplied_comment) { create(:inbox_comment, user: user) }
+
+ it "renders only unreplied" do
+ get :show, params: { user_id: user.login, filters: { replied_to: "false" } }
+ expect(assigns(:filters)[:replied_to]).to eq("false")
+ expect(assigns(:inbox_comments)).to eq([unreplied_comment])
+ expect(assigns(:inbox_total)).to eq(2)
+ expect(assigns(:unread)).to eq(2)
+ end
+ end
+
+ context "with a deleted comment" do
+ let(:inbox_comment) { create(:inbox_comment, user: user) }
+
+ it "excludes deleted comments" do
+ inbox_comment.feedback_comment.destroy!
+ get :show, params: { user_id: user.login }
+ expect(assigns(:inbox_total)).to eq(0)
+ expect(assigns(:unread)).to eq(0)
+ end
+ end
+ end
+ end
+ end
+
context "when logged in as the same user" do
before { fake_login_known_user(user) }
@@ -82,7 +187,7 @@
let(:inbox_comment) { create(:inbox_comment, user: user) }
it "excludes deleted comments" do
- inbox_comment.feedback_comment.destroy
+ inbox_comment.feedback_comment.destroy!
get :show, params: { user_id: user.login }
expect(assigns(:inbox_total)).to eq(0)
expect(assigns(:unread)).to eq(0)
@@ -149,92 +254,107 @@
end
describe "PUT #update" do
- before { fake_login_known_user(user) }
-
- context "with no comments selected" do
- it "redirects to inbox with caution and a notice" do
- put :update, params: { user_id: user.login, read: "yeah" }
- it_redirects_to_with_caution_and_notice(user_inbox_path(user),
- "Please select something first",
- "Inbox successfully updated.")
- end
-
- it "redirects to the previously viewed page if HTTP_REFERER is set, with a caution and a notice" do
- @request.env['HTTP_REFERER'] = root_path
- put :update, params: { user_id: user.login, read: "yeah" }
- it_redirects_to_with_caution_and_notice(root_path,
- "Please select something first",
- "Inbox successfully updated.")
+ %w[superadmin policy_and_abuse].each do |role|
+ context "when logged in as an admin with the role #{role}" do
+ let(:admin) { create(:admin, roles: [role]) }
+
+ before { fake_login_admin(admin) }
+
+ it "redirects to root with error" do
+ put :update, params: { user_id: user.login }
+ it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
+ end
end
end
- context "with unread comments" do
- let!(:inbox_comment_1) { create(:inbox_comment, user: user) }
- let!(:inbox_comment_2) { create(:inbox_comment, user: user) }
-
- it "marks all as read and redirects to inbox with a notice" do
- parameters = {
- user_id: user.login,
- inbox_comments: [inbox_comment_1.id, inbox_comment_2.id],
- read: "yeah"
- }
+ context "when logged in as the comment receiver" do
+ before { fake_login_known_user(user) }
- put :update, params: parameters
- it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
+ context "with no comments selected" do
+ it "redirects to inbox with caution and a notice" do
+ put :update, params: { user_id: user.login, read: "yeah" }
+ it_redirects_to_with_caution_and_notice(user_inbox_path(user),
+ "Please select something first",
+ "Inbox successfully updated.")
+ end
- inbox_comment_1.reload
- expect(inbox_comment_1.read).to be_truthy
- inbox_comment_2.reload
- expect(inbox_comment_2.read).to be_truthy
+ it "redirects to the previously viewed page if HTTP_REFERER is set, with a caution and a notice" do
+ @request.env["HTTP_REFERER"] = root_path
+ put :update, params: { user_id: user.login, read: "yeah" }
+ it_redirects_to_with_caution_and_notice(root_path,
+ "Please select something first",
+ "Inbox successfully updated.")
+ end
end
- it "marks one as read and redirects to inbox with a notice" do
- put :update, params: { user_id: user.login, inbox_comments: [inbox_comment_1.id], read: "yeah" }
- it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
+ context "with unread comments" do
+ let!(:inbox_comment1) { create(:inbox_comment, user: user) }
+ let!(:inbox_comment2) { create(:inbox_comment, user: user) }
+
+ it "marks all as read and redirects to inbox with a notice" do
+ parameters = {
+ user_id: user.login,
+ inbox_comments: [inbox_comment1.id, inbox_comment2.id],
+ read: "yeah"
+ }
+
+ put :update, params: parameters
+ it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
+
+ inbox_comment1.reload
+ expect(inbox_comment1.read).to be_truthy
+ inbox_comment2.reload
+ expect(inbox_comment2.read).to be_truthy
+ end
- inbox_comment_1.reload
- expect(inbox_comment_1.read).to be_truthy
- inbox_comment_2.reload
- expect(inbox_comment_2.read).to be_falsy
- end
+ it "marks one as read and redirects to inbox with a notice" do
+ put :update, params: { user_id: user.login, inbox_comments: [inbox_comment1.id], read: "yeah" }
+ it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
- it "deletes one and redirects to inbox with a notice" do
- put :update, params: { user_id: user.login, inbox_comments: [inbox_comment_1.id], delete: "yeah" }
- it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
+ inbox_comment1.reload
+ expect(inbox_comment1.read).to be_truthy
+ inbox_comment2.reload
+ expect(inbox_comment2.read).to be_falsy
+ end
- expect(InboxComment.find_by(id: inbox_comment_1.id)).to be_nil
- inbox_comment_2.reload
- expect(inbox_comment_2.read).to be_falsy
+ it "deletes one and redirects to inbox with a notice" do
+ put :update, params: { user_id: user.login, inbox_comments: [inbox_comment1.id], delete: "yeah" }
+ it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
+
+ expect(InboxComment.find_by(id: inbox_comment1.id)).to be_nil
+ inbox_comment2.reload
+ expect(inbox_comment2.read).to be_falsy
+ end
end
- end
- context "with a read comment and redirects to inbox with a notice" do
- let!(:inbox_comment) { create(:inbox_comment, user: user, read: true) }
+ context "with a read comment and redirects to inbox with a notice" do
+ let!(:inbox_comment) { create(:inbox_comment, user: user, read: true) }
- it "marks as unread and redirects to inbox with a notice" do
- put :update, params: { user_id: user.login, inbox_comments: [inbox_comment.id], unread: "yeah" }
- it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
+ it "marks as unread and redirects to inbox with a notice" do
+ put :update, params: { user_id: user.login, inbox_comments: [inbox_comment.id], unread: "yeah" }
+ it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")
- inbox_comment.reload
- expect(inbox_comment.read).to be_falsy
- end
+ inbox_comment.reload
+ expect(inbox_comment.read).to be_falsy
+ end
- it "marks as unread and returns a JSON response" do
- parameters = {
- user_id: user.login,
- inbox_comments: [inbox_comment.id],
- unread: "yeah",
- format: "json"
- }
+ it "marks as unread and returns a JSON response" do
+ parameters = {
+ user_id: user.login,
+ inbox_comments: [inbox_comment.id],
+ unread: "yeah",
+ format: "json"
+ }
- put :update, params: parameters
+ put :update, params: parameters
- inbox_comment.reload
- expect(inbox_comment.read).to be_falsy
+ inbox_comment.reload
+ expect(inbox_comment.read).to be_falsy
- parsed_body = JSON.parse(response.body, symbolize_names: true)
- expect(parsed_body[:item_success_message]).to eq("Inbox successfully updated.")
- expect(response).to have_http_status(:success)
+ parsed_body = JSON.parse(response.body, symbolize_names: true)
+ expect(parsed_body[:item_success_message]).to eq("Inbox successfully updated.")
+ expect(response).to have_http_status(:success)
+ end
end
end
end