Allow trusted user to access response templates (#17978)

* Allow trusted user to access response templates

* Add request specs for trusted user templates

* WIP: was just investigating...

* Move permission check to authorizer

* Revert "WIP: was just investigating..."

This reverts commit c38c60f7ab19ce43c174729c2542a9041e9322e2.

* Improved accessibility

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
This commit is contained in:
Joshua Wehner 2022-07-12 11:37:59 +02:00 committed by GitHub
parent 10c44013be
commit 5dee4a11c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 223 additions and 79 deletions

View file

@ -31,9 +31,12 @@ class ResponseTemplatesController < ApplicationController
def create
authorize ResponseTemplate
response_template.user_id = current_user.id
unless tries_to_create_a_mod_response_template? && can_create_mod_response_templates?
response_template.user_id = current_user.id
response_template.type_of = "personal_comment"
end
response_template.content_type = "body_markdown"
response_template.type_of = "personal_comment"
if response_template.save
flash[:settings_notice] =
@ -87,6 +90,18 @@ class ResponseTemplatesController < ApplicationController
private
def authorized_user
@authorized_user ||= Authorizer.for(user: current_user)
end
def can_create_mod_response_templates?
authorized_user.accesses_mod_response_templates?
end
def tries_to_create_a_mod_response_template?
params[:response_template][:type_of] == "mod_comment"
end
def response_template
@response_template ||= if params[:id].present?
ResponseTemplate.find(params[:id])

View file

@ -218,8 +218,6 @@ class UsersController < ApplicationController
def signout_confirm; end
def handle_settings_tab
return @tab = "profile" if @tab.blank?
case @tab
when "profile"
handle_integrations_tab
@ -302,8 +300,10 @@ class UsersController < ApplicationController
end
def handle_response_templates_tab
@response_templates = current_user.response_templates
@response_template = ResponseTemplate.find_or_initialize_by(id: params[:id], user: current_user)
@personal_response_templates = current_user.response_templates
@trusted_response_templates = policy_scope(ResponseTemplate).where(type_of: "mod_comment")
@response_template = policy_scope(ResponseTemplate).find_by(id: params[:id]) ||
ResponseTemplate.new
end
def set_user

View file

@ -75,6 +75,10 @@ module Authorizer
has_role?(:creator)
end
def accesses_mod_response_templates?
has_trusted_role? || any_admin? || moderator? || tag_moderator?
end
# When you need to know if we trust the user, but don't want to
# have stale information that the `trusted?` method might give
# you.

View file

@ -3,8 +3,8 @@ class ResponseTemplatePolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.has_trusted_role? || user.any_admin? || user.moderator? || user.tag_moderator?
scope.where(user: user, type_of: "personal_comment") + scope.where.not(type_of: "personal_comment")
if Authorizer.for(user: user).accesses_mod_response_templates?
scope.where(user: user, type_of: "personal_comment").or(scope.where.not(type_of: "personal_comment"))
else
scope.where(user: user, type_of: "personal_comment")
end
@ -30,14 +30,21 @@ class ResponseTemplatePolicy < ApplicationPolicy
user_moderator? && mod_comment?
end
def destroy?
def modify?
return true if mod_comment? && user_trusted?
user_owner?
end
alias update? destroy?
alias update? modify?
alias destroy? modify?
def permitted_attributes_for_create
PERMITTED_ATTRIBUTES
if user_trusted?
PERMITTED_ATTRIBUTES + [:type_of]
else
PERMITTED_ATTRIBUTES
end
end
def permitted_attributes_for_update
@ -50,6 +57,10 @@ class ResponseTemplatePolicy < ApplicationPolicy
user.id == record.user_id
end
def user_trusted?
Authorizer.for(user: user).accesses_mod_response_templates?
end
def user_moderator?
user_any_admin? || user.moderator_for_tags&.present?
end

View file

@ -0,0 +1,13 @@
<div class="<%= "github-repo-row-featured" if response_template.id == params[:id].to_i %> mb-4">
<div class="flex items-center py-2 mt-2">
<div class="flex-1 ff-monospace">
<%= response_template.title %>
</div>
<div class="flex">
<%= form_with url: response_template_path(response_template.id), method: :delete, local: true, html: { onsubmit: "return confirm('#{t('views.settings.extensions.comment.confirm', template: response_template.title)}');" } do %>
<button type="submit" class="crayons-btn crayons-btn--secondary"><%= t("views.settings.extensions.comment.remove") %></button>
<% end %>
<a class="crayons-btn crayons-btn--secondary ml-2" href="/settings/response-templates/<%= response_template.id %>" role="button"><%= t("views.settings.extensions.comment.edit") %></a>
</div>
</div>
</div>

View file

@ -7,28 +7,23 @@
</p>
</header>
<% if @response_templates.present? %>
<% if @personal_response_templates.present? %>
<section>
<h3 class="crayons-subtitle-3">
<%= t("views.settings.extensions.comment.saved") %>
</h3>
<% @response_templates.each do |response_template| %>
<div class="<%= "github-repo-row-featured" if response_template.id == params[:id].to_i %> mb-4">
<div class="flex items-center py-2 mt-2">
<div class="flex-1 ff-monospace">
<%= response_template.title %>
</div>
<div class="flex">
<%= form_with url: response_template_path(response_template.id), method: :delete, local: true, html: { onsubmit: "return confirm('#{t('views.settings.extensions.comment.confirm', template: response_template.title)}');" } do %>
<button type="submit" class="crayons-btn crayons-btn--secondary"><%= t("views.settings.extensions.comment.remove") %></button>
<% end %>
<a class="crayons-btn crayons-btn--secondary ml-2" href="/settings/response-templates/<%= response_template.id %>" role="button"><%= t("views.settings.extensions.comment.edit") %></a>
</div>
</div>
</div>
<% end %>
</section>
<% end %>
<%= render partial: "response_template", collection: @personal_response_templates %>
</section>
<% end %>
<% if @trusted_response_templates.present? %>
<section>
<h3 class="crayons-subtitle-3">
<%= t("views.settings.extensions.comment.trusted") %>
</h3>
<%= render partial: "response_template", collection: @trusted_response_templates %>
</section>
<% end %>
<%= form_with model: @response_template do |f| %>
<section class="grid gap-3">
@ -60,6 +55,22 @@
<%= f.text_area :content, value: content, class: "crayons-textfield" %>
</div>
<% if current_user.has_trusted_role? && @response_template.new_record? %>
<div class="crayons-field">
<fieldset>
<legend class="screen-reader-only">Template type</legend>
<%= f.radio_button :type_of, "personal_comment",
checked: @response_template.type_of != "mod_comment",
class: "crayons-radio", role: "radio" %>
<%= label_tag "response_template_type_of_personal_comment", "Personal" %>
<%= f.radio_button :type_of, "mod_comment",
class: "crayons-radio", role: "radio" %>
<%= label_tag "response_template_type_of_mod_comment", "Trusted User" %>
</fieldset>
</div>
<% end %>
<div>
<button type="submit" class="crayons-btn"><%= t("views.settings.extensions.comment.save") %></button>
</div>

View file

@ -130,6 +130,7 @@ en:
remove: Remove
save: Save template
saved: Saved templates
trusted: Trusted user templates
github:
heading: GitHub
desc1: Pin your GitHub repositories to your profile.

View file

@ -39,15 +39,14 @@ RSpec.describe ResponseTemplatePolicy, type: :policy do
let(:user) { create(:user, :tag_moderator) }
let(:response_template) { create(:response_template, type_of: "mod_comment", user: nil) }
it { is_expected.to permit_actions(%i[moderator_index create moderator_create]) }
it { is_expected.to forbid_actions(%i[update destroy admin_index]) }
it { is_expected.to permit_actions(%i[moderator_index create moderator_create update destroy]) }
it { is_expected.to forbid_actions(%i[admin_index]) }
end
context "when user is an admin" do
let(:user) { create(:user, :admin) }
let(:response_template) { create(:response_template, type_of: "mod_comment", user: nil) }
it { is_expected.to permit_actions(%i[moderator_index create moderator_create admin_index]) }
it { is_expected.to forbid_actions(%i[update destroy]) }
it { is_expected.to permit_actions(%i[moderator_index create moderator_create admin_index update destroy]) }
end
end

View file

@ -2,6 +2,7 @@ require "rails_helper"
RSpec.describe "ResponseTemplate", type: :request do
let(:user) { create(:user) }
let(:trusted_user) { create(:user, :trusted) }
let(:moderator) { create(:user, :tag_moderator) }
let(:admin) { create(:user, :admin) }
@ -30,7 +31,6 @@ RSpec.describe "ResponseTemplate", type: :request do
headers = { HTTP_ACCEPT: "application/json" }
get response_templates_path, params: { type_of: "personal_comment" }, headers: headers
expect(response.parsed_body.class).to eq Array
expect(response.parsed_body.length).to eq total_response_templates
end
@ -120,65 +120,155 @@ RSpec.describe "ResponseTemplate", type: :request do
end
describe "POST /response_templates #create" do
before { sign_in user }
context "when signed in as normal user" do
before { sign_in user }
let(:attributes) do
{
title: "some_title",
content: "some content",
type_of: "personal_comment"
}
let(:attributes) do
{
title: "some_title",
content: "some content",
type_of: "personal_comment"
}
end
it "successfully creates the proper response template" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content]
}
}
response_template = ResponseTemplate.last
expect(response_template.user_id).to eq user.id
expect(response_template.title).to eq attributes[:title]
expect(response_template.content).to eq attributes[:content]
expect(response_template.type_of).to eq attributes[:type_of]
end
it "can only create personal response templates" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content],
type_of: "mod_comment"
}
}
response_template = ResponseTemplate.last
expect(response_template.type_of).to eq "personal_comment"
end
it "redirects to the edit page upon success" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content]
}
}
expect(response.redirect_url).to include user_settings_path(tab: "response-templates",
id: ResponseTemplate.last.id)
end
end
it "successfully creates the proper response template" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content]
}
}
context "when signed in as trusted user" do
before { sign_in trusted_user }
response_template = ResponseTemplate.last
expect(response_template.user_id).to eq user.id
expect(response_template.title).to eq attributes[:title]
expect(response_template.content).to eq attributes[:content]
expect(response_template.type_of).to eq attributes[:type_of]
end
it "redirects to the edit page upon success" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content]
let(:attributes) do
{
title: "some_title",
content: "some content",
type_of: "mod_comment"
}
}
expect(response.redirect_url).to include user_settings_path(tab: "response-templates",
id: ResponseTemplate.last.id)
end
it "successfully creates a personal response template" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content]
}
}
response_template = ResponseTemplate.last
expect(response_template.user_id).to eq trusted_user.id
expect(response_template.title).to eq attributes[:title]
expect(response_template.content).to eq attributes[:content]
expect(response_template.type_of).to eq "personal_comment"
end
it "successfully creates a mod_comment response template" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content],
type_of: attributes[:type_of]
}
}
response_template = ResponseTemplate.last
expect(response_template.user_id).to be_nil
expect(response_template.title).to eq attributes[:title]
expect(response_template.content).to eq attributes[:content]
expect(response_template.type_of).to eq "mod_comment"
end
it "redirects to the edit page upon success" do
post response_templates_path, params: {
response_template: {
title: attributes[:title],
content: attributes[:content]
}
}
expect(response.redirect_url).to include user_settings_path(tab: "response-templates",
id: ResponseTemplate.last.id)
end
end
end
describe "PATCH /response_templates/:id #update" do
before { sign_in user }
context "when signed-in as normal user updating a personal template" do
before { sign_in user }
let(:response_template) { create(:response_template, user: user) }
let(:response_template) { create(:response_template, user: user) }
it "successfully updates the response template" do
title = "something else"
patch response_template_path(response_template.id), params: { response_template: { title: title } }
expect(ResponseTemplate.first.title).to eq title
it "successfully updates the response template" do
title = "something else"
patch response_template_path(response_template.id), params: { response_template: { title: title } }
expect(ResponseTemplate.first.title).to eq title
end
it "redirects back to the response template" do
patch response_template_path(response_template.id), params: { response_template: { title: "something else" } }
expect(response.redirect_url).to include user_settings_path(tab: "response-templates",
id: ResponseTemplate.first.id)
end
it "shows the previously written content on a failed submission" do
content = "something something something"
patch response_template_path(response_template.id),
params: { response_template: { title: "", content: content } }
follow_redirect!
expect(response.body).to include content
end
end
it "redirects back to the response template" do
patch response_template_path(response_template.id), params: { response_template: { title: "something else" } }
expect(response.redirect_url).to include user_settings_path(tab: "response-templates",
id: ResponseTemplate.first.id)
end
context "when signed-in as trusted user updating a mod_comment template" do
before { sign_in trusted_user }
it "shows the previously written content on a failed submission" do
content = "something something something"
patch response_template_path(response_template.id), params: { response_template: { title: "", content: content } }
follow_redirect!
expect(response.body).to include content
let(:response_template) { create :response_template, user: nil, type_of: "mod_comment" }
it "successfully updates the response template" do
title = "something else"
patch response_template_path(response_template.id), params: { response_template: { title: title } }
expect(ResponseTemplate.first.title).to eq title
end
it "does not permit changing template type_of" do
patch response_template_path(response_template.id),
params: { response_template: { type_of: "personal_comment" } }
expect(ResponseTemplate.first.type_of).to eq("mod_comment")
end
end
end