diaspora-vines 0.1.2

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 (174) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +19 -0
  4. data/README.md +7 -0
  5. data/Rakefile +23 -0
  6. data/bin/vines +4 -0
  7. data/conf/certs/README +39 -0
  8. data/conf/certs/ca-bundle.crt +3895 -0
  9. data/conf/config.rb +42 -0
  10. data/lib/vines/cli.rb +132 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/cluster.rb +246 -0
  17. data/lib/vines/command/bcrypt.rb +12 -0
  18. data/lib/vines/command/cert.rb +50 -0
  19. data/lib/vines/command/init.rb +68 -0
  20. data/lib/vines/command/ldap.rb +38 -0
  21. data/lib/vines/command/restart.rb +12 -0
  22. data/lib/vines/command/schema.rb +24 -0
  23. data/lib/vines/command/start.rb +28 -0
  24. data/lib/vines/command/stop.rb +18 -0
  25. data/lib/vines/config/host.rb +125 -0
  26. data/lib/vines/config/port.rb +132 -0
  27. data/lib/vines/config/pubsub.rb +108 -0
  28. data/lib/vines/config.rb +223 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +23 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza/iq/auth.rb +18 -0
  37. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  38. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  39. data/lib/vines/stanza/iq/error.rb +16 -0
  40. data/lib/vines/stanza/iq/ping.rb +16 -0
  41. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  42. data/lib/vines/stanza/iq/query.rb +10 -0
  43. data/lib/vines/stanza/iq/result.rb +16 -0
  44. data/lib/vines/stanza/iq/roster.rb +140 -0
  45. data/lib/vines/stanza/iq/session.rb +17 -0
  46. data/lib/vines/stanza/iq/vcard.rb +56 -0
  47. data/lib/vines/stanza/iq/version.rb +25 -0
  48. data/lib/vines/stanza/iq.rb +48 -0
  49. data/lib/vines/stanza/message.rb +40 -0
  50. data/lib/vines/stanza/presence/error.rb +23 -0
  51. data/lib/vines/stanza/presence/probe.rb +37 -0
  52. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  53. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  54. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  55. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  56. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  57. data/lib/vines/stanza/presence.rb +141 -0
  58. data/lib/vines/stanza/pubsub/create.rb +39 -0
  59. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  60. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  61. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  62. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  63. data/lib/vines/stanza/pubsub.rb +22 -0
  64. data/lib/vines/stanza.rb +175 -0
  65. data/lib/vines/storage/ldap.rb +71 -0
  66. data/lib/vines/storage/local.rb +139 -0
  67. data/lib/vines/storage/null.rb +39 -0
  68. data/lib/vines/storage/sql.rb +138 -0
  69. data/lib/vines/storage.rb +239 -0
  70. data/lib/vines/store.rb +110 -0
  71. data/lib/vines/stream/client/auth.rb +74 -0
  72. data/lib/vines/stream/client/auth_restart.rb +29 -0
  73. data/lib/vines/stream/client/bind.rb +72 -0
  74. data/lib/vines/stream/client/bind_restart.rb +24 -0
  75. data/lib/vines/stream/client/closed.rb +13 -0
  76. data/lib/vines/stream/client/ready.rb +17 -0
  77. data/lib/vines/stream/client/session.rb +210 -0
  78. data/lib/vines/stream/client/start.rb +27 -0
  79. data/lib/vines/stream/client/tls.rb +38 -0
  80. data/lib/vines/stream/client.rb +84 -0
  81. data/lib/vines/stream/component/handshake.rb +26 -0
  82. data/lib/vines/stream/component/ready.rb +23 -0
  83. data/lib/vines/stream/component/start.rb +19 -0
  84. data/lib/vines/stream/component.rb +58 -0
  85. data/lib/vines/stream/http/auth.rb +22 -0
  86. data/lib/vines/stream/http/bind.rb +32 -0
  87. data/lib/vines/stream/http/bind_restart.rb +37 -0
  88. data/lib/vines/stream/http/ready.rb +29 -0
  89. data/lib/vines/stream/http/request.rb +172 -0
  90. data/lib/vines/stream/http/session.rb +120 -0
  91. data/lib/vines/stream/http/sessions.rb +65 -0
  92. data/lib/vines/stream/http/start.rb +23 -0
  93. data/lib/vines/stream/http.rb +157 -0
  94. data/lib/vines/stream/parser.rb +79 -0
  95. data/lib/vines/stream/sasl.rb +128 -0
  96. data/lib/vines/stream/server/auth.rb +13 -0
  97. data/lib/vines/stream/server/auth_restart.rb +13 -0
  98. data/lib/vines/stream/server/final_restart.rb +21 -0
  99. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  100. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  101. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  102. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  103. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  104. data/lib/vines/stream/server/outbound/start.rb +20 -0
  105. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  106. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  107. data/lib/vines/stream/server/ready.rb +24 -0
  108. data/lib/vines/stream/server/start.rb +13 -0
  109. data/lib/vines/stream/server/tls.rb +13 -0
  110. data/lib/vines/stream/server.rb +150 -0
  111. data/lib/vines/stream/state.rb +60 -0
  112. data/lib/vines/stream.rb +247 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +6 -0
  116. data/lib/vines/xmpp_server.rb +25 -0
  117. data/lib/vines.rb +203 -0
  118. data/test/cluster/publisher_test.rb +57 -0
  119. data/test/cluster/sessions_test.rb +47 -0
  120. data/test/cluster/subscriber_test.rb +109 -0
  121. data/test/config/host_test.rb +369 -0
  122. data/test/config/pubsub_test.rb +187 -0
  123. data/test/config_test.rb +732 -0
  124. data/test/contact_test.rb +102 -0
  125. data/test/error_test.rb +58 -0
  126. data/test/ext/nokogiri.rb +14 -0
  127. data/test/jid_test.rb +147 -0
  128. data/test/kit_test.rb +31 -0
  129. data/test/router_test.rb +243 -0
  130. data/test/stanza/iq/disco_info_test.rb +78 -0
  131. data/test/stanza/iq/disco_items_test.rb +49 -0
  132. data/test/stanza/iq/private_storage_test.rb +184 -0
  133. data/test/stanza/iq/roster_test.rb +229 -0
  134. data/test/stanza/iq/session_test.rb +25 -0
  135. data/test/stanza/iq/vcard_test.rb +146 -0
  136. data/test/stanza/iq/version_test.rb +64 -0
  137. data/test/stanza/iq_test.rb +70 -0
  138. data/test/stanza/message_test.rb +126 -0
  139. data/test/stanza/presence/probe_test.rb +50 -0
  140. data/test/stanza/presence/subscribe_test.rb +83 -0
  141. data/test/stanza/pubsub/create_test.rb +116 -0
  142. data/test/stanza/pubsub/delete_test.rb +169 -0
  143. data/test/stanza/pubsub/publish_test.rb +309 -0
  144. data/test/stanza/pubsub/subscribe_test.rb +205 -0
  145. data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
  146. data/test/stanza_test.rb +85 -0
  147. data/test/storage/ldap_test.rb +201 -0
  148. data/test/storage/local_test.rb +59 -0
  149. data/test/storage/mock_redis.rb +97 -0
  150. data/test/storage/null_test.rb +29 -0
  151. data/test/storage/storage_tests.rb +182 -0
  152. data/test/storage_test.rb +85 -0
  153. data/test/store_test.rb +130 -0
  154. data/test/stream/client/auth_test.rb +137 -0
  155. data/test/stream/client/ready_test.rb +47 -0
  156. data/test/stream/client/session_test.rb +27 -0
  157. data/test/stream/component/handshake_test.rb +52 -0
  158. data/test/stream/component/ready_test.rb +103 -0
  159. data/test/stream/component/start_test.rb +39 -0
  160. data/test/stream/http/auth_test.rb +70 -0
  161. data/test/stream/http/ready_test.rb +86 -0
  162. data/test/stream/http/request_test.rb +209 -0
  163. data/test/stream/http/sessions_test.rb +49 -0
  164. data/test/stream/http/start_test.rb +50 -0
  165. data/test/stream/parser_test.rb +122 -0
  166. data/test/stream/sasl_test.rb +195 -0
  167. data/test/stream/server/auth_test.rb +61 -0
  168. data/test/stream/server/outbound/auth_test.rb +75 -0
  169. data/test/stream/server/ready_test.rb +98 -0
  170. data/test/test_helper.rb +42 -0
  171. data/test/token_bucket_test.rb +44 -0
  172. data/test/user_test.rb +96 -0
  173. data/vines.gemspec +30 -0
  174. metadata +387 -0
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Result < Iq
7
+ register "/iq[@id and @type='result']"
8
+
9
+ def process
10
+ return if route_iq
11
+ # do nothing
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,140 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Roster < Query
7
+ NS = NAMESPACES[:roster]
8
+
9
+ register "/iq[@id and (@type='get' or @type='set')]/ns:query", 'ns' => NS
10
+
11
+ def process
12
+ validate_to_address
13
+ get? ? roster_query : update_roster
14
+ end
15
+
16
+ private
17
+
18
+ # Send an iq result stanza containing roster items to the user in
19
+ # response to their roster get request. Requesting the roster makes
20
+ # this stream an "interested resource" that can now receive roster
21
+ # updates.
22
+ def roster_query
23
+ stream.requested_roster!
24
+ stream.write(stream.user.to_roster_xml(self['id']))
25
+ end
26
+
27
+ # Roster sets must have no 'to' address or be addressed to the same
28
+ # JID that sent the stanza. RFC 6121 sections 2.1.5 and 2.3.3.
29
+ def validate_to_address
30
+ to = validate_to
31
+ unless to.nil? || to.bare == stream.user.jid.bare
32
+ raise StanzaErrors::Forbidden.new(self, 'auth')
33
+ end
34
+ end
35
+
36
+ # Add, update, or delete the roster item contained in the iq set
37
+ # stanza received from the client. RFC 6121 sections 2.3, 2.4, 2.5.
38
+ def update_roster
39
+ items = self.xpath('ns:query/ns:item', 'ns' => NS)
40
+ raise StanzaErrors::BadRequest.new(self, 'modify') if items.size != 1
41
+ item = items.first
42
+
43
+ jid = JID.new(item['jid']) rescue (raise StanzaErrors::JidMalformed.new(self, 'modify'))
44
+ raise StanzaErrors::BadRequest.new(self, 'modify') if jid.empty? || !jid.bare?
45
+
46
+ if item['subscription'] == 'remove'
47
+ remove_contact(jid)
48
+ return
49
+ end
50
+
51
+ raise StanzaErrors::NotAllowed.new(self, 'modify') if jid == stream.user.jid.bare
52
+ groups = item.xpath('ns:group', 'ns' => NS).map {|g| g.text.strip }
53
+ raise StanzaErrors::BadRequest.new(self, 'modify') if groups.uniq!
54
+ raise StanzaErrors::NotAcceptable.new(self, 'modify') if groups.include?('')
55
+
56
+ contact = stream.user.contact(jid)
57
+ unless contact
58
+ contact = Contact.new(jid: jid)
59
+ stream.user.roster << contact
60
+ end
61
+ contact.name = item['name']
62
+ contact.groups = groups
63
+ storage.save_user(stream.user)
64
+ stream.update_user_streams(stream.user)
65
+ send_result_iq
66
+ push_roster_updates(stream.user.jid, contact)
67
+ end
68
+
69
+ # Remove the contact with this JID from the user's roster and send
70
+ # roster pushes to the user's interested resources. This is triggered
71
+ # by receiving an iq set with an item element like
72
+ # <item jid="alice@wonderland.lit" subscription="remove"/>. RFC 6121
73
+ # section 2.5.
74
+ def remove_contact(jid)
75
+ contact = stream.user.contact(jid)
76
+ raise StanzaErrors::ItemNotFound.new(self, 'modify') unless contact
77
+ if local_jid?(contact.jid)
78
+ user = storage(contact.jid.domain).find_user(contact.jid)
79
+ end
80
+
81
+ if user && user.contact(stream.user.jid)
82
+ user.contact(stream.user.jid).subscription = 'none'
83
+ user.contact(stream.user.jid).ask = nil
84
+ end
85
+ stream.user.remove_contact(contact.jid)
86
+ [user, stream.user].compact.each do |save|
87
+ storage(save.jid.domain).save_user(save)
88
+ stream.update_user_streams(save)
89
+ end
90
+
91
+ send_result_iq
92
+ push_roster_updates(stream.user.jid,
93
+ Contact.new(jid: contact.jid, subscription: 'remove'))
94
+
95
+ if local_jid?(contact.jid)
96
+ send_unavailable(stream.user.jid, contact.jid) if contact.subscribed_from?
97
+ send_unsubscribe(contact)
98
+ if user && user.contact(stream.user.jid)
99
+ push_roster_updates(contact.jid, user.contact(stream.user.jid))
100
+ end
101
+ else
102
+ send_unsubscribe(contact)
103
+ end
104
+ end
105
+
106
+ # Notify the contact that it's been removed from the user's roster
107
+ # and no longer has any presence relationship with the user.
108
+ def send_unsubscribe(contact)
109
+ presence = [%w[to unsubscribe], %w[from unsubscribed]].map do |meth, type|
110
+ presence(contact.jid, type) if contact.send("subscribed_#{meth}?")
111
+ end.compact
112
+ broadcast_to_interested_resources(presence, contact.jid)
113
+ end
114
+
115
+ def presence(to, type)
116
+ doc = Document.new
117
+ doc.create_element('presence',
118
+ 'from' => stream.user.jid.bare.to_s,
119
+ 'id' => Kit.uuid,
120
+ 'to' => to.to_s,
121
+ 'type' => type)
122
+ end
123
+
124
+ # Send an iq set stanza to the user's interested resources, letting them
125
+ # know their roster has been updated.
126
+ def push_roster_updates(to, contact)
127
+ stream.interested_resources(to).each do |recipient|
128
+ contact.send_roster_push(recipient)
129
+ end
130
+ end
131
+
132
+ def send_result_iq
133
+ node = to_result
134
+ node.remove_attribute('from')
135
+ stream.write(node)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ # Session support is deprecated, but Adium requires it, so reply with an
7
+ # iq result stanza.
8
+ class Session < Iq
9
+ register "/iq[@id and @type='set']/ns:session", 'ns' => NAMESPACES[:session]
10
+
11
+ def process
12
+ stream.write(to_result)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Vcard < Iq
7
+ NS = NAMESPACES[:vcard]
8
+
9
+ register "/iq[@id and @type='get' or @type='set']/ns:vCard", 'ns' => NS
10
+
11
+ def process
12
+ return unless allowed?
13
+ if local?
14
+ get? ? vcard_query : vcard_update
15
+ else
16
+ self['from'] = stream.user.jid.to_s
17
+ route
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def vcard_query
24
+ to = validate_to
25
+ jid = to ? to.bare : stream.user.jid.bare
26
+ card = storage(jid.domain).find_vcard(jid)
27
+
28
+ raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless card
29
+
30
+ doc = Document.new
31
+ result = doc.create_element('iq') do |node|
32
+ node['from'] = jid.to_s unless jid == stream.user.jid.bare
33
+ node['id'] = self['id']
34
+ node['to'] = stream.user.jid.to_s
35
+ node['type'] = 'result'
36
+ node << card
37
+ end
38
+ stream.write(result)
39
+ end
40
+
41
+ def vcard_update
42
+ to = validate_to
43
+ unless to.nil? || to == stream.user.jid.bare
44
+ raise StanzaErrors::Forbidden.new(self, 'auth')
45
+ end
46
+
47
+ storage.save_vcard(stream.user.jid, elements.first)
48
+
49
+ result = to_result
50
+ result.remove_attribute('from')
51
+ stream.write(result)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq
6
+ class Version < Query
7
+ NS = NAMESPACES[:version]
8
+
9
+ register "/iq[@id and @type='get']/ns:query", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq || to_pubsub_domain? || !allowed?
13
+ result = to_result.tap do |node|
14
+ node << node.document.create_element('query') do |query|
15
+ query.default_namespace = NS
16
+ query << node.document.create_element('name', 'Vines')
17
+ query << node.document.create_element('version', VERSION)
18
+ end
19
+ end
20
+ stream.write(result)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Iq < Stanza
6
+ register "/iq"
7
+
8
+ VALID_TYPES = %w[get set result error].freeze
9
+
10
+ VALID_TYPES.each do |type|
11
+ define_method "#{type}?" do
12
+ self['type'] == type
13
+ end
14
+ end
15
+
16
+ def process
17
+ if self['id'] && VALID_TYPES.include?(self['type'])
18
+ route_iq or raise StanzaErrors::FeatureNotImplemented.new(@node, 'cancel')
19
+ else
20
+ raise StanzaErrors::BadRequest.new(@node, 'modify')
21
+ end
22
+ end
23
+
24
+ def to_result
25
+ doc = Document.new
26
+ doc.create_element('iq',
27
+ 'from' => validate_to || stream.domain,
28
+ 'id' => self['id'],
29
+ 'to' => stream.user.jid,
30
+ 'type' => 'result')
31
+ end
32
+
33
+ private
34
+
35
+ # Return false if this IQ stanza is addressed to the server, or a pubsub
36
+ # service hosted here, and must be handled locally. Return true if the
37
+ # stanza must not be handled locally and has been routed to the appropriate
38
+ # component, s2s, or c2s stream.
39
+ def route_iq
40
+ to = validate_to
41
+ return false if to.nil? || stream.config.vhost?(to) || to_pubsub_domain?
42
+ self['from'] = stream.user.jid.to_s
43
+ local? ? broadcast(stream.connected_resources(to)) : route
44
+ true
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Message < Stanza
6
+ register "/message"
7
+
8
+ TYPE, FROM = %w[type from].map {|s| s.freeze }
9
+ VALID_TYPES = %w[chat error groupchat headline normal].freeze
10
+
11
+ VALID_TYPES.each do |type|
12
+ define_method "#{type}?" do
13
+ self[TYPE] == type
14
+ end
15
+ end
16
+
17
+ def process
18
+ unless self[TYPE].nil? || VALID_TYPES.include?(self[TYPE])
19
+ raise StanzaErrors::BadRequest.new(self, 'modify')
20
+ end
21
+
22
+ if local?
23
+ to = validate_to || stream.user.jid.bare
24
+ recipients = stream.connected_resources(to)
25
+ if recipients.empty?
26
+ if user = storage(to.domain).find_user(to)
27
+ # TODO Implement offline messaging storage
28
+ raise StanzaErrors::ServiceUnavailable.new(self, 'cancel')
29
+ end
30
+ else
31
+ broadcast(recipients)
32
+ end
33
+ else
34
+ self[FROM] = stream.user.jid.to_s
35
+ route
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Presence
6
+ class Error < Presence
7
+ register "/presence[@type='error']"
8
+
9
+ def process
10
+ inbound? ? process_inbound : process_outbound
11
+ end
12
+
13
+ def process_outbound
14
+ # FIXME Implement error handling
15
+ end
16
+
17
+ def process_inbound
18
+ # FIXME Implement error handling
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Presence
6
+ class Probe < Presence
7
+ register "/presence[@type='probe']"
8
+
9
+ def process
10
+ inbound? ? process_inbound : process_outbound
11
+ end
12
+
13
+ def process_outbound
14
+ self['from'] = stream.user.jid.to_s
15
+ local? ? process_inbound : route
16
+ end
17
+
18
+ def process_inbound
19
+ to = validate_to
20
+ raise StanzaErrors::BadRequest.new(self, 'modify') unless to
21
+
22
+ user = storage(to.domain).find_user(to)
23
+ unless user && user.subscribed_from?(stream.user.jid)
24
+ auto_reply_to_subscription_request(to.bare, 'unsubscribed')
25
+ else
26
+ stream.available_resources(to).each do |recipient|
27
+ el = recipient.last_broadcast_presence.clone
28
+ el['from'] = recipient.user.jid.to_s
29
+ el['to'] = stream.user.jid.to_s
30
+ stream.write(el)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Presence
6
+ class Subscribe < Presence
7
+ register "/presence[@type='subscribe']"
8
+
9
+ def process
10
+ stamp_from
11
+ inbound? ? process_inbound : process_outbound
12
+ end
13
+
14
+ def process_outbound
15
+ to = stamp_to
16
+ stream.user.request_subscription(to)
17
+ storage.save_user(stream.user)
18
+ stream.update_user_streams(stream.user)
19
+ local? ? process_inbound : route
20
+ send_roster_push(to)
21
+ end
22
+
23
+ def process_inbound
24
+ to = stamp_to
25
+ contact = storage(to.domain).find_user(to)
26
+ if contact.nil?
27
+ auto_reply_to_subscription_request(to, 'unsubscribed')
28
+ elsif contact.subscribed_from?(stream.user.jid)
29
+ auto_reply_to_subscription_request(to, 'subscribed')
30
+ else
31
+ recipients = stream.available_resources(to)
32
+ if recipients.empty?
33
+ # TODO store subscription request per RFC 6121 3.1.3 #4
34
+ else
35
+ broadcast_to_available_resources([@node], to)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Presence
6
+ class Subscribed < Presence
7
+ register "/presence[@type='subscribed']"
8
+
9
+ def process
10
+ stamp_from
11
+ inbound? ? process_inbound : process_outbound
12
+ end
13
+
14
+ def process_outbound
15
+ to = stamp_to
16
+ stream.user.add_subscription_from(to)
17
+ storage.save_user(stream.user)
18
+ stream.update_user_streams(stream.user)
19
+ local? ? process_inbound : route
20
+ send_roster_push(to)
21
+ send_known_presence(to)
22
+ end
23
+
24
+ def process_inbound
25
+ to = stamp_to
26
+ user = storage(to.domain).find_user(to)
27
+ contact = user.contact(stream.user.jid) if user
28
+ return unless contact && contact.can_subscribe?
29
+ contact.subscribe_to
30
+ storage(to.domain).save_user(user)
31
+ stream.update_user_streams(user)
32
+ broadcast_subscription_change(contact)
33
+ end
34
+
35
+ private
36
+
37
+ # After approving a contact's subscription to this user's presence,
38
+ # broadcast this user's most recent presence stanzas to the contact.
39
+ def send_known_presence(to)
40
+ stanzas = stream.available_resources(stream.user.jid).map do |stream|
41
+ stream.last_broadcast_presence.clone.tap do |node|
42
+ node['from'] = stream.user.jid.to_s
43
+ node['id'] = Kit.uuid
44
+ end
45
+ end
46
+ broadcast_to_available_resources(stanzas, to)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Presence
6
+ class Unavailable < Presence
7
+ register "/presence[@type='unavailable']"
8
+
9
+ def process
10
+ inbound? ? inbound_broadcast_presence : outbound_broadcast_presence
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Presence
6
+ class Unsubscribe < Presence
7
+ register "/presence[@type='unsubscribe']"
8
+
9
+ def process
10
+ stamp_from
11
+ inbound? ? process_inbound : process_outbound
12
+ end
13
+
14
+ def process_outbound
15
+ to = stamp_to
16
+ return unless stream.user.subscribed_to?(to)
17
+ stream.user.remove_subscription_to(to)
18
+ storage.save_user(stream.user)
19
+ stream.update_user_streams(stream.user)
20
+ local? ? process_inbound : route
21
+ send_roster_push(to)
22
+ end
23
+
24
+ def process_inbound
25
+ to = stamp_to
26
+ user = storage(to.domain).find_user(to)
27
+ return unless user && user.subscribed_from?(stream.user.jid)
28
+ contact = user.contact(stream.user.jid)
29
+ contact.unsubscribe_from
30
+ storage(to.domain).save_user(user)
31
+ stream.update_user_streams(user)
32
+ broadcast_subscription_change(contact)
33
+ send_unavailable(to, stream.user.jid.bare)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class Presence
6
+ class Unsubscribed < Presence
7
+ register "/presence[@type='unsubscribed']"
8
+
9
+ def process
10
+ stamp_from
11
+ inbound? ? process_inbound : process_outbound
12
+ end
13
+
14
+ def process_outbound
15
+ to = stamp_to
16
+ return unless stream.user.subscribed_from?(to)
17
+ send_unavailable(stream.user.jid, to)
18
+ stream.user.remove_subscription_from(to)
19
+ storage.save_user(stream.user)
20
+ stream.update_user_streams(stream.user)
21
+ local? ? process_inbound : route
22
+ send_roster_push(to)
23
+ end
24
+
25
+ def process_inbound
26
+ to = stamp_to
27
+ user = storage(to.domain).find_user(to)
28
+ return unless user && user.subscribed_to?(stream.user.jid)
29
+ contact = user.contact(stream.user.jid)
30
+ contact.unsubscribe_to
31
+ storage(to.domain).save_user(user)
32
+ stream.update_user_streams(user)
33
+ broadcast_subscription_change(contact)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end