Merge pull request #85 from SuperTux88/add-author-to-diaspora-url

Add author to diaspora:// URL
This commit is contained in:
Dennis Schubert 2017-09-13 08:08:27 +02:00
commit 0a358c8724
No known key found for this signature in database
GPG key ID: 5A0304BEA7966D7E
39 changed files with 84 additions and 84 deletions

View file

@ -13,20 +13,25 @@ it as software specific URL.
The format is similar to the route used for [fetching][fetching], so if the
receiving server doesn't know the linked entity yet, it can just be fetched.
When the entity with that `guid` is already available locally, the recipient
should validate that it's from `author` before linking to it.
### Format
`diaspora://:type/:guid`
`diaspora://:author/:type/:guid`
#### Parameters
| Name | Description |
| ------ | ---------------------------------------------- |
| `type` | The type of the linked entity in `snake_case`. |
| `guid` | The [GUID][guid] of the linked entity. |
| Name | Description |
| -------- | -------------------------------------------------------------------- |
| `author` | The [diaspora\* ID][diaspora-id] of the author of the linked entity. |
| `type` | The type of the linked entity in `snake_case`. |
| `guid` | The [GUID][guid] of the linked entity. |
#### Example
`diaspora://post/17faf230675101350d995254001bd39e`
`diaspora://alice@example.org/post/17faf230675101350d995254001bd39e`
[fetching]: {{ site.baseurl }}/federation/fetching.html
[diaspora-id]: {{ site.baseurl }}/federation/types.html#diaspora-id
[guid]: {{ site.baseurl }}/federation/types.html#guid

View file

@ -10,7 +10,7 @@ module DiasporaFederation
# The diaspora* ID of the person who posted the original post
# @see Person#author
# @return [String] diaspora* ID
property :root_author, :string, xml_name: :root_diaspora_id
property :root_author, :string, optional: true, xml_name: :root_diaspora_id
# @!attribute [r] root_guid
# Guid of the original post

View file

@ -5,22 +5,27 @@ module DiasporaFederation
include Logging
# Regex to find diaspora:// URLs
DIASPORA_URL_REGEX = %r{diaspora://(#{Entity::ENTITY_NAME_REGEX})/(#{Validation::Rule::Guid::VALID_CHARS})}
DIASPORA_URL_REGEX = %r{
diaspora://
(#{Validation::Rule::DiasporaId::DIASPORA_ID_REGEX})/
(#{Entity::ENTITY_NAME_REGEX})/
(#{Validation::Rule::Guid::VALID_CHARS})
}ux
# Parses all diaspora:// URLs from the text and fetches the entities from
# the remote server if needed.
# @param [String] sender the diaspora* ID of the sender of the entity
# @param [String] text text with diaspora:// URLs to fetch
def self.fetch_linked_entities(sender, text)
text.scan(DIASPORA_URL_REGEX).each do |type, guid|
fetch_entity(sender, type, guid)
def self.fetch_linked_entities(text)
text.scan(DIASPORA_URL_REGEX).each do |author, type, guid|
fetch_entity(author, type, guid)
end
end
private_class_method def self.fetch_entity(sender, type, guid)
private_class_method def self.fetch_entity(author, type, guid)
class_name = Entity.entity_class(type).to_s.rpartition("::").last
return if DiasporaFederation.callbacks.trigger(:fetch_related_entity, class_name, guid)
Fetcher.fetch_public(sender, type, guid)
Fetcher.fetch_public(author, type, guid)
rescue => e
logger.error "Failed to fetch linked entity #{type}:#{guid}: #{e.class}: #{e.message}"
end

View file

@ -46,7 +46,7 @@ module DiasporaFederation
end
def fetch_linked_entities_from_text
DiasporaUrlParser.fetch_linked_entities(sender, entity.text) if entity.respond_to?(:text) && entity.text
DiasporaUrlParser.fetch_linked_entities(entity.text) if entity.respond_to?(:text) && entity.text
end
end
end

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class AccountDeletionValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
end
end
end

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class AccountMigrationValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :profile, :not_nil
end

View file

@ -4,8 +4,8 @@ module DiasporaFederation
class ContactValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :recipient, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :recipient, :diaspora_id
rule :following, :boolean
rule :sharing, :boolean
end

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class ConversationValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :guid, :guid
rule :subject, [:not_empty, length: {maximum: 255}]

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class EventValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :guid, :guid

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class MessageValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :guid, :guid
rule :conversation_guid, :guid

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class ParticipationValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :guid, :guid
rule :parent_guid, :guid
rule :parent_type, [:not_empty, regular_expression: {regex: /\APost\z/}]

View file

@ -6,7 +6,7 @@ module DiasporaFederation
rule :guid, :guid
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :url, %i[not_nil URI]

View file

@ -6,7 +6,7 @@ module DiasporaFederation
rule :guid, :guid
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :public, :boolean

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class RelatedEntityValidator < Validation::Validator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :local, :boolean
rule :public, :boolean
end

View file

@ -6,7 +6,7 @@ module DiasporaFederation
# @param [Validation::Validator] validator the validator in which it is included
def self.included(validator)
validator.class_eval do
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :guid, :guid
rule :parent_guid, :guid
rule :parent, :not_nil

View file

@ -8,7 +8,7 @@ module DiasporaFederation
rule :root_guid, :guid
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :guid, :guid

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class RetractionValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :target_guid, :guid
rule :target_type, :not_empty

View file

@ -5,23 +5,25 @@ module Validation
# A simple rule to validate the base structure of diaspora* IDs.
class DiasporaId
# The Regex for a valid diaspora* ID
DIASPORA_ID = begin
DIASPORA_ID_REGEX = begin
letter = "a-zA-Z"
digit = "0-9"
hexadecimal = "[a-fA-F#{digit}]"
username = "[#{letter}#{digit}\\-\\_\\.]+"
hostname_part = "[#{letter}#{digit}\\-]"
hostname = "#{hostname_part}+([.]#{hostname_part}*)*"
hostname = "#{hostname_part}+(?:[.]#{hostname_part}*)*"
ipv4 = "(?:[#{digit}]{1,3}\\.){3}[#{digit}]{1,3}"
ipv6 = "\\[(?:#{hexadecimal}{0,4}:){0,7}#{hexadecimal}{1,4}\\]"
ip_addr = "(?:#{ipv4}|#{ipv6})"
domain = "(?:#{hostname}|#{ip_addr})"
port = "(:[#{digit}]+)?"
addr_spec = "(#{username}\\@#{domain}#{port})?"
port = "(?::[#{digit}]+)?"
/\A#{addr_spec}\z/u
"#{username}\\@#{domain}#{port}"
end
# The Regex for validating a full diaspora* ID
DIASPORA_ID = /\A#{DIASPORA_ID_REGEX}\z/u
# The error key for this rule
# @return [Symbol] error key
def error_key
@ -30,7 +32,7 @@ module Validation
# Determines if value is a valid diaspora* ID
def valid_value?(value)
value.nil? || !DIASPORA_ID.match(value).nil?
value.is_a?(String) && value =~ DIASPORA_ID
end
# This rule has no params.

View file

@ -9,7 +9,7 @@ module Validation
# Special chars aren't allowed at the end.
class Guid
# Allowed chars to validate a GUID with a regex
VALID_CHARS = "[0-9A-Za-z\\-_@.:]{15,254}[0-9a-z]".freeze
VALID_CHARS = "[0-9A-Za-z\\-_@.:]{15,254}[0-9A-Za-z]".freeze
# The error key for this rule
# @return [Symbol] error key

View file

@ -4,7 +4,7 @@ module DiasporaFederation
class StatusMessageValidator < OptionalAwareValidator
include Validation
rule :author, %i[not_empty diaspora_id]
rule :author, :diaspora_id
rule :guid, :guid

View file

@ -1,6 +1,6 @@
module DiasporaFederation
describe Federation::DiasporaUrlParser do
let(:sender) { Fabricate.sequence(:diaspora_id) }
let(:author) { Fabricate.sequence(:diaspora_id) }
let(:guid) { Fabricate.sequence(:guid) }
describe ".fetch_linked_entities" do
@ -11,56 +11,56 @@ module DiasporaFederation
expect_callback(:fetch_related_entity, "Post", guid2).and_return(double)
expect_callback(:fetch_related_entity, "Post", guid3).and_return(double)
text = "This is a [link to a post with markdown](diaspora://post/#{guid}) and one without " \
"diaspora://post/#{guid2} and finally a last one diaspora://post/#{guid3}."
text = "This is a [link to a post with markdown](diaspora://#{author}/post/#{guid}) and one without " \
"diaspora://#{author}/post/#{guid2} and finally a last one diaspora://#{author}/post/#{guid3}."
Federation::DiasporaUrlParser.fetch_linked_entities(sender, text)
Federation::DiasporaUrlParser.fetch_linked_entities(text)
end
it "ignores invalid diaspora:// urls" do
expect(DiasporaFederation.callbacks).not_to receive(:trigger)
text = "This is an invalid link diaspora://Post/#{guid}) and another one: " \
"diaspora://post/abcd."
text = "This is an invalid link diaspora://#{author}/Post/#{guid} and another one " \
"diaspora://#{author}/post/abcd and last one: diaspora://example.org/post/#{guid}."
Federation::DiasporaUrlParser.fetch_linked_entities(sender, text)
Federation::DiasporaUrlParser.fetch_linked_entities(text)
end
it "allows to link other entities" do
expect_callback(:fetch_related_entity, "Event", guid).and_return(double)
text = "This is a link to an event diaspora://event/#{guid}."
text = "This is a link to an event diaspora://#{author}/event/#{guid}."
Federation::DiasporaUrlParser.fetch_linked_entities(sender, text)
Federation::DiasporaUrlParser.fetch_linked_entities(text)
end
it "handles unknown entities gracefully" do
expect(DiasporaFederation.callbacks).not_to receive(:trigger)
text = "This is a link to an event diaspora://unknown/#{guid}."
text = "This is a link to an event diaspora://#{author}/unknown/#{guid}."
Federation::DiasporaUrlParser.fetch_linked_entities(sender, text)
Federation::DiasporaUrlParser.fetch_linked_entities(text)
end
it "fetches entities from sender when not found locally" do
expect_callback(:fetch_related_entity, "Post", guid).and_return(nil)
expect(Federation::Fetcher).to receive(:fetch_public).with(sender, "post", guid)
expect(Federation::Fetcher).to receive(:fetch_public).with(author, "post", guid)
text = "This is a link to a post: diaspora://post/#{guid}."
text = "This is a link to a post: diaspora://#{author}/post/#{guid}."
Federation::DiasporaUrlParser.fetch_linked_entities(sender, text)
Federation::DiasporaUrlParser.fetch_linked_entities(text)
end
it "handles fetch errors gracefully" do
expect_callback(:fetch_related_entity, "Post", guid).and_return(nil)
expect(Federation::Fetcher).to receive(:fetch_public).with(
sender, "post", guid
author, "post", guid
).and_raise(Federation::Fetcher::NotFetchable, "Something went wrong!")
text = "This is a link to a post: diaspora://post/#{guid}."
text = "This is a link to a post: diaspora://#{author}/post/#{guid}."
expect {
Federation::DiasporaUrlParser.fetch_linked_entities(sender, text)
Federation::DiasporaUrlParser.fetch_linked_entities(text)
}.not_to raise_error
end
end

View file

@ -119,7 +119,7 @@ module DiasporaFederation
end
it "fetches linked entities when the received entity has a text property" do
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(post.author, post.text)
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(post.text)
described_class.new(magic_env, recipient).receive
end
@ -128,7 +128,7 @@ module DiasporaFederation
profile = Fabricate(:profile_entity)
magic_env = Salmon::MagicEnvelope.new(profile, profile.author)
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(profile.author, profile.bio)
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(profile.bio)
described_class.new(magic_env, recipient).receive
end

View file

@ -141,7 +141,7 @@ module DiasporaFederation
end
it "fetches linked entities when the received entity has a text property" do
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(post.author, post.text)
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(post.text)
described_class.new(magic_env).receive
end
@ -150,7 +150,7 @@ module DiasporaFederation
profile = Fabricate(:profile_entity, public: true)
magic_env = Salmon::MagicEnvelope.new(profile, profile.author)
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(profile.author, profile.bio)
expect(Federation::DiasporaUrlParser).to receive(:fetch_linked_entities).with(profile.bio)
described_class.new(magic_env).receive
end

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
end
end

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
describe "#person" do

View file

@ -8,7 +8,6 @@ module DiasporaFederation
describe "##{prop}" do
it_behaves_like "a diaspora* ID validator" do
let(:property) { prop }
let(:mandatory) { true }
end
end
end

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
it_behaves_like "a guid validator" do

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
it_behaves_like "a guid validator" do

View file

@ -5,7 +5,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
describe "#guid" do

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
describe "#guid" do

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
it_behaves_like "a guid validator" do

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
describe "#guid" do

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { false }
end
%i[first_name last_name].each do |prop|

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
%i[local public].each do |prop|

View file

@ -6,7 +6,6 @@ module DiasporaFederation
describe "#author" do
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
end
@ -23,9 +22,10 @@ module DiasporaFederation
end
describe "#root_author" do
it_behaves_like "a diaspora* ID validator" do
it_behaves_like "a property with a value validation/restriction" do
let(:property) { :root_author }
let(:mandatory) { false }
let(:wrong_values) { ["i am a weird diaspora* ID @@@ ### 12345"] }
let(:correct_values) { [nil, "alice@example.org"] }
end
end

View file

@ -9,7 +9,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
describe "#target_type" do

View file

@ -83,13 +83,13 @@ describe Validation::Rule::DiasporaId do
expect(validator.errors).to include(:diaspora_id)
end
it "allows nil and empty" do
it "fails for nil and empty" do
[nil, ""].each do |val|
validator = Validation::Validator.new(OpenStruct.new(diaspora_id: val))
validator.rule(:diaspora_id, :diaspora_id)
expect(validator).to be_valid
expect(validator.errors).to be_empty
expect(validator).not_to be_valid
expect(validator.errors).to include(:diaspora_id)
end
end
end

View file

@ -6,7 +6,6 @@ module DiasporaFederation
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
it_behaves_like "a guid validator" do

View file

@ -20,7 +20,6 @@ end
shared_examples "a relayable validator" do
it_behaves_like "a diaspora* ID validator" do
let(:property) { :author }
let(:mandatory) { true }
end
describe "#guid" do
@ -74,16 +73,18 @@ shared_examples "a diaspora* ID validator" do
[nil, ""].each do |val|
validator = described_class.new(entity_stub(entity, property => val))
if mandatory
expect(validator).not_to be_valid
expect(validator.errors).to include(property)
else
expect(validator).to be_valid
expect(validator.errors).to be_empty
end
expect(validator).not_to be_valid
expect(validator.errors).to include(property)
end
end
it "validates a well-formed diaspora* ID" do
validator = described_class.new(entity_stub(entity, property => "alice@example.org"))
expect(validator).to be_valid
expect(validator.errors).to be_empty
end
it "must be a valid diaspora* ID" do
validator = described_class.new(entity_stub(entity, property => "i am a weird diaspora* ID @@@ ### 12345"))