diff --git a/app/models/user_api_key_scope.rb b/app/models/user_api_key_scope.rb index c4f99975009..60a4ca72dcb 100644 --- a/app/models/user_api_key_scope.rb +++ b/app/models/user_api_key_scope.rb @@ -12,7 +12,8 @@ class UserApiKeyScope < ActiveRecord::Base RouteMatcher.new(methods: :get, actions: 'notifications#index'), RouteMatcher.new(methods: :put, actions: 'notifications#mark_read') ], - session_info: [ RouteMatcher.new(methods: :get, actions: 'session#current') ] + session_info: [ RouteMatcher.new(methods: :get, actions: 'session#current') ], + bookmarks_calendar: [ RouteMatcher.new(methods: :get, actions: 'users#bookmarks', formats: :ics, params: %i[username]) ] } def self.all_scopes @@ -20,7 +21,7 @@ class UserApiKeyScope < ActiveRecord::Base end def permits?(env) - matchers.any? { |m| m.match?(env: env) } + matchers.any? { |m| m.with_allowed_param_values(allowed_parameters).match?(env: env) } end private @@ -35,11 +36,12 @@ end # # Table name: user_api_key_scopes # -# id :bigint not null, primary key -# user_api_key_id :integer not null -# name :string not null -# created_at :datetime not null -# updated_at :datetime not null +# id :bigint not null, primary key +# user_api_key_id :integer not null +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# allowed_parameters :jsonb # # Indexes # diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index a15abb7c68a..786519aa456 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1049,6 +1049,7 @@ en: read: "Read all" write: "Write all" one_time_password: "Create a one-time login token" + bookmarks_calendar: "Read bookmark reminders" invalid_public_key: "Sorry, the public key is invalid." invalid_auth_redirect: "Sorry, this auth_redirect host is not allowed." invalid_token: "Missing, invalid or expired token." diff --git a/db/migrate/20201008105539_add_allowed_parameters_to_user_api_key_scope.rb b/db/migrate/20201008105539_add_allowed_parameters_to_user_api_key_scope.rb new file mode 100644 index 00000000000..23c2fae2989 --- /dev/null +++ b/db/migrate/20201008105539_add_allowed_parameters_to_user_api_key_scope.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddAllowedParametersToUserApiKeyScope < ActiveRecord::Migration[6.0] + def change + add_column :user_api_key_scopes, :allowed_parameters, :jsonb + end +end diff --git a/spec/fabricators/user_api_key_fabricator.rb b/spec/fabricators/user_api_key_fabricator.rb index c6614b95bf9..5d9fe484398 100644 --- a/spec/fabricators/user_api_key_fabricator.rb +++ b/spec/fabricators/user_api_key_fabricator.rb @@ -8,3 +8,10 @@ Fabricator(:readonly_user_api_key, from: :user_api_key) do 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 diff --git a/spec/integration/api_keys_spec.rb b/spec/integration/api_keys_spec.rb index d74378c7e45..db6d9052b9c 100644 --- a/spec/integration/api_keys_spec.rb +++ b/spec/integration/api_keys_spec.rb @@ -99,4 +99,33 @@ describe 'user api keys' do expect(response.status).to eq(302) end + it "can restrict scopes by parameters" do + admin = Fabricate(:admin) + + calendar_key = Fabricate(:bookmarks_calendar_user_api_key, user: admin) + + get "/u/#{user.username}/bookmarks.json", headers: { + HTTP_USER_API_KEY: calendar_key.key, + } + expect(response.status).to eq(403) # Does not allow json + + get "/u/#{user.username}/bookmarks.ics", headers: { + HTTP_USER_API_KEY: calendar_key.key, + } + expect(response.status).to eq(200) # Allows ICS + + # Now restrict the key + calendar_key.scopes.first.update(allowed_parameters: { username: admin.username }) + + get "/u/#{user.username}/bookmarks.ics", headers: { + HTTP_USER_API_KEY: calendar_key.key, + } + expect(response.status).to eq(403) # Cannot access another users calendar + + get "/u/#{admin.username}/bookmarks.ics", headers: { + HTTP_USER_API_KEY: calendar_key.key, + } + expect(response.status).to eq(200) # Can access own calendar + end + end