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
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