summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEmelia Smith <ThisIsMissEm@users.noreply.github.com>2024-05-06 15:17:56 +0200
committerGitHub <noreply@github.com>2024-05-06 13:17:56 +0000
commit116f01ec7d1e0793fc6c1749867d660d7c19a5b7 (patch)
tree05b548cefcc88980f351cac74daeb548277245a5
parent30ef9fccf0c603ba917191ddbefdd497523d3d67 (diff)
Implement RFC 8414 for OAuth 2.0 server metadata (#29191)
-rw-r--r--app/controllers/well_known/oauth_metadata_controller.rb23
-rw-r--r--app/presenters/oauth_metadata_presenter.rb67
-rw-r--r--app/serializers/oauth_metadata_serializer.rb9
-rw-r--r--config/routes.rb1
-rw-r--r--spec/requests/well_known/oauth_metadata_spec.rb37
5 files changed, 137 insertions, 0 deletions
diff --git a/app/controllers/well_known/oauth_metadata_controller.rb b/app/controllers/well_known/oauth_metadata_controller.rb
new file mode 100644
index 00000000000..c80be2d6525
--- /dev/null
+++ b/app/controllers/well_known/oauth_metadata_controller.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module WellKnown
+ class OauthMetadataController < ActionController::Base # rubocop:disable Rails/ApplicationController
+ include CacheConcern
+
+ # Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user`
+ # and thus re-issuing session cookies
+ serialization_scope nil
+
+ def show
+ # Due to this document potentially changing between Mastodon versions (as
+ # new OAuth scopes are added), we don't use expires_in to cache upstream,
+ # instead just caching in the rails cache:
+ render_with_cache(
+ json: ::OauthMetadataPresenter.new,
+ serializer: ::OauthMetadataSerializer,
+ content_type: 'application/json',
+ expires_in: 15.minutes
+ )
+ end
+ end
+end
diff --git a/app/presenters/oauth_metadata_presenter.rb b/app/presenters/oauth_metadata_presenter.rb
new file mode 100644
index 00000000000..546503bfcca
--- /dev/null
+++ b/app/presenters/oauth_metadata_presenter.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+class OauthMetadataPresenter < ActiveModelSerializers::Model
+ include RoutingHelper
+
+ attributes :issuer, :authorization_endpoint, :token_endpoint,
+ :revocation_endpoint, :scopes_supported,
+ :response_types_supported, :response_modes_supported,
+ :grant_types_supported, :token_endpoint_auth_methods_supported,
+ :service_documentation, :app_registration_endpoint
+
+ def issuer
+ root_url
+ end
+
+ def service_documentation
+ 'https://docs.joinmastodon.org/'
+ end
+
+ def authorization_endpoint
+ oauth_authorization_url
+ end
+
+ def token_endpoint
+ oauth_token_url
+ end
+
+ # As the api_v1_apps route doesn't technically conform to the specification
+ # for OAuth 2.0 Dynamic Client Registration defined in RFC 7591 we use a
+ # non-standard property for now to indicate the mastodon specific registration
+ # endpoint. See: https://datatracker.ietf.org/doc/html/rfc7591
+ def app_registration_endpoint
+ api_v1_apps_url
+ end
+
+ def revocation_endpoint
+ oauth_revoke_url
+ end
+
+ def scopes_supported
+ doorkeeper.scopes
+ end
+
+ def response_types_supported
+ doorkeeper.authorization_response_types
+ end
+
+ def response_modes_supported
+ doorkeeper.authorization_response_flows.flat_map(&:response_mode_matches).uniq
+ end
+
+ def grant_types_supported
+ grant_types_supported = doorkeeper.grant_flows.dup
+ grant_types_supported << 'refresh_token' if doorkeeper.refresh_token_enabled?
+ grant_types_supported
+ end
+
+ def token_endpoint_auth_methods_supported
+ %w(client_secret_basic client_secret_post)
+ end
+
+ private
+
+ def doorkeeper
+ @doorkeeper ||= Doorkeeper.configuration
+ end
+end
diff --git a/app/serializers/oauth_metadata_serializer.rb b/app/serializers/oauth_metadata_serializer.rb
new file mode 100644
index 00000000000..5f3dc7b87e2
--- /dev/null
+++ b/app/serializers/oauth_metadata_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class OauthMetadataSerializer < ActiveModel::Serializer
+ attributes :issuer, :authorization_endpoint, :token_endpoint,
+ :revocation_endpoint, :scopes_supported,
+ :response_types_supported, :response_modes_supported,
+ :grant_types_supported, :token_endpoint_auth_methods_supported,
+ :service_documentation, :app_registration_endpoint
+end
diff --git a/config/routes.rb b/config/routes.rb
index 3d3c94096c1..f4662dd5da4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -62,6 +62,7 @@ Rails.application.routes.draw do
tokens: 'oauth/tokens'
end
+ get '.well-known/oauth-authorization-server', to: 'well_known/oauth_metadata#show', as: :oauth_metadata, defaults: { format: 'json' }
get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' }
get '.well-known/nodeinfo', to: 'well_known/node_info#index', as: :nodeinfo, defaults: { format: 'json' }
get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
diff --git a/spec/requests/well_known/oauth_metadata_spec.rb b/spec/requests/well_known/oauth_metadata_spec.rb
new file mode 100644
index 00000000000..deef189ac90
--- /dev/null
+++ b/spec/requests/well_known/oauth_metadata_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'The /.well-known/oauth-authorization-server request' do
+ let(:protocol) { ENV.fetch('LOCAL_HTTPS', true) ? :https : :http }
+
+ before do
+ host! ENV.fetch('LOCAL_DOMAIN')
+ end
+
+ it 'returns http success with valid JSON response' do
+ get '/.well-known/oauth-authorization-server'
+
+ expect(response)
+ .to have_http_status(200)
+ .and have_attributes(
+ media_type: 'application/json'
+ )
+
+ grant_types_supported = Doorkeeper.configuration.grant_flows.dup
+ grant_types_supported << 'refresh_token' if Doorkeeper.configuration.refresh_token_enabled?
+
+ expect(body_as_json).to include(
+ issuer: root_url(protocol: protocol),
+ service_documentation: 'https://docs.joinmastodon.org/',
+ authorization_endpoint: oauth_authorization_url(protocol: protocol),
+ token_endpoint: oauth_token_url(protocol: protocol),
+ revocation_endpoint: oauth_revoke_url(protocol: protocol),
+ scopes_supported: Doorkeeper.configuration.scopes.map(&:to_s),
+ response_types_supported: Doorkeeper.configuration.authorization_response_types,
+ grant_types_supported: grant_types_supported,
+ # non-standard extension:
+ app_registration_endpoint: api_v1_apps_url(protocol: protocol)
+ )
+ end
+end