summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThibG <thib@sitedethib.com>2019-07-19 01:44:42 +0200
committerEugen Rochko <eugen@zeonfederated.com>2019-07-19 01:44:42 +0200
commit730c4053d642024b9949d72c8a9f1873532c6212 (patch)
treef6bee482d07ccf0e51cd024bc759d3b48f6894f2
parent15c7478c5560a1f654d0d00d8ee2a624acb34089 (diff)
Add ActivityPub actor representing the entire server (#11321)
* Add support for an instance actor * Skip username validation for local Application accounts * Add migration script to create instance actor * Make Codeclimate happy * Switch to id -99 for instance actor * Remove unused `icon` and `image` attributes from instance actor * Use if/elsif/else instead of return + ternary operator * Add instance actor to fresh installs * Use instance actor as instance representative Use instance actor for forwarding reports, relay operations, and spam auto-reporting. * Seed database in test environment * Fix single-user mode * Fix tests * Fix specs to accomodate for an extra `Account` * Auto-reject follows on instance actor Following an instance actor might make sense, but we are not handling that right now, so auto-reject. * Fix webfinger lookup and serialization for instance actor * Rename instance actor * Make it clear in the HTML view that the instance actor should not be blocked * Raise cache time for instance actor as there's no dynamic content * Re-use /about/more with a flash message for instance actor profile
-rw-r--r--app/controllers/about_controller.rb4
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/home_controller.rb2
-rw-r--r--app/controllers/instance_actors_controller.rb20
-rw-r--r--app/javascript/styles/mastodon/containers.scss4
-rw-r--r--app/lib/activitypub/activity/follow.rb2
-rw-r--r--app/lib/activitypub/tag_manager.rb5
-rw-r--r--app/lib/webfinger_resource.rb6
-rw-r--r--app/models/account.rb8
-rw-r--r--app/models/concerns/account_finder_concern.rb2
-rw-r--r--app/serializers/activitypub/actor_serializer.rb14
-rw-r--r--app/serializers/webfinger_serializer.rb25
-rw-r--r--app/views/about/more.html.haml2
-rw-r--r--app/views/well_known/webfinger/show.xml.ruby57
-rw-r--r--config/locales/en.yml3
-rw-r--r--config/routes.rb4
-rw-r--r--db/migrate/20190715164535_add_instance_actor.rb9
-rw-r--r--db/schema.rb2
-rw-r--r--db/seeds.rb4
-rw-r--r--spec/models/account_spec.rb12
-rw-r--r--spec/services/fetch_remote_account_service_spec.rb1
-rw-r--r--spec/services/fetch_resource_service_spec.rb4
-rw-r--r--spec/spec_helper.rb1
23 files changed, 141 insertions, 52 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 52fb1dc1b3e..33bac9bbc72 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -11,7 +11,9 @@ class AboutController < ApplicationController
def show; end
- def more; end
+ def more
+ flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
+ end
def terms; end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 26f3b1def5b..51e9764d49e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -91,7 +91,7 @@ class ApplicationController < ActionController::Base
end
def single_user_mode?
- @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
+ @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
end
def use_seamless_external_login?
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index d1c52513479..42493cd7825 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -58,7 +58,7 @@ class HomeController < ApplicationController
if request.path.start_with?('/web')
new_user_session_path
elsif single_user_mode?
- short_account_path(Account.local.without_suspended.first)
+ short_account_path(Account.local.without_suspended.where('id > 0').first)
else
about_path
end
diff --git a/app/controllers/instance_actors_controller.rb b/app/controllers/instance_actors_controller.rb
new file mode 100644
index 00000000000..41f33602e69
--- /dev/null
+++ b/app/controllers/instance_actors_controller.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class InstanceActorsController < ApplicationController
+ include AccountControllerConcern
+
+ def show
+ expires_in 10.minutes, public: true
+ render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
+ end
+
+ private
+
+ def set_account
+ @account = Account.find(-99)
+ end
+
+ def restrict_fields_to
+ %i(id type preferred_username inbox public_key endpoints url manually_approves_followers)
+ end
+end
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 3564bf07b4d..2b6794ee2ce 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -145,6 +145,10 @@
min-height: 100%;
}
+ .flash-message {
+ margin-bottom: 10px;
+ }
+
@media screen and (max-width: 738px) {
grid-template-columns: minmax(0, 50%) minmax(0, 50%);
diff --git a/app/lib/activitypub/activity/follow.rb b/app/lib/activitypub/activity/follow.rb
index 3eb88339aeb..28f1da19f89 100644
--- a/app/lib/activitypub/activity/follow.rb
+++ b/app/lib/activitypub/activity/follow.rb
@@ -8,7 +8,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
- if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
+ if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved? || target_account.instance_actor?
reject_follow_request!(target_account)
return
end
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index 4d452f290ba..512272dbebe 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -17,7 +17,7 @@ class ActivityPub::TagManager
case target.object_type
when :person
- short_account_url(target)
+ target.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(target)
when :note, :comment, :activity
return activity_account_status_url(target.account, target) if target.reblog?
short_account_status_url(target.account, target)
@@ -29,7 +29,7 @@ class ActivityPub::TagManager
case target.object_type
when :person
- account_url(target)
+ target.instance_actor? ? instance_actor_url : account_url(target)
when :note, :comment, :activity
return activity_account_status_url(target.account, target) if target.reblog?
account_status_url(target.account, target)
@@ -119,6 +119,7 @@ class ActivityPub::TagManager
def uri_to_local_id(uri, param = :id)
path_params = Rails.application.routes.recognize_path(uri)
+ path_params[:username] = Rails.configuration.x.local_domain if path_params[:controller] == 'instance_actors'
path_params[param]
end
diff --git a/app/lib/webfinger_resource.rb b/app/lib/webfinger_resource.rb
index a54a702a2e9..22d78874a4b 100644
--- a/app/lib/webfinger_resource.rb
+++ b/app/lib/webfinger_resource.rb
@@ -23,11 +23,17 @@ class WebfingerResource
def username_from_url
if account_show_page?
path_params[:username]
+ elsif instance_actor_page?
+ Rails.configuration.x.local_domain
else
raise ActiveRecord::RecordNotFound
end
end
+ def instance_actor_page?
+ path_params[:controller] == 'instance_actors'
+ end
+
def account_show_page?
path_params[:controller] == 'accounts' && path_params[:action] == 'show'
end
diff --git a/app/models/account.rb b/app/models/account.rb
index adf4586fa5f..ccd116d6e2e 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -77,7 +77,7 @@ class Account < ApplicationRecord
validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, if: -> { !local? && will_save_change_to_username? }
# Local user validations
- validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
+ validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? }
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
@@ -139,6 +139,10 @@ class Account < ApplicationRecord
%w(Application Service).include? actor_type
end
+ def instance_actor?
+ id == -99
+ end
+
alias bot bot?
def bot=(val)
@@ -498,7 +502,7 @@ class Account < ApplicationRecord
end
def generate_keys
- return unless local? && !Rails.env.test?
+ return unless local? && private_key.blank? && public_key.blank?
keypair = OpenSSL::PKey::RSA.new(2048)
self.private_key = keypair.to_pem
diff --git a/app/models/concerns/account_finder_concern.rb b/app/models/concerns/account_finder_concern.rb
index ccd7bfa1231..a54c2174d49 100644
--- a/app/models/concerns/account_finder_concern.rb
+++ b/app/models/concerns/account_finder_concern.rb
@@ -13,7 +13,7 @@ module AccountFinderConcern
end
def representative
- find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) || Account.local.without_suspended.first
+ Account.find(-99)
end
def find_local(username)
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 0644219fb69..0bd7aed2e97 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -39,11 +39,17 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
delegate :moved?, to: :object
def id
- account_url(object)
+ object.instance_actor? ? instance_actor_url : account_url(object)
end
def type
- object.bot? ? 'Service' : 'Person'
+ if object.instance_actor?
+ 'Application'
+ elsif object.bot?
+ 'Service'
+ else
+ 'Person'
+ end
end
def following
@@ -55,7 +61,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def inbox
- account_inbox_url(object)
+ object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object)
end
def outbox
@@ -95,7 +101,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def url
- short_account_url(object)
+ object.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(object)
end
def avatar_exists?
diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb
index f4af215510c..008d0c18293 100644
--- a/app/serializers/webfinger_serializer.rb
+++ b/app/serializers/webfinger_serializer.rb
@@ -10,15 +10,26 @@ class WebfingerSerializer < ActiveModel::Serializer
end
def aliases
- [short_account_url(object), account_url(object)]
+ if object.instance_actor?
+ [instance_actor_url]
+ else
+ [short_account_url(object), account_url(object)]
+ end
end
def links
- [
- { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) },
- { rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(object, format: 'atom') },
- { rel: 'self', type: 'application/activity+json', href: account_url(object) },
- { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
- ]
+ if object.instance_actor?
+ [
+ { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: about_more_url(instance_actor: true) },
+ { rel: 'self', type: 'application/activity+json', href: instance_actor_url },
+ ]
+ else
+ [
+ { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) },
+ { rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(object, format: 'atom') },
+ { rel: 'self', type: 'application/activity+json', href: account_url(object) },
+ { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
+ ]
+ end
end
end
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index b248ed1d230..21431ef8e5d 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -43,5 +43,7 @@
= mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email
.column-3
+ = render 'application/flashes'
+
.box-widget
.rich-formatting= @instance_presenter.site_extended_description.html_safe.presence || t('about.extended_description_html')
diff --git a/app/views/well_known/webfinger/show.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby
index ae80df9d2f8..f5a54052a6c 100644
--- a/app/views/well_known/webfinger/show.xml.ruby
+++ b/app/views/well_known/webfinger/show.xml.ruby
@@ -4,30 +4,47 @@ doc << Ox::Element.new('XRD').tap do |xrd|
xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'
xrd << (Ox::Element.new('Subject') << @account.to_webfinger_s)
- xrd << (Ox::Element.new('Alias') << short_account_url(@account))
- xrd << (Ox::Element.new('Alias') << account_url(@account))
- xrd << Ox::Element.new('Link').tap do |link|
- link['rel'] = 'http://webfinger.net/rel/profile-page'
- link['type'] = 'text/html'
- link['href'] = short_account_url(@account)
- end
+ if @account.instance_actor?
+ xrd << (Ox::Element.new('Alias') << instance_actor_url)
- xrd << Ox::Element.new('Link').tap do |link|
- link['rel'] = 'http://schemas.google.com/g/2010#updates-from'
- link['type'] = 'application/atom+xml'
- link['href'] = account_url(@account, format: 'atom')
- end
+ xrd << Ox::Element.new('Link').tap do |link|
+ link['rel'] = 'http://webfinger.net/rel/profile-page'
+ link['type'] = 'text/html'
+ link['href'] = about_more_url(instance_actor: true)
+ end
- xrd << Ox::Element.new('Link').tap do |link|
- link['rel'] = 'self'
- link['type'] = 'application/activity+json'
- link['href'] = account_url(@account)
- end
+ xrd << Ox::Element.new('Link').tap do |link|
+ link['rel'] = 'self'
+ link['type'] = 'application/activity+json'
+ link['href'] = instance_actor_url
+ end
+ else
+ xrd << (Ox::Element.new('Alias') << short_account_url(@account))
+ xrd << (Ox::Element.new('Alias') << account_url(@account))
+
+ xrd << Ox::Element.new('Link').tap do |link|
+ link['rel'] = 'http://webfinger.net/rel/profile-page'
+ link['type'] = 'text/html'
+ link['href'] = short_account_url(@account)
+ end
+
+ xrd << Ox::Element.new('Link').tap do |link|
+ link['rel'] = 'http://schemas.google.com/g/2010#updates-from'
+ link['type'] = 'application/atom+xml'
+ link['href'] = account_url(@account, format: 'atom')
+ end
+
+ xrd << Ox::Element.new('Link').tap do |link|
+ link['rel'] = 'self'
+ link['type'] = 'application/activity+json'
+ link['href'] = account_url(@account)
+ end
- xrd << Ox::Element.new('Link').tap do |link|
- link['rel'] = 'http://ostatus.org/schema/1.0/subscribe'
- link['template'] = "#{authorize_interaction_url}?acct={uri}"
+ xrd << Ox::Element.new('Link').tap do |link|
+ link['rel'] = 'http://ostatus.org/schema/1.0/subscribe'
+ link['template'] = "#{authorize_interaction_url}?acct={uri}"
+ end
end
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 4e252945f18..89c52b84ae2 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -24,6 +24,9 @@ en:
generic_description: "%{domain} is one server in the network"
get_apps: Try a mobile app
hosted_on: Mastodon hosted on %{domain}
+ instance_actor_flash: |
+ This account is a virtual actor used to represent the server itself and not any individual user.
+ It is used for federation purposes and should not be blocked unless you want to block the whole instance, in which case you should use a domain block.
learn_more: Learn more
privacy_policy: Privacy policy
see_whats_happening: See what's happening
diff --git a/config/routes.rb b/config/routes.rb
index 95f8a39add4..27b53664196 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -28,6 +28,10 @@ Rails.application.routes.draw do
get 'intent', to: 'intents#show'
get 'custom.css', to: 'custom_css#show', as: :custom_css
+ resource :instance_actor, path: 'actor', only: [:show] do
+ resource :inbox, only: [:create], module: :activitypub
+ end
+
devise_scope :user do
get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
match '/auth/finish_signup' => 'auth/confirmations#finish_signup', via: [:get, :patch], as: :finish_signup
diff --git a/db/migrate/20190715164535_add_instance_actor.rb b/db/migrate/20190715164535_add_instance_actor.rb
new file mode 100644
index 00000000000..a26d5494932
--- /dev/null
+++ b/db/migrate/20190715164535_add_instance_actor.rb
@@ -0,0 +1,9 @@
+class AddInstanceActor < ActiveRecord::Migration[5.2]
+ def up
+ Account.create!(id: -99, actor_type: 'Application', locked: true, username: Rails.configuration.x.local_domain)
+ end
+
+ def down
+ Account.find_by(id: -99, actor_type: 'Application').destroy!
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c7b6b9be69c..a6a14827b9b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_07_06_233204) do
+ActiveRecord::Schema.define(version: 2019_07_15_164535) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/db/seeds.rb b/db/seeds.rb
index 9a6e9dd78ed..5f43fbac8bb 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,7 +1,9 @@
Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow')
+domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
+Account.create!(id: -99, actor_type: 'Application', locked: true, username: domain)
+
if Rails.env.development?
- domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
admin.save(validate: false)
User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true).save!
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index ce9ea250d13..6495a61934a 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -450,7 +450,7 @@ RSpec.describe Account, type: :model do
describe '.domains' do
it 'returns domains' do
Fabricate(:account, domain: 'domain')
- expect(Account.domains).to match_array(['domain'])
+ expect(Account.remote.domains).to match_array(['domain'])
end
end
@@ -665,7 +665,7 @@ RSpec.describe Account, type: :model do
{ username: 'b', domain: 'b' },
].map(&method(:Fabricate).curry(2).call(:account))
- expect(Account.alphabetic).to eq matches
+ expect(Account.where('id > 0').alphabetic).to eq matches
end
end
@@ -732,7 +732,7 @@ RSpec.describe Account, type: :model do
2.times { Fabricate(:account, domain: 'example.com') }
Fabricate(:account, domain: 'example2.com')
- results = Account.by_domain_accounts
+ results = Account.where('id > 0').by_domain_accounts
expect(results.length).to eq 2
expect(results.first.domain).to eq 'example.com'
expect(results.first.accounts_count).to eq 2
@@ -745,7 +745,7 @@ RSpec.describe Account, type: :model 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(Account.local).to match_array([account_1])
+ expect(Account.where('id > 0').local).to match_array([account_1])
end
end
@@ -756,14 +756,14 @@ RSpec.describe Account, type: :model do
matches[index] = Fabricate(:account, domain: matches[index])
end
- expect(Account.partitioned).to match_array(matches)
+ expect(Account.where('id > 0').partitioned).to match_array(matches)
end
end
describe 'recent' do
it 'returns a relation of accounts sorted by recent creation' do
matches = 2.times.map { Fabricate(:account) }
- expect(Account.recent).to match_array(matches)
+ expect(Account.where('id > 0').recent).to match_array(matches)
end
end
diff --git a/spec/services/fetch_remote_account_service_spec.rb b/spec/services/fetch_remote_account_service_spec.rb
index b374458610b..ee7325be286 100644
--- a/spec/services/fetch_remote_account_service_spec.rb
+++ b/spec/services/fetch_remote_account_service_spec.rb
@@ -4,7 +4,6 @@ RSpec.describe FetchRemoteAccountService, type: :service do
let(:url) { 'https://example.com/alice' }
let(:prefetched_body) { nil }
let(:protocol) { :ostatus }
- let!(:representative) { Fabricate(:account) }
subject { FetchRemoteAccountService.new.call(url, prefetched_body, protocol) }
diff --git a/spec/services/fetch_resource_service_spec.rb b/spec/services/fetch_resource_service_spec.rb
index 98630966b54..f836147d354 100644
--- a/spec/services/fetch_resource_service_spec.rb
+++ b/spec/services/fetch_resource_service_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
RSpec.describe FetchResourceService, type: :service do
- let!(:representative) { Fabricate(:account) }
-
describe '#call' do
let(:url) { 'http://example.com' }
@@ -60,7 +58,7 @@ RSpec.describe FetchResourceService, type: :service do
it 'signs request' do
subject
- expect(a_request(:get, url).with(headers: { 'Sign