DEV: Introduce plugin API to contribute user api key scopes

This commit is contained in:
David Taylor 2020-10-09 14:52:48 +01:00
parent 23e5c605f6
commit b7c680853d
5 changed files with 61 additions and 7 deletions

View File

@ -17,7 +17,11 @@ class UserApiKeyScope < ActiveRecord::Base
}
def self.all_scopes
SCOPES
scopes = SCOPES
DiscoursePluginRegistry.user_api_key_scope_mappings.each do |mapping|
scopes = scopes.merge!(mapping)
end
scopes
end
def permits?(env)

View File

@ -81,6 +81,7 @@ class DiscoursePluginRegistry
define_filtered_register :api_parameter_routes
define_filtered_register :api_key_scope_mappings
define_filtered_register :user_api_key_scope_mappings
define_filtered_register :permitted_bulk_action_parameters

View File

@ -793,6 +793,35 @@ class Plugin::Instance
DiscoursePluginRegistry.register_api_key_scope_mapping({ resource => action }, self)
end
# Register a new UserApiKey scope, and its allowed routes. Scope will be prefixed
# with the (parametetized) plugin name followed by a colon.
#
# For example, if discourse-awesome-plugin registered this:
#
# add_user_api_key_scope(:read_my_route,
# methods: :get,
# actions: "mycontroller#myaction",
# formats: :ics,
# parameters: :testparam
# )
#
# The scope registered would be `discourse-awesome-plugin:read_my_route`
#
# Multiple matchers can be attached by supplying an array of parameter hashes
#
# See UserApiKeyScope::SCOPES for more examples
# And lib/route_matcher.rb for the route matching logic
def add_user_api_key_scope(scope_name, matcher_parameters)
raise ArgumentError.new("scope_name must be a symbol") if !scope_name.is_a?(Symbol)
matcher_parameters = [matcher_parameters] if !matcher_parameters.is_a?(Array)
prefixed_scope_name = :"#{(name || directory_name).parameterize}:#{scope_name}"
DiscoursePluginRegistry.register_user_api_key_scope_mapping(
{
prefixed_scope_name => matcher_parameters&.map { |m| RouteMatcher.new(**m) }
}, self)
end
# Register a route which can be authenticated using an api key or user api key
# in a query parameter rather than a header. For example:
#

View File

@ -1,17 +1,17 @@
# frozen_string_literal: true
Fabricator(:user_api_key) do
user
client_id { SecureRandom.hex }
application_name 'some app'
end
Fabricator(:user_api_key_scope)
Fabricator(:readonly_user_api_key, from: :user_api_key) do
user
scopes { [Fabricate.build(:user_api_key_scope, name: 'read')] }
client_id { SecureRandom.hex }
application_name 'some app'
end
Fabricator(:bookmarks_calendar_user_api_key, from: :user_api_key) do
user
scopes { [Fabricate.build(:user_api_key_scope, name: 'bookmarks_calendar')] }
client_id { SecureRandom.hex }
application_name 'some app'
end

View File

@ -128,4 +128,24 @@ describe 'user api keys' do
expect(response.status).to eq(200) # Can access own calendar
end
context "with a plugin registered user api key scope" do
let(:user_api_key) { Fabricate(:user_api_key) }
before do
metadata = Plugin::Metadata.new
metadata.name = "My Amazing Plugin"
plugin = Plugin::Instance.new metadata
plugin.add_user_api_key_scope :my_magic_scope, methods: :get, actions: "session#current"
user_api_key.scopes = [UserApiKeyScope.new(name: "my-amazing-plugin:my_magic_scope")]
user_api_key.save!
end
it 'allows parameter access to the registered route' do
get '/session/current.json', headers: {
HTTP_USER_API_KEY: user_api_key.key
}
expect(response.status).to eq(200)
end
end
end