diff --git a/.eslintrc.js b/.eslintrc.js index 3bac9ed694ee11..70506f60c48521 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -236,7 +236,7 @@ module.exports = { }, // Common React utilities { - pattern: '{classnames,react-helmet,react-router-dom}', + pattern: '{classnames,react-helmet,react-router,react-router-dom}', group: 'external', position: 'before', }, diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index bcf63bb59e82b6..d14a34f3c2791d 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -114,6 +114,7 @@ jobs: BUNDLE_WITH: 'pam_authentication test' CI_JOBS: ${{ matrix.ci_job }}/4 ES_ENABLED: false + GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }} strategy: fail-fast: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 28316b82529577..2450afb5829560 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -48,27 +48,6 @@ Lint/UnusedBlockArgument: - 'config/initializers/paperclip.rb' - 'config/initializers/simple_form.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Lint/UselessAssignment: - Exclude: - - 'app/services/activitypub/process_status_update_service.rb' - - 'config/initializers/3_omniauth.rb' - - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb' - - 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb' - - 'spec/controllers/api/v1/favourites_controller_spec.rb' - - 'spec/controllers/concerns/account_controller_concern_spec.rb' - - 'spec/helpers/jsonld_helper_spec.rb' - - 'spec/models/account_spec.rb' - - 'spec/models/domain_block_spec.rb' - - 'spec/models/status_spec.rb' - - 'spec/models/user_spec.rb' - - 'spec/models/webauthn_credentials_spec.rb' - - 'spec/services/account_search_service_spec.rb' - - 'spec/services/post_status_service_spec.rb' - - 'spec/services/precompute_feed_service_spec.rb' - - 'spec/services/resolve_url_service_spec.rb' - - 'spec/views/statuses/show.html.haml_spec.rb' - # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 144 @@ -86,26 +65,6 @@ Metrics/CyclomaticComplexity: Metrics/PerceivedComplexity: Max: 27 -# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. -# SupportedStyles: snake_case, normalcase, non_integer -# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 -Naming/VariableNumber: - Exclude: - - 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb' - - 'db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb' - - 'db/migrate/20190820003045_update_statuses_index.rb' - - 'db/migrate/20190823221802_add_local_index_to_statuses.rb' - - 'db/migrate/20200119112504_add_public_index_to_statuses.rb' - - 'spec/models/account_spec.rb' - - 'spec/models/domain_block_spec.rb' - - 'spec/models/user_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: SafeMultiline. -Performance/DeletePrefix: - Exclude: - - 'app/models/featured_tag.rb' - Performance/MapMethodChain: Exclude: - 'app/models/feed.rb' diff --git a/app/controllers/api/v1/apps/credentials_controller.rb b/app/controllers/api/v1/apps/credentials_controller.rb index 0475b2d4a208d4..6256bed64cf831 100644 --- a/app/controllers/api/v1/apps/credentials_controller.rb +++ b/app/controllers/api/v1/apps/credentials_controller.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true class Api::V1::Apps::CredentialsController < Api::BaseController - before_action -> { doorkeeper_authorize! :read } - def show - render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key) + 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) end end diff --git a/app/helpers/admin/disputes_helper.rb b/app/helpers/admin/disputes_helper.rb new file mode 100644 index 00000000000000..366a470ed2a451 --- /dev/null +++ b/app/helpers/admin/disputes_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Admin + module DisputesHelper + def strike_action_label(appeal) + t(key_for_action(appeal), + scope: 'admin.strikes.actions', + name: content_tag(:span, appeal.strike.account.username, class: 'username'), + target: content_tag(:span, appeal.account.username, class: 'target')) + .html_safe + end + + private + + def key_for_action(appeal) + AccountWarning.actions.slice(appeal.strike.action).keys.first + end + end +end diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index 112c4256f577de..f0faf69b431fae 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -9,6 +9,10 @@ def linkify(text, options = {}) TextFormatter.new(text, options).to_s end + def url_for_preview_card(preview_card) + preview_card.url + end + def extract_status_plain_text(status) PlainTextFormatter.new(status.text, status.local?).to_s end diff --git a/app/javascript/mastodon/components/__tests__/button-test.jsx b/app/javascript/mastodon/components/__tests__/button-test.jsx index 6de961f784ebf5..ad7a0c49cab724 100644 --- a/app/javascript/mastodon/components/__tests__/button-test.jsx +++ b/app/javascript/mastodon/components/__tests__/button-test.jsx @@ -1,7 +1,7 @@ import { render, fireEvent, screen } from '@testing-library/react'; import renderer from 'react-test-renderer'; -import Button from '../button'; +import { Button } from '../button'; describe(' - ); - } - -} diff --git a/app/javascript/mastodon/components/button.tsx b/app/javascript/mastodon/components/button.tsx new file mode 100644 index 00000000000000..0b6a0f267ebd6e --- /dev/null +++ b/app/javascript/mastodon/components/button.tsx @@ -0,0 +1,58 @@ +import { useCallback } from 'react'; + +import classNames from 'classnames'; + +interface BaseProps extends React.ButtonHTMLAttributes { + block?: boolean; + secondary?: boolean; + text?: JSX.Element; +} + +interface PropsWithChildren extends BaseProps { + text?: never; +} + +interface PropsWithText extends BaseProps { + text: JSX.Element; + children: never; +} + +type Props = PropsWithText | PropsWithChildren; + +export const Button: React.FC = ({ + text, + type = 'button', + onClick, + disabled, + block, + secondary, + className, + title, + children, + ...props +}) => { + const handleClick = useCallback>( + (e) => { + if (!disabled && onClick) { + onClick(e); + } + }, + [disabled, onClick], + ); + + return ( + + ); +}; diff --git a/app/javascript/mastodon/components/column_back_button.jsx b/app/javascript/mastodon/components/column_back_button.jsx index 74a03b093ab8cc..b47718ed8924bd 100644 --- a/app/javascript/mastodon/components/column_back_button.jsx +++ b/app/javascript/mastodon/components/column_back_button.jsx @@ -4,29 +4,28 @@ import { createPortal } from 'react-dom'; import { FormattedMessage } from 'react-intl'; -import { Icon } from 'mastodon/components/icon'; +import { withRouter } from 'react-router-dom'; -export default class ColumnBackButton extends PureComponent { +import { Icon } from 'mastodon/components/icon'; +import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - static contextTypes = { - router: PropTypes.object, - }; +class ColumnBackButton extends PureComponent { static propTypes = { multiColumn: PropTypes.bool, onClick: PropTypes.func, + ...WithRouterPropTypes, }; handleClick = () => { - const { router } = this.context; - const { onClick } = this.props; + const { onClick, history } = this.props; if (onClick) { onClick(); - } else if (router.history.location?.state?.fromMastodon) { - router.history.goBack(); + } else if (history.location?.state?.fromMastodon) { + history.goBack(); } else { - router.history.push('/'); + history.push('/'); } }; @@ -60,3 +59,5 @@ export default class ColumnBackButton extends PureComponent { } } + +export default withRouter(ColumnBackButton); diff --git a/app/javascript/mastodon/components/column_header.jsx b/app/javascript/mastodon/components/column_header.jsx index 9d29bbae0346be..2896a501be7d70 100644 --- a/app/javascript/mastodon/components/column_header.jsx +++ b/app/javascript/mastodon/components/column_header.jsx @@ -5,8 +5,10 @@ import { createPortal } from 'react-dom'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import { Icon } from 'mastodon/components/icon'; +import { WithRouterPropTypes } from 'mastodon/utils/react_router'; const messages = defineMessages({ show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, @@ -18,7 +20,6 @@ const messages = defineMessages({ class ColumnHeader extends PureComponent { static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; @@ -38,6 +39,7 @@ class ColumnHeader extends PureComponent { onClick: PropTypes.func, appendContent: PropTypes.node, collapseIssues: PropTypes.bool, + ...WithRouterPropTypes, }; state = { @@ -63,12 +65,12 @@ class ColumnHeader extends PureComponent { }; handleBackClick = () => { - const { router } = this.context; + const { history } = this.props; - if (router.history.location?.state?.fromMastodon) { - router.history.goBack(); + if (history.location?.state?.fromMastodon) { + history.goBack(); } else { - router.history.push('/'); + history.push('/'); } }; @@ -78,15 +80,14 @@ class ColumnHeader extends PureComponent { handlePin = () => { if (!this.props.pinned) { - this.context.router.history.replace('/'); + this.props.history.replace('/'); } this.props.onPin(); }; render () { - const { router } = this.context; - const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props; + const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; const { collapsed, animating } = this.state; const wrapperClassName = classNames('column-header__wrapper', { @@ -129,7 +130,7 @@ class ColumnHeader extends PureComponent { pinButton = ; } - if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) { + if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { backButton = ( - diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.jsx b/app/javascript/mastodon/features/ui/components/boost_modal.jsx index f1a026b2b30be6..be5c60ab652ebc 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -12,9 +13,10 @@ import { changeBoostPrivacy } from 'mastodon/actions/boosts'; import AttachmentList from 'mastodon/components/attachment_list'; import { Icon } from 'mastodon/components/icon'; import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown'; +import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { Avatar } from '../../../components/avatar'; -import Button from '../../../components/button'; +import { Button } from '../../../components/button'; import { DisplayName } from '../../../components/display_name'; import { RelativeTimestamp } from '../../../components/relative_timestamp'; import StatusContent from '../../../components/status_content'; @@ -49,11 +51,6 @@ const mapDispatchToProps = dispatch => { }; class BoostModal extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { status: ImmutablePropTypes.map.isRequired, onReblog: PropTypes.func.isRequired, @@ -61,12 +58,9 @@ class BoostModal extends ImmutablePureComponent { onChangeBoostPrivacy: PropTypes.func.isRequired, privacy: PropTypes.string.isRequired, intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; - componentDidMount() { - this.button.focus(); - } - handleReblog = () => { this.props.onReblog(this.props.status, this.props.privacy); this.props.onClose(); @@ -76,7 +70,7 @@ class BoostModal extends ImmutablePureComponent { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.props.onClose(); - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } }; @@ -84,10 +78,6 @@ class BoostModal extends ImmutablePureComponent { return document.getElementsByClassName('modal-root__container')[0]; }; - setRef = (c) => { - this.button = c; - }; - render () { const { status, privacy, intl } = this.props; const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; @@ -147,7 +137,7 @@ class BoostModal extends ImmutablePureComponent { onChange={this.props.onChangeBoostPrivacy} /> )} - - diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index 5b412ba14735f0..fd3974314eb81a 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import { WordmarkLogo } from 'mastodon/components/logo'; -import NavigationPortal from 'mastodon/components/navigation_portal'; +import { NavigationPortal } from 'mastodon/components/navigation_portal'; import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag } from 'mastodon/initial_state'; import { transientSingleColumn } from 'mastodon/is_mobile'; @@ -41,7 +41,6 @@ const messages = defineMessages({ class NavigationPanel extends Component { static contextTypes = { - router: PropTypes.object.isRequired, identity: PropTypes.object.isRequired, }; @@ -66,25 +65,31 @@ class NavigationPanel extends Component { ) : ( )); + + let banner = undefined; + + if(transientSingleColumn) + banner = (
+ {intl.formatMessage(messages.openedInClassicInterface)} + {" "} + + {intl.formatMessage(messages.advancedInterface)} + +
); return (
- - {transientSingleColumn ? ( -
- {intl.formatMessage(messages.openedInClassicInterface)} - {" "} - - {intl.formatMessage(messages.advancedInterface)} - -
- ) : ( -
- )} + {!banner &&
}
+ {banner && + + } + {signedIn && ( <> diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 4fb9522c167ff5..b40f3938e0a2c4 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -16,6 +16,7 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodo import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import PictureInPicture from 'mastodon/features/picture_in_picture'; import { layoutFromWindow } from 'mastodon/is_mobile'; +import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { clearHeight } from '../../actions/height_cache'; @@ -278,7 +279,6 @@ class SwitchingColumnsArea extends PureComponent { class UI extends PureComponent { static contextTypes = { - router: PropTypes.object.isRequired, identity: PropTypes.object.isRequired, }; @@ -289,12 +289,12 @@ class UI extends PureComponent { hasComposingText: PropTypes.bool, hasMediaAttachments: PropTypes.bool, canUploadMore: PropTypes.bool, - location: PropTypes.object, intl: PropTypes.object.isRequired, dropdownMenuIsOpen: PropTypes.bool, layout: PropTypes.string.isRequired, firstLaunch: PropTypes.bool, username: PropTypes.string, + ...WithRouterPropTypes, }; state = { @@ -391,7 +391,7 @@ class UI extends PureComponent { handleServiceWorkerPostMessage = ({ data }) => { if (data.type === 'navigate') { - this.context.router.history.push(data.path); + this.props.history.push(data.path); } else { console.warn('Unknown message type:', data.type); } @@ -512,12 +512,12 @@ class UI extends PureComponent { }; handleHotkeyBack = () => { - const { router } = this.context; + const { history } = this.props; - if (router.history.location?.state?.fromMastodon) { - router.history.goBack(); + if (history.location?.state?.fromMastodon) { + history.goBack(); } else { - router.history.push('/'); + history.push('/'); } }; @@ -527,38 +527,38 @@ class UI extends PureComponent { handleHotkeyToggleHelp = () => { if (this.props.location.pathname === '/keyboard-shortcuts') { - this.context.router.history.goBack(); + this.props.history.goBack(); } else { - this.context.router.history.push('/keyboard-shortcuts'); + this.props.history.push('/keyboard-shortcuts'); } }; handleHotkeyGoToHome = () => { - this.context.router.history.push('/home'); + this.props.history.push('/home'); }; handleHotkeyGoToNotifications = () => { - this.context.router.history.push('/notifications'); + this.props.history.push('/notifications'); }; handleHotkeyGoToLocal = () => { - this.context.router.history.push('/public/local'); + this.props.history.push('/public/local'); }; handleHotkeyGoToFederated = () => { - this.context.router.history.push('/public'); + this.props.history.push('/public'); }; handleHotkeyGoToDirect = () => { - this.context.router.history.push('/conversations'); + this.props.history.push('/conversations'); }; handleHotkeyGoToStart = () => { - this.context.router.history.push('/getting-started'); + this.props.history.push('/getting-started'); }; handleHotkeyGoToFavourites = () => { - this.context.router.history.push('/favourites'); + this.props.history.push('/favourites'); }; handleHotkeyGoToEmojiReactions = () => { @@ -566,23 +566,23 @@ class UI extends PureComponent { }; handleHotkeyGoToPinned = () => { - this.context.router.history.push('/pinned'); + this.props.history.push('/pinned'); }; handleHotkeyGoToProfile = () => { - this.context.router.history.push(`/@${this.props.username}`); + this.props.history.push(`/@${this.props.username}`); }; handleHotkeyGoToBlocked = () => { - this.context.router.history.push('/blocks'); + this.props.history.push('/blocks'); }; handleHotkeyGoToMuted = () => { - this.context.router.history.push('/mutes'); + this.props.history.push('/mutes'); }; handleHotkeyGoToRequests = () => { - this.context.router.history.push('/follow_requests'); + this.props.history.push('/follow_requests'); }; render () { diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx index 99277268573cc5..c0ee31bf68041b 100644 --- a/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx +++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; -import { Component, PureComponent, cloneElement, Children } from 'react'; +import { Component, cloneElement, Children } from 'react'; -import { Switch, Route } from 'react-router-dom'; +import { Switch, Route, useLocation } from 'react-router-dom'; import StackTrace from 'stacktrace-js'; @@ -10,27 +10,20 @@ import ColumnLoading from '../components/column_loading'; import BundleContainer from '../containers/bundle_container'; // Small wrapper to pass multiColumn to the route components -export class WrappedSwitch extends PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - - render () { - const { multiColumn, children } = this.props; - const { location } = this.context.router.route; - - const decklessLocation = multiColumn && location.pathname.startsWith('/deck') - ? {...location, pathname: location.pathname.slice(5)} - : location; - - return ( - - {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)} - - ); - } +export const WrappedSwitch = ({ multiColumn, children }) => { + const location = useLocation(); + + const decklessLocation = multiColumn && location.pathname.startsWith('/deck') + ? {...location, pathname: location.pathname.slice(5)} + : location; + + return ( + + {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)} + + ); +}; -} WrappedSwitch.propTypes = { multiColumn: PropTypes.bool, diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index a7a68533d677e3..aac4256ffdbf96 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -71,11 +71,11 @@ "account.unmute_notifications_short": "Poista ilmoitusten mykistys", "account.unmute_short": "Poista mykistys", "account_note.placeholder": "Lisää muistiinpano napsauttamalla", - "admin.dashboard.daily_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen päivään mennessä", - "admin.dashboard.monthly_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen kuukauteen mennessä", + "admin.dashboard.daily_retention": "Käyttäjien pysyvyys rekisteröitymisen jälkeen päivittäin", + "admin.dashboard.monthly_retention": "Käyttäjien pysyvyys rekisteröitymisen jälkeen kuukausittain", "admin.dashboard.retention.average": "Keskimäärin", - "admin.dashboard.retention.cohort": "Kirjautumiset", - "admin.dashboard.retention.cohort_size": "Uudet käyttäjät", + "admin.dashboard.retention.cohort": "Rekisteröitymis-kk.", + "admin.dashboard.retention.cohort_size": "Uusia käyttäjiä", "admin.impact_report.instance_accounts": "Tilien profiilit, jotka tämä poistaisi", "admin.impact_report.instance_followers": "Seuraajat, jotka käyttäjämme menettäisivät", "admin.impact_report.instance_follows": "Seuraajat, jotka heidän käyttäjänsä menettäisivät", @@ -114,7 +114,7 @@ "column.directory": "Selaa profiileja", "column.domain_blocks": "Estetyt verkkotunnukset", "column.favourites": "Suosikit", - "column.firehose": "Live-syötteet", + "column.firehose": "Livesyötteet", "column.follow_requests": "Seuraamispyynnöt", "column.home": "Koti", "column.lists": "Listat", @@ -135,7 +135,7 @@ "community.column_settings.remote_only": "Vain etätilit", "compose.language.change": "Vaihda kieli", "compose.language.search": "Hae kieliä...", - "compose.published.body": "Julkaisusi julkaistiin.", + "compose.published.body": "Julkaisu lähetetty.", "compose.published.open": "Avaa", "compose.saved.body": "Julkaisu tallennettu.", "compose_form.direct_message_warning_learn_more": "Lisätietoja", @@ -436,10 +436,10 @@ "notifications.clear": "Tyhjennä ilmoitukset", "notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?", "notifications.column_settings.admin.report": "Uudet ilmoitukset:", - "notifications.column_settings.admin.sign_up": "Uudet kirjautumiset:", + "notifications.column_settings.admin.sign_up": "Uudet rekisteröitymiset:", "notifications.column_settings.alert": "Työpöytäilmoitukset", "notifications.column_settings.favourite": "Suosikit:", - "notifications.column_settings.filter_bar.advanced": "Näytä kaikki kategoriat", + "notifications.column_settings.filter_bar.advanced": "Näytä kaikki luokat", "notifications.column_settings.filter_bar.category": "Pikasuodatuspalkki", "notifications.column_settings.filter_bar.show_bar": "Näytä suodatinpalkki", "notifications.column_settings.follow": "Uudet seuraajat:", @@ -517,7 +517,7 @@ "privacy.private.short": "Vain seuraajat", "privacy.public.long": "Näkyy kaikille", "privacy.public.short": "Julkinen", - "privacy.unlisted.long": "Näkyy kaikille, mutta jää pois löytämisominaisuuksista", + "privacy.unlisted.long": "Näkyy kaikille mutta jää pois löytämisominaisuuksista", "privacy.unlisted.short": "Listaamaton", "privacy_policy.last_updated": "Viimeksi päivitetty {date}", "privacy_policy.title": "Tietosuojakäytäntö", @@ -589,13 +589,13 @@ "search.quick_action.go_to_hashtag": "Siirry aihetunnisteeseen {x}", "search.quick_action.open_url": "Avaa URL-osoite Mastodonissa", "search.quick_action.status_search": "Julkaisut haulla {x}", - "search.search_or_paste": "Hae tai kirjoita URL-osoite", + "search.search_or_paste": "Hae tai liitä URL-osoite", "search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.", "search_popout.language_code": "ISO-kielikoodi", "search_popout.options": "Hakuvalinnat", "search_popout.quick_actions": "Pikatoiminnot", "search_popout.recent": "Viimeaikaiset haut", - "search_popout.specific_date": "tietty päivämäärä", + "search_popout.specific_date": "tarkka päiväys", "search_popout.user": "käyttäjä", "search_results.accounts": "Profiilit", "search_results.all": "Kaikki", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index dbd0a3f3a2ff23..12365c879d76fe 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -163,13 +163,13 @@ "confirmation_modal.cancel": "Annulearje", "confirmations.block.block_and_report": "Blokkearje en rapportearje", "confirmations.block.confirm": "Blokkearje", - "confirmations.block.message": "Bisto wis datsto {name} blokkearje wolst?", + "confirmations.block.message": "Binne jo wis dat jo {name} blokkearje wolle?", "confirmations.cancel_follow_request.confirm": "Fersyk annulearje", "confirmations.cancel_follow_request.message": "Binne jo wis dat jo jo fersyk om {name} te folgjen annulearje wolle?", "confirmations.delete.confirm": "Fuortsmite", "confirmations.delete.message": "Binne jo wis dat jo dit berjocht fuortsmite wolle?", "confirmations.delete_list.confirm": "Fuortsmite", - "confirmations.delete_list.message": "Bisto wis datsto dizze list foar permanint fuortsmite wolst?", + "confirmations.delete_list.message": "Binne jo wis dat jo dizze list foar permanint fuortsmite wolle?", "confirmations.discard_edit_media.confirm": "Fuortsmite", "confirmations.discard_edit_media.message": "Jo hawwe net-bewarre wizigingen yn de mediabeskriuwing of foarfertoaning, wolle jo dizze dochs fuortsmite?", "confirmations.domain_block.confirm": "Alles fan dit domein blokkearje", @@ -177,16 +177,16 @@ "confirmations.edit.confirm": "Bewurkje", "confirmations.edit.message": "Troch no te bewurkjen sil it berjocht dat jo no oan it skriuwen binne oerskreaun wurde. Wolle jo trochgean?", "confirmations.logout.confirm": "Ofmelde", - "confirmations.logout.message": "Bisto wis datsto ôfmelde wolst?", + "confirmations.logout.message": "Binne jo wis dat jo ôfmelde wolle?", "confirmations.mute.confirm": "Negearje", - "confirmations.mute.explanation": "Dit sil berjochten fan harren en berjochten dêr’t se yn fermeld wurde ûnsichtber meitsje, mar se sille berjochten noch hieltyd sjen kinne en jo folgje kinne.", + "confirmations.mute.explanation": "Dit sil berjochten fan harren en berjochten dêr’t se yn fermeld wurde ûnsichtber meitsje, mar se sille jo berjochten noch hieltyd sjen kinne en jo folgje kinne.", "confirmations.mute.message": "Binne jo wis dat jo {name} negearje wolle?", "confirmations.redraft.confirm": "Fuortsmite en opnij opstelle", "confirmations.redraft.message": "Binne jo wis dat jo dit berjocht fuortsmite en opnij opstelle wolle? Favoriten en boosts geane dan ferlern en reaksjes op it oarspronklike berjocht reitsje jo kwyt.", "confirmations.reply.confirm": "Reagearje", "confirmations.reply.message": "Troch no te reagearjen sil it berjocht dat jo no oan it skriuwen binne oerskreaun wurde. Wolle jo trochgean?", "confirmations.unfollow.confirm": "Net mear folgje", - "confirmations.unfollow.message": "Bisto wis datsto {name} net mear folgje wolst?", + "confirmations.unfollow.message": "Binne jo wis dat jo {name} net mear folgje wolle?", "conversation.delete": "Petear fuortsmite", "conversation.mark_as_read": "As lêzen markearje", "conversation.open": "Petear toane", @@ -351,7 +351,7 @@ "keyboard_shortcuts.local": "to open local timeline", "keyboard_shortcuts.mention": "Skriuwer fermelde", "keyboard_shortcuts.muted": "to open muted users list", - "keyboard_shortcuts.my_profile": "Dyn profyl iepenje", + "keyboard_shortcuts.my_profile": "Jo profyl iepenje", "keyboard_shortcuts.notifications": "Meldingen toane", "keyboard_shortcuts.open_media": "Media iepenje", "keyboard_shortcuts.pinned": "Fêstsette berjochten toane", @@ -421,20 +421,20 @@ "navigation_bar.public_timeline": "Globale tiidline", "navigation_bar.search": "Sykje", "navigation_bar.security": "Befeiliging", - "not_signed_in_indicator.not_signed_in": "Do moatst oanmelde om tagong ta dizze ynformaasje te krijen.", + "not_signed_in_indicator.not_signed_in": "Jo moatte oanmelde om tagong ta dizze ynformaasje te krijen.", "notification.admin.report": "{name} hat {target} rapportearre", "notification.admin.sign_up": "{name} hat harren registrearre", "notification.favourite": "{name} hat jo berjocht as favoryt markearre", "notification.follow": "{name} folget dy", "notification.follow_request": "{name} hat dy in folchfersyk stjoerd", "notification.mention": "{name} hat dy fermeld", - "notification.own_poll": "Dyn poll is beëinige", + "notification.own_poll": "Jo poll is beëinige", "notification.poll": "In enkête dêr’t jo yn stimd hawwe is beëinige", "notification.reblog": "{name} hat jo berjocht boost", "notification.status": "{name} hat in berjocht pleatst", "notification.update": "{name} hat in berjocht bewurke", "notifications.clear": "Meldingen wiskje", - "notifications.clear_confirmation": "Bisto wis datsto al dyn meldingen permanint fuortsmite wolst?", + "notifications.clear_confirmation": "Binne jo wis dat jo al jo meldingen permanint fuortsmite wolle?", "notifications.column_settings.admin.report": "Nije rapportaazjes:", "notifications.column_settings.admin.sign_up": "Nije registraasjes:", "notifications.column_settings.alert": "Desktopmeldingen", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index d1bcc8cd9707e5..1477974d3a7a00 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -209,7 +209,7 @@ "compose.language.search": "言語を検索...", "compose.published.body": "投稿されました!", "compose.published.open": "開く", - "compose.saved.body": "投稿が保存されました", + "compose.saved.body": "変更を保存しました。", "compose_form.direct_message_warning_learn_more": "もっと詳しく", "compose_form.encryption_warning": "Mastodonの投稿はエンドツーエンド暗号化に対応していません。安全に送受信されるべき情報をMastodonで共有しないでください。", "compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index f2237d693e698e..09c95e02cdad2e 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -204,7 +204,7 @@ "dismissable_banner.explore_links": "이 소식들은 오늘 소셜 웹에서 가장 많이 공유된 내용들입니다. 새 소식을 더 많은 사람들이 공유할수록 높은 순위가 됩니다.", "dismissable_banner.explore_statuses": "이 게시물들은 오늘 소셜 웹에서 호응을 얻고 있는 게시물들입니다. 부스트와 관심을 받는 새로운 글들이 높은 순위가 됩니다.", "dismissable_banner.explore_tags": "이 해시태그들은 이 서버와 분산화된 네트워크의 다른 서버에서 사람들의 인기를 끌고 있는 것들입니다.", - "dismissable_banner.public_timeline": "이것들은 {domain}에 있는 사람들이 팔로우한 사람들의 최신 게시물들입니다.", + "dismissable_banner.public_timeline": "{domain} 사람들이 팔로우하는 소셜 웹 사람들의 최신 공개 게시물입니다.", "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.", "embed.preview": "이렇게 표시됩니다:", "emoji_button.activity": "활동", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 0a14ea93a39f87..8ff390aabb8ed1 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -153,7 +153,7 @@ "compose_form.publish": "Legg ut", "compose_form.publish_form": "Legg ut", "compose_form.publish_loud": "{publish}!", - "compose_form.save_changes": "Gøym", + "compose_form.save_changes": "Lagre endringar", "compose_form.sensitive.hide": "{count, plural, one {Marker mediet som ømtolig} other {Marker media som ømtolige}}", "compose_form.sensitive.marked": "{count, plural, one {Mediet er markert som ømtolig} other {Media er markerte som ømtolige}}", "compose_form.sensitive.unmarked": "{count, plural, one {Mediet er ikkje markert som ømtolig} other {Media er ikkje markerte som ømtolige}}", @@ -171,7 +171,7 @@ "confirmations.delete_list.confirm": "Slett", "confirmations.delete_list.message": "Er du sikker på at du vil sletta denne lista for alltid?", "confirmations.discard_edit_media.confirm": "Forkast", - "confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkaste dei likevel?", + "confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkasta dei likevel?", "confirmations.domain_block.confirm": "Skjul alt frå domenet", "confirmations.domain_block.message": "Er du heilt, heilt sikker på at du vil skjula heile {domain}? I dei fleste tilfelle er det godt nok og føretrekt med nokre få målretta blokkeringar eller målbindingar. Du kjem ikkje til å sjå innhald frå domenet i fødererte tidsliner eller i varsla dine. Fylgjarane dine frå domenet vert fjerna.", "confirmations.edit.confirm": "Rediger", @@ -285,7 +285,7 @@ "footer.privacy_policy": "Personvernsreglar", "footer.source_code": "Vis kjeldekode", "footer.status": "Status", - "generic.saved": "Gøymt", + "generic.saved": "Lagra", "getting_started.heading": "Kom i gang", "hashtag.column_header.tag_mode.all": "og {additional}", "hashtag.column_header.tag_mode.any": "eller {additional}", @@ -314,7 +314,7 @@ "home.pending_critical_update.link": "Sjå oppdateringar", "home.pending_critical_update.title": "Kritisk sikkerheitsoppdatering er tilgjengeleg!", "home.show_announcements": "Vis kunngjeringar", - "interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerkja dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.", + "interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerka dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.", "interaction_modal.description.follow": "Med ein konto på Mastodon kan du fylgja {name} for å sjå innlegga deira i din heimestraum.", "interaction_modal.description.reblog": "Med ein konto på Mastodon kan du framheva dette innlegget for å dela det med dine eigne fylgjarar.", "interaction_modal.description.reply": "Med ein konto på Mastodon kan du svara på dette innlegget.", @@ -673,7 +673,7 @@ "status.unmute_conversation": "Opphev målbinding av samtalen", "status.unpin": "Løys frå profil", "subscribed_languages.lead": "Kun innlegg på valde språk vil bli dukke opp i heimestraumen din og i listene dine etter denne endringa. For å motta innlegg på alle språk, la vere å velje nokon.", - "subscribed_languages.save": "Gøym", + "subscribed_languages.save": "Lagre endringar", "subscribed_languages.target": "Endre abonnerte språk for {target}", "tabs_bar.home": "Heim", "tabs_bar.notifications": "Varsel", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 130b28bc7466c9..ec2c76e8f942e1 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -303,7 +303,7 @@ "hashtag.unfollow": "Отпрати хеш ознаку", "hashtags.and_other": "…и {count, plural, one {још #} few {још #}other {још #}}", "home.actions.go_to_explore": "Погледате шта је у тренду", - "home.actions.go_to_suggestions": "Пронађeте људе које бисте пратили", + "home.actions.go_to_suggestions": "Пронађете људе које бисте пратили", "home.column_settings.basic": "Основна", "home.column_settings.show_reblogs": "Прикажи подржавања", "home.column_settings.show_replies": "Прикажи одговоре", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index b1cd0ca753ce22..93db5c9e2ea62e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -629,7 +629,7 @@ "status.edit": "編輯", "status.edited": "編輯於 {date}", "status.edited_x_times": "已編輯 {count, plural, one {{count} 次} other {{count} 次}}", - "status.embed": "內嵌", + "status.embed": "內嵌嘟文", "status.favourite": "最愛", "status.filter": "過濾此嘟文", "status.filtered": "已過濾", diff --git a/app/javascript/mastodon/utils/react_router.jsx b/app/javascript/mastodon/utils/react_router.jsx new file mode 100644 index 00000000000000..fa8f0db2b5cbd6 --- /dev/null +++ b/app/javascript/mastodon/utils/react_router.jsx @@ -0,0 +1,61 @@ +import PropTypes from "prop-types"; + +import { __RouterContext } from "react-router"; + +import hoistStatics from "hoist-non-react-statics"; + +export const WithRouterPropTypes = { + match: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, +}; + +export const WithOptionalRouterPropTypes = { + match: PropTypes.object, + location: PropTypes.object, + history: PropTypes.object, +}; + +// This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js +// but does not fail if called outside of a React Router context +export function withOptionalRouter(Component) { + const displayName = `withRouter(${Component.displayName || Component.name})`; + const C = props => { + const { wrappedComponentRef, ...remainingProps } = props; + + return ( + <__RouterContext.Consumer> + {context => { + if(context) + return ( + + ); + else + return ( + + ); + }} + + ); + }; + + C.displayName = displayName; + C.WrappedComponent = Component; + C.propTypes = { + ...Component.propTypes, + wrappedComponentRef: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.func, + PropTypes.object + ]) + }; + + return hoistStatics(C, Component); +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index ad12fc3b3e0413..67b061e52ec8c2 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2536,6 +2536,7 @@ $ui-header-height: 55px; .navigation-panel__sign-in-banner, .navigation-panel__logo, + .navigation-panel__banner, .getting-started__trends { display: none; } diff --git a/app/lib/activitypub/linked_data_signature.rb b/app/lib/activitypub/linked_data_signature.rb index ea59879f3b7eed..faea63e8f12c6e 100644 --- a/app/lib/activitypub/linked_data_signature.rb +++ b/app/lib/activitypub/linked_data_signature.rb @@ -18,8 +18,8 @@ def verify_actor! return unless type == 'RsaSignature2017' - creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri) - creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false) + creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri) + creator = ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false) if creator&.public_key.blank? return if creator.nil? @@ -28,6 +28,8 @@ def verify_actor! to_be_verified = options_hash + document_hash creator if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified) + rescue OpenSSL::PKey::RSAError + false end def sign!(creator, sign_with: nil) diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb index 6aa5834aca60d8..3412a076cc0af4 100644 --- a/app/models/featured_tag.rb +++ b/app/models/featured_tag.rb @@ -51,7 +51,7 @@ def decrement(deleted_status_id) private def strip_name - self.name = name&.strip&.gsub(/\A#/, '') + self.name = name&.strip&.delete_prefix('#') end def set_tag diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 4e24fab2402c03..a1751c426dc0fc 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -55,7 +55,7 @@ class PreviewCard < ApplicationRecord has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false - validates :url, presence: true, uniqueness: true + validates :url, presence: true, uniqueness: true, url: true validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES validates_attachment_size :image, less_than: LIMIT remotable_attachment :image, LIMIT diff --git a/app/serializers/rest/application_serializer.rb b/app/serializers/rest/application_serializer.rb index ab68219ade8fb2..e4806a3c9fc5b4 100644 --- a/app/serializers/rest/application_serializer.rb +++ b/app/serializers/rest/application_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class REST::ApplicationSerializer < ActiveModel::Serializer - attributes :id, :name, :website, :redirect_uri, + attributes :id, :name, :website, :scopes, :redirect_uri, :client_id, :client_secret, :vapid_key def id diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 765a59c890d543..aa8fc418890d98 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -107,8 +107,6 @@ def update_media_attachments! end end - added_media_attachments = @next_media_attachments - previous_media_attachments - @status.ordered_media_attachment_ids = @next_media_attachments.map(&:id) @media_attachments_changed = true if @status.ordered_media_attachment_ids != previous_media_attachments_ids diff --git a/app/views/admin/disputes/appeals/_appeal.html.haml b/app/views/admin/disputes/appeals/_appeal.html.haml index 3f6efb856e5046..d5611211ed134b 100644 --- a/app/views/admin/disputes/appeals/_appeal.html.haml +++ b/app/views/admin/disputes/appeals/_appeal.html.haml @@ -4,7 +4,7 @@ = image_tag appeal.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' .log-entry__content .log-entry__title - = t(appeal.strike.action, scope: 'admin.strikes.actions', name: content_tag(:span, appeal.strike.account.username, class: 'username'), target: content_tag(:span, appeal.account.username, class: 'target')).html_safe + = strike_action_label(appeal) .log-entry__timestamp %time.formatted{ datetime: appeal.strike.created_at.iso8601 } = l(appeal.strike.created_at) diff --git a/app/views/admin/trends/links/_preview_card.html.haml b/app/views/admin/trends/links/_preview_card.html.haml index 1ca34837151477..ee3774790c45ad 100644 --- a/app/views/admin/trends/links/_preview_card.html.haml +++ b/app/views/admin/trends/links/_preview_card.html.haml @@ -4,7 +4,7 @@ .batch-table__row__content.pending-account .pending-account__header - = link_to preview_card.title, preview_card.url + = link_to preview_card.title, url_for_preview_card(preview_card) %br/ diff --git a/config/brakeman.ignore b/config/brakeman.ignore deleted file mode 100644 index f1ef5ed3b18fe1..00000000000000 --- a/config/brakeman.ignore +++ /dev/null @@ -1,118 +0,0 @@ -{ - "ignored_warnings": [ - { - "warning_type": "Cross-Site Scripting", - "warning_code": 2, - "fingerprint": "71cf98c8235b5cfa9946b5db8fdc1a2f3a862566abb34e4542be6f3acae78233", - "check_name": "CrossSiteScripting", - "message": "Unescaped model attribute", - "file": "app/views/admin/disputes/appeals/_appeal.html.haml", - "line": 7, - "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting", - "code": "t((Unresolved Model).new.strike.action, :scope => \"admin.strikes.actions\", :name => content_tag(:span, (Unresolved Model).new.strike.account.username, :class => \"username\"), :target => content_tag(:span, (Unresolved Model).new.account.username, :class => \"target\"))", - "render_path": [ - { - "type": "template", - "name": "admin/disputes/appeals/index", - "line": 20, - "file": "app/views/admin/disputes/appeals/index.html.haml", - "rendered": { - "name": "admin/disputes/appeals/_appeal", - "file": "app/views/admin/disputes/appeals/_appeal.html.haml" - } - } - ], - "location": { - "type": "template", - "template": "admin/disputes/appeals/_appeal" - }, - "user_input": "(Unresolved Model).new.strike", - "confidence": "Weak", - "cwe_id": [ - 79 - ], - "note": "" - }, - { - "warning_type": "Cross-Site Scripting", - "warning_code": 4, - "fingerprint": "cd5cfd7f40037fbfa753e494d7129df16e358bfc43ef0da3febafbf4ee1ed3ac", - "check_name": "LinkToHref", - "message": "Potentially unsafe model attribute in `link_to` href", - "file": "app/views/admin/trends/links/_preview_card.html.haml", - "line": 7, - "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", - "code": "link_to((Unresolved Model).new.title, (Unresolved Model).new.url)", - "render_path": [ - { - "type": "template", - "name": "admin/trends/links/index", - "line": 49, - "file": "app/views/admin/trends/links/index.html.haml", - "rendered": { - "name": "admin/trends/links/_preview_card", - "file": "app/views/admin/trends/links/_preview_card.html.haml" - } - } - ], - "location": { - "type": "template", - "template": "admin/trends/links/_preview_card" - }, - "user_input": "(Unresolved Model).new.url", - "confidence": "Weak", - "cwe_id": [ - 79 - ], - "note": "" - }, - { - "warning_type": "Mass Assignment", - "warning_code": 105, - "fingerprint": "d0511f0287aea4ed9511f5a744f880cb15af77a8ec88f81b7365b00b642cf427", - "check_name": "PermitAttributes", - "message": "Potentially dangerous key allowed for mass assignment", - "file": "app/controllers/api/v1/reports_controller.rb", - "line": 26, - "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", - "code": "params.permit(:account_id, :comment, :category, :forward, :forward_to_domains => ([]), :status_ids => ([]), :rule_ids => ([]))", - "render_path": null, - "location": { - "type": "method", - "class": "Api::V1::ReportsController", - "method": "report_params" - }, - "user_input": ":account_id", - "confidence": "High", - "cwe_id": [ - 915 - ], - "note": "" - }, - { - "warning_type": "Mass Assignment", - "warning_code": 105, - "fingerprint": "dd59382eb5fda8da4d29f5d52dc8261ed9070d3c61cecc12b8332e18448b1c11", - "check_name": "PermitAttributes", - "message": "Potentially dangerous key allowed for mass assignment", - "file": "app/controllers/api/v2/search_controller.rb", - "line": 42, - "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", - "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :following, :searchability)", - "render_path": null, - "location": { - "type": "method", - "class": "Api::V2::SearchController", - "method": "search_params" - }, - "user_input": ":account_id", - "confidence": "High", - "cwe_id": [ - 915 - ], - "note": "" - } - ], - "updated": "2023-07-30 22:53:30 +0900", - "brakeman_version": "6.0.1" -} diff --git a/config/brakeman.yml b/config/brakeman.yml index 95ebc13865cd89..9ac95c80068e1b 100644 --- a/config/brakeman.yml +++ b/config/brakeman.yml @@ -1,3 +1,5 @@ --- :skip_checks: - CheckPermitAttributes +:url_safe_methods: + - url_for_preview_card diff --git a/config/initializers/3_omniauth.rb b/config/initializers/3_omniauth.rb index 566e7362a53d65..d316c3b73a7411 100644 --- a/config/initializers/3_omniauth.rb +++ b/config/initializers/3_omniauth.rb @@ -9,9 +9,6 @@ end Devise.setup do |config| - # Devise omniauth strategies - options = {} - # CAS strategy if ENV['CAS_ENABLED'] == 'true' cas_options = {} diff --git a/config/locales/activerecord.sk.yml b/config/locales/activerecord.sk.yml index 33f53a88ed988f..d13c416e516a63 100644 --- a/config/locales/activerecord.sk.yml +++ b/config/locales/activerecord.sk.yml @@ -53,3 +53,7 @@ sk: position: elevated: nemôže byť vyššia ako vaša súčasná rola own_role: nie je možné zmeniť s vašou aktuálnou rolou + webhook: + attributes: + events: + invalid_permissions: nemožno zahrnúť udalosti, ku ktorým nemáte práva diff --git a/config/locales/activerecord.zh-CN.yml b/config/locales/activerecord.zh-CN.yml index a52e7cff6f8e75..c510a58d1446cc 100644 --- a/config/locales/activerecord.zh-CN.yml +++ b/config/locales/activerecord.zh-CN.yml @@ -36,7 +36,7 @@ zh-CN: status: attributes: reblog: - taken: 已被转嘟过 + taken: 已经被转嘟过 user: attributes: email: diff --git a/config/locales/devise.fy.yml b/config/locales/devise.fy.yml index 9e93d2c19f947e..910d2d30947b1b 100644 --- a/config/locales/devise.fy.yml +++ b/config/locales/devise.fy.yml @@ -13,7 +13,7 @@ fy: locked: Jo account is blokkearre. not_found_in_database: "%{authentication_keys} of wachtwurd ûnjildich." pending: Jo account moat noch hieltyd beoardiele wurde. - timeout: Dyn sesje is ferrûn, meld dy opnij oan. + timeout: Jo sesje is ferrûn. Meld jo opnij oan om troch te gean. unauthenticated: Jo moatte oanmelde of registrearje. unconfirmed: Jo moatte earst jo account befêstigje. mailer: diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml index 5a3dbff7e42570..e46c4335301dc2 100644 --- a/config/locales/devise.zh-CN.yml +++ b/config/locales/devise.zh-CN.yml @@ -9,7 +9,7 @@ zh-CN: already_authenticated: 你已登录。 inactive: 你还没有激活账户。 invalid: "%{authentication_keys} 无效或密码错误。" - last_attempt: 你只有最后一次尝试机会,若未通过,账号将被锁定。 + last_attempt: 你只有最后一次尝试机会,若未通过,帐号将被锁定。 locked: 你的账户已被锁定。 not_found_in_database: "%{authentication_keys}或密码错误。" pending: 你的账号仍在审核中。 @@ -85,7 +85,7 @@ zh-CN: send_instructions: 如果你的电子邮件地址存在于我们的数据库中,你将在几分钟后收到一个密码恢复链接。如果你没有收到这封邮件,请检查你邮箱的垃圾箱。 send_paranoid_instructions: 如果你的电子邮件地址存在于我们的数据库中,你将在几分钟后收到一个密码恢复链接。如果你没有收到这封邮件,请检查你邮箱的垃圾箱。 updated: 你的密码已成功修改,现在你已登录。 - updated_not_active: 你的密码已成功修改。 + updated_not_active: 你的密码已修改成功。 registrations: destroyed: 再见!你的账户已成功注销。我们希望很快可以再见到你。 signed_up: 欢迎!你已成功注册。 diff --git a/config/locales/doorkeeper.fy.yml b/config/locales/doorkeeper.fy.yml index 8acf7ea9b8f754..a43defc427ef67 100644 --- a/config/locales/doorkeeper.fy.yml +++ b/config/locales/doorkeeper.fy.yml @@ -43,7 +43,7 @@ fy: new: Nije tapassing scopes: Tastimmingen show: Toane - title: Dyn tapassingen + title: Jo tapassingen new: title: Nije tapassing show: @@ -60,7 +60,7 @@ fy: error: title: Der is in flater bard new: - prompt_html: "%{client_name} hat tastimming nedich om tagong te krijen ta dyn account. It giet om in tapassing fan in tredde partij.Asto dit net fertroust, moatsto gjin tastimming jaan." + prompt_html: "%{client_name} hat tastimming nedich om tagong te krijen ta jo account. It giet om in tapassing fan in tredde partij.As jo dit net fertrouwe, moatte jo gjin tastimming jaan." review_permissions: Tastimmingen beoardiele title: Autorisaasje fereaske show: @@ -69,15 +69,15 @@ fy: buttons: revoke: Ynlûke confirmations: - revoke: Bisto wis? + revoke: Binne jo wis? index: authorized_at: Autorisearre op %{date} - description_html: Dit binne tapassingen dy’t tagong hawwe ta dyn account fia de API. As der tapassingen tusken steane dy’tsto net werkenst of in tapassing harren misdraacht, kinsto de tagongsrjochten fan de tapassing ynlûke. + description_html: Dit binne tapassingen dy’t fia de API tagong hawwe ta jo account. As der tapassingen tusken steane dy’t jo net werkenne of in tapassing harren misdraacht, kinne jo de tagongsrjochten fan de tapassing ynlûke. last_used_at: Lêst brûkt op %{date} never_used: Nea brûkt scopes: Tastimmingen superapp: Yntern - title: Dyn autorisearre tapassingen + title: Jo autorisearre tapassingen errors: messages: access_denied: De boarne-eigener of autorisaasjeserver hat it fersyk wegere. @@ -165,22 +165,22 @@ fy: admin:write:reports: moderaasjemaatregelen nimme yn rapportaazjes crypto: ein-ta-ein-fersifering brûke follow: relaasjes tusken accounts bewurkje - push: dyn pushmeldingen ûntfange - read: alle gegevens fan dyn account lêze + push: jo pushmeldingen ûntfange + read: alle gegevens fan jo account lêze read:accounts: accountynformaasje besjen - read:blocks: dyn blokkearre brûkers besjen - read:bookmarks: dyn blêdwizers besjen + read:blocks: jo blokkearre brûkers besjen + read:bookmarks: jo blêdwizers besjen read:favourites: jo favoriten besjen - read:filters: dyn filters besjen + read:filters: jo filters besjen read:follows: de accounts dy’tsto folgest besjen - read:lists: dyn listen besjen - read:mutes: dyn negearre brûkers besjen - read:notifications: dyn meldingen besjen - read:reports: dyn rapportearre berjochten besjen - read:search: út dyn namme sykje + read:lists: jo listen besjen + read:mutes: jo negearre brûkers besjen + read:notifications: jo meldingen besjen + read:reports: jo rapportearre berjochten besjen + read:search: út jo namme sykje read:statuses: alle berjochten besjen - write: alle gegevens fan dyn account bewurkje - write:accounts: dyn profyl bewurkje + write: alle gegevens fan jo account bewurkje + write:accounts: jo profyl bewurkje write:blocks: accounts en domeinen blokkearje write:bookmarks: berjochten oan blêdwizers tafoegje write:conversations: petearen negearre en fuortsmite diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml index acfd59b3e77011..91c9430b3029c5 100644 --- a/config/locales/doorkeeper.sk.yml +++ b/config/locales/doorkeeper.sk.yml @@ -129,6 +129,7 @@ sk: crypto: Šifrovanie End-to-end favourites: Obľúbené filters: Filtre + follow: Sledovanie, stlmenie a blokovanie follows: Sledovania lists: Zoznamy media: Mediálne prílohy @@ -148,9 +149,19 @@ sk: scopes: admin:read: prezeraj všetky dáta na serveri admin:read:accounts: prezeraj chúlostivé informácie na všetkých účtoch + admin:read:canonical_email_blocks: čítať citlivé informácie všetkých kanonických e-mailových blokov + admin:read:domain_allows: čítať citlivé informácie zo všetkých povolených domén + admin:read:domain_blocks: čítať citlivé informácie zo všetkých blokov domén + admin:read:email_domain_blocks: čítať citlivé informácie zo všetkých blokov emailových domén + admin:read:ip_blocks: čítať citlivé informácie zo všetkých blokov IP admin:read:reports: čítaj chulostivé informácie o všetkých hláseniach a nahlásených účtoch admin:write: uprav všetky dáta na serveri admin:write:accounts: urob moderovacie úkony na účtoch + admin:write:canonical_email_blocks: vykonať akcie moderácie na kanonických emailových blokoch + admin:write:domain_allows: vykonať akcie moderácie na povolených doménach + admin:write:domain_blocks: vykonať akcie moderácie na doménových blokoch + admin:write:email_domain_blocks: vykonať akcie moderácie na blokoch emailových domén + admin:write:ip_blocks: vykonať akcie moderácie na blokoch IP admin:write:reports: urob moderovacie úkony voči hláseniam crypto: používať end-to-end šifrovanie follow: uprav vzťahy svojho účtu @@ -159,6 +170,7 @@ sk: read:accounts: prezri si informácie o účte read:blocks: prezri svoje bloky read:bookmarks: pozri svoje záložky + read:favourites: zobraziť vaše obľúbené read:filters: prezri svoje filtrovanie read:follows: prezri si svoje sledovania read:lists: prezri si svoje zoznamy diff --git a/config/locales/fi.yml b/config/locales/fi.yml index cdc5d12678730e..09d2a68779e882 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -33,7 +33,7 @@ fi: accounts: add_email_domain_block: Estä sähköpostiverkkotunnus approve: Hyväksy - approved_msg: Käyttäjän %{username} liittymishakemus hyväksyttiin + approved_msg: Käyttäjän %{username} rekisteröitymishakemus hyväksyttiin are_you_sure: Oletko varma? avatar: Profiilikuva by_domain: Verkkotunnus @@ -364,7 +364,7 @@ fi: other: "%{count} odottavaa käyttäjää" resolved_reports: ratkaistut raportit software: Ohjelmisto - sources: Rekisteröitymisen lähteet + sources: Rekisteröitymislähteet space: Tilankäyttö title: Hallintapaneeli top_languages: Aktiivisimmat kielet @@ -440,7 +440,7 @@ fi: title: Estä uusi sähköpostiverkkotunnus no_email_domain_block_selected: Sähköpostin verkkotunnuksia ei muutettu, koska yhtään ei ollut valittuna not_permitted: Ei sallittu - resolved_dns_records_hint_html: Verkkotunnuksen nimi määräytyy seuraaviin MX-verkkotunnuksiin, jotka ovat viime kädessä vastuussa sähköpostin vastaanottamisesta. MX-verkkotunnuksen estäminen estää kirjautumisen mistä tahansa sähköpostiosoitteesta, joka käyttää samaa MX-verkkotunnusta, vaikka näkyvä verkkotunnuksen nimi olisikin erilainen. Varo estämästä suuria sähköpostin palveluntarjoajia. + resolved_dns_records_hint_html: Verkkotunnuksen nimi määräytyy seuraaviin MX-verkkotunnuksiin, jotka ovat viime kädessä vastuussa sähköpostin vastaanottamisesta. MX-verkkotunnuksen estäminen estää rekisteröitymisen mistä tahansa sähköpostiosoitteesta, joka käyttää samaa MX-verkkotunnusta, vaikka näkyvä verkkotunnuksen nimi olisikin erilainen. Varo estämästä suuria sähköpostin palveluntarjoajia. resolved_through_html: Ratkaistu %{domain} kautta title: Estetyt sähköpostiverkkotunnukset export_domain_allows: @@ -1405,7 +1405,7 @@ fi: migrations: acct: Muuttanut tunnukselle cancel: Peruuta uudelleenohjaus - cancel_explanation: Uudelleenohjauksen peruuttaminen aktivoi uudelleen nykyisen tilisi, mutta ei palauta seuraajia, jotka on siirretty kyseiselle tilille. + cancel_explanation: Uudelleenohjauksen peruuttaminen aktivoi nykyisen tilisi uudelleen mutta ei palauta seuraajia, jotka on siirretty kyseiselle tilille. cancelled_msg: Uudelleenohjaus peruttu onnistuneesti. errors: already_moved: on sama tili, jonka olet jo siirtänyt diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 9f34e5621654a9..1c187c1de1f285 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -570,7 +570,7 @@ nn: enabled: Skrudd på inbox_url: Overførings-URL pending: Avventer overgangens godkjenning - save_and_enable: Lagr og slå på + save_and_enable: Lagre og slå på setup: Sett opp en overgangsforbindelse signatures_not_enabled: Overgangar fungerer ikkje så lenge sikker- eller kvitlistingsmodus er aktivert status: Status @@ -1280,7 +1280,7 @@ nn: deselect: Vel ingen none: Ingen order_by: Sorter etter - save_changes: Lagr endringar + save_changes: Lagre endringar select_all_matching_items: one: Vel %{count} element som passar til søket ditt. other: Vel %{count} element som passar til søket ditt. diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 6c8875327bee31..403162b8203525 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -59,14 +59,14 @@ fi: setting_display_media_default: Piilota arkaluonteiseksi merkitty media setting_display_media_hide_all: Piilota media aina setting_display_media_show_all: Näytä media aina - setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin, mutta sumentavat yksityiskohdat + setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin mutta sumentavat yksityiskohdat setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse syötteen automaattisen vierityksen sijaan username: Voit käyttää kirjaimia, numeroita ja alaviivoja whole_word: Kun avainsana tai -fraasi on kokonaan aakkosnumeerinen, se on voimassa vain, jos se vastaa koko sanaa domain_allow: domain: Tämä verkkotunnus voi noutaa tietoja tältä palvelimelta ja sieltä saapuvat tiedot käsitellään ja tallennetaan email_domain_block: - domain: Tämä voi olla se verkkotunnus, joka näkyy sähköpostiosoitteessa tai MX tietueessa jota se käyttää. Ne tarkistetaan rekisteröitymisen yhteydessä. + domain: Tämä voi olla verkkotunnus, joka näkyy sähköpostiosoitteessa tai sen käyttämässä MX-tietueessa. Ne tarkistetaan rekisteröitymisen yhteydessä. with_dns_records: Annetun verkkotunnuksen DNS-tietueet yritetään ratkaista ja tulokset myös estetään featured_tag: name: 'Tässä muutamia hiljattain käyttämiäsi aihetunnisteita:' @@ -112,7 +112,7 @@ fi: ip: Kirjoita IPv4- tai IPv6-osoite. Voit estää kokonaisia alueita käyttämällä CIDR-syntaksia. Varo, että et lukitse itseäsi ulos! severities: no_access: Estä pääsy kaikkiin resursseihin - sign_up_block: Uudet kirjautumiset eivät ole mahdollisia + sign_up_block: Uudet rekisteröitymiset eivät ole mahdollisia sign_up_requires_approval: Uudet rekisteröitymiset edellyttävät hyväksyntääsi severity: Valitse, mitä tapahtuu tämän IP-osoitteen pyynnöille rule: diff --git a/config/locales/simple_form.fy.yml b/config/locales/simple_form.fy.yml index 474d858382a468..f506b792522054 100644 --- a/config/locales/simple_form.fy.yml +++ b/config/locales/simple_form.fy.yml @@ -86,7 +86,7 @@ fy: media_cache_retention_period: Mediabestannen dy’t fan oare servers download binne wurde nei it opjûne oantal dagen fuortsmiten en wurde op fersyk opnij download. peers_api_enabled: In list mei domeinnammen, dêr’t dizze server yn fediverse kontakt hân mei hat. Hjir wurdt gjin data dield, oft jo mei in bepaalde server federearrest, mar alinnich, dat jo server dat wit. Dit wurdt foar tsjinsten brûkt, dy’t statistiken oer federaasje yn algemiene sin sammelet. profile_directory: De brûkersgids befettet in list fan alle brûkers dy¥t derfoar keazen hawwe om ûntdekt wurde te kinnen. - require_invite_text: Meitsje it ynfoljen fan "Wêrom wolle jo jo hjir registrearje?" ferplicht yn stee fan opsjoneel, wannear’t registraasjes hânmjittich goedkard wurde moatte + require_invite_text: Meitsje it ynfoljen fan ‘Wêrom wolle jo jo hjir registrearje?’ ferplicht yn stee fan opsjoneel, wannear’t registraasjes hânmjittich goedkard wurde moatte site_contact_email: Hoe minsken jo berikke kinne foar juridyske fragen of stipe. site_contact_username: Hoe minsken jo op Mastodon berikke kinne. site_extended_description: Alle oanfoljende ynformaasje dy’t nuttich wêze kin foar besikers en jo brûkers. Kin opmakke wurde mei Markdown. diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index c48c659fa6a37b..4580e772fcb4e1 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -30,7 +30,7 @@ zh-TW: suspend: 禁止所有對該帳號任何互動,並且刪除其內容。三十天內可以撤銷此動作。關閉所有對此帳號之檢舉報告。 warning_preset_id: 選用。您仍可在預設的結尾新增自訂文字 announcement: - all_day: 核取後,只會顯示出時間範圍中的日期部分 + all_day: 當選取時,僅顯示出時間範圍中的日期部分 ends_at: 可選的,公告會於該時間點自動取消發布 scheduled_at: 空白則立即發布公告 starts_at: 可選的,讓公告在特定時間範圍內顯示 @@ -60,7 +60,7 @@ zh-TW: setting_display_media_hide_all: 總是隱藏所有媒體 setting_display_media_show_all: 總是顯示標為敏感內容的媒體 setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊 - setting_use_pending_items: 關閉自動捲動更新,時間軸只會於點擊後更新 + setting_use_pending_items: 關閉自動捲動更新,時間軸僅於點擊後更新 username: 您可以使用字幕、數字與底線 whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用 domain_allow: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 51baaf7a4e6479..190c811dfd9dea 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -40,7 +40,7 @@ zh-CN: current_email: 当前的电子邮箱 label: 更改电子邮箱 new_email: 新的电子邮箱 - submit: 更改电子邮件地址 + submit: 更改电子邮箱 title: 更改 %{username} 的电子邮箱 change_role: changed_msg: 已成功更改角色! @@ -68,8 +68,8 @@ zh-CN: enable_sign_in_token_auth: 启用电子邮件令牌认证 enabled: 已启用 enabled_msg: 成功解冻 %{username} 的账号 - followers: 关注者 - follows: 正在关注 + followers: 粉丝 + follows: 关注 header: 个人资料页横幅图片 inbox_url: 收件箱(Inbox)URL invite_request_text: 加入理由 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index e139742ff17e54..fd934815096d64 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -578,7 +578,7 @@ zh-TW: mark_as_sensitive_description_html: 被檢舉的嘟文中的媒體將會被標記為敏感內容,並將會記錄一次警告,以協助您升級同一帳號未來的違規行為。 other_description_html: 檢視更多控制帳號行為以及自訂檢舉帳號通知之選項。 resolve_description_html: 被檢舉的帳號將不被採取任何行動,不會加以刪除線標記,並且此份報告將被關閉。 - silence_description_html: 此帳號僅會對已跟隨帳號之使用者或手動查詢可見,將大幅度限制觸及範圍。此設定可隨時被還原。關閉所有對此帳號之檢舉報告。 + silence_description_html: 此帳號僅對已跟隨帳號之使用者或手動查詢可見,將大幅度限制觸及範圍。此設定可隨時被還原。關閉所有對此帳號之檢舉報告。 suspend_description_html: 此帳號及其所有內容將不可被存取並且最終被移除,並且無法與之進行互動。三十天內可以撤銷此動作。關閉所有對此帳號之檢舉報告。 actions_description_html: 決定應對此報告採取何種行動。若您對檢舉之帳號採取懲罰措施,則將對他們發送 e-mail 通知,如非選擇了 垃圾郵件 類別。 actions_description_remote_html: 決定將對此檢舉報告採取何種動作。這將僅作用於您的伺服器與此遠端帳號及其內容之通訊行為。 diff --git a/db/migrate/.rubocop.yml b/db/migrate/.rubocop.yml new file mode 100644 index 00000000000000..4e23800dd14965 --- /dev/null +++ b/db/migrate/.rubocop.yml @@ -0,0 +1,4 @@ +inherit_from: ../../.rubocop.yml + +Naming/VariableNumber: + CheckSymbols: false diff --git a/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb index 7301e960d55da7..c9f0849557d532 100644 --- a/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb +++ b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb @@ -19,7 +19,6 @@ def up # Record suspend date of blocks and silences for users whose limitations match # a domain block DomainBlock.where(severity: [:silence, :suspend]).find_each do |block| - scope = block.accounts if block.suspend? block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at) else diff --git a/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb index 615f35cd0d205c..7788431cd5af77 100644 --- a/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb +++ b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb @@ -18,7 +18,6 @@ def up # Record suspend date of blocks and silences for users whose limitations match # a domain block DomainBlock.where(severity: [:silence, :suspend]).find_each do |block| - scope = block.accounts if block.suspend? block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at) else diff --git a/package.json b/package.json index 902dd8e8785efa..a75e68414acca7 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "fuzzysort": "^2.0.4", "glob": "^10.2.6", "history": "^4.10.1", + "hoist-non-react-statics": "^3.3.2", "http-link-header": "^1.1.1", "immutable": "^4.3.0", "imports-loader": "^1.2.0", @@ -112,8 +113,8 @@ "react-overlays": "^5.2.1", "react-redux": "^8.0.4", "react-redux-loading-bar": "^5.0.4", - "react-router": "^4.3.1", - "react-router-dom": "^4.1.1", + "react-router": "^5.3.4", + "react-router-dom": "^5.3.4", "react-router-scroll-4": "^1.0.0-beta.1", "react-select": "^5.7.3", "react-sparklines": "^1.7.0", @@ -159,6 +160,7 @@ "@types/emoji-mart": "^3.0.9", "@types/escape-html": "^1.0.2", "@types/express": "^4.17.17", + "@types/hoist-non-react-statics": "^3.3.1", "@types/http-link-header": "^1.0.3", "@types/intl": "^1.2.0", "@types/jest": "^29.5.2", @@ -175,6 +177,7 @@ "@types/react-immutable-proptypes": "^2.1.0", "@types/react-motion": "^0.0.35", "@types/react-overlays": "^3.1.0", + "@types/react-router": "^5.1.20", "@types/react-router-dom": "^5.3.3", "@types/react-select": "^5.0.1", "@types/react-sparklines": "^1.7.2", diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb index 4afe074921aa3a..3f4175a28177c9 100644 --- a/spec/controllers/admin/disputes/appeals_controller_spec.rb +++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb @@ -18,10 +18,14 @@ describe 'GET #index' do let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - it 'lists appeals' do + before { appeal } + + it 'returns a page that lists details of appeals' do get :index - expect(response).to have_http_status(200) + expect(response).to have_http_status(:success) + expect(response.body).to include("#{strike.account.username}") + expect(response.body).to include("#{appeal.account.username}") end end diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb index d080475c32a835..56ffcfb047c844 100644 --- a/spec/controllers/concerns/account_controller_concern_spec.rb +++ b/spec/controllers/concerns/account_controller_concern_spec.rb @@ -62,7 +62,7 @@ def success end it 'sets link headers' do - account = Fabricate(:account, username: 'username') + Fabricate(:account, username: 'username') get 'success', params: { account_username: 'username' } expect(response.headers['Link'].to_s).to eq '; rel="lrdd"; type="application/jrd+json", ; rel="alternate"; type="application/activity+json"' end diff --git a/spec/helpers/admin/disputes_helper_spec.rb b/spec/helpers/admin/disputes_helper_spec.rb new file mode 100644 index 00000000000000..5f9a85df869140 --- /dev/null +++ b/spec/helpers/admin/disputes_helper_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::DisputesHelper do + describe 'strike_action_label' do + it 'returns html describing the appeal' do + adam = Account.new(username: 'Adam') + becky = Account.new(username: 'Becky') + strike = AccountWarning.new(account: adam, action: :suspend) + appeal = Appeal.new(strike: strike, account: becky) + + expected = <<~OUTPUT.strip + Adam suspended Becky's account + OUTPUT + result = helper.strike_action_label(appeal) + + expect(result).to eq(expected) + end + end +end diff --git a/spec/helpers/jsonld_helper_spec.rb b/spec/helpers/jsonld_helper_spec.rb index 1f1a7fd8918ecc..e4c1e0cec47c65 100644 --- a/spec/helpers/jsonld_helper_spec.rb +++ b/spec/helpers/jsonld_helper_spec.rb @@ -179,14 +179,14 @@ it 'deems a safe compacting as such' do json['object'].delete('convo') compacted = compact(json) - deemed_compatible = patch_for_forwarding!(json, compacted) + patch_for_forwarding!(json, compacted) expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public'] expect(safe_for_forwarding?(json, compacted)).to be true end it 'deems an unsafe compacting as such' do compacted = compact(json) - deemed_compatible = patch_for_forwarding!(json, compacted) + patch_for_forwarding!(json, compacted) expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public'] expect(safe_for_forwarding?(json, compacted)).to be false end diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb index d5b713b347ad20..97268eea6d04b2 100644 --- a/spec/lib/activitypub/linked_data_signature_spec.rb +++ b/spec/lib/activitypub/linked_data_signature_spec.rb @@ -34,6 +34,40 @@ end end + context 'when local account record is missing a public key' do + let(:raw_signature) do + { + 'creator' => 'http://example.com/alice', + 'created' => '2017-09-23T20:21:34Z', + } + end + + let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) } + + let(:service_stub) { instance_double(ActivityPub::FetchRemoteKeyService) } + + before do + # Ensure signature is computed with the old key + signature + + # Unset key + old_key = sender.public_key + sender.update!(private_key: '', public_key: '') + + allow(ActivityPub::FetchRemoteKeyService).to receive(:new).and_return(service_stub) + + allow(service_stub).to receive(:call).with('http://example.com/alice', id: false) do + sender.update!(public_key: old_key) + sender + end + end + + it 'fetches key and returns creator' do + expect(subject.verify_actor!).to eq sender + expect(service_stub).to have_received(:call).with('http://example.com/alice', id: false).once + end + end + context 'when signature is missing' do let(:signature) { nil } diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index 5ecea5ea162b4e..2c8c994712d069 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -1356,4 +1356,254 @@ def stub_parallelize_with_progress! end end end + + describe '#prune' do + let!(:local_account) { Fabricate(:account) } + let!(:bot_account) { Fabricate(:account, bot: true, domain: 'example.com') } + let!(:group_account) { Fabricate(:account, actor_type: 'Group', domain: 'example.com') } + let!(:mentioned_account) { Fabricate(:account, domain: 'example.com') } + let!(:prunable_accounts) do + Fabricate.times(3, :account, domain: 'example.com', bot: false, suspended_at: nil, silenced_at: nil) + end + + before do + Fabricate(:mention, account: mentioned_account, status: Fabricate(:status, account: Fabricate(:account))) + stub_parallelize_with_progress! + end + + it 'prunes all remote accounts with no interactions with local users' do + cli.prune + + prunable_account_ids = prunable_accounts.pluck(:id) + + expect(Account.where(id: prunable_account_ids).count).to eq(0) + end + + it 'displays a successful message' do + expect { cli.prune }.to output( + a_string_including("OK, pruned #{prunable_accounts.size} accounts") + ).to_stdout + end + + it 'does not prune local accounts' do + cli.prune + + expect(Account.exists?(id: local_account.id)).to be(true) + end + + it 'does not prune bot accounts' do + cli.prune + + expect(Account.exists?(id: bot_account.id)).to be(true) + end + + it 'does not prune group accounts' do + cli.prune + + expect(Account.exists?(id: group_account.id)).to be(true) + end + + it 'does not prune accounts that have been mentioned' do + cli.prune + + expect(Account.exists?(id: mentioned_account.id)).to be true + end + + context 'with --dry-run option' do + before do + cli.options = { dry_run: true } + end + + it 'does not prune any account' do + cli.prune + + prunable_account_ids = prunable_accounts.pluck(:id) + + expect(Account.where(id: prunable_account_ids).count).to eq(prunable_accounts.size) + end + + it 'displays a successful message with (DRY RUN)' do + expect { cli.prune }.to output( + a_string_including("OK, pruned #{prunable_accounts.size} accounts (DRY RUN)") + ).to_stdout + end + end + end + + describe '#migrate' do + let!(:source_account) { Fabricate(:account) } + let!(:target_account) { Fabricate(:account, domain: 'example.com') } + let(:arguments) { [source_account.username] } + let(:resolve_account_service) { instance_double(ResolveAccountService, call: nil) } + let(:move_service) { instance_double(MoveService, call: nil) } + + before do + allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service) + allow(MoveService).to receive(:new).and_return(move_service) + end + + shared_examples 'a successful migration' do + it 'calls the MoveService for the last migration' do + cli.invoke(:migrate, arguments, options) + + last_migration = source_account.migrations.last + + expect(move_service).to have_received(:call).with(last_migration).once + end + + it 'displays a successful message' do + expect { cli.invoke(:migrate, arguments, options) }.to output( + a_string_including("OK, migrated #{source_account.acct} to #{target_account.acct}") + ).to_stdout + end + end + + context 'when both --replay and --target options are given' do + let(:options) { { replay: true, target: "#{target_account.username}@example.com" } } + + it 'exits with an error message indicating that using both options is not possible' do + expect { cli.invoke(:migrate, arguments, options) }.to output( + a_string_including('Use --replay or --target, not both') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when no option is given' do + it 'exits with an error message indicating that at least one option must be used' do + expect { cli.invoke(:migrate, arguments, {}) }.to output( + a_string_including('Use either --replay or --target') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating that there is no such account' do + expect { cli.invoke(:migrate, arguments, replay: true) }.to output( + a_string_including("No such account: #{arguments.first}") + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'with --replay option' do + let(:options) { { replay: true } } + + context 'when the specified account has no previous migrations' do + it 'exits with an error message indicating that the given account has no previous migrations' do + expect { cli.invoke(:migrate, arguments, options) }.to output( + a_string_including('The specified account has not performed any migration') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the specified account has a previous migration' do + before do + allow(resolve_account_service).to receive(:call).with(source_account.acct, any_args).and_return(source_account) + allow(resolve_account_service).to receive(:call).with(target_account.acct, any_args).and_return(target_account) + target_account.aliases.create!(acct: source_account.acct) + source_account.migrations.create!(acct: target_account.acct) + source_account.update!(moved_to_account: target_account) + end + + it_behaves_like 'a successful migration' + + context 'when the specified account is redirecting to a different target account' do + before do + source_account.update!(moved_to_account: nil) + end + + it 'exits with an error message' do + expect { cli.invoke(:migrate, arguments, options) }.to output( + a_string_including('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'with --force option' do + let(:options) { { replay: true, force: true } } + + it_behaves_like 'a successful migration' + end + end + end + + context 'with --target option' do + let(:options) { { target: target_account.acct } } + + before do + allow(resolve_account_service).to receive(:call).with(source_account.acct, any_args).and_return(source_account) + allow(resolve_account_service).to receive(:call).with(target_account.acct, any_args).and_return(target_account) + end + + context 'when the specified target account is not found' do + before do + allow(resolve_account_service).to receive(:call).with(target_account.acct).and_return(nil) + end + + it 'exits with an error message indicating that there is no such account' do + expect { cli.invoke(:migrate, arguments, options) }.to output( + a_string_including("The specified target account could not be found: #{options[:target]}") + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the specified target account exists' do + before do + target_account.aliases.create!(acct: source_account.acct) + end + + it 'creates a migration for the specified account with the target account' do + cli.invoke(:migrate, arguments, options) + + last_migration = source_account.migrations.last + + expect(last_migration.acct).to eq(target_account.acct) + end + + it_behaves_like 'a successful migration' + end + + context 'when the migration record is invalid' do + it 'exits with an error indicating that the validation failed' do + expect { cli.invoke(:migrate, arguments, options) }.to output( + a_string_including('Error: Validation failed') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the specified account is redirecting to a different target account' do + before do + allow(Account).to receive(:find_local).with(source_account.username).and_return(source_account) + allow(source_account).to receive(:moved_to_account_id).and_return(-1) + end + + it 'exits with an error message' do + expect { cli.invoke(:migrate, arguments, options) }.to output( + a_string_including('The specified account is redirecting to a different target account. Use --force if you want to change the migration target') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'with --target and --force options' do + let(:options) { { target: target_account.acct, force: true } } + + before do + target_account.aliases.create!(acct: source_account.acct) + allow(Account).to receive(:find_local).with(source_account.username).and_return(source_account) + allow(source_account).to receive(:moved_to_account_id).and_return(-1) + end + + it_behaves_like 'a successful migration' + end + end + end end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 5cdc31ecd94358..68f6ec485f03b0 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -557,7 +557,7 @@ end it 'does not return suspended users' do - match = Fabricate( + Fabricate( :account, display_name: 'Display Name', username: 'username', @@ -684,7 +684,7 @@ end it 'does not return non-followed accounts' do - match = Fabricate( + Fabricate( :account, display_name: 'A & l & i & c & e', username: 'username', @@ -696,7 +696,7 @@ end it 'does not return suspended users' do - match = Fabricate( + Fabricate( :account, display_name: 'Display Name', username: 'username', @@ -736,7 +736,7 @@ end it 'does not return suspended users' do - match = Fabricate( + Fabricate( :account, display_name: 'Display Name', username: 'username', @@ -920,10 +920,10 @@ context 'when is local' do it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do - account_1 = Fabricate(:account, username: 'the_doctor') - account_2 = Fabricate.build(:account, username: 'the_Doctor') - account_2.valid? - expect(account_2).to model_have_error_on_field(:username) + _account = Fabricate(:account, username: 'the_doctor') + non_unique_account = Fabricate.build(:account, username: 'the_Doctor') + non_unique_account.valid? + expect(non_unique_account).to model_have_error_on_field(:username) end it 'is invalid if the username is reserved' do @@ -944,9 +944,9 @@ end it 'is valid if we are creating a possibly-conflicting instance actor account' do - account_1 = Fabricate(:account, username: 'examplecom') - account_2 = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com') - expect(account_2.valid?).to be true + _account = Fabricate(:account, username: 'examplecom') + instance_account = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com') + expect(instance_account.valid?).to be true end it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do @@ -1078,17 +1078,17 @@ describe 'remote' do it 'returns an array of accounts who have a domain' do - account_1 = Fabricate(:account, domain: nil) - account_2 = Fabricate(:account, domain: 'example.com') - expect(described_class.remote).to contain_exactly(account_2) + _account = Fabricate(:account, domain: nil) + account_with_domain = Fabricate(:account, domain: 'example.com') + expect(described_class.remote).to contain_exactly(account_with_domain) end end describe 'local' do it 'returns an array of accounts who do not have a domain' do - account_1 = Fabricate(:account, domain: nil) - account_2 = Fabricate(:account, domain: 'example.com') - expect(described_class.where('id > 0').local).to contain_exactly(account_1) + local_account = Fabricate(:account, domain: nil) + _account_with_domain = Fabricate(:account, domain: 'example.com') + expect(described_class.where('id > 0').local).to contain_exactly(local_account) end end @@ -1112,17 +1112,17 @@ describe 'silenced' do it 'returns an array of accounts who are silenced' do - account_1 = Fabricate(:account, silenced: true) - account_2 = Fabricate(:account, silenced: false) - expect(described_class.silenced).to contain_exactly(account_1) + silenced_account = Fabricate(:account, silenced: true) + _account = Fabricate(:account, silenced: false) + expect(described_class.silenced).to contain_exactly(silenced_account) end end describe 'suspended' do it 'returns an array of accounts who are suspended' do - account_1 = Fabricate(:account, suspended: true) - account_2 = Fabricate(:account, suspended: false) - expect(described_class.suspended).to contain_exactly(account_1) + suspended_account = Fabricate(:account, suspended: true) + _account = Fabricate(:account, suspended: false) + expect(described_class.suspended).to contain_exactly(suspended_account) end end diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb index 67f53fa7851349..d595441fd30a8d 100644 --- a/spec/models/domain_block_spec.rb +++ b/spec/models/domain_block_spec.rb @@ -11,10 +11,10 @@ end it 'is invalid if the same normalized domain already exists' do - domain_block_1 = Fabricate(:domain_block, domain: 'にゃん') - domain_block_2 = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b') - domain_block_2.valid? - expect(domain_block_2).to model_have_error_on_field(:domain) + _domain_block = Fabricate(:domain_block, domain: 'にゃん') + domain_block_with_normalized_value = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b') + domain_block_with_normalized_value.valid? + expect(domain_block_with_normalized_value).to model_have_error_on_field(:domain) end end diff --git a/spec/models/preview_card_spec.rb b/spec/models/preview_card_spec.rb new file mode 100644 index 00000000000000..a17c7532e9eeb3 --- /dev/null +++ b/spec/models/preview_card_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PreviewCard do + describe 'validations' do + describe 'urls' do + it 'allows http schemes' do + record = described_class.new(url: 'http://example.host/path') + + expect(record).to be_valid + end + + it 'allows https schemes' do + record = described_class.new(url: 'https://example.host/path') + + expect(record).to be_valid + end + + it 'does not allow javascript: schemes' do + record = described_class.new(url: 'javascript:alert()') + + expect(record).to_not be_valid + expect(record).to model_have_error_on_field(:url) + end + end + end +end diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 136b30193333e5..153d6db25c2242 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -341,7 +341,7 @@ describe '#replies_count' do it 'is the number of replies' do - reply = Fabricate(:status, account: bob, thread: subject) + Fabricate(:status, account: bob, thread: subject) expect(subject.replies_count).to eq 1 end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index bb61c02a637ce1..92ce87e3696329 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -55,17 +55,17 @@ describe 'scopes' do describe 'recent' do it 'returns an array of recent users ordered by id' do - user_1 = Fabricate(:user) - user_2 = Fabricate(:user) - expect(described_class.recent).to eq [user_2, user_1] + first_user = Fabricate(:user) + second_user = Fabricate(:user) + expect(described_class.recent).to eq [second_user, first_user] end end describe 'confirmed' do it 'returns an array of users who are confirmed' do - user_1 = Fabricate(:user, confirmed_at: nil) - user_2 = Fabricate(:user, confirmed_at: Time.zone.now) - expect(described_class.confirmed).to contain_exactly(user_2) + Fabricate(:user, confirmed_at: nil) + confirmed_user = Fabricate(:user, confirmed_at: Time.zone.now) + expect(described_class.confirmed).to contain_exactly(confirmed_user) end end diff --git a/spec/models/webauthn_credentials_spec.rb b/spec/models/webauthn_credentials_spec.rb index 4579ebb82e99a4..9631245e11bbc7 100644 --- a/spec/models/webauthn_credentials_spec.rb +++ b/spec/models/webauthn_credentials_spec.rb @@ -37,7 +37,7 @@ end it 'is invalid if already exist a webauthn credential with the same external id' do - existing_webauthn_credential = Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw') + Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw') new_webauthn_credential = Fabricate.build(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw') new_webauthn_credential.valid? @@ -47,7 +47,7 @@ it 'is invalid if user already registered a webauthn credential with the same nickname' do user = Fabricate(:user) - existing_webauthn_credential = Fabricate(:webauthn_credential, user_id: user.id, nickname: 'USB Key') + Fabricate(:webauthn_credential, user_id: user.id, nickname: 'USB Key') new_webauthn_credential = Fabricate.build(:webauthn_credential, user_id: user.id, nickname: 'USB Key') new_webauthn_credential.valid? diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb index 1268b36f8ae08a..e1455fe799a068 100644 --- a/spec/requests/api/v1/apps/credentials_spec.rb +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -9,7 +9,8 @@ end context 'with an oauth token' do - let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) } + let(:application) { Fabricate(:application, scopes: 'read') } + let(:token) { Fabricate(:accessible_access_token, application: application) } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } it 'returns the app information correctly', :aggregate_failures do @@ -21,7 +22,35 @@ a_hash_including( name: token.application.name, website: token.application.website, - vapid_key: Rails.configuration.x.vapid_public_key + vapid_key: Rails.configuration.x.vapid_public_key, + scopes: token.application.scopes.map(&:to_s), + client_id: token.application.uid + ) + ) + end + end + + context 'with a non-read scoped oauth token' do + let(:application) { Fabricate(:application, scopes: 'admin:write') } + let(:token) { Fabricate(:accessible_access_token, application: application) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the app information correctly' do + subject + + expect(body_as_json).to match( + a_hash_including( + 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 ) ) end @@ -36,5 +65,49 @@ expect(response).to have_http_status(401) end end + + context 'with a revoked oauth token' do + let(:application) { Fabricate(:application, scopes: 'read') } + let(:token) { Fabricate(:accessible_access_token, application: application, revoked_at: DateTime.now.utc) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it 'returns http authorization error' do + subject + + expect(response).to have_http_status(401) + end + + it 'returns the error in the json response' do + subject + + expect(body_as_json).to match( + a_hash_including( + error: 'The access token was revoked' + ) + ) + end + end + + context 'with an invalid oauth token' do + let(:application) { Fabricate(:application, scopes: 'read') } + let(:token) { Fabricate(:accessible_access_token, application: application) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}-invalid" } } + + it 'returns http authorization error' do + subject + + expect(response).to have_http_status(401) + end + + it 'returns the error in the json response' do + subject + + expect(body_as_json).to match( + a_hash_including( + error: 'The access token is invalid' + ) + ) + end + end end end diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb index 1cd036f4848878..4f89cd220c8df8 100644 --- a/spec/services/account_search_service_spec.rb +++ b/spec/services/account_search_service_spec.rb @@ -56,7 +56,7 @@ service = instance_double(ResolveAccountService, call: nil) allow(ResolveAccountService).to receive(:new).and_return(service) - results = subject.call('newuser@remote.com', nil, limit: 10, resolve: true) + subject.call('newuser@remote.com', nil, limit: 10, resolve: true) expect(service).to have_received(:call).with('newuser@remote.com') end @@ -64,14 +64,14 @@ service = instance_double(ResolveAccountService, call: nil) allow(ResolveAccountService).to receive(:new).and_return(service) - results = subject.call('newuser@remote.com', nil, limit: 10, resolve: false) + subject.call('newuser@remote.com', nil, limit: 10, resolve: false) expect(service).to_not have_received(:call) end end it 'returns the fuzzy match first, and does not return suspended exacts' do partial = Fabricate(:account, username: 'exactness') - exact = Fabricate(:account, username: 'exact', suspended: true) + Fabricate(:account, username: 'exact', suspended: true) results = subject.call('exact', nil, limit: 10) expect(results.size).to eq 1 @@ -79,7 +79,7 @@ end it 'does not return suspended remote accounts' do - remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true) + Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true) results = subject.call('a@example.com', nil, limit: 2) expect(results.size).to eq 0 diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index a1a242a51752c4..e27baf13a91673 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -307,7 +307,7 @@ it 'processes duplicate mentions correctly' do account = Fabricate(:account) - mentioned_account = Fabricate(:account, username: 'alice') + Fabricate(:account, username: 'alice') expect do subject.call(account, text: '@alice @alice @alice hey @alice') @@ -377,7 +377,7 @@ account = Fabricate(:account) media = Fabricate(:media_attachment, account: Fabricate(:account)) - status = subject.call( + subject.call( account, text: 'test status update', media_ids: [media.id] @@ -458,7 +458,7 @@ it 'hit ng words for mention' do account = Fabricate(:account) - mentioned = Fabricate(:account, username: 'ohagi', domain: nil) + Fabricate(:account, username: 'ohagi', domain: nil) text = 'ng word test @ohagi' Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save @@ -467,7 +467,7 @@ it 'hit ng words for mention but local posts are not checked' do account = Fabricate(:account) - mentioned = Fabricate(:account, username: 'ohagi', domain: nil) + Fabricate(:account, username: 'ohagi', domain: nil) text = 'ng word test @ohagi' Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb index 54e0d94ee0a80e..663babae8a9c02 100644 --- a/spec/services/precompute_feed_service_spec.rb +++ b/spec/services/precompute_feed_service_spec.rb @@ -27,7 +27,7 @@ muted_account = Fabricate(:account) Fabricate(:mute, account: account, target_account: muted_account) reblog = Fabricate(:status, account: muted_account) - status = Fabricate(:status, account: account, reblog: reblog) + Fabricate(:status, account: account, reblog: reblog) subject.call(account) diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb index 7991aa6ef14383..bcfb9dbfb0f893 100644 --- a/spec/services/resolve_url_service_spec.rb +++ b/spec/services/resolve_url_service_spec.rb @@ -7,8 +7,8 @@ describe '#call' do it 'returns nil when there is no resource url' do - url = 'http://example.com/missing-resource' - known_account = Fabricate(:account, uri: url, domain: 'example.com') + url = 'http://example.com/missing-resource' + Fabricate(:account, uri: url, domain: 'example.com') service = instance_double(FetchResourceService) allow(FetchResourceService).to receive(:new).and_return service diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 95bed8f59b80b3..b81be752ca8a89 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,7 +44,7 @@ config.display_try_failure_messages = true # Use the GitHub Annotations formatter for CI - if ENV['GITHUB_ACTIONS'] == 'true' + if ENV['GITHUB_ACTIONS'] == 'true' && ENV['GITHUB_RSPEC'] == 'true' require 'rspec/github' config.add_formatter RSpec::Github::Formatter end diff --git a/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb b/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb new file mode 100644 index 00000000000000..82a1dee6d72feb --- /dev/null +++ b/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'admin/trends/links/_preview_card.html.haml' do + it 'correctly escapes user supplied url values' do + form = instance_double(ActionView::Helpers::FormHelper, check_box: nil) + trend = PreviewCardTrend.new(allowed: false) + preview_card = Fabricate.build( + :preview_card, + url: 'https://host.example/path?query=