diaspora_federation 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8af59c7c4e5a24b7e263f5d216ae15033dcfde71
4
- data.tar.gz: 5c0db7743659a37fd00cd7bcad942c063cd46c5f
3
+ metadata.gz: c09fc18228d8f509ac6ee451d3f86514112cc959
4
+ data.tar.gz: 7176641baf249258ad914643fb3d0ed07121e481
5
5
  SHA512:
6
- metadata.gz: a9a83b4a6a02cd027dc0aa3471b2e7f38717ce172776b9d60109ef025a9b0450791be6e2afaed47d4ba63464d09fd36f2388e22c689cc1f0a0577c14de769861
7
- data.tar.gz: 4eb06d94138f85abf4ee1d6259fdae829da7ee6fab6a24ea5afb3619feba69bb0dc51b2b17367b1ddc7b94e041c484da4b6161e1627b3390f69ed0f5b9d8c9f2
6
+ metadata.gz: fb127f2108104ba7a3191af13f2620b8f308df88a67671d488a2f61243c4e04cc11e5f5eb1d31fe5ffda66dee7c35f10d8e6ed5edc995e20b966241f537c1517
7
+ data.tar.gz: 0b8222a2035a70e4b92f2d599ef0d5ab34c7a17a3ad7b811f203f86e956f80c849ddab90a555b911d50db095c8346b6ab185f95b492b3ecf7b999d47c4cbd244
data/README.md CHANGED
@@ -43,16 +43,16 @@ DiasporaFederation.configure do |config|
43
43
  config.server_uri = AppConfig.pod_uri
44
44
 
45
45
  config.define_callbacks do
46
- on :person_webfinger_fetch do |handle|
47
- person = Person.find_local_by_diaspora_handle(handle)
46
+ on :fetch_person_for_webfinger do |diaspora_id|
47
+ person = Person.find_local_by_diaspora_id(diaspora_id)
48
48
  if person
49
- DiasporaFederation::WebFinger::WebFinger.new(
49
+ DiasporaFederation::Discovery::WebFinger.new(
50
50
  # ...
51
51
  )
52
52
  end
53
53
  end
54
54
 
55
- on :person_hcard_fetch do |guid|
55
+ on :fetch_person_for_hcard do |guid|
56
56
  # ...
57
57
  end
58
58
  end
@@ -3,16 +3,22 @@ require "diaspora_federation/logging"
3
3
  require "diaspora_federation/callbacks"
4
4
  require "diaspora_federation/properties_dsl"
5
5
  require "diaspora_federation/entity"
6
+ require "diaspora_federation/validators"
6
7
 
7
- require "diaspora_federation/web_finger"
8
+ require "diaspora_federation/fetcher"
9
+
10
+ require "diaspora_federation/entities"
11
+
12
+ require "diaspora_federation/discovery"
8
13
 
9
14
  # diaspora* federation library
10
15
  module DiasporaFederation
11
16
  extend Logging
12
17
 
13
18
  @callbacks = Callbacks.new %i(
14
- person_webfinger_fetch
15
- person_hcard_fetch
19
+ fetch_person_for_webfinger
20
+ fetch_person_for_hcard
21
+ save_person_after_webfinger
16
22
  )
17
23
 
18
24
  class << self
@@ -30,6 +36,12 @@ module DiasporaFederation
30
36
  # config.server_uri = AppConfig.pod_uri
31
37
  attr_accessor :server_uri
32
38
 
39
+ # Set the bundle of certificate authorities (CA) certificates
40
+ #
41
+ # @example
42
+ # config.certificate_authorities = AppConfig.environment.certificate_authorities.get
43
+ attr_accessor :certificate_authorities
44
+
33
45
  # configure the federation library
34
46
  #
35
47
  # @example
@@ -63,10 +75,17 @@ module DiasporaFederation
63
75
  # called from after_initialize
64
76
  # @raise [ConfigurationError] if the configuration is incomplete or invalid
65
77
  def validate_config
66
- configuration_error "Missing server_uri" unless @server_uri.respond_to? :host
78
+ configuration_error "server_uri: Missing or invalid" unless @server_uri.respond_to? :host
79
+
80
+ configuration_error "certificate_authorities: Not configured" if @certificate_authorities.nil?
81
+ unless File.file? @certificate_authorities
82
+ configuration_error "certificate_authorities: File not found: #{@certificate_authorities}"
83
+ end
84
+
67
85
  unless @callbacks.definition_complete?
68
86
  configuration_error "Missing handlers for #{@callbacks.missing_handlers.join(', ')}"
69
87
  end
88
+
70
89
  logger.info "successfully configured the federation library"
71
90
  end
72
91
 
@@ -0,0 +1,14 @@
1
+ module DiasporaFederation
2
+ # This module provides the namespace for the various classes implementing
3
+ # WebFinger and other protocols used for metadata discovery on remote servers
4
+ # in the Diaspora* network.
5
+ module Discovery
6
+ end
7
+ end
8
+
9
+ require "diaspora_federation/discovery/exceptions"
10
+ require "diaspora_federation/discovery/xrd_document"
11
+ require "diaspora_federation/discovery/host_meta"
12
+ require "diaspora_federation/discovery/web_finger"
13
+ require "diaspora_federation/discovery/h_card"
14
+ require "diaspora_federation/discovery/discovery"
@@ -0,0 +1,94 @@
1
+ module DiasporaFederation
2
+ module Discovery
3
+ # This class contains the logic to fetch all data for the given diaspora ID
4
+ class Discovery
5
+ include DiasporaFederation::Logging
6
+
7
+ # @return [String] the diaspora ID of the account
8
+ attr_reader :diaspora_id
9
+
10
+ # create a discovery class for the diaspora-id
11
+ # @param [String] diaspora_id the diaspora id to discover
12
+ def initialize(diaspora_id)
13
+ @diaspora_id = clean_diaspora_id(diaspora_id)
14
+ end
15
+
16
+ # fetch all metadata for the account and saves it via callback
17
+ # @return [Person]
18
+ def fetch_and_save
19
+ logger.info "Fetch data for #{diaspora_id}"
20
+
21
+ unless diaspora_id == clean_diaspora_id(webfinger.acct_uri)
22
+ raise DiscoveryError, "Diaspora ID does not match: Wanted #{diaspora_id} but got" \
23
+ " #{clean_diaspora_id(webfinger.acct_uri)}"
24
+ end
25
+
26
+ DiasporaFederation.callbacks.trigger(:save_person_after_webfinger, person)
27
+ logger.info "successfully webfingered #{diaspora_id}"
28
+ person
29
+ end
30
+
31
+ private
32
+
33
+ def clean_diaspora_id(diaspora_id)
34
+ diaspora_id.strip.sub("acct:", "").to_s.downcase
35
+ end
36
+
37
+ def get(url, http_fallback=false)
38
+ logger.info "Fetching #{url} for #{diaspora_id}"
39
+ response = Fetcher.get(url)
40
+ raise "Failed to fetch #{url}: #{response.status}" unless response.success?
41
+ response.body
42
+ rescue => e
43
+ if http_fallback && url.start_with?("https://")
44
+ logger.warn "Retry with http: #{url} for #{diaspora_id}: #{e.class}: #{e.message}"
45
+ url.sub!("https://", "http://")
46
+ retry
47
+ else
48
+ raise DiscoveryError, "Failed to fetch #{url} for #{diaspora_id}: #{e.class}: #{e.message}"
49
+ end
50
+ end
51
+
52
+ def host_meta_url
53
+ domain = diaspora_id.split("@")[1]
54
+ "https://#{domain}/.well-known/host-meta"
55
+ end
56
+
57
+ def legacy_webfinger_url_from_host_meta
58
+ # this tries the xrd url with https first, then falls back to http
59
+ host_meta = HostMeta.from_xml get(host_meta_url, true)
60
+ host_meta.webfinger_template_url.gsub("{uri}", "acct:#{diaspora_id}")
61
+ end
62
+
63
+ def webfinger
64
+ @webfinger ||= WebFinger.from_xml get(legacy_webfinger_url_from_host_meta)
65
+ end
66
+
67
+ def hcard
68
+ @hcard ||= HCard.from_html get(webfinger.hcard_url)
69
+ end
70
+
71
+ def person
72
+ @person ||= Entities::Person.new(
73
+ guid: hcard.guid || webfinger.guid,
74
+ diaspora_id: diaspora_id,
75
+ url: webfinger.seed_url,
76
+ exported_key: hcard.public_key || webfinger.public_key,
77
+ profile: profile
78
+ )
79
+ end
80
+
81
+ def profile
82
+ Entities::Profile.new(
83
+ diaspora_id: diaspora_id,
84
+ first_name: hcard.first_name,
85
+ last_name: hcard.last_name,
86
+ image_url: hcard.photo_large_url,
87
+ image_url_medium: hcard.photo_medium_url,
88
+ image_url_small: hcard.photo_small_url,
89
+ searchable: hcard.searchable
90
+ )
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,5 +1,5 @@
1
1
  module DiasporaFederation
2
- module WebFinger
2
+ module Discovery
3
3
  # Raised, if the XML structure is invalid
4
4
  class InvalidDocument < RuntimeError
5
5
  end
@@ -11,5 +11,9 @@ module DiasporaFederation
11
11
  # * if the html passed to {HCard.from_html} in some way is malformed, invalid or incomplete.
12
12
  class InvalidData < RuntimeError
13
13
  end
14
+
15
+ # Raised, if there is an error while discover a new person
16
+ class DiscoveryError < RuntimeError
17
+ end
14
18
  end
15
19
  end
@@ -1,5 +1,5 @@
1
1
  module DiasporaFederation
2
- module WebFinger
2
+ module Discovery
3
3
  # This class provides the means of generating an parsing account data to and
4
4
  # from the hCard format.
5
5
  # hCard is based on +RFC 2426+ (vCard) which got superseded by +RFC 6350+.
@@ -15,17 +15,17 @@ module DiasporaFederation
15
15
  #
16
16
  # @example Creating a hCard document from a person hash
17
17
  # hc = HCard.new(
18
- # guid: "0123456789abcdef",
19
- # nickname: "user",
20
- # full_name: "User Name",
21
- # seed_url: "https://server.example/",
22
- # photo_large_url: "https://server.example/uploads/l.jpg",
23
- # photo_medium_url: "https://server.example/uploads/m.jpg",
24
- # photo_small_url: "https://server.example/uploads/s.jpg",
25
- # serialized_public_key: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----",
26
- # searchable: true,
27
- # first_name: "User",
28
- # last_name: "Name"
18
+ # guid: "0123456789abcdef",
19
+ # nickname: "user",
20
+ # full_name: "User Name",
21
+ # seed_url: "https://server.example/",
22
+ # photo_large_url: "https://server.example/uploads/l.jpg",
23
+ # photo_medium_url: "https://server.example/uploads/m.jpg",
24
+ # photo_small_url: "https://server.example/uploads/s.jpg",
25
+ # public_key: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----",
26
+ # searchable: true,
27
+ # first_name: "User",
28
+ # last_name: "Name"
29
29
  # )
30
30
  # html_string = hc.to_html
31
31
  #
@@ -48,12 +48,12 @@ module DiasporaFederation
48
48
  property :guid
49
49
 
50
50
  # @!attribute [r] nickname
51
- # the first part of the diaspora handle
51
+ # the first part of the diaspora ID
52
52
  # @return [String] nickname
53
53
  property :nickname
54
54
 
55
55
  # @!attribute [r] full_name
56
- # @return [String] display name of the user
56
+ # @return [String] display name of the user
57
57
  property :full_name
58
58
 
59
59
  # @!attribute [r] url
@@ -70,10 +70,8 @@ module DiasporaFederation
70
70
  # DER-encoded PKCS#1 key beginning with the text
71
71
  # "-----BEGIN PUBLIC KEY-----" and ending with "-----END PUBLIC KEY-----".
72
72
  #
73
- # @note the public key is new in the hcard and is optional now.
74
- #
75
73
  # @return [String] public key
76
- property :public_key, default: nil
74
+ property :public_key
77
75
 
78
76
  # @!attribute [r] photo_large_url
79
77
  # @return [String] url to the big avatar (300x300)
@@ -163,8 +161,8 @@ module DiasporaFederation
163
161
  def self.from_html(html_string)
164
162
  doc = parse_html_and_validate(html_string)
165
163
 
166
- data = {
167
- guid: content_from_doc(doc, :uid),
164
+ new(
165
+ guid: guid_from_doc(doc),
168
166
  nickname: content_from_doc(doc, :nickname),
169
167
  full_name: content_from_doc(doc, :fn),
170
168
  url: element_from_doc(doc, :url)["href"],
@@ -172,15 +170,13 @@ module DiasporaFederation
172
170
  photo_medium_url: photo_from_doc(doc, :photo_medium),
173
171
  photo_small_url: photo_from_doc(doc, :photo_small),
174
172
  searchable: (content_from_doc(doc, :searchable) == "true"),
173
+ # TODO: public key is new and can be missing
174
+ public_key: (content_from_doc(doc, :key) unless element_from_doc(doc, :key).nil?),
175
175
 
176
- # TODO: change me! ###################
176
+ # TODO: remove first_name and last_name!
177
177
  first_name: content_from_doc(doc, :given_name),
178
178
  last_name: content_from_doc(doc, :family_name)
179
- #######################################
180
- }
181
- # TODO: public key is new and can be missing
182
- data[:public_key] = content_from_doc(doc, :key) unless element_from_doc(doc, :key).nil?
183
- new(data)
179
+ )
184
180
  end
185
181
 
186
182
  private
@@ -288,6 +284,14 @@ module DiasporaFederation
288
284
  element_from_doc(doc, photo_selector)["src"]
289
285
  end
290
286
  private_class_method :photo_from_doc
287
+
288
+ # @deprecated hack for old hcard
289
+ # @todo remove this when all pods have the new generator
290
+ def self.guid_from_doc(doc)
291
+ uid_element = element_from_doc(doc, :uid)
292
+ uid_element.content unless uid_element[:class].include? "nickname"
293
+ end
294
+ private_class_method :guid_from_doc
291
295
  end
292
296
  end
293
297
  end
@@ -1,6 +1,6 @@
1
1
 
2
2
  module DiasporaFederation
3
- module WebFinger
3
+ module Discovery
4
4
  # Generates and parses Host Meta documents.
5
5
  #
6
6
  # This is a minimal implementation of the standard, only to the degree of what
@@ -19,8 +19,14 @@ module DiasporaFederation
19
19
  class HostMeta
20
20
  private_class_method :new
21
21
 
22
+ # create a new host-meta instance
23
+ # @param [String] webfinger_url the webfinger-url
24
+ def initialize(webfinger_url)
25
+ @webfinger_url = webfinger_url
26
+ end
27
+
22
28
  # URL fragment to append to the base URL
23
- WEBFINGER_SUFFIX = "webfinger?q={uri}"
29
+ WEBFINGER_SUFFIX = "/webfinger?q={uri}"
24
30
 
25
31
  # Returns the WebFinger URL that was used to build this instance (either from
26
32
  # xml or by giving a base URL).
@@ -42,18 +48,14 @@ module DiasporaFederation
42
48
 
43
49
  # Builds a new HostMeta instance and constructs the WebFinger URL from the
44
50
  # given base URL by appending HostMeta::WEBFINGER_SUFFIX.
51
+ # @param [String, URL] base_url the base-url for the webfinger-url
45
52
  # @return [HostMeta]
46
53
  # @raise [InvalidData] if the webfinger url is malformed
47
54
  def self.from_base_url(base_url)
48
- raise ArgumentError, "base_url is not a String" unless base_url.instance_of?(String)
49
-
50
- base_url += "/" unless base_url.end_with?("/")
51
- webfinger_url = base_url + WEBFINGER_SUFFIX
55
+ webfinger_url = "#{base_url.to_s.chomp('/')}#{WEBFINGER_SUFFIX}"
52
56
  raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url)
53
57
 
54
- hm = allocate
55
- hm.instance_variable_set(:@webfinger_url, webfinger_url)
56
- hm
58
+ new(webfinger_url)
57
59
  end
58
60
 
59
61
  # Reads the given Host Meta XML document string and populates the
@@ -67,16 +69,14 @@ module DiasporaFederation
67
69
  webfinger_url = webfinger_url_from_xrd(data)
68
70
  raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url)
69
71
 
70
- hm = allocate
71
- hm.instance_variable_set(:@webfinger_url, webfinger_url)
72
- hm
72
+ new(webfinger_url)
73
73
  end
74
74
 
75
75
  # Applies some basic sanity-checking to the given URL
76
76
  # @param [String] url validation subject
77
77
  # @return [Boolean] validation result
78
78
  def self.webfinger_url_valid?(url)
79
- !url.nil? && url.instance_of?(String) && url =~ %r{^https?:\/\/.*\{uri\}}i
79
+ !url.nil? && url.instance_of?(String) && url =~ %r{^https?:\/\/.*\/.*\{uri\}.*}i
80
80
  end
81
81
  private_class_method :webfinger_url_valid?
82
82
 
@@ -1,5 +1,5 @@
1
1
  module DiasporaFederation
2
- module WebFinger
2
+ module Discovery
3
3
  # The WebFinger document used for Diaspora* user discovery is based on an older
4
4
  # draft of the specification you can find in the wiki of the "webfinger" project
5
5
  # on {http://code.google.com/p/webfinger/wiki/WebFingerProtocol Google Code}
@@ -147,20 +147,23 @@ module DiasporaFederation
147
147
  def self.from_xml(webfinger_xml)
148
148
  data = parse_xml_and_validate(webfinger_xml)
149
149
 
150
- hcard_url, seed_url, guid, profile_url, atom_url, salmon_url, public_key = parse_links(data)
150
+ links = data[:links]
151
+
152
+ # TODO: remove! public key is deprecated in webfinger
153
+ public_key = parse_link(links, REL_PUBKEY)
151
154
 
152
155
  new(
153
156
  acct_uri: data[:subject],
154
- alias_url: data[:aliases].first,
155
- hcard_url: hcard_url,
156
- seed_url: seed_url,
157
- profile_url: profile_url,
158
- atom_url: atom_url,
159
- salmon_url: salmon_url,
157
+ alias_url: clean_alias(data[:aliases].first),
158
+ hcard_url: parse_link(links, REL_HCARD),
159
+ seed_url: parse_link(links, REL_SEED),
160
+ profile_url: parse_link(links, REL_PROFILE),
161
+ atom_url: parse_link(links, REL_ATOM),
162
+ salmon_url: parse_link(links, REL_SALMON),
160
163
 
161
164
  # TODO: remove me! ##########
162
- guid: guid,
163
- public_key: Base64.strict_decode64(public_key)
165
+ guid: parse_link(links, REL_GUID),
166
+ public_key: (Base64.strict_decode64(public_key) if public_key)
164
167
  )
165
168
  end
166
169
 
@@ -172,10 +175,10 @@ module DiasporaFederation
172
175
  # @return [Hash] data XML data
173
176
  # @raise [InvalidData] if the given XML string is invalid or incomplete
174
177
  def self.parse_xml_and_validate(webfinger_xml)
175
- data = XrdDocument.xml_data(webfinger_xml)
176
- valid = data.key?(:subject) && data.key?(:aliases) && data.key?(:links)
177
- raise InvalidData, "webfinger xml is incomplete" unless valid
178
- data
178
+ XrdDocument.xml_data(webfinger_xml).tap do |data|
179
+ valid = data.key?(:subject) && data.key?(:aliases) && data.key?(:links)
180
+ raise InvalidData, "webfinger xml is incomplete" unless valid
181
+ end
179
182
  end
180
183
  private_class_method :parse_xml_and_validate
181
184
 
@@ -209,22 +212,15 @@ module DiasporaFederation
209
212
  ##################################
210
213
  end
211
214
 
212
- def self.parse_links(data)
213
- links = data[:links]
214
- hcard = parse_link(links, REL_HCARD)
215
- seed = parse_link(links, REL_SEED)
216
- guid = parse_link(links, REL_GUID)
217
- profile = parse_link(links, REL_PROFILE)
218
- atom = parse_link(links, REL_ATOM)
219
- salmon = parse_link(links, REL_SALMON)
220
- pubkey = parse_link(links, REL_PUBKEY)
221
- raise InvalidData, "webfinger xml is incomplete" unless [hcard, seed, guid, profile, atom, salmon, pubkey].all?
222
- [hcard[:href], seed[:href], guid[:href], profile[:href], atom[:href], salmon[:href], pubkey[:href]]
215
+ def self.parse_link(links, rel)
216
+ element = links.find {|l| l[:rel] == rel }
217
+ element ? element[:href] : nil
223
218
  end
224
- private_class_method :parse_links
219
+ private_class_method :parse_link
225
220
 
226
- def self.parse_link(links, rel)
227
- links.find {|l| l[:rel] == rel }
221
+ # @deprecated remove this, when all pods use this gem for generation
222
+ def self.clean_alias(alias_string)
223
+ alias_string.gsub(/\A"|"\Z/, "")
228
224
  end
229
225
  private_class_method :parse_link
230
226
  end