From 054e15e4f03eecb174374466581b9662a6b38e24 Mon Sep 17 00:00:00 2001 From: Fire Demon Date: Tue, 28 Jul 2020 20:40:25 -0500 Subject: [Privacy] Add options for private accounts --- app/controllers/accounts_controller.rb | 24 +++++++++---- app/controllers/activitypub/outboxes_controller.rb | 11 +++--- app/controllers/activitypub/replies_controller.rb | 8 ++--- .../api/v1/accounts/statuses_controller.rb | 6 ++-- app/controllers/application_controller.rb | 42 +++++++++++++++++++++- app/controllers/settings/profiles_controller.rb | 2 +- app/controllers/statuses_controller.rb | 8 +++-- app/controllers/tags_controller.rb | 12 +++---- 8 files changed, 80 insertions(+), 33 deletions(-) (limited to 'app/controllers') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 651da89ad..ebc472087 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -10,6 +10,8 @@ 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? @@ -20,10 +22,10 @@ class AccountsController < ApplicationController 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,7 +42,9 @@ class AccountsController < ApplicationController end format.rss do - expires_in 1.minute, public: !(user_signed_in? || signed_request_account.present?) + return forbidden if unauthorized? + + expires_in 1.minute, public: !current_account? limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE @statuses = filtered_statuses.without_reblogs.limit(limit) @@ -49,7 +53,7 @@ class AccountsController < ApplicationController 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, fields: restrict_fields_to end end @@ -152,10 +156,18 @@ class AccountsController < ApplicationController end def restrict_fields_to - if signed_request_account.present? || public_fetch_mode? + if signed_request_account.present? && !blocked? # Return all fields else %i(id type preferred_username inbox public_key endpoints) end end + + def blocked? + @blocked ||= current_account && @account.blocking?(current_account) + end + + def unauthorized? + @unauthorized ||= blocked? || (@account.private? && !following?(@account)) + end end diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 60f1c526b..c4c0ce0c9 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', target_domain: signed_request_account&.domain + render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain end private @@ -49,7 +52,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController def set_statuses return unless page_requested? - @statuses = if known_visitor? + @statuses = if authenticated_or_following?(@account) @account.statuses.without_semiprivate.permitted_for(@account, signed_request_account) else @account.statuses.permitted_for(@account, signed_request_account, user_signed_in: true) @@ -66,8 +69,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController def page_params { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact end - - def known_visitor? - @known_visitor ||= user_signed_in? || (signed_request_account.present? && signed_request_account.following?(@account)) - end end diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index cec571e8a..4d553fc07 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, target_domain: signed_request_account&.domain + 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 @@ -33,7 +33,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController def set_replies @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted]) - @replies = @replies.without_semiprivate unless known_visitor? + @replies = @replies.without_semiprivate unless authenticated_or_following?(@account) @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) end @@ -78,8 +78,4 @@ class ActivityPub::RepliesController < ActivityPub::BaseController def page_params params_slice(:only_other_accounts, :min_id).merge(page: true) end - - def known_visitor? - @known_visitor ||= user_signed_in? || (signed_request_account.present? && signed_request_account.following?(@account)) - end end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 4735fea8c..1c744ad73 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -26,6 +26,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def account_statuses + return [] if (@account.private && !following?(@account)) || (@account.require_auth && !current_account?) + statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses statuses.merge!(only_media_scope) if truthy_param?(:only_media) @@ -37,7 +39,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def permitted_account_statuses - @account.statuses.permitted_for(@account, current_account, user_signed_in: user_signed_in?) + @account.statuses.permitted_for(@account, current_account, user_signed_in: authenticated_or_following?(@account)) end def only_media_scope @@ -49,7 +51,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController # Also, Avoid getting slow by not narrowing down by `statuses.account_id`. # When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used # and the table will be joined by `Merge Semi Join`, so the query will be slow. - @account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account) + @account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account, user_signed_in: authenticated_or_following?(@account)) .paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) .reorder(id: :desc).distinct(:id).pluck(:id) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e996c2217..9608f1cf9 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 @@ -71,6 +72,28 @@ class ApplicationController < ActionController::Base 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) new_user_session_path end @@ -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.functional? || following?(account) + end + + def current_account? + current_account.present? + end end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index d6e3c9863..8c4efa21d 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -24,7 +24,7 @@ class Settings::ProfilesController < Settings::BaseController def account_params params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, - :require_dereference, :show_replies, :show_unlisted, + :require_dereference, :show_replies, :show_unlisted, :private, :require_auth, fields_attributes: [:name, :value]) end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 23cbb8c37..5c977d212 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -9,6 +9,8 @@ class StatusesController < ApplicationController layout 'public' before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? } + 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,7 +39,7 @@ 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, target_domain: signed_request_account&.domain + render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter, target_domain: current_account&.domain end end end @@ -48,7 +50,7 @@ class StatusesController < ApplicationController content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter, - target_domain: signed_request_account&.domain + target_domain: current_account&.domain end def embed diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 9cba38771..368419ef5 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -9,13 +9,13 @@ 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_tag before_action :set_local 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| @@ -38,11 +38,11 @@ class TagsController < ApplicationController expires_in 3.minutes, public: public_fetch_mode? @statuses = HashtagQueryService.new.call(@tag, filter_params, current_account, @local) - @statuses = @statuses.without_semiprivate unless known_visitor? + @statuses = @statuses.without_semiprivate unless authenticated_or_following?(@account) @statuses = @statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id]) @statuses = cache_collection(@statuses, Status) - render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: signed_request_account&.domain + render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain end end end @@ -77,8 +77,4 @@ class TagsController < ApplicationController def filter_params params.slice(:any, :all, :none).permit(:any, :all, :none) end - - def known_visitor? - @known_visitor ||= user_signed_in? || (signed_request_account.present? && signed_request_account.following?(@account)) - end end -- cgit