diaspora_federation 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/lib/diaspora_federation.rb +23 -4
- data/lib/diaspora_federation/discovery.rb +14 -0
- data/lib/diaspora_federation/discovery/discovery.rb +94 -0
- data/lib/diaspora_federation/{web_finger → discovery}/exceptions.rb +5 -1
- data/lib/diaspora_federation/{web_finger → discovery}/h_card.rb +29 -25
- data/lib/diaspora_federation/{web_finger → discovery}/host_meta.rb +13 -13
- data/lib/diaspora_federation/{web_finger → discovery}/web_finger.rb +24 -28
- data/lib/diaspora_federation/{web_finger → discovery}/xrd_document.rb +10 -11
- data/lib/diaspora_federation/entities.rb +12 -0
- data/lib/diaspora_federation/entities/person.rb +33 -0
- data/lib/diaspora_federation/entities/profile.rb +54 -0
- data/lib/diaspora_federation/entity.rb +62 -30
- data/lib/diaspora_federation/fetcher.rb +42 -0
- data/lib/diaspora_federation/logging.rb +4 -2
- data/lib/diaspora_federation/properties_dsl.rb +11 -2
- data/lib/diaspora_federation/validators.rb +38 -0
- data/lib/diaspora_federation/validators/h_card_validator.rb +30 -0
- data/lib/diaspora_federation/validators/person_validator.rb +18 -0
- data/lib/diaspora_federation/validators/profile_validator.rb +33 -0
- data/lib/diaspora_federation/validators/rules/birthday.rb +38 -0
- data/lib/diaspora_federation/validators/rules/boolean.rb +38 -0
- data/lib/diaspora_federation/validators/rules/diaspora_id.rb +46 -0
- data/lib/diaspora_federation/validators/rules/guid.rb +28 -0
- data/lib/diaspora_federation/validators/rules/nilable_uri.rb +19 -0
- data/lib/diaspora_federation/validators/rules/not_nil.rb +23 -0
- data/lib/diaspora_federation/validators/rules/public_key.rb +33 -0
- data/lib/diaspora_federation/validators/rules/tag_count.rb +34 -0
- data/lib/diaspora_federation/validators/web_finger_validator.rb +20 -0
- data/lib/diaspora_federation/version.rb +1 -1
- metadata +82 -8
- data/lib/diaspora_federation/web_finger.rb +0 -13
@@ -1,5 +1,5 @@
|
|
1
1
|
module DiasporaFederation
|
2
|
-
module
|
2
|
+
module Discovery
|
3
3
|
# This class implements basic handling of XRD documents as far as it is
|
4
4
|
# necessary in the context of the protocols used with Diaspora* federation.
|
5
5
|
#
|
@@ -92,19 +92,18 @@ module DiasporaFederation
|
|
92
92
|
# @raise [InvalidDocument] if the XRD is malformed
|
93
93
|
def self.xml_data(xrd_doc)
|
94
94
|
doc = parse_xrd_document(xrd_doc)
|
95
|
-
data = {}
|
96
95
|
|
97
|
-
|
98
|
-
|
96
|
+
{}.tap do |data|
|
97
|
+
exp_elem = doc.at_xpath("xrd:XRD/xrd:Expires", NS)
|
98
|
+
data[:expires] = DateTime.strptime(exp_elem.content, DATETIME_FORMAT) unless exp_elem.nil?
|
99
99
|
|
100
|
-
|
101
|
-
|
100
|
+
subj_elem = doc.at_xpath("xrd:XRD/xrd:Subject", NS)
|
101
|
+
data[:subject] = subj_elem.content unless subj_elem.nil?
|
102
102
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
data
|
103
|
+
parse_aliases_from_xml_doc(doc, data)
|
104
|
+
parse_properties_from_xml_doc(doc, data)
|
105
|
+
parse_links_from_xml_doc(doc, data)
|
106
|
+
end
|
108
107
|
end
|
109
108
|
|
110
109
|
private
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
# This namespace contains all the entities used to encapsulate data that is
|
3
|
+
# passed around in the Diaspora* network as part of the federation protocol.
|
4
|
+
#
|
5
|
+
# All entities must be defined in this namespace. otherwise the XML
|
6
|
+
# de-serialization will fail.
|
7
|
+
module Entities
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require "diaspora_federation/entities/profile"
|
12
|
+
require "diaspora_federation/entities/person"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Entities
|
3
|
+
# this entity contains the base data of a person
|
4
|
+
#
|
5
|
+
# @see Validators::PersonValidator
|
6
|
+
class Person < Entity
|
7
|
+
# @!attribute [r] guid
|
8
|
+
# @see HCard#guid
|
9
|
+
# @return [String] guid
|
10
|
+
property :guid
|
11
|
+
|
12
|
+
# @!attribute [r] diaspora_id
|
13
|
+
# The diaspora ID of the person
|
14
|
+
# @return [String] diaspora ID
|
15
|
+
property :diaspora_id, xml_name: :diaspora_handle
|
16
|
+
|
17
|
+
# @!attribute [r] url
|
18
|
+
# @see WebFinger#seed_url
|
19
|
+
# @return [String] link to the pod
|
20
|
+
property :url
|
21
|
+
|
22
|
+
# @!attribute [r] profile
|
23
|
+
# all profile data of the person
|
24
|
+
# @return [Profile] the profile of the person
|
25
|
+
entity :profile, Entities::Profile
|
26
|
+
|
27
|
+
# @!attribute [r] exported_key
|
28
|
+
# @see HCard#public_key
|
29
|
+
# @return [String] public key
|
30
|
+
property :exported_key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Entities
|
3
|
+
# this entity contains all the profile data of a person
|
4
|
+
#
|
5
|
+
# @see Validators::ProfileValidator
|
6
|
+
class Profile < Entity
|
7
|
+
# @!attribute [r] diaspora_id
|
8
|
+
# The diaspora ID of the person
|
9
|
+
# @see Person#diaspora_id
|
10
|
+
# @return [String] diaspora ID
|
11
|
+
property :diaspora_id, xml_name: :diaspora_handle
|
12
|
+
|
13
|
+
# @!attribute [r] first_name
|
14
|
+
# @deprecated
|
15
|
+
# @see #full_name
|
16
|
+
# @see HCard#first_name
|
17
|
+
# @return [String] first name
|
18
|
+
property :first_name, default: nil
|
19
|
+
|
20
|
+
# @!attribute [r] last_name
|
21
|
+
# @deprecated
|
22
|
+
# @see #full_name
|
23
|
+
# @see HCard#last_name
|
24
|
+
# @return [String] last name
|
25
|
+
property :last_name, default: nil
|
26
|
+
|
27
|
+
# @!attribute [r] image_url
|
28
|
+
# @see HCard#photo_large_url
|
29
|
+
# @return [String] url to the big avatar (300x300)
|
30
|
+
property :image_url, default: nil
|
31
|
+
# @!attribute [r] image_url_medium
|
32
|
+
# @see HCard#photo_medium_url
|
33
|
+
# @return [String] url to the medium avatar (100x100)
|
34
|
+
property :image_url_medium, default: nil
|
35
|
+
# @!attribute [r] image_url_small
|
36
|
+
# @see HCard#photo_small_url
|
37
|
+
# @return [String] url to the small avatar (50x50)
|
38
|
+
property :image_url_small, default: nil
|
39
|
+
|
40
|
+
property :birthday, default: nil
|
41
|
+
property :gender, default: nil
|
42
|
+
property :bio, default: nil
|
43
|
+
property :location, default: nil
|
44
|
+
|
45
|
+
# @!attribute [r] searchable
|
46
|
+
# @see HCard#searchable
|
47
|
+
# @return [Boolean] searchable flag
|
48
|
+
property :searchable, default: true
|
49
|
+
|
50
|
+
property :nsfw, default: false
|
51
|
+
property :tag_string, default: nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -15,6 +15,7 @@ module DiasporaFederation
|
|
15
15
|
# property :prop
|
16
16
|
# property :optional, default: false
|
17
17
|
# property :dynamic_default, default: -> { Time.now }
|
18
|
+
# property :another_prop, xml_name: :another_name
|
18
19
|
# entity :nested, NestedEntity
|
19
20
|
# entity :multiple, [OtherEntity]
|
20
21
|
# end
|
@@ -37,6 +38,12 @@ module DiasporaFederation
|
|
37
38
|
# Initializes the Entity with the given attribute hash and freezes the created
|
38
39
|
# instance it returns.
|
39
40
|
#
|
41
|
+
# After creation, the entity is validated against a Validator, if one is defined.
|
42
|
+
# The Validator needs to be in the {DiasporaFederation::Validators} namespace and
|
43
|
+
# named like "<EntityName>Validator". Only valid entities can be created.
|
44
|
+
#
|
45
|
+
# @see DiasporaFederation::Validators
|
46
|
+
#
|
40
47
|
# @note Attributes not defined as part of the class definition ({PropertiesDSL#property},
|
41
48
|
# {PropertiesDSL#entity}) get discarded silently.
|
42
49
|
#
|
@@ -50,16 +57,18 @@ module DiasporaFederation
|
|
50
57
|
end
|
51
58
|
|
52
59
|
self.class.default_values.merge(data).each do |k, v|
|
53
|
-
instance_variable_set("@#{k}", v) if setable?(k, v)
|
60
|
+
instance_variable_set("@#{k}", nilify(v)) if setable?(k, v)
|
54
61
|
end
|
62
|
+
|
55
63
|
freeze
|
64
|
+
validate
|
56
65
|
end
|
57
66
|
|
58
67
|
# Returns a Hash representing this Entity (attributes => values)
|
59
68
|
# @return [Hash] entity data (mostly equal to the hash used for initialization).
|
60
69
|
def to_h
|
61
70
|
self.class.class_prop_names.each_with_object({}) do |prop, hash|
|
62
|
-
hash[prop] =
|
71
|
+
hash[prop] = public_send(prop)
|
63
72
|
end
|
64
73
|
end
|
65
74
|
|
@@ -76,16 +85,12 @@ module DiasporaFederation
|
|
76
85
|
|
77
86
|
# some of this is from Rails "Inflector.demodulize" and "Inflector.undersore"
|
78
87
|
def self.entity_name
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
86
|
-
word.tr!("-", "_")
|
87
|
-
word.downcase!
|
88
|
-
word
|
88
|
+
name.rpartition("::").last.tap do |word|
|
89
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
90
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
91
|
+
word.tr!("-", "_")
|
92
|
+
word.downcase!
|
93
|
+
end
|
89
94
|
end
|
90
95
|
|
91
96
|
private
|
@@ -113,34 +118,61 @@ module DiasporaFederation
|
|
113
118
|
val.all? {|v| v.instance_of?(t.first) })
|
114
119
|
end
|
115
120
|
|
121
|
+
def nilify(value)
|
122
|
+
return nil if value.respond_to?(:empty?) && value.empty?
|
123
|
+
value
|
124
|
+
end
|
125
|
+
|
126
|
+
def validate
|
127
|
+
validator_name = "#{self.class.name.split('::').last}Validator"
|
128
|
+
return unless Validators.const_defined? validator_name
|
129
|
+
|
130
|
+
validator_class = Validators.const_get validator_name
|
131
|
+
validator = validator_class.new self
|
132
|
+
raise ValidationError, error_message(validator) unless validator.valid?
|
133
|
+
end
|
134
|
+
|
135
|
+
def error_message(validator)
|
136
|
+
errors = validator.errors.map do |prop, rule|
|
137
|
+
"property: #{prop}, value: #{public_send(prop).inspect}, rule: #{rule[:rule]}, with params: #{rule[:params]}"
|
138
|
+
end
|
139
|
+
"Failed validation for properties: #{errors.join(' | ')}"
|
140
|
+
end
|
141
|
+
|
116
142
|
# Serialize the Entity into XML elements
|
117
143
|
# @return [Nokogiri::XML::Element] root node
|
118
144
|
def entity_xml
|
119
145
|
doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
name = prop_def[:name]
|
124
|
-
type = prop_def[:type]
|
125
|
-
if type == String
|
126
|
-
root_element << simple_node(doc, name)
|
127
|
-
else
|
128
|
-
# call #to_xml for each item and append to root
|
129
|
-
[*send(name)].compact.each do |item|
|
130
|
-
root_element << item.to_xml
|
131
|
-
end
|
146
|
+
Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element|
|
147
|
+
self.class.class_props.each do |prop_def|
|
148
|
+
add_property_to_xml(doc, prop_def, root_element)
|
132
149
|
end
|
133
150
|
end
|
151
|
+
end
|
134
152
|
|
135
|
-
|
153
|
+
def add_property_to_xml(doc, prop_def, root_element)
|
154
|
+
property = prop_def[:name]
|
155
|
+
type = prop_def[:type]
|
156
|
+
if type == String
|
157
|
+
root_element << simple_node(doc, prop_def[:xml_name], property)
|
158
|
+
else
|
159
|
+
# call #to_xml for each item and append to root
|
160
|
+
[*public_send(property)].compact.each do |item|
|
161
|
+
root_element << item.to_xml
|
162
|
+
end
|
163
|
+
end
|
136
164
|
end
|
137
165
|
|
138
166
|
# create simple node, fill it with text and append to root
|
139
|
-
def simple_node(doc, name)
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
167
|
+
def simple_node(doc, name, property)
|
168
|
+
Nokogiri::XML::Element.new(name.to_s, doc).tap do |node|
|
169
|
+
data = public_send(property).to_s
|
170
|
+
node.content = data unless data.empty?
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Raised, if entity is not valid
|
175
|
+
class ValidationError < RuntimeError
|
144
176
|
end
|
145
177
|
end
|
146
178
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "faraday_middleware/response/follow_redirects"
|
3
|
+
require "typhoeus/adapters/faraday"
|
4
|
+
|
5
|
+
module DiasporaFederation
|
6
|
+
# A wrapper for {https://github.com/lostisland/faraday Faraday} used for
|
7
|
+
# fetching
|
8
|
+
#
|
9
|
+
# @see Discovery::Discovery
|
10
|
+
class Fetcher
|
11
|
+
# Perform a GET request
|
12
|
+
#
|
13
|
+
# @param [String] uri the URI
|
14
|
+
# @return [Faraday::Response] the response
|
15
|
+
def self.get(uri)
|
16
|
+
connection.get(uri)
|
17
|
+
end
|
18
|
+
|
19
|
+
# gets the Faraday connection
|
20
|
+
#
|
21
|
+
# @return [Faraday::Connection] the response
|
22
|
+
def self.connection
|
23
|
+
create_default_connection unless @connection
|
24
|
+
@connection.dup
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create_default_connection
|
28
|
+
options = {
|
29
|
+
request: {timeout: 30},
|
30
|
+
ssl: {ca_file: DiasporaFederation.certificate_authorities}
|
31
|
+
}
|
32
|
+
|
33
|
+
@connection = Faraday::Connection.new(options) do |builder|
|
34
|
+
builder.use FaradayMiddleware::FollowRedirects, limit: 4
|
35
|
+
builder.adapter :typhoeus
|
36
|
+
end
|
37
|
+
|
38
|
+
@connection.headers["User-Agent"] = "DiasporaFederation/#{DiasporaFederation::VERSION}"
|
39
|
+
end
|
40
|
+
private_class_method :create_default_connection
|
41
|
+
end
|
42
|
+
end
|
@@ -13,10 +13,12 @@ module DiasporaFederation
|
|
13
13
|
# use logging-gem if available
|
14
14
|
return ::Logging::Logger[self] if defined?(::Logging::Logger)
|
15
15
|
|
16
|
+
# use rails logger if running in rails and no logging-gem is available
|
17
|
+
return ::Rails.logger if defined?(::Rails)
|
18
|
+
|
16
19
|
# fallback logger
|
17
20
|
@logger = Logger.new(STDOUT)
|
18
|
-
|
19
|
-
@logger.level = Logger.const_get(loglevel)
|
21
|
+
@logger.level = Logger::INFO
|
20
22
|
@logger
|
21
23
|
end
|
22
24
|
end
|
@@ -6,6 +6,7 @@ module DiasporaFederation
|
|
6
6
|
# property :prop
|
7
7
|
# property :optional, default: false
|
8
8
|
# property :dynamic_default, default: -> { Time.now }
|
9
|
+
# property :another_prop, xml_name: :another_name
|
9
10
|
# entity :nested, NestedEntity
|
10
11
|
# entity :multiple, [OtherEntity]
|
11
12
|
module PropertiesDSL
|
@@ -19,6 +20,7 @@ module DiasporaFederation
|
|
19
20
|
# @param [Hash] opts further options
|
20
21
|
# @option opts [Object, #call] :default a default value, making the
|
21
22
|
# property optional
|
23
|
+
# @option opts [Symbol] :xml_name another name used for xml generation
|
22
24
|
def property(name, opts={})
|
23
25
|
define_property name, String, opts
|
24
26
|
end
|
@@ -69,7 +71,14 @@ module DiasporaFederation
|
|
69
71
|
def define_property(name, type, opts={})
|
70
72
|
raise InvalidName unless name_valid?(name)
|
71
73
|
|
72
|
-
|
74
|
+
xml_name = name
|
75
|
+
if opts.has_key? :xml_name
|
76
|
+
raise ArgumentError, "xml_name is not supported for nested entities" unless type == String
|
77
|
+
xml_name = opts[:xml_name]
|
78
|
+
raise InvalidName, "invalid xml_name" unless name_valid?(xml_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
class_props << {name: name, xml_name: xml_name, type: type}
|
73
82
|
default_props[name] = opts[:default] if opts.has_key? :default
|
74
83
|
|
75
84
|
instance_eval { attr_reader name }
|
@@ -79,7 +88,7 @@ module DiasporaFederation
|
|
79
88
|
# @param [String, Symbol] name the name to check
|
80
89
|
# @return [Boolean]
|
81
90
|
def name_valid?(name)
|
82
|
-
name.instance_of?(Symbol)
|
91
|
+
name.instance_of?(Symbol)
|
83
92
|
end
|
84
93
|
|
85
94
|
# checks if the type extends {Entity}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "validation"
|
2
|
+
require "validation/rule/regular_expression"
|
3
|
+
require "validation/rule/not_empty"
|
4
|
+
require "validation/rule/uri"
|
5
|
+
|
6
|
+
# +valid+ gem namespace
|
7
|
+
module Validation
|
8
|
+
# This module contains custom validation rules for various data field types.
|
9
|
+
# That includes types for which there are no provided rules by the +valid+ gem
|
10
|
+
# or types that are very specific to Diaspora* federation and need special handling.
|
11
|
+
# The rules are used inside the {DiasporaFederation::Validators validator classes}
|
12
|
+
# to perform basic sanity-checks on {DiasporaFederation::Entities federation entities}.
|
13
|
+
module Rule
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require "diaspora_federation/validators/rules/birthday"
|
18
|
+
require "diaspora_federation/validators/rules/boolean"
|
19
|
+
require "diaspora_federation/validators/rules/diaspora_id"
|
20
|
+
require "diaspora_federation/validators/rules/guid"
|
21
|
+
require "diaspora_federation/validators/rules/nilable_uri"
|
22
|
+
require "diaspora_federation/validators/rules/not_nil"
|
23
|
+
require "diaspora_federation/validators/rules/public_key"
|
24
|
+
require "diaspora_federation/validators/rules/tag_count"
|
25
|
+
|
26
|
+
module DiasporaFederation
|
27
|
+
# Validators to perform basic sanity-checks on {DiasporaFederation::Entities federation entities}.
|
28
|
+
#
|
29
|
+
# The Validators are mapped with the entities by name. The naming schema
|
30
|
+
# is "<EntityName>Validator".
|
31
|
+
module Validators
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require "diaspora_federation/validators/h_card_validator"
|
36
|
+
require "diaspora_federation/validators/person_validator"
|
37
|
+
require "diaspora_federation/validators/profile_validator"
|
38
|
+
require "diaspora_federation/validators/web_finger_validator"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
module Validators
|
3
|
+
# This validates a {Discovery::HCard}
|
4
|
+
#
|
5
|
+
# @todo activate guid and public key validation after all pod have it in
|
6
|
+
# the hcard.
|
7
|
+
#
|
8
|
+
# @note
|
9
|
+
class HCardValidator < Validation::Validator
|
10
|
+
include Validation
|
11
|
+
|
12
|
+
# rule :guid, :guid
|
13
|
+
|
14
|
+
# the name must not contain a semicolon because of mentions
|
15
|
+
# @{<full_name> ; <diaspora_id>}
|
16
|
+
rule :full_name, regular_expression: {regex: /\A[^;]{,70}\z/}
|
17
|
+
rule :first_name, regular_expression: {regex: /\A[^;]{,32}\z/}
|
18
|
+
rule :last_name, regular_expression: {regex: /\A[^;]{,32}\z/}
|
19
|
+
|
20
|
+
# this urls can be relative
|
21
|
+
rule :photo_large_url, [:not_nil, nilableURI: [:path]]
|
22
|
+
rule :photo_medium_url, [:not_nil, nilableURI: [:path]]
|
23
|
+
rule :photo_small_url, [:not_nil, nilableURI: [:path]]
|
24
|
+
|
25
|
+
# rule :exported_key, :public_key
|
26
|
+
|
27
|
+
rule :searchable, :boolean
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|