mirror of https://github.com/mastodon/mastodon.git
Compare commits
6 Commits
40104547ea
...
cc55c891ee
Author | SHA1 | Date |
---|---|---|
Matt Jankowski | cc55c891ee | |
Emelia Smith | 2da2a1dae9 | |
Claire | 12472e7f40 | |
Jeong Arm | a627219b25 | |
Matt Jankowski | e5e87441a2 | |
Matt Jankowski | 6f16033054 |
|
@ -4,7 +4,8 @@ NODE_ENV=production
|
|||
LOCAL_DOMAIN=cb6e6126.ngrok.io
|
||||
LOCAL_HTTPS=true
|
||||
|
||||
# Required by ActiveRecord encryption feature
|
||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR
|
||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E
|
||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr
|
||||
# Secret values required by ActiveRecord encryption feature
|
||||
# Use `bin/rails db:encryption:init` to generate fresh secrets
|
||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
|
||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=test_salt_DO_NOT_USE_IN_PRODUCTION
|
||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=test_primary_key_DO_NOT_USE_IN_PRODUCTION
|
||||
|
|
|
@ -136,15 +136,17 @@ jobs:
|
|||
additional-system-dependencies: ffmpeg imagemagick libpam-dev
|
||||
|
||||
- name: Load database schema
|
||||
run: './bin/rails db:create db:schema:load db:seed'
|
||||
run: |
|
||||
bundle exec rake db:setup
|
||||
bin/flatware fan rake db:test:prepare
|
||||
|
||||
- run: bin/rspec
|
||||
- run: bin/flatware rspec -r ./spec/flatware_helper.rb
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: matrix.ruby-version == '.ruby-version'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: coverage/lcov/mastodon.lcov
|
||||
files: coverage/lcov/*.lcov
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -122,6 +122,9 @@ group :opentelemetry do
|
|||
end
|
||||
|
||||
group :test do
|
||||
# Enable usage of all available CPUs/cores during spec runs
|
||||
gem 'flatware-rspec'
|
||||
|
||||
# Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
|
||||
gem 'rspec-github', '~> 2.4', require: false
|
||||
|
||||
|
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -264,6 +264,11 @@ GEM
|
|||
ffi-compiler (1.3.2)
|
||||
ffi (>= 1.15.5)
|
||||
rake
|
||||
flatware (2.2.1)
|
||||
thor (< 2.0)
|
||||
flatware-rspec (2.2.1)
|
||||
flatware (= 2.2.1)
|
||||
rspec (>= 3.6)
|
||||
fog-core (2.4.0)
|
||||
builder
|
||||
excon (~> 0.71)
|
||||
|
@ -702,6 +707,10 @@ GEM
|
|||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec (3.13.0)
|
||||
rspec-core (~> 3.13.0)
|
||||
rspec-expectations (~> 3.13.0)
|
||||
rspec-mocks (~> 3.13.0)
|
||||
rspec-core (3.13.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.0)
|
||||
|
@ -935,6 +944,7 @@ DEPENDENCIES
|
|||
faker (~> 3.2)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
flatware-rspec
|
||||
fog-core (<= 2.4.0)
|
||||
fog-openstack (~> 1.0)
|
||||
fuubar (~> 2.5)
|
||||
|
|
|
@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
|
|||
def show
|
||||
return doorkeeper_render_error unless valid_doorkeeper_token?
|
||||
|
||||
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes)
|
||||
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ class Api::V1::AppsController < Api::BaseController
|
|||
|
||||
def create
|
||||
@app = Doorkeeper::Application.create!(application_options)
|
||||
render json: @app, serializer: REST::ApplicationSerializer
|
||||
render json: @app, serializer: REST::CredentialApplicationSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -24,6 +24,6 @@ class Api::V1::AppsController < Api::BaseController
|
|||
end
|
||||
|
||||
def app_params
|
||||
params.permit(:client_name, :redirect_uris, :scopes, :website)
|
||||
params.permit(:client_name, :scopes, :website, :redirect_uris, redirect_uris: [])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,6 +23,12 @@ module ApplicationExtension
|
|||
redirect_uri.lines.first.strip
|
||||
end
|
||||
|
||||
def redirect_uris
|
||||
# Doorkeeper stores the redirect_uri value as a newline delimeted list in
|
||||
# the database:
|
||||
redirect_uri.split
|
||||
end
|
||||
|
||||
def push_to_streaming_api
|
||||
# TODO: #28793 Combine into a single topic
|
||||
payload = Oj.dump(event: :kill)
|
||||
|
|
|
@ -59,6 +59,7 @@ class Admin::ActionLogFilter
|
|||
unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze,
|
||||
update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,
|
||||
update_custom_emoji: { target_type: 'CustomEmoji', action: 'update' }.freeze,
|
||||
update_report: { target_type: 'Report', action: 'update' }.freeze,
|
||||
update_status: { target_type: 'Status', action: 'update' }.freeze,
|
||||
update_user_role: { target_type: 'UserRole', action: 'update' }.freeze,
|
||||
update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze,
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::ApplicationSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :website, :scopes, :redirect_uri,
|
||||
:client_id, :client_secret
|
||||
attributes :id, :name, :website, :scopes, :redirect_uris
|
||||
|
||||
# NOTE: Deprecated in 4.3.0, needs to be removed in 5.0.0
|
||||
attribute :vapid_key
|
||||
|
||||
# We should consider this property deprecated for 4.3.0
|
||||
attribute :redirect_uri
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def client_id
|
||||
object.uid
|
||||
end
|
||||
|
||||
def client_secret
|
||||
object.secret
|
||||
end
|
||||
|
||||
def website
|
||||
object.website.presence
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::CredentialApplicationSerializer < REST::ApplicationSerializer
|
||||
attributes :client_id, :client_secret
|
||||
|
||||
def client_id
|
||||
object.uid
|
||||
end
|
||||
|
||||
def client_secret
|
||||
object.secret
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'flatware' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
bundle_binstub = File.expand_path("bundle", __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("flatware", "flatware")
|
|
@ -5,7 +5,7 @@
|
|||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
|
||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
||||
).each do |key|
|
||||
ENV.fetch(key) do
|
||||
value = ENV.fetch(key) do
|
||||
abort <<~MESSAGE
|
||||
|
||||
Mastodon now requires that these variables are set:
|
||||
|
@ -14,9 +14,18 @@
|
|||
- ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
|
||||
- ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
||||
|
||||
Run `bin/rails db:encryption:init` to generate values and then assign the environment variables.
|
||||
Run `bin/rails db:encryption:init` to generate new secrets and then assign the environment variables.
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
next unless Rails.env.production? && value.end_with?('DO_NOT_USE_IN_PRODUCTION')
|
||||
|
||||
abort <<~MESSAGE
|
||||
|
||||
It looks like you are trying to run Mastodon in production with a #{key} value from the test environment.
|
||||
|
||||
Please generate fresh secrets using `bin/rails db:encryption:init` and use them instead.
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
Rails.application.configure do
|
||||
|
|
|
@ -285,6 +285,7 @@ en:
|
|||
update_custom_emoji_html: "%{name} updated emoji %{target}"
|
||||
update_domain_block_html: "%{name} updated domain block for %{target}"
|
||||
update_ip_block_html: "%{name} changed rule for IP %{target}"
|
||||
update_report_html: "%{name} updated report %{target}"
|
||||
update_status_html: "%{name} updated post by %{target}"
|
||||
update_user_role_html: "%{name} changed %{target} role"
|
||||
deleted_account: deleted account
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace :db do
|
|||
desc 'Generate a set of keys for configuring Active Record encryption in a given environment'
|
||||
task :init do # rubocop:disable Rails/RakeEnvironment
|
||||
puts <<~MSG
|
||||
Add these environment variables to your Mastodon environment:#{' '}
|
||||
Add these secret environment variables to your Mastodon environment (e.g. .env.production):#{' '}
|
||||
|
||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=#{SecureRandom.alphanumeric(32)}
|
||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=#{SecureRandom.alphanumeric(32)}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
if defined?(Flatware)
|
||||
Flatware.configure do |config|
|
||||
config.after_fork do |test_env_number|
|
||||
unless ENV.fetch('DISABLE_SIMPLECOV', nil) == 'true'
|
||||
require 'simplecov'
|
||||
SimpleCov.at_fork.call(test_env_number) # Combines parallel coverage results
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,14 +20,26 @@ describe 'Credentials' do
|
|||
|
||||
expect(body_as_json).to match(
|
||||
a_hash_including(
|
||||
id: token.application.id.to_s,
|
||||
name: token.application.name,
|
||||
website: token.application.website,
|
||||
vapid_key: Rails.configuration.x.vapid_public_key,
|
||||
scopes: token.application.scopes.map(&:to_s),
|
||||
client_id: token.application.uid
|
||||
redirect_uris: token.application.redirect_uris,
|
||||
# Deprecated properties as of 4.3:
|
||||
redirect_uri: token.application.redirect_uri.split.first,
|
||||
vapid_key: Rails.configuration.x.vapid_public_key
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not expose the client_id or client_secret' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(body_as_json[:client_id]).to_not be_present
|
||||
expect(body_as_json[:client_secret]).to_not be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non-read scoped oauth token' do
|
||||
|
@ -46,11 +58,14 @@ describe 'Credentials' do
|
|||
|
||||
expect(body_as_json).to match(
|
||||
a_hash_including(
|
||||
id: token.application.id.to_s,
|
||||
name: token.application.name,
|
||||
website: token.application.website,
|
||||
vapid_key: Rails.configuration.x.vapid_public_key,
|
||||
scopes: token.application.scopes.map(&:to_s),
|
||||
client_id: token.application.uid
|
||||
redirect_uris: token.application.redirect_uris,
|
||||
# Deprecated properties as of 4.3:
|
||||
redirect_uri: token.application.redirect_uri.split.first,
|
||||
vapid_key: Rails.configuration.x.vapid_public_key
|
||||
)
|
||||
)
|
||||
end
|
||||
|
|
|
@ -9,8 +9,9 @@ RSpec.describe 'Apps' do
|
|||
end
|
||||
|
||||
let(:client_name) { 'Test app' }
|
||||
let(:scopes) { nil }
|
||||
let(:redirect_uris) { 'urn:ietf:wg:oauth:2.0:oob' }
|
||||
let(:scopes) { 'read write' }
|
||||
let(:redirect_uri) { 'urn:ietf:wg:oauth:2.0:oob' }
|
||||
let(:redirect_uris) { [redirect_uri] }
|
||||
let(:website) { nil }
|
||||
|
||||
let(:params) do
|
||||
|
@ -26,13 +27,63 @@ RSpec.describe 'Apps' do
|
|||
it 'creates an OAuth app', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
app = Doorkeeper::Application.find_by(name: client_name)
|
||||
|
||||
expect(app).to be_present
|
||||
expect(app.scopes.to_s).to eq scopes
|
||||
expect(app.redirect_uris).to eq redirect_uris
|
||||
|
||||
expect(body_as_json).to match(
|
||||
a_hash_including(
|
||||
id: app.id.to_s,
|
||||
client_id: app.uid,
|
||||
client_secret: app.secret,
|
||||
name: client_name,
|
||||
website: website,
|
||||
scopes: ['read', 'write'],
|
||||
redirect_uris: redirect_uris,
|
||||
# Deprecated properties as of 4.3:
|
||||
redirect_uri: redirect_uri,
|
||||
vapid_key: Rails.configuration.x.vapid_public_key
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without scopes being supplied' do
|
||||
let(:scopes) { nil }
|
||||
|
||||
it 'creates an OAuth App with the default scope' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(Doorkeeper::Application.find_by(name: client_name)).to be_present
|
||||
|
||||
body = body_as_json
|
||||
|
||||
expect(body[:client_id]).to be_present
|
||||
expect(body[:client_secret]).to be_present
|
||||
expect(body[:scopes]).to eq Doorkeeper.config.default_scopes.to_a
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: This is a bug: https://github.com/mastodon/mastodon/issues/30152
|
||||
context 'with scopes as an array' do
|
||||
let(:scopes) { %w(read write follow) }
|
||||
|
||||
it 'creates an OAuth App with the default scope' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
app = Doorkeeper::Application.find_by(name: client_name)
|
||||
|
||||
expect(app).to be_present
|
||||
expect(app.scopes.to_s).to eq 'read'
|
||||
|
||||
body = body_as_json
|
||||
|
||||
expect(body[:scopes]).to eq ['read']
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -77,8 +128,8 @@ RSpec.describe 'Apps' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a too-long redirect_uris' do
|
||||
let(:redirect_uris) { "https://foo.bar/#{'hoge' * 2_000}" }
|
||||
context 'with a too-long redirect_uri' do
|
||||
let(:redirect_uris) { "https://app.example/#{'hoge' * 2_000}" }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
@ -87,8 +138,80 @@ RSpec.describe 'Apps' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'without required params' do
|
||||
let(:client_name) { '' }
|
||||
# NOTE: This spec currently tests the same as the "with a too-long redirect_uri test case"
|
||||
context 'with too many redirect_uris' do
|
||||
let(:redirect_uris) { (0...500).map { |i| "https://app.example/#{i}/callback" } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple redirect_uris as a string' do
|
||||
let(:redirect_uris) { "https://redirect1.example/\napp://redirect2.example/" }
|
||||
|
||||
it 'creates an OAuth application with multiple redirect URIs' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
app = Doorkeeper::Application.find_by(name: client_name)
|
||||
|
||||
expect(app).to be_present
|
||||
expect(app.redirect_uri).to eq redirect_uris
|
||||
expect(app.redirect_uris).to eq redirect_uris.split
|
||||
|
||||
body = body_as_json
|
||||
|
||||
expect(body[:redirect_uri]).to eq redirect_uris
|
||||
expect(body[:redirect_uris]).to eq redirect_uris.split
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple redirect_uris as an array' do
|
||||
let(:redirect_uris) { ['https://redirect1.example/', 'app://redirect2.example/'] }
|
||||
|
||||
it 'creates an OAuth application with multiple redirect URIs' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
app = Doorkeeper::Application.find_by(name: client_name)
|
||||
|
||||
expect(app).to be_present
|
||||
expect(app.redirect_uri).to eq redirect_uris.join "\n"
|
||||
expect(app.redirect_uris).to eq redirect_uris
|
||||
|
||||
body = body_as_json
|
||||
|
||||
expect(body[:redirect_uri]).to eq redirect_uris.join "\n"
|
||||
expect(body[:redirect_uris]).to eq redirect_uris
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an empty redirect_uris array' do
|
||||
let(:redirect_uris) { [] }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with just a newline as the redirect_uris string' do
|
||||
let(:redirect_uris) { "\n" }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an empty redirect_uris string' do
|
||||
let(:redirect_uris) { '' }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
|
@ -97,5 +220,30 @@ RSpec.describe 'Apps' do
|
|||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a required param' do
|
||||
let(:client_name) { '' }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a website' do
|
||||
let(:website) { 'https://app.example/' }
|
||||
|
||||
it 'creates an OAuth application with the website specified' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
app = Doorkeeper::Application.find_by(name: client_name)
|
||||
|
||||
expect(app).to be_present
|
||||
expect(app.website).to eq website
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue