FEATURE: Add an API scope for running queries (#154)
This commit is contained in:
parent
70b973ea9a
commit
ea66bcdc75
|
@ -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
|
||||||
|
|
|
@ -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."
|
||||||
|
|
11
plugin.rb
11
plugin.rb
|
@ -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
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue