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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/lib/diaspora_federation.rb +23 -4
  4. data/lib/diaspora_federation/discovery.rb +14 -0
  5. data/lib/diaspora_federation/discovery/discovery.rb +94 -0
  6. data/lib/diaspora_federation/{web_finger → discovery}/exceptions.rb +5 -1
  7. data/lib/diaspora_federation/{web_finger → discovery}/h_card.rb +29 -25
  8. data/lib/diaspora_federation/{web_finger → discovery}/host_meta.rb +13 -13
  9. data/lib/diaspora_federation/{web_finger → discovery}/web_finger.rb +24 -28
  10. data/lib/diaspora_federation/{web_finger → discovery}/xrd_document.rb +10 -11
  11. data/lib/diaspora_federation/entities.rb +12 -0
  12. data/lib/diaspora_federation/entities/person.rb +33 -0
  13. data/lib/diaspora_federation/entities/profile.rb +54 -0
  14. data/lib/diaspora_federation/entity.rb +62 -30
  15. data/lib/diaspora_federation/fetcher.rb +42 -0
  16. data/lib/diaspora_federation/logging.rb +4 -2
  17. data/lib/diaspora_federation/properties_dsl.rb +11 -2
  18. data/lib/diaspora_federation/validators.rb +38 -0
  19. data/lib/diaspora_federation/validators/h_card_validator.rb +30 -0
  20. data/lib/diaspora_federation/validators/person_validator.rb +18 -0
  21. data/lib/diaspora_federation/validators/profile_validator.rb +33 -0
  22. data/lib/diaspora_federation/validators/rules/birthday.rb +38 -0
  23. data/lib/diaspora_federation/validators/rules/boolean.rb +38 -0
  24. data/lib/diaspora_federation/validators/rules/diaspora_id.rb +46 -0
  25. data/lib/diaspora_federation/validators/rules/guid.rb +28 -0
  26. data/lib/diaspora_federation/validators/rules/nilable_uri.rb +19 -0
  27. data/lib/diaspora_federation/validators/rules/not_nil.rb +23 -0
  28. data/lib/diaspora_federation/validators/rules/public_key.rb +33 -0
  29. data/lib/diaspora_federation/validators/rules/tag_count.rb +34 -0
  30. data/lib/diaspora_federation/validators/web_finger_validator.rb +20 -0
  31. data/lib/diaspora_federation/version.rb +1 -1
  32. metadata +82 -8
  33. data/lib/diaspora_federation/web_finger.rb +0 -13
@@ -1,5 +1,5 @@
1
1
  module DiasporaFederation
2
- module WebFinger
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
- exp_elem = doc.at_xpath("xrd:XRD/xrd:Expires", NS)
98
- data[:expires] = DateTime.strptime(exp_elem.content, DATETIME_FORMAT) unless exp_elem.nil?
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
- subj_elem = doc.at_xpath("xrd:XRD/xrd:Subject", NS)
101
- data[:subject] = subj_elem.content unless subj_elem.nil?
100
+ subj_elem = doc.at_xpath("xrd:XRD/xrd:Subject", NS)
101
+ data[:subject] = subj_elem.content unless subj_elem.nil?
102
102
 
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
-
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] = send(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
- word = name.dup
80
- i = word.rindex("::")
81
- word = word[(i + 2)..-1] if i
82
-
83
- word.gsub!("::", "/")
84
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
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
- root_element = Nokogiri::XML::Element.new(self.class.entity_name, doc)
121
-
122
- self.class.class_props.each do |prop_def|
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
- root_element
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
- node = Nokogiri::XML::Element.new(name.to_s, doc)
141
- data = send(name).to_s
142
- node.content = data unless data.empty?
143
- node
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
- loglevel = defined?(::Rails) ? ::Rails.configuration.log_level.to_s.upcase : "INFO"
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
- class_props << {name: name, type: type}
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) || name.instance_of?(String)
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