about summary refs log tree commit diff
path: root/app
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 /app
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
Diffstat (limited to 'app')
-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
14 files changed, 113 insertions, 40 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 52fb1dc1b..33bac9bbc 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 26f3b1def..51e9764d4 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 d1c525134..42493cd78 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 000000000..41f33602e
--- /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 3564bf07b..2b6794ee2 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 3eb88339a..28f1da19f 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 4d452f290..512272dbe 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 a54a702a2..22d78874a 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 adf4586fa..ccd116d6e 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 ccd7bfa12..a54c2174d 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 0644219fb..0bd7aed2e 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 f4af21551..008d0c182 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 b248ed1d2..21431ef8e 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 ae80df9d2..f5a54052a 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