From b40dfc124b1fc72a675edff20e79301e92aa789f Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 6 May 2019 05:33:56 +0200 Subject: [PATCH 01/19] Add description on hover in media gallery (#10713) --- .../mastodon/features/account_gallery/components/media_item.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 5643e6449..f44e20939 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.js +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js @@ -88,6 +88,7 @@ export default class MediaItem extends ImmutablePureComponent { const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`; const height = width; const status = attachment.get('status'); + const title = status.get('spoiler_text') || attachment.get('description'); let thumbnail = ''; @@ -133,7 +134,7 @@ export default class MediaItem extends ImmutablePureComponent { return (
- + {visible && thumbnail} From 9679ec4fcba08aa5db6e8365230bfccfee2baab3 Mon Sep 17 00:00:00 2001 From: nzws Date: Wed, 8 May 2019 06:53:58 +0900 Subject: [PATCH 02/19] Fix some colors of high contrast theme (#10711) * Fix "nothing here" text color of high contrast * Fix counter border color of high contrast --- app/javascript/styles/contrast/diff.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss index 8429103b8..f78e60597 100644 --- a/app/javascript/styles/contrast/diff.scss +++ b/app/javascript/styles/contrast/diff.scss @@ -67,3 +67,11 @@ text-decoration: none; } } + +.nothing-here { + color: $darker-text-color; +} + +.public-layout .public-account-header__tabs__tabs .counter.active::after { + border-bottom: 4px solid $ui-highlight-color; +} From 7a6464bea090d874266441b46dd8570797f6d3b5 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 9 May 2019 01:01:33 +0900 Subject: [PATCH 03/19] Bring back crossed eye icon on gallery (#10715) --- .../features/account_gallery/components/media_item.js | 11 +++++++++++ app/javascript/styles/mastodon/components.scss | 8 ++++++++ 2 files changed, 19 insertions(+) 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 f44e20939..2609b96ff 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.js +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import Icon from 'mastodon/components/icon'; import { autoPlayGif, displayMedia } from 'mastodon/initial_state'; import classNames from 'classnames'; import { decode } from 'blurhash'; @@ -91,6 +92,7 @@ export default class MediaItem extends ImmutablePureComponent { const title = status.get('spoiler_text') || attachment.get('description'); let thumbnail = ''; + let icon; if (attachment.get('type') === 'unknown') { // Skip @@ -132,11 +134,20 @@ export default class MediaItem extends ImmutablePureComponent { ); } + if (!visible) { + icon = ( + + + + ); + } + return (
{visible && thumbnail} + {!visible && icon}
); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index cf8fa9392..0da3ed909 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -4829,6 +4829,14 @@ a.status-card.compact:hover { border-radius: 4px; overflow: hidden; margin: 2px; + + &__icons { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 24px; + } } .notification__filter-bar, From c407a4edf8e38fa5cb38abd6bfa526376706e084 Mon Sep 17 00:00:00 2001 From: Maciek Baron Date: Thu, 9 May 2019 21:03:32 +0100 Subject: [PATCH 04/19] Improve poll link accessibility (#10720) * Add distinction between hover and active/focus states * Resolves #10198 --- app/javascript/styles/mastodon/polls.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index d8bc5473a..37c454a78 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -114,11 +114,14 @@ text-decoration: underline; font-size: inherit; - &:hover, - &:focus, - &:active { + &:hover { text-decoration: none; } + + &:active, + &:focus { + background-color: rgba($dark-text-color, .1); + } } .button { From 47e0928c5b7bb87627b0fe768ff89ded787eaffe Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 10 May 2019 17:59:57 +0200 Subject: [PATCH 05/19] Change icon and label depending on whether media is marked as sensitive (#10748) * Change icon and label depending on whether media is marked as sensitive * WiP use a checkbox --- .../containers/sensitive_button_container.js | 17 +++++++--- .../styles/mastodon/components.scss | 31 +++++++++++++++++-- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js index 50612b086..7073f76c2 100644 --- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js +++ b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import { changeComposeSensitivity } from 'mastodon/actions/compose'; import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; -import Icon from 'mastodon/components/icon'; const messages = defineMessages({ marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' }, @@ -38,9 +37,19 @@ class SensitiveButton extends React.PureComponent { return (
- +
); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 0da3ed909..a93284612 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -268,9 +268,34 @@ padding: 10px; padding-top: 0; - .icon-button { - font-size: 14px; - font-weight: 500; + font-size: 14px; + font-weight: 500; + + &.active { + color: $highlight-text-color; + } + + input[type=checkbox] { + display: none; + } + + .checkbox { + display: inline-block; + position: relative; + border: 1px solid $ui-primary-color; + box-sizing: border-box; + width: 18px; + height: 18px; + flex: 0 0 auto; + margin-right: 10px; + top: -1px; + border-radius: 4px; + vertical-align: middle; + + &.active { + border-color: $highlight-text-color; + background: $highlight-text-color; + } } } From 91e25a20ce55a13d533e3f50cf2ad5b2a40a791c Mon Sep 17 00:00:00 2001 From: nzws Date: Sun, 12 May 2019 12:15:42 +0900 Subject: [PATCH 06/19] Fix some colors in light theme (#10754) * Fix typo in light theme * Fix background color of empty column --- app/javascript/styles/mastodon-light/diff.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index de03cf1a6..48236a286 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -162,7 +162,7 @@ .actions-modal ul li:not(:empty) a:focus button, .actions-modal ul li:not(:empty) a:hover, .actions-modal ul li:not(:empty) a:hover button, -.admin-wrapper .sidebar ul ul a.selected, +.admin-wrapper .sidebar ul li a.selected, .simple_form .block-button, .simple_form .button, .simple_form button { @@ -230,6 +230,7 @@ .empty-column-indicator, .error-column { color: $primary-text-color; + background: $white; } // Change the default colors used on some parts of the profile pages From 9e95af3391837789a1039c4ea6181588817d3939 Mon Sep 17 00:00:00 2001 From: Neil Moore Date: Wed, 15 May 2019 00:53:23 -0400 Subject: [PATCH 07/19] Adds click-able div that expands status (#10733) (#10766) The clickable div is positioned under the account avatar and covers all empty space below it to the end of the status. --- app/javascript/mastodon/components/status.js | 1 + app/javascript/styles/mastodon/components.scss | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 95ca4a548..42535ea68 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -356,6 +356,7 @@ class Status extends ImmutablePureComponent { {prepend}
+
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index a93284612..cdf3b3b13 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1411,6 +1411,15 @@ a.account__display-name { width: 48px; } +.status__expand { + width: 68px; + position: absolute; + left: 0; + top: 0; + height: 100%; + cursor: pointer; +} + .muted { .status__content p, .status__content a { From ee17d81b8a4e02b5c72e39922f15634b4352c817 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 15 May 2019 06:54:06 +0200 Subject: [PATCH 08/19] Minor performance improvements and cleanup in formatter (#10765) --- app/lib/formatter.rb | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 59dfc9004..8a1aad41a 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -187,7 +187,7 @@ class Formatter end def rewrite(text, entities) - chars = text.to_s.to_char_a + text = text.to_s # Sort by start index entities = entities.sort_by do |entity| @@ -199,12 +199,12 @@ class Formatter last_index = entities.reduce(0) do |index, entity| indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices] - result << encode(chars[index...indices.first].join) + result << encode(text[index...indices.first]) result << yield(entity) indices.last end - result << encode(chars[last_index..-1].join) + result << encode(text[last_index..-1]) result.flatten.join end @@ -231,23 +231,14 @@ class Formatter # Note: I couldn't obtain list_slug with @user/list-name format # for mention so this requires additional check special = Extractor.extract_urls_with_indices(escaped, options).map do |extract| - # exactly one of :url, :hashtag, :screen_name, :cashtag keys is present - key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first - new_indices = [ old_to_new_index.find_index(extract[:indices].first), old_to_new_index.find_index(extract[:indices].last), ] - has_prefix_char = [:hashtag, :screen_name, :cashtag].include?(key) - value_indices = [ - new_indices.first + (has_prefix_char ? 1 : 0), # account for #, @ or $ - new_indices.last - 1, - ] - next extract.merge( - :indices => new_indices, - key => text[value_indices.first..value_indices.last] + indices: new_indices, + url: text[new_indices.first..new_indices.last - 1] ) end From 3c27687a6e1f283ff4f4300b2b07fbc10ba3bba7 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 18 May 2019 00:28:51 +0200 Subject: [PATCH 09/19] Prevent from publicly boosting one's own private toots (#10775) --- app/services/reblog_service.rb | 4 +++- spec/services/reblog_service_spec.rb | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index ff48d9c75..1710640c8 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -18,7 +18,9 @@ class ReblogService < BaseService return reblog unless reblog.nil? - reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: options[:visibility] || account.user&.setting_default_privacy) + visibility = options[:visibility] || account.user&.setting_default_privacy + visibility = reblogged_status.visibility if reblogged_status.hidden? + reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility) DistributionWorker.perform_async(reblog.id) Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id) diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb index 9e66c6643..9d84c41d5 100644 --- a/spec/services/reblog_service_spec.rb +++ b/spec/services/reblog_service_spec.rb @@ -4,10 +4,9 @@ RSpec.describe ReblogService, type: :service do let(:alice) { Fabricate(:account, username: 'alice') } context 'creates a reblog with appropriate visibility' do - let(:bob) { Fabricate(:account, username: 'bob') } let(:visibility) { :public } let(:reblog_visibility) { :public } - let(:status) { Fabricate(:status, account: bob, visibility: visibility) } + let(:status) { Fabricate(:status, account: alice, visibility: visibility) } subject { ReblogService.new } @@ -22,6 +21,15 @@ RSpec.describe ReblogService, type: :service do expect(status.reblogs.first.visibility).to eq 'private' end end + + describe 'public reblogs of private toots should remain private' do + let(:visibility) { :private } + let(:reblog_visibility) { :public } + + it 'reblogs privately' do + expect(status.reblogs.first.visibility).to eq 'private' + end + end end context 'OStatus' do From d587a943a549d7ba5bb8433401390ac665bbdf17 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sat, 18 May 2019 13:57:45 -0500 Subject: [PATCH 10/19] add og:image:alt for media attachments in embeds (#10779) --- app/views/stream_entries/_og_image.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/stream_entries/_og_image.html.haml b/app/views/stream_entries/_og_image.html.haml index e1b977da3..67f9274b6 100644 --- a/app/views/stream_entries/_og_image.html.haml +++ b/app/views/stream_entries/_og_image.html.haml @@ -7,6 +7,8 @@ - unless media.file.meta.nil? = opengraph 'og:image:width', media.file.meta.dig('original', 'width') = opengraph 'og:image:height', media.file.meta.dig('original', 'height') + - if media.description.present? + = opengraph 'og:image:alt', media.description - elsif media.video? || media.gifv? - player_card = true = opengraph 'og:image', full_asset_url(media.file.url(:small)) From a6815a757886620315ff0906e175c9d5e5fbb3d8 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 19 May 2019 13:49:31 +0200 Subject: [PATCH 11/19] Add post-deployment migration script to delete public-boosts-of-private-toots (#10783) --- ...9130537_remove_boosts_widening_audience.rb | 23 +++++++++++++++++++ db/schema.rb | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 db/post_migrate/20190519130537_remove_boosts_widening_audience.rb diff --git a/db/post_migrate/20190519130537_remove_boosts_widening_audience.rb b/db/post_migrate/20190519130537_remove_boosts_widening_audience.rb new file mode 100644 index 000000000..d2d924239 --- /dev/null +++ b/db/post_migrate/20190519130537_remove_boosts_widening_audience.rb @@ -0,0 +1,23 @@ +class RemoveBoostsWideningAudience < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def up + public_boosts = Status.find_by_sql(<<-SQL) + SELECT boost.id + FROM statuses AS boost + LEFT JOIN statuses AS boosted ON boost.reblog_of_id = boosted.id + WHERE + boost.id > 101746055577600000 + AND (boost.local = TRUE OR boost.uri IS NULL) + AND boost.visibility IN (0, 1) + AND boost.reblog_of_id IS NOT NULL + AND boosted.visibility = 2 + SQL + + RemovalWorker.push_bulk(public_boosts.pluck(:id)) + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 8613539d6..fe66ed2be 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_04_20_025523) do +ActiveRecord::Schema.define(version: 2019_05_19_130537) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From 94439a1da7e585207dacf199c0eb4af2be1fdf7d Mon Sep 17 00:00:00 2001 From: Hinaloe Date: Mon, 20 May 2019 01:41:41 +0900 Subject: [PATCH 12/19] fix `isSubmitting` prop case (#10785) --- .../features/compose/containers/compose_form_container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index f9f1fba36..93b813ebf 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -20,7 +20,7 @@ const mapStateToProps = state => ({ focusDate: state.getIn(['compose', 'focusDate']), caretPosition: state.getIn(['compose', 'caretPosition']), preselectDate: state.getIn(['compose', 'preselectDate']), - is_submitting: state.getIn(['compose', 'is_submitting']), + isSubmitting: state.getIn(['compose', 'is_submitting']), isChangingUpload: state.getIn(['compose', 'is_changing_upload']), isUploading: state.getIn(['compose', 'is_uploading']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), From 9222c26e19ec843d0b1b50b14901983270c9b8b5 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 19 May 2019 21:40:36 +0200 Subject: [PATCH 13/19] =?UTF-8?q?Fix=20=E2=80=9Cinvited=20by=E2=80=9D=20no?= =?UTF-8?q?t=20showing=20up=20for=20invited=20accounts=20in=20admin=20inte?= =?UTF-8?q?rface=20(#10791)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 6 +++++- app/validators/blacklisted_email_validator.rb | 2 +- spec/validators/blacklisted_email_validator_spec.rb | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index bce28aa5f..408e2761d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -114,6 +114,10 @@ class User < ApplicationRecord end def invited? + invite_id.present? + end + + def valid_invitation? invite_id.present? && invite.valid_for_use? end @@ -274,7 +278,7 @@ class User < ApplicationRecord private def set_approved - self.approved = open_registrations? || invited? || external? + self.approved = open_registrations? || valid_invitation? || external? end def open_registrations? diff --git a/app/validators/blacklisted_email_validator.rb b/app/validators/blacklisted_email_validator.rb index a288c20ef..0d01a1c47 100644 --- a/app/validators/blacklisted_email_validator.rb +++ b/app/validators/blacklisted_email_validator.rb @@ -2,7 +2,7 @@ class BlacklistedEmailValidator < ActiveModel::Validator def validate(user) - return if user.invited? + return if user.valid_invitation? @email = user.email diff --git a/spec/validators/blacklisted_email_validator_spec.rb b/spec/validators/blacklisted_email_validator_spec.rb index 84b0107dd..ccc5dc0f4 100644 --- a/spec/validators/blacklisted_email_validator_spec.rb +++ b/spec/validators/blacklisted_email_validator_spec.rb @@ -8,7 +8,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do let(:errors) { double(add: nil) } before do - allow(user).to receive(:invited?) { false } + allow(user).to receive(:valid_invitation?) { false } allow_any_instance_of(described_class).to receive(:blocked_email?) { blocked_email } described_class.new.validate(user) end From 370ec7e7718868a1bd3d645a98b968471e50a349 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 19 May 2019 16:46:49 +0200 Subject: [PATCH 14/19] Bump version to 2.8.3 --- CHANGELOG.md | 22 ++++++++++++++++++++++ lib/mastodon/version.rb | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 222b7411d..9893e2267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ Changelog All notable changes to this project will be documented in this file. +## [2.8.3] - 2019-05-19 +### Added + +- Add `og:image:alt` OpenGraph tag ([BenLubar](https://github.com/tootsuite/mastodon/pull/10779)) +- Add clickable area below avatar in statuses in web UI ([Dar13](https://github.com/tootsuite/mastodon/pull/10766)) +- Add crossed-out eye icon on account gallery in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10715)) +- Add media description tooltip to thumbnails in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10713)) + +### Changed + +- Change "mark as sensitive" button into a checkbox for clarity ([ThibG](https://github.com/tootsuite/mastodon/pull/10748)) + +### Fixed + +- Fix bug allowing users to publicly boost their private statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10775), [ThibG](https://github.com/tootsuite/mastodon/pull/10783)) +- Fix performance in formatter by a little ([ThibG](https://github.com/tootsuite/mastodon/pull/10765)) +- Fix some colors in the light theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10754)) +- Fix some colors of the high contrast theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10711)) +- Fix ambivalent active state of poll refresh button in web UI ([MaciekBaron](https://github.com/tootsuite/mastodon/pull/10720)) +- Fix duplicate posting being possible from web UI ([hinaloe](https://github.com/tootsuite/mastodon/pull/10785)) +- Fix "invited by" not showing up in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10791)) + ## [2.8.2] - 2019-05-05 ### Added diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 91f45e45d..d384ea4d7 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 2 + 3 end def pre From 9a881c70e215e7860ca7c3a653fc43692c793cdd Mon Sep 17 00:00:00 2001 From: ThibG Date: Thu, 23 May 2019 15:00:30 +0200 Subject: [PATCH 15/19] Retry ActivityPub inbox delivery on HTTP 401 and 408 errors (#10812) HTTP 401 responses returned by Mastodon's inbox controller may be temporary if, for instance, the requesting user's actor/key json could not be retrieved in a timely fashion. This changes allow retries instead of dropping the message entirely. Also added HTTP 408 as that error is by nature temporary. --- app/workers/activitypub/delivery_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index f9c385ea3..5e4c391f0 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -51,7 +51,7 @@ class ActivityPub::DeliveryWorker end def response_error_unsalvageable?(response) - (400...500).cover?(response.code) && response.code != 429 + (400...500).cover?(response.code) && ![401, 408, 429].include?(response.code) end def failure_tracker From 39d1d022de00114d481b1ad522aa7441ad2c56eb Mon Sep 17 00:00:00 2001 From: ThibG Date: Thu, 23 May 2019 15:22:39 +0200 Subject: [PATCH 16/19] Move signature verification stoplight to the requests themselves (#10813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move signature verification stoplight to the requests themselves This avoids blocking messages from known keys for 5 minutes when only one fails… * Put the stoplight on the actual client IP, not a potential reverse proxy --- .../concerns/signature_verification.rb | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 91566c4fa..90a57197c 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -43,13 +43,7 @@ module SignatureVerification return end - account_stoplight = Stoplight("source:#{request.ip}") { account_from_key_id(signature_params['keyId']) } - .with_fallback { nil } - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) } - - account = account_stoplight.run + account = account_from_key_id(signature_params['keyId']) if account.nil? @signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}" @@ -62,13 +56,7 @@ module SignatureVerification return account unless verify_signature(account, signature, compare_signed_string).nil? - account_stoplight = Stoplight("source:#{request.ip}") { account.possibly_stale? ? account.refresh! : account_refresh_key(account) } - .with_fallback { nil } - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) } - - account = account_stoplight.run + account = stoplight_wrap_request { account.possibly_stale? ? account.refresh! : account_refresh_key(account) } if account.nil? @signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}" @@ -136,14 +124,23 @@ module SignatureVerification def account_from_key_id(key_id) if key_id.start_with?('acct:') - ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) + stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) } elsif !ActivityPub::TagManager.instance.local_uri?(key_id) account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account) - account ||= ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) + account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) } account end end + def stoplight_wrap_request(&block) + Stoplight("source:#{request.remote_ip}", &block) + .with_fallback { nil } + .with_threshold(1) + .with_cool_off_time(5.minutes.seconds) + .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) } + .run + end + def account_refresh_key(account) return if account.local? || !account.activitypub? ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true) From 130fbf839b656dfd544bc02aeff5cf52a9f4cd7c Mon Sep 17 00:00:00 2001 From: ThibG Date: Thu, 23 May 2019 20:00:39 +0200 Subject: [PATCH 17/19] Fix possible race condition when processing statuses (#10815) --- app/lib/activitypub/activity/create.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 6b16c9986..2bc33c04b 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -267,7 +267,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def conversation_from_uri(uri) return nil if uri.nil? return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri) - Conversation.find_by(uri: uri) || Conversation.create(uri: uri) + begin + Conversation.find_or_create_by!(uri: uri) + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique + retry + end end def visibility_from_audience From aa80292170967662e74ea5196bd2773ce4f77c07 Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 24 May 2019 15:21:42 +0200 Subject: [PATCH 18/19] Improve streaming server security (#10818) * Check OAuth token scopes in the streaming API * Use Sec-WebSocket-Protocol instead of query string to pass WebSocket token Inspired by https://github.com/kubevirt/kubevirt/issues/1242 --- app/javascript/mastodon/stream.js | 6 +--- streaming/index.js | 50 ++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 306a068b7..c4642344f 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -71,11 +71,7 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({ export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) { const params = [ `stream=${stream}` ]; - if (accessToken !== null) { - params.push(`access_token=${accessToken}`); - } - - const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`); + const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken); ws.onopen = connected; ws.onmessage = e => received(JSON.parse(e.data)); diff --git a/streaming/index.js b/streaming/index.js index 2a51a1a0d..4bd53d3c4 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -195,14 +195,14 @@ const startWorker = (workerId) => { next(); }; - const accountFromToken = (token, req, next) => { + const accountFromToken = (token, allowedScopes, req, next) => { pgPool.connect((err, client, done) => { if (err) { next(err); return; } - client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token], (err, result) => { + client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id, users.chosen_languages, oauth_access_tokens.scopes FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token], (err, result) => { done(); if (err) { @@ -218,18 +218,29 @@ const startWorker = (workerId) => { return; } + const scopes = result.rows[0].scopes.split(' '); + + if (allowedScopes.size > 0 && !scopes.some(scope => allowedScopes.includes(scope))) { + err = new Error('Access token does not cover required scopes'); + err.statusCode = 401; + + next(err); + return; + } + req.accountId = result.rows[0].account_id; req.chosenLanguages = result.rows[0].chosen_languages; + req.allowNotifications = scopes.some(scope => ['read', 'read:notifications'].includes(scope)); next(); }); }); }; - const accountFromRequest = (req, next, required = true) => { + const accountFromRequest = (req, next, required = true, allowedScopes = ['read']) => { const authorization = req.headers.authorization; const location = url.parse(req.url, true); - const accessToken = location.query.access_token; + const accessToken = location.query.access_token || req.headers['sec-websocket-protocol']; if (!authorization && !accessToken) { if (required) { @@ -246,7 +257,7 @@ const startWorker = (workerId) => { const token = authorization ? authorization.replace(/^Bearer /, '') : accessToken; - accountFromToken(token, req, next); + accountFromToken(token, allowedScopes, req, next); }; const PUBLIC_STREAMS = [ @@ -261,6 +272,16 @@ const startWorker = (workerId) => { const wsVerifyClient = (info, cb) => { const location = url.parse(info.req.url, true); const authRequired = !PUBLIC_STREAMS.some(stream => stream === location.query.stream); + const allowedScopes = []; + + if (authRequired) { + allowedScopes.push('read'); + if (location.query.stream === 'user:notification') { + allowedScopes.push('read:notifications'); + } else { + allowedScopes.push('read:statuses'); + } + } accountFromRequest(info.req, err => { if (!err) { @@ -269,7 +290,7 @@ const startWorker = (workerId) => { log.error(info.req.requestId, err.toString()); cb(false, 401, 'Unauthorized'); } - }, authRequired); + }, authRequired, allowedScopes); }; const PUBLIC_ENDPOINTS = [ @@ -286,7 +307,18 @@ const startWorker = (workerId) => { } const authRequired = !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path); - accountFromRequest(req, next, authRequired); + const allowedScopes = []; + + if (authRequired) { + allowedScopes.push('read'); + if (req.path === '/api/v1/streaming/user/notification') { + allowedScopes.push('read:notifications'); + } else { + allowedScopes.push('read:statuses'); + } + } + + accountFromRequest(req, next, authRequired, allowedScopes); }; const errorMiddleware = (err, req, res, {}) => { @@ -339,6 +371,10 @@ const startWorker = (workerId) => { return; } + if (event === 'notification' && !req.allowNotifications) { + return; + } + // Only messages that may require filtering are statuses, since notifications // are already personalized and deletes do not matter if (!needsFiltering || event !== 'update') { From 7d92c2c81d564d2648a362c20bc7914cd377525f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 24 May 2019 15:35:32 +0200 Subject: [PATCH 19/19] Bump version to 2.8.4 --- CHANGELOG.md | 11 +++++++++++ lib/mastodon/version.rb | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9893e2267..f183b6f5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ Changelog All notable changes to this project will be documented in this file. +## [2.8.4] - 2019-05-24 +### Fixed + +- Fix delivery not retrying on some inbox errors that should be retriable ([ThibG](https://github.com/tootsuite/mastodon/pull/10812)) +- Fix unnecessary 5 minute cooldowns on signature verifications in some cases ([ThibG](https://github.com/tootsuite/mastodon/pull/10813)) +- Fix possible race condition when processing statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10815)) + +### Security + +- Require specific OAuth scopes for specific endpoints of the streaming API, instead of merely requiring a token for all endpoints, and allow using WebSockets protocol negotiation to specify the access token instead of using a query string ([ThibG](https://github.com/tootsuite/mastodon/pull/10818)) + ## [2.8.3] - 2019-05-19 ### Added diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index d384ea4d7..59ded05f7 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 3 + 4 end def pre