Compare commits
17 commits
kb_develop
...
releases/1
Author | SHA1 | Date | |
---|---|---|---|
|
9b7d70bed1 | ||
|
f5da1c82bf | ||
|
c3aa760f55 | ||
|
5c5989c9a6 | ||
|
2f2c28789d | ||
|
bce7e2f8c1 | ||
|
ab5d9f71d4 | ||
|
f23993a9fd | ||
|
33d5ef9f16 | ||
|
0b06fb17d8 | ||
|
ad7d1fff25 | ||
|
fe85d7400a | ||
|
fc77844266 | ||
|
63f4a2d1c4 | ||
|
3f0682cb07 | ||
|
51248f5268 | ||
|
1957ec0416 |
32 changed files with 577 additions and 49 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -2,6 +2,39 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## |4.2.11] - 2024-08-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for incoming `<s>` tag ([mediaformat](https://github.com/mastodon/mastodon/pull/31375))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change logic of block/mute bypass for mentions from moderators to only apply to visible roles with moderation powers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31271))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix incorrect rate limit on PUT requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31356))
|
||||||
|
- Fix presence of `ß` in adjacent word preventing mention and hashtag matching ([adamniedzielski](https://github.com/mastodon/mastodon/pull/31122))
|
||||||
|
- Fix processing of webfinger responses with multiple `self` links ([adamniedzielski](https://github.com/mastodon/mastodon/pull/31110))
|
||||||
|
- Fix duplicate `orderedItems` in user archive's `outbox.json` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31099))
|
||||||
|
- Fix click event handling when clicking outside of an open dropdown menu ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31251))
|
||||||
|
- Fix status processing failing halfway when a remote post has a malformed `replies` attribute ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31246))
|
||||||
|
- Fix `--verbose` option of `tootctl media remove`, which was previously erroneously removed ([mjankowski](https://github.com/mastodon/mastodon/pull/30536))
|
||||||
|
- Fix division by zero on some video/GIF files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30600))
|
||||||
|
- Fix Web UI trying to save user settings despite being logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30324))
|
||||||
|
- Fix hashtag regexp matching some link anchors ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30190))
|
||||||
|
- Fix local account search on LDAP login being case-sensitive ([raucao](https://github.com/mastodon/mastodon/pull/30113))
|
||||||
|
- Fix development environment admin account not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29958))
|
||||||
|
- Fix report reason selector in moderation interface not unselecting rules when changing category ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29026))
|
||||||
|
- Fix already-invalid reports failing to resolve ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29027))
|
||||||
|
- Fix OCR when using S3/CDN for assets ([vmstan](https://github.com/mastodon/mastodon/pull/28551))
|
||||||
|
- Fix error when encountering malformed `Tag` objects from Kbin ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28235))
|
||||||
|
- Fix not all allowed image formats showing in file picker when uploading custom emoji ([june128](https://github.com/mastodon/mastodon/pull/28076))
|
||||||
|
- Fix search popout listing unusable search options when logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27918))
|
||||||
|
- Fix processing of featured collections lacking an `items` attribute ([tribela](https://github.com/mastodon/mastodon/pull/27581))
|
||||||
|
- Fix `mastodon:stats` decoration of stats rake task ([mjankowski](https://github.com/mastodon/mastodon/pull/31104))
|
||||||
|
|
||||||
## [4.2.10] - 2024-07-04
|
## [4.2.10] - 2024-07-04
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
|
@ -39,6 +39,7 @@ class DropdownMenu extends PureComponent {
|
||||||
if (this.node && !this.node.contains(e.target)) {
|
if (this.node && !this.node.contains(e.target)) {
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -433,13 +433,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
|
|
||||||
def fetch_replies(status)
|
def fetch_replies(status)
|
||||||
collection = @object['replies']
|
collection = @object['replies']
|
||||||
return if collection.nil?
|
return if collection.blank?
|
||||||
|
|
||||||
replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
|
replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
|
||||||
return unless replies.nil?
|
return unless replies.nil?
|
||||||
|
|
||||||
uri = value_or_id(collection)
|
uri = value_or_id(collection)
|
||||||
ActivityPub::FetchRepliesWorker.perform_async(status.id, uri, { 'request_id' => @options[:request_id] }) unless uri.nil?
|
ActivityPub::FetchRepliesWorker.perform_async(status.id, uri, { 'request_id' => @options[:request_id] }) unless uri.nil?
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.warn "Error fetching replies: #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_from_activity
|
def conversation_from_activity
|
||||||
|
|
|
@ -20,6 +20,6 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||||
serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
|
serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
|
||||||
serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
|
serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
|
||||||
|
|
||||||
{ '@context' => serialized_context(named_contexts, context_extensions) }.merge(serialized_hash)
|
{ '@context': serialized_context(named_contexts, context_extensions) }.merge(serialized_hash)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,8 @@ class Webfinger
|
||||||
class RedirectError < Error; end
|
class RedirectError < Error; end
|
||||||
|
|
||||||
class Response
|
class Response
|
||||||
|
ACTIVITYPUB_READY_TYPE = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze
|
||||||
|
|
||||||
attr_reader :uri
|
attr_reader :uri
|
||||||
|
|
||||||
def initialize(uri, body)
|
def initialize(uri, body)
|
||||||
|
@ -20,17 +22,28 @@ class Webfinger
|
||||||
end
|
end
|
||||||
|
|
||||||
def link(rel, attribute)
|
def link(rel, attribute)
|
||||||
links.dig(rel, attribute)
|
links.dig(rel, 0, attribute)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self_link_href
|
||||||
|
self_link.fetch('href')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def links
|
def links
|
||||||
@links ||= @json['links'].index_by { |link| link['rel'] }
|
@links ||= @json.fetch('links', []).group_by { |link| link['rel'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self_link
|
||||||
|
links.fetch('self', []).find do |link|
|
||||||
|
ACTIVITYPUB_READY_TYPE.include?(link['type'])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_response!
|
def validate_response!
|
||||||
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank?
|
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank?
|
||||||
|
raise Webfinger::Error, "Missing self link in response for #{@uri}" if self_link.blank?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ class Account < ApplicationRecord
|
||||||
INSTANCE_ACTOR_ID = -99
|
INSTANCE_ACTOR_ID = -99
|
||||||
|
|
||||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
|
USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
|
||||||
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
|
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}
|
||||||
URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
|
URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
|
||||||
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
|
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
|
||||||
USERNAME_LENGTH_LIMIT = 30
|
USERNAME_LENGTH_LIMIT = 30
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Tag < ApplicationRecord
|
||||||
HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
|
HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
|
||||||
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}"
|
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}"
|
||||||
|
|
||||||
HASHTAG_RE = %r{(?<![=/)\p{Alnum}])#(#{HASHTAG_NAME_PAT})}i
|
HASHTAG_RE = %r{(?<![=/)\p{Alnum}])#(#{HASHTAG_NAME_PAT})}
|
||||||
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
|
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
|
||||||
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/
|
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ActivityPub::FetchRemoteActorService < BaseService
|
||||||
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
|
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
|
||||||
|
|
||||||
if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
|
if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
|
||||||
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
|
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.self_link_href != @uri
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -58,8 +58,7 @@ class ActivityPub::FetchRemoteActorService < BaseService
|
||||||
@username, @domain = split_acct(webfinger.subject)
|
@username, @domain = split_acct(webfinger.subject)
|
||||||
|
|
||||||
raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
|
raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
|
||||||
|
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.self_link_href != @uri
|
||||||
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
|
|
||||||
rescue Webfinger::RedirectError => e
|
rescue Webfinger::RedirectError => e
|
||||||
raise Error, e.message
|
raise Error, e.message
|
||||||
rescue Webfinger::Error => e
|
rescue Webfinger::Error => e
|
||||||
|
|
|
@ -19,8 +19,8 @@ class BackupService < BaseService
|
||||||
|
|
||||||
def build_outbox_json!(file)
|
def build_outbox_json!(file)
|
||||||
skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer)
|
skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer)
|
||||||
skeleton['@context'] = full_context
|
skeleton[:@context] = full_context
|
||||||
skeleton['orderedItems'] = ['!PLACEHOLDER!']
|
skeleton[:orderedItems] = ['!PLACEHOLDER!']
|
||||||
skeleton = Oj.dump(skeleton)
|
skeleton = Oj.dump(skeleton)
|
||||||
prepend, append = skeleton.split('"!PLACEHOLDER!"')
|
prepend, append = skeleton.split('"!PLACEHOLDER!"')
|
||||||
add_comma = false
|
add_comma = false
|
||||||
|
@ -33,7 +33,7 @@ class BackupService < BaseService
|
||||||
|
|
||||||
file.write(statuses.map do |status|
|
file.write(statuses.map do |status|
|
||||||
item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status, use_bearcap: false), ActivityPub::ActivitySerializer)
|
item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status, use_bearcap: false), ActivityPub::ActivitySerializer)
|
||||||
item.delete('@context')
|
item.delete(:@context)
|
||||||
|
|
||||||
unless item[:type] == 'Announce' || item[:object][:attachment].blank?
|
unless item[:type] == 'Announce' || item[:object][:attachment].blank?
|
||||||
item[:object][:attachment].each do |attachment|
|
item[:object][:attachment].each do |attachment|
|
||||||
|
|
|
@ -52,7 +52,7 @@ class NotifyService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_staff?
|
def from_staff?
|
||||||
@sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role)
|
@sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role) && @sender.user_role&.highlighted? && @sender.user_role&.can?(*UserRole::Flags::CATEGORIES[:moderation])
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_self?
|
def from_self?
|
||||||
|
|
|
@ -106,8 +106,6 @@ class ResolveAccountService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_account!
|
def fetch_account!
|
||||||
return unless activitypub_ready?
|
|
||||||
|
|
||||||
with_redis_lock("resolve:#{@username}@#{@domain}") do
|
with_redis_lock("resolve:#{@username}@#{@domain}") do
|
||||||
@account = ActivityPub::FetchRemoteAccountService.new.call(actor_url, suppress_errors: @options[:suppress_errors])
|
@account = ActivityPub::FetchRemoteAccountService.new.call(actor_url, suppress_errors: @options[:suppress_errors])
|
||||||
end
|
end
|
||||||
|
@ -122,12 +120,8 @@ class ResolveAccountService < BaseService
|
||||||
@options[:skip_cache] || @account.nil? || @account.possibly_stale?
|
@options[:skip_cache] || @account.nil? || @account.possibly_stale?
|
||||||
end
|
end
|
||||||
|
|
||||||
def activitypub_ready?
|
|
||||||
['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self', 'type'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def actor_url
|
def actor_url
|
||||||
@actor_url ||= @webfinger.link('self', 'href')
|
@actor_url ||= @webfinger.self_link_href
|
||||||
end
|
end
|
||||||
|
|
||||||
def gone_from_origin?
|
def gone_from_origin?
|
||||||
|
|
|
@ -144,7 +144,7 @@ class Rack::Attack
|
||||||
API_CREATE_EMOJI_REACTION_REGEX = %r{\A/api/v1/statuses/\d+/emoji_reactions}
|
API_CREATE_EMOJI_REACTION_REGEX = %r{\A/api/v1/statuses/\d+/emoji_reactions}
|
||||||
|
|
||||||
throttle('throttle_password_change/account', limit: 10, period: 10.minutes) do |req|
|
throttle('throttle_password_change/account', limit: 10, period: 10.minutes) do |req|
|
||||||
req.warden_user_id if (req.put? && !req.path.match?(API_CREATE_EMOJI_REACTION_REGEX)) || (req.patch? && req.path_matches?('/auth'))
|
req.warden_user_id if (req.put? || req.patch?) && (req.path_matches?('/auth') || req.path_matches?('/auth/password'))
|
||||||
end
|
end
|
||||||
|
|
||||||
self.throttled_responder = lambda do |request|
|
self.throttled_responder = lambda do |request|
|
||||||
|
|
|
@ -58,7 +58,7 @@ services:
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.2.10
|
image: ghcr.io/mastodon/mastodon:v4.2.11
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec puma -C config/puma.rb
|
command: bundle exec puma -C config/puma.rb
|
||||||
|
@ -79,7 +79,7 @@ services:
|
||||||
|
|
||||||
streaming:
|
streaming:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.2.10
|
image: ghcr.io/mastodon/mastodon:v4.2.11
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: node ./streaming
|
command: node ./streaming
|
||||||
|
@ -97,7 +97,7 @@ services:
|
||||||
|
|
||||||
sidekiq:
|
sidekiq:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.2.10
|
image: ghcr.io/mastodon/mastodon:v4.2.11
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec sidekiq
|
command: bundle exec sidekiq
|
||||||
|
|
82
install/13.0/setup-imagemagick-7.sh
Normal file
82
install/13.0/setup-imagemagick-7.sh
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================ [imagemagick 7 setup script] ====================
|
||||||
|
Remove old ImageMagick
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
apt remove -y imagemagick
|
||||||
|
apt autoremove -y
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================ [imagemagick 7 setup script] ====================
|
||||||
|
Download source
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git clone https://github.com/ImageMagick/ImageMagick.git ImageMagick
|
||||||
|
cd ImageMagick
|
||||||
|
git checkout $(git tag -l | grep -E '^7' | sort -V | tail -n 1)
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================ [imagemagick 7 setup script] ====================
|
||||||
|
Install dependent packages
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
apt update
|
||||||
|
apt install -y \
|
||||||
|
libjpeg-dev libpng-dev libpng16-16 libltdl-dev libheif-dev libraw-dev libtiff-dev libopenjp2-tools \
|
||||||
|
libopenjp2-7-dev libjpeg-turbo-progs libfreetype6-dev libheif-dev libfreetype6-dev libopenexr-dev \
|
||||||
|
libwebp-dev libgif-dev
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================ [imagemagick 7 setup script] ====================
|
||||||
|
Configure
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
./configure --with-modules --enable-file-type --with-quantum-depth=32 --with-jpeg=yes --with-png=yes \
|
||||||
|
--with-gif=yes --with-webp=yes --with-heic=yes --with-raw=yes --with-tiff=yes --with-openjp2 \
|
||||||
|
--with-freetype=yes --with-webp=yes --with-openexr=yes --with-gslib=yes --with-gif=yes --with-perl=yes \
|
||||||
|
--with-jxl=yes
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================ [imagemagick 7 setup script] ====================
|
||||||
|
Make
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
make
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================ [imagemagick 7 setup script] ====================
|
||||||
|
Make install
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
make install
|
||||||
|
ldconfig /usr/local/lib
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
=========== [imagemagick 7 setup script completed] ===============
|
||||||
|
ImageMagick 7 setup is completed!
|
||||||
|
Please check AVIF format on your Mastodon.
|
||||||
|
|
||||||
|
To check ImageMagick version:
|
||||||
|
exec bash
|
||||||
|
convert -version
|
||||||
|
|
||||||
|
Or
|
||||||
|
sudo su - mastodon
|
||||||
|
convert -version
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
209
install/13.0/setup1.sh
Executable file
209
install/13.0/setup1.sh
Executable file
|
@ -0,0 +1,209 @@
|
||||||
|
VERSION=13.0
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
Hello, new kmyblue admin.
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
INPUT kmyblue version for install
|
||||||
|
|
||||||
|
- lts : [RECOMMENDED] The long time support version
|
||||||
|
- latest: The latest version
|
||||||
|
|
||||||
|
- debug : [deprecated] The version in development
|
||||||
|
- abort : Abort the setup script
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
KMYBLUE_VERSION=unset
|
||||||
|
until [ "$KMYBLUE_VERSION" == "lts" ] || [ "$KMYBLUE_VERSION" == "latest" ] || [ "$KMYBLUE_VERSION" == "debug" ] || [ "$KMYBLUE_VERSION" == "abort" ]
|
||||||
|
do
|
||||||
|
echo -n "kmyblue version for install [lts/latest/debug/abort]: "
|
||||||
|
read KMYBLUE_VERSION
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$KMYBLUE_VERSION" == "abort" ]; then
|
||||||
|
echo Good bye.
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
apt updates and upgrades
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
apt update && apt upgrade -y
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Install basis softwares
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
apt install -y curl wget gnupg apt-transport-https lsb-release ca-certificates
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Install Node.js
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Node.js
|
||||||
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||||
|
sudo apt-get update && sudo apt-get install nodejs -y
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Install PostgreSQL
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# PostgreSQL
|
||||||
|
wget -O /usr/share/keyrings/postgresql.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/postgresql.asc] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/postgresql.list
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Install packages
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 必要なパッケージをまとめてインストール
|
||||||
|
apt update
|
||||||
|
apt install -y \
|
||||||
|
imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev file git-core \
|
||||||
|
g++ libprotobuf-dev protobuf-compiler pkg-config nodejs gcc autoconf \
|
||||||
|
bison build-essential libssl-dev libyaml-dev libreadline6-dev \
|
||||||
|
zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev \
|
||||||
|
nginx redis-server redis-tools postgresql postgresql-contrib \
|
||||||
|
certbot python3-certbot-nginx libidn11-dev libicu-dev libjemalloc-dev
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Initialize yarn
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
corepack enable
|
||||||
|
yarn set version classic
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Install requested package
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Mastodonパッケージにもnode-gypは入ってるけど、npmのほうからグローバルにインストールしないと
|
||||||
|
# yarn installで一部のOptionalパッケージインストール時にエラーが出てしまう様子
|
||||||
|
npm i -g node-gyp
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Add mastodon user
|
||||||
|
|
||||||
|
Input user information (No need to type)
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# mastodonユーザーを追加
|
||||||
|
adduser --disabled-login mastodon
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Create PostgreSQL mastodon user
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# PostgreSQLにmastodonユーザーを追加
|
||||||
|
sudo -u postgres psql << EOF
|
||||||
|
CREATE USER mastodon WITH PASSWORD 'ohagi' CREATEDB;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Download kmyblue
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# kmyblueソースコードをダウンロード
|
||||||
|
# 続きのシェルスクリプトをgit管理外にコピーし権限を与える
|
||||||
|
su - mastodon <<EOF
|
||||||
|
git clone https://github.com/kmycode/mastodon.git live
|
||||||
|
cp /home/mastodon/live/install/$VERSION/setup2.sh /home/mastodon/setup2.sh
|
||||||
|
chmod +x /home/mastodon/setup2.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Checkout tag on kmyblue repository
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# kmyblueのリポジトリをチェックアウト
|
||||||
|
cd /home/mastodon/live
|
||||||
|
git config --global --add safe.directory /home/mastodon/live
|
||||||
|
if [ "$KMYBLUE_VERSION" == "debug" ]; then
|
||||||
|
echo 'DEBUG'
|
||||||
|
elif [ "$KMYBLUE_VERSION" == "newest" ] || [ "$KMYBLUE_VERSION" == "latest" ]; then
|
||||||
|
sudo -u mastodon git checkout $(git tag -l | grep -E '^kb[0-9]' | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)
|
||||||
|
else
|
||||||
|
# LTS
|
||||||
|
sudo -u mastodon git checkout $(git tag -l | grep -E '^kb[0-9].*lts$' | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)
|
||||||
|
fi
|
||||||
|
git config --global --unset safe.directory /home/mastodon/live
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Install rbenv to control Ruby versions
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Rubyバージョン管理用のrbenvをインストール、初期設定
|
||||||
|
su - mastodon <<EOF
|
||||||
|
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
|
||||||
|
cd ~/.rbenv && src/configure && make -C src
|
||||||
|
echo 'export PATH="\$HOME/.rbenv/bin:\$PATH"' >> ~/.bashrc
|
||||||
|
echo 'eval "\$(rbenv init -)"' >> ~/.bashrc
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 1] ======================
|
||||||
|
Copy setting files and services
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# これを設定しておかないと、Web表示時にNginxがPermission Errorを起こす
|
||||||
|
chmod o+x /home/mastodon
|
||||||
|
|
||||||
|
# 必要なファイルをコピー
|
||||||
|
cp /home/mastodon/live/dist/nginx.conf /etc/nginx/sites-available/mastodon
|
||||||
|
ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/mastodon
|
||||||
|
cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# ---------------------------------------------------
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
============== [kmyblue setup script 1 completed] ================
|
||||||
|
|
||||||
|
Input this command to continue setup:
|
||||||
|
sudo su - mastodon
|
||||||
|
./setup2.sh
|
||||||
|
|
||||||
|
EOF
|
68
install/13.0/setup2.sh
Normal file
68
install/13.0/setup2.sh
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 2] ======================
|
||||||
|
Install Ruby
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
|
||||||
|
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install $(cat /home/mastodon/live/.ruby-version)
|
||||||
|
rbenv global $(cat /home/mastodon/live/.ruby-version)
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 2] ======================
|
||||||
|
Install Ruby bundler
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
gem install bundler --no-document
|
||||||
|
|
||||||
|
cd ~/live
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 2] ======================
|
||||||
|
Install yarn packages
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
================== [kmyblue setup script 2] ======================
|
||||||
|
Install bundle packages
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
bundle config deployment 'true'
|
||||||
|
bundle config without 'development test'
|
||||||
|
bundle install -j$(getconf _NPROCESSORS_ONLN)
|
||||||
|
|
||||||
|
# ---------------------------------------------------
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
============== [kmyblue setup script 2 completed] ================
|
||||||
|
|
||||||
|
PostgreSQL and Redis are now available on localhost.
|
||||||
|
|
||||||
|
* PostgreSQL
|
||||||
|
host : /var/run/postgresql
|
||||||
|
user : mastodon
|
||||||
|
database : mastodon_production
|
||||||
|
password : ohagi
|
||||||
|
|
||||||
|
* Redis
|
||||||
|
host : localhost
|
||||||
|
password is empty
|
||||||
|
|
||||||
|
[IMPORTANT] Check PostgreSQL password before setup!
|
||||||
|
|
||||||
|
Input this command to finish setup:
|
||||||
|
cd live
|
||||||
|
RAILS_ENV=production bundle exec rake mastodon:setup
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
|
@ -9,13 +9,13 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def kmyblue_minor
|
def kmyblue_minor
|
||||||
0
|
1
|
||||||
end
|
end
|
||||||
|
|
||||||
def kmyblue_flag
|
def kmyblue_flag
|
||||||
# 'LTS'
|
# 'LTS'
|
||||||
'dev'
|
# 'dev'
|
||||||
# nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def major
|
def major
|
||||||
|
|
|
@ -88,7 +88,7 @@ class Sanitize
|
||||||
end
|
end
|
||||||
|
|
||||||
MASTODON_STRICT = freeze_config(
|
MASTODON_STRICT = freeze_config(
|
||||||
elements: %w(p br span a del pre blockquote code b strong u i em ul ol li ruby rt rp),
|
elements: %w(p br span a del s pre blockquote code b strong u i em ul ol li ruby rt rp),
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
'a' => %w(href rel class translate),
|
'a' => %w(href rel class translate),
|
||||||
|
|
|
@ -9,11 +9,13 @@ namespace :mastodon do
|
||||||
[
|
[
|
||||||
['App Libraries', 'app/lib'],
|
['App Libraries', 'app/lib'],
|
||||||
%w(Presenters app/presenters),
|
%w(Presenters app/presenters),
|
||||||
|
%w(Policies app/policies),
|
||||||
|
%w(Serializers app/serializers),
|
||||||
%w(Services app/services),
|
%w(Services app/services),
|
||||||
%w(Validators app/validators),
|
%w(Validators app/validators),
|
||||||
%w(Workers app/workers),
|
%w(Workers app/workers),
|
||||||
].each do |name, dir|
|
].each do |name, dir|
|
||||||
STATS_DIRECTORIES << [name, Rails.root.join(dir)]
|
STATS_DIRECTORIES << [name, dir]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,4 +4,4 @@ Content-Type: application/jrd+json; charset=utf-8
|
||||||
X-Content-Type-Options: nosniff
|
X-Content-Type-Options: nosniff
|
||||||
Date: Sun, 17 Sep 2017 06:22:50 GMT
|
Date: Sun, 17 Sep 2017 06:22:50 GMT
|
||||||
|
|
||||||
{"subject":"acct:foo@ap.example.com","aliases":["https://ap.example.com/@foo","https://ap.example.com/users/foo"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://ap.example.com/@foo"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://ap.example.com/users/foo.atom"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"salmon","href":"https://ap.example.com/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.u3L4vnpNLzVH31MeWI394F0wKeJFsLDAsNXGeOu0QF2x-h1zLWZw_agqD2R3JPU9_kaDJGPIV2Sn5zLyUA9S6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh8lDET6X4Pyw-ZJU0_OLo_41q9w-OrGtlsTm_PuPIeXnxa6BLqnDaxC-4IcjG_FiPahNCTINl_1F_TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq-t8nhQYkgAkt64euWpva3qL5KD1mTIZQEP-LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3QvuHQ==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://ap.example.com/authorize_follow?acct={uri}"}]}
|
{"subject":"acct:foo@ap.example.com","aliases":["https://ap.example.com/@foo","https://ap.example.com/users/foo"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://ap.example.com/@foo"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://ap.example.com/users/foo.atom"},{"rel":"self","type":"application/html","href":"https://ap.example.com/users/foo.html"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"self","type":"application/json","href":"https://ap.example.com/users/foo.json"},{"rel":"salmon","href":"https://ap.example.com/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.u3L4vnpNLzVH31MeWI394F0wKeJFsLDAsNXGeOu0QF2x-h1zLWZw_agqD2R3JPU9_kaDJGPIV2Sn5zLyUA9S6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh8lDET6X4Pyw-ZJU0_OLo_41q9w-OrGtlsTm_PuPIeXnxa6BLqnDaxC-4IcjG_FiPahNCTINl_1F_TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq-t8nhQYkgAkt64euWpva3qL5KD1mTIZQEP-LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3QvuHQ==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://ap.example.com/authorize_follow?acct={uri}"}]}
|
2
spec/fixtures/requests/webfinger.txt
vendored
2
spec/fixtures/requests/webfinger.txt
vendored
|
@ -8,4 +8,4 @@ Access-Control-Allow-Origin: *
|
||||||
Vary: Accept-Encoding,Cookie
|
Vary: Accept-Encoding,Cookie
|
||||||
Strict-Transport-Security: max-age=31536000; includeSubdomains;
|
Strict-Transport-Security: max-age=31536000; includeSubdomains;
|
||||||
|
|
||||||
{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]}
|
{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]}
|
||||||
|
|
|
@ -1109,7 +1109,7 @@ RSpec.describe ActivityPub::Activity::Create do
|
||||||
inbox: 'https://foo.test/inbox',
|
inbox: 'https://foo.test/inbox',
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
end
|
end
|
||||||
let!(:webfinger) { { subject: 'acct:actor@foo.test', links: [{ rel: 'self', href: 'https://foo.test' }] } }
|
let!(:webfinger) { { subject: 'acct:actor@foo.test', links: [{ rel: 'self', href: 'https://foo.test', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
let(:object_json) do
|
let(:object_json) do
|
||||||
{
|
{
|
||||||
|
|
|
@ -59,7 +59,7 @@ RSpec.describe ActivityPub::Adapter do
|
||||||
let(:serializer_class) { TestWithBasicContextSerializer }
|
let(:serializer_class) { TestWithBasicContextSerializer }
|
||||||
|
|
||||||
it 'renders a basic @context' do
|
it 'renders a basic @context' do
|
||||||
expect(subject).to include({ '@context' => 'https://www.w3.org/ns/activitystreams' })
|
expect(subject).to include({ '@context': 'https://www.w3.org/ns/activitystreams' })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ RSpec.describe ActivityPub::Adapter do
|
||||||
let(:serializer_class) { TestWithNamedContextSerializer }
|
let(:serializer_class) { TestWithNamedContextSerializer }
|
||||||
|
|
||||||
it 'renders a @context with both items' do
|
it 'renders a @context with both items' do
|
||||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
|
expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ RSpec.describe ActivityPub::Adapter do
|
||||||
let(:serializer_class) { TestWithNestedNamedContextSerializer }
|
let(:serializer_class) { TestWithNestedNamedContextSerializer }
|
||||||
|
|
||||||
it 'renders a @context with both items' do
|
it 'renders a @context with both items' do
|
||||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
|
expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ RSpec.describe ActivityPub::Adapter do
|
||||||
let(:serializer_class) { TestWithContextExtensionSerializer }
|
let(:serializer_class) { TestWithContextExtensionSerializer }
|
||||||
|
|
||||||
it 'renders a @context with the extension' do
|
it 'renders a @context with the extension' do
|
||||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] })
|
expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ RSpec.describe ActivityPub::Adapter do
|
||||||
let(:serializer_class) { TestWithNestedContextExtensionSerializer }
|
let(:serializer_class) { TestWithNestedContextExtensionSerializer }
|
||||||
|
|
||||||
it 'renders a @context with both extensions' do
|
it 'renders a @context with both extensions' do
|
||||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] })
|
expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
41
spec/lib/webfinger_spec.rb
Normal file
41
spec/lib/webfinger_spec.rb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Webfinger do
|
||||||
|
describe 'self link' do
|
||||||
|
context 'when self link is specified with type application/activity+json' do
|
||||||
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
|
it 'correctly parses the response' do
|
||||||
|
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
|
||||||
|
|
||||||
|
response = described_class.new('acct:alice@example.com').perform
|
||||||
|
|
||||||
|
expect(response.self_link_href).to eq 'https://example.com/alice'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when self link is specified with type application/ld+json' do
|
||||||
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }] } }
|
||||||
|
|
||||||
|
it 'correctly parses the response' do
|
||||||
|
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
|
||||||
|
|
||||||
|
response = described_class.new('acct:alice@example.com').perform
|
||||||
|
|
||||||
|
expect(response.self_link_href).to eq 'https://example.com/alice'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when self link is specified with incorrect type' do
|
||||||
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/json"' }] } }
|
||||||
|
|
||||||
|
it 'raises an error' do
|
||||||
|
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
|
||||||
|
|
||||||
|
expect { described_class.new('acct:alice@example.com').perform }.to raise_error(Webfinger::Error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -934,6 +934,14 @@ RSpec.describe Account do
|
||||||
it 'does not match URL query string' do
|
it 'does not match URL query string' do
|
||||||
expect(subject.match('https://example.com/?x=@alice')).to be_nil
|
expect(subject.match('https://example.com/?x=@alice')).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'matches usernames immediately following the letter ß' do
|
||||||
|
expect(subject.match('Hello toß @alice from me')[1]).to eq 'alice'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'matches usernames containing uppercase characters' do
|
||||||
|
expect(subject.match('Hello to @aLice@Example.com from me')[1]).to eq 'aLice@Example.com'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
|
|
|
@ -95,6 +95,14 @@ RSpec.describe Tag do
|
||||||
it 'does not match purely-numeric hashtags' do
|
it 'does not match purely-numeric hashtags' do
|
||||||
expect(subject.match('hello #0123456')).to be_nil
|
expect(subject.match('hello #0123456')).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'matches hashtags immediately following the letter ß' do
|
||||||
|
expect(subject.match('Hello toß #ruby').to_s).to eq '#ruby'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'matches hashtags containing uppercase characters' do
|
||||||
|
expect(subject.match('Hello #rubyOnRails').to_s).to eq '#rubyOnRails'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#to_param' do
|
describe '#to_param' do
|
||||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the account does not have a inbox' do
|
context 'when the account does not have a inbox' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
actor[:inbox] = nil
|
actor[:inbox] = nil
|
||||||
|
@ -52,7 +52,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when URI and WebFinger share the same host' do
|
context 'when URI and WebFinger share the same host' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
@ -74,7 +74,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WebFinger presents different domain than URI' do
|
context 'when WebFinger presents different domain than URI' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
@ -98,7 +98,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WebFinger returns a different URI' do
|
context 'when WebFinger returns a different URI' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
@ -115,7 +115,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WebFinger returns a different URI after a redirection' do
|
context 'when WebFinger returns a different URI after a redirection' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the account does not have a inbox' do
|
context 'when the account does not have a inbox' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
actor[:inbox] = nil
|
actor[:inbox] = nil
|
||||||
|
@ -52,7 +52,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when URI and WebFinger share the same host' do
|
context 'when URI and WebFinger share the same host' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
@ -74,7 +74,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WebFinger presents different domain than URI' do
|
context 'when WebFinger presents different domain than URI' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
@ -98,7 +98,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WebFinger returns a different URI' do
|
context 'when WebFinger returns a different URI' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
@ -115,7 +115,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WebFinger returns a different URI after a redirection' do
|
context 'when WebFinger returns a different URI after a redirection' do
|
||||||
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
|
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
|
|
@ -5,7 +5,7 @@ require 'rails_helper'
|
||||||
RSpec.describe ActivityPub::FetchRemoteKeyService do
|
RSpec.describe ActivityPub::FetchRemoteKeyService do
|
||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
|
||||||
|
|
||||||
let(:public_key_pem) do
|
let(:public_key_pem) do
|
||||||
<<~TEXT
|
<<~TEXT
|
||||||
|
|
|
@ -601,7 +601,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
webfinger = {
|
webfinger = {
|
||||||
subject: "acct:user#{i}@foo.test",
|
subject: "acct:user#{i}@foo.test",
|
||||||
links: [{ rel: 'self', href: "https://foo.test/users/#{i}" }],
|
links: [{ rel: 'self', href: "https://foo.test/users/#{i}", type: 'application/activity+json' }],
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
stub_request(:get, "https://foo.test/users/#{i}").to_return(status: 200, body: actor_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, "https://foo.test/users/#{i}").to_return(status: 200, body: actor_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
|
||||||
stub_request(:get, "https://foo.test/users/#{i}/featured").to_return(status: 200, body: featured_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
|
stub_request(:get, "https://foo.test/users/#{i}/featured").to_return(status: 200, body: featured_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
|
||||||
|
|
|
@ -62,6 +62,7 @@ RSpec.describe BackupService do
|
||||||
|
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
expect(body.scan('@context').count).to eq 1
|
expect(body.scan('@context').count).to eq 1
|
||||||
|
expect(body.scan('orderedItems').count).to eq 1
|
||||||
expect(json['@context']).to_not be_nil
|
expect(json['@context']).to_not be_nil
|
||||||
expect(json['type']).to eq 'OrderedCollection'
|
expect(json['type']).to eq 'OrderedCollection'
|
||||||
expect(json['totalItems']).to eq 4
|
expect(json['totalItems']).to eq 4
|
||||||
|
|
|
@ -129,6 +129,73 @@ RSpec.describe NotifyService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the blocked sender has a role' do
|
||||||
|
let(:sender) { Fabricate(:user, role: sender_role).account }
|
||||||
|
let(:activity) { Fabricate(:mention, status: Fabricate(:status, account: sender)) }
|
||||||
|
let(:type) { :mention }
|
||||||
|
|
||||||
|
before do
|
||||||
|
recipient.block!(sender)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the role is a visible moderator' do
|
||||||
|
let(:sender_role) { Fabricate(:user_role, highlighted: true, permissions: UserRole::FLAGS[:manage_users]) }
|
||||||
|
|
||||||
|
it 'does notify' do
|
||||||
|
expect { subject }.to change(Notification, :count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the role is a non-visible moderator' do
|
||||||
|
let(:sender_role) { Fabricate(:user_role, highlighted: false, permissions: UserRole::FLAGS[:manage_users]) }
|
||||||
|
|
||||||
|
it 'does not notify' do
|
||||||
|
expect { subject }.to_not change(Notification, :count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the role is a visible non-moderator' do
|
||||||
|
let(:sender_role) { Fabricate(:user_role, highlighted: true) }
|
||||||
|
|
||||||
|
it 'does not notify' do
|
||||||
|
expect { subject }.to_not change(Notification, :count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with filtered notifications' do
|
||||||
|
let(:unknown) { Fabricate(:account, username: 'unknown') }
|
||||||
|
let(:status) { Fabricate(:status, account: unknown) }
|
||||||
|
let(:activity) { Fabricate(:mention, account: recipient, status: status) }
|
||||||
|
let(:type) { :mention }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Fabricate(:notification_policy, account: recipient, filter_not_following: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a filtered notification' do
|
||||||
|
expect { subject }.to change(Notification, :count)
|
||||||
|
expect(Notification.last).to be_filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no notification request exists' do
|
||||||
|
it 'creates a notification request' do
|
||||||
|
expect { subject }.to change(NotificationRequest, :count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a notification request exists' do
|
||||||
|
let!(:notification_request) do
|
||||||
|
Fabricate(:notification_request, account: recipient, from_account: unknown, last_status: Fabricate(:status, account: unknown))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the existing notification request' do
|
||||||
|
expect { subject }.to_not change(NotificationRequest, :count)
|
||||||
|
expect(notification_request.reload.last_status).to eq status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe NotifyService::DismissCondition do
|
describe NotifyService::DismissCondition do
|
||||||
subject { described_class.new(notification) }
|
subject { described_class.new(notification) }
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue