diff options
author | David Yip <yipdw@member.fsf.org> | 2017-09-09 14:27:47 -0500 |
---|---|---|
committer | David Yip <yipdw@member.fsf.org> | 2017-09-09 14:27:47 -0500 |
commit | b9f7bc149b2a6abfbdaee83e6992b617b8bdb18e (patch) | |
tree | 355225f4424a6ea1b40c66c5540ccab42096e3bf /app/controllers | |
parent | e18ed4bbc7ab4e258d05a3e2a5db0790f67a8f37 (diff) | |
parent | 5d170587e3b6c1a3b3ebe0910b62a4c526e2900d (diff) |
Merge branch 'origin/master' into sync/upstream
Conflicts: app/javascript/mastodon/components/status_list.js app/javascript/mastodon/features/notifications/index.js app/javascript/mastodon/features/ui/components/modal_root.js app/javascript/mastodon/features/ui/components/onboarding_modal.js app/javascript/mastodon/features/ui/index.js app/javascript/styles/about.scss app/javascript/styles/accounts.scss app/javascript/styles/components.scss app/presenters/instance_presenter.rb app/services/post_status_service.rb app/services/reblog_service.rb app/views/about/more.html.haml app/views/about/show.html.haml app/views/accounts/_header.html.haml config/webpack/loaders/babel.js spec/controllers/api/v1/accounts/credentials_controller_spec.rb
Diffstat (limited to 'app/controllers')
23 files changed, 337 insertions, 33 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index c270eb000..8dad12f11 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -7,8 +7,17 @@ class AccountsController < ApplicationController def show respond_to do |format| format.html do - @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id]) - @statuses = cache_collection(@statuses, Status) + @pinned_statuses = [] + + if current_account && @account.blocking?(current_account) + @statuses = [] + return + end + + @pinned_statuses = cache_collection(@account.pinned_statuses, Status) unless media_requested? + @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) + @statuses = cache_collection(@statuses, Status) + @next_url = next_url unless @statuses.empty? end format.atom do @@ -17,14 +26,55 @@ class AccountsController < ApplicationController end format.json do - render json: @account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter + render json: @account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end end end private + def filtered_statuses + default_statuses.tap do |statuses| + statuses.merge!(only_media_scope) if media_requested? + statuses.merge!(no_replies_scope) unless replies_requested? + end + end + + def default_statuses + @account.statuses.where(visibility: [:public, :unlisted]) + end + + def only_media_scope + Status.where(id: account_media_status_ids) + end + + def account_media_status_ids + @account.media_attachments.attached.reorder(nil).select(:status_id).distinct + end + + def no_replies_scope + Status.without_replies + end + def set_account @account = Account.find_local!(params[:username]) end + + def next_url + if media_requested? + short_account_media_url(@account, max_id: @statuses.last.id) + elsif replies_requested? + short_account_with_replies_url(@account, max_id: @statuses.last.id) + else + short_account_url(@account, max_id: @statuses.last.id) + end + end + + def media_requested? + request.path.ends_with?('/media') + end + + def replies_requested? + request.path.ends_with?('/with_replies') + end end diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb new file mode 100644 index 000000000..5fce505fd --- /dev/null +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class ActivityPub::InboxesController < Api::BaseController + include SignatureVerification + + before_action :set_account + + def create + if signed_request_account + upgrade_account + process_payload + head 201 + else + head 202 + end + end + + private + + def set_account + @account = Account.find_local!(params[:account_username]) if params[:account_username] + end + + def body + @body ||= request.body.read + end + + def upgrade_account + return unless signed_request_account.subscribed? + Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) + end + + def process_payload + ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8')) + end +end diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 30b91f370..9f97ff622 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -7,7 +7,7 @@ class ActivityPub::OutboxesController < Api::BaseController @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id]) @statuses = cache_collection(@statuses, Status) - render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter + render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end private diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 7bceee2cd..54c659e1b 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -17,7 +17,7 @@ module Admin end def unsubscribe - UnsubscribeService.new.call(@account) + Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id) redirect_to admin_account_path(@account.id) end diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index 50712f0dd..b05000b16 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -9,7 +9,7 @@ module Admin before_action :set_account before_action :set_status, only: [:update, :destroy] - PAR_PAGE = 20 + PER_PAGE = 20 def index @statuses = @account.statuses @@ -17,7 +17,7 @@ module Admin account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct @statuses.merge!(Status.where(id: account_media_status_ids)) end - @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PAR_PAGE) + @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE) @form = Form::StatusBatch.new end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 105a2859d..7cfe8fe71 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -43,7 +43,7 @@ class Api::BaseController < ApplicationController links = [] links << [next_path, [%w(rel next)]] if next_path links << [prev_path, [%w(rel prev)]] if prev_path - response.headers['Link'] = LinkHeader.new(links) + response.headers['Link'] = LinkHeader.new(links) unless links.empty? end def limit_param(default_limit) @@ -62,10 +62,11 @@ class Api::BaseController < ApplicationController end def require_user! - current_resource_owner - set_user_activity - rescue ActiveRecord::RecordNotFound - render json: { error: 'This method requires an authenticated user' }, status: 422 + if current_user + set_user_activity + else + render json: { error: 'This method requires an authenticated user' }, status: 422 + end end def render_empty diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb index f8c87dd16..37a163cd3 100644 --- a/app/controllers/api/oembed_controller.rb +++ b/app/controllers/api/oembed_controller.rb @@ -4,14 +4,14 @@ class Api::OEmbedController < Api::BaseController respond_to :json def show - @stream_entry = find_stream_entry.stream_entry - render json: @stream_entry, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default + @status = status_finder.status + render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default end private - def find_stream_entry - StreamEntryFinder.new(params[:url]) + def status_finder + StatusFinder.new(params[:url]) end def maxwidth_or_default diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 073808532..da534d960 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Api::V1::Accounts::CredentialsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read }, except: [:update] before_action -> { doorkeeper_authorize! :write }, only: [:update] before_action :require_user! @@ -10,8 +11,9 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController end def update - current_account.update!(account_params) @account = current_account + UpdateAccountService.new.call(@account, account_params, raise_error: true) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) render json: @account, serializer: REST::CredentialAccountSerializer end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index d9ae5c089..095f6937b 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -29,6 +29,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def account_statuses default_statuses.tap do |statuses| statuses.merge!(only_media_scope) if params[:only_media] + statuses.merge!(pinned_scope) if params[:pinned] statuses.merge!(no_replies_scope) if params[:exclude_replies] end end @@ -53,6 +54,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController @account.media_attachments.attached.reorder(nil).select(:status_id).distinct end + def pinned_scope + @account.pinned_statuses + end + def no_replies_scope Status.without_replies end diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb new file mode 100644 index 000000000..3de1009b8 --- /dev/null +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::PinsController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write } + before_action :require_user! + before_action :set_status + + respond_to :json + + def create + StatusPin.create!(account: current_account, status: @status) + render json: @status, serializer: REST::StatusSerializer + end + + def destroy + pin = StatusPin.find_by(account: current_account, status: @status) + pin&.destroy! + render json: @status, serializer: REST::StatusSerializer + end + + private + + def set_status + @status = Status.find(params[:status_id]) + end +end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 9c7124d0f..544a4ce21 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -29,7 +29,7 @@ class Api::V1::StatusesController < Api::BaseController end def card - @card = PreviewCard.find_by(status: @status) + @card = @status.preview_cards.first if @card.nil? render_empty diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb new file mode 100644 index 000000000..2ed516161 --- /dev/null +++ b/app/controllers/api/web/embeds_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Api::Web::EmbedsController < Api::BaseController + respond_to :json + + before_action :require_user! + + def create + status = StatusFinder.new(params[:url]).status + render json: status, serializer: OEmbedSerializer, width: 400 + rescue ActiveRecord::RecordNotFound + oembed = OEmbed::Providers.get(params[:url]) + render json: Oj.dump(oembed.fields) + rescue OEmbed::NotFound + render json: {}, status: :not_found + end +end diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb index d36fc8c93..5b9981aa2 100644 --- a/app/controllers/concerns/account_controller_concern.rb +++ b/app/controllers/concerns/account_controller_concern.rb @@ -23,6 +23,7 @@ module AccountControllerConcern [ webfinger_account_link, atom_account_url_link, + actor_url_link, ] ) end @@ -41,6 +42,13 @@ module AccountControllerConcern ] end + def actor_url_link + [ + ActivityPub::TagManager.instance.uri_for(@account), + [%w(rel alternate), %w(type application/activity+json)], + ] + end + def webfinger_account_url webfinger_url(resource: @account.to_webfinger_s) end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index abe845d93..4211283ed 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -31,7 +31,7 @@ module SignatureVerification return end - account = ResolveRemoteAccountService.new.call(signature_params['keyId'].gsub(/\Aacct:/, '')) + account = account_from_key_id(signature_params['keyId']) if account.nil? @signed_request_account = nil @@ -49,6 +49,10 @@ module SignatureVerification end end + def request_body + @request_body ||= request.raw_post + end + private def build_signed_string(signed_headers) @@ -57,6 +61,8 @@ module SignatureVerification signed_headers.split(' ').map do |signed_header| if signed_header == Request::REQUEST_TARGET "#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}" + elsif signed_header == 'digest' + "digest: #{body_digest}" else "#{signed_header}: #{request.headers[to_header_name(signed_header)]}" end @@ -73,6 +79,10 @@ module SignatureVerification (Time.now.utc - time_sent).abs <= 30 end + def body_digest + "SHA-256=#{Digest::SHA256.base64digest(request_body)}" + end + def to_header_name(name) name.split(/-/).map(&:capitalize).join('-') end @@ -81,7 +91,16 @@ module SignatureVerification signature_params['keyId'].blank? || signature_params['signature'].blank? || signature_params['algorithm'].blank? || - signature_params['algorithm'] != 'rsa-sha256' || - !signature_params['keyId'].start_with?('acct:') + signature_params['algorithm'] != 'rsa-sha256' + end + + def account_from_key_id(key_id) + if key_id.start_with?('acct:') + ResolveRemoteAccountService.new.call(key_id.gsub(/\Aacct:/, '')) + elsif !ActivityPub::TagManager.instance.local_uri?(key_id) + account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account) + account ||= ActivityPub::FetchRemoteKeyService.new.call(key_id) + account + end end end diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 5edb4d67c..0e1949897 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -10,7 +10,7 @@ class FollowerAccountsController < ApplicationController format.html format.json do - render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter + render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end end end diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 7cafe5fda..d4593093f 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -10,7 +10,7 @@ class FollowingAccountsController < ApplicationController format.html format.json do - render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter + render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end end end diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb new file mode 100644 index 000000000..504befd1f --- /dev/null +++ b/app/controllers/intents_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class IntentsController < ApplicationController + def show + uri = Addressable::URI.parse(params[:uri]) + + if uri.scheme == 'web+mastodon' + case uri.host + when 'follow' + return redirect_to authorize_follow_path(acct: uri.query_values['uri'].gsub(/\Aacct:/, '')) + when 'share' + return redirect_to share_path(text: uri.query_values['text']) + end + end + + not_found + end +end diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb new file mode 100644 index 000000000..8fc9a0fa9 --- /dev/null +++ b/app/controllers/settings/applications_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class Settings::ApplicationsController < ApplicationController + layout 'admin' + + before_action :authenticate_user! + before_action :set_application, only: [:show, :update, :destroy, :regenerate] + before_action :prepare_scopes, only: [:create, :update] + + def index + @applications = current_user.applications.page(params[:page]) + end + + def new + @application = Doorkeeper::Application.new( + redirect_uri: Doorkeeper.configuration.native_redirect_uri, + scopes: 'read write follow' + ) + end + + def show; end + + def create + @application = current_user.applications.build(application_params) + + if @application.save + redirect_to settings_applications_path, notice: I18n.t('applications.created') + else + render :new + end + end + + def update + if @application.update(application_params) + redirect_to settings_applications_path, notice: I18n.t('generic.changes_saved_msg') + else + render :show + end + end + + def destroy + @application.destroy + redirect_to settings_applications_path, notice: I18n.t('applications.destroyed') + end + + def regenerate + @access_token = current_user.token_for_app(@application) + @access_token.destroy + + redirect_to settings_application_path(@application), notice: I18n.t('applications.token_regenerated') + end + + private + + def set_application + @application = current_user.applications.find(params[:id]) + end + + def application_params + params.require(:doorkeeper_application).permit( + :name, + :redirect_uri, + :scopes, + :website + ) + end + + def prepare_scopes + scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil) + params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array + end +end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 0367e3593..28f78a4fb 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -14,7 +14,8 @@ class Settings::ProfilesController < ApplicationController def show; end def update - if @account.update(account_params) + if UpdateAccountService.new.call(@account, account_params) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg') else render :show diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb new file mode 100644 index 000000000..994742c3d --- /dev/null +++ b/app/controllers/shares_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class SharesController < ApplicationController + layout 'modal' + + before_action :authenticate_user! + before_action :set_body_classes + + def show + serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) + @initial_state_json = serializable_resource.to_json + end + + private + + def initial_state_params + { + settings: Web::Setting.find_by(user: current_user)&.data || {}, + push_subscription: current_account.user.web_push_subscription(current_session), + current_account: current_account, + token: current_session.token, + admin: Account.find_local(Setting.site_contact_username), + text: params[:text], + } + end + + def set_body_classes + @body_classes = 'compose-standalone' + end +end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 8e0ce0ec3..65206ea96 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -9,6 +9,7 @@ class StatusesController < ApplicationController before_action :set_status before_action :set_link_headers before_action :check_account_suspension + before_action :redirect_to_original, only: [:show] def show respond_to do |format| @@ -20,13 +21,18 @@ class StatusesController < ApplicationController end format.json do - render json: @status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter + render json: @status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end end end def activity - render json: @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter + render json: @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' + end + + def embed + response.headers['X-Frame-Options'] = 'ALLOWALL' + render 'stream_entries/embed', layout: 'embedded' end private @@ -36,7 +42,12 @@ class StatusesController < ApplicationController end def set_link_headers - response.headers['Link'] = LinkHeader.new([[account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]]]) + response.headers['Link'] = LinkHeader.new( + [ + [account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]], + [ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]], + ] + ) end def set_status @@ -53,4 +64,8 @@ class StatusesController < ApplicationController def check_account_suspension gone if @account.suspended? end + + def redirect_to_original + redirect_to ::TagManager.instance.url_for(@status.reblog) if @status.reblog? + end end diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 3eb91d830..cc579dbc8 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -25,10 +25,7 @@ class StreamEntriesController < ApplicationController end def embed - response.headers['X-Frame-Options'] = 'ALLOWALL' - return gone if @stream_entry.activity.nil? - - render layout: 'embedded' + redirect_to embed_short_account_status_url(@account, @stream_entry.activity), status: 301 end private @@ -38,7 +35,12 @@ class StreamEntriesController < ApplicationController end def set_link_headers - response.headers['Link'] = LinkHeader.new([[account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]]]) + response.headers['Link'] = LinkHeader.new( + [ + [account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]], + [ActivityPub::TagManager.instance.uri_for(@stream_entry.activity), [%w(rel alternate), %w(type application/activity+json)]], + ] + ) end def set_stream_entry diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 2cd85e185..3001b2ee3 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -12,7 +12,7 @@ class TagsController < ApplicationController format.html format.json do - render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter + render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end end end |