diff options
Diffstat (limited to 'app/controllers')
45 files changed, 1004 insertions, 181 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 5d5db937c..bf3d3ff42 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -4,16 +4,14 @@ class AboutController < ApplicationController before_action :set_pack layout 'public' - before_action :require_open_federation!, only: [:show, :more] + #before_action :require_open_federation!, only: [:show, :more] before_action :set_body_classes, only: :show before_action :set_instance_presenter before_action :set_expires_in, only: [:show, :more, :terms] skip_before_action :require_functional!, only: [:more, :terms] - def show; end - - def more + def show flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor] toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description) @@ -21,10 +19,15 @@ class AboutController < ApplicationController @contents = toc_generator.html @table_of_contents = toc_generator.toc @blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks? + @allows = DomainAllow.where(hidden: false) if display_allows? end + alias more show + def terms; end + helper_method :display_allows? + helper_method :display_blocks? helper_method :display_blocks_rationale? helper_method :public_fetch_mode? @@ -66,4 +69,10 @@ class AboutController < ApplicationController def set_expires_in expires_in 0, public: true end + + # Monsterfork additions + + def display_allows? + Setting.show_domain_allows == 'all' || (Setting.show_domain_allows == 'users' && user_signed_in?) + end end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 356542767..352f84ea7 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -11,20 +11,24 @@ class AccountsController < ApplicationController before_action :set_cache_headers before_action :set_body_classes + before_action :require_authenticated!, if: -> { @account.require_auth? || @account.private? } + skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) } - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional! # , unless: :whitelist_mode? def show + @without_unlisted = !@account.show_unlisted? + respond_to do |format| format.html do use_pack 'public' - expires_in 0, public: true unless user_signed_in? + expires_in 0, public: true unless user_signed_in? || signed_request_account.present? @pinned_statuses = [] - @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4) - @featured_hashtags = @account.featured_tags.order(statuses_count: :desc) + @endorsed_accounts = unauthorized? ? [] : @account.endorsed_accounts.to_a.sample(4) + @featured_hashtags = unauthorized? ? [] : @account.featured_tags.order(statuses_count: :desc) - if current_account && @account.blocking?(current_account) + if unauthorized? @statuses = [] return end @@ -40,16 +44,19 @@ class AccountsController < ApplicationController end format.rss do - expires_in 1.minute, public: true + return render xml: '', status: 404 if rss_disabled? || unauthorized? + + expires_in 1.minute, public: !current_account? - limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE + @without_unlisted = true + limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE @statuses = filtered_statuses.without_reblogs.limit(limit) @statuses = cache_collection(@statuses, Status) render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag]) end format.json do - expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?) + expires_in 3.minutes, public: !current_account? render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter end end @@ -62,19 +69,27 @@ class AccountsController < ApplicationController end def show_pinned_statuses? - [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none? + [threads_requested?, replies_requested?, reblogs_requested?, mentions_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none? end def filtered_statuses + return mentions_scope if mentions_requested? + default_statuses.tap do |statuses| - statuses.merge!(hashtag_scope) if tag_requested? statuses.merge!(only_media_scope) if media_requested? - statuses.merge!(no_replies_scope) unless replies_requested? end end def default_statuses - @account.statuses.not_local_only.where(visibility: [:public, :unlisted]) + @account.statuses.permitted_for( + @account, + current_account, + include_reblogs: !(threads_requested? || replies_requested?), + only_reblogs: reblogs_requested?, + include_replies: replies_requested?, + tag: tag_requested? ? params[:tag] : nil, + public: @without_unlisted + ) end def only_media_scope @@ -85,18 +100,10 @@ class AccountsController < ApplicationController @account.media_attachments.attached.reorder(nil).select(:status_id).distinct end - def no_replies_scope - Status.without_replies - end + def mentions_scope + return Status.none unless current_account? - def hashtag_scope - tag = Tag.find_normalized(params[:tag]) - - if tag - Status.tagged_with(tag.id) - else - Status.none - end + Status.mentions_between(@account, current_account) end def username_param @@ -124,8 +131,14 @@ class AccountsController < ApplicationController short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id) elsif media_requested? short_account_media_url(@account, max_id: max_id, min_id: min_id) + elsif threads_requested? + short_account_threads_url(@account, max_id: max_id, min_id: min_id) elsif replies_requested? short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) + elsif reblogs_requested? + short_account_reblogs_url(@account, max_id: max_id, min_id: min_id) + elsif mentions_requested? + short_account_mentions_url(@account, max_id: max_id, min_id: min_id) else short_account_url(@account, max_id: max_id, min_id: min_id) end @@ -135,7 +148,13 @@ class AccountsController < ApplicationController request.path.split('.').first.ends_with?('/media') && !tag_requested? end + def threads_requested? + request.path.split('.').first.ends_with?('/threads') && !tag_requested? + end + def replies_requested? + return false unless current_account&.id == @account.id || @account.show_replies? + request.path.split('.').first.ends_with?('/with_replies') && !tag_requested? end @@ -143,6 +162,26 @@ class AccountsController < ApplicationController request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) end + def reblogs_requested? + request.path.split('.').first.ends_with?('/reblogs') && !tag_requested? + end + + def mentions_requested? + request.path.split('.').first.ends_with?('/mentions') && !tag_requested? + end + + def blocked? + @blocked ||= current_account && @account.blocking?(current_account) + end + + def unauthorized? + @unauthorized ||= blocked? || (@account.private? && !following?(@account)) + end + + def rss_disabled? + @account.user&.setting_rss_disabled + end + def cached_filtered_status_page cache_collection_paginated_by_id( filtered_statuses, diff --git a/app/controllers/activitypub/claims_controller.rb b/app/controllers/activitypub/claims_controller.rb index 08ad952df..5009a9f05 100644 --- a/app/controllers/activitypub/claims_controller.rb +++ b/app/controllers/activitypub/claims_controller.rb @@ -4,7 +4,7 @@ class ActivityPub::ClaimsController < ActivityPub::BaseController include SignatureVerification include AccountOwnedConcern - skip_before_action :authenticate_user! + #skip_before_action :authenticate_user! before_action :require_signature! before_action :set_claim_result diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index fdb60d590..4c0e83122 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -7,7 +7,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController before_action :skip_unknown_actor_delete before_action :require_signature! - skip_before_action :authenticate_user! + #skip_before_action :authenticate_user! def create upgrade_account diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 5fd735ad6..7c914298b 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -10,9 +10,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController before_action :set_statuses before_action :set_cache_headers + before_action :require_authenticated!, if: -> { @account.require_auth? } + before_action -> { require_following!(@account) }, if: -> { @account.private? } + def show - expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?)) - render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' + expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(current_account.present? && page_requested?)) + render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain end private @@ -54,11 +57,38 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController account_outbox_url(@account, page: true, min_id: @statuses.first.id) unless @statuses.empty? end + def permitted_account_statuses + @account.statuses.permitted_for( + @account, + current_account, + include_replies: true, + include_reblogs: true, + public: !(owner? || follower?), + exclude_local_only: true + ) + end + + def owner? + return @owner if defined?(@owner) + + @owner = @account.id == current_account&.id + @owner ||= @account.moved_to_account_id == current_account&.id if @account.moved_to_account_id.present? + @owner + end + + def follower? + @following ||= current_account&.following?(@account) + end + + def mutual_follower? + follower? && @account.following?(current_account) + end + def set_statuses return unless page_requested? @statuses = cache_collection_paginated_by_id( - @account.statuses.permitted_for(@account, signed_request_account), + permitted_account_statuses, Status, LIMIT, params_slice(:max_id, :min_id, :since_id) diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index 43bf4e657..fd12f0745 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -14,7 +14,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController def index expires_in 0, public: public_fetch_mode? - render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true + render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true, target_domain: current_account&.domain end private diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb deleted file mode 100644 index 71efb543e..000000000 --- a/app/controllers/admin/custom_emojis_controller.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -module Admin - class CustomEmojisController < BaseController - def index - authorize :custom_emoji, :index? - - @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]) - @form = Form::CustomEmojiBatch.new - end - - def new - authorize :custom_emoji, :create? - - @custom_emoji = CustomEmoji.new - end - - def create - authorize :custom_emoji, :create? - - @custom_emoji = CustomEmoji.new(resource_params) - - if @custom_emoji.save - log_action :create, @custom_emoji - redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg') - else - render :new - end - end - - def batch - @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button)) - @form.save - rescue ActionController::ParameterMissing - flash[:alert] = I18n.t('admin.accounts.no_account_selected') - rescue Mastodon::NotPermittedError - flash[:alert] = I18n.t('admin.custom_emojis.not_permitted') - ensure - redirect_to admin_custom_emojis_path(filter_params) - end - - private - - def resource_params - params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker) - end - - def filtered_custom_emojis - CustomEmojiFilter.new(filter_params).results - end - - def filter_params - params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS) - end - - def action_from_button - if params[:update] - 'update' - elsif params[:list] - 'list' - elsif params[:unlist] - 'unlist' - elsif params[:enable] - 'enable' - elsif params[:disable] - 'disable' - elsif params[:copy] - 'copy' - elsif params[:delete] - 'delete' - end - end - - def form_custom_emoji_batch_params - params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: []) - end - end -end diff --git a/app/controllers/admin/domain_allows_controller.rb b/app/controllers/admin/domain_allows_controller.rb index 31be1978b..95d9a31fb 100644 --- a/app/controllers/admin/domain_allows_controller.rb +++ b/app/controllers/admin/domain_allows_controller.rb @@ -35,6 +35,6 @@ class Admin::DomainAllowsController < Admin::BaseController end def resource_params - params.require(:domain_allow).permit(:domain) + params.require(:domain_allow).permit(:domain, :hidden) end end diff --git a/app/controllers/admin/pending_accounts_controller.rb b/app/controllers/admin/pending_accounts_controller.rb index b62a9bc84..8a9a51d84 100644 --- a/app/controllers/admin/pending_accounts_controller.rb +++ b/app/controllers/admin/pending_accounts_controller.rb @@ -18,19 +18,19 @@ module Admin end def approve_all - Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'approve').save + Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.confirmed.pluck(:account_id), action: 'approve').save redirect_to admin_pending_accounts_path(current_params) end def reject_all - Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'reject').save + Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.confirmed.pluck(:account_id), action: 'reject').save redirect_to admin_pending_accounts_path(current_params) end private def set_accounts - @accounts = Account.joins(:user).merge(User.pending.recent).includes(user: :invite_request).page(params[:page]) + @accounts = Account.joins(:user).merge(User.pending.confirmed.recent).includes(user: :invite_request).page(params[:page]) end def form_account_batch_params diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb index 59df4470e..8abe19626 100644 --- a/app/controllers/admin/tags_controller.rb +++ b/app/controllers/admin/tags_controller.rb @@ -54,7 +54,7 @@ module Admin def set_usage_by_domain @usage_by_domain = @tag.statuses - .with_public_visibility + .distributable .excluding_silenced_accounts .where(Status.arel_table[:id].gteq(Mastodon::Snowflake.id_at(Time.now.utc.beginning_of_day))) .joins(:account) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index e962c4e97..818819a3f 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 92ccb8061..a0ce810ad 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 @account.suspended? ? [] : 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,65 @@ 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_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 +104,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 3e66ff212..6e909bbf2 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -42,7 +42,7 @@ class Api::V1::AccountsController < Api::BaseController end def mute - MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration] || 0)) + MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), timelines_only: truthy_param?(:timelines_only), duration: (params[:duration] || 0)) 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/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..c7c429bfb 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, unpublished: true) + @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 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e996c2217..5e12e89c8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base include SessionTrackingConcern include CacheConcern include DomainControlHelper + include SignatureVerification helper_method :current_account helper_method :current_session @@ -44,11 +45,11 @@ class ApplicationController < ActionController::Base private def https_enabled? - Rails.env.production? && !request.path.start_with?('/health') + Rails.env.production? && !request.path.start_with?('/health', '/_matrix-internal/') end def authorized_fetch_mode? - ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode + !(Rails.env.development? || Rails.env.test?) end def public_fetch_mode? @@ -68,7 +69,29 @@ class ApplicationController < ActionController::Base end def require_functional! - redirect_to edit_user_registration_path unless current_user.functional? + redirect_to edit_user_registration_path unless current_user&.functional? + end + + def require_authenticated! + return if current_account? + + respond_to do |format| + format.any { redirect_to edit_user_registration_path } + format.json { forbidden } + end + end + + def require_known!(account) + return if authenticated_or_following?(account) + + respond_to do |format| + format.any { redirect_to edit_user_registration_path } + format.json { forbidden } + end + end + + def require_following!(account) + forbidden unless following?(account) end def after_sign_out_path_for(_resource_or_scope) @@ -197,7 +220,7 @@ class ApplicationController < ActionController::Base def current_account return @current_account if defined?(@current_account) - @current_account = current_user&.account + @current_account = current_user&.account.presence || signed_request_account end def current_session @@ -225,4 +248,21 @@ class ApplicationController < ActionController::Base format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code } end end + + def following?(account) + return if account.blank? + + @account_following ||= {} + return @account_following[account.id] if @account_following[account.id].present? + + @account_following[account.id] = current_account.present? && (current_account.id == account.id || current_account.following?(account)) + end + + def authenticated_or_following?(account) + current_user&.account.present? || following?(account) + end + + def current_account? + current_account.present? + end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 23e5a22e1..4dedef817 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -35,6 +35,12 @@ class Auth::RegistrationsController < Devise::RegistrationsController end end + def create + super do |resource| + return redirect_to root_path, notice: I18n.t('about.registration.failed_kobold') if resource.destroyed? + end + end + protected def update_resource(resource, params) @@ -55,7 +61,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up) do |u| - u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement) + u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :username, :email, :password, :password_confirmation, :kobold, :invite_code, :agreement) end end diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb index 460f71f65..65168efff 100644 --- a/app/controllers/concerns/account_owned_concern.rb +++ b/app/controllers/concerns/account_owned_concern.rb @@ -4,7 +4,7 @@ module AccountOwnedConcern extend ActiveSupport::Concern included do - before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json } + #before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json } before_action :set_account, if: :account_required? before_action :check_account_approval, if: :account_required? before_action :check_account_suspension, if: :account_required? diff --git a/app/controllers/custom_emojis_controller.rb b/app/controllers/custom_emojis_controller.rb new file mode 100644 index 000000000..0ef8d0a50 --- /dev/null +++ b/app/controllers/custom_emojis_controller.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +class CustomEmojisController < ApplicationController + include Authorization + include AccountableConcern + + layout 'admin' + + before_action :authenticate_user! + before_action :set_pack + before_action :set_body_classes + + def index + authorize :custom_emoji, :index? + + @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]) + @form = Form::CustomEmojiBatch.new + end + + def new + authorize :custom_emoji, :create? + + @custom_emoji = CustomEmoji.new(account: current_account) + end + + def create + authorize :custom_emoji, :create? + + @custom_emoji = CustomEmoji.new(resource_params.merge(account: current_account)) + + if @custom_emoji.save + log_action :create, @custom_emoji + redirect_to custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg') + else + render :new + end + end + + def batch + @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button)) + @form.save + rescue ActionController::ParameterMissing + flash[:alert] = I18n.t('admin.accounts.no_account_selected') + rescue Mastodon::NotPermittedError + flash[:alert] = I18n.t('admin.custom_emojis.not_permitted') + ensure + redirect_to custom_emojis_path(filter_params) + end + + private + + def resource_params + params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker) + end + + def filtered_custom_emojis + CustomEmojiFilter.new(filter_params, current_account).results + end + + def filter_params + params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS) + end + + def action_from_button + if params[:update] + 'update' + elsif params[:list] + 'list' + elsif params[:unlist] + 'unlist' + elsif params[:enable] + 'enable' + elsif params[:disable] + 'disable' + elsif params[:copy] + 'copy' + elsif params[:delete] + 'delete' + elsif params[:claim] + 'claim' + elsif params[:unclaim] + 'unclaim' + end + end + + def form_custom_emoji_batch_params + params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: []) + end + + def set_pack + use_pack 'settings' + end + + def set_body_classes + @body_classes = 'admin' + end +end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index c9b840881..d15adbf62 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -47,7 +47,7 @@ class HomeController < ApplicationController end def default_redirect_path - if request.path.start_with?('/web') || whitelist_mode? + if request.path.start_with?('/web') #|| whitelist_mode? new_user_session_path elsif single_user_mode? short_account_path(Account.local.without_suspended.where('id > 0').first) diff --git a/app/controllers/matrix/base_controller.rb b/app/controllers/matrix/base_controller.rb new file mode 100644 index 000000000..5922501ec --- /dev/null +++ b/app/controllers/matrix/base_controller.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +class Matrix::BaseController < ApplicationController + include RateLimitHeaders + + skip_before_action :store_current_location + skip_before_action :require_functional! + + before_action :set_cache_headers + + protect_from_forgery with: :null_session + + skip_around_action :set_locale + + rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| + render json: { success: false, error: e.to_s }, status: 422 + end + + rescue_from ActiveRecord::RecordNotUnique do + render json: { success: false, error: 'Duplicate record' }, status: 422 + end + + rescue_from ActiveRecord::RecordNotFound do + render json: { success: false, error: 'Record not found' }, status: 404 + end + + rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do + render json: { success: false, error: 'Remote data could not be fetched' }, status: 503 + end + + rescue_from OpenSSL::SSL::SSLError do + render json: { success: false, error: 'Remote SSL certificate could not be verified' }, status: 503 + end + + rescue_from Mastodon::NotPermittedError do + render json: { success: false, error: 'This action is not allowed' }, status: 403 + end + + rescue_from Mastodon::RaceConditionError do + render json: { success: false, error: 'There was a temporary problem serving your request, please try again' }, status: 503 + end + + rescue_from Mastodon::RateLimitExceededError do + render json: { auth: { success: false }, success: false, error: I18n.t('errors.429') }, status: 429 + end + + rescue_from ActionController::ParameterMissing do |e| + render json: { success: false, error: e.to_s }, status: 400 + end + + def doorkeeper_unauthorized_render_options(error: nil) + { json: { success: false, error: (error.try(:description) || 'Not authorized') } } + end + + def doorkeeper_forbidden_render_options(*) + { json: { success: false, error: 'This action is outside the authorized scopes' } } + end + + protected + + def current_resource_owner + @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token + end + + def current_user + current_resource_owner || super + rescue ActiveRecord::RecordNotFound + nil + end + + def require_authenticated_user! + render json: { success: false, error: 'This method requires an authenticated user' }, status: 401 unless current_user + end + + def require_user! + if !current_user + render json: { success: false, error: 'This method requires an authenticated user' }, status: 422 + elsif current_user.disabled? + render json: { success: false, error: 'Your login is currently disabled' }, status: 403 + elsif !current_user.confirmed? + render json: { success: false, error: 'Your login is missing a confirmed e-mail address' }, status: 403 + elsif !current_user.approved? + render json: { success: false, error: 'Your login is currently pending approval' }, status: 403 + else + set_user_activity + end + end + + def render_empty + render json: {}, status: 200 + end + + def authorize_if_got_token!(*scopes) + doorkeeper_authorize!(*scopes) if doorkeeper_token + end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end +end diff --git a/app/controllers/matrix/identity/v1/check_credentials_controller.rb b/app/controllers/matrix/identity/v1/check_credentials_controller.rb new file mode 100644 index 000000000..1770c6767 --- /dev/null +++ b/app/controllers/matrix/identity/v1/check_credentials_controller.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +class Matrix::Identity::V1::CheckCredentialsController < Matrix::BaseController + def create + matrix_profile = matrix_profile_json + return render json: fail_json if matrix_profile.blank? + + render json: matrix_profile + rescue ActionController::ParameterMissing, ActiveRecord::RecordNotFound + render json: fail_json + end + + private + + def resource_params + params.require(:user).permit(:id, :password) + end + + def matrix_domains + ENV.fetch('MATRIX_AUTH_DOMAINS', '').delete(',').split.to_set + end + + def matrix_profile_json + user_params = resource_params + return unless user_params[:id].present? && user_params[:password].present? && user_params[:id][0] == '@' + + (username, domain) = user_params[:id].downcase.split(':', 2) + return unless matrix_domains.include?(domain) + + user = User.find_by_lower_username!(username[1..-1]) + return unless user.valid_password?(user_params[:password]) + + { + auth: { + success: true, + mxid: "@#{username}:#{domain}", + profile: { + display_name: user.account.display_name.presence || user.username, + three_pids: [ + { + medium: 'email', + address: user.email, + }, + ] + } + } + } + end + + def fail_json + { auth: { success: false } } + end +end diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 772fc42cb..db8ccd173 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -4,9 +4,9 @@ class MediaController < ApplicationController include Authorization skip_before_action :store_current_location - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional! #, unless: :whitelist_mode? - before_action :authenticate_user!, if: :whitelist_mode? + #before_action :authenticate_user!, if: :whitelist_mode? before_action :set_media_attachment before_action :verify_permitted_status! before_action :check_playable, only: :player @@ -33,6 +33,7 @@ class MediaController < ApplicationController def verify_permitted_status! authorize @media_attachment.status, :show? + authorize @media_attachment.status.reblog, :show? if @media_attachment.status.reblog? rescue Mastodon::NotPermittedError not_found end diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb index 0b1d09de9..ee7568a33 100644 --- a/app/controllers/media_proxy_controller.rb +++ b/app/controllers/media_proxy_controller.rb @@ -7,7 +7,7 @@ class MediaProxyController < ApplicationController skip_before_action :store_current_location skip_before_action :require_functional! - before_action :authenticate_user!, if: :whitelist_mode? + #before_action :authenticate_user!, if: :whitelist_mode? rescue_from ActiveRecord::RecordInvalid, with: :not_found rescue_from Mastodon::UnexpectedResponseError, with: :not_found @@ -19,6 +19,7 @@ class MediaProxyController < ApplicationController if lock.acquired? @media_attachment = MediaAttachment.remote.attached.find(params[:id]) authorize @media_attachment.status, :show? + authorize @media_attachment.status.reblog, :show? if @media_attachment.status.reblog? redownload! if @media_attachment.needs_redownload? && !reject_media? else raise Mastodon::RaceConditionError diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb index a277bfa10..5ead3aaa0 100644 --- a/app/controllers/remote_interaction_controller.rb +++ b/app/controllers/remote_interaction_controller.rb @@ -5,13 +5,13 @@ class RemoteInteractionController < ApplicationController layout 'modal' - before_action :authenticate_user!, if: :whitelist_mode? + #before_action :authenticate_user!, if: :whitelist_mode? before_action :set_interaction_type before_action :set_status before_action :set_body_classes before_action :set_pack - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional! #, unless: :whitelist_mode? def new @remote_follow = RemoteFollow.new(session_params) diff --git a/app/controllers/settings/preferences/filters_controller.rb b/app/controllers/settings/preferences/filters_controller.rb new file mode 100644 index 000000000..c58a698ef --- /dev/null +++ b/app/controllers/settings/preferences/filters_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::FiltersController < Settings::PreferencesController + private + + def after_update_redirect_path + settings_preferences_filters_path + end +end diff --git a/app/controllers/settings/preferences/privacy_controller.rb b/app/controllers/settings/preferences/privacy_controller.rb new file mode 100644 index 000000000..f447fa598 --- /dev/null +++ b/app/controllers/settings/preferences/privacy_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::PrivacyController < Settings::PreferencesController + private + + def after_update_redirect_path + settings_preferences_privacy_path + end +end diff --git a/app/controllers/settings/preferences/publishing_controller.rb b/app/controllers/settings/preferences/publishing_controller.rb new file mode 100644 index 000000000..5b298d94d --- /dev/null +++ b/app/controllers/settings/preferences/publishing_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::PublishingController < Settings::PreferencesController + private + + def after_update_redirect_path + settings_preferences_publishing_path + end +end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index d05ceb53f..7e42d4e40 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -4,7 +4,11 @@ class Settings::PreferencesController < Settings::BaseController def show; end def update - user_settings.update(user_settings_params.to_h) + old_home_reblogs = current_user.home_reblogs? + + if user_settings.update(user_settings_params.to_h) + ClearReblogsWorker.perform_async(current_user.account_id) unless old_home_reblogs == current_user.home_reblogs? || current_user.home_reblogs? + end if current_user.update(user_params) I18n.locale = current_user.locale @@ -50,7 +54,6 @@ class Settings::PreferencesController < Settings::BaseController :setting_noindex, :setting_hide_network, :setting_hide_followers_count, - :setting_aggregate_reblogs, :setting_show_application, :setting_advanced_layout, :setting_default_content_type, @@ -58,6 +61,26 @@ class Settings::PreferencesController < Settings::BaseController :setting_use_pending_items, :setting_trends, :setting_crop_images, + :setting_manual_publish, + :setting_style_dashed_nest, + :setting_style_underline_a, + :setting_style_css_profile, + :setting_style_css_webapp, + :setting_style_wide_media, + :setting_style_lowercase, + :setting_publish_in, + :setting_unpublish_in, + :setting_unpublish_delete, + :setting_boost_every, + :setting_boost_jitter, + :setting_boost_random, + :setting_filter_unknown, + :setting_unpublish_on_delete, + :setting_rss_disabled, + :setting_home_reblogs, + :setting_max_history_public, + :setting_max_history_private, + :setting_web_push, notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag), interactions: %i(must_be_follower must_be_following must_be_following_dm) ) diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 0c15447a6..541ba2d5d 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -20,7 +20,9 @@ class Settings::ProfilesController < Settings::BaseController private def account_params - params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value]) + params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, + :require_dereference, :show_replies, :show_unlisted, :private, :require_auth, + fields_attributes: [:name, :value]) end def set_account diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index a6ab8828f..6f8e74414 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -8,7 +8,9 @@ class StatusesController < ApplicationController layout 'public' - before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? } + before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? && current_user&.account_id != @account.id } + before_action :require_authenticated!, if: -> { @account.require_auth? } + before_action -> { require_following!(@account) }, if: -> { request.format != :json && @account.private? } before_action :set_status before_action :set_instance_presenter before_action :set_link_headers @@ -19,7 +21,7 @@ class StatusesController < ApplicationController before_action :set_autoplay, only: :embed skip_around_action :set_locale, if: -> { request.format == :json } - skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode? + skip_before_action :require_functional!, only: [:show, :embed] # , unless: :whitelist_mode? content_security_policy only: :embed do |p| p.frame_ancestors(false) @@ -37,14 +39,18 @@ class StatusesController < ApplicationController format.json do expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? - render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter + render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter, target_domain: current_account&.domain end end end def activity expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? - render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status), content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter + render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status), + content_type: 'application/activity+json', + serializer: ActivityPub::ActivitySerializer, + adapter: ActivityPub::Adapter, + target_domain: current_account&.domain end def embed diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 64736e77f..d8b6019f5 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -9,14 +9,14 @@ class TagsController < ApplicationController layout 'public' before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } - before_action :authenticate_user!, if: :whitelist_mode? + # before_action :authenticate_user!, if: :whitelist_mode? before_action :set_local before_action :set_tag before_action :set_statuses before_action :set_body_classes before_action :set_instance_presenter - skip_before_action :require_functional!, unless: :whitelist_mode? + skip_before_action :require_functional! # , unless: :whitelist_mode? def show respond_to do |format| @@ -32,7 +32,7 @@ class TagsController < ApplicationController format.json do expires_in 3.minutes, public: public_fetch_mode? - render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' + render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain end end end diff --git a/app/controllers/user_profile_css_controller.rb b/app/controllers/user_profile_css_controller.rb new file mode 100644 index 000000000..0a0588e88 --- /dev/null +++ b/app/controllers/user_profile_css_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class UserProfileCssController < ApplicationController + skip_before_action :store_current_location + skip_before_action :require_functional! + + before_action :set_cache_headers + before_action :set_account + + def show + expires_in 3.minutes, public: true + render plain: css, content_type: 'text/css' + end + + private + + def css + @account.user&.setting_style_css_profile_errors.blank? ? (@account.user&.setting_style_css_profile || '') : '' + end + + def set_account + @account = Account.find(params[:id]) + end +end diff --git a/app/controllers/user_webapp_css_controller.rb b/app/controllers/user_webapp_css_controller.rb new file mode 100644 index 000000000..3acb21bb1 --- /dev/null +++ b/app/controllers/user_webapp_css_controller.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +class UserWebappCssController < ApplicationController + skip_before_action :store_current_location + skip_before_action :require_functional! + + before_action :set_account + + def show + render plain: css, content_type: 'text/css' + end + + private + + def css_dashed_nest + return unless @account.user&.setting_style_dashed_nest + + %( + div[data-nest-level] + { border-style: dashed; } + ) + end + + def css_underline_a + return unless @account.user&.setting_style_underline_a + + %( + .status__content__text a, + .reply-indicator__content a, + .composer--reply > .content a, + .account__header__content a + { text-decoration: underline; } + + .status__content__text a:hover, + .reply-indicator__content a:hover, + .composer--reply > .content a:hover, + .account__header__content a:hover + { text-decoration: none; } + ) + end + + def css_wide_media + return unless @account.user&.setting_style_wide_media + + %( + .media-gallery + { height: auto !important; } + + .media-gallery__item + { width: 100% !important; } + + .spoiler-button + .media-gallery__item + { height: 5em !important; } + + .spoiler-button--minified + .media-gallery__item + { height: 280px !important; } + ) + end + + def css_lowercase + return unless @account.user&.setting_style_lowercase + + %( + div, button, span + { text-transform: lowercase; } + + code, pre + { text-transform: initial !important; } + ) + end + + def css_webapp + @account.user&.setting_style_css_webapp_errors.blank? ? (@account.user&.setting_style_css_webapp || '') : '' + end + + def css + "#{css_dashed_nest}\n#{css_underline_a}\n#{css_wide_media}\n#{css_lowercase}\n#{css_webapp}".squish + end + + def set_account + @account = Account.find(params[:id]) + end +end |