diff --git a/.eslintrc.js b/.eslintrc.js index bbdfa7de27..a9b02e2943 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -71,7 +71,7 @@ module.exports = { 'comma-style': ['warn', 'last'], 'consistent-return': 'error', 'dot-notation': 'error', - eqeqeq: 'error', + eqeqeq: ['error', 'always', { 'null': 'ignore' }], indent: ['warn', 2], 'jsx-quotes': ['error', 'prefer-single'], 'no-case-declarations': 'off', diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml new file mode 100644 index 0000000000..31fef5d28a --- /dev/null +++ b/.github/workflows/build-nightly.yml @@ -0,0 +1,53 @@ +name: Build nightly container image +on: + workflow_dispatch: + schedule: + - cron: '0 2 * * *' # run at 2 AM UTC +permissions: + contents: read + packages: write + +jobs: + build-nightly-image: + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + steps: + - uses: actions/checkout@v3 + - uses: hadolint/hadolint-action@v3.1.0 + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + + - name: Log in to the Github Container registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/metadata-action@v4 + id: meta + with: + images: | + ghcr.io/mastodon/mastodon + flavor: | + latest=auto + tags: | + type=raw,value=nightly + labels: | + org.opencontainers.image.description=Nightly build image used for testing purposes + + - uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + provenance: false + builder: ${{ steps.buildx.outputs.name }} + push: ${{ github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2e4801a552..9110956d13 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1224,9 +1224,6 @@ Rails/ActiveRecordCallbacksOrder: Rails/ApplicationController: Exclude: - 'app/controllers/health_controller.rb' - - 'app/controllers/well_known/host_meta_controller.rb' - - 'app/controllers/well_known/nodeinfo_controller.rb' - - 'app/controllers/well_known/webfinger_controller.rb' # Configuration parameters: Database, Include. # SupportedDatabases: mysql, postgresql diff --git a/Gemfile b/Gemfile index 4488f77e13..aa8d9d7a7c 100644 --- a/Gemfile +++ b/Gemfile @@ -120,7 +120,7 @@ end group :test do gem 'capybara', '~> 3.39' gem 'climate_control' - gem 'faker', '~> 3.1' + gem 'faker', '~> 3.2' gem 'json-schema', '~> 3.0' gem 'rack-test', '~> 2.1' gem 'rails-controller-testing', '~> 1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 5f2a5a51d6..6923522097 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -243,7 +243,7 @@ GEM tzinfo excon (0.95.0) fabrication (2.30.0) - faker (3.1.1) + faker (3.2.0) i18n (>= 1.8.11, < 2) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -346,19 +346,19 @@ GEM ipaddress (0.8.3) jmespath (1.6.2) json (2.6.3) - json-canonicalization (0.3.0) + json-canonicalization (0.3.1) json-jwt (1.15.3) activesupport (>= 4.2) aes_key_wrap bindata httpclient - json-ld (3.2.3) + json-ld (3.2.4) htmlentities (~> 4.3) json-canonicalization (~> 0.3) link_header (~> 0.0, >= 0.0.8) multi_json (~> 1.15) - rack (~> 2.2) - rdf (~> 3.2, >= 3.2.9) + rack (>= 2.2, < 4) + rdf (~> 3.2, >= 3.2.10) json-ld-preloaded (3.2.2) json-ld (~> 3.2) rdf (~> 3.2) @@ -477,7 +477,7 @@ GEM openssl-signature_algorithm (1.3.0) openssl (> 2.0) orm_adapter (0.5.0) - ox (2.14.14) + ox (2.14.16) parallel (1.22.1) parser (3.2.2.0) ast (~> 2.4.1) @@ -485,7 +485,7 @@ GEM pastel (0.8.0) tty-color (~> 0.5) pg (1.4.6) - pghero (3.3.1) + pghero (3.3.2) activerecord (>= 6) pkg-config (1.5.1) posix-spawn (0.3.15) @@ -555,7 +555,7 @@ GEM thor (~> 1.0) rainbow (3.1.1) rake (13.0.6) - rdf (3.2.9) + rdf (3.2.10) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.5.1) rdf (~> 3.2) @@ -797,7 +797,7 @@ DEPENDENCIES dotenv-rails (~> 2.8) ed25519 (~> 1.3) fabrication (~> 2.30) - faker (~> 3.1) + faker (~> 3.2) fast_blank (~> 1.0) fastimage fog-core (<= 2.4.0) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 6ee784b921..8f54f92e75 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -7,8 +7,9 @@ class AccountsController < ApplicationController include AccountControllerConcern include SignatureAuthentication + vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' } + before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } - before_action :set_cache_headers skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) } skip_before_action :require_functional!, unless: :whitelist_mode? diff --git a/app/controllers/activitypub/base_controller.rb b/app/controllers/activitypub/base_controller.rb index b8a7e0ab96..388d4b9e1d 100644 --- a/app/controllers/activitypub/base_controller.rb +++ b/app/controllers/activitypub/base_controller.rb @@ -7,10 +7,6 @@ class ActivityPub::BaseController < Api::BaseController private - def set_cache_headers - response.headers['Vary'] = 'Signature' if authorized_fetch_mode? - end - def skip_temporary_suspension_response? false end diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index d94a285eae..2188eb72a3 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -4,11 +4,12 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController include SignatureVerification include AccountOwnedConcern + vary_by -> { 'Signature' if authorized_fetch_mode? } + before_action :require_account_signature!, if: :authorized_fetch_mode? before_action :set_items before_action :set_size before_action :set_type - before_action :set_cache_headers def show expires_in 3.minutes, public: public_fetch_mode? diff --git a/app/controllers/activitypub/followers_synchronizations_controller.rb b/app/controllers/activitypub/followers_synchronizations_controller.rb index 4e445bcb1f..976caa3445 100644 --- a/app/controllers/activitypub/followers_synchronizations_controller.rb +++ b/app/controllers/activitypub/followers_synchronizations_controller.rb @@ -4,9 +4,10 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro include SignatureVerification include AccountOwnedConcern + vary_by -> { 'Signature' if authorized_fetch_mode? } + before_action :require_account_signature! before_action :set_items - before_action :set_cache_headers def show expires_in 0, public: false diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 60d201f763..bf10ba762a 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -6,9 +6,10 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController include SignatureVerification include AccountOwnedConcern + vary_by -> { 'Signature' if authorized_fetch_mode? || page_requested? } + before_action :require_account_signature!, if: :authorized_fetch_mode? before_action :set_statuses - before_action :set_cache_headers def show if page_requested? @@ -16,6 +17,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController else expires_in(3.minutes, public: public_fetch_mode?) end + render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end @@ -80,8 +82,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController def set_account @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative end - - def set_cache_headers - response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested? - end end diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index e65dbb7d38..5c53f58e44 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -7,9 +7,10 @@ class ActivityPub::RepliesController < ActivityPub::BaseController DESCENDANTS_LIMIT = 60 + vary_by -> { 'Signature' if authorized_fetch_mode? } + before_action :require_account_signature!, if: :authorized_fetch_mode? before_action :set_status - before_action :set_cache_headers before_action :set_replies def index diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 5b7a7ec11a..4b5afbe157 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -8,6 +8,8 @@ module Admin layout 'admin' before_action :set_body_classes + before_action :set_cache_headers + after_action :verify_authorized private @@ -16,6 +18,10 @@ module Admin @body_classes = 'admin' end + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end + def set_user @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 9d2d817d09..277dad4b28 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -12,7 +12,7 @@ class Api::BaseController < ApplicationController before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access? before_action :require_not_suspended! - before_action :set_cache_headers + before_action :set_cache_control_defaults protect_from_forgery with: :null_session @@ -148,8 +148,8 @@ class Api::BaseController < ApplicationController doorkeeper_authorize!(*scopes) if doorkeeper_token end - def set_cache_headers - response.headers['Cache-Control'] = 'private, no-store' + def set_cache_control_defaults + response.cache_control.replace(private: true, no_store: true) end def disallow_unauthenticated_api_access? diff --git a/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb new file mode 100644 index 0000000000..5d9fcc82c0 --- /dev/null +++ b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController + include Authorization + + LIMIT = 100 + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + before_action :set_providers, only: :index + + after_action :verify_authorized + after_action :insert_pagination_headers, only: :index + + PAGINATION_PARAMS = %i(limit).freeze + + def index + authorize :preview_card_provider, :index? + + render json: @providers, each_serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + def approve + authorize :preview_card_provider, :review? + + provider = PreviewCardProvider.find(params[:id]) + provider.update(trendable: true, reviewed_at: Time.now.utc) + render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + def reject + authorize :preview_card_provider, :review? + + provider = PreviewCardProvider.find(params[:id]) + provider.update(trendable: false, reviewed_at: Time.now.utc) + render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + private + + def set_providers + @providers = PreviewCardProvider.all.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_trends_links_preview_card_providers_url(pagination_params(max_id: pagination_max_id)) if records_continue? + end + + def prev_path + api_v1_admin_trends_links_preview_card_providers_url(pagination_params(min_id: pagination_since_id)) unless @providers.empty? + end + + def pagination_max_id + @providers.last.id + end + + def pagination_since_id + @providers.first.id + end + + def records_continue? + @providers.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) + end +end diff --git a/app/controllers/api/v1/admin/trends/links_controller.rb b/app/controllers/api/v1/admin/trends/links_controller.rb index cc63889806..7f4ca48288 100644 --- a/app/controllers/api/v1/admin/trends/links_controller.rb +++ b/app/controllers/api/v1/admin/trends/links_controller.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index + + def index + if current_user&.can?(:manage_taxonomies) + render json: @links, each_serializer: REST::Admin::Trends::LinkSerializer + else + super + end + end + + def approve + authorize :preview_card, :review? + + link = PreviewCard.find(params[:id]) + link.update(trendable: true) + render json: link, serializer: REST::Admin::Trends::LinkSerializer + end + + def reject + authorize :preview_card, :review? + + link = PreviewCard.find(params[:id]) + link.update(trendable: false) + render json: link, serializer: REST::Admin::Trends::LinkSerializer + end private diff --git a/app/controllers/api/v1/admin/trends/statuses_controller.rb b/app/controllers/api/v1/admin/trends/statuses_controller.rb index c39f77363c..34b6580df1 100644 --- a/app/controllers/api/v1/admin/trends/statuses_controller.rb +++ b/app/controllers/api/v1/admin/trends/statuses_controller.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index + + def index + if current_user&.can?(:manage_taxonomies) + render json: @statuses, each_serializer: REST::Admin::Trends::StatusSerializer + else + super + end + end + + def approve + authorize [:admin, :status], :review? + + status = Status.find(params[:id]) + status.update(trendable: true) + render json: status, serializer: REST::Admin::Trends::StatusSerializer + end + + def reject + authorize [:admin, :status], :review? + + status = Status.find(params[:id]) + status.update(trendable: false) + render json: status, serializer: REST::Admin::Trends::StatusSerializer + end private diff --git a/app/controllers/api/v1/admin/trends/tags_controller.rb b/app/controllers/api/v1/admin/trends/tags_controller.rb index e77df30216..2eeea95225 100644 --- a/app/controllers/api/v1/admin/trends/tags_controller.rb +++ b/app/controllers/api/v1/admin/trends/tags_controller.rb @@ -1,7 +1,12 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index def index if current_user&.can?(:manage_taxonomies) @@ -11,6 +16,22 @@ class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController end end + def approve + authorize :tag, :review? + + tag = Tag.find(params[:id]) + tag.update(trendable: true, reviewed_at: Time.now.utc) + render json: tag, serializer: REST::Admin::TagSerializer + end + + def reject + authorize :tag, :review? + + tag = Tag.find(params[:id]) + tag.update(trendable: false, reviewed_at: Time.now.utc) + render json: tag, serializer: REST::Admin::TagSerializer + end + private def enabled? diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index 08b3474cc8..380dbe8bf3 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::V1::CustomEmojisController < Api::BaseController - skip_before_action :set_cache_headers - def index expires_in 3.minutes, public: true render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) } diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb index bad61425a5..7ccfec7036 100644 --- a/app/controllers/api/v1/instances/activity_controller.rb +++ b/app/controllers/api/v1/instances/activity_controller.rb @@ -3,7 +3,6 @@ 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? def show diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index 2877fec52d..b2669a84eb 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -3,7 +3,6 @@ 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? def index diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index 913319a869..3cdb404cb8 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Api::V1::InstancesController < Api::BaseController - skip_before_action :set_cache_headers skip_before_action :require_authenticated_user!, unless: :whitelist_mode? def show diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index b55f7f309f..f9d30c1ebd 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -152,6 +152,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def set_cache_headers - response.headers['Cache-Control'] = 'private, no-store' + response.cache_control.replace(private: true, no_store: true) end end diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb index a5a9ba3e1f..ffede5839d 100644 --- a/app/controllers/concerns/cache_concern.rb +++ b/app/controllers/concerns/cache_concern.rb @@ -155,8 +155,16 @@ module CacheConcern end end + class_methods do + def vary_by(value) + before_action do |controller| + response.headers['Vary'] = value.respond_to?(:call) ? controller.instance_exec(&value) : value + end + end + end + def render_with_cache(**options) - raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given? + raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given? key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':') expires_in = options.delete(:expires_in) || 3.minutes @@ -176,10 +184,6 @@ module CacheConcern end end - def set_cache_headers - response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature' - end - def cache_collection(raw, klass) return raw unless klass.respond_to?(:with_includes) diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb index 9270c467dc..e7a02ea89c 100644 --- a/app/controllers/custom_css_controller.rb +++ b/app/controllers/custom_css_controller.rb @@ -1,18 +1,8 @@ # frozen_string_literal: true -class CustomCssController < ApplicationController - skip_before_action :store_current_location - skip_before_action :require_functional! - skip_before_action :update_user_sign_in - skip_before_action :set_session_activity - - skip_around_action :set_locale - - before_action :set_cache_headers - +class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController def show expires_in 3.minutes, public: true - request.session_options[:skip] = true render content_type: 'text/css' end end diff --git a/app/controllers/disputes/base_controller.rb b/app/controllers/disputes/base_controller.rb index 865146b5cc..1054f3db80 100644 --- a/app/controllers/disputes/base_controller.rb +++ b/app/controllers/disputes/base_controller.rb @@ -9,10 +9,15 @@ class Disputes::BaseController < ApplicationController before_action :set_body_classes before_action :authenticate_user! + before_action :set_cache_headers private def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end end diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb index 41f1e1c5ca..72bc56de04 100644 --- a/app/controllers/emojis_controller.rb +++ b/app/controllers/emojis_controller.rb @@ -2,15 +2,12 @@ class EmojisController < ApplicationController before_action :set_emoji - before_action :set_cache_headers + + vary_by -> { 'Signature' if authorized_fetch_mode? } def show - respond_to do |format| - format.json do - expires_in 3.minutes, public: true - render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter - end - end + expires_in 3.minutes, public: true + render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter end private diff --git a/app/controllers/filters/statuses_controller.rb b/app/controllers/filters/statuses_controller.rb index 7779c6d95b..94993f938b 100644 --- a/app/controllers/filters/statuses_controller.rb +++ b/app/controllers/filters/statuses_controller.rb @@ -7,6 +7,7 @@ class Filters::StatusesController < ApplicationController before_action :set_filter before_action :set_status_filters before_action :set_body_classes + before_action :set_cache_headers PER_PAGE = 20 @@ -44,4 +45,8 @@ class Filters::StatusesController < ApplicationController def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end end diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb index cc5cb5d9f2..1881dd5a0b 100644 --- a/app/controllers/filters_controller.rb +++ b/app/controllers/filters_controller.rb @@ -6,6 +6,7 @@ class FiltersController < ApplicationController before_action :authenticate_user! before_action :set_filter, only: [:edit, :update, :destroy] before_action :set_body_classes + before_action :set_cache_headers def index @filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase) @@ -54,4 +55,8 @@ class FiltersController < ApplicationController def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end end diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 9ced184491..3068c07e32 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -5,8 +5,9 @@ class FollowerAccountsController < ApplicationController include SignatureVerification include WebAppControllerConcern + vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' } + before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } - before_action :set_cache_headers skip_around_action :set_locale, if: -> { request.format == :json } skip_before_action :require_functional!, unless: :whitelist_mode? diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index febd13c975..022884193d 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -5,8 +5,9 @@ class FollowingAccountsController < ApplicationController include SignatureVerification include WebAppControllerConcern + vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' } + before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } - before_action :set_cache_headers skip_around_action :set_locale, if: -> { request.format == :json } skip_before_action :require_functional!, unless: :whitelist_mode? diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 8d92147e27..9bc5164d59 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -7,6 +7,7 @@ class InvitesController < ApplicationController before_action :authenticate_user! before_action :set_body_classes + before_action :set_cache_headers def index authorize :invite, :create? @@ -49,4 +50,8 @@ class InvitesController < ApplicationController def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end end diff --git a/app/controllers/manifests_controller.rb b/app/controllers/manifests_controller.rb index 960510f601..593b76c536 100644 --- a/app/controllers/manifests_controller.rb +++ b/app/controllers/manifests_controller.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true -class ManifestsController < ApplicationController - skip_before_action :store_current_location - skip_before_action :require_functional! - +class ManifestsController < ActionController::Base # rubocop:disable Rails/ApplicationController def show expires_in 3.minutes, public: true render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance' diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb index 5449cfb1a2..66e774425d 100644 --- a/app/controllers/oauth/authorizations_controller.rb +++ b/app/controllers/oauth/authorizations_controller.rb @@ -34,6 +34,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController end def set_cache_headers - response.headers['Cache-Control'] = 'private, no-store' + response.cache_control.replace(private: true, no_store: true) end end diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 45151cdd77..e3a3c4fc1e 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -7,6 +7,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio before_action :authenticate_resource_owner! before_action :require_not_suspended!, only: :destroy before_action :set_body_classes + before_action :set_cache_headers skip_before_action :require_functional! @@ -30,4 +31,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio def require_not_suspended! forbidden if current_account.suspended? end + + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end end diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb index de5dc58792..e87b5a656f 100644 --- a/app/controllers/relationships_controller.rb +++ b/app/controllers/relationships_controller.rb @@ -7,6 +7,7 @@ class RelationshipsController < ApplicationController before_action :set_accounts, only: :show before_action :set_relationships, only: :show before_action :set_body_classes + before_action :set_cache_headers helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship? @@ -70,4 +71,8 @@ class RelationshipsController < ApplicationController def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end end diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb index 8722fd64ae..64dcd47d12 100644 --- a/app/controllers/settings/base_controller.rb +++ b/app/controllers/settings/base_controller.rb @@ -14,7 +14,7 @@ class Settings::BaseController < ApplicationController end def set_cache_headers - response.headers['Cache-Control'] = 'private, no-store' + response.cache_control.replace(private: true, no_store: true) end def require_not_suspended! diff --git a/app/controllers/statuses_cleanup_controller.rb b/app/controllers/statuses_cleanup_controller.rb index e912967fd7..19ae971ce4 100644 --- a/app/controllers/statuses_cleanup_controller.rb +++ b/app/controllers/statuses_cleanup_controller.rb @@ -6,6 +6,7 @@ class StatusesCleanupController < ApplicationController before_action :authenticate_user! before_action :set_policy before_action :set_body_classes + before_action :set_cache_headers def show; end @@ -36,4 +37,8 @@ class StatusesCleanupController < ApplicationController def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.cache_control.replace(private: true, no_store: true) + end end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index d369cd8e6d..07927d119f 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -6,11 +6,12 @@ class StatusesController < ApplicationController include Authorization include AccountOwnedConcern + vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' } + before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_status before_action :set_instance_presenter before_action :redirect_to_original, only: :show - before_action :set_cache_headers before_action :set_body_classes, only: :embed after_action :set_link_headers diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 4b747c9add..4afc1aca5f 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -7,6 +7,8 @@ class TagsController < ApplicationController PAGE_SIZE = 20 PAGE_SIZE_MAX = 200 + vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' } + before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :authenticate_user!, if: :whitelist_mode? before_action :set_local diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb index 2fd6bc7cc9..201da9fbc3 100644 --- a/app/controllers/well_known/host_meta_controller.rb +++ b/app/controllers/well_known/host_meta_controller.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true module WellKnown - class HostMetaController < ActionController::Base + class HostMetaController < ActionController::Base # rubocop:disable Rails/ApplicationController include RoutingHelper - before_action { response.headers['Vary'] = 'Accept' } - def show @webfinger_template = "#{webfinger_url}?resource={uri}" expires_in 3.days, public: true diff --git a/app/controllers/well_known/nodeinfo_controller.rb b/app/controllers/well_known/nodeinfo_controller.rb index 11a699ebc8..ab6b8f5a40 100644 --- a/app/controllers/well_known/nodeinfo_controller.rb +++ b/app/controllers/well_known/nodeinfo_controller.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true module WellKnown - class NodeInfoController < ActionController::Base + class NodeInfoController < ActionController::Base # rubocop:disable Rails/ApplicationController include CacheConcern - before_action { response.headers['Vary'] = 'Accept' } - def index expires_in 3.days, public: true render_with_cache json: {}, serializer: NodeInfo::DiscoverySerializer, adapter: NodeInfo::Adapter, expires_in: 3.days, root: 'nodeinfo' diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 2b296ea3be..a06253f456 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module WellKnown - class WebfingerController < ActionController::Base + class WebfingerController < ActionController::Base # rubocop:disable Rails/ApplicationController include RoutingHelper before_action :set_account @@ -34,7 +34,12 @@ module WellKnown end def check_account_suspension - expires_in(3.minutes, public: true) && gone if @account.suspended_permanently? + gone if @account.suspended_permanently? + end + + def gone + expires_in(3.minutes, public: true) + head 410 end def bad_request @@ -46,9 +51,5 @@ module WellKnown expires_in(3.minutes, public: true) head 404 end - - def gone - head 410 - end end end diff --git a/app/javascript/mastodon/components/short_number.jsx b/app/javascript/mastodon/components/short_number.jsx index 535c17727d..861d0bc589 100644 --- a/app/javascript/mastodon/components/short_number.jsx +++ b/app/javascript/mastodon/components/short_number.jsx @@ -32,17 +32,14 @@ function ShortNumber({ value, renderer, children }) { const shortNumber = toShortNumber(value); const [, division] = shortNumber; - // eslint-disable-next-line eqeqeq if (children != null && renderer != null) { console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.'); } - // eslint-disable-next-line eqeqeq const customRenderer = children != null ? children : renderer; const displayNumber = ; - // eslint-disable-next-line eqeqeq return customRenderer != null ? customRenderer(displayNumber, pluralReady(value, division)) : displayNumber; diff --git a/app/javascript/mastodon/utils/numbers.js b/app/javascript/mastodon/utils/numbers.js index 6ef563ad8f..fa3d58fad1 100644 --- a/app/javascript/mastodon/utils/numbers.js +++ b/app/javascript/mastodon/utils/numbers.js @@ -60,7 +60,6 @@ export function toShortNumber(sourceNumber) { * // => 1790 */ export function pluralReady(sourceNumber, division) { - // eslint-disable-next-line eqeqeq if (division == null || division < DECIMAL_UNITS.HUNDRED) { return sourceNumber; } diff --git a/app/models/preview_card_provider.rb b/app/models/preview_card_provider.rb index 1dd95fc91c..9f5f6d3cb9 100644 --- a/app/models/preview_card_provider.rb +++ b/app/models/preview_card_provider.rb @@ -18,6 +18,7 @@ # class PreviewCardProvider < ApplicationRecord + include Paginable include DomainNormalizable include Attachmentable diff --git a/app/serializers/rest/admin/trends/link_serializer.rb b/app/serializers/rest/admin/trends/link_serializer.rb new file mode 100644 index 0000000000..c93e6c1781 --- /dev/null +++ b/app/serializers/rest/admin/trends/link_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::LinkSerializer < REST::Trends::LinkSerializer + attributes :id, :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb b/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb new file mode 100644 index 0000000000..fba0259fbb --- /dev/null +++ b/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::Links::PreviewCardProviderSerializer < ActiveModel::Serializer + attributes :id, :domain, :trendable, :reviewed_at, + :requested_review_at, :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/app/serializers/rest/admin/trends/status_serializer.rb b/app/serializers/rest/admin/trends/status_serializer.rb new file mode 100644 index 0000000000..e46be30ab3 --- /dev/null +++ b/app/serializers/rest/admin/trends/status_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::StatusSerializer < REST::StatusSerializer + attributes :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/config/application.rb b/config/application.rb index f0e65f4437..171dab11f6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -43,6 +43,7 @@ require_relative '../lib/chewy/strategy/bypass_with_warning' require_relative '../lib/webpacker/manifest_extensions' require_relative '../lib/webpacker/helper_extensions' require_relative '../lib/rails/engine_extensions' +require_relative '../lib/action_controller/conditional_get_extensions' require_relative '../lib/active_record/database_tasks_extensions' require_relative '../lib/active_record/batches' require_relative '../lib/simple_navigation/item_extensions' diff --git a/config/routes.rb b/config/routes.rb index a1e2033ce7..109ad8f954 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -676,9 +676,33 @@ Rails.application.routes.draw do resources :ip_blocks, only: [:index, :show, :update, :create, :destroy] namespace :trends do - resources :tags, only: [:index] - resources :links, only: [:index] - resources :statuses, only: [:index] + resources :tags, only: [:index] do + member do + post :approve + post :reject + end + end + resources :links, only: [:index] do + member do + post :approve + post :reject + end + end + resources :statuses, only: [:index] do + member do + post :approve + post :reject + end + end + + namespace :links do + resources :preview_card_providers, only: [:index], path: :publishers do + member do + post :approve + post :reject + end + end + end end post :measures, to: 'measures#create' diff --git a/lib/action_controller/conditional_get_extensions.rb b/lib/action_controller/conditional_get_extensions.rb new file mode 100644 index 0000000000..192ed341ea --- /dev/null +++ b/lib/action_controller/conditional_get_extensions.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActionController + module ConditionalGetExtensions + def expires_in(*) + # This backports a fix from Rails 7 so that a more private Cache-Control + # can be overriden by calling expires_in on a specific controller action + response.cache_control.delete(:no_store) + + super + end + end +end + +ActionController::ConditionalGet.prepend(ActionController::ConditionalGetExtensions) diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 1601a6cd37..5194cd80a8 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -189,9 +189,10 @@ module Mastodon user.disabled = true if options[:disable] user.approved = true if options[:approve] user.otp_required_for_login = false if options[:disable_2fa] - user.confirm if options[:confirm] if user.save + user.confirm if options[:confirm] + say('OK', :green) say("New password: #{password}") if options[:reset_password] else diff --git a/package.json b/package.json index b36f057792..b796f59d2c 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "fuzzysort": "^2.0.4", "glob": "^10.0.0", "history": "^4.10.1", - "http-link-header": "^1.1.0", + "http-link-header": "^1.1.1", "immutable": "^4.3.0", "imports-loader": "^1.2.0", "intl": "^1.2.5", @@ -83,7 +83,7 @@ "path-complete-extname": "^1.0.0", "pg": "^8.5.0", "pg-connection-string": "^2.5.0", - "postcss": "^8.4.21", + "postcss": "^8.4.22", "postcss-loader": "^4.3.0", "promise.prototype.finally": "^3.1.4", "prop-types": "^15.8.1", @@ -113,7 +113,7 @@ "redux-thunk": "^2.4.2", "regenerator-runtime": "^0.13.11", "requestidlecallback": "^0.3.0", - "reselect": "^4.1.7", + "reselect": "^4.1.8", "rimraf": "^5.0.0", "sass": "^1.61.0", "sass-loader": "^10.2.0", diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index 9c38b30322..0cdccd70d1 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -17,6 +17,10 @@ RSpec.describe AccountsController, type: :controller do expect(session).to be_empty end + it 'returns Vary header' do + expect(response.headers['Vary']).to include 'Accept' + end + it 'returns public Cache-Control header' do expect(response.headers['Cache-Control']).to include 'public' end diff --git a/spec/controllers/admin/base_controller_spec.rb b/spec/controllers/admin/base_controller_spec.rb index 5fbf8777c7..bfb9d2c7d4 100644 --- a/spec/controllers/admin/base_controller_spec.rb +++ b/spec/controllers/admin/base_controller_spec.rb @@ -18,6 +18,14 @@ describe Admin::BaseController, type: :controller do expect(response).to have_http_status(403) end + it 'returns private cache control headers' do + routes.draw { get 'success' => 'admin/base#success' } + sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator'))) + get :success + + expect(response.headers['Cache-Control']).to include('private, no-store') + end + it 'renders admin layout as a moderator' do routes.draw { get 'success' => 'admin/base#success' } sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator'))) diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index c286b8cbfc..080eab3c05 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -15,6 +15,12 @@ describe Api::BaseController do end end + it 'returns private cache control headers by default' do + routes.draw { get 'success' => 'api/base#success' } + get :success + expect(response.headers['Cache-Control']).to include('private, no-store') + end + describe 'forgery protection' do before do routes.draw { post 'success' => 'api/base#success' } diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb index 930f362505..02875ee9f3 100644 --- a/spec/controllers/api/oembed_controller_spec.rb +++ b/spec/controllers/api/oembed_controller_spec.rb @@ -17,5 +17,9 @@ RSpec.describe Api::OEmbedController, type: :controller do it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end end diff --git a/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb b/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb new file mode 100644 index 0000000000..883a55b7b6 --- /dev/null +++ b/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::Admin::Trends::Links::PreviewCardProvidersController do + render_views + + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:account) { Fabricate(:account) } + let(:preview_card_provider) { Fabricate(:preview_card_provider) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + describe 'GET #index' do + it 'returns http success' do + get :index, params: { account_id: account.id, limit: 2 } + + expect(response).to have_http_status(200) + end + end + + describe 'POST #approve' do + before do + post :approve, params: { id: preview_card_provider.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: preview_card_provider.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb b/spec/controllers/api/v1/admin/trends/links_controller_spec.rb index a64292f067..9c144d3faf 100644 --- a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/links_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::LinksController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:preview_card) { Fabricate(:preview_card) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::LinksController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: preview_card.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: preview_card.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb b/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb index 821cc499f4..d25186b376 100644 --- a/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::StatusesController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:status) { Fabricate(:status) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::StatusesController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: status.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: status.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb b/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb index 480306ce7e..5ee443d575 100644 --- a/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::TagsController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:tag) { Fabricate(:tag) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::TagsController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: tag.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: tag.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index e3a00fa397..fffa7e06d7 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -33,27 +33,42 @@ RSpec.describe Auth::RegistrationsController, type: :controller do end describe 'GET #edit' do - it 'returns http success' do + before do request.env['devise.mapping'] = Devise.mappings[:user] sign_in(Fabricate(:user)) get :edit + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control header' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'GET #update' do - it 'returns http success' do + let(:user) { Fabricate(:user) } + + before do request.env['devise.mapping'] = Devise.mappings[:user] - sign_in(Fabricate(:user), scope: :user) + sign_in(user, scope: :user) post :update + end + + it 'returns http success' do expect(response).to have_http_status(200) end + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end + context 'when suspended' do + let(:user) { Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }) } + it 'returns http forbidden' do - request.env['devise.mapping'] = Devise.mappings[:user] - sign_in(Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }), scope: :user) - post :update expect(response).to have_http_status(403) end end diff --git a/spec/controllers/custom_css_controller_spec.rb b/spec/controllers/custom_css_controller_spec.rb index 47fe6031fa..99d36d21b9 100644 --- a/spec/controllers/custom_css_controller_spec.rb +++ b/spec/controllers/custom_css_controller_spec.rb @@ -6,9 +6,25 @@ describe CustomCssController do render_views describe 'GET #show' do - it 'returns http success' do + before do get :show + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns public cache control header' do + expect(response.headers['Cache-Control']).to include('public') + end + + it 'does not set cookies' do + expect(response.cookies).to be_empty + expect(response.headers['Set-Cookies']).to be_nil + end + + it 'does not set sessions' do + expect(session).to be_empty + end end end diff --git a/spec/controllers/filters/statuses_controller_spec.rb b/spec/controllers/filters/statuses_controller_spec.rb index 492361188b..2c80613302 100644 --- a/spec/controllers/filters/statuses_controller_spec.rb +++ b/spec/controllers/filters/statuses_controller_spec.rb @@ -18,21 +18,27 @@ describe Filters::StatusesController do context 'with a signed in user' do context 'with the filter user signed in' do - before { sign_in(filter.account.user) } + before do + sign_in(filter.account.user) + get :index, params: { filter_id: filter } + end it 'returns http success' do - get :index, params: { filter_id: filter } - expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end context 'with another user signed in' do - before { sign_in(Fabricate(:user)) } + before do + sign_in(Fabricate(:user)) + get :index, params: { filter_id: filter } + end it 'returns http not found' do - get :index, params: { filter_id: filter } - expect(response).to have_http_status(404) end end diff --git a/spec/controllers/filters_controller_spec.rb b/spec/controllers/filters_controller_spec.rb index f68f87ba7a..091f714bb3 100644 --- a/spec/controllers/filters_controller_spec.rb +++ b/spec/controllers/filters_controller_spec.rb @@ -7,21 +7,28 @@ describe FiltersController do describe 'GET #index' do context 'with signed out user' do - it 'redirects' do + before do get :index + end + it 'redirects' do expect(response).to be_redirect end end context 'with a signed in user' do - before { sign_in(Fabricate(:user)) } + before do + sign_in(Fabricate(:user)) + get :index + end it 'returns http success' do - get :index - expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end end end diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index 408c5e1b53..8718403bf3 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -5,35 +5,40 @@ require 'rails_helper' describe InvitesController do render_views + let(:user) { Fabricate(:user) } + before do sign_in user end describe 'GET #index' do - subject { get :index } - - let(:user) { Fabricate(:user) } - let!(:invite) { Fabricate(:invite, user: user) } + before do + Fabricate(:invite, user: user) + end context 'when everyone can invite' do before do UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users]) + get :index end - it 'renders index page' do - expect(subject).to render_template :index - expect(assigns(:invites)).to include invite - expect(assigns(:invites).count).to eq 1 + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') end end context 'when not everyone can invite' do before do UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users]) + get :index end - it 'returns 403' do - expect(subject).to have_http_status 403 + it 'returns http forbidden' do + expect(response).to have_http_status(403) end end end @@ -42,8 +47,6 @@ describe InvitesController do subject { post :create, params: { invite: { max_uses: '10', expires_in: 1800 } } } context 'when everyone can invite' do - let(:user) { Fabricate(:user) } - before do UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users]) end @@ -56,26 +59,28 @@ describe InvitesController do end context 'when not everyone can invite' do - let(:user) { Fabricate(:user) } - before do UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users]) end - it 'returns 403' do - expect(subject).to have_http_status 403 + it 'returns http forbidden' do + expect(subject).to have_http_status(403) end end end describe 'DELETE #create' do - subject { delete :destroy, params: { id: invite.id } } + let(:invite) { Fabricate(:invite, user: user, expires_at: nil) } - let(:user) { Fabricate(:user) } - let!(:invite) { Fabricate(:invite, user: user, expires_at: nil) } + before do + delete :destroy, params: { id: invite.id } + end + + it 'redirects' do + expect(response).to redirect_to invites_path + end it 'expires invite' do - expect(subject).to redirect_to invites_path expect(invite.reload).to be_expired end end diff --git a/spec/controllers/manifests_controller_spec.rb b/spec/controllers/manifests_controller_spec.rb index ecd6957fc2..d0699c438b 100644 --- a/spec/controllers/manifests_controller_spec.rb +++ b/spec/controllers/manifests_controller_spec.rb @@ -7,11 +7,24 @@ describe ManifestsController do describe 'GET #show' do before do - get :show, format: :json + get :show end it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns public cache control header' do + expect(response.headers['Cache-Control']).to include('public') + end + + it 'does not set cookies' do + expect(response.cookies).to be_empty + expect(response.headers['Set-Cookies']).to be_nil + end + + it 'does not set sessions' do + expect(session).to be_empty + end end end diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index c5eeea397e..6f087625c5 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -31,6 +31,11 @@ RSpec.describe Oauth::AuthorizationsController, type: :controller do expect(response).to have_http_status(200) end + it 'returns private cache control headers' do + subject + expect(response.headers['Cache-Control']).to include('private, no-store') + end + it 'gives options to authorize and deny' do subject expect(response.body).to match(/Authorize/) diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/controllers/oauth/authorized_applications_controller_spec.rb index 885bfa35b3..b54610604c 100644 --- a/spec/controllers/oauth/authorized_applications_controller_spec.rb +++ b/spec/controllers/oauth/authorized_applications_controller_spec.rb @@ -27,6 +27,11 @@ describe Oauth::AuthorizedApplicationsController do expect(response).to have_http_status(200) end + it 'returns private cache control headers' do + subject + expect(response.headers['Cache-Control']).to include('private, no-store') + end + include_examples 'stores location for user' end diff --git a/spec/controllers/relationships_controller_spec.rb b/spec/controllers/relationships_controller_spec.rb index 53a5daa517..9495fc214f 100644 --- a/spec/controllers/relationships_controller_spec.rb +++ b/spec/controllers/relationships_controller_spec.rb @@ -7,42 +7,39 @@ describe RelationshipsController do let(:user) { Fabricate(:user) } - shared_examples 'authenticate user' do - it 'redirects when not signed in' do - expect(subject).to redirect_to '/auth/sign_in' - end - end - describe 'GET #show' do - subject { get :show, params: { page: 2, relationship: 'followed_by' } } + context 'when signed in' do + before do + sign_in user, scope: :user + get :show, params: { page: 2, relationship: 'followed_by' } + end - it 'assigns @accounts' do - Fabricate(:account, domain: 'old').follow!(user.account) - Fabricate(:account, domain: 'recent').follow!(user.account) + it 'returns http success' do + expect(response).to have_http_status(200) + end - sign_in user, scope: :user - subject - - assigned = assigns(:accounts).per(1).to_a - expect(assigned.size).to eq 1 - expect(assigned[0].domain).to eq 'old' + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end - it 'returns http success' do - sign_in user, scope: :user - subject - expect(response).to have_http_status(200) - end + context 'when not signed in' do + before do + get :show, params: { page: 2, relationship: 'followed_by' } + end - include_examples 'authenticate user' + it 'redirects when not signed in' do + expect(response).to redirect_to '/auth/sign_in' + end + end end describe 'PATCH #update' do - let(:poopfeast) { Fabricate(:account, username: 'poopfeast', domain: 'example.com') } + let(:alice) { Fabricate(:account, username: 'alice', domain: 'example.com') } shared_examples 'redirects back to followers page' do it 'redirects back to followers page' do - poopfeast.follow!(user.account) + alice.follow!(user.account) sign_in user, scope: :user subject @@ -58,27 +55,36 @@ describe RelationshipsController do end context 'when select parameter is provided' do - subject { patch :update, params: { form_account_batch: { account_ids: [poopfeast.id] }, remove_domains_from_followers: '' } } + subject { patch :update, params: { form_account_batch: { account_ids: [alice.id] }, remove_domains_from_followers: '' } } it 'soft-blocks followers from selected domains' do - poopfeast.follow!(user.account) + alice.follow!(user.account) sign_in user, scope: :user subject - expect(poopfeast.following?(user.account)).to be false + expect(alice.following?(user.account)).to be false end it 'does not unfollow users from selected domains' do - user.account.follow!(poopfeast) + user.account.follow!(alice) sign_in user, scope: :user subject - expect(user.account.following?(poopfeast)).to be true + expect(user.account.following?(alice)).to be true + end + + context 'when not signed in' do + before do + subject + end + + it 'redirects when not signed in' do + expect(response).to redirect_to '/auth/sign_in' + end end - include_examples 'authenticate user' include_examples 'redirects back to followers page' end end diff --git a/spec/controllers/settings/aliases_controller_spec.rb b/spec/controllers/settings/aliases_controller_spec.rb index ef8724faf4..9636c1ac55 100644 --- a/spec/controllers/settings/aliases_controller_spec.rb +++ b/spec/controllers/settings/aliases_controller_spec.rb @@ -13,10 +13,17 @@ describe Settings::AliasesController do end describe 'GET #index' do - it 'returns http success' do + before do get :index + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'POST #create' do diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb index 5c6b04a155..e12628a18b 100644 --- a/spec/controllers/settings/applications_controller_spec.rb +++ b/spec/controllers/settings/applications_controller_spec.rb @@ -13,13 +13,17 @@ describe Settings::ApplicationsController do end describe 'GET #index' do - let!(:other_app) { Fabricate(:application) } - - it 'shows apps' do + before do + Fabricate(:application) get :index + end + + it 'returns http success' do expect(response).to have_http_status(200) - expect(assigns(:applications)).to include(app) - expect(assigns(:applications)).to_not include(other_app) + end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') end end diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb index a7edac6a96..7ee18f9fb1 100644 --- a/spec/controllers/settings/deletes_controller_spec.rb +++ b/spec/controllers/settings/deletes_controller_spec.rb @@ -11,20 +11,27 @@ describe Settings::DeletesController do before do sign_in user, scope: :user + get :show end it 'renders confirmation page' do - get :show expect(response).to have_http_status(200) end + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end + context 'when suspended' do let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) } it 'returns http forbidden' do - get :show expect(response).to have_http_status(403) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end end diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb index a46fe095d8..6a42021318 100644 --- a/spec/controllers/settings/exports_controller_spec.rb +++ b/spec/controllers/settings/exports_controller_spec.rb @@ -11,16 +11,16 @@ describe Settings::ExportsController do before do sign_in user, scope: :user + get :show end - it 'renders export' do - get :show - - export = assigns(:export) - expect(export).to be_instance_of Export - expect(export.account).to eq user.account + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end context 'when not signed in' do diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index 78973df2b3..98ba897e41 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -10,10 +10,17 @@ RSpec.describe Settings::ImportsController, type: :controller do end describe 'GET #show' do - it 'returns http success' do + before do get :show + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'POST #create' do diff --git a/spec/controllers/settings/login_activities_controller_spec.rb b/spec/controllers/settings/login_activities_controller_spec.rb index 6f1f3de314..80c8f484cc 100644 --- a/spec/controllers/settings/login_activities_controller_spec.rb +++ b/spec/controllers/settings/login_activities_controller_spec.rb @@ -12,9 +12,16 @@ describe Settings::LoginActivitiesController do end describe 'GET #index' do - it 'returns http success' do + before do get :index + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end end diff --git a/spec/controllers/settings/migration/redirects_controller_spec.rb b/spec/controllers/settings/migration/redirects_controller_spec.rb index 54897bb7fd..aa6df64cff 100644 --- a/spec/controllers/settings/migration/redirects_controller_spec.rb +++ b/spec/controllers/settings/migration/redirects_controller_spec.rb @@ -12,10 +12,17 @@ describe Settings::Migration::RedirectsController do end describe 'GET #new' do - it 'returns http success' do + before do get :new + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'POST #create' do diff --git a/spec/controllers/settings/preferences/appearance_controller_spec.rb b/spec/controllers/settings/preferences/appearance_controller_spec.rb index df0237a6b3..083bf49544 100644 --- a/spec/controllers/settings/preferences/appearance_controller_spec.rb +++ b/spec/controllers/settings/preferences/appearance_controller_spec.rb @@ -12,11 +12,17 @@ describe Settings::Preferences::AppearanceController do end describe 'GET #show' do - it 'returns http success' do + before do get :show + end + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'PUT #update' do diff --git a/spec/controllers/settings/preferences/notifications_controller_spec.rb b/spec/controllers/settings/preferences/notifications_controller_spec.rb index 29b7b6aec5..6a04df9edb 100644 --- a/spec/controllers/settings/preferences/notifications_controller_spec.rb +++ b/spec/controllers/settings/preferences/notifications_controller_spec.rb @@ -12,10 +12,17 @@ describe Settings::Preferences::NotificationsController do end describe 'GET #show' do - it 'returns http success' do + before do get :show + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'PUT #update' do diff --git a/spec/controllers/settings/preferences/other_controller_spec.rb b/spec/controllers/settings/preferences/other_controller_spec.rb index 249d1b5b59..750510b04b 100644 --- a/spec/controllers/settings/preferences/other_controller_spec.rb +++ b/spec/controllers/settings/preferences/other_controller_spec.rb @@ -12,10 +12,17 @@ describe Settings::Preferences::OtherController do end describe 'GET #show' do - it 'returns http success' do + before do get :show + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'PUT #update' do diff --git a/spec/controllers/settings/profiles_controller_spec.rb b/spec/controllers/settings/profiles_controller_spec.rb index 563e60271f..52ae1f5191 100644 --- a/spec/controllers/settings/profiles_controller_spec.rb +++ b/spec/controllers/settings/profiles_controller_spec.rb @@ -13,10 +13,17 @@ RSpec.describe Settings::ProfilesController, type: :controller do end describe 'GET #show' do - it 'returns http success' do + before do get :show + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'PUT #update' do diff --git a/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb b/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb index 153eca1a5a..3e61912ad4 100644 --- a/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb @@ -26,23 +26,25 @@ describe Settings::TwoFactorAuthenticationMethodsController do describe 'when user has enabled otp' do before do user.update(otp_required_for_login: true) + get :index end it 'returns http success' do - get :index - expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'when user has not enabled otp' do before do user.update(otp_required_for_login: false) + get :index end it 'redirects to enable otp' do - get :index - expect(response).to redirect_to(settings_otp_authentication_path) end end diff --git a/spec/controllers/statuses_cleanup_controller_spec.rb b/spec/controllers/statuses_cleanup_controller_spec.rb index 969778bbfe..693260f92b 100644 --- a/spec/controllers/statuses_cleanup_controller_spec.rb +++ b/spec/controllers/statuses_cleanup_controller_spec.rb @@ -11,19 +11,32 @@ RSpec.describe StatusesCleanupController, type: :controller do end describe 'GET #show' do - it 'returns http success' do + before do get :show + end + + it 'returns http success' do expect(response).to have_http_status(200) end + + it 'returns private cache control headers' do + expect(response.headers['Cache-Control']).to include('private, no-store') + end end describe 'PUT #update' do - it 'updates the account status cleanup policy' do + before do put :update, params: { account_statuses_cleanup_policy: { enabled: true, min_status_age: 2.weeks.seconds, keep_direct: false, keep_polls: true } } - expect(response).to redirect_to(statuses_cleanup_path) + end + + it 'updates the account status cleanup policy' do expect(@user.account.statuses_cleanup_policy.enabled).to be true expect(@user.account.statuses_cleanup_policy.keep_direct).to be false expect(@user.account.statuses_cleanup_policy.keep_polls).to be true end + + it 'redirects' do + expect(response).to redirect_to(statuses_cleanup_path) + end end end diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index c8b503d683..f0a40707fe 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -15,6 +15,10 @@ describe StatusesController do expect(session).to be_empty end + it 'returns Vary header' do + expect(response.headers['Vary']).to include 'Accept' + end + it 'returns public Cache-Control header' do expect(response.headers['Cache-Control']).to include 'public' end diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb index 8a3fa0bf8c..9287a6ab52 100644 --- a/spec/controllers/tags_controller_spec.rb +++ b/spec/controllers/tags_controller_spec.rb @@ -6,21 +6,50 @@ RSpec.describe TagsController, type: :controller do render_views describe 'GET #show' do - let!(:tag) { Fabricate(:tag, name: 'test') } - let!(:local) { Fabricate(:status, tags: [tag], text: 'local #test') } - let!(:remote) { Fabricate(:status, tags: [tag], text: 'remote #test', account: Fabricate(:account, domain: 'remote')) } - let!(:late) { Fabricate(:status, tags: [tag], text: 'late #test') } + let(:format) { 'html' } + let(:tag) { Fabricate(:tag, name: 'test') } + let(:tag_name) { tag&.name } + + before do + get :show, params: { id: tag_name, format: format } + end context 'when tag exists' do - it 'returns http success' do - get :show, params: { id: 'test', max_id: late.id } - expect(response).to have_http_status(200) + context 'when requested as HTML' do + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + end + + context 'when requested as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end end end context 'when tag does not exist' do + let(:tag_name) { 'hoge' } + it 'returns http not found' do - get :show, params: { id: 'none' } expect(response).to have_http_status(404) end end diff --git a/yarn.lock b/yarn.lock index df91c9fb86..4bc8b644cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6032,10 +6032,10 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-link-header@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.0.tgz#a1ca87efdbcb7778d8d0d4525de1e6964ec1f129" - integrity sha512-pj6N1yxOz/ANO8HHsWGg/OoIL1kmRYvQnXQ7PIRpgp+15AnEsRH8fmIJE6D1OdWG2Bov+BJHVla1fFXxg1JbbA== +http-link-header@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.1.tgz#f0e6971b0ed86e858d2077066ecb7ba4f2e50de9" + integrity sha512-mW3N/rTYpCn99s1do0zx6nzFZSwLH9HGfUM4ZqLWJ16ylmYaC2v5eYGqrNTQlByx8AzUgGI+V/32gXPugs1+Sw== http-parser-js@>=0.5.1: version "0.5.3" @@ -8005,10 +8005,10 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== nanomatch@^1.2.9: version "1.2.13" @@ -9007,12 +9007,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.2.15, postcss@^8.4.21: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== +postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.22: + version "8.4.22" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.22.tgz#c29e6776b60ab3af602d4b513d5bd2ff9aa85dc1" + integrity sha512-XseknLAfRHzVWjCEtdviapiBtfLdgyzExD50Rg2ePaucEesyh8Wv4VPdW0nbyDa1ydbrAxV19jvMT4+LFmcNUA== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -9789,10 +9789,10 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -reselect@^4.1.7: - version "4.1.7" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42" - integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== +reselect@^4.1.8: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== resolve-cwd@^2.0.0: version "2.0.0"