diff --git a/.github/workflows/build-security.yml b/.github/workflows/build-security.yml index b03b787d5..1e2455d3d 100644 --- a/.github/workflows/build-security.yml +++ b/.github/workflows/build-security.yml @@ -38,7 +38,7 @@ jobs: tags: | type=raw,value=edge type=raw,value=nightly - type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }} + type=raw,value=${{ needs.compute-suffix.outputs.prerelease }} secrets: inherit build-image-streaming: @@ -60,5 +60,5 @@ jobs: tags: | type=raw,value=edge type=raw,value=nightly - type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }} + type=raw,value=${{ needs.compute-suffix.outputs.prerelease }} secrets: inherit diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 4275f5942..7fd259ae0 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -224,7 +224,7 @@ jobs: if: failure() with: name: e2e-screenshots - path: tmp/screenshots/ + path: tmp/capybara/ test-search: name: Elastic Search integration testing @@ -328,4 +328,4 @@ jobs: if: failure() with: name: test-search-screenshots - path: tmp/screenshots/ + path: tmp/capybara/ diff --git a/.rubocop.yml b/.rubocop.yml index 330c40de1..a8310489e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -134,6 +134,11 @@ Rails/UnusedIgnoredColumns: Rails/NegateInclude: Enabled: false +# Reason: Enforce default limit, but allow some elements to span lines +# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecexamplelength +RSpec/ExampleLength: + CountAsOne: ['array', 'heredoc', 'method_call'] + # Reason: Deprecated cop, will be removed in 3.0, replaced by SpecFilePathFormat # https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath RSpec/FilePath: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 09e9fd73d..11ac57083 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -36,7 +36,7 @@ Metrics/PerceivedComplexity: # Configuration parameters: CountAsOne. RSpec/ExampleLength: - Max: 22 + Max: 20 # Override default of 5 RSpec/MultipleExpectations: Max: 7 diff --git a/Gemfile.lock b/Gemfile.lock index 57ad96437..152436b47 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -146,7 +146,7 @@ GEM blurhash (0.1.7) bootsnap (1.18.3) msgpack (~> 1.2) - brakeman (6.1.1) + brakeman (6.1.2) racc browser (5.3.1) brpoplpush-redis_script (0.1.3) @@ -182,7 +182,8 @@ GEM cose (1.3.0) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) - crack (0.4.5) + crack (0.4.6) + bigdecimal rexml crass (1.0.6) css_parser (1.14.0) @@ -314,7 +315,7 @@ GEM rainbow rubocop (>= 1.0) sysexits (~> 1.1) - hashdiff (1.0.1) + hashdiff (1.1.0) hashie (5.0.0) hcaptcha (7.1.0) json @@ -350,7 +351,7 @@ GEM terminal-table (>= 1.5.1) idn-ruby (0.1.5) io-console (0.7.2) - irb (1.11.1) + irb (1.11.2) rdoc reline (>= 0.4.2) jmespath (1.6.2) @@ -505,7 +506,7 @@ GEM pastel (0.8.0) tty-color (~> 0.5) pg (1.5.4) - pghero (3.4.0) + pghero (3.4.1) activerecord (>= 6) posix-spawn (0.3.15) premailer (1.21.0) @@ -792,7 +793,7 @@ GEM webfinger (1.2.0) activesupport httpclient (>= 2.4) - webmock (3.19.1) + webmock (3.20.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index ba85e0a72..e8b0f47cd 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -62,11 +62,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' || signed_request_account.nil? # Re-using the syntax for signature parameters - tree = SignatureParamsParser.new.parse(raw_params) - params = SignatureParamsTransformer.new.apply(tree) + params = SignatureParser.parse(raw_params) ActivityPub::PrepareFollowersSynchronizationService.new.call(signed_request_account, params) - rescue Parslet::ParseFailed + rescue SignatureParser::ParsingError Rails.logger.warn 'Error parsing Collection-Synchronization header' end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 064e7632a..01c371876 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -71,13 +71,9 @@ class Api::V1::StatusesController < Api::BaseController with_rate_limit: true ) - render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer + render json: @status, serializer: serializer_for_status rescue PostStatusService::UnexpectedMentionsError => e - unexpected_accounts = ActiveModel::Serializer::CollectionSerializer.new( - e.accounts, - serializer: REST::AccountSerializer - ) - render json: { error: e.message, unexpected_accounts: unexpected_accounts }, status: 422 + render json: unexpected_accounts_error_json(e), status: 422 end def update @@ -155,6 +151,21 @@ class Api::V1::StatusesController < Api::BaseController ) end + def serializer_for_status + @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer + end + + def unexpected_accounts_error_json(error) + { + error: error.message, + unexpected_accounts: serialized_accounts(error.accounts), + } + end + + def serialized_accounts(accounts) + ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer) + end + def pagination_params(core_params) params.slice(:limit).permit(:limit).merge(core_params) end diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 962b78de6..6ed7b2baa 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -183,7 +183,9 @@ class Auth::SessionsController < Devise::SessionsController ) # Only send a notification email every hour at most - return if redis.set("2fa_failure_notification:#{user.id}", '1', ex: 1.hour, get: true).present? + return if redis.get("2fa_failure_notification:#{user.id}").present? + + redis.set("2fa_failure_notification:#{user.id}", '1', ex: 1.hour) UserMailer.failed_2fa(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later! end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 92f1eb5a1..315586627 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -12,39 +12,6 @@ module SignatureVerification class SignatureVerificationError < StandardError; end - class SignatureParamsParser < Parslet::Parser - rule(:token) { match("[0-9a-zA-Z!#$%&'*+.^_`|~-]").repeat(1).as(:token) } - rule(:quoted_string) { str('"') >> (qdtext | quoted_pair).repeat.as(:quoted_string) >> str('"') } - # qdtext and quoted_pair are not exactly according to spec but meh - rule(:qdtext) { match('[^\\\\"]') } - rule(:quoted_pair) { str('\\') >> any } - rule(:bws) { match('\s').repeat } - rule(:param) { (token.as(:key) >> bws >> str('=') >> bws >> (token | quoted_string).as(:value)).as(:param) } - rule(:comma) { bws >> str(',') >> bws } - # Old versions of node-http-signature add an incorrect "Signature " prefix to the header - rule(:buggy_prefix) { str('Signature ') } - rule(:params) { buggy_prefix.maybe >> (param >> (comma >> param).repeat).as(:params) } - root(:params) - end - - class SignatureParamsTransformer < Parslet::Transform - 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 - [key, val] - end - - rule(quoted_string: simple(:string)) do - string.to_s - end - - rule(token: simple(:string)) do - string.to_s - end - end - def require_account_signature! render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account end @@ -135,12 +102,8 @@ module SignatureVerification end def signature_params - @signature_params ||= begin - raw_signature = request.headers['Signature'] - tree = SignatureParamsParser.new.parse(raw_signature) - SignatureParamsTransformer.new.apply(tree) - end - rescue Parslet::ParseFailed + @signature_params ||= SignatureParser.parse(request.headers['Signature']) + rescue SignatureParser::ParsingError raise SignatureVerificationError, 'Error parsing signature parameters' end diff --git a/app/javascript/mastodon/features/compose/components/upload_button.jsx b/app/javascript/mastodon/features/compose/components/upload_button.jsx index 3866f239d..50c9ad632 100644 --- a/app/javascript/mastodon/features/compose/components/upload_button.jsx +++ b/app/javascript/mastodon/features/compose/components/upload_button.jsx @@ -65,6 +65,7 @@ class UploadButton extends ImmutablePureComponent { key={resetFileKey} ref={this.setRef} type='file' + name='file-upload-input' multiple accept={acceptContentTypes.toArray().join(',')} onChange={this.handleChange} diff --git a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx index ac414d04d..f76526e04 100644 --- a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx +++ b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx @@ -59,7 +59,7 @@ Source.propTypes = { id: PropTypes.oneOf(['friends_of_friends', 'similar_to_recently_followed', 'featured', 'most_followed', 'most_interactions']), }; -const Card = ({ id, source }) => { +const Card = ({ id, sources }) => { const intl = useIntl(); const account = useSelector(state => state.getIn(['accounts', id])); const relationship = useSelector(state => state.getIn(['relationships', id])); @@ -89,7 +89,7 @@ const Card = ({ id, source }) => {
- {firstVerifiedField ? : } + {firstVerifiedField ? : }