Fix embed dropdown menu item for unauthenticated users (#25964)
This commit is contained in:
		
							parent
							
								
									644c5fddd8
								
							
						
					
					
						commit
						41f65edb21
					
				
					 10 changed files with 194 additions and 76 deletions
				
			
		|  | @ -1,17 +1,20 @@ | ||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class Api::Web::EmbedsController < Api::Web::BaseController | class Api::Web::EmbedsController < Api::Web::BaseController | ||||||
|   before_action :require_user! |   include Authorization | ||||||
| 
 | 
 | ||||||
|   def create |   before_action :set_status | ||||||
|     status = StatusFinder.new(params[:url]).status |  | ||||||
| 
 | 
 | ||||||
|     return not_found if status.hidden? |   def show | ||||||
|  |     return not_found if @status.hidden? | ||||||
| 
 | 
 | ||||||
|     render json: status, serializer: OEmbedSerializer, width: 400 |     if @status.local? | ||||||
|   rescue ActiveRecord::RecordNotFound |       render json: @status, serializer: OEmbedSerializer, width: 400 | ||||||
|     oembed = FetchOEmbedService.new.call(params[:url]) |     else | ||||||
|  |       return not_found unless user_signed_in? | ||||||
| 
 | 
 | ||||||
|  |       url = ActivityPub::TagManager.instance.url_for(@status) | ||||||
|  |       oembed = FetchOEmbedService.new.call(url) | ||||||
|       return not_found if oembed.nil? |       return not_found if oembed.nil? | ||||||
| 
 | 
 | ||||||
|       begin |       begin | ||||||
|  | @ -22,4 +25,12 @@ class Api::Web::EmbedsController < Api::Web::BaseController | ||||||
| 
 | 
 | ||||||
|       render json: oembed |       render json: oembed | ||||||
|     end |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def set_status | ||||||
|  |     @status = Status.find(params[:id]) | ||||||
|  |     authorize @status, :show? | ||||||
|  |   rescue Mastodon::NotPermittedError | ||||||
|  |     not_found | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -258,7 +258,7 @@ class StatusActionBar extends ImmutablePureComponent { | ||||||
|       menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick }); |       menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (publicStatus) { |     if (publicStatus && (signedIn || !isRemote)) { | ||||||
|       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); |       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -139,7 +139,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ | ||||||
|     dispatch(openModal({ |     dispatch(openModal({ | ||||||
|       modalType: 'EMBED', |       modalType: 'EMBED', | ||||||
|       modalProps: { |       modalProps: { | ||||||
|         url: status.get('url'), |         id: status.get('id'), | ||||||
|         onError: error => dispatch(showAlertForError(error)), |         onError: error => dispatch(showAlertForError(error)), | ||||||
|       }, |       }, | ||||||
|     })); |     })); | ||||||
|  |  | ||||||
|  | @ -205,7 +205,7 @@ class ActionBar extends PureComponent { | ||||||
|       menu.push({ text: intl.formatMessage(messages.share), action: this.handleShare }); |       menu.push({ text: intl.formatMessage(messages.share), action: this.handleShare }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (publicStatus) { |     if (publicStatus && (signedIn || !isRemote)) { | ||||||
|       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); |       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -110,7 +110,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | ||||||
|     dispatch(openModal({ |     dispatch(openModal({ | ||||||
|       modalType: 'EMBED', |       modalType: 'EMBED', | ||||||
|       modalProps: { |       modalProps: { | ||||||
|         url: status.get('url'), |         id: status.get('id'), | ||||||
|         onError: error => dispatch(showAlertForError(error)), |         onError: error => dispatch(showAlertForError(error)), | ||||||
|       }, |       }, | ||||||
|     })); |     })); | ||||||
|  |  | ||||||
|  | @ -449,7 +449,7 @@ class Status extends ImmutablePureComponent { | ||||||
|   handleEmbed = (status) => { |   handleEmbed = (status) => { | ||||||
|     this.props.dispatch(openModal({ |     this.props.dispatch(openModal({ | ||||||
|       modalType: 'EMBED', |       modalType: 'EMBED', | ||||||
|       modalProps: { url: status.get('url') }, |       modalProps: { id: status.get('id') }, | ||||||
|     })); |     })); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ const messages = defineMessages({ | ||||||
| class EmbedModal extends ImmutablePureComponent { | class EmbedModal extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     url: PropTypes.string.isRequired, |     id: PropTypes.string.isRequired, | ||||||
|     onClose: PropTypes.func.isRequired, |     onClose: PropTypes.func.isRequired, | ||||||
|     onError: PropTypes.func.isRequired, |     onError: PropTypes.func.isRequired, | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|  | @ -26,11 +26,11 @@ class EmbedModal extends ImmutablePureComponent { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     const { url } = this.props; |     const { id } = this.props; | ||||||
| 
 | 
 | ||||||
|     this.setState({ loading: true }); |     this.setState({ loading: true }); | ||||||
| 
 | 
 | ||||||
|     api().post('/api/web/embed', { url }).then(res => { |     api().get(`/api/web/embeds/${id}`).then(res => { | ||||||
|       this.setState({ loading: false, oembed: res.data }); |       this.setState({ loading: false, oembed: res.data }); | ||||||
| 
 | 
 | ||||||
|       const iframeDocument = this.iframe.contentWindow.document; |       const iframeDocument = this.iframe.contentWindow.document; | ||||||
|  |  | ||||||
|  | @ -296,7 +296,7 @@ namespace :api, format: false do | ||||||
| 
 | 
 | ||||||
|   namespace :web do |   namespace :web do | ||||||
|     resource :settings, only: [:update] |     resource :settings, only: [:update] | ||||||
|     resource :embed, only: [:create] |     resources :embeds, only: [:show] | ||||||
|     resources :push_subscriptions, only: [:create] do |     resources :push_subscriptions, only: [:create] do | ||||||
|       member do |       member do | ||||||
|         put :update |         put :update | ||||||
|  |  | ||||||
|  | @ -1,54 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| require 'rails_helper' |  | ||||||
| 
 |  | ||||||
| describe Api::Web::EmbedsController do |  | ||||||
|   render_views |  | ||||||
| 
 |  | ||||||
|   let(:user) { Fabricate(:user) } |  | ||||||
| 
 |  | ||||||
|   before { sign_in user } |  | ||||||
| 
 |  | ||||||
|   describe 'POST #create' do |  | ||||||
|     subject(:body) { JSON.parse(response.body, symbolize_names: true) } |  | ||||||
| 
 |  | ||||||
|     let(:response) { post :create, params: { url: url } } |  | ||||||
| 
 |  | ||||||
|     context 'when successfully finds status' do |  | ||||||
|       let(:status) { Fabricate(:status) } |  | ||||||
|       let(:url) { "http://#{Rails.configuration.x.web_domain}/@#{status.account.username}/#{status.id}" } |  | ||||||
| 
 |  | ||||||
|       it 'returns a right response' do |  | ||||||
|         expect(response).to have_http_status 200 |  | ||||||
|         expect(body[:author_name]).to eq status.account.username |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when fails to find status' do |  | ||||||
|       let(:url) { 'https://host.test/oembed.html' } |  | ||||||
|       let(:service_instance) { instance_double(FetchOEmbedService) } |  | ||||||
| 
 |  | ||||||
|       before do |  | ||||||
|         allow(FetchOEmbedService).to receive(:new) { service_instance } |  | ||||||
|         allow(service_instance).to receive(:call) { call_result } |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       context 'when successfully fetching oembed' do |  | ||||||
|         let(:call_result) { { result: :ok } } |  | ||||||
| 
 |  | ||||||
|         it 'returns a right response' do |  | ||||||
|           expect(response).to have_http_status 200 |  | ||||||
|           expect(body[:result]).to eq 'ok' |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       context 'when fails to fetch oembed' do |  | ||||||
|         let(:call_result) { nil } |  | ||||||
| 
 |  | ||||||
|         it 'returns a right response' do |  | ||||||
|           expect(response).to have_http_status 404 |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
							
								
								
									
										161
									
								
								spec/requests/api/web/embeds_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								spec/requests/api/web/embeds_spec.rb
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,161 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'rails_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe '/api/web/embed' do | ||||||
|  |   subject { get "/api/web/embeds/#{id}", headers: headers } | ||||||
|  | 
 | ||||||
|  |   context 'when accessed anonymously' do | ||||||
|  |     let(:headers) { {} } | ||||||
|  | 
 | ||||||
|  |     context 'when the requested status is local' do | ||||||
|  |       let(:id) { status.id } | ||||||
|  | 
 | ||||||
|  |       context 'when the requested status is public' do | ||||||
|  |         let(:status) { Fabricate(:status, visibility: :public) } | ||||||
|  | 
 | ||||||
|  |         it 'returns JSON with an html attribute' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_http_status(200) | ||||||
|  |           expect(body_as_json[:html]).to be_present | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when the requested status is private' do | ||||||
|  |         let(:status) { Fabricate(:status, visibility: :private) } | ||||||
|  | 
 | ||||||
|  |         it 'returns http not found' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_http_status(404) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the requested status is remote' do | ||||||
|  |       let(:remote_account) { Fabricate(:account, domain: 'example.com') } | ||||||
|  |       let(:status)         { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') } | ||||||
|  |       let(:id)             { status.id } | ||||||
|  | 
 | ||||||
|  |       it 'returns http not found' do | ||||||
|  |         subject | ||||||
|  | 
 | ||||||
|  |         expect(response).to have_http_status(404) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the requested status does not exist' do | ||||||
|  |       let(:id) { -1 } | ||||||
|  | 
 | ||||||
|  |       it 'returns http not found' do | ||||||
|  |         subject | ||||||
|  | 
 | ||||||
|  |         expect(response).to have_http_status(404) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'with an API token' do | ||||||
|  |     let(:user)    { Fabricate(:user) } | ||||||
|  |     let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') } | ||||||
|  |     let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } | ||||||
|  | 
 | ||||||
|  |     context 'when the requested status is local' do | ||||||
|  |       let(:id) { status.id } | ||||||
|  | 
 | ||||||
|  |       context 'when the requested status is public' do | ||||||
|  |         let(:status) { Fabricate(:status, visibility: :public) } | ||||||
|  | 
 | ||||||
|  |         it 'returns JSON with an html attribute' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_http_status(200) | ||||||
|  |           expect(body_as_json[:html]).to be_present | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the requesting user is blocked' do | ||||||
|  |           before do | ||||||
|  |             status.account.block!(user.account) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it 'returns http not found' do | ||||||
|  |             subject | ||||||
|  | 
 | ||||||
|  |             expect(response).to have_http_status(404) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when the requested status is private' do | ||||||
|  |         let(:status) { Fabricate(:status, visibility: :private) } | ||||||
|  | 
 | ||||||
|  |         before do | ||||||
|  |           user.account.follow!(status.account) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         it 'returns http not found' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_http_status(404) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the requested status is remote' do | ||||||
|  |       let(:remote_account) { Fabricate(:account, domain: 'example.com') } | ||||||
|  |       let(:status)         { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') } | ||||||
|  |       let(:id)             { status.id } | ||||||
|  | 
 | ||||||
|  |       let(:service_instance) { instance_double(FetchOEmbedService) } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         allow(FetchOEmbedService).to receive(:new) { service_instance } | ||||||
|  |         allow(service_instance).to receive(:call) { call_result } | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when the requesting user is blocked' do | ||||||
|  |         before do | ||||||
|  |           status.account.block!(user.account) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         it 'returns http not found' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_http_status(404) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when successfully fetching OEmbed' do | ||||||
|  |         let(:call_result) { { html: 'ok' } } | ||||||
|  | 
 | ||||||
|  |         it 'returns JSON with an html attribute' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_http_status(200) | ||||||
|  |           expect(body_as_json[:html]).to be_present | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when failing to fetch OEmbed' do | ||||||
|  |         let(:call_result) { nil } | ||||||
|  | 
 | ||||||
|  |         it 'returns http not found' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_http_status(404) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the requested status does not exist' do | ||||||
|  |       let(:id) { -1 } | ||||||
|  | 
 | ||||||
|  |       it 'returns http not found' do | ||||||
|  |         subject | ||||||
|  | 
 | ||||||
|  |         expect(response).to have_http_status(404) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
		Loading…
	
		Reference in a new issue