Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version up v4.2.5 #1391

Merged
merged 27 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d7875ad
Fix call to inefficient `delete_matched` cache method in domain block…
ClearlyClaire Dec 19, 2023
279be07
Fix `LinkCrawlWorker` error when encountering empty OEmbed response (…
ClearlyClaire Dec 12, 2023
7abc618
Fix empty column explainer getting cropped under certain conditions (…
ClearlyClaire Dec 12, 2023
a12b755
Fix N+1s because of association preloaders not actually getting calle…
ClearlyClaire Dec 13, 2023
0a01bc0
Fix Undo Announce activity is not sent, when not followed by the rebl…
MitarashiDango Jan 12, 2024
e8c5754
Fix line wrapping of language selection button with long locale codes…
gunchleoc Sep 25, 2023
dfc8fcc
Fix width of large text icon buttons (#27127)
ClearlyClaire Sep 25, 2023
8f2dac0
Fix missing background behind dismissable banner in web UI (#27479)
Gargron Oct 20, 2023
3ecc991
Fix "Hide these posts from home" list setting not refreshing when swi…
brianholley Nov 14, 2023
4d96d71
Fix unsupported time zone or locale preventing sign-up (#28035)
Gargron Nov 22, 2023
c609b72
Fix error when processing link preview with an array as `inLanguage` …
ClearlyClaire Dec 7, 2023
01caa18
Fix streaming API redirection ignoring the port of `streaming_api_bas…
ClearlyClaire Jan 2, 2024
c0a9db3
Fix potential redirection loop of streaming endpoint (#28665)
ClearlyClaire Jan 10, 2024
1998c56
Convert signature verification specs to request specs (#28443)
ClearlyClaire Dec 22, 2023
3837ec2
Fix Mastodon not correctly processing HTTP Signatures with query stri…
ClearlyClaire Jan 3, 2024
499bc71
Ignore RecordNotUnique errors in LinkCrawlWorker (#28748)
tribela Jan 16, 2024
2dbf176
Retry 401 errors on replies fetching (#28788)
ShadowJonathan Jan 19, 2024
6fe2a47
Add rate-limit of TOTP authentication attempts at controller level (#…
ClearlyClaire Jan 19, 2024
b377f82
Fix processing of compacted single-item JSON-LD collections (#28816)
ClearlyClaire Jan 19, 2024
779237f
Fix error when processing remote files with unusually long names (#28…
ClearlyClaire Jan 19, 2024
c5c4648
Update dependency puma to v6.4.2
ClearlyClaire Jan 23, 2024
7a22999
Bump ruby version to 3.2.3
ClearlyClaire Jan 23, 2024
4eb98ef
Ignore the devise-two-factor advisory as we have rate limits in place…
ClearlyClaire Jan 15, 2024
1ab050e
Change PostgreSQL version check to check for PostgreSQL 10+
ClearlyClaire Jan 24, 2024
4633bb8
Bump version to v4.2.4
ClearlyClaire Jan 23, 2024
a6641f8
Merge pull request from GHSA-3fjr-858r-92rw
ClearlyClaire Feb 1, 2024
b768df3
Merge branch 'main' into versionup-v4.2.5
Feb 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .bundler-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
ignore:
# devise-two-factor advisory about brute-forcing TOTP
# We have rate-limits on authentication endpoints in place (including second
# factor verification) since Mastodon v3.2.0
- CVE-2024-0227
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.2
3.2.3
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

All notable changes to this project will be documented in this file.

## [4.2.5] - 2024-02-01

### Security

- Fix insufficient origin validation (CVE-2024-23832, [GHSA-3fjr-858r-92rw](https://github.com/mastodon/mastodon/security/advisories/GHSA-3fjr-858r-92rw))

## [4.2.4] - 2024-01-24

### Fixed

- Fix error when processing remote files with unusually long names ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28823))
- Fix processing of compacted single-item JSON-LD collections ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28816))
- Retry 401 errors on replies fetching ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28788))
- Fix `RecordNotUnique` errors in LinkCrawlWorker ([tribela](https://github.com/mastodon/mastodon/pull/28748))
- Fix Mastodon not correctly processing HTTP Signatures with query strings ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28443), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28476))
- Fix potential redirection loop of streaming endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28665))
- Fix streaming API redirection ignoring the port of `streaming_api_base_url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28558))
- Fix error when processing link preview with an array as `inLanguage` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28252))
- Fix unsupported time zone or locale preventing sign-up ([Gargron](https://github.com/mastodon/mastodon/pull/28035))
- Fix "Hide these posts from home" list setting not refreshing when switching lists ([brianholley](https://github.com/mastodon/mastodon/pull/27763))
- Fix missing background behind dismissable banner in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/27479))
- Fix line wrapping of language selection button with long locale codes ([gunchleoc](https://github.com/mastodon/mastodon/pull/27100), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27127))
- Fix `Undo Announce` activity not being sent to non-follower authors ([MitarashiDango](https://github.com/mastodon/mastodon/pull/18482))
- Fix N+1s because of association preloaders not actually getting called ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28339))
- Fix empty column explainer getting cropped under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28337))
- Fix `LinkCrawlWorker` error when encountering empty OEmbed response ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28268))
- Fix call to inefficient `delete_matched` cache method in domain blocks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28367))

### Security

- Add rate-limit of TOTP authentication attempts at controller level ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28801))

## [4.2.3] - 2023-12-05

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
ARG NODE_VERSION="20.6-bookworm-slim"

FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.3-slim as ruby
FROM node:${NODE_VERSION} as build

COPY --link --from=ruby /opt/ruby /opt/ruby
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ GEM
net-smtp (0.3.3)
net-protocol
net-ssh (7.1.0)
nio4r (2.5.9)
nio4r (2.7.0)
nokogiri (1.15.4)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
Expand Down Expand Up @@ -534,7 +534,7 @@ GEM
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0)
public_suffix (5.0.3)
puma (6.3.1)
puma (6.4.2)
nio4r (~> 2.0)
pundit (2.3.0)
activesupport (>= 3.0.0)
Expand Down
4 changes: 1 addition & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,4 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
| ------- | ---------------- |
| 4.2.x | Yes |
| 4.1.x | Yes |
| 4.0.x | No |
| 3.5.x | Until 2023-12-31 |
| < 3.5 | No |
| < 4.1 | No |
2 changes: 1 addition & 1 deletion app/controllers/api/v1/accounts/notes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ def set_account
end

def relationships_presenter
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
AccountRelationshipsPresenter.new([@account], current_user.account_id)
end
end
2 changes: 1 addition & 1 deletion app/controllers/api/v1/accounts/pins_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ def set_account
end

def relationships_presenter
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
AccountRelationshipsPresenter.new([@account], current_user.account_id)
end
end
5 changes: 2 additions & 3 deletions app/controllers/api/v1/accounts/relationships_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user!

def index
accounts = Account.without_suspended.where(id: account_ids).select('id')
@accounts = Account.without_suspended.where(id: account_ids).select(:id, :domain).to_a
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
render json: @accounts.index_by(&:id).values_at(*account_ids).compact, each_serializer: REST::RelationshipSerializer, relationships: relationships
end

private
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def check_account_confirmation
end

def relationships(**options)
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options)
AccountRelationshipsPresenter.new([@account], current_user.account_id, **options)
end

def account_params
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/api/v1/follow_requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ def reject
private

def account
Account.find(params[:id])
@account ||= Account.find(params[:id])
end

def relationships(**options)
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, **options)
AccountRelationshipsPresenter.new([account], current_user.account_id, **options)
end

def load_accounts
Expand Down
11 changes: 9 additions & 2 deletions app/controllers/api/v1/streaming_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class Api::V1::StreamingController < Api::BaseController
def index
if Rails.configuration.x.streaming_api_base_url == request.host
if same_host?
not_found
else
redirect_to streaming_api_url, status: 301, allow_other_host: true
Expand All @@ -11,9 +11,16 @@ def index

private

def same_host?
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
request.host == base_url.host && request.port == (base_url.port || 80)
end

def streaming_api_url
Addressable::URI.parse(request.url).tap do |uri|
uri.host = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url).host
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
uri.host = base_url.host
uri.port = base_url.port
end.to_s
end
end
22 changes: 22 additions & 0 deletions app/controllers/auth/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# frozen_string_literal: true

class Auth::SessionsController < Devise::SessionsController
include Redisable

MAX_2FA_ATTEMPTS_PER_HOUR = 10

layout 'auth'

skip_before_action :require_no_authentication, only: [:create]
Expand Down Expand Up @@ -134,9 +138,23 @@ def clear_attempt_from_session
session.delete(:attempt_user_updated_at)
end

def clear_2fa_attempt_from_user(user)
redis.del(second_factor_attempts_key(user))
end

def check_second_factor_rate_limits(user)
attempts, = redis.multi do |multi|
multi.incr(second_factor_attempts_key(user))
multi.expire(second_factor_attempts_key(user), 1.hour)
end

attempts >= MAX_2FA_ATTEMPTS_PER_HOUR
end

def on_authentication_success(user, security_measure)
@on_authentication_success_called = true

clear_2fa_attempt_from_user(user)
clear_attempt_from_session

user.update_sign_in!(new_sign_in: true)
Expand Down Expand Up @@ -168,4 +186,8 @@ def on_authentication_failure(user, security_measure, failure_reason)
user_agent: request.user_agent
)
end

def second_factor_attempts_key(user)
"2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}"
end
end
24 changes: 20 additions & 4 deletions app/controllers/concerns/signature_verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,23 @@ def signed_request_actor
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?

signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string
compare_signed_string = build_signed_string(include_query_string: true)

return actor unless verify_signature(actor, signature, compare_signed_string).nil?

# Compatibility quirk with older Mastodon versions
compare_signed_string = build_signed_string(include_query_string: false)
return actor unless verify_signature(actor, signature, compare_signed_string).nil?

actor = stoplight_wrap_request { actor_refresh_key!(actor) }

raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?

compare_signed_string = build_signed_string(include_query_string: true)
return actor unless verify_signature(actor, signature, compare_signed_string).nil?

# Compatibility quirk with older Mastodon versions
compare_signed_string = build_signed_string(include_query_string: false)
return actor unless verify_signature(actor, signature, compare_signed_string).nil?

fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
Expand Down Expand Up @@ -180,11 +189,18 @@ def verify_signature(actor, signature, compare_signed_string)
nil
end

def build_signed_string
def build_signed_string(include_query_string: true)
signed_headers.map do |signed_header|
case signed_header
when Request::REQUEST_TARGET
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
if include_query_string
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
else
# Current versions of Mastodon incorrectly omit the query string from the (request-target) pseudo-header.
# Therefore, temporarily support such incorrect signatures for compatibility.
# TODO: remove eventually some time after release of the fixed version
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
end
when '(created)'
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
Expand Down Expand Up @@ -250,7 +266,7 @@ def actor_from_key_id(key_id)
stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) }
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) }
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) }
account
end
rescue Mastodon::PrivateNetworkAddressError => e
Expand Down
5 changes: 5 additions & 0 deletions app/controllers/concerns/two_factor_authentication_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ def authenticate_with_two_factor_via_webauthn(user)
end

def authenticate_with_two_factor_via_otp(user)
if check_second_factor_rate_limits(user)
flash.now[:alert] = I18n.t('users.rate_limited')
return prompt_for_two_factor(user)
end

if valid_otp_attempt?(user)
on_authentication_success(user, :otp)
else
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/relationships_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def set_accounts
end

def set_relationships
@relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id)
@relationships = AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
end

def form_account_batch_params
Expand Down
14 changes: 7 additions & 7 deletions app/helpers/jsonld_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,23 +155,23 @@ def safe_for_forwarding?(original, compacted)
end
end

def fetch_resource(uri, id, on_behalf_of = nil)
unless id
def fetch_resource(uri, id_is_known, on_behalf_of = nil, request_options: {})
unless id_is_known
json = fetch_resource_without_id_validation(uri, on_behalf_of)

return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])

uri = json['id']
end

json = fetch_resource_without_id_validation(uri, on_behalf_of)
json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
json.present? && json['id'] == uri ? json : nil
end

def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false)
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
on_behalf_of ||= Account.representative

build_request(uri, on_behalf_of).perform do |response|
build_request(uri, on_behalf_of, options: request_options).perform do |response|
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error

body_to_json(response.body_with_limit) if response.code == 200
Expand Down Expand Up @@ -204,8 +204,8 @@ def response_error_unsalvageable?(response)
response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
end

def build_request(uri, on_behalf_of = nil)
Request.new(:get, uri).tap do |request|
def build_request(uri, on_behalf_of = nil, options: {})
Request.new(:get, uri, **options).tap do |request|
request.on_behalf_of(on_behalf_of) if on_behalf_of
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PureComponent } from 'react';
const iconStyle = {
height: null,
lineHeight: '27px',
width: `${18 * 1.28571429}px`,
minWidth: `${18 * 1.28571429}px`,
};

export default class TextIconButton extends PureComponent {
Expand Down
32 changes: 14 additions & 18 deletions app/javascript/mastodon/features/explore/statuses.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,20 @@ class Statuses extends PureComponent {
const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />;

return (
<>
<DismissableBanner id='explore/statuses'>
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' />
</DismissableBanner>

<StatusList
trackScroll
timelineId='explore'
statusIds={statusIds}
scrollKey='explore-statuses'
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
withCounters
/>
</>
<StatusList
trackScroll
prepend={<DismissableBanner id='explore/statuses'><FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' /></DismissableBanner>}
alwaysPrepend
timelineId='explore'
statusIds={statusIds}
scrollKey='explore-statuses'
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
withCounters
/>
);
}

Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/features/list_timeline/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class ListTimeline extends PureComponent {
</div>

<div className='setting-toggle'>
<Toggle id={`list-${id}-exclusive`} defaultChecked={isExclusive} onChange={this.onExclusiveToggle} />
<Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
</label>
Expand Down
Loading
Loading