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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b3836cd6caf68ffba2a101eb90c98932a7cfc1e
|
4
|
+
data.tar.gz: 87fcf932a0f7c2b9151af3bd12c72a1acaf6c935
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94783e21640104926dac495d63976435dee0e47f99cf0d6e9d71a07615cd645e2aecee0a3cb73f98f46fc974e4a6835d32999906824610a607fd598557cc0848
|
7
|
+
data.tar.gz: 13a49b3cb39df94c54c67289da76fb41f2c8a9db3ec44ad865ce88a4439cd9487cdf3f796a341c85293437b3a330fd40b2510f48908d8b422eb927a59ca857e0
|
data/README.md
CHANGED
@@ -1,18 +1,68 @@
|
|
1
|
-
# diaspora* federation
|
1
|
+
# diaspora* federation library
|
2
2
|
|
3
|
-
|
3
|
+
**A library that provides functionalities needed for the diaspora* federation protocol**
|
4
4
|
|
5
5
|
[![Build Status](https://travis-ci.org/SuperTux88/diaspora_federation.svg?branch=master)](https://travis-ci.org/SuperTux88/diaspora_federation)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/SuperTux88/diaspora_federation/badges/gpa.svg)](https://codeclimate.com/github/SuperTux88/diaspora_federation)
|
7
7
|
[![Test Coverage](https://codeclimate.com/github/SuperTux88/diaspora_federation/badges/coverage.svg)](https://codeclimate.com/github/SuperTux88/diaspora_federation/coverage)
|
8
8
|
[![Dependency Status](https://gemnasium.com/SuperTux88/diaspora_federation.svg)](https://gemnasium.com/SuperTux88/diaspora_federation)
|
9
9
|
[![Inline docs](https://inch-ci.org/github/SuperTux88/diaspora_federation.svg?branch=master)](https://inch-ci.org/github/SuperTux88/diaspora_federation)
|
10
|
+
[![Gem Version](https://badge.fury.io/rb/diaspora_federation.svg)](https://badge.fury.io/rb/diaspora_federation)
|
10
11
|
|
11
|
-
[Documentation](http://www.rubydoc.info/
|
12
|
-
[Project site](https://diasporafoundation.org) |
|
13
|
-
[Wiki](https://wiki.diasporafoundation.org) |
|
12
|
+
[Documentation](http://www.rubydoc.info/gems/diaspora_federation/) |
|
14
13
|
[Bugtracker](https://github.com/SuperTux88/diaspora_federation/issues)
|
15
14
|
|
15
|
+
## Library
|
16
|
+
|
17
|
+
The ```diaspora_federation``` gem provides the functionality for de-/serialization and de-/encryption of Entities
|
18
|
+
in the protocols used for communication among the various installations of Diaspora*
|
19
|
+
|
20
|
+
## Rails Engine
|
21
|
+
|
22
|
+
The ```diaspora_federation-rails``` gem is a rails engine that adds the diaspora* federation protocol to a rails app.
|
23
|
+
|
24
|
+
### Usage
|
25
|
+
|
26
|
+
Add the gem to your ```Gemfile```:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
gem "diaspora_federation-rails"
|
30
|
+
```
|
31
|
+
|
32
|
+
Mount the routes in your ```config/routes.rb```:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
mount DiasporaFederation::Engine => "/"
|
36
|
+
```
|
37
|
+
|
38
|
+
Configure the engine in ```config/initializers/diaspora_federation.rb```:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
DiasporaFederation.configure do |config|
|
42
|
+
# the pod url
|
43
|
+
config.server_uri = AppConfig.pod_uri
|
44
|
+
|
45
|
+
# the class to be used for a person
|
46
|
+
config.person_class = Person
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Development
|
51
|
+
|
52
|
+
**!!! This gem is currently under heavy development, so every release can contain breaking changes !!!**
|
53
|
+
|
54
|
+
If you want to help, please contact me, help is welcome.
|
55
|
+
|
56
|
+
After the first stable release, this repo will be moved to the [diaspora organization](https://github.com/diaspora/).
|
57
|
+
|
58
|
+
## Diaspora
|
59
|
+
|
60
|
+
A privacy-aware, distributed, open source social network
|
61
|
+
|
62
|
+
Links:
|
63
|
+
[Project site](https://diasporafoundation.org) |
|
64
|
+
[Wiki](https://wiki.diasporafoundation.org)
|
65
|
+
|
16
66
|
## License
|
17
67
|
|
18
68
|
This gem is published under the terms of the "GNU Affero General Public License". See the LICENSE file for the exact wording.
|
data/lib/diaspora_federation.rb
CHANGED
@@ -1,113 +1,77 @@
|
|
1
|
-
require "diaspora_federation/engine"
|
2
1
|
require "diaspora_federation/logging"
|
3
2
|
|
3
|
+
require "diaspora_federation/callbacks"
|
4
|
+
require "diaspora_federation/properties_dsl"
|
5
|
+
require "diaspora_federation/entity"
|
6
|
+
|
4
7
|
require "diaspora_federation/web_finger"
|
5
8
|
|
6
|
-
|
7
|
-
# diaspora* federation rails engine
|
9
|
+
# diaspora* federation library
|
8
10
|
module DiasporaFederation
|
9
11
|
extend Logging
|
10
12
|
|
13
|
+
@callbacks = Callbacks.new %i(
|
14
|
+
person_webfinger_fetch
|
15
|
+
person_hcard_fetch
|
16
|
+
)
|
17
|
+
|
11
18
|
class << self
|
12
|
-
|
19
|
+
# {Callbacks} instance with defined callbacks
|
20
|
+
# @see Callbacks#on
|
21
|
+
# @see Callbacks#trigger
|
22
|
+
#
|
23
|
+
attr_reader :callbacks
|
24
|
+
|
13
25
|
# the pod url
|
14
26
|
#
|
15
|
-
#
|
27
|
+
# @example with uri
|
16
28
|
# config.server_uri = URI("http://localhost:3000/")
|
17
|
-
#
|
29
|
+
# @example with configured pod_uri
|
18
30
|
# config.server_uri = AppConfig.pod_uri
|
19
31
|
attr_accessor :server_uri
|
20
32
|
|
21
|
-
|
22
|
-
# the class to use as +Person+
|
23
|
-
#
|
24
|
-
# Example:
|
25
|
-
# config.person_class = Person.to_s
|
26
|
-
#
|
27
|
-
# This class must have the following methods:
|
33
|
+
# configure the federation library
|
28
34
|
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# *find_local_by_guid*
|
33
|
-
# This should return a +Person+, which is on this pod and the account is not closed.
|
34
|
-
#
|
35
|
-
# *webfinger_hash*
|
36
|
-
# This should return a +Hash+ with the following information:
|
37
|
-
# {
|
38
|
-
# acct_uri: "acct:user@server.example",
|
39
|
-
# alias_url: "https://server.example/people/0123456789abcdef",
|
40
|
-
# hcard_url: "https://server.example/hcard/users/0123456789abcdef",
|
41
|
-
# seed_url: "https://server.example/",
|
42
|
-
# profile_url: "https://server.example/u/user",
|
43
|
-
# atom_url: "https://server.example/public/user.atom",
|
44
|
-
# salmon_url: "https://server.example/receive/users/0123456789abcdef",
|
45
|
-
# guid: "0123456789abcdef",
|
46
|
-
# pubkey: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----"
|
47
|
-
# }
|
35
|
+
# @example
|
36
|
+
# DiasporaFederation.configure do |config|
|
37
|
+
# config.server_uri = URI("http://localhost:3000/")
|
48
38
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
# url: "https://server.example/",
|
56
|
-
# photo_large_url: "https://server.example/uploads/f.jpg",
|
57
|
-
# photo_medium_url: "https://server.example/uploads/m.jpg",
|
58
|
-
# photo_small_url: "https://server.example/uploads/s.jpg",
|
59
|
-
# pubkey: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----",
|
60
|
-
# searchable: true,
|
61
|
-
# first_name: "User",
|
62
|
-
# last_name: "Name"
|
63
|
-
# }
|
64
|
-
attr_accessor :person_class
|
65
|
-
def person_class
|
66
|
-
const_get(@person_class)
|
39
|
+
# config.define_callbacks do
|
40
|
+
# # callback configuration
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
def configure
|
44
|
+
yield self
|
67
45
|
end
|
68
46
|
|
69
|
-
|
70
|
-
# configure the federation engine
|
47
|
+
# define the callbacks
|
71
48
|
#
|
72
|
-
#
|
73
|
-
#
|
49
|
+
# @example
|
50
|
+
# config.define_callbacks do
|
51
|
+
# on :some_event do |arg1|
|
52
|
+
# # do something
|
53
|
+
# end
|
74
54
|
# end
|
75
|
-
|
76
|
-
|
55
|
+
#
|
56
|
+
# @param [Proc] block the callbacks to define
|
57
|
+
def define_callbacks(&block)
|
58
|
+
@callbacks.instance_eval(&block)
|
77
59
|
end
|
78
60
|
|
79
|
-
##
|
80
61
|
# validates if the engine is configured correctly
|
81
62
|
#
|
82
63
|
# called from after_initialize
|
83
64
|
# @raise [ConfigurationError] if the configuration is incomplete or invalid
|
84
65
|
def validate_config
|
85
|
-
configuration_error "
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
hcard_profile_hash
|
91
|
-
))
|
92
|
-
logger.info "successfully configured the federation engine"
|
66
|
+
configuration_error "Missing server_uri" unless @server_uri.respond_to? :host
|
67
|
+
unless @callbacks.definition_complete?
|
68
|
+
configuration_error "Missing handlers for #{@callbacks.missing_handlers.join(', ')}"
|
69
|
+
end
|
70
|
+
logger.info "successfully configured the federation library"
|
93
71
|
end
|
94
72
|
|
95
73
|
private
|
96
74
|
|
97
|
-
def validate_class(klass, name, methods)
|
98
|
-
configuration_error "missing #{name}" unless klass
|
99
|
-
entity = const_get(klass)
|
100
|
-
|
101
|
-
return logger.warn "table for #{entity} doesn't exist, skip validation" unless entity.table_exists?
|
102
|
-
|
103
|
-
methods.each {|method| entity_respond_to?(entity, name, method) }
|
104
|
-
end
|
105
|
-
|
106
|
-
def entity_respond_to?(entity, name, method)
|
107
|
-
valid = entity.respond_to?(method) || entity.column_names.include?(method.to_s) || entity.method_defined?(method)
|
108
|
-
configuration_error "the configured class #{entity} for #{name} doesn't respond to #{method}" unless valid
|
109
|
-
end
|
110
|
-
|
111
75
|
def configuration_error(message)
|
112
76
|
logger.fatal("diaspora federation configuration error: #{message}")
|
113
77
|
raise ConfigurationError, message
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
# Callbacks are used to communicate with the application. They are called to
|
3
|
+
# fetch data and after data is received.
|
4
|
+
class Callbacks
|
5
|
+
# Initializes a new Callbacks object with the event-keys that need to be defined.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Callbacks.new %i(
|
9
|
+
# some_event
|
10
|
+
# another_event
|
11
|
+
# )
|
12
|
+
#
|
13
|
+
# @param [Hash] events event keys
|
14
|
+
def initialize(events)
|
15
|
+
@events = events
|
16
|
+
@handlers = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# defines a callback
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# callbacks.on :some_event do |arg1|
|
23
|
+
# # do something
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @param [Symbol] event the event key
|
27
|
+
# @param [Proc] callback the callback block
|
28
|
+
# @raise [ArgumentError] if the event key is undefined or has already a handler
|
29
|
+
def on(event, &callback)
|
30
|
+
raise ArgumentError, "Undefined event #{event}" unless @events.include? event
|
31
|
+
raise ArgumentError, "Already defined event #{event}" if @handlers.has_key? event
|
32
|
+
|
33
|
+
@handlers[event] = callback
|
34
|
+
end
|
35
|
+
|
36
|
+
# triggers a callback
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# callbacks.trigger :some_event, "foo"
|
40
|
+
#
|
41
|
+
# @param [Symbol] event the event key
|
42
|
+
# @return [Object] the return-value of the callback
|
43
|
+
# @raise [ArgumentError] if the event key is undefined
|
44
|
+
def trigger(event, *args)
|
45
|
+
raise ArgumentError, "Undefined event #{event}" unless @events.include? event
|
46
|
+
|
47
|
+
@handlers[event].call(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
# checks if all callbacks are defined
|
51
|
+
# @return [Boolean]
|
52
|
+
def definition_complete?
|
53
|
+
missing_handlers.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns all undefined callbacks
|
57
|
+
# @return [Hash] callback keys
|
58
|
+
def missing_handlers
|
59
|
+
@events - @handlers.keys
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module DiasporaFederation
|
2
|
+
# +Entity+ is the base class for all other objects used to encapsulate data
|
3
|
+
# for federation messages in the Diaspora* network.
|
4
|
+
# Entity fields are specified using a simple {PropertiesDSL DSL} as part of
|
5
|
+
# the class definition.
|
6
|
+
#
|
7
|
+
# Any entity also provides the means to serialize itself and all nested
|
8
|
+
# entities to XML (for deserialization from XML to +Entity+ instances, see
|
9
|
+
# {XmlPayload}).
|
10
|
+
#
|
11
|
+
# @abstract Subclass and specify properties to implement various entities.
|
12
|
+
#
|
13
|
+
# @example Entity subclass definition
|
14
|
+
# class MyEntity < Entity
|
15
|
+
# property :prop
|
16
|
+
# property :optional, default: false
|
17
|
+
# property :dynamic_default, default: -> { Time.now }
|
18
|
+
# entity :nested, NestedEntity
|
19
|
+
# entity :multiple, [OtherEntity]
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @example Entity instantiation
|
23
|
+
# nentity = NestedEntity.new
|
24
|
+
# oe1 = OtherEntity.new
|
25
|
+
# oe2 = OtherEntity.new
|
26
|
+
#
|
27
|
+
# entity = MyEntity.new(prop: 'some property',
|
28
|
+
# nested: nentity,
|
29
|
+
# multiple: [oe1, oe2])
|
30
|
+
#
|
31
|
+
# @note Entity properties can only be set during initialization, after that the
|
32
|
+
# entity instance becomes frozen and must not be modified anymore. Instances
|
33
|
+
# are intended to be immutable data containers, only.
|
34
|
+
class Entity
|
35
|
+
extend PropertiesDSL
|
36
|
+
|
37
|
+
# Initializes the Entity with the given attribute hash and freezes the created
|
38
|
+
# instance it returns.
|
39
|
+
#
|
40
|
+
# @note Attributes not defined as part of the class definition ({PropertiesDSL#property},
|
41
|
+
# {PropertiesDSL#entity}) get discarded silently.
|
42
|
+
#
|
43
|
+
# @param [Hash] data
|
44
|
+
# @return [Entity] new instance
|
45
|
+
def initialize(data)
|
46
|
+
raise ArgumentError, "expected a Hash" unless data.is_a?(Hash)
|
47
|
+
missing_props = self.class.missing_props(data)
|
48
|
+
unless missing_props.empty?
|
49
|
+
raise ArgumentError, "missing required properties: #{missing_props.join(', ')}"
|
50
|
+
end
|
51
|
+
|
52
|
+
self.class.default_values.merge(data).each do |k, v|
|
53
|
+
instance_variable_set("@#{k}", v) if setable?(k, v)
|
54
|
+
end
|
55
|
+
freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a Hash representing this Entity (attributes => values)
|
59
|
+
# @return [Hash] entity data (mostly equal to the hash used for initialization).
|
60
|
+
def to_h
|
61
|
+
self.class.class_prop_names.each_with_object({}) do |prop, hash|
|
62
|
+
hash[prop] = send(prop)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the XML representation for this entity constructed out of
|
67
|
+
# {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Element Nokogiri::XML::Element}s
|
68
|
+
#
|
69
|
+
# @see Nokogiri::XML::Node.to_xml
|
70
|
+
# @see XmlPayload.pack
|
71
|
+
#
|
72
|
+
# @return [Nokogiri::XML::Element] root element containing properties as child elements
|
73
|
+
def to_xml
|
74
|
+
entity_xml
|
75
|
+
end
|
76
|
+
|
77
|
+
# some of this is from Rails "Inflector.demodulize" and "Inflector.undersore"
|
78
|
+
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
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def setable?(name, val)
|
94
|
+
prop_def = self.class.class_props.find {|p| p[:name] == name }
|
95
|
+
return false if prop_def.nil? # property undefined
|
96
|
+
|
97
|
+
setable_string?(prop_def, val) || setable_nested?(prop_def, val) || setable_multi?(prop_def, val)
|
98
|
+
end
|
99
|
+
|
100
|
+
def setable_string?(definition, val)
|
101
|
+
(definition[:type] == String && val.respond_to?(:to_s))
|
102
|
+
end
|
103
|
+
|
104
|
+
def setable_nested?(definition, val)
|
105
|
+
t = definition[:type]
|
106
|
+
(t.is_a?(Class) && t.ancestors.include?(Entity) && val.is_a?(Entity))
|
107
|
+
end
|
108
|
+
|
109
|
+
def setable_multi?(definition, val)
|
110
|
+
t = definition[:type]
|
111
|
+
(t.instance_of?(Array) &&
|
112
|
+
val.instance_of?(Array) &&
|
113
|
+
val.all? {|v| v.instance_of?(t.first) })
|
114
|
+
end
|
115
|
+
|
116
|
+
# Serialize the Entity into XML elements
|
117
|
+
# @return [Nokogiri::XML::Element] root node
|
118
|
+
def entity_xml
|
119
|
+
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
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
root_element
|
136
|
+
end
|
137
|
+
|
138
|
+
# 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
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|