diff options
Diffstat (limited to 'app/controllers/api')
17 files changed, 405 insertions, 44 deletions
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 467225547..ac49a4dca 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController include RateLimitHeaders skip_before_action :store_current_location - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional! #, unless: :whitelist_mode? before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access? before_action :set_cache_headers diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 64b5cb747..3c8187a99 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -21,7 +21,9 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController private def account_params - params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value]) + params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, + :require_dereference, :show_replies, :show_unlisted, + fields_attributes: [:name, :value]) end def user_settings_params diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 85a9133e3..d7e973e31 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -8,7 +8,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def index @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_account&.id) end private @@ -17,17 +17,17 @@ class Api::V1::Accounts::StatusesController < Api::BaseController @account = Account.find(params[:account_id]) end + def owner? + @account.id == current_account&.id + end + def load_statuses cached_account_statuses end def cached_account_statuses statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses - statuses.merge!(only_media_scope) if truthy_param?(:only_media) - statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies) - statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) - statuses.merge!(hashtag_scope) if params[:tagged].present? cache_collection_paginated_by_id( statuses, @@ -38,39 +38,66 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def permitted_account_statuses - @account.statuses.permitted_for(@account, current_account) + return mentions_scope if truthy_param?(:mentions) + return Status.none if unauthorized? + + @account.statuses.permitted_for( + @account, + current_account, + include_semiprivate: true, + include_reblogs: include_reblogs?, + include_replies: include_replies?, + only_reblogs: only_reblogs?, + only_replies: only_replies?, + include_unpublished: owner?, + tag: params[:tagged] + ) end def only_media_scope Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id) end - def pinned_scope - return Status.none if @account.blocking?(current_account) + def unauthorized? + (@account.private && !following?(@account)) || (@account.require_auth && !current_account?) + end - @account.pinned_statuses + def include_reblogs? + params[:include_reblogs].present? ? truthy_param?(:include_reblogs) : !truthy_param?(:exclude_reblogs) + end + + def include_replies? + return false unless owner? || @account.show_replies? + + params[:include_replies].present? ? truthy_param?(:include_replies) : !truthy_param?(:exclude_replies) end - def no_replies_scope - Status.without_replies + def only_reblogs? + truthy_param?(:only_reblogs).presence || false end - def no_reblogs_scope - Status.without_reblogs + def only_replies? + return false unless owner? || @account.show_replies? + + truthy_param?(:only_replies).presence || false end - def hashtag_scope - tag = Tag.find_normalized(params[:tagged]) + def mentions_scope + return Status.none unless current_account? + + Status.mentions_between(@account, current_account) + end - if tag - Status.tagged_with(tag.id) - else - Status.none - end + def pinned_scope + return Status.none if @account.blocking?(current_account) + + @account.pinned_statuses end def pagination_params(core_params) - params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) + params.slice(:limit, :only_media, :include_replies, :exclude_replies, :only_replies, :include_reblogs, :exclude_reblogs, :only_relogs, :mentions) + .permit(:limit, :only_media, :include_replies, :exclude_replies, :only_replies, :include_reblogs, :exclude_reblogs, :only_relogs, :mentions) + .merge(core_params) end def insert_pagination_headers @@ -78,15 +105,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def next_path - if records_continue? - api_v1_account_statuses_url pagination_params(max_id: pagination_max_id) - end + api_v1_account_statuses_url pagination_params(max_id: pagination_max_id) if records_continue? end def prev_path - unless @statuses.empty? - api_v1_account_statuses_url pagination_params(min_id: pagination_since_id) - end + api_v1_account_statuses_url pagination_params(min_id: pagination_since_id) unless @statuses.empty? end def records_continue? diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 0080faf33..e9f848ac9 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -44,7 +44,7 @@ class Api::V1::AccountsController < Api::BaseController end def mute - MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications)) + MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), timelines_only: truthy_param?(:timelines_only)) render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end diff --git a/app/controllers/api/v1/admin/domain_allows_controller.rb b/app/controllers/api/v1/admin/domain_allows_controller.rb new file mode 100644 index 000000000..1b150d480 --- /dev/null +++ b/app/controllers/api/v1/admin/domain_allows_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Api::V1::Admin::DomainAllowsController < Api::BaseController + include Authorization + + LIMIT = 100 + + before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:domain_allows' }, only: :show + before_action :require_staff! + after_action :insert_pagination_headers, only: :show + + def show + @allows = load_domain_allows + render json: @allows + end + + private + + def load_domain_allows + DomainAllow.paginate_by_max_id( + limit_param(LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_domain_allows_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_admin_domain_allows_url pagination_params(since_id: pagination_since_id) unless @allows.empty? + end + + def pagination_max_id + @allows.last.id + end + + def pagination_since_id + @allows.first.id + end + + def records_continue? + @allows.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end +end diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb new file mode 100644 index 000000000..c0ce0da25 --- /dev/null +++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Api::V1::Admin::DomainBlocksController < Api::BaseController + include Authorization + + LIMIT = 100 + + before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:domain_blocks' }, only: :show + before_action :require_staff! + after_action :insert_pagination_headers, only: :show + + def show + @blocks = load_domain_blocks + render json: @blocks + end + + private + + def load_domain_blocks + DomainBlock.paginate_by_max_id( + limit_param(LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_domain_blocks_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_admin_domain_blocks_url pagination_params(since_id: pagination_since_id) unless @blocks.empty? + end + + def pagination_max_id + @blocks.last.id + end + + def pagination_since_id + @blocks.first.id + end + + def records_continue? + @blocks.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end +end diff --git a/app/controllers/api/v1/domain_permissions_controller.rb b/app/controllers/api/v1/domain_permissions_controller.rb new file mode 100644 index 000000000..1b0e37135 --- /dev/null +++ b/app/controllers/api/v1/domain_permissions_controller.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +class Api::V1::DomainPermissionsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:domain_permissions', :'read:domain_permissions:account' }, only: :show + before_action -> { doorkeeper_authorize! :write, :'write:domain_permissions', :'write:domain_permissions:account' }, only: [:create, :update, :destroy] + before_action :require_user! + before_action :set_permission, except: [:show, :create] + after_action :insert_pagination_headers + + LIMIT = 100 + + def show + @permissions = load_account_domain_permissions + render json: @permissions, each_serializer: REST::AccountDomainPermissionSerializer + end + + def create + @permission = current_account.domain_permissions.create!(domain_permission_params) + render json: @permission, serializer: REST::AccountDomainPermissionSerializer + end + + def update + @permission.update!(domain_permission_params) + render json: @permission, serializer: REST::AccountDomainPermissionSerializer + end + + def destroy + @permission.destroy! + render_empty + end + + private + + def load_account_domain_permissions + account_domain_permissions.paginate_by_max_id( + limit_param(LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def set_permission + @permission = current_account.domain_permissions.find(params[:id]) + end + + def account_domain_permissions + current_account.domain_permissions + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_domain_permissions_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_domain_permissions_url pagination_params(since_id: pagination_since_id) unless @permissions.empty? + end + + def pagination_max_id + @permissions.last.id + end + + def pagination_since_id + @permissions.first.id + end + + def records_continue? + @permissions.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end + + def domain_permission_params + params.permit(:domain, :visibility) + end +end diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb index 4f6b4bcbf..f2ac902e1 100644 --- a/app/controllers/api/v1/instances/activity_controller.rb +++ b/app/controllers/api/v1/instances/activity_controller.rb @@ -4,7 +4,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController before_action :require_enabled_api! skip_before_action :set_cache_headers - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user! #, unless: :whitelist_mode? def show expires_in 1.day, public: true @@ -33,6 +33,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController end def require_enabled_api! - head 404 unless Setting.activity_api_enabled && !whitelist_mode? + head 404 unless Setting.activity_api_enabled #&& !whitelist_mode? end end diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index 9fa440935..d30ef1fe9 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -4,7 +4,7 @@ class Api::V1::Instances::PeersController < Api::BaseController before_action :require_enabled_api! skip_before_action :set_cache_headers - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user! #, unless: :whitelist_mode? def index expires_in 1.day, public: true @@ -14,6 +14,6 @@ class Api::V1::Instances::PeersController < Api::BaseController private def require_enabled_api! - head 404 unless Setting.peers_api_enabled && !whitelist_mode? + head 404 unless Setting.peers_api_enabled #&& !whitelist_mode? end end diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index 5b5058a7b..844bab68a 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -2,7 +2,7 @@ class Api::V1::InstancesController < Api::BaseController skip_before_action :set_cache_headers - skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + skip_before_action :require_authenticated_user! #, unless: :whitelist_mode? def show expires_in 3.minutes, public: true diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb index 513b937ef..91ca96ef0 100644 --- a/app/controllers/api/v1/polls/votes_controller.rb +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -17,6 +17,7 @@ class Api::V1::Polls::VotesController < Api::BaseController def set_poll @poll = Poll.attached.find(params[:poll_id]) authorize @poll.status, :show? + authorize @poll.status.reblog, :show? if @poll.status.reblog? rescue Mastodon::NotPermittedError not_found end diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb index 6435e9f0d..75f5a9f08 100644 --- a/app/controllers/api/v1/polls_controller.rb +++ b/app/controllers/api/v1/polls_controller.rb @@ -16,6 +16,7 @@ class Api::V1::PollsController < Api::BaseController def set_poll @poll = Poll.attached.find(params[:id]) authorize @poll.status, :show? + authorize @poll.status.reblog, :show? if @poll.status.reblog? rescue Mastodon::NotPermittedError not_found end diff --git a/app/controllers/api/v1/statuses/hides_controller.rb b/app/controllers/api/v1/statuses/hides_controller.rb new file mode 100644 index 000000000..8c5457c82 --- /dev/null +++ b/app/controllers/api/v1/statuses/hides_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::HidesController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write, :'write:mutes' } + before_action :require_user! + before_action :set_status + + def create + MuteStatusService.new.call(current_account, @status) + render json: @status, serializer: REST::StatusSerializer + end + + def destroy + current_account.unmute_status!(@status) + render json: @status, serializer: REST::StatusSerializer + end + + private + + def set_status + @status = Status.find(params[:status_id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end +end diff --git a/app/controllers/api/v1/statuses/mutes_controller.rb b/app/controllers/api/v1/statuses/mutes_controller.rb index 87071a2b9..418c19840 100644 --- a/app/controllers/api/v1/statuses/mutes_controller.rb +++ b/app/controllers/api/v1/statuses/mutes_controller.rb @@ -9,12 +9,14 @@ class Api::V1::Statuses::MutesController < Api::BaseController before_action :set_conversation def create - current_account.mute_conversation!(@conversation) + MuteConversationService.new.call(current_account, @status.conversation) @mutes_map = { @conversation.id => true } render json: @status, serializer: REST::StatusSerializer end + alias update create + def destroy current_account.unmute_conversation!(@conversation) @mutes_map = { @conversation.id => false } diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb index 51b1621b6..187b6145c 100644 --- a/app/controllers/api/v1/statuses/pins_controller.rb +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -9,7 +9,7 @@ class Api::V1::Statuses::PinsController < Api::BaseController def create StatusPin.create!(account: current_account, status: @status) - distribute_add_activity! + distribute_add_activity! unless @status.semiprivate? render json: @status, serializer: REST::StatusSerializer end diff --git a/app/controllers/api/v1/statuses/publishing_controller.rb b/app/controllers/api/v1/statuses/publishing_controller.rb new file mode 100644 index 000000000..97c052e22 --- /dev/null +++ b/app/controllers/api/v1/statuses/publishing_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::PublishingController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write, :'write:statuses:publish' } + before_action :require_user! + before_action :set_status + + def create + PublishStatusService.new.call(@status) + + render json: @status, + serializer: (@status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer), + source_requested: truthy_param?(:source) + end + + private + + def set_status + @status = Status.unpublished.find(params[:status_id]) + authorize @status, :destroy? + rescue Mastodon::NotPermittedError + not_found + end +end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index c8529318f..b0cc95118 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -19,7 +19,7 @@ class Api::V1::StatusesController < Api::BaseController def show @status = cache_collection([@status], Status).first - render json: @status, serializer: REST::StatusSerializer + render json: @status, serializer: REST::StatusSerializer, source_requested: truthy_param?(:source) end def context @@ -31,7 +31,7 @@ class Api::V1::StatusesController < Api::BaseController @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants) statuses = [@status] + @context.ancestors + @context.descendants - render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) + render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id), current_account_id: current_user&.account_id end def create @@ -41,24 +41,82 @@ class Api::V1::StatusesController < Api::BaseController media_ids: status_params[:media_ids], sensitive: status_params[:sensitive], spoiler_text: status_params[:spoiler_text], + title: status_params[:title], + footer: status_params[:footer], + notify: status_params[:notify], + publish: status_params[:publish], visibility: status_params[:visibility], + local_only: status_params[:local_only], scheduled_at: status_params[:scheduled_at], application: doorkeeper_token.application, poll: status_params[:poll], content_type: status_params[:content_type], + tags: parse_tags_param(status_params[:tags]), + mentions: parse_mentions_param(status_params[:mentions]), idempotency: request.headers['Idempotency-Key'], - with_rate_limit: true) + with_rate_limit: true, + expires_at: status_params[:expires_at], + publish_at: status_params[:publish_at]) - render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer + render json: @status, + serializer: (@status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer), + source_requested: truthy_param?(:source) + end + + def update + @status = Status.where(account_id: current_user.account).find(params[:id]) + authorize @status, :destroy? + + @status = PostStatusService.new.call(current_user.account, + text: status_params[:status], + thread: @thread, + media_ids: status_params[:media_ids], + sensitive: status_params[:sensitive], + spoiler_text: status_params[:spoiler_text], + title: status_params[:title], + footer: status_params[:footer], + notify: status_params[:notify], + publish: status_params[:publish], + visibility: status_params[:visibility], + local_only: status_params[:local_only], + scheduled_at: status_params[:scheduled_at], + application: doorkeeper_token.application, + poll: status_params[:poll], + content_type: status_params[:content_type], + status: @status, + tags: parse_tags_param(status_params[:tags]), + mentions: parse_mentions_param(status_params[:mentions]), + idempotency: request.headers['Idempotency-Key'], + with_rate_limit: true, + expires_at: status_params[:expires_at], + publish_at: status_params[:publish_at]) + + render json: @status, + serializer: (@status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer), + source_requested: truthy_param?(:source) end def destroy @status = Status.where(account_id: current_user.account).find(params[:id]) authorize @status, :destroy? - @status.discard - RemovalWorker.perform_async(@status.id, redraft: true) - @status.account.statuses_count = @status.account.statuses_count - 1 + if !current_user.setting_unpublish_on_delete || !@status.published? || truthy_param?(:redraft) + @status.discard + RemovalWorker.perform_async(@status.id, redraft: true, immediate: !current_user.setting_unpublish_on_delete) + @status.account.statuses_count = @status.account.statuses_count - 1 + else + RemovalWorker.perform_async(@status.id, redraft: true, unpublish: true) + tag_script = "#!redraft #{@status.id}\n" + @status.text = "#{tag_script}#{@status.text.sub(/^\s*#!redraft \d+\n/, '')}" + @status.original_text = "#{tag_script}#{@status.original_text.sub(/^\s*#!redraft \d+\n/, '')}" + end + + @status.local_only = @status.originally_local_only? + unless @status.original_text.match?(/^\s*#!\s*federate\b/i) + tag_script = "#!federate #{@status.originally_local_only? ? 'off' : 'on'}\n" + @status.text.prepend(tag_script) + @status.original_text.prepend(tag_script) + end render json: @status, serializer: REST::StatusSerializer, source_requested: true end @@ -84,9 +142,18 @@ class Api::V1::StatusesController < Api::BaseController :in_reply_to_id, :sensitive, :spoiler_text, + :title, + :footer, + :notify, + :publish, :visibility, + :local_only, :scheduled_at, :content_type, + :expires_at, + :publish_at, + tags: [], + mentions: [], media_ids: [], poll: [ :multiple, @@ -100,4 +167,26 @@ class Api::V1::StatusesController < Api::BaseController def pagination_params(core_params) params.slice(:limit).permit(:limit).merge(core_params) end + + def parse_tags_param(tags_param) + return if tags_param.blank? + + tags_param.select { |value| value.respond_to?(:to_str) && value.present? } + end + + def parse_mentions_param(mentions_param) + return if mentions_param.blank? + + mentions_param.map do |value| + next if value.blank? + + value = value.split('@', 3) if value.respond_to?(:to_str) + next unless value.is_a?(Enumerable) + + mentioned_account = Account.find_by(username: value[0], domain: value[1]) + next if mentioned_account.nil? || mentioned_account.suspended? + + mentioned_account + end + end end |