From 7c3fea727583dd9bf482729cc87272e976a25b2d Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Wed, 18 Oct 2023 14:10:07 +0200 Subject: [PATCH] Feature: Allow token introspection without read scope (#27142) --- .../api/v1/apps/credentials_controller.rb | 6 +- .../rest/application_serializer.rb | 2 +- spec/requests/api/v1/apps/credentials_spec.rb | 77 ++++++++++++++++++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/v1/apps/credentials_controller.rb b/app/controllers/api/v1/apps/credentials_controller.rb index 0475b2d4a2..6256bed64c 100644 --- a/app/controllers/api/v1/apps/credentials_controller.rb +++ b/app/controllers/api/v1/apps/credentials_controller.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true class Api::V1::Apps::CredentialsController < Api::BaseController - before_action -> { doorkeeper_authorize! :read } - def show - render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key) + return doorkeeper_render_error unless valid_doorkeeper_token? + + render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes) end end diff --git a/app/serializers/rest/application_serializer.rb b/app/serializers/rest/application_serializer.rb index ab68219ade..e4806a3c9f 100644 --- a/app/serializers/rest/application_serializer.rb +++ b/app/serializers/rest/application_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class REST::ApplicationSerializer < ActiveModel::Serializer - attributes :id, :name, :website, :redirect_uri, + attributes :id, :name, :website, :scopes, :redirect_uri, :client_id, :client_secret, :vapid_key def id diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb index 1268b36f8a..e1455fe799 100644 --- a/spec/requests/api/v1/apps/credentials_spec.rb +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -9,7 +9,8 @@ describe 'Credentials' do end context 'with an oauth token' do - let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) } + let(:application) { Fabricate(:application, scopes: 'read') } + let(:token) { Fabricate(:accessible_access_token, application: application) } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } it 'returns the app information correctly', :aggregate_failures do @@ -21,7 +22,35 @@ describe 'Credentials' do a_hash_including( name: token.application.name, website: token.application.website, - vapid_key: Rails.configuration.x.vapid_public_key + vapid_key: Rails.configuration.x.vapid_public_key, + scopes: token.application.scopes.map(&:to_s), + client_id: token.application.uid + ) + ) + end + end + + context 'with a non-read scoped oauth token' do + let(:application) { Fabricate(:application, scopes: 'admin:write') } + let(:token) { Fabricate(:accessible_access_token, application: application) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the app information correctly' do + subject + + expect(body_as_json).to match( + a_hash_including( + name: token.application.name, + website: token.application.website, + vapid_key: Rails.configuration.x.vapid_public_key, + scopes: token.application.scopes.map(&:to_s), + client_id: token.application.uid ) ) end @@ -36,5 +65,49 @@ describe 'Credentials' do expect(response).to have_http_status(401) end end + + context 'with a revoked oauth token' do + let(:application) { Fabricate(:application, scopes: 'read') } + let(:token) { Fabricate(:accessible_access_token, application: application, revoked_at: DateTime.now.utc) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it 'returns http authorization error' do + subject + + expect(response).to have_http_status(401) + end + + it 'returns the error in the json response' do + subject + + expect(body_as_json).to match( + a_hash_including( + error: 'The access token was revoked' + ) + ) + end + end + + context 'with an invalid oauth token' do + let(:application) { Fabricate(:application, scopes: 'read') } + let(:token) { Fabricate(:accessible_access_token, application: application) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}-invalid" } } + + it 'returns http authorization error' do + subject + + expect(response).to have_http_status(401) + end + + it 'returns the error in the json response' do + subject + + expect(body_as_json).to match( + a_hash_including( + error: 'The access token is invalid' + ) + ) + end + end end end