diff --git a/.circleci/config.yml b/.circleci/config.yml
index bddfd2d27a..a373d685e0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,8 +1,8 @@
version: 2.1
orbs:
- ruby: circleci/ruby@1.4.1
- node: circleci/node@5.0.1
+ ruby: circleci/ruby@2.0.0
+ node: circleci/node@5.0.3
executors:
default:
@@ -19,11 +19,11 @@ executors:
DB_USER: root
DISABLE_SIMPLECOV: true
RAILS_ENV: test
- - image: cimg/postgres:14.0
+ - image: cimg/postgres:14.5
environment:
POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- - image: cimg/redis:6.2
+ - image: cimg/redis:7.0
commands:
install-system-dependencies:
@@ -45,7 +45,7 @@ commands:
bundle config without 'development production'
name: Set bundler settings
- ruby/install-deps:
- bundler-version: '2.3.8'
+ bundler-version: '2.3.26'
key: ruby<< parameters.ruby-version >>-gems-v1
wait-db:
steps:
@@ -221,5 +221,5 @@ workflows:
pkg-manager: yarn
requires:
- build
- version: lts
+ version: '16.18'
yarn-run: test:jest
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index ac495e1c91..425b86a6bb 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -9,7 +9,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
# The value is a comma-separated list of allowed domains
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev"
-# [Choice] Node.js version: lts/*, 16, 14, 12, 10
+# [Choice] Node.js version: lts/*, 18, 16, 14
ARG NODE_VERSION="lts/*"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 47497794fb..01941a9d30 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -2,7 +2,7 @@
"name": "Mastodon",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
- "workspaceFolder": "/workspaces/mastodon",
+ "workspaceFolder": "/mastodon",
// Set *default* container specific settings.json values on container create.
"settings": {},
@@ -20,7 +20,7 @@
"forwardPorts": [3000, 4000],
// Use 'postCreateCommand' to run commands after the container is created.
- "postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup",
+ "postCreateCommand": ".devcontainer/post-create.sh",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
index 46f42c4549..95f401379c 100644
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/docker-compose.yml
@@ -11,9 +11,9 @@ services:
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: '3.0-bullseye'
# Optional Node.js version to install
- NODE_VERSION: '14'
+ NODE_VERSION: '16'
volumes:
- - ..:/workspaces/mastodon:cached
+ - ..:/mastodon:cached
environment:
RAILS_ENV: development
NODE_ENV: development
diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh
new file mode 100755
index 0000000000..02f488f120
--- /dev/null
+++ b/.devcontainer/post-create.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e # Fail the whole script on first error
+
+# Fetch Ruby gem dependencies
+bundle install --path vendor/bundle --with='development test'
+
+# Fetch Javascript dependencies
+yarn install
+
+# Make Gemfile.lock pristine again
+git checkout -- Gemfile.lock
+
+# [re]create, migrate, and seed the test database
+RAILS_ENV=test ./bin/rails db:setup
+
+# Precompile assets for development
+RAILS_ENV=development ./bin/rails assets:precompile
+
+# Precompile assets for test
+RAILS_ENV=test NODE_ENV=tests ./bin/rails assets:precompile
diff --git a/.env.production.sample b/.env.production.sample
index da4c7fe4c8..7bcce0f7e5 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -103,7 +103,7 @@ VAPID_PUBLIC_KEY=
# Sending mail
# ------------
-SMTP_SERVER=smtp.mailgun.org
+SMTP_SERVER=
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml
index 3a880fabf2..bf50afe8c7 100644
--- a/.github/workflows/build-image.yml
+++ b/.github/workflows/build-image.yml
@@ -17,6 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
+ - uses: hadolint/hadolint-action@v3.0.0
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000000..88ac2fb085
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,63 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ "main" ]
+ schedule:
+ - cron: '22 6 * * 1'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'javascript', 'ruby' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.nvmrc b/.nvmrc
index 8351c19397..b6a7d89c68 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-14
+16
diff --git a/.rubocop.yml b/.rubocop.yml
index aec11b0306..67284fe34b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,12 +1,18 @@
require:
- rubocop-rails
+ - rubocop-rspec
+ - rubocop-performance
AllCops:
TargetRubyVersion: 2.7
- NewCops: disable
+ DisplayCopNames: true
+ DisplayStyleGuide: true
+ ExtraDetails: true
+ UseCache: true
+ CacheRootDirectory: tmp
+ NewCops: enable
Exclude:
- - 'spec/**/*'
- - 'db/**/*'
+ - db/schema.rb
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
@@ -67,15 +73,57 @@ Lint/UselessAccessModifier:
- class_methods
Metrics/AbcSize:
- Max: 115
+ Max: 34 # RuboCop default 17
Exclude:
- - 'lib/mastodon/*_cli.rb'
+ - 'lib/**/*cli*.rb'
+ - db/*migrate/**/*
+ - lib/paperclip/color_extractor.rb
+ - app/workers/scheduler/follow_recommendations_scheduler.rb
+ - app/services/activitypub/fetch*_service.rb
+ - lib/paperclip/**/*
+ CountRepeatedAttributes: false
+ AllowedMethods:
+ - update_media_attachments!
+ - account_link_to
+ - attempt_oembed
+ - build_crutches
+ - calculate_scores
+ - cc
+ - dump_actor!
+ - filter_from_home?
+ - hydrate
+ - import_bookmarks!
+ - import_relationships!
+ - initialize
+ - link_to_mention
+ - log_target
+ - matches_time_window?
+ - parse_metadata
+ - perform_statuses_search!
+ - privatize_media_attachments!
+ - process_update
+ - publish_media_attachments!
+ - remotable_attachment
+ - render_initial_state
+ - render_with_cache
+ - searchable_by
+ - self.cached_filters_for
+ - set_fetchable_attributes!
+ - signed_request_actor
+ - statuses_to_delete
+ - update_poll!
Metrics/BlockLength:
Max: 55
Exclude:
- - 'lib/tasks/**/*'
- 'lib/mastodon/*_cli.rb'
+ CountComments: false
+ CountAsOne: [array, heredoc]
+ AllowedMethods:
+ - task
+ - namespace
+ - class_methods
+ - included
Metrics/BlockNesting:
Max: 3
@@ -85,34 +133,144 @@ Metrics/BlockNesting:
Metrics/ClassLength:
CountComments: false
Max: 500
+ CountAsOne: [array, heredoc]
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/CyclomaticComplexity:
- Max: 25
+ Max: 12
Exclude:
- - 'lib/mastodon/*_cli.rb'
+ - lib/mastodon/*cli*.rb
+ - db/*migrate/**/*
+ AllowedMethods:
+ - attempt_oembed
+ - blocked?
+ - build_crutches
+ - calculate_scores
+ - cc
+ - discover_endpoint!
+ - filter_from_home?
+ - hydrate
+ - klass
+ - link_to_mention
+ - log_target
+ - matches_time_window?
+ - patch_for_forwarding!
+ - preprocess_attributes!
+ - process_update
+ - remotable_attachment
+ - scan_text!
+ - self.cached_filters_for
+ - set_fetchable_attributes!
+ - setup_redis_env_url
+ - update_media_attachments!
Layout/LineLength:
+ Max: 140 # RuboCop default 120
+ AllowHeredoc: true
AllowURI: true
- Enabled: false
+ IgnoreCopDirectives: true
+ AllowedPatterns:
+ # Allow comments to be long lines
+ - !ruby/regexp / \# .*$/
+ - !ruby/regexp /^\# .*$/
+ Exclude:
+ - lib/**/*cli*.rb
+ - db/*migrate/**/*
+ - db/seeds/**/*
Metrics/MethodLength:
CountComments: false
- Max: 65
+ CountAsOne: [array, heredoc]
+ Max: 25 # RuboCop default 10
Exclude:
- 'lib/mastodon/*_cli.rb'
+ AllowedMethods:
+ - account_link_to
+ - attempt_oembed
+ - body_with_limit
+ - build_crutches
+ - cached_filters_for
+ - calculate_scores
+ - check_webfinger!
+ - clean_feeds!
+ - collection_items
+ - collection_presenter
+ - copy_account_notes!
+ - deduplicate_accounts!
+ - deduplicate_conversations!
+ - deduplicate_local_accounts!
+ - deduplicate_statuses!
+ - deduplicate_tags!
+ - deduplicate_users!
+ - discover_endpoint!
+ - extract_extra_uris_with_indices
+ - extract_hashtags_with_indices
+ - extract_mentions_or_lists_with_indices
+ - filter_from_home?
+ - from_elasticsearch
+ - handle_explicit_update!
+ - handle_mark_as_sensitive!
+ - hsl_to_rgb
+ - import_bookmarks!
+ - import_domain_blocks!
+ - import_relationships!
+ - ldap_options
+ - matches_time_window?
+ - outbox_presenter
+ - pam_get_user
+ - parallelize_with_progress
+ - parse_and_transform
+ - patch_for_forwarding!
+ - populate_home
+ - post_process_style
+ - preload_cache_collection_target_statuses
+ - privatize_media_attachments!
+ - provides_callback_for
+ - publish_media_attachments!
+ - relevant_account_timestamp
+ - remotable_attachment
+ - rgb_to_hsl
+ - rss_status_content_format
+ - set_fetchable_attributes!
+ - setup_redis_env_url
+ - signed_request_actor
+ - to_preview_card_attributes
+ - upgrade_storage_filesystem
+ - upgrade_storage_s3
+ - user_settings_params
+ - hydrate
+ - cc
+ - self_destruct
Metrics/ModuleLength:
CountComments: false
Max: 200
+ CountAsOne: [array, heredoc]
Metrics/ParameterLists:
- Max: 5
- CountKeywordArgs: true
+ Max: 5 # RuboCop default 5
+ CountKeywordArgs: true # RuboCop default true
+ MaxOptionalParameters: 3 # RuboCop default 3
+ Exclude:
+ - app/models/concerns/account_interactions.rb
+ - app/services/activitypub/fetch_remote_account_service.rb
+ - app/services/activitypub/fetch_remote_actor_service.rb
Metrics/PerceivedComplexity:
- Max: 25
+ Max: 16 # RuboCop default 8
+ AllowedMethods:
+ - attempt_oembed
+ - build_crutches
+ - calculate_scores
+ - deduplicate_users!
+ - discover_endpoint!
+ - filter_from_home?
+ - hydrate
+ - patch_for_forwarding!
+ - process_update
+ - remove_orphans
+ - update_media_attachments!
Naming/MemoizedInstanceVariableName:
Enabled: false
@@ -267,9 +425,6 @@ Style/PercentLiteralDelimiters:
Style/PerlBackrefs:
AutoCorrect: false
-Style/RedundantAssignment:
- Enabled: false
-
Style/RedundantFetchBlock:
Enabled: true
@@ -292,7 +447,7 @@ Style/RegexpLiteral:
Enabled: false
Style/RescueStandardError:
- Enabled: false
+ Enabled: true
Style/SignalException:
Enabled: false
@@ -311,3 +466,14 @@ Style/TrailingCommaInHashLiteral:
Style/UnpackFirst:
Enabled: false
+
+RSpec/ScatteredSetup:
+ Enabled: false
+RSpec/ImplicitExpect:
+ Enabled: false
+RSpec/NamedSubject:
+ Enabled: false
+RSpec/DescribeClass:
+ Enabled: false
+RSpec/LetSetup:
+ Enabled: false
diff --git a/Aptfile b/Aptfile
index a52eef4e18..8f5bb72a25 100644
--- a/Aptfile
+++ b/Aptfile
@@ -1,26 +1,4 @@
ffmpeg
-libicu[0-9][0-9]
-libicu-dev
-libidn12
-libidn-dev
libpq-dev
libxdamage1
libxfixes3
-zlib1g-dev
-libcairo2
-libcroco3
-libdatrie1
-libgdk-pixbuf2.0-0
-libgraphite2-3
-libharfbuzz0b
-libpango-1.0-0
-libpangocairo-1.0-0
-libpangoft2-1.0-0
-libpixman-1-0
-librsvg2-2
-libthai-data
-libthai0
-libvpx[5-9]
-libxcb-render0
-libxcb-shm0
-libxrender1
diff --git a/Dockerfile b/Dockerfile
index 1a97965ac6..ce7f4d7186 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,8 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /opt/mastodon
COPY Gemfile* package.json yarn.lock /opt/mastodon/
-RUN apt update && \
+# hadolint ignore=DL3008
+RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential \
ca-certificates \
git \
@@ -50,10 +51,12 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
+# Ignoreing these here since we don't want to pin any versions and the Debian image removes apt-get content after use
+# hadolint ignore=DL3008,DL3009
RUN apt-get update && \
echo "Etc/UTC" > /etc/localtime && \
groupadd -g "${GID}" mastodon && \
- useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
+ useradd -l -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
apt-get -y --no-install-recommends install whois \
wget \
procps \
diff --git a/Gemfile b/Gemfile
index 42d30589f8..099e84fc08 100644
--- a/Gemfile
+++ b/Gemfile
@@ -107,6 +107,10 @@ group :development, :test do
gem 'pry-byebug', '~> 3.10'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 5.1'
+ gem 'rubocop-performance', require: false
+ gem 'rubocop-rails', require: false
+ gem 'rubocop-rspec', require: false
+ gem 'rubocop', require: false
end
group :production, :test do
@@ -117,13 +121,14 @@ group :test do
gem 'capybara', '~> 3.38'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 3.0'
+ gem 'json-schema', '~> 3.0'
gem 'microformats', '~> 4.4'
+ gem 'rack-test', '~> 2.0'
gem 'rails-controller-testing', '~> 1.0'
+ gem 'rspec_junit_formatter', '~> 0.6'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.21', require: false
gem 'webmock', '~> 3.18'
- gem 'rspec_junit_formatter', '~> 0.6'
- gem 'rack-test', '~> 2.0'
end
group :development do
@@ -135,8 +140,6 @@ group :development do
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0'
gem 'memory_profiler'
- gem 'rubocop', '~> 1.30', require: false
- gem 'rubocop-rails', '~> 2.15', require: false
gem 'brakeman', '~> 5.4', require: false
gem 'bundler-audit', '~> 0.9', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 037b3b1ee6..15d0e60015 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -346,6 +346,8 @@ GEM
json-ld-preloaded (3.2.2)
json-ld (~> 3.2)
rdf (~> 3.2)
+ json-schema (3.0.0)
+ addressable (>= 2.8)
jsonapi-renderer (0.2.2)
jwt (2.4.1)
kaminari (1.2.2)
@@ -587,21 +589,27 @@ GEM
rspec-support (3.11.1)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
- rubocop (1.30.1)
+ rubocop (1.39.0)
+ json (~> 2.3)
parallel (~> 1.10)
- parser (>= 3.1.0.0)
+ parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
- rubocop-ast (>= 1.18.0, < 2.0)
+ rubocop-ast (>= 1.23.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
- rubocop-ast (1.18.0)
+ rubocop-ast (1.23.0)
parser (>= 3.1.1.0)
- rubocop-rails (2.15.0)
+ rubocop-performance (1.15.1)
+ rubocop (>= 1.7.0, < 2.0)
+ rubocop-ast (>= 0.4.0)
+ rubocop-rails (2.17.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
- rubocop (>= 1.7.0, < 2.0)
+ rubocop (>= 1.33.0, < 2.0)
+ rubocop-rspec (2.15.0)
+ rubocop (~> 1.33)
ruby-progressbar (1.11.0)
ruby-saml (1.13.0)
nokogiri (>= 1.10.5)
@@ -794,6 +802,7 @@ DEPENDENCIES
idn-ruby
json-ld
json-ld-preloaded (~> 3.2)
+ json-schema (~> 3.0)
kaminari (~> 1.2)
kt-paperclip (~> 7.1)
letter_opener (~> 1.8)
@@ -843,8 +852,10 @@ DEPENDENCIES
rspec-rails (~> 5.1)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.6)
- rubocop (~> 1.30)
- rubocop-rails (~> 2.15)
+ rubocop
+ rubocop-performance
+ rubocop-rails
+ rubocop-rspec
ruby-progressbar (~> 1.11)
sanitize (~> 6.0)
scenic (~> 1.6)
@@ -869,3 +880,9 @@ DEPENDENCIES
webpacker (~> 5.4)
webpush!
xorcist (~> 1.1)
+
+RUBY VERSION
+ ruby 3.0.4p208
+
+BUNDLED WITH
+ 2.2.33
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index e79f7a43e1..74764640b8 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -55,12 +55,8 @@ module Admin
def update
authorize :domain_block, :update?
- @domain_block.update(update_params)
-
- severity_changed = @domain_block.severity_changed?
-
- if @domain_block.save
- DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
+ if @domain_block.update(update_params)
+ DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?)
log_action :update, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else
diff --git a/app/controllers/admin/relays_controller.rb b/app/controllers/admin/relays_controller.rb
index 6fbb6e0630..c1297c8b99 100644
--- a/app/controllers/admin/relays_controller.rb
+++ b/app/controllers/admin/relays_controller.rb
@@ -3,7 +3,7 @@
module Admin
class RelaysController < BaseController
before_action :set_relay, except: [:index, :new, :create]
- before_action :require_signatures_enabled!, only: [:new, :create, :enable]
+ before_action :warn_signatures_not_enabled!, only: [:new, :create, :enable]
def index
authorize :relay, :update?
@@ -56,8 +56,8 @@ module Admin
params.require(:relay).permit(:inbox_url)
end
- def require_signatures_enabled!
- redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
+ def warn_signatures_not_enabled!
+ flash.now[:error] = I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
end
end
end
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index defef0656f..41f3ce2ee3 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -16,6 +16,26 @@ class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session
+ content_security_policy do |p|
+ # Set every directive that does not have a fallback
+ p.default_src :none
+ p.frame_ancestors :none
+ p.form_action :none
+
+ # Disable every directive with a fallback to cut on response size
+ p.base_uri false
+ p.font_src false
+ p.img_src false
+ p.style_src false
+ p.media_src false
+ p.frame_src false
+ p.manifest_src false
+ p.connect_src false
+ p.script_src false
+ p.child_src false
+ p.worker_src false
+ end
+
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
render json: { error: e.to_s }, status: 422
end
diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb
index df5b1b3fcb..8b77e9717d 100644
--- a/app/controllers/api/v1/admin/domain_blocks_controller.rb
+++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb
@@ -40,10 +40,8 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
def update
authorize @domain_block, :update?
- @domain_block.update(domain_block_params)
- severity_changed = @domain_block.severity_changed?
- @domain_block.save!
- DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
+ @domain_block.update!(domain_block_params)
+ DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?)
log_action :update, @domain_block
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
end
diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb
index ac49167cb7..a6ed359c98 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -40,7 +40,7 @@ class Api::V1::NotificationsController < Api::BaseController
private
def load_notifications
- notifications = browserable_account_notifications.includes(from_account: :account_stat).to_a_paginated_by_id(
+ notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_paginated_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb
index 609220eb18..576c3e7bcf 100644
--- a/app/controllers/auth/passwords_controller.rb
+++ b/app/controllers/auth/passwords_controller.rb
@@ -11,6 +11,8 @@ class Auth::PasswordsController < Devise::PasswordsController
super do |resource|
if resource.errors.empty?
resource.session_activations.destroy_all
+
+ resource.revoke_access!
end
end
end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index d363efeee4..40c38bc6dd 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -57,8 +57,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def configure_sign_up_params
- devise_parameter_sanitizer.permit(:sign_up) do |u|
- u.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
+ devise_parameter_sanitizer.permit(:sign_up) do |user_params|
+ user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
end
end
diff --git a/app/controllers/concerns/rate_limit_headers.rb b/app/controllers/concerns/rate_limit_headers.rb
index 86fe58a71c..b8696df736 100644
--- a/app/controllers/concerns/rate_limit_headers.rb
+++ b/app/controllers/concerns/rate_limit_headers.rb
@@ -58,7 +58,7 @@ module RateLimitHeaders
end
def api_throttle_data
- most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] - v[:count] }
+ most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_key, value| value[:limit] - value[:count] }
request.env['rack.attack.throttle_data'][most_limited_type]
end
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index 2394574b3b..4502da698c 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -28,8 +28,8 @@ module SignatureVerification
end
class SignatureParamsTransformer < Parslet::Transform
- rule(params: subtree(:p)) do
- (p.is_a?(Array) ? p : [p]).each_with_object({}) { |(key, val), h| h[key] = val }
+ rule(params: subtree(:param)) do
+ (param.is_a?(Array) ? param : [param]).each_with_object({}) { |(key, value), hash| hash[key] = value }
end
rule(param: { key: simple(:key), value: simple(:val) }) do
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 35ce31f806..1f5ed30de9 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -63,7 +63,7 @@ class FollowerAccountsController < ApplicationController
if page_requested?
ActivityPub::CollectionPresenter.new(
id: account_followers_url(@account, page: params.fetch(:page, 1)),
- items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
+ items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.account) },
part_of: account_followers_url(@account),
next: next_page_url,
prev: prev_page_url,
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index f84dca1e5a..febd13c975 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -66,7 +66,7 @@ class FollowingAccountsController < ApplicationController
id: account_following_index_url(@account, page: params.fetch(:page, 1)),
type: :ordered,
size: @account.following_count,
- items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
+ items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.target_account) },
part_of: account_following_index_url(@account),
next: next_page_url,
prev: prev_page_url
diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb
index d2de432ba4..f9160d8c43 100644
--- a/app/controllers/media_controller.rb
+++ b/app/controllers/media_controller.rb
@@ -13,8 +13,8 @@ class MediaController < ApplicationController
before_action :allow_iframing, only: :player
before_action :set_pack, only: :player
- content_security_policy only: :player do |p|
- p.frame_ancestors(false)
+ content_security_policy only: :player do |policy|
+ policy.frame_ancestors(false)
end
def show
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 1a835c7268..e5221df3a2 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -17,8 +17,8 @@ class StatusesController < ApplicationController
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
- content_security_policy only: :embed do |p|
- p.frame_ancestors(false)
+ content_security_policy only: :embed do |policy|
+ policy.frame_ancestors(false)
end
def show
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index f0a0993506..65017acba3 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -65,7 +65,7 @@ class TagsController < ApplicationController
id: tag_url(@tag),
type: :ordered,
size: @tag.statuses.count,
- items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
+ items: @statuses.map { |status| ActivityPub::TagManager.instance.uri_for(status) }
)
end
end
diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
index 448177bec2..05c003037e 100644
--- a/app/helpers/formatting_helper.rb
+++ b/app/helpers/formatting_helper.rb
@@ -23,19 +23,28 @@ module FormattingHelper
before_html = begin
if status.spoiler_text?
- "
#{I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)} #{h(status.spoiler_text)}
"
- else
- ''
+ tag.p do
+ tag.strong do
+ I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)
+ end
+
+ status.spoiler_text
+ end + tag.hr
end
- end.html_safe # rubocop:disable Rails/OutputSafety
+ end
after_html = begin
if status.preloadable_poll
- "#{status.preloadable_poll.options.map { |o| " #{h(o)}" }.join('
')}
"
- else
- ''
+ tag.p do
+ safe_join(
+ status.preloadable_poll.options.map do |o|
+ tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', o, disabled: true)
+ end,
+ tag.br
+ )
+ end
end
- end.html_safe # rubocop:disable Rails/OutputSafety
+ end
prerender_custom_emojis(
safe_join([before_html, html, after_html]),
diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb
index fff073cedb..bb87dd596c 100644
--- a/app/helpers/languages_helper.rb
+++ b/app/helpers/languages_helper.rb
@@ -190,12 +190,15 @@ module LanguagesHelper
ISO_639_3 = {
ast: ['Asturian', 'Asturianu'].freeze,
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
+ cnr: ['Montenegrin', 'crnogorski'].freeze,
jbo: ['Lojban', 'la .lojban.'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
ldn: ['Láadan', 'Láadan'].freeze,
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
sco: ['Scots', 'Scots'].freeze,
+ sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
+ smj: ['Lule Sami', 'Julevsámegiella'].freeze,
tok: ['Toki Pona', 'toki pona'].freeze,
zba: ['Balaibalan', 'باليبلن'].freeze,
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb
index 488eabeec5..d1e3fddafe 100644
--- a/app/helpers/statuses_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -21,7 +21,7 @@ module StatusesHelper
def media_summary(status)
attachments = { image: 0, video: 0, audio: 0 }
- status.media_attachments.each do |media|
+ status.ordered_media_attachments.each do |media|
if media.video?
attachments[:video] += 1
elsif media.audio?
diff --git a/app/javascript/images/logo-symbol-icon.svg b/app/javascript/images/logo-symbol-icon.svg
index 56cf03921a..c4c14f098a 100644
--- a/app/javascript/images/logo-symbol-icon.svg
+++ b/app/javascript/images/logo-symbol-icon.svg
@@ -1,2 +1,2 @@
-
+
diff --git a/app/javascript/images/logo-symbol-wordmark.svg b/app/javascript/images/logo-symbol-wordmark.svg
index 7e7f7b087c..ee0b636d93 100644
--- a/app/javascript/images/logo-symbol-wordmark.svg
+++ b/app/javascript/images/logo-symbol-wordmark.svg
@@ -7,5 +7,5 @@
-
+
diff --git a/app/javascript/mastodon/actions/announcements.js b/app/javascript/mastodon/actions/announcements.js
index 1bdea909f7..586dcfd337 100644
--- a/app/javascript/mastodon/actions/announcements.js
+++ b/app/javascript/mastodon/actions/announcements.js
@@ -102,7 +102,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name, alreadyAdded));
}
- api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
+ api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(addReactionSuccess(announcementId, name, alreadyAdded));
}).catch(err => {
if (!alreadyAdded) {
@@ -136,7 +136,7 @@ export const addReactionFail = (announcementId, name, error) => ({
export const removeReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(removeReactionRequest(announcementId, name));
- api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
+ api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(removeReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(removeReactionFail(announcementId, name, err));
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index 2a1fedb93b..40c86afdf0 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -246,12 +246,13 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
- if (publicStatus) {
- if (isRemote) {
- menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
- }
+ if (publicStatus && isRemote) {
+ menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
+ }
- menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
+ menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
+
+ if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
}
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 724719f74f..002b71e93d 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -23,7 +23,9 @@ export const store = configureStore();
const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction);
-store.dispatch(fetchCustomEmojis());
+if (initialState.meta.me) {
+ store.dispatch(fetchCustomEmojis());
+}
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
diff --git a/app/javascript/mastodon/features/account/components/follow_request_note.js b/app/javascript/mastodon/features/account/components/follow_request_note.js
new file mode 100644
index 0000000000..300ae42660
--- /dev/null
+++ b/app/javascript/mastodon/features/account/components/follow_request_note.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import Icon from 'mastodon/components/icon';
+
+export default class FollowRequestNote extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ };
+
+ render () {
+ const { account, onAuthorize, onReject } = this.props;
+
+ return (
+
+
+ }} />
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index f117412be8..dddbf4dd4c 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -14,6 +14,7 @@ import ShortNumber from 'mastodon/components/short_number';
import { NavLink } from 'react-router-dom';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import AccountNoteContainer from '../containers/account_note_container';
+import FollowRequestNoteContainer from '../containers/follow_request_note_container';
import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions';
import { Helmet } from 'react-helmet';
@@ -311,6 +312,8 @@ class Header extends ImmutablePureComponent {
return (
+ {!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) &&
}
+
{!suspended && info}
diff --git a/app/javascript/mastodon/features/account/containers/follow_request_note_container.js b/app/javascript/mastodon/features/account/containers/follow_request_note_container.js
new file mode 100644
index 0000000000..c33c3de591
--- /dev/null
+++ b/app/javascript/mastodon/features/account/containers/follow_request_note_container.js
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux';
+import FollowRequestNote from '../components/follow_request_note';
+import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts';
+
+const mapDispatchToProps = (dispatch, { account }) => ({
+ onAuthorize () {
+ dispatch(authorizeFollowRequest(account.get('id')));
+ },
+
+ onReject () {
+ dispatch(rejectFollowRequest(account.get('id')));
+ },
+});
+
+export default connect(null, mapDispatchToProps)(FollowRequestNote);
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index f16fe07f1d..13fd7fe03d 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -104,6 +104,7 @@ export default class MediaItem extends ImmutablePureComponent {