Skip to content

Commit

Permalink
Fix embed dropdown menu item for unauthenticated users (#25964)
Browse files Browse the repository at this point in the history
  • Loading branch information
ClearlyClaire committed Jul 13, 2023
1 parent 644c5fd commit 41f65ed
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 76 deletions.
37 changes: 24 additions & 13 deletions app/controllers/api/web/embeds_controller.rb
@@ -1,25 +1,36 @@
# frozen_string_literal: true

class Api::Web::EmbedsController < Api::Web::BaseController
before_action :require_user!
include Authorization

def create
status = StatusFinder.new(params[:url]).status
before_action :set_status

return not_found if status.hidden?
def show
return not_found if @status.hidden?

render json: status, serializer: OEmbedSerializer, width: 400
rescue ActiveRecord::RecordNotFound
oembed = FetchOEmbedService.new.call(params[:url])
if @status.local?
render json: @status, serializer: OEmbedSerializer, width: 400
else
return not_found unless user_signed_in?

return not_found if oembed.nil?
url = ActivityPub::TagManager.instance.url_for(@status)
oembed = FetchOEmbedService.new.call(url)
return not_found if oembed.nil?

begin
oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
rescue ArgumentError
return not_found
begin
oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
rescue ArgumentError
return not_found
end

render json: oembed
end
end

render json: oembed
def set_status
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/status_action_bar.jsx
Expand Up @@ -258,7 +258,7 @@ class StatusActionBar extends ImmutablePureComponent {
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 });
}

Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/containers/status_container.jsx
Expand Up @@ -139,7 +139,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
dispatch(openModal({
modalType: 'EMBED',
modalProps: {
url: status.get('url'),
id: status.get('id'),
onError: error => dispatch(showAlertForError(error)),
},
}));
Expand Down
Expand Up @@ -205,7 +205,7 @@ class ActionBar extends PureComponent {
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 });
}

Expand Down
Expand Up @@ -110,7 +110,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(openModal({
modalType: 'EMBED',
modalProps: {
url: status.get('url'),
id: status.get('id'),
onError: error => dispatch(showAlertForError(error)),
},
}));
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/features/status/index.jsx
Expand Up @@ -449,7 +449,7 @@ class Status extends ImmutablePureComponent {
handleEmbed = (status) => {
this.props.dispatch(openModal({
modalType: 'EMBED',
modalProps: { url: status.get('url') },
modalProps: { id: status.get('id') },
}));
};

Expand Down
Expand Up @@ -14,7 +14,7 @@ const messages = defineMessages({
class EmbedModal extends ImmutablePureComponent {

static propTypes = {
url: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
Expand All @@ -26,11 +26,11 @@ class EmbedModal extends ImmutablePureComponent {
};

componentDidMount () {
const { url } = this.props;
const { id } = this.props;

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 });

const iframeDocument = this.iframe.contentWindow.document;
Expand Down
2 changes: 1 addition & 1 deletion config/routes/api.rb
Expand Up @@ -296,7 +296,7 @@

namespace :web do
resource :settings, only: [:update]
resource :embed, only: [:create]
resources :embeds, only: [:show]
resources :push_subscriptions, only: [:create] do
member do
put :update
Expand Down
54 changes: 0 additions & 54 deletions spec/controllers/api/web/embeds_controller_spec.rb

This file was deleted.

161 changes: 161 additions & 0 deletions spec/requests/api/web/embeds_spec.rb
@@ -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

0 comments on commit 41f65ed

Please sign in to comment.