FEATURE: Add an API scope for running queries (#154)

This commit is contained in:
Osama Sayegh 2022-01-21 07:15:04 +03:00 committed by GitHub
parent 70b973ea9a
commit ea66bcdc75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 2 deletions

View File

@ -5,7 +5,14 @@ class DataExplorer::QueryController < ::ApplicationController
before_action :set_group, only: %i(group_reports_index group_reports_show group_reports_run) before_action :set_group, only: %i(group_reports_index group_reports_show group_reports_run)
before_action :set_query, only: %i(group_reports_show group_reports_run show update) before_action :set_query, only: %i(group_reports_show group_reports_run show update)
before_action :ensure_admin
skip_before_action :check_xhr, only: %i(show group_reports_run run) skip_before_action :check_xhr, only: %i(show group_reports_run run)
skip_before_action :ensure_admin, only: %i(
group_reports_index
group_reports_show
group_reports_run
)
def index def index
queries = DataExplorer::Query.where(hidden: false).order(:last_run_at, :name).includes(:groups).to_a queries = DataExplorer::Query.where(hidden: false).order(:last_run_at, :name).includes(:groups).to_a

View File

@ -90,3 +90,9 @@ en:
no_search_results: "Sorry, we couldn't find any results matching your text." no_search_results: "Sorry, we couldn't find any results matching your text."
group: group:
reports: "Reports" reports: "Reports"
admin:
api:
scopes:
descriptions:
data_explorer:
run_queries: "Run Data Explorer queries. Restrict the API key to a set of queries by specifying queries IDs."

View File

@ -886,7 +886,7 @@ SQL
get 'queries/:id' => "query#show" get 'queries/:id' => "query#show"
put 'queries/:id' => "query#update" put 'queries/:id' => "query#update"
delete 'queries/:id' => "query#destroy" delete 'queries/:id' => "query#destroy"
post 'queries/:id/run' => "query#run" post 'queries/:id/run' => "query#run", constraints: { format: /(json|csv)/ }
end end
Discourse::Application.routes.append do Discourse::Application.routes.append do
@ -894,6 +894,13 @@ SQL
get '/g/:group_name/reports/:id' => 'data_explorer/query#group_reports_show' get '/g/:group_name/reports/:id' => 'data_explorer/query#group_reports_show'
post '/g/:group_name/reports/:id/run' => 'data_explorer/query#group_reports_run' post '/g/:group_name/reports/:id/run' => 'data_explorer/query#group_reports_run'
mount ::DataExplorer::Engine, at: '/admin/plugins/explorer', constraints: AdminConstraint.new mount ::DataExplorer::Engine, at: '/admin/plugins/explorer'
end end
add_api_key_scope(:data_explorer, {
run_queries: {
actions: %w[data_explorer/query#run],
params: %i[id]
}
})
end end

View File

@ -0,0 +1,102 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'API keys scoped to query#run' do
before do
SiteSetting.data_explorer_enabled = true
end
fab!(:query1) { DataExplorer::Query.create!(name: "Query 1", sql: "SELECT 1 AS query1_res") }
fab!(:query2) { DataExplorer::Query.create!(name: "Query 2", sql: "SELECT 1 AS query2_res") }
fab!(:admin) { Fabricate(:admin) }
let(:all_queries_api_key) do
key = ApiKey.create!
ApiKeyScope.create!(
resource: "data_explorer",
action: "run_queries",
api_key_id: key.id
)
key
end
let(:single_query_api_key) do
key = ApiKey.create!
ApiKeyScope.create!(
resource: "data_explorer",
action: "run_queries",
api_key_id: key.id,
allowed_parameters: { "id" => [query1.id.to_s] }
)
key
end
it 'cannot hit any other endpoints' do
get "/latest.json", headers: {
"Api-Key" => all_queries_api_key.key,
"Api-Username" => admin.username
}
expect(response.status).to eq(403)
get "/latest.json", headers: {
"Api-Key" => single_query_api_key.key,
"Api-Username" => admin.username
}
expect(response.status).to eq(403)
get "/u/#{admin.username}.json", headers: {
"Api-Key" => all_queries_api_key.key,
"Api-Username" => admin.username
}
expect(response.status).to eq(403)
get "/u/#{admin.username}.json", headers: {
"Api-Key" => single_query_api_key.key,
"Api-Username" => admin.username
}
expect(response.status).to eq(403)
end
it "can only run the queries they're allowed to run" do
expect {
post "/admin/plugins/explorer/queries/#{query1.id}/run.json", headers: {
"Api-Key" => single_query_api_key.key,
"Api-Username" => admin.username
}
}.to change { query1.reload.last_run_at }
expect(response.status).to eq(200)
expect(response.parsed_body["success"]).to eq(true)
expect(response.parsed_body["columns"]).to eq(["query1_res"])
expect {
post "/admin/plugins/explorer/queries/#{query2.id}/run.json", headers: {
"Api-Key" => single_query_api_key.key,
"Api-Username" => admin.username
}
}.not_to change { query2.reload.last_run_at }
expect(response.status).to eq(403)
end
it "can run all queries if they're not restricted to any queries" do
expect {
post "/admin/plugins/explorer/queries/#{query1.id}/run.json", headers: {
"Api-Key" => all_queries_api_key.key,
"Api-Username" => admin.username
}
}.to change { query1.reload.last_run_at }
expect(response.status).to eq(200)
expect(response.parsed_body["success"]).to eq(true)
expect(response.parsed_body["columns"]).to eq(["query1_res"])
expect {
post "/admin/plugins/explorer/queries/#{query2.id}/run.json", headers: {
"Api-Key" => all_queries_api_key.key,
"Api-Username" => admin.username
}
}.to change { query2.reload.last_run_at }
expect(response.status).to eq(200)
expect(response.parsed_body["success"]).to eq(true)
expect(response.parsed_body["columns"]).to eq(["query2_res"])
end
end