Merge remote-tracking branch 'parent/main' into upstream-20230105
This commit is contained in:
commit
a0a3d1b101
65 changed files with 1008 additions and 453 deletions
|
@ -40,7 +40,7 @@ module Admin
|
|||
(@email_domain_block.other_domains || []).uniq.each do |domain|
|
||||
next if EmailDomainBlock.where(domain: domain).exists?
|
||||
|
||||
other_email_domain_block = EmailDomainBlock.create!(domain: domain, parent: @email_domain_block)
|
||||
other_email_domain_block = EmailDomainBlock.create!(domain: domain, allow_with_approval: @email_domain_block.allow_with_approval, parent: @email_domain_block)
|
||||
log_action :create, other_email_domain_block
|
||||
end
|
||||
end
|
||||
|
@ -65,7 +65,7 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:email_domain_block).permit(:domain, other_domains: [])
|
||||
params.require(:email_domain_block).permit(:domain, :allow_with_approval, other_domains: [])
|
||||
end
|
||||
|
||||
def form_email_domain_block_batch_params
|
||||
|
|
|
@ -68,7 +68,7 @@ module Admin
|
|||
|
||||
def export_data
|
||||
CSV.generate(headers: export_headers, write_headers: true) do |content|
|
||||
DomainBlock.with_limitations.each do |instance|
|
||||
DomainBlock.with_limitations.order(id: :asc).each do |instance|
|
||||
content << [instance.domain, instance.severity, instance.reject_media, instance.reject_reports, instance.public_comment, instance.obfuscate]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,7 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:domain)
|
||||
params.permit(:domain, :allow_with_approval)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
|
|
@ -91,14 +91,23 @@ module SignatureVerification
|
|||
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
|
||||
|
||||
signature = Base64.decode64(signature_params['signature'])
|
||||
compare_signed_string = build_signed_string
|
||||
compare_signed_string = build_signed_string(include_query_string: true)
|
||||
|
||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||
|
||||
# Compatibility quirk with older Mastodon versions
|
||||
compare_signed_string = build_signed_string(include_query_string: false)
|
||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||
|
||||
actor = stoplight_wrap_request { actor_refresh_key!(actor) }
|
||||
|
||||
raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
|
||||
|
||||
compare_signed_string = build_signed_string(include_query_string: true)
|
||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||
|
||||
# Compatibility quirk with older Mastodon versions
|
||||
compare_signed_string = build_signed_string(include_query_string: false)
|
||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||
|
||||
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
|
||||
|
@ -180,11 +189,18 @@ module SignatureVerification
|
|||
nil
|
||||
end
|
||||
|
||||
def build_signed_string
|
||||
def build_signed_string(include_query_string: true)
|
||||
signed_headers.map do |signed_header|
|
||||
case signed_header
|
||||
when Request::REQUEST_TARGET
|
||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
||||
if include_query_string
|
||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
|
||||
else
|
||||
# Current versions of Mastodon incorrectly omit the query string from the (request-target) pseudo-header.
|
||||
# Therefore, temporarily support such incorrect signatures for compatibility.
|
||||
# TODO: remove eventually some time after release of the fixed version
|
||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
||||
end
|
||||
when '(created)'
|
||||
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
|
||||
raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
|
||||
|
|
|
@ -21,7 +21,7 @@ module WellKnown
|
|||
username = username_from_resource
|
||||
|
||||
@account = begin
|
||||
if username == Rails.configuration.x.local_domain
|
||||
if username == Rails.configuration.x.local_domain || username == Rails.configuration.x.web_domain
|
||||
Account.representative
|
||||
else
|
||||
Account.find_local!(username)
|
||||
|
|
|
@ -654,16 +654,20 @@ class Status extends ImmutablePureComponent {
|
|||
));
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
setContainerRef = c => {
|
||||
this.node = c;
|
||||
};
|
||||
|
||||
setStatusRef = c => {
|
||||
this.statusNode = c;
|
||||
};
|
||||
|
||||
_scrollStatusIntoView () {
|
||||
const { status, multiColumn } = this.props;
|
||||
|
||||
if (status) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.node?.querySelector('.detailed-status__wrapper')?.scrollIntoView(true);
|
||||
requestIdleCallback(() => {
|
||||
this.statusNode?.scrollIntoView(true);
|
||||
|
||||
// In the single-column interface, `scrollIntoView` will put the post behind the header,
|
||||
// so compensate for that.
|
||||
|
@ -701,9 +705,8 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
// Scroll to focused post if it is loaded
|
||||
const child = this.node?.querySelector('.detailed-status__wrapper');
|
||||
if (child) {
|
||||
return [0, child.offsetTop];
|
||||
if (this.statusNode) {
|
||||
return [0, this.statusNode.offsetTop];
|
||||
}
|
||||
|
||||
// Do not scroll otherwise, `componentDidUpdate` will take care of that
|
||||
|
@ -768,12 +771,12 @@ class Status extends ImmutablePureComponent {
|
|||
/>
|
||||
|
||||
<ScrollContainer scrollKey='thread' shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setContainerRef}>
|
||||
{references}
|
||||
{ancestors}
|
||||
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)}>
|
||||
<div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)} ref={this.setStatusRef}>
|
||||
<DetailedStatus
|
||||
key={`details-${status.get('id')}`}
|
||||
status={status}
|
||||
|
|
|
@ -38,12 +38,14 @@
|
|||
"confirmation_modal.cancel": "Cancellar",
|
||||
"confirmations.delete.confirm": "Deler",
|
||||
"confirmations.delete_list.confirm": "Deler",
|
||||
"confirmations.edit.confirm": "Modificar",
|
||||
"confirmations.logout.confirm": "Clauder le session",
|
||||
"copy_icon_button.copied": "Copiate al area de transferentia",
|
||||
"copypaste.copy_to_clipboard": "Copiar al area de transferentia",
|
||||
"disabled_account_banner.account_settings": "Parametros de conto",
|
||||
"dismissable_banner.dismiss": "Dimitter",
|
||||
"emoji_button.activity": "Activitate",
|
||||
"emoji_button.clear": "Rader",
|
||||
"emoji_button.custom": "Personalisate",
|
||||
"emoji_button.search_results": "Resultatos de recerca",
|
||||
"empty_column.account_unavailable": "Profilo non disponibile",
|
||||
|
@ -69,16 +71,37 @@
|
|||
"navigation_bar.about": "A proposito de",
|
||||
"navigation_bar.advanced_interface": "Aperir in un interfacie web avantiate",
|
||||
"navigation_bar.blocks": "Usatores blocate",
|
||||
"navigation_bar.discover": "Discoperir",
|
||||
"navigation_bar.edit_profile": "Modificar profilo",
|
||||
"navigation_bar.favourites": "Favoritos",
|
||||
"navigation_bar.lists": "Listas",
|
||||
"navigation_bar.logout": "Clauder le session",
|
||||
"navigation_bar.preferences": "Preferentias",
|
||||
"navigation_bar.search": "Cercar",
|
||||
"navigation_bar.security": "Securitate",
|
||||
"notifications.column_settings.alert": "Notificationes de scriptorio",
|
||||
"notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias",
|
||||
"notifications.column_settings.sound": "Reproducer sono",
|
||||
"notifications.filter.all": "Toto",
|
||||
"notifications.grant_permission": "Conceder permission.",
|
||||
"notifications.group": "{count} notificationes",
|
||||
"onboarding.compose.template": "Salute #Mastodon!",
|
||||
"onboarding.profile.save_and_continue": "Salvar e continuar",
|
||||
"onboarding.share.title": "Compartir tu profilo"
|
||||
"onboarding.share.title": "Compartir tu profilo",
|
||||
"onboarding.steps.share_profile.title": "Compartir tu profilo de Mastodon",
|
||||
"relative_time.just_now": "ora",
|
||||
"relative_time.today": "hodie",
|
||||
"reply_indicator.cancel": "Cancellar",
|
||||
"report.next": "Sequente",
|
||||
"report.placeholder": "Commentos additional",
|
||||
"report.reasons.dislike": "Non me place",
|
||||
"search.quick_action.go_to_account": "Vader al profilo {x}",
|
||||
"search_results.accounts": "Profilos",
|
||||
"search_results.see_all": "Vider toto",
|
||||
"status.delete": "Deler",
|
||||
"status.share": "Compartir",
|
||||
"status.translate": "Traducer",
|
||||
"status.translated_from_with": "Traducite ab {lang} usante {provider}",
|
||||
"tabs_bar.home": "Initio",
|
||||
"tabs_bar.notifications": "Notificationes"
|
||||
}
|
||||
|
|
|
@ -501,6 +501,7 @@
|
|||
"onboarding.steps.setup_profile.title": "Personaliza tu profil",
|
||||
"onboarding.steps.share_profile.body": "Informe a tus amigos komo toparte en Mastodon",
|
||||
"onboarding.steps.share_profile.title": "Partaja tu profil de Mastodon",
|
||||
"password_confirmation.mismatching": "Los dos kodes son desferentes",
|
||||
"picture_in_picture.restore": "Restora",
|
||||
"poll.closed": "Serrado",
|
||||
"poll.refresh": "Arefreska",
|
||||
|
|
|
@ -606,7 +606,7 @@
|
|||
"search.quick_action.status_search": "Innlegg som samsvarer med {x}",
|
||||
"search.search_or_paste": "Søk eller lim inn URL",
|
||||
"search_popout.full_text_search_disabled_message": "Ikkje tilgjengeleg på {domain}.",
|
||||
"search_popout.full_text_search_logged_out_message": "Bare tilgjengelig ved innlogging.",
|
||||
"search_popout.full_text_search_logged_out_message": "Bare tilgjengelig når man er logget inn.",
|
||||
"search_popout.language_code": "ISO-språkkode",
|
||||
"search_popout.options": "Søkjealternativ",
|
||||
"search_popout.quick_actions": "Hurtighandlinger",
|
||||
|
|
|
@ -606,7 +606,7 @@
|
|||
"search.quick_action.status_search": "Innlegg som samsvarer med {x}",
|
||||
"search.search_or_paste": "Søk eller lim inn URL",
|
||||
"search_popout.full_text_search_disabled_message": "Ikke tilgjengelig på {domain}.",
|
||||
"search_popout.full_text_search_logged_out_message": "Bare tilgjengelig ved innlogging.",
|
||||
"search_popout.full_text_search_logged_out_message": "Bare tilgjengelig når man er logget inn.",
|
||||
"search_popout.language_code": "ISO språkkode",
|
||||
"search_popout.options": "Alternativer for søk",
|
||||
"search_popout.quick_actions": "Hurtighandlinger",
|
||||
|
|
|
@ -77,6 +77,7 @@ class Request
|
|||
@url = Addressable::URI.parse(url).normalize
|
||||
@http_client = options.delete(:http_client)
|
||||
@allow_local = options.delete(:allow_local)
|
||||
@full_path = options.delete(:with_query_string)
|
||||
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
|
||||
@options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT)
|
||||
@options = @options.merge(proxy_url) if use_proxy?
|
||||
|
@ -146,7 +147,7 @@ class Request
|
|||
private
|
||||
|
||||
def set_common_headers!
|
||||
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
|
||||
@headers[REQUEST_TARGET] = request_target
|
||||
@headers['User-Agent'] = Mastodon::Version.user_agent
|
||||
@headers['Host'] = @url.host
|
||||
@headers['Date'] = Time.now.utc.httpdate
|
||||
|
@ -157,6 +158,14 @@ class Request
|
|||
@headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
|
||||
end
|
||||
|
||||
def request_target
|
||||
if @url.query.nil? || !@full_path
|
||||
"#{@verb} #{@url.path}"
|
||||
else
|
||||
"#{@verb} #{@url.path}?#{@url.query}"
|
||||
end
|
||||
end
|
||||
|
||||
def signature
|
||||
algorithm = 'rsa-sha256'
|
||||
signature = Base64.strict_encode64(@keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string))
|
||||
|
|
|
@ -11,11 +11,12 @@ module Attachmentable
|
|||
# For some file extensions, there exist different content
|
||||
# type variants, and browsers often send the wrong one,
|
||||
# for example, sending an audio .ogg file as video/ogg,
|
||||
# likewise, MimeMagic also misreports them as such. For
|
||||
# likewise, kt-paperclip also misreports them as such. For
|
||||
# those files, it is necessary to use the output of the
|
||||
# `file` utility instead
|
||||
INCORRECT_CONTENT_TYPES = %w(
|
||||
audio/vorbis
|
||||
audio/opus
|
||||
video/ogg
|
||||
video/webm
|
||||
).freeze
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
#
|
||||
# Table name: email_domain_blocks
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# domain :string default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# parent_id :bigint(8)
|
||||
# id :bigint(8) not null, primary key
|
||||
# domain :string default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# parent_id :bigint(8)
|
||||
# allow_with_approval :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class EmailDomainBlock < ApplicationRecord
|
||||
|
@ -42,8 +43,8 @@ class EmailDomainBlock < ApplicationRecord
|
|||
@attempt_ip = attempt_ip
|
||||
end
|
||||
|
||||
def match?
|
||||
blocking? || invalid_uri?
|
||||
def match?(...)
|
||||
blocking?(...) || invalid_uri?
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -52,8 +53,8 @@ class EmailDomainBlock < ApplicationRecord
|
|||
@uris.any?(&:nil?)
|
||||
end
|
||||
|
||||
def blocking?
|
||||
blocks = EmailDomainBlock.where(domain: domains_with_variants).order(Arel.sql('char_length(domain) desc'))
|
||||
def blocking?(allow_with_approval: false)
|
||||
blocks = EmailDomainBlock.where(domain: domains_with_variants, allow_with_approval: allow_with_approval).order(Arel.sql('char_length(domain) desc'))
|
||||
blocks.each { |block| block.history.add(@attempt_ip) } if @attempt_ip.present?
|
||||
blocks.any?
|
||||
end
|
||||
|
@ -86,4 +87,8 @@ class EmailDomainBlock < ApplicationRecord
|
|||
def self.block?(domain_or_domains, attempt_ip: nil)
|
||||
Matcher.new(domain_or_domains, attempt_ip: attempt_ip).match?
|
||||
end
|
||||
|
||||
def self.requires_approval?(domain_or_domains, attempt_ip: nil)
|
||||
Matcher.new(domain_or_domains, attempt_ip: attempt_ip).match?(allow_with_approval: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -420,7 +420,7 @@ class User < ApplicationRecord
|
|||
|
||||
def set_approved
|
||||
self.approved = begin
|
||||
if sign_up_from_ip_requires_approval?
|
||||
if sign_up_from_ip_requires_approval? || sign_up_email_requires_approval?
|
||||
false
|
||||
else
|
||||
open_registrations? || valid_invitation? || external?
|
||||
|
@ -432,6 +432,12 @@ class User < ApplicationRecord
|
|||
!sign_up_ip.nil? && IpBlock.where(severity: :sign_up_requires_approval).where('ip >>= ?', sign_up_ip.to_s).exists?
|
||||
end
|
||||
|
||||
def sign_up_email_requires_approval?
|
||||
return false unless email.present? || unconfirmed_email.present?
|
||||
|
||||
EmailDomainBlock.requires_approval?(email.presence || unconfirmed_email, attempt_ip: sign_up_ip)
|
||||
end
|
||||
|
||||
def open_registrations?
|
||||
Setting.registrations_mode == 'open'
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::Admin::EmailDomainBlockSerializer < ActiveModel::Serializer
|
||||
attributes :id, :domain, :created_at, :history
|
||||
attributes :id, :domain, :created_at, :history, :allow_with_approval
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
|
|
|
@ -12,3 +12,7 @@
|
|||
·
|
||||
|
||||
= t('admin.email_domain_blocks.attempts_over_week', count: email_domain_block.history.reduce(0) { |sum, day| sum + day.accounts })
|
||||
|
||||
- if email_domain_block.allow_with_approval?
|
||||
·
|
||||
= t('admin.email_domain_blocks.allow_registrations_with_approval')
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
.fields-group
|
||||
= f.input :domain, wrapper: :with_block_label, label: t('admin.email_domain_blocks.domain'), input_html: { readonly: defined?(@resolved_records) }
|
||||
|
||||
.fields-group
|
||||
= f.input :allow_with_approval, wrapper: :with_label, hint: false, label: I18n.t('admin.email_domain_blocks.allow_registrations_with_approval')
|
||||
|
||||
- if defined?(@resolved_records)
|
||||
%p.hint= t('admin.email_domain_blocks.resolved_dns_records_hint_html')
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue