SECURITY: ensure users have permission when moving categories

This commit is contained in:
Sam 2018-03-02 12:13:04 +11:00
parent 4a7a371557
commit 75172024ca
9 changed files with 113 additions and 23 deletions

View File

@ -200,6 +200,15 @@ class PostsController < ApplicationController
if post.is_first_post? if post.is_first_post?
changes[:title] = params[:title] if params[:title] changes[:title] = params[:title] if params[:title]
changes[:category_id] = params[:post][:category_id] if params[:post][:category_id] changes[:category_id] = params[:post][:category_id] if params[:post][:category_id]
if changes[:category_id] && changes[:category_id].to_i != post.topic.category_id.to_i
category = Category.find_by(id: changes[:category_id])
if category || (changes[:category_id].to_i == 0)
guardian.ensure_can_create_topic_on_category!(category)
else
return render_json_error(I18n.t('category.errors.not_found'))
end
end
end end
# We don't need to validate edits to small action posts by staff # We don't need to validate edits to small action posts by staff

View File

@ -226,6 +226,15 @@ class TopicsController < ApplicationController
topic = Topic.find_by(id: params[:topic_id]) topic = Topic.find_by(id: params[:topic_id])
guardian.ensure_can_edit!(topic) guardian.ensure_can_edit!(topic)
if params[:category_id] && (params[:category_id].to_i != topic.category_id.to_i)
category = Category.find_by(id: params[:category_id])
if category || (params[:category_id].to_i == 0)
guardian.ensure_can_create_topic_on_category!(category)
else
return render_json_error(I18n.t('category.errors.not_found'))
end
end
changes = {} changes = {}
PostRevisor.tracked_topic_fields.each_key do |f| PostRevisor.tracked_topic_fields.each_key do |f|
changes[f] = params[f] if params.has_key?(f) changes[f] = params[f] if params.has_key?(f)

View File

@ -506,6 +506,7 @@ en:
replace_paragraph: "(Replace this first paragraph with a brief description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters. **Until you edit this description or create topics, this category won't appear on the categories page.**)" replace_paragraph: "(Replace this first paragraph with a brief description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters. **Until you edit this description or create topics, this category won't appear on the categories page.**)"
post_template: "%{replace_paragraph}\n\nUse the following paragraphs for a longer description, or to establish category guidelines or rules:\n\n- Why should people use this category? What is it for?\n\n- How exactly is this different than the other categories we already have?\n\n- What should topics in this category generally contain?\n\n- Do we need this category? Can we merge with another category, or subcategory?\n" post_template: "%{replace_paragraph}\n\nUse the following paragraphs for a longer description, or to establish category guidelines or rules:\n\n- Why should people use this category? What is it for?\n\n- How exactly is this different than the other categories we already have?\n\n- What should topics in this category generally contain?\n\n- Do we need this category? Can we merge with another category, or subcategory?\n"
errors: errors:
not_found: "Category not found!"
uncategorized_parent: "Uncategorized can't have a parent category" uncategorized_parent: "Uncategorized can't have a parent category"
self_parent: "A subcategory's parent cannot be itself" self_parent: "A subcategory's parent cannot be itself"
depth: "You can't nest a subcategory under another" depth: "You can't nest a subcategory under another"

View File

@ -19,8 +19,12 @@ module TopicGuardian
end end
def can_create_topic_on_category?(category) def can_create_topic_on_category?(category)
# allow for category to be a number as well
category_id = category
category_id = category.id if Category === category
can_create_topic?(nil) && can_create_topic?(nil) &&
(!category || Category.topic_create_allowed(self).where(id: category.id).count == 1) (!category || Category.topic_create_allowed(self).where(id: category_id).count == 1)
end end
def can_create_post_on_topic?(topic) def can_create_post_on_topic?(topic)

View File

@ -69,9 +69,11 @@ class PostRevisor
end end
track_topic_field(:category_id) do |tc, category_id| track_topic_field(:category_id) do |tc, category_id|
if tc.guardian.can_create_topic_on_category?(category_id)
tc.record_change('category_id', tc.topic.category_id, category_id) tc.record_change('category_id', tc.topic.category_id, category_id)
tc.check_result(tc.topic.change_category_to_id(category_id)) tc.check_result(tc.topic.change_category_to_id(category_id))
end end
end
track_topic_field(:tags) do |tc, tags| track_topic_field(:tags) do |tc, tags|
if tc.guardian.can_tag_topics? if tc.guardian.can_tag_topics?

View File

@ -38,6 +38,32 @@ describe PostRevisor do
end end
end end
context 'editing category' do
it 'does not revise category when no permission to create a topic in category' do
category = Fabricate(:category)
category.set_permissions(staff: :full)
category.save!
post = create_post
old_id = post.topic.category_id
post.revise(post.user, category_id: category.id)
post.reload
expect(post.topic.category_id).to eq(old_id)
category.set_permissions(everyone: :full)
category.save!
post.revise(post.user, category_id: category.id)
post.reload
expect(post.topic.category_id).to eq(category.id)
end
end
context 'revise wiki' do context 'revise wiki' do
before do before do

View File

@ -1136,22 +1136,6 @@ describe TopicsController do
expect(@topic.title).to eq('This is a new title for the topic') expect(@topic.title).to eq('This is a new title for the topic')
end end
it 'triggers a change of category' do
Topic.any_instance.expects(:change_category_to_id).with(123).returns(true)
put :update, params: {
topic_id: @topic.id, slug: @topic.title, category_id: 123
}, format: :json
end
it 'allows to change category to "uncategorized"' do
Topic.any_instance.expects(:change_category_to_id).with(0).returns(true)
put :update, params: {
topic_id: @topic.id, slug: @topic.title, category_id: ""
}, format: :json
end
it "returns errors with invalid titles" do it "returns errors with invalid titles" do
put :update, params: { put :update, params: {
topic_id: @topic.id, slug: @topic.title, title: 'asdf' topic_id: @topic.id, slug: @topic.title, title: 'asdf'
@ -1170,7 +1154,6 @@ describe TopicsController do
end end
it "returns errors with invalid categories" do it "returns errors with invalid categories" do
Topic.any_instance.expects(:change_category_to_id).returns(false)
put :update, params: { put :update, params: {
topic_id: @topic.id, slug: @topic.title, category_id: -1 topic_id: @topic.id, slug: @topic.title, category_id: -1
}, format: :json }, format: :json
@ -1197,8 +1180,9 @@ describe TopicsController do
context 'when there are no changes' do context 'when there are no changes' do
it 'does not call the PostRevisor' do it 'does not call the PostRevisor' do
PostRevisor.any_instance.expects(:revise!).never PostRevisor.any_instance.expects(:revise!).never
put :update, params: { put :update, params: {
topic_id: @topic.id, slug: @topic.title, title: @topic.title, category_id: nil topic_id: @topic.id, slug: @topic.title, title: @topic.title, category_id: @topic.category_id
}, format: :json }, format: :json
expect(response).to be_success expect(response).to be_success
@ -1212,10 +1196,10 @@ describe TopicsController do
end end
it "can add a category to an uncategorized topic" do it "can add a category to an uncategorized topic" do
Topic.any_instance.expects(:change_category_to_id).with(456).returns(true) c = Fabricate(:category)
put :update, params: { put :update, params: {
topic_id: @topic.id, slug: @topic.title, category_id: 456 topic_id: @topic.id, slug: @topic.title, category_id: c.id
}, format: :json }, format: :json
expect(response).to be_success expect(response).to be_success

View File

@ -12,11 +12,47 @@ RSpec.describe PostsController do
let(:private_post) { Fabricate(:post, user: user, topic: private_topic) } let(:private_post) { Fabricate(:post, user: user, topic: private_topic) }
describe '#update' do
it 'can not change category to a disallowed category' do
post = create_post
sign_in(post.user)
category = Fabricate(:category)
category.set_permissions(staff: :full)
category.save!
# strange API, why is topic id in here twice
put "/posts/#{post.id}.json", params: {
post: { category_id: category.id, raw: "this is a test edit to post" }
}
expect(response.status).not_to eq(200)
expect(post.topic.category_id).not_to eq(category.id)
end
end
describe '#create' do describe '#create' do
before do before do
sign_in(user) sign_in(user)
end end
it 'can not create a post in a disallowed category' do
category.set_permissions(staff: :full)
category.save!
post "/posts.json", params: {
raw: 'this is the test content',
title: 'this is the test title for the topic',
category: category.id,
meta_data: { xyz: 'abc' }
}
expect(response.status).to eq(403)
end
it 'creates the post' do it 'creates the post' do
post "/posts.json", params: { post "/posts.json", params: {
raw: 'this is the test content', raw: 'this is the test content',

View File

@ -4,6 +4,25 @@ RSpec.describe TopicsController do
let(:topic) { Fabricate(:topic) } let(:topic) { Fabricate(:topic) }
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
describe '#update' do
it 'can not change category to a disallowed category' do
post = create_post
sign_in(post.user)
category = Fabricate(:category)
category.set_permissions(staff: :full)
category.save!
# strange API, why is topic id in here twice
put "/t/#{post.topic_id}.json", params: { category_id: category.id, topic_id: post.topic_id }
expect(response.status).not_to eq(200)
expect(post.topic.category_id).not_to eq(category.id)
end
end
describe '#show' do describe '#show' do
let(:private_topic) { Fabricate(:private_message_topic) } let(:private_topic) { Fabricate(:private_message_topic) }