diaspora_federation 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +55 -5
- data/lib/diaspora_federation.rb +43 -79
- data/lib/diaspora_federation/callbacks.rb +62 -0
- data/lib/diaspora_federation/entity.rb +146 -0
- data/lib/diaspora_federation/logging.rb +3 -4
- data/lib/diaspora_federation/properties_dsl.rb +106 -0
- data/lib/diaspora_federation/version.rb +1 -2
- data/lib/diaspora_federation/web_finger.rb +0 -1
- data/lib/diaspora_federation/web_finger/exceptions.rb +1 -5
- data/lib/diaspora_federation/web_finger/h_card.rb +101 -127
- data/lib/diaspora_federation/web_finger/host_meta.rb +0 -7
- data/lib/diaspora_federation/web_finger/web_finger.rb +79 -110
- data/lib/diaspora_federation/web_finger/xrd_document.rb +0 -3
- data/lib/tasks/build.rake +17 -0
- metadata +20 -30
- data/Rakefile +0 -26
- data/app/controllers/diaspora_federation/application_controller.rb +0 -6
- data/app/controllers/diaspora_federation/h_card_controller.rb +0 -20
- data/app/controllers/diaspora_federation/receive_controller.rb +0 -35
- data/app/controllers/diaspora_federation/webfinger_controller.rb +0 -60
- data/config/routes.rb +0 -15
- data/lib/diaspora_federation/engine.rb +0 -15
@@ -1,12 +1,10 @@
|
|
1
1
|
module DiasporaFederation
|
2
|
-
|
3
|
-
# logging module for the diaspora federation engine
|
2
|
+
# logging module for the diaspora federation
|
4
3
|
#
|
5
4
|
# it uses the logging-gem if available
|
6
5
|
module Logging
|
7
6
|
private
|
8
7
|
|
9
|
-
##
|
10
8
|
# get the logger for this class
|
11
9
|
#
|
12
10
|
# use the logging-gem if available, else use a default logger
|
@@ -17,7 +15,8 @@ module DiasporaFederation
|
|
17
15
|
|
18
16
|
# fallback logger
|
19
17
|
@logger = Logger.new(STDOUT)
|
20
|
-
|
18
|
+
loglevel = defined?(::Rails) ? ::Rails.configuration.log_level.to_s.upcase : "INFO"
|
19
|
+
@logger.level = Logger.const_get(loglevel)
|
21
20
|
@logger
|
22
21
|
end
|
23
22
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
# Provides a simple DSL for specifying {Entity} properties during class
|
3
|
+
# definition.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# property :prop
|
7
|
+
# property :optional, default: false
|
8
|
+
# property :dynamic_default, default: -> { Time.now }
|
9
|
+
# entity :nested, NestedEntity
|
10
|
+
# entity :multiple, [OtherEntity]
|
11
|
+
module PropertiesDSL
|
12
|
+
# @return [Array<Hash>] hash of declared entity properties
|
13
|
+
def class_props
|
14
|
+
@class_props ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define a generic (string-type) property
|
18
|
+
# @param [Symbol] name property name
|
19
|
+
# @param [Hash] opts further options
|
20
|
+
# @option opts [Object, #call] :default a default value, making the
|
21
|
+
# property optional
|
22
|
+
def property(name, opts={})
|
23
|
+
define_property name, String, opts
|
24
|
+
end
|
25
|
+
|
26
|
+
# Define a property that should contain another Entity or an array of
|
27
|
+
# other Entities
|
28
|
+
# @param [Symbol] name property name
|
29
|
+
# @param [Entity, Array<Entity>] type Entity subclass or
|
30
|
+
# Array with exactly one Entity subclass constant inside
|
31
|
+
# @param [Hash] opts further options
|
32
|
+
# @option opts [Object, #call] :default a default value, making the
|
33
|
+
# property optional
|
34
|
+
def entity(name, type, opts={})
|
35
|
+
raise InvalidType unless type_valid?(type)
|
36
|
+
|
37
|
+
define_property name, type, opts
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return array of missing required property names
|
41
|
+
# @return [Array<Symbol>] missing required property names
|
42
|
+
def missing_props(args)
|
43
|
+
class_prop_names - default_props.keys - args.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return a new hash of default values, with dynamic values
|
47
|
+
# resolved on each call
|
48
|
+
# @return [Hash] default values
|
49
|
+
def default_values
|
50
|
+
default_props.each_with_object({}) { |(name, prop), hash|
|
51
|
+
hash[name] = prop.respond_to?(:call) ? prop.call : prop
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns all nested Entities
|
56
|
+
# @return [Array<Hash>] nested properties
|
57
|
+
def nested_class_props
|
58
|
+
@nested_class_props ||= class_props.select {|p| p[:type] != String }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns all property names
|
62
|
+
# @return [Array] property names
|
63
|
+
def class_prop_names
|
64
|
+
@class_prop_names ||= class_props.map {|p| p[:name] }
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def define_property(name, type, opts={})
|
70
|
+
raise InvalidName unless name_valid?(name)
|
71
|
+
|
72
|
+
class_props << {name: name, type: type}
|
73
|
+
default_props[name] = opts[:default] if opts.has_key? :default
|
74
|
+
|
75
|
+
instance_eval { attr_reader name }
|
76
|
+
end
|
77
|
+
|
78
|
+
# checks if the name is a +Symbol+ or a +String+
|
79
|
+
# @param [String, Symbol] name the name to check
|
80
|
+
# @return [Boolean]
|
81
|
+
def name_valid?(name)
|
82
|
+
name.instance_of?(Symbol) || name.instance_of?(String)
|
83
|
+
end
|
84
|
+
|
85
|
+
# checks if the type extends {Entity}
|
86
|
+
# @param [Class] type the type to check
|
87
|
+
# @return [Boolean]
|
88
|
+
def type_valid?(type)
|
89
|
+
[type].flatten.all? { |type|
|
90
|
+
type.respond_to?(:ancestors) && type.ancestors.include?(Entity)
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_props
|
95
|
+
@default_props ||= {}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Raised, if the name is of an unexpected type
|
99
|
+
class InvalidName < RuntimeError
|
100
|
+
end
|
101
|
+
|
102
|
+
# Raised, if the type is of an unexpected type
|
103
|
+
class InvalidType < RuntimeError
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,18 +1,14 @@
|
|
1
1
|
module DiasporaFederation
|
2
2
|
module WebFinger
|
3
|
-
##
|
4
3
|
# Raised, if the XML structure is invalid
|
5
4
|
class InvalidDocument < RuntimeError
|
6
5
|
end
|
7
6
|
|
8
|
-
##
|
9
7
|
# Raised, if something is wrong with the webfinger data
|
10
8
|
#
|
11
9
|
# * if the +webfinger_url+ is missing or malformed in {HostMeta.from_base_url} or {HostMeta.from_xml}
|
12
|
-
# * if the +data+ given to {WebFinger.from_person} is an invalid type or doesn't contain all required entries
|
13
10
|
# * if the parsed XML from {WebFinger.from_xml} is incomplete
|
14
|
-
# * if the
|
15
|
-
# are in some way malformed, invalid or incomplete.
|
11
|
+
# * if the html passed to {HCard.from_html} in some way is malformed, invalid or incomplete.
|
16
12
|
class InvalidData < RuntimeError
|
17
13
|
end
|
18
14
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module DiasporaFederation
|
2
2
|
module WebFinger
|
3
|
-
##
|
4
3
|
# This class provides the means of generating an parsing account data to and
|
5
4
|
# from the hCard format.
|
6
5
|
# hCard is based on +RFC 2426+ (vCard) which got superseded by +RFC 6350+.
|
@@ -14,20 +13,20 @@ module DiasporaFederation
|
|
14
13
|
# correctly nested according to the hCard standard and class names are
|
15
14
|
# partially wrong. Also, apart from that, it's just ugly.
|
16
15
|
#
|
17
|
-
# @example Creating a hCard document from
|
18
|
-
# hc = HCard.
|
19
|
-
# guid:
|
20
|
-
# nickname:
|
21
|
-
# full_name:
|
22
|
-
#
|
23
|
-
# photo_large_url:
|
24
|
-
# photo_medium_url:
|
25
|
-
# photo_small_url:
|
26
|
-
#
|
27
|
-
# searchable:
|
28
|
-
# first_name:
|
29
|
-
# last_name:
|
30
|
-
#
|
16
|
+
# @example Creating a hCard document from a person hash
|
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"
|
29
|
+
# )
|
31
30
|
# html_string = hc.to_html
|
32
31
|
#
|
33
32
|
# @example Create a HCard instance from an hCard document
|
@@ -40,65 +39,77 @@ module DiasporaFederation
|
|
40
39
|
# @see http://microformats.org/wiki/h-card "h-card" (draft)
|
41
40
|
# @see http://www.ietf.org/rfc/rfc2426.txt "vCard MIME Directory Profile" (obsolete)
|
42
41
|
# @see http://www.ietf.org/rfc/rfc6350.txt "vCard Format Specification"
|
43
|
-
class HCard
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
#
|
42
|
+
class HCard < Entity
|
43
|
+
# @!attribute [r] guid
|
44
|
+
# This is just the guid. When a user creates an account on a pod, the pod
|
45
|
+
# MUST assign them a guid - a random hexadecimal string of at least 8
|
46
|
+
# hexadecimal digits.
|
47
|
+
# @return [String] guid
|
48
|
+
property :guid
|
49
|
+
|
50
|
+
# @!attribute [r] nickname
|
51
|
+
# the first part of the diaspora handle
|
52
|
+
# @return [String] nickname
|
53
|
+
property :nickname
|
54
|
+
|
55
|
+
# @!attribute [r] full_name
|
56
|
+
# @return [String] display name of the user
|
57
|
+
property :full_name
|
58
|
+
|
59
|
+
# @!attribute [r] url
|
60
|
+
# @deprecated should be changed to the profile url. The pod url is in
|
61
|
+
# the WebFinger (see {WebFinger#seed_url}, will affect older Diaspora*
|
62
|
+
# installations).
|
62
63
|
#
|
63
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
attr_reader :pubkey
|
72
|
-
|
73
|
-
# @return [String] url to the big avatar (300x300)
|
74
|
-
attr_reader :photo_large_url
|
75
|
-
# @return [String] url to the medium avatar (100x100)
|
76
|
-
attr_reader :photo_medium_url
|
77
|
-
# @return [String] url to the small avatar (50x50)
|
78
|
-
attr_reader :photo_small_url
|
79
|
-
|
80
|
-
# @deprecated We decided to only use one name field, these should be removed
|
81
|
-
# in later iterations (will affect older Diaspora* installations).
|
64
|
+
# @return [String] link to the pod
|
65
|
+
property :url
|
66
|
+
|
67
|
+
# @!attribute [r] public_key
|
68
|
+
# When a user is created on the pod, the pod MUST generate a pgp keypair
|
69
|
+
# for them. This key is used for signing messages. The format is a
|
70
|
+
# DER-encoded PKCS#1 key beginning with the text
|
71
|
+
# "-----BEGIN PUBLIC KEY-----" and ending with "-----END PUBLIC KEY-----".
|
82
72
|
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
73
|
+
# @note the public key is new in the hcard and is optional now.
|
74
|
+
#
|
75
|
+
# @return [String] public key
|
76
|
+
property :public_key, default: nil
|
77
|
+
|
78
|
+
# @!attribute [r] photo_large_url
|
79
|
+
# @return [String] url to the big avatar (300x300)
|
80
|
+
property :photo_large_url
|
81
|
+
# @!attribute [r] photo_medium_url
|
82
|
+
# @return [String] url to the medium avatar (100x100)
|
83
|
+
property :photo_medium_url
|
84
|
+
# @!attribute [r] photo_small_url
|
85
|
+
# @return [String] url to the small avatar (50x50)
|
86
|
+
property :photo_small_url
|
87
|
+
|
88
|
+
# @!attribute [r] first_name
|
89
|
+
# @deprecated We decided to only use one name field, these should be removed
|
90
|
+
# in later iterations (will affect older Diaspora* installations).
|
91
|
+
#
|
92
|
+
# @see #full_name
|
93
|
+
# @return [String] first name
|
94
|
+
property :first_name
|
86
95
|
|
87
|
-
#
|
88
|
-
#
|
96
|
+
# @!attribute [r] last_name
|
97
|
+
# @deprecated We decided to only use one name field, these should be removed
|
98
|
+
# in later iterations (will affect older Diaspora* installations).
|
89
99
|
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
100
|
+
# @see #full_name
|
101
|
+
# @return [String] last name
|
102
|
+
property :last_name
|
103
|
+
|
104
|
+
# @!attribute [r] searchable
|
105
|
+
# @deprecated As this is a simple property, consider move to WebFinger instead
|
106
|
+
# of HCard. vCard has no comparable field for this information, but
|
107
|
+
# Webfinger may declare arbitrary properties (will affect older Diaspora*
|
108
|
+
# installations).
|
98
109
|
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
|
110
|
+
# flag if a user is searchable by name
|
111
|
+
# @return [Boolean] searchable flag
|
112
|
+
property :searchable
|
102
113
|
|
103
114
|
# CSS selectors for finding all the hCard fields
|
104
115
|
SELECTORS = {
|
@@ -128,7 +139,7 @@ module DiasporaFederation
|
|
128
139
|
add_simple_property(content, :searchable, "searchable", @searchable)
|
129
140
|
|
130
141
|
add_property(content, :key) do |html|
|
131
|
-
html.pre(@
|
142
|
+
html.pre(@public_key.to_s, class: "key")
|
132
143
|
end
|
133
144
|
|
134
145
|
# TODO: remove me! ###################
|
@@ -145,33 +156,6 @@ module DiasporaFederation
|
|
145
156
|
builder.doc.to_xhtml(indent: 2, indent_text: " ")
|
146
157
|
end
|
147
158
|
|
148
|
-
# Creates a new HCard instance from the given Hash containing profile data
|
149
|
-
# @param [Hash] data account data
|
150
|
-
# @return [HCard] HCard instance
|
151
|
-
# @raise [InvalidData] if the account data Hash is invalid or incomplete
|
152
|
-
def self.from_profile(data)
|
153
|
-
raise InvalidData unless account_data_complete?(data)
|
154
|
-
|
155
|
-
hc = allocate
|
156
|
-
hc.instance_eval {
|
157
|
-
@guid = data[:guid]
|
158
|
-
@nickname = data[:nickname]
|
159
|
-
@full_name = data[:full_name]
|
160
|
-
@url = data[:url]
|
161
|
-
@photo_large_url = data[:photo_large_url]
|
162
|
-
@photo_medium_url = data[:photo_medium_url]
|
163
|
-
@photo_small_url = data[:photo_small_url]
|
164
|
-
@pubkey = data[:pubkey]
|
165
|
-
@searchable = data[:searchable]
|
166
|
-
|
167
|
-
# TODO: remove me! ###################
|
168
|
-
@first_name = data[:first_name]
|
169
|
-
@last_name = data[:last_name]
|
170
|
-
#######################################
|
171
|
-
}
|
172
|
-
hc
|
173
|
-
end
|
174
|
-
|
175
159
|
# Creates a new HCard instance from the given HTML string.
|
176
160
|
# @param html_string [String] HTML string
|
177
161
|
# @return [HCard] HCard instance
|
@@ -179,24 +163,24 @@ module DiasporaFederation
|
|
179
163
|
def self.from_html(html_string)
|
180
164
|
doc = parse_html_and_validate(html_string)
|
181
165
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
@pubkey = content_from_doc(doc, :key) unless element_from_doc(doc, :key).nil?
|
192
|
-
@searchable = content_from_doc(doc, :searchable) == "true"
|
166
|
+
data = {
|
167
|
+
guid: content_from_doc(doc, :uid),
|
168
|
+
nickname: content_from_doc(doc, :nickname),
|
169
|
+
full_name: content_from_doc(doc, :fn),
|
170
|
+
url: element_from_doc(doc, :url)["href"],
|
171
|
+
photo_large_url: photo_from_doc(doc, :photo),
|
172
|
+
photo_medium_url: photo_from_doc(doc, :photo_medium),
|
173
|
+
photo_small_url: photo_from_doc(doc, :photo_small),
|
174
|
+
searchable: (content_from_doc(doc, :searchable) == "true"),
|
193
175
|
|
194
176
|
# TODO: change me! ###################
|
195
|
-
|
196
|
-
|
177
|
+
first_name: content_from_doc(doc, :given_name),
|
178
|
+
last_name: content_from_doc(doc, :family_name)
|
197
179
|
#######################################
|
198
180
|
}
|
199
|
-
|
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)
|
200
184
|
end
|
201
185
|
|
202
186
|
private
|
@@ -271,19 +255,6 @@ module DiasporaFederation
|
|
271
255
|
end
|
272
256
|
end
|
273
257
|
|
274
|
-
# Checks the given account data Hash for correct type and completeness.
|
275
|
-
# @param [Hash] data account data
|
276
|
-
# @return [Boolean] validation result
|
277
|
-
def self.account_data_complete?(data)
|
278
|
-
data.instance_of?(Hash) &&
|
279
|
-
%i(
|
280
|
-
guid nickname full_name url
|
281
|
-
photo_large_url photo_medium_url photo_small_url
|
282
|
-
pubkey searchable first_name last_name
|
283
|
-
).all? {|k| data.key? k }
|
284
|
-
end
|
285
|
-
private_class_method :account_data_complete?
|
286
|
-
|
287
258
|
# Make sure some of the most important elements are present in the parsed
|
288
259
|
# HTML document.
|
289
260
|
# @param [LibXML::XML::Document] doc HTML document
|
@@ -303,17 +274,20 @@ module DiasporaFederation
|
|
303
274
|
end
|
304
275
|
private_class_method :parse_html_and_validate
|
305
276
|
|
306
|
-
def element_from_doc(doc, selector)
|
277
|
+
def self.element_from_doc(doc, selector)
|
307
278
|
doc.at_css(SELECTORS[selector])
|
308
279
|
end
|
280
|
+
private_class_method :element_from_doc
|
309
281
|
|
310
|
-
def content_from_doc(doc, content_selector)
|
282
|
+
def self.content_from_doc(doc, content_selector)
|
311
283
|
element_from_doc(doc, content_selector).content
|
312
284
|
end
|
285
|
+
private_class_method :content_from_doc
|
313
286
|
|
314
|
-
def photo_from_doc(doc, photo_selector)
|
287
|
+
def self.photo_from_doc(doc, photo_selector)
|
315
288
|
element_from_doc(doc, photo_selector)["src"]
|
316
289
|
end
|
290
|
+
private_class_method :photo_from_doc
|
317
291
|
end
|
318
292
|
end
|
319
293
|
end
|