Implement RFC 8414 for OAuth 2.0 server metadata (#29191)
This commit is contained in:
		
							parent
							
								
									30ef9fccf0
								
							
						
					
					
						commit
						116f01ec7d
					
				
							
								
								
									
										23
									
								
								app/controllers/well_known/oauth_metadata_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/controllers/well_known/oauth_metadata_controller.rb
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
							
								
								
									
										67
									
								
								app/presenters/oauth_metadata_presenter.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								app/presenters/oauth_metadata_presenter.rb
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
							
								
								
									
										9
									
								
								app/serializers/oauth_metadata_serializer.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/serializers/oauth_metadata_serializer.rb
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
@ -62,6 +62,7 @@ Rails.application.routes.draw do
 | 
				
			|||||||
                tokens: 'oauth/tokens'
 | 
					                tokens: 'oauth/tokens'
 | 
				
			||||||
  end
 | 
					  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/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/nodeinfo', to: 'well_known/node_info#index', as: :nodeinfo, defaults: { format: 'json' }
 | 
				
			||||||
  get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
 | 
					  get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										37
									
								
								spec/requests/well_known/oauth_metadata_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								spec/requests/well_known/oauth_metadata_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user