Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/diaspora/diaspora.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api/openid_connect/authorization_point/endpoint.rb10
-rw-r--r--lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb4
-rw-r--r--lib/api/openid_connect/authorization_point/endpoint_start_point.rb6
-rw-r--r--lib/api/openid_connect/id_token.rb2
-rw-r--r--lib/api/openid_connect/protected_resource_endpoint.rb8
-rw-r--r--lib/api/openid_connect/token_endpoint.rb1
-rw-r--r--lib/api/paging/index_paginator.rb42
-rw-r--r--lib/api/paging/rest_paged_response_builder.rb50
-rw-r--r--lib/api/paging/rest_paginator_builder.rb82
-rw-r--r--lib/api/paging/time_paginator.rb109
-rw-r--r--lib/archive_importer.rb135
-rw-r--r--lib/archive_importer/archive_helper.rb49
-rw-r--r--lib/archive_importer/block_importer.rb30
-rw-r--r--lib/archive_importer/contact_importer.rb40
-rw-r--r--lib/archive_importer/entity_importer.rb33
-rw-r--r--lib/archive_importer/own_entity_importer.rb33
-rw-r--r--lib/archive_importer/own_relayable_importer.rb29
-rw-r--r--lib/archive_importer/post_importer.rb35
-rw-r--r--lib/archive_validator.rb60
-rw-r--r--lib/archive_validator/author_private_key_validator.rb16
-rw-r--r--lib/archive_validator/base_validator.rb27
-rw-r--r--lib/archive_validator/collection_validator.rb16
-rw-r--r--lib/archive_validator/contact_validator.rb41
-rw-r--r--lib/archive_validator/contacts_validator.rb13
-rw-r--r--lib/archive_validator/entities_helper.rb35
-rw-r--r--lib/archive_validator/others_relayables_validator.rb13
-rw-r--r--lib/archive_validator/own_relayable_validator.rb19
-rw-r--r--lib/archive_validator/post_validator.rb22
-rw-r--r--lib/archive_validator/posts_validator.rb13
-rw-r--r--lib/archive_validator/relayable_validator.rb61
-rw-r--r--lib/archive_validator/relayables_validator.rb13
-rw-r--r--lib/archive_validator/schema_validator.rb13
-rw-r--r--lib/configuration_methods.rb14
-rw-r--r--lib/diaspora/federated/fetchable.rb21
-rw-r--r--lib/diaspora/federation/dispatcher/public.rb7
-rw-r--r--lib/diaspora/federation/receive.rb41
-rw-r--r--lib/diaspora/markdownify/html.rb2
-rw-r--r--lib/diaspora/mentionable.rb14
-rw-r--r--lib/diaspora/mentions_container.rb5
-rw-r--r--lib/diaspora/message_renderer.rb2
-rw-r--r--lib/schemas/api_v1.json486
-rw-r--r--lib/stream.rb21
-rw-r--r--lib/stream/local_public.rb23
-rw-r--r--lib/tasks/accounts.rake44
44 files changed, 1663 insertions, 77 deletions
diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb
index c88a43a09..ef12179ff 100644
--- a/lib/api/openid_connect/authorization_point/endpoint.rb
+++ b/lib/api/openid_connect/authorization_point/endpoint.rb
@@ -47,12 +47,18 @@ module Api
end
def build_scopes(req)
- replace_profile_scope_with_specific_claims(req)
@scopes = req.scope.map {|scope|
scope.tap do |scope_name|
- req.invalid_scope! "Unknown scope: #{scope_name}" unless auth_scopes.include? scope_name
+ req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.unknown_scope", scope_name: scope_name) \
+ unless auth_scopes.include?(scope_name)
end
}
+
+ @scopes.push("public:read") unless @scopes.include?("public:read")
+ has_private_scope = @scopes.include?("private:read") || @scopes.include?("private:modify")
+ has_contacts_scope = @scopes.include? "contacts:read"
+ req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.private_contacts_linkage_error") \
+ if has_private_scope && !has_contacts_scope
end
def auth_scopes
diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb
index 9c4eadeea..7cdd02143 100644
--- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb
+++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb
@@ -21,10 +21,6 @@ module Api
end
end
- def replace_profile_scope_with_specific_claims(_req)
- # Empty
- end
-
def build_from_request_object(_req)
# Empty
end
diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb
index 7ce6b6b5d..f0dd6a6dc 100644
--- a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb
+++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb
@@ -16,12 +16,6 @@ module Api
@response_type = req.response_type
end
- def replace_profile_scope_with_specific_claims(req)
- profile_claims = %w(sub aud name nickname profile picture)
- scopes_as_claims = req.scope.flat_map {|scope| scope == "profile" ? profile_claims : [scope] }.uniq
- req.update_param("scope", scopes_as_claims)
- end
-
private
def build_request_object(req)
diff --git a/lib/api/openid_connect/id_token.rb b/lib/api/openid_connect/id_token.rb
index 44b957790..231507a29 100644
--- a/lib/api/openid_connect/id_token.rb
+++ b/lib/api/openid_connect/id_token.rb
@@ -53,7 +53,7 @@ module Api
def claims
sub = build_sub
@claims ||= {
- iss: Rails.application.routes.url_helpers.root_url,
+ iss: AppConfig.environment.url,
sub: sub,
aud: @authorization.o_auth_application.client_id,
exp: @expires_at.to_i,
diff --git a/lib/api/openid_connect/protected_resource_endpoint.rb b/lib/api/openid_connect/protected_resource_endpoint.rb
index b75513183..23be2fc4c 100644
--- a/lib/api/openid_connect/protected_resource_endpoint.rb
+++ b/lib/api/openid_connect/protected_resource_endpoint.rb
@@ -29,11 +29,15 @@ module Api
attr_reader :current_token
def require_access_token(required_scopes)
+ raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) unless
+ access_token?(required_scopes)
+ end
+
+ def access_token?(required_scopes)
@current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") unless
@current_token && @current_token.authorization
- raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) unless
- @current_token.authorization.try(:accessible?, required_scopes)
+ @current_token.authorization.try(:accessible?, required_scopes)
end
end
end
diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb
index b9b9d39e9..96fa44632 100644
--- a/lib/api/openid_connect/token_endpoint.rb
+++ b/lib/api/openid_connect/token_endpoint.rb
@@ -27,6 +27,7 @@ module Api
auth = Api::OpenidConnect::Authorization.with_redirect_uri(req.redirect_uri).use_code(req.code)
req.invalid_grant! if auth.blank?
res.access_token = auth.create_access_token
+ res.access_token.refresh_token = auth.refresh_token
if auth.accessible? "openid"
id_token = auth.create_id_token
res.id_token = id_token.to_jwt(access_token: res.access_token)
diff --git a/lib/api/paging/index_paginator.rb b/lib/api/paging/index_paginator.rb
new file mode 100644
index 000000000..8dc5108e0
--- /dev/null
+++ b/lib/api/paging/index_paginator.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Api
+ module Paging
+ class IndexPaginator
+ def initialize(query_base, current_page, limit)
+ @query_base = query_base
+ @current_page = current_page.to_i
+ @limit = limit.to_i
+ end
+
+ def page_data
+ @page_data ||= @query_base.paginate(page: @current_page, per_page: @limit)
+ @max_page = (@query_base.count * 1.0 / @limit * 1.0).ceil
+ @max_page = 1 if @max_page < 1
+ @page_data
+ end
+
+ def next_page(for_url=true)
+ page_data
+ return nil if for_url && @current_page == @max_page
+
+ return "page=#{@current_page + 1}" if for_url
+
+ IndexPaginator.new(@query_base, @current_page + 1, @limit)
+ end
+
+ def previous_page(for_url=true)
+ page_data
+ return nil if for_url && @current_page == 1
+
+ return "page=#{@current_page - 1}" if for_url
+
+ IndexPaginator.new(@query_base, @current_page - 1, @limit)
+ end
+
+ def filter_parameters(parameters)
+ parameters.delete(:page)
+ end
+ end
+ end
+end
diff --git a/lib/api/paging/rest_paged_response_builder.rb b/lib/api/paging/rest_paged_response_builder.rb
new file mode 100644
index 000000000..727a0180b
--- /dev/null
+++ b/lib/api/paging/rest_paged_response_builder.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Api
+ module Paging
+ class RestPagedResponseBuilder
+ def initialize(pager, request, allowed_params=nil)
+ @pager = pager
+ @base_url = request.original_url.split("?").first if request
+ @query_parameters = if allowed_params
+ allowed_params
+ elsif request&.query_parameters
+ request&.query_parameters
+ else
+ {}
+ end
+ end
+
+ def response
+ {
+ links: navigation_builder,
+ data: @pager.page_data
+ }
+ end
+
+ private
+
+ def navigation_builder
+ previous_page = @pager.previous_page
+ links = {}
+ links[:previous] = link_builder(previous_page) if previous_page
+
+ next_page = @pager.next_page
+ links[:next] = link_builder(next_page) if next_page
+
+ links
+ end
+
+ def link_builder(page_parameter)
+ "#{@base_url}?#{filtered_original_parameters}#{page_parameter}"
+ end
+
+ def filtered_original_parameters
+ @pager.filter_parameters(@query_parameters)
+ return "" if @query_parameters.empty?
+
+ @query_parameters.map {|k, v| "#{k}=#{v}" }.join("&") + "&"
+ end
+ end
+ end
+end
diff --git a/lib/api/paging/rest_paginator_builder.rb b/lib/api/paging/rest_paginator_builder.rb
new file mode 100644
index 000000000..36614b26d
--- /dev/null
+++ b/lib/api/paging/rest_paginator_builder.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module Api
+ module Paging
+ class RestPaginatorBuilder
+ MAX_LIMIT = 100
+ DEFAULT_LIMIT = 15
+
+ def initialize(base_query, request, allow_default_page=true, default_limit=DEFAULT_LIMIT)
+ @base_query = base_query
+ @request = request
+ @allow_default_page = allow_default_page
+ @default_limit = if default_limit < MAX_LIMIT && default_limit > 0
+ default_limit
+ else
+ DEFAULT_LIMIT
+ end
+ end
+
+ def index_pager(params)
+ current_page = current_page_settings(params)
+ paged_response_builder(IndexPaginator.new(@base_query, current_page, limit_settings(params)))
+ end
+
+ def time_pager(params, query_time_field="created_at", data_time_field=query_time_field)
+ is_descending, current_time = time_settings(params)
+ paged_response_builder(
+ TimePaginator.new(
+ query_base: @base_query,
+ query_time_field: query_time_field,
+ data_time_field: data_time_field,
+ current_time: current_time,
+ is_descending: is_descending,
+ limit: limit_settings(params)
+ )
+ )
+ end
+
+ private
+
+ def current_page_settings(params)
+ if params["page"]
+ requested_page = params["page"].to_i
+ requested_page = 1 if requested_page < 1
+ requested_page
+ elsif @allow_default_page
+ 1
+ else
+ raise ActionController::ParameterMissing
+ end
+ end
+
+ def paged_response_builder(paginator)
+ Api::Paging::RestPagedResponseBuilder.new(paginator, @request)
+ end
+
+ def time_settings(params)
+ time_params = params.permit("before", "after")
+ time_params["before"] = (Time.current + 1.year).iso8601 if time_params.empty? && @allow_default_page
+
+ raise "Missing time parameters for query building" if time_params.empty?
+
+ if time_params["before"]
+ is_descending = true
+ current_time = Time.iso8601(time_params["before"])
+ else
+ is_descending = false
+ current_time = Time.iso8601(time_params["after"])
+ end
+ [is_descending, current_time]
+ end
+
+ def limit_settings(params)
+ requested_limit = params["per_page"].to_i if params["per_page"]
+ return @default_limit unless requested_limit
+
+ requested_limit = [1, requested_limit].max
+ [requested_limit, MAX_LIMIT].min
+ end
+ end
+ end
+end
diff --git a/lib/api/paging/time_paginator.rb b/lib/api/paging/time_paginator.rb
new file mode 100644
index 000000000..e9293aa32
--- /dev/null
+++ b/lib/api/paging/time_paginator.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+module Api
+ module Paging
+ class TimePaginator
+ def initialize(opts={})
+ @query_base = opts[:query_base]
+ @query_time_field = opts[:query_time_field]
+ @data_time_field = opts[:data_time_field]
+ @current_time = opts[:current_time]
+ @limit = opts[:limit]
+ @is_descending = opts[:is_descending]
+ direction = if @is_descending
+ "<"
+ else
+ ">"
+ end
+ @time_query_string = "#{@query_time_field} #{direction} ?"
+ @sort_string = if @is_descending
+ "#{@query_time_field} DESC"
+ else
+ "#{@query_time_field} ASC"
+ end
+ end
+
+ def page_data
+ return @data if @data
+
+ @data = @query_base.where([@time_query_string, @current_time.iso8601(3)]).limit(@limit).order(@sort_string)
+ time_data = @data.map {|d| d[@data_time_field] }.sort
+ @min_time = time_data.first
+ @max_time = time_data.last + 0.001.seconds if time_data.last
+
+ @data
+ end
+
+ def next_page(for_url=true)
+ page_data
+ return nil unless next_time
+
+ return next_page_as_query_parameter if for_url
+
+ TimePaginator.new(
+ query_base: @query_base,
+ query_time_field: @query_time_field,
+ query_data_field: @data_time_field,
+ current_time: next_time,
+ is_descending: @is_descending,
+ limit: @limit
+ )
+ end
+
+ def previous_page(for_url=true)
+ page_data
+ return nil unless previous_time
+
+ return previous_page_as_query_parameter if for_url
+
+ TimePaginator.new(
+ query_base: @query_base,
+ query_time_field: @query_time_field,
+ query_data_field: @data_time_field,
+ current_time: previous_time,
+ is_descending: !@is_descending,
+ limit: @limit
+ )
+ end
+
+ def filter_parameters(parameters)
+ parameters.delete(:before)
+ parameters.delete(:after)
+ end
+
+ private
+
+ def next_time
+ if @is_descending
+ @min_time
+ else
+ @max_time
+ end
+ end
+
+ def previous_time
+ if @is_descending
+ @max_time
+ else
+ @min_time
+ end
+ end
+
+ def next_page_as_query_parameter
+ if @is_descending
+ "before=#{next_time.iso8601(3)}"
+ else
+ "after=#{next_time.iso8601(3)}"
+ end
+ end
+
+ def previous_page_as_query_parameter
+ if @is_descending
+ "after=#{previous_time.iso8601(3)}"
+ else
+ "before=#{previous_time.iso8601(3)}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/archive_importer.rb b/lib/archive_importer.rb
new file mode 100644
index 000000000..4b307aa88
--- /dev/null
+++ b/lib/archive_importer.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ include ArchiveHelper
+ include Diaspora::Logging
+
+ attr_accessor :user
+
+ def initialize(archive_hash)
+ @archive_hash = archive_hash
+ end
+
+ def import
+ import_tag_followings
+ import_aspects
+ import_contacts
+ import_posts
+ import_relayables
+ import_subscriptions
+ import_others_relayables
+ import_blocks
+ end
+
+ def create_user(attr)
+ allowed_keys = %w[
+ email strip_exif show_community_spotlight_in_stream language disable_mail auto_follow_back
+ ]
+ data = convert_keys(archive_hash["user"], allowed_keys)
+ # setting getting_started to false as the user doesn't need to see the getting started wizard
+ data.merge!(
+ username: attr[:username],
+ password: attr[:password],
+ password_confirmation: attr[:password],
+ getting_started: false,
+ person: {
+ profile_attributes: profile_attributes
+ }
+ )
+ self.user = User.build(data)
+ user.save!
+ end
+
+ private
+
+ attr_reader :archive_hash
+
+ def profile_attributes
+ allowed_keys = %w[first_name last_name image_url bio gender location birthday searchable nsfw tag_string]
+ profile_data = archive_hash["user"]["profile"]["entity_data"]
+ convert_keys(profile_data, allowed_keys).tap do |attrs|
+ attrs[:public_details] = profile_data["public"]
+ end
+ end
+
+ def import_contacts
+ import_collection(contacts, ContactImporter)
+ end
+
+ def set_auto_follow_back_aspect
+ name = archive_hash["user"]["auto_follow_back_aspect"]
+ return if name.nil?
+
+ aspect = user.aspects.find_by(name: name)
+ user.update(auto_follow_back_aspect: aspect) if aspect
+ end
+
+ def import_aspects
+ contact_groups.each do |group|
+ begin
+ user.aspects.create!(group.slice("name"))
+ rescue ActiveRecord::RecordInvalid => e
+ logger.warn "#{self}: #{e}"
+ end
+ end
+ set_auto_follow_back_aspect
+ end
+
+ def import_posts
+ import_collection(posts, PostImporter)
+ end
+
+ def import_relayables
+ import_collection(relayables, OwnRelayableImporter)
+ end
+
+ def import_others_relayables
+ import_collection(others_relayables, EntityImporter)
+ end
+
+ def import_blocks
+ import_collection(blocks, BlockImporter)
+ end
+
+ def import_collection(collection, importer_class)
+ collection.each do |object|
+ importer_class.new(object, user).import
+ end
+ end
+
+ def import_tag_followings
+ archive_hash.fetch("user").fetch("followed_tags", []).each do |tag_name|
+ begin
+ tag = ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name)
+ user.tag_followings.create!(tag: tag)
+ rescue ActiveRecord::RecordInvalid => e
+ logger.warn "#{self}: #{e}"
+ end
+ end
+ end
+
+ def import_subscriptions
+ post_subscriptions.each do |post_guid|
+ post = Post.find_or_fetch_by(archive_author_diaspora_id, post_guid)
+ if post.nil?
+ logger.warn "#{self}: post with guid #{post_guid} not found, can't subscribe"
+ next
+ end
+ begin
+ user.participations.create!(target: post)
+ rescue ActiveRecord::RecordInvalid => e
+ logger.warn "#{self}: #{e}"
+ end
+ end
+ end
+
+ def convert_keys(hash, allowed_keys)
+ hash
+ .slice(*allowed_keys)
+ .symbolize_keys
+ end
+
+ def to_s
+ "#{self.class}:#{archive_author_diaspora_id}:#{user.diaspora_handle}"
+ end
+end
diff --git a/lib/archive_importer/archive_helper.rb b/lib/archive_importer/archive_helper.rb
new file mode 100644
index 000000000..9813c8219
--- /dev/null
+++ b/lib/archive_importer/archive_helper.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ module ArchiveHelper
+ def posts
+ @posts ||= archive_hash.fetch("user").fetch("posts", [])
+ end
+
+ def relayables
+ @relayables ||= archive_hash.fetch("user").fetch("relayables", [])
+ end
+
+ def others_relayables
+ @others_relayables ||= archive_hash.fetch("others_data", {}).fetch("relayables", [])
+ end
+
+ def post_subscriptions
+ archive_hash.fetch("user").fetch("post_subscriptions", [])
+ end
+
+ def contacts
+ archive_hash.fetch("user").fetch("contacts", [])
+ end
+
+ def contact_groups
+ @contact_groups ||= archive_hash.fetch("user").fetch("contact_groups", [])
+ end
+
+ def archive_author_diaspora_id
+ @archive_author_diaspora_id ||= archive_hash.fetch("user").fetch("profile").fetch("entity_data").fetch("author")
+ end
+
+ def person
+ @person ||= Person.find_or_fetch_by_identifier(archive_author_diaspora_id)
+ end
+
+ def blocks
+ @blocks ||= archive_hash.fetch("user").fetch("blocks", [])
+ end
+
+ def private_key
+ OpenSSL::PKey::RSA.new(serialized_private_key)
+ end
+
+ def serialized_private_key
+ archive_hash.fetch("user").fetch("private_key")
+ end
+ end
+end
diff --git a/lib/archive_importer/block_importer.rb b/lib/archive_importer/block_importer.rb
new file mode 100644
index 000000000..882dd6986
--- /dev/null
+++ b/lib/archive_importer/block_importer.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ class BlockImporter
+ include Diaspora::Logging
+ attr_reader :json, :user
+
+ def initialize(json, user)
+ @json = json
+ @user = user
+ end
+
+ def import
+ p = Person.find_or_fetch_by_identifier(json)
+ migrant_person = handle_migrant_person(p)
+ user.blocks.create(person_id: migrant_person.id)
+ rescue ActiveRecord::RecordInvalid,
+ DiasporaFederation::Discovery::DiscoveryError => e
+ logger.warn "#{self}: #{e}"
+ end
+
+ private
+
+ def handle_migrant_person(person)
+ return person if person.account_migration.nil?
+
+ person.account_migration.newest_person
+ end
+ end
+end
diff --git a/lib/archive_importer/contact_importer.rb b/lib/archive_importer/contact_importer.rb
new file mode 100644
index 000000000..b64dfe7b7
--- /dev/null
+++ b/lib/archive_importer/contact_importer.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ class ContactImporter
+ include Diaspora::Logging
+
+ def initialize(json, user)
+ @json = json
+ @user = user
+ end
+
+ attr_reader :json
+ attr_reader :user
+
+ def import
+ @imported_contact = create_contact
+ add_to_aspects
+ rescue ActiveRecord::RecordInvalid => e
+ logger.warn "#{self}: #{e}"
+ end
+
+ private
+
+ def add_to_aspects
+ json.fetch("contact_groups_membership", []).each do |group_name|
+ aspect = user.aspects.find_by(name: group_name)
+ if aspect.nil?
+ logger.warn "#{self}: aspect \"#{group_name}\" is missing"
+ next
+ end
+ @imported_contact.aspects << aspect
+ end
+ end
+
+ def create_contact
+ person = Person.by_account_identifier(json.fetch("account_id"))
+ user.contacts.create!(person_id: person.id, sharing: json.fetch("sharing"), receiving: json.fetch("receiving"))
+ end
+ end
+end
diff --git a/lib/archive_importer/entity_importer.rb b/lib/archive_importer/entity_importer.rb
new file mode 100644
index 000000000..22c6f49e4
--- /dev/null
+++ b/lib/archive_importer/entity_importer.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ class EntityImporter
+ include ArchiveValidator::EntitiesHelper
+ include Diaspora::Logging
+
+ def initialize(json, user)
+ @json = json
+ @user = user
+ end
+
+ def import
+ self.persisted_object = Diaspora::Federation::Receive.perform(entity, skip_relaying: true)
+ rescue DiasporaFederation::Entities::Signable::SignatureVerificationFailed,
+ DiasporaFederation::Discovery::InvalidDocument,
+ DiasporaFederation::Discovery::DiscoveryError,
+ DiasporaFederation::Federation::Fetcher::NotFetchable,
+ OwnRelayableImporter::NoParentError,
+ ActiveRecord::RecordInvalid => e
+ logger.warn "#{self}: #{e}"
+ self.persisted_object = nil
+ end
+
+ attr_reader :json
+ attr_reader :user
+ attr_accessor :persisted_object
+
+ def entity
+ entity_class.from_json(json)
+ end
+ end
+end
diff --git a/lib/archive_importer/own_entity_importer.rb b/lib/archive_importer/own_entity_importer.rb
new file mode 100644
index 000000000..736115f63
--- /dev/null
+++ b/lib/archive_importer/own_entity_importer.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ class OwnEntityImporter < EntityImporter
+ def import
+ substitute_author
+ super
+ rescue Diaspora::Federation::InvalidAuthor
+ return if real_author == old_author_id
+
+ logger.warn "#{self.class}: attempt to import an entity with guid \"#{guid}\" which belongs to #{real_author}"
+ end
+
+ private
+
+ def substitute_author
+ @old_author_id = entity_data["author"]
+ entity_data["author"] = user.diaspora_handle
+ end
+
+ attr_reader :old_author_id
+
+ def persisted_object
+ return @persisted_object if defined?(@persisted_object)
+
+ @persisted_object = (instance if real_author == old_author_id)
+ end
+
+ def real_author
+ instance.author.diaspora_handle
+ end
+ end
+end
diff --git a/lib/archive_importer/own_relayable_importer.rb b/lib/archive_importer/own_relayable_importer.rb
new file mode 100644
index 000000000..c9ff6f82f
--- /dev/null
+++ b/lib/archive_importer/own_relayable_importer.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ class OwnRelayableImporter < OwnEntityImporter
+ class NoParentError < RuntimeError; end
+
+ def entity
+ fetch_parent(symbolized_entity_data)
+ entity_class.new(symbolized_entity_data)
+ end
+
+ private
+
+ def symbolized_entity_data
+ @symbolized_entity_data ||= entity_data.slice(*entity_class.class_props.keys.map(&:to_s)).symbolize_keys
+ end
+
+ # Copied over from DiasporaFederation::Entities::Relayable
+ def fetch_parent(data)
+ type = data.fetch(:parent_type) {
+ break entity_class::PARENT_TYPE if entity_class.const_defined?(:PARENT_TYPE)
+ }
+ entity = Diaspora::Federation::Mappings.model_class_for(type).find_by(guid: data.fetch(:parent_guid))
+ raise NoParentError if entity.nil?
+
+ data[:parent] = Diaspora::Federation::Entities.related_entity(entity)
+ end
+ end
+end
diff --git a/lib/archive_importer/post_importer.rb b/lib/archive_importer/post_importer.rb
new file mode 100644
index 000000000..79d592521
--- /dev/null
+++ b/lib/archive_importer/post_importer.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class ArchiveImporter
+ class PostImporter < OwnEntityImporter
+ include Diaspora::Logging
+
+ def import
+ super
+ import_subscriptions if persisted_object
+ end
+
+ private
+
+ def substitute_author
+ super
+ return unless entity_type == "status_message"
+
+ entity_data["photos"].each do |photo|
+ photo["entity_data"]["author"] = user.diaspora_handle
+ end
+ end
+
+ def import_subscriptions
+ json.fetch("subscribed_users_ids", []).each do |diaspora_id|
+ begin
+ person = Person.find_or_fetch_by_identifier(diaspora_id)
+ person = person.account_migration.newest_person unless person.account_migration.nil?
+ next if person.closed_account?
+ # TODO: unless person.nil? import subscription: subscription import is not supported yet
+ rescue DiasporaFederation::Discovery::DiscoveryError
+ end
+ end
+ end
+ end
+end
diff --git a/lib/archive_validator.rb b/lib/archive_validator.rb
new file mode 100644
index 000000000..4dda3df2e
--- /dev/null
+++ b/lib/archive_validator.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require "yajl"
+
+# ArchiveValidator checks for errors in archive. It also find non-critical problems and fixes them in the archive hash
+# so that the ArchiveImporter doesn't have to handle this issues. Non-critical problems found are indicated as warnings.
+# Also it performs necessary data fetch where required.
+class ArchiveValidator
+ include ArchiveImporter::ArchiveHelper
+
+ def initialize(archive)
+ @archive = archive
+ end
+
+ def validate
+ run_validators(CRITICAL_VALIDATORS, errors)
+ run_validators(NON_CRITICAL_VALIDATORS, warnings)
+ rescue KeyError => e
+ errors.push("Missing mandatory data: #{e}")
+ rescue Yajl::ParseError => e
+ errors.push("Bad JSON provided: #{e}")
+ end
+
+ def errors
+ @errors ||= []
+ end
+
+ def warnings
+ @warnings ||= []
+ end
+
+ def archive_hash
+ @archive_hash ||= Yajl::Parser.new.parse(archive)
+ end
+
+ CRITICAL_VALIDATORS = [
+ SchemaValidator,
+ AuthorPrivateKeyValidator
+ ].freeze
+
+ NON_CRITICAL_VALIDATORS = [
+ ContactsValidator,
+ PostsValidator,
+ RelayablesValidator,
+ OthersRelayablesValidator
+ ].freeze
+
+ private_constant :CRITICAL_VALIDATORS, :NON_CRITICAL_VALIDATORS
+
+ private
+
+ attr_reader :archive
+
+ def run_validators(list, messages)
+ list.each do |validator_class|
+ validator = validator_class.new(archive_hash)
+ messages.concat(validator.messages)
+ end
+ end
+end
diff --git a/lib/archive_validator/author_private_key_validator.rb b/lib/archive_validator/author_private_key_validator.rb
new file mode 100644
index 000000000..7a4c747e3
--- /dev/null
+++ b/lib/archive_validator/author_private_key_validator.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class AuthorPrivateKeyValidator < BaseValidator
+ include Diaspora::Logging
+
+ def validate
+ return if person.public_key.export == private_key.public_key.export
+
+ messages.push("Private key in the archive doesn't match the known key of #{person.diaspora_handle}")
+ rescue DiasporaFederation::Discovery::DiscoveryError
+ logger.info "Archive author couldn't be fetched (old home pod is down?), will continue with data"\
+ " import only"
+ end
+ end
+end
diff --git a/lib/archive_validator/base_validator.rb b/lib/archive_validator/base_validator.rb
new file mode 100644
index 000000000..183cafa22
--- /dev/null
+++ b/lib/archive_validator/base_validator.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class BaseValidator
+ include ArchiveImporter::ArchiveHelper
+ attr_reader :archive_hash
+
+ def initialize(archive_hash)
+ @archive_hash = archive_hash
+ validate
+ end
+
+ def messages
+ @messages ||= []
+ end
+
+ def valid?
+ @valid.nil? ? messages.empty? : @valid
+ end
+
+ private
+
+ attr_writer :valid
+
+ def validate; end
+ end
+end
diff --git a/lib/archive_validator/collection_validator.rb b/lib/archive_validator/collection_validator.rb
new file mode 100644
index 000000000..e03c2fe2f
--- /dev/null
+++ b/lib/archive_validator/collection_validator.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class CollectionValidator < BaseValidator
+ # Runs validations over each element in collection and removes every element
+ # which fails the validations. Any messages produced by the entity_validator are
+ # concatenated to the messages of the CollectionValidator instance.
+ def validate
+ collection.keep_if do |item|
+ subvalidator = entity_validator.new(archive_hash, item)
+ messages.concat(subvalidator.messages)
+ subvalidator.valid?
+ end
+ end
+ end
+end
diff --git a/lib/archive_validator/contact_validator.rb b/lib/archive_validator/contact_validator.rb
new file mode 100644
index 000000000..4b79b0d30
--- /dev/null
+++ b/lib/archive_validator/contact_validator.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class ContactValidator < BaseValidator
+ def initialize(archive_hash, contact)
+ @contact = contact
+ super(archive_hash)
+ end
+
+ private
+
+ def validate
+ handle_migrant_contact
+ self.valid = account_open?
+ rescue DiasporaFederation::Discovery::DiscoveryError => e
+ messages.push("#{self.class}: failed to fetch person #{diaspora_id}: #{e}")
+ self.valid = false
+ end
+
+ attr_reader :contact
+
+ def diaspora_id
+ contact.fetch("account_id")
+ end
+
+ def handle_migrant_contact
+ return if person.account_migration.nil?
+
+ contact["account_id"] = person.account_migration.newest_person.diaspora_handle
+ @person = nil
+ end
+
+ def person
+ @person ||= Person.find_or_fetch_by_identifier(diaspora_id)
+ end
+
+ def account_open?
+ !person.closed_account? || (messages.push("#{self.class}: account #{diaspora_id} is closed") && false)
+ end
+ end
+end
diff --git a/lib/archive_validator/contacts_validator.rb b/lib/archive_validator/contacts_validator.rb
new file mode 100644
index 000000000..f7968012d
--- /dev/null
+++ b/lib/archive_validator/contacts_validator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class ContactsValidator < CollectionValidator
+ def collection
+ contacts
+ end
+
+ def entity_validator
+ ContactValidator
+ end
+ end
+end
diff --git a/lib/archive_validator/entities_helper.rb b/lib/archive_validator/entities_helper.rb
new file mode 100644
index 000000000..40c47a59a
--- /dev/null
+++ b/lib/archive_validator/entities_helper.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ module EntitiesHelper
+ private
+
+ def instance
+ @instance ||= model_class.find_by(guid: guid)
+ end
+
+ def entity_type
+ json.fetch("entity_type")
+ end
+
+ def entity_data
+ json.fetch("entity_data")
+ end
+
+ def model_class
+ @model_class ||= Diaspora::Federation::Mappings.model_class_for(entity_type.camelize)
+ end
+
+ def entity_class
+ DiasporaFederation::Entity.entity_class(entity_type)
+ end
+
+ def guid
+ @guid ||= entity_data.fetch("guid")
+ end
+
+ def to_s
+ "#{entity_class.class_name}:#{guid}"
+ end
+ end
+end
diff --git a/lib/archive_validator/others_relayables_validator.rb b/lib/archive_validator/others_relayables_validator.rb
new file mode 100644
index 000000000..7352c9957
--- /dev/null
+++ b/lib/archive_validator/others_relayables_validator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class OthersRelayablesValidator < CollectionValidator
+ def collection
+ others_relayables
+ end
+
+ def entity_validator
+ RelayableValidator
+ end
+ end
+end
diff --git a/lib/archive_validator/own_relayable_validator.rb b/lib/archive_validator/own_relayable_validator.rb
new file mode 100644
index 000000000..01b8dfdbe
--- /dev/null
+++ b/lib/archive_validator/own_relayable_validator.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class OwnRelayableValidator < RelayableValidator
+ private
+
+ def post_find_by_guid(guid)
+ super || by_guid(Post, guid)
+ end
+
+ def post_find_by_poll_guid(guid)
+ super || by_guid(Poll, guid)&.status_message
+ end
+
+ def by_guid(klass, guid)
+ klass.find_or_fetch_by(archive_author_diaspora_id, guid)
+ end
+ end
+end
diff --git a/lib/archive_validator/post_validator.rb b/lib/archive_validator/post_validator.rb
new file mode 100644
index 000000000..f1f2bf142
--- /dev/null
+++ b/lib/archive_validator/post_validator.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class PostValidator < BaseValidator
+ include EntitiesHelper
+
+ def initialize(archive_hash, post)
+ @json = post
+ super(archive_hash)
+ end
+
+ private
+
+ def validate
+ return unless entity_type == "reshare" && entity_data["root_guid"].nil?
+
+ messages.push("reshare #{self} doesn't have a root, ignored")
+ end
+
+ attr_reader :json
+ end
+end
diff --git a/lib/archive_validator/posts_validator.rb b/lib/archive_validator/posts_validator.rb
new file mode 100644
index 000000000..fb2a1d744
--- /dev/null
+++ b/lib/archive_validator/posts_validator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class PostsValidator < CollectionValidator
+ def collection
+ posts
+ end
+
+ def entity_validator
+ PostValidator
+ end
+ end
+end
diff --git a/lib/archive_validator/relayable_validator.rb b/lib/archive_validator/relayable_validator.rb
new file mode 100644
index 000000000..e026ff2a0
--- /dev/null
+++ b/lib/archive_validator/relayable_validator.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ # We have to validate relayables before import because during import we'll not be able to fetch parent anymore
+ # because parent author will point to ourselves.
+ class RelayableValidator < BaseValidator
+ include EntitiesHelper
+
+ def initialize(archive_hash, relayable)
+ @relayable = relayable
+ super(archive_hash)
+ end
+
+ private
+
+ def validate
+ self.valid = parent_present?
+ end
+
+ attr_reader :relayable
+ alias json relayable
+
+ # Common methods used by subclasses:
+
+ def missing_parent_message
+ messages.push("Parent entity for #{self} is missing. Impossible to import, ignoring.")
+ end
+
+ def parent_present?
+ parent.present? || (missing_parent_message && false)
+ end
+
+ def parent
+ @parent ||= find_parent
+ end
+
+ def find_parent
+ if entity_type == "poll_participation"
+ post_find_by_poll_guid(parent_guid)
+ else
+ post_find_by_guid(parent_guid)
+ end
+ end
+
+ def parent_guid
+ entity_data.fetch("parent_guid")
+ end
+
+ def post_find_by_guid(guid)
+ posts.find {|post|
+ post.fetch("entity_data").fetch("guid") == guid
+ }
+ end
+
+ def post_find_by_poll_guid(guid)
+ posts.find {|post|
+ post.fetch("entity_data").fetch("poll", nil)&.fetch("entity_data", nil)&.fetch("guid", nil) == guid
+ }
+ end
+ end
+end
diff --git a/lib/archive_validator/relayables_validator.rb b/lib/archive_validator/relayables_validator.rb
new file mode 100644
index 000000000..ecfdb6a04
--- /dev/null
+++ b/lib/archive_validator/relayables_validator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class RelayablesValidator < CollectionValidator
+ def collection
+ relayables
+ end
+
+ def entity_validator
+ OwnRelayableValidator
+ end
+ end
+end
diff --git a/lib/archive_validator/schema_validator.rb b/lib/archive_validator/schema_validator.rb
new file mode 100644
index 000000000..1d4d1ab64
--- /dev/null
+++ b/lib/archive_validator/schema_validator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ArchiveValidator
+ class SchemaValidator < BaseValidator
+ JSON_SCHEMA = "lib/schemas/archive-format.json"
+
+ def validate
+ return if JSON::Validator.validate(JSON_SCHEMA, archive_hash)
+
+ messages.push("Archive schema validation failed")
+ end
+ end
+end
diff --git a/lib/configuration_methods.rb b/lib/configuration_methods.rb
index 58a050f8c..22a5e1a16 100644
--- a/lib/configuration_methods.rb
+++ b/lib/configuration_methods.rb
@@ -50,6 +50,15 @@ module Configuration
self["services.#{service}.authorized"] == true
end
+ def local_posts_stream?(user)
+ return true if settings.enable_local_posts_stream == "admins" &&
+ Role.is_admin?(user)
+ return true if settings.enable_local_posts_stream == "moderators" &&
+ (Role.moderator?(user) || Role.is_admin?(user))
+
+ settings.enable_local_posts_stream == "everyone"
+ end
+
def secret_token
if heroku?
return ENV["SECRET_TOKEN"] if ENV["SECRET_TOKEN"]
@@ -128,11 +137,6 @@ module Configuration
end
def bitcoin_donation_address
- if AppConfig.settings.bitcoin_wallet_id.present?
- warn "WARNING: bitcoin_wallet_id is now bitcoin_address. Change in diaspora.yml."
- return AppConfig.settings.bitcoin_wallet_id
- end
-
if AppConfig.settings.bitcoin_address.present?
AppConfig.settings.bitcoin_address
end
diff --git a/lib/diaspora/federated/fetchable.rb b/lib/diaspora/federated/fetchable.rb
new file mode 100644
index 000000000..b0ab24290
--- /dev/null
+++ b/lib/diaspora/federated/fetchable.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Diaspora
+ module Federated
+ module Fetchable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def find_or_fetch_by(diaspora_id, guid)
+ instance = find_by(guid: guid)
+ return instance if instance.present?
+
+ DiasporaFederation::Federation::Fetcher.fetch_public(diaspora_id, to_s, guid)
+ find_by(guid: guid)
+ rescue DiasporaFederation::Federation::Fetcher::NotFetchable
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/lib/diaspora/federation/dispatcher/public.rb b/lib/diaspora/federation/dispatcher/public.rb
index 51b7b5454..a82dfe09a 100644
--- a/lib/diaspora/federation/dispatcher/public.rb
+++ b/lib/diaspora/federation/dispatcher/public.rb
@@ -12,7 +12,7 @@ module Diaspora
end
def deliver_to_remote(people)
- targets = target_urls(people) + additional_target_urls
+ targets = target_urls(people)
return if targets.empty?
@@ -25,11 +25,6 @@ module Diaspora
active.map {|pod| pod.url_to("/receive/public") }
end
- def additional_target_urls
- return [] unless AppConfig.relay.outbound.send? && object.instance_of?(StatusMessage)
- [AppConfig.relay.outbound.url]
- end
-
def deliver_to_hub
logger.debug "deliver to pubsubhubbub sender: #{sender.diaspora_handle}"
Workers::PublishToHub.perform_async(sender.atom_url)
diff --git a/lib/diaspora/federation/receive.rb b/lib/diaspora/federation/receive.rb
index edbdf2b9f..5f63b72af 100644
--- a/lib/diaspora/federation/receive.rb
+++ b/lib/diaspora/federation/receive.rb
@@ -5,8 +5,8 @@ module Diaspora
module Receive
extend Diaspora::Logging
- def self.perform(entity)
- public_send(Mappings.receiver_for(entity), entity)
+ def self.perform(entity, opts={})
+ public_send(Mappings.receiver_for(entity), entity, opts)
end
def self.handle_closed_recipient(sender, recipient)
@@ -26,9 +26,9 @@ module Diaspora
logger.warn "ignoring error on receive AccountDeletion:#{entity.author}: #{e.class}: #{e.message}"
end
- def self.account_migration(entity)
+ def self.account_migration(entity, opts)
old_person = author_of(entity)
- profile = profile(entity.profile)
+ profile = profile(entity.profile, opts)
return if AccountMigration.exists?(old_person: old_person, new_person: profile.person)
AccountMigration.create!(old_person: old_person, new_person: profile.person).tap do |migration|
@@ -42,8 +42,8 @@ module Diaspora
nil
end
- def self.comment(entity)
- receive_relayable(Comment, entity) do
+ def self.comment(entity, opts)
+ receive_relayable(Comment, entity, opts) do
Comment.new(
author: author_of(entity),
guid: entity.guid,
@@ -54,7 +54,7 @@ module Diaspora
end
end
- def self.contact(entity)
+ def self.contact(entity, _opts)
recipient = Person.find_by(diaspora_handle: entity.recipient).owner
if entity.sharing
Contact.create_or_update_sharing_contact(recipient, author_of(entity))
@@ -64,7 +64,7 @@ module Diaspora
end
end
- def self.conversation(entity)
+ def self.conversation(entity, _opts)
author = author_of(entity)
ignore_existing_guid(Conversation, entity.guid, author) do
Conversation.create!(
@@ -78,8 +78,8 @@ module Diaspora
end
end
- def self.like(entity)
- receive_relayable(Like, entity) do
+ def self.like(entity, opts)
+ receive_relayable(Like, entity, opts) do
Like.new(
author: author_of(entity),
guid: entity.guid,
@@ -89,13 +89,13 @@ module Diaspora
end
end
- def self.message(entity)
+ def self.message(entity, _opts)
ignore_existing_guid(Message, entity.guid, author_of(entity)) do
build_message(entity).tap(&:save!)
end
end
- def self.participation(entity)
+ def self.participation(entity, _opts)
author = author_of(entity)
ignore_existing_guid(Participation, entity.guid, author) do
Participation.create!(
@@ -106,7 +106,7 @@ module Diaspora
end
end
- def self.photo(entity)
+ def self.photo(entity, _opts)
author = author_of(entity)
persisted_photo = load_from_database(Photo, entity.guid, author)
@@ -128,8 +128,8 @@ module Diaspora
end
end
- def self.poll_participation(entity)
- receive_relayable(PollParticipation, entity) do
+ def self.poll_participation(entity, opts)
+ receive_relayable(PollParticipation, entity, opts) do
PollParticipation.new(
author: author_of(entity),
guid: entity.guid,
@@ -139,7 +139,7 @@ module Diaspora
end
end
- def self.profile(entity)
+ def self.profile(entity, _opts)
author_of(entity).profile.tap do |profile|
profile.update_attributes(
first_name: entity.first_name,
@@ -159,7 +159,7 @@ module Diaspora
end
end
- def self.reshare(entity)
+ def self.reshare(entity, _opts)
author = author_of(entity)
ignore_existing_guid(Reshare, entity.guid, author) do
Reshare.create!(
@@ -192,7 +192,7 @@ module Diaspora
end
end
- def self.status_message(entity)
+ def self.status_message(entity, _opts) # rubocop:disable Metrics/AbcSize
try_load_existing_guid(StatusMessage, entity.guid, author_of(entity)) do
StatusMessage.new(
author: author_of(entity),
@@ -271,8 +271,9 @@ module Diaspora
end
end
- private_class_method def self.receive_relayable(klass, entity)
- save_relayable(klass, entity) { yield }.tap {|relayable| relay_relayable(relayable) if relayable }
+ private_class_method def self.receive_relayable(klass, entity, opts)
+ save_relayable(klass, entity) { yield }
+ .tap {|relayable| relay_relayable(relayable) if relayable && !opts[:skip_relaying] }
end
private_class_method def self.save_relayable(klass, entity)
diff --git a/lib/diaspora/markdownify/html.rb b/lib/diaspora/markdownify/html.rb
index 19d4dd649..610c45223 100644
--- a/lib/diaspora/markdownify/html.rb
+++ b/lib/diaspora/markdownify/html.rb
@@ -6,7 +6,7 @@ module Diaspora
include ActionView::Helpers::TextHelper
def autolink link, type
- Twitter::Autolink.auto_link_urls(
+ Twitter::TwitterText::Autolink.auto_link_urls(
link,
url_target: "_blank",
link_attribute_block: lambda {|_, attr| attr[:rel] += " noopener noreferrer" }
diff --git a/lib/diaspora/mentionable.rb b/lib/diaspora/mentionable.rb
index 59f2da8f0..fe6396a9d 100644
--- a/lib/diaspora/mentionable.rb
+++ b/lib/diaspora/mentionable.rb
@@ -71,20 +71,6 @@ module Diaspora::Mentionable
}
end
- # Regex to find mentions with new syntax, only used for backporting to old syntax
- NEW_SYNTAX_REGEX = /@\{[^\} ]+\}/
-
- # replaces new syntax with old syntax, to be compatible with old pods
- # @deprecated remove when most of the posts can handle the new syntax
- def self.backport_mention_syntax(text)
- text.to_s.gsub(NEW_SYNTAX_REGEX) do |match_str|
- _, diaspora_id = mention_attrs(match_str)
- person = find_or_fetch_person_by_identifier(diaspora_id)
- old_syntax = "@{#{person.name.delete('{}')}; #{diaspora_id}}" if person
- old_syntax || match_str
- end
- end
-
private_class_method def self.find_or_fetch_person_by_identifier(identifier)
Person.find_or_fetch_by_identifier(identifier) if Validation::Rule::DiasporaId.new.valid_value?(identifier)
rescue DiasporaFederation::Discovery::DiscoveryError
diff --git a/lib/diaspora/mentions_container.rb b/lib/diaspora/mentions_container.rb
index b02dddb20..e3f3ab9a1 100644
--- a/lib/diaspora/mentions_container.rb
+++ b/lib/diaspora/mentions_container.rb
@@ -5,11 +5,6 @@ module Diaspora
extend ActiveSupport::Concern
included do
- before_create do
- # TODO: remove when most of the posts can handle the new syntax
- self.text = Diaspora::Mentionable.backport_mention_syntax(text) if text && author.local?
- end
-
after_create :create_mentions
has_many :mentions, as: :mentions_container, dependent: :destroy
end
diff --git a/lib/diaspora/message_renderer.rb b/lib/diaspora/message_renderer.rb
index d3f0e0232..4f12c85f8 100644
--- a/lib/diaspora/message_renderer.rb
+++ b/lib/diaspora/message_renderer.rb
@@ -248,7 +248,7 @@ module Diaspora
# Extracts all the urls from the raw message and return them in the form of a string
# Different URLs are seperated with a space
def urls
- @urls ||= Twitter::Extractor.extract_urls(plain_text_without_markdown).map {|url|
+ @urls ||= Twitter::TwitterText::Extractor.extract_urls(plain_text_without_markdown).map {|url|
Addressable::URI.parse(url).normalize.to_s
}
end
diff --git a/lib/schemas/api_v1.json b/lib/schemas/api_v1.json
new file mode 100644
index 000000000..2d807610a
--- /dev/null
+++ b/lib/schemas/api_v1.json
@@ -0,0 +1,486 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "https://diaspora.software/api/v1/schema.json",
+ "oneOf": [
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/aspects"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/aspect"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/comments"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/messages"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/users"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/conversations"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/conversation"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/authored_content_references"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/likes"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/notifications"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/notification"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photos"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photo"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/post"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/posts"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/tags"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/own_user"},
+ {"$ref": "https://diaspora.software/api/v1/schema.json#/definitions/user"}
+ ],
+
+ "definitions": {
+ "guid": {
+ "type": "string",
+ "minLength": 16,
+ "maxLength": 255
+ },
+
+ "timestamp": {
+ "type": "string",
+ "format": "date-time"
+ },
+
+ "url": {
+ "type": "string",
+ "pattern": "^https?://"
+ },
+
+ "short_profile": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "diaspora_id": { "type": "string" },
+ "name": { "type": "string" },
+ "avatar": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/url" }
+ },
+ "required": ["guid", "diaspora_id", "name"],
+ "additionalProperties": false
+ },
+
+ "aspects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "order": {
+ "type": "integer"
+ }
+ },
+ "required": ["id", "name", "order"],
+ "additionalProperties": false
+ }
+ },
+
+ "aspect": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "order": {
+ "type": "integer"
+ }
+ },
+ "required": ["id", "name", "order"],
+ "additionalProperties": false
+ },
+
+ "comments": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "author": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" },
+ "body": { "type": "string" },
+ "mentioned_people": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ },
+ "reported": { "type": "boolean" }
+ },
+ "required": ["guid", "created_at", "author", "body", "reported"],
+ "additionalProperties": false
+ }
+ },
+
+ "messages": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "author": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" },
+ "body": { "type": "string" }
+ },
+ "required": ["guid", "created_at", "author", "body"],
+ "additionalProperties": false
+ }
+ },
+
+ "users": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ },
+
+ "conversations": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/conversation" }
+ },
+
+ "conversation": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "subject": { "type": "string" },
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "read": { "type": "boolean" },
+ "participants": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ }
+ },
+ "required": ["subject", "created_at", "read", "participants"],
+ "additionalProperties": false
+ },
+
+ "authored_content_reference": {
+ "type": "object",
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "author": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ },
+ "required": ["guid", "created_at", "author"],
+ "additionalProperties": false
+ },
+
+ "authored_content_references": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/authored_content_reference" }
+ },
+
+ "likes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "author": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ },
+ "required": ["guid", "author"],
+ "additionalProperties": false
+ }
+ },
+
+ "notifications": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/notification" }
+ },
+
+ "notification": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "type": {
+ "enum": [
+ "also_commented",
+ "comment_on_post",
+ "liked",
+ "mentioned",
+ "mentioned_in_comment",
+ "reshared",
+ "started_sharing",
+ "contacts_birthday"
+ ]
+ },
+ "read": { "type": "boolean" },
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "target": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "author": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ },
+ "required": ["guid"]
+ },
+ "event_creators": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ }
+ },
+ "required": ["guid", "type", "read", "created_at"],
+ "additionalProperties": false
+ },
+
+ "photos": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photo"}
+ },
+
+ "photo_sizes": {
+ "type": "object",
+ "properties": {
+ "raw": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/url" },
+ "large": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/url" },
+ "medium": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/url" },
+ "small": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/url" }
+ },
+ "required": ["raw", "large", "medium", "small"],
+ "additionalProperties": false
+ },
+
+ "photo_dimensions": {
+ "type": "object",
+ "properties": {
+ "width": { "type": ["integer", "null"] },
+ "height": { "type": ["integer", "null"] }
+ },
+ "required": ["width", "height"]
+ },
+
+ "photo": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "post": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "dimensions": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photo_dimensions" },
+ "sizes": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photo_sizes" }
+ },
+ "required": ["guid", "created_at", "dimensions", "sizes"],
+ "additionalProperties": false
+ },
+
+ "post_common": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "title": { "type": "string" },
+ "body": { "type": "string" },
+ "provider_display_name": { "type": "string" },
+ "public": { "type": "boolean" },
+ "nsfw": { "type": "boolean" },
+ "author": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" },
+ "interaction_counters": {
+ "type": "object",
+ "properties": {
+ "comments": { "type": "integer" },
+ "likes": { "type": "integer" },
+ "reshares":{ "type": "integer" }
+ },
+ "required": ["comments", "likes", "reshares"],
+ "additionalProperties": false
+ },
+ "own_interaction_state": {
+ "type": "object",
+ "properties": {
+ "liked": { "type": "boolean" },
+ "reshared": { "type": "boolean" },
+ "subscribed": { "type": "boolean" },
+ "reported": { "type": "boolean" }
+ },
+ "required": ["liked", "reshared", "subscribed", "reported"],
+ "additionalProperties": false
+ },
+ "mentioned_people": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ },
+ "photos": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "dimensions": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photo_dimensions" },
+ "sizes": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photo_sizes" }
+ },
+ "required": ["dimensions", "sizes"]
+ }
+ },
+ "poll": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "participation_count": { "type": "integer" },
+ "already_participated": { "type": "boolean" },
+ "question": { "type": "string" },
+ "poll_answers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "integer" },
+ "answer": { "type": "string" },
+ "vote_count": { "type": "integer" },
+ "own_answer": { "type": "boolean" }
+ },
+ "required": ["id", "answer", "vote_count"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["guid", "participation_count", "already_participated", "question", "poll_answers"],
+ "additionalProperties": false
+ },
+ "location": {
+ "type": "object",
+ "properties": {
+ "address": { "type": "string" },
+ "lat": { "type": "number" },
+ "lng": { "type": "number" }
+ },
+ "required": ["address", "lat", "lng"],
+ "additionalProperties": false
+ },
+ "open_graph_object": {
+ "type": "object",
+ "properties": {
+ "url": { "type": "string" },
+ "type": { "type": "string" },
+ "title": { "type": "string" },
+ "image": { "type": "string" },
+ "description": { "type": "string" },
+ "video_url": { "type": "string" }
+ },
+ "required": ["url", "type", "title", "image"],
+ "additionalProperties": false
+ },
+ "oembed": {
+ "type": "object",
+ "description": "An oEmbed response according to 2.3.4 of the oEmbed spec.",
+ "properties": {
+ "trusted_endpoint_url": { "type": "string" }
+ },
+ "required": ["trusted_endpoint_url"],
+ "additionalProperties": true
+ }
+ },
+ "required": ["guid", "created_at", "title", "body", "public", "nsfw", "author", "interaction_counters", "own_interaction_state", "mentioned_people", "photos"]
+ },
+
+ "post": {
+ "anyOf": [
+ {
+ "allOf": [
+ { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/post_common" },
+ {
+ "properties": {
+ "post_type": { "type": "string", "format": "^StatusMessage$" }
+ },
+ "required": ["post_type"]
+ }
+ ]
+ },
+ {
+ "allOf": [
+ { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/post_common" },
+ {
+ "properties": {
+ "post_type": { "type": "string", "format": "^Reshare$" },
+ "root": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/authored_content_reference" }
+ },
+ "required": ["post_type", "root"]
+ }
+ ]
+ }
+ ]
+ },
+
+ "reshares": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "created_at": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/timestamp" },
+ "author": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
+ },
+ "required": ["guid", "created_at", "author"],
+ "additionalProperties": false
+ }
+ },
+
+ "posts": {
+ "type": "array",
+ "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/post" }
+ },
+
+ "tags": {
+ "type": "array",
+ "items": { "type": "string", "pattern": "^[^#]" }
+ },
+
+ "birthday": { "type": "string", "pattern": "^\\d\\d\\d\\d-\\d\\d-\\d\\d$" },
+
+ "user_data": {
+ "type": "object",
+ "properties": {
+ "guid": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/guid" },
+ "diaspora_id": { "type": "string" },
+ "name": { "type": "string" },
+ "birthday": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/birthday" },
+ "gender": { "type": "string" },
+ "location": { "type": "string" },
+ "bio": { "type": "string" },
+ "avatar": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/photo_sizes" },
+ "tags": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/tags" }
+ },
+ "required": ["guid", "diaspora_id", "tags"]
+ },
+
+ "own_user": {
+ "allOf": [
+ { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/user_data" },
+ {
+ "type": "object",
+ "properties": {
+ "searchable": { "type": "boolean" },
+ "show_profile_info": { "type": "boolean" }
+ },
+ "required": ["searchable", "show_profile_info"]
+ }
+ ]
+ },
+
+ "user": {
+ "allOf": [
+ { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/user_data" },
+ {
+ "type": "object",
+ "properties": {
+ "blocked": { "type": "boolean" },
+ "relationship": {
+ "type": "object",
+ "properties": {
+ "receiving": { "type": "boolean" },
+ "sharing": { "type": "boolean" }
+ },
+ "required": ["receiving", "sharing"],
+ "additionalProperties": false
+ },
+ "aspects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" }
+ },
+ "required": ["id", "name"],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": ["blocked", "relationship", "aspects"]
+ }
+ ]
+ }
+ }
+}
diff --git a/lib/stream.rb b/lib/stream.rb
index 27cb67e25..51c7512e6 100644
--- a/lib/stream.rb
+++ b/lib/stream.rb
@@ -1,14 +1,15 @@
# frozen_string_literal: true
module Stream
- require 'stream/activity'
- require 'stream/aspect'
- require 'stream/comments'
- require 'stream/followed_tag'
- require 'stream/likes'
- require 'stream/mention'
- require 'stream/multi'
- require 'stream/person'
- require 'stream/public'
- require 'stream/tag'
+ require "stream/activity"
+ require "stream/aspect"
+ require "stream/comments"
+ require "stream/followed_tag"
+ require "stream/likes"
+ require "stream/mention"
+ require "stream/multi"
+ require "stream/person"
+ require "stream/public"
+ require "stream/local_public"
+ require "stream/tag"
end
diff --git a/lib/stream/local_public.rb b/lib/stream/local_public.rb
new file mode 100644
index 000000000..ecbb02133
--- /dev/null
+++ b/lib/stream/local_public.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# rubocop:disable Style/ClassAndModuleChildren
+class Stream::LocalPublic < Stream::Base
+ def link(opts={})
+ Rails.application.routes.url_helpers.local_public_stream_path(opts)
+ end
+
+ def title
+ I18n.t("streams.local_public.title")
+ end
+
+ # @return [ActiveRecord::Association<Post>] AR association of posts
+ def posts
+ @posts ||= Post.all_local_public
+ end
+
+ # Override base class method
+ def aspects
+ ["public"]
+ end
+end
+# rubocop:enable Style/ClassAndModuleChildren
diff --git a/lib/tasks/accounts.rake b/lib/tasks/accounts.rake
new file mode 100644
index 000000000..c33c528fd
--- /dev/null
+++ b/lib/tasks/accounts.rake
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+namespace :accounts do
+ desc "Perform migration"
+ task :migration, %i[archive_path new_user_name] => :environment do |_t, args|
+ puts "Account migration is requested"
+ args = %i[archive_path new_user_name].map {|name| [name, args[name]] }.to_h
+ process_arguments(args)
+
+ begin
+ service = MigrationService.new(args[:archive_path], args[:new_user_name])
+ service.validate
+ puts "Warnings:\n#{service.warnings.join("\n")}\n-----" if service.warnings.any?
+ if service.only_import?
+ puts "Warning: Archive owner is not fetchable. Proceeding with data import, but account migration record "\
+ "won't be created"
+ end
+ print "Do you really want to execute the archive import? Note: this is irreversible! [y/N]: "
+ next unless $stdin.gets.strip.casecmp?("y")
+
+ start_time = Time.now.getlocal
+ service.perform!
+ puts service.only_import? ? "Data import complete!" : "Data import and migration complete!"
+ puts "Migration took #{Time.now.getlocal - start_time} seconds"
+ rescue MigrationService::ArchiveValidationFailed => exception
+ puts "Errors in the archive found:\n#{exception.message}\n-----"
+ rescue MigrationService::MigrationAlreadyExists
+ puts "Migration record already exists for the user, can't continue"
+ end
+ end
+
+ def process_arguments(args)
+ if args[:archive_path].nil?
+ print "Enter the archive path: "
+ args[:archive_path] = $stdin.gets.strip
+ end
+ if args[:new_user_name].nil?
+ print "Enter the new user name: "
+ args[:new_user_name] = $stdin.gets.strip
+ end
+ puts "Archive path: #{args[:archive_path]}"
+ puts "New username: #{args[:new_user_name]}"
+ end
+end