respect hub.lease_seconds (fix #46)master
@@ -4,6 +4,7 @@ class Api::SubscriptionsController < ApiController | |||||
def show | def show | ||||
if @account.subscription(api_subscription_url(@account.id)).valid?(params['hub.topic'], params['hub.verify_token']) | if @account.subscription(api_subscription_url(@account.id)).valid?(params['hub.topic'], params['hub.verify_token']) | ||||
@account.update(subscription_expires_at: Time.now + (params['hub.lease_seconds'].to_i).seconds) | |||||
render plain: HTMLEntities.new.encode(params['hub.challenge']), status: 200 | render plain: HTMLEntities.new.encode(params['hub.challenge']), status: 200 | ||||
else | else | ||||
head 404 | head 404 | ||||
@@ -38,6 +38,12 @@ class Account < ApplicationRecord | |||||
has_many :media_attachments, dependent: :destroy | has_many :media_attachments, dependent: :destroy | ||||
scope :remote, -> { where.not(domain: nil) } | |||||
scope :local, -> { where(domain: nil) } | |||||
scope :without_followers, -> { where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) = 0') } | |||||
scope :with_followers, -> { where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) > 0') } | |||||
scope :expiring, -> (time) { where(subscription_expires_at: nil).or(where('subscription_expires_at < ?', time)).remote.with_followers } | |||||
def follow!(other_account) | def follow!(other_account) | ||||
self.active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account) | self.active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account) | ||||
end | end | ||||
@@ -3,22 +3,18 @@ class FollowRemoteAccountService < BaseService | |||||
# When creating, look up the user's webfinger and fetch all | # When creating, look up the user's webfinger and fetch all | ||||
# important information from their feed | # important information from their feed | ||||
# @param [String] uri User URI in the form of username@domain | # @param [String] uri User URI in the form of username@domain | ||||
# @param [Boolean] subscribe Whether to initiate a PubSubHubbub subscription | |||||
# @return [Account] | # @return [Account] | ||||
def call(uri, subscribe = true) | |||||
def call(uri) | |||||
username, domain = uri.split('@') | username, domain = uri.split('@') | ||||
return Account.find_local(username) if domain == Rails.configuration.x.local_domain || domain.nil? | return Account.find_local(username) if domain == Rails.configuration.x.local_domain || domain.nil? | ||||
account = Account.find_remote(username, domain) | account = Account.find_remote(username, domain) | ||||
if account.nil? | |||||
Rails.logger.debug "Creating new remote account for #{uri}" | |||||
account = Account.new(username: username, domain: domain) | |||||
elsif account.subscribed? | |||||
Rails.logger.debug "Already subscribed to remote account #{uri}" | |||||
return account | |||||
end | |||||
return account unless account.nil? | |||||
Rails.logger.debug "Creating new remote account for #{uri}" | |||||
account = Account.new(username: username, domain: domain) | |||||
data = Goldfinger.finger("acct:#{uri}") | data = Goldfinger.finger("acct:#{uri}") | ||||
@@ -45,16 +41,6 @@ class FollowRemoteAccountService < BaseService | |||||
get_profile(feed, account) | get_profile(feed, account) | ||||
account.save! | account.save! | ||||
if subscribe | |||||
account.secret = SecureRandom.hex | |||||
account.verify_token = SecureRandom.hex | |||||
subscription = account.subscription(api_subscription_url(account.id)) | |||||
subscription.subscribe | |||||
account.save! | |||||
end | |||||
return account | return account | ||||
end | end | ||||
@@ -90,8 +76,3 @@ class FollowRemoteAccountService < BaseService | |||||
end | end | ||||
end | end | ||||
class NoAuthorFeedError < StandardError | |||||
end | |||||
class NoHubError < StandardError | |||||
end |
@@ -12,6 +12,7 @@ class FollowService < BaseService | |||||
if target_account.local? | if target_account.local? | ||||
NotificationMailer.follow(target_account, source_account).deliver_later | NotificationMailer.follow(target_account, source_account).deliver_later | ||||
else | else | ||||
subscribe_service.(target_account) | |||||
NotificationWorker.perform_async(follow.stream_entry.id, target_account.id) | NotificationWorker.perform_async(follow.stream_entry.id, target_account.id) | ||||
end | end | ||||
@@ -40,4 +41,8 @@ class FollowService < BaseService | |||||
def follow_remote_account_service | def follow_remote_account_service | ||||
@follow_remote_account_service ||= FollowRemoteAccountService.new | @follow_remote_account_service ||= FollowRemoteAccountService.new | ||||
end | end | ||||
def subscribe_service | |||||
@subscribe_service ||= SubscribeService.new | |||||
end | |||||
end | end |
@@ -106,7 +106,7 @@ class ProcessFeedService < BaseService | |||||
end | end | ||||
def delete_post!(status) | def delete_post!(status) | ||||
RemoveStatusService.new.(status) | |||||
remove_status_service.(status) | |||||
end | end | ||||
def find_original_status(_xml, id) | def find_original_status(_xml, id) | ||||
@@ -126,7 +126,7 @@ class ProcessFeedService < BaseService | |||||
account = Account.find_by(username: username, domain: domain) | account = Account.find_by(username: username, domain: domain) | ||||
if account.nil? | if account.nil? | ||||
account = follow_remote_account_service.("#{username}@#{domain}", false) | |||||
account = follow_remote_account_service.("#{username}@#{domain}") | |||||
end | end | ||||
status = Status.new(account: account, uri: target_id(xml), text: target_content(xml), url: target_url(xml), created_at: published(xml), updated_at: updated(xml)) | status = Status.new(account: account, uri: target_id(xml), text: target_content(xml), url: target_url(xml), created_at: published(xml), updated_at: updated(xml)) | ||||
@@ -196,4 +196,8 @@ class ProcessFeedService < BaseService | |||||
def update_remote_profile_service | def update_remote_profile_service | ||||
@update_remote_profile_service ||= UpdateRemoteProfileService.new | @update_remote_profile_service ||= UpdateRemoteProfileService.new | ||||
end | end | ||||
def remove_status_service | |||||
@remove_status_service ||= RemoveStatusService.new | |||||
end | |||||
end | end |
@@ -14,7 +14,7 @@ class ProcessInteractionService < BaseService | |||||
account = Account.find_by(username: username, domain: domain) | account = Account.find_by(username: username, domain: domain) | ||||
if account.nil? | if account.nil? | ||||
account = follow_remote_account_service.("#{username}@#{domain}", false) | |||||
account = follow_remote_account_service.("#{username}@#{domain}") | |||||
end | end | ||||
if salmon.verify(envelope, account.keypair) | if salmon.verify(envelope, account.keypair) | ||||
@@ -71,7 +71,7 @@ class ProcessInteractionService < BaseService | |||||
return if status.nil? | return if status.nil? | ||||
if account.id == status.account_id | if account.id == status.account_id | ||||
RemoveStatusService.new.(status) | |||||
remove_status_service.(status) | |||||
end | end | ||||
end | end | ||||
@@ -108,4 +108,8 @@ class ProcessInteractionService < BaseService | |||||
def update_remote_profile_service | def update_remote_profile_service | ||||
@update_remote_profile_service ||= UpdateRemoteProfileService.new | @update_remote_profile_service ||= UpdateRemoteProfileService.new | ||||
end | end | ||||
def remove_status_service | |||||
@remove_status_service ||= RemoveStatusService.new | |||||
end | |||||
end | end |
@@ -0,0 +1,20 @@ | |||||
class SubscribeService < BaseService | |||||
def call(account) | |||||
account.secret = SecureRandom.hex | |||||
account.verify_token = SecureRandom.hex | |||||
subscription = account.subscription(api_subscription_url(account.id)) | |||||
response = subscription.subscribe | |||||
unless response.successful? | |||||
account.secret = '' | |||||
account.verify_token = '' | |||||
Rails.logger.debug "PuSH subscription request for #{account.acct} failed: #{response.message}" | |||||
end | |||||
account.save! | |||||
rescue HTTP::Error, OpenSSL::SSL::SSLError | |||||
Rails.logger.debug "PuSH subscription request for #{account.acct} could not be made due to HTTP or SSL error" | |||||
end | |||||
end |
@@ -0,0 +1,5 @@ | |||||
class AddSubscriptionExpiresAtToAccounts < ActiveRecord::Migration[5.0] | |||||
def change | |||||
add_column :accounts, :subscription_expires_at, :datetime, null: true, default: nil | |||||
end | |||||
end |
@@ -10,26 +10,26 @@ | |||||
# | # | ||||
# It's strongly recommended that you check this file into your version control system. | # It's strongly recommended that you check this file into your version control system. | ||||
ActiveRecord::Schema.define(version: 20160905150353) do | |||||
ActiveRecord::Schema.define(version: 20160919221059) do | |||||
# These are extensions that must be enabled in order to support this database | # These are extensions that must be enabled in order to support this database | ||||
enable_extension "plpgsql" | enable_extension "plpgsql" | ||||
create_table "accounts", force: :cascade do |t| | create_table "accounts", force: :cascade do |t| | ||||
t.string "username", default: "", null: false | |||||
t.string "username", default: "", null: false | |||||
t.string "domain" | t.string "domain" | ||||
t.string "verify_token", default: "", null: false | |||||
t.string "secret", default: "", null: false | |||||
t.string "verify_token", default: "", null: false | |||||
t.string "secret", default: "", null: false | |||||
t.text "private_key" | t.text "private_key" | ||||
t.text "public_key", default: "", null: false | |||||
t.string "remote_url", default: "", null: false | |||||
t.string "salmon_url", default: "", null: false | |||||
t.string "hub_url", default: "", null: false | |||||
t.datetime "created_at", null: false | |||||
t.datetime "updated_at", null: false | |||||
t.text "note", default: "", null: false | |||||
t.string "display_name", default: "", null: false | |||||
t.string "uri", default: "", null: false | |||||
t.text "public_key", default: "", null: false | |||||
t.string "remote_url", default: "", null: false | |||||
t.string "salmon_url", default: "", null: false | |||||
t.string "hub_url", default: "", null: false | |||||
t.datetime "created_at", null: false | |||||
t.datetime "updated_at", null: false | |||||
t.text "note", default: "", null: false | |||||
t.string "display_name", default: "", null: false | |||||
t.string "uri", default: "", null: false | |||||
t.string "url" | t.string "url" | ||||
t.string "avatar_file_name" | t.string "avatar_file_name" | ||||
t.string "avatar_content_type" | t.string "avatar_content_type" | ||||
@@ -40,6 +40,7 @@ ActiveRecord::Schema.define(version: 20160905150353) do | |||||
t.integer "header_file_size" | t.integer "header_file_size" | ||||
t.datetime "header_updated_at" | t.datetime "header_updated_at" | ||||
t.string "avatar_remote_url" | t.string "avatar_remote_url" | ||||
t.datetime "subscription_expires_at" | |||||
t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree | t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree | ||||
end | end | ||||
@@ -11,9 +11,16 @@ namespace :mastodon do | |||||
namespace :push do | namespace :push do | ||||
desc 'Unsubscribes from PuSH updates of feeds nobody follows locally' | desc 'Unsubscribes from PuSH updates of feeds nobody follows locally' | ||||
task clear: :environment do | task clear: :environment do | ||||
Account.where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) = 0').where.not(domain: nil).find_each do |a| | |||||
Account.remote.without_followers.find_each do |a| | |||||
a.subscription('').unsubscribe | a.subscription('').unsubscribe | ||||
a.update!(verify_token: '', secret: '') | |||||
a.update!(verify_token: '', secret: '', subscription_expires_at: nil) | |||||
end | |||||
end | |||||
desc 'Re-subscribes to soon expiring PuSH subscriptions' | |||||
task refresh: :environment do | |||||
Account.expiring(1.day.from_now).find_each do |a| | |||||
SubscribeService.new.(a) | |||||
end | end | ||||
end | end | ||||
end | end | ||||