diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 4ad8b879117..c2a54dc9ff6 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -479,7 +479,12 @@ class TopicsController < ApplicationController enabled = params[:enabled] == "true" check_for_status_presence(:status, status) - @topic = Topic.find_by(id: topic_id) + @topic = + if params[:category_id] + Topic.find_by(id: topic_id, category_id: params[:category_id].to_i) + else + Topic.find_by(id: topic_id) + end case status when "closed" diff --git a/app/models/api_key_scope.rb b/app/models/api_key_scope.rb index c0d072d0bca..55b4d0dda47 100644 --- a/app/models/api_key_scope.rb +++ b/app/models/api_key_scope.rb @@ -28,8 +28,8 @@ class ApiKeyScope < ActiveRecord::Base params: %i[topic_id], }, update: { - actions: %w[topics#update], - params: %i[topic_id], + actions: %w[topics#update topics#status], + params: %i[topic_id category_id], }, read: { actions: %w[topics#show topics#feed topics#posts], diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a807ded6f47..0bd807d42d7 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4670,7 +4670,7 @@ en: topics: read: Read a topic or a specific post in it. RSS is also supported. write: Create a new topic or post to an existing one. - update: Update a topic. Change the title, category, tags, etc. + update: Update a topic. Change the title, category, tags, status, archetype, featured_link etc. read_lists: Read topic lists like top, new, latest, etc. RSS is also supported. posts: edit: Edit any post or a specific one. diff --git a/lib/route_matcher.rb b/lib/route_matcher.rb index 6512f7942f1..4e4fd028d1b 100644 --- a/lib/route_matcher.rb +++ b/lib/route_matcher.rb @@ -59,7 +59,7 @@ class RouteMatcher params.all? do |param| param_alias = aliases&.[](param) - allowed_values = [allowed_param_values[param.to_s]].flatten + allowed_values = [allowed_param_values.fetch(param.to_s, [])].flatten value = requested_params[param.to_s] alias_value = requested_params[param_alias.to_s] diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb index ea369bef341..3c00a04b904 100644 --- a/spec/requests/topics_controller_spec.rb +++ b/spec/requests/topics_controller_spec.rb @@ -1037,6 +1037,99 @@ RSpec.describe TopicsController do expect(topic.posts.last.action_code).to eq("visible.enabled") end end + + context "with API key" do + let(:api_key) { Fabricate(:api_key, user: moderator, created_by: moderator) } + + context "when key scope has restricted params" do + before do + ApiKeyScope.create( + resource: "topics", + action: "update", + api_key_id: api_key.id, + allowed_parameters: { + "category_id" => ["#{topic.category_id}"], + }, + ) + end + + it "fails to update topic status in an unpermitted category" do + put "/t/#{topic.id}/status.json", + params: { + status: "closed", + enabled: "true", + category_id: tracked_category.id, + }, + headers: { + "HTTP_API_KEY" => api_key.key, + "HTTP_API_USERNAME" => api_key.user.username, + } + + expect(response.status).to eq(403) + expect(response.body).to include(I18n.t("invalid_access")) + expect(topic.reload.closed).to eq(false) + end + + it "fails without a category_id" do + put "/t/#{topic.id}/status.json", + params: { + status: "closed", + enabled: "true", + }, + headers: { + "HTTP_API_KEY" => api_key.key, + "HTTP_API_USERNAME" => api_key.user.username, + } + + expect(response.status).to eq(403) + expect(response.body).to include(I18n.t("invalid_access")) + expect(topic.reload.closed).to eq(false) + end + + it "updates topic status in a permitted category" do + put "/t/#{topic.id}/status.json", + params: { + status: "closed", + enabled: "true", + category_id: topic.category_id, + }, + headers: { + "HTTP_API_KEY" => api_key.key, + "HTTP_API_USERNAME" => api_key.user.username, + } + + expect(response.status).to eq(200) + expect(topic.reload.closed).to eq(true) + end + end + + context "when key scope has no param restrictions" do + before do + ApiKeyScope.create( + resource: "topics", + action: "update", + api_key_id: api_key.id, + allowed_parameters: { + }, + ) + end + + it "updates topic status" do + put "/t/#{topic.id}/status.json", + params: { + status: "closed", + enabled: "true", + }, + headers: { + "HTTP_API_KEY" => api_key.key, + "HTTP_API_USERNAME" => api_key.user.username, + } + + expect(response.status).to eq(200) + expect(topic.reload.closed).to eq(true) + end + end + end end describe "#destroy_timings" do