vines 0.1.0
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.
- data/LICENSE +19 -0
- data/README +34 -0
- data/Rakefile +55 -0
- data/bin/vines +95 -0
- data/conf/certs/README +32 -0
- data/conf/certs/ca-bundle.crt +3987 -0
- data/conf/config.rb +114 -0
- data/lib/vines.rb +155 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +49 -0
- data/lib/vines/command/init.rb +58 -0
- data/lib/vines/command/ldap.rb +35 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/config.rb +191 -0
- data/lib/vines/contact.rb +99 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +56 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/router.rb +125 -0
- data/lib/vines/stanza.rb +55 -0
- data/lib/vines/stanza/iq.rb +50 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +25 -0
- data/lib/vines/stanza/iq/disco_items.rb +23 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +153 -0
- data/lib/vines/stanza/iq/session.rb +22 -0
- data/lib/vines/stanza/iq/vcard.rb +58 -0
- data/lib/vines/stanza/message.rb +41 -0
- data/lib/vines/stanza/presence.rb +119 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +38 -0
- data/lib/vines/stanza/presence/subscribe.rb +66 -0
- data/lib/vines/stanza/presence/subscribed.rb +64 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +57 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +50 -0
- data/lib/vines/storage.rb +216 -0
- data/lib/vines/storage/couchdb.rb +119 -0
- data/lib/vines/storage/ldap.rb +59 -0
- data/lib/vines/storage/local.rb +66 -0
- data/lib/vines/storage/redis.rb +108 -0
- data/lib/vines/storage/sql.rb +174 -0
- data/lib/vines/store.rb +51 -0
- data/lib/vines/stream.rb +198 -0
- data/lib/vines/stream/client.rb +131 -0
- data/lib/vines/stream/client/auth.rb +94 -0
- data/lib/vines/stream/client/auth_restart.rb +33 -0
- data/lib/vines/stream/client/bind.rb +58 -0
- data/lib/vines/stream/client/bind_restart.rb +25 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +15 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +37 -0
- data/lib/vines/stream/component.rb +53 -0
- data/lib/vines/stream/component/handshake.rb +25 -0
- data/lib/vines/stream/component/ready.rb +24 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/http.rb +111 -0
- data/lib/vines/stream/http/http_request.rb +22 -0
- data/lib/vines/stream/http/http_state.rb +139 -0
- data/lib/vines/stream/http/http_states.rb +53 -0
- data/lib/vines/stream/parser.rb +78 -0
- data/lib/vines/stream/server.rb +126 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +19 -0
- data/lib/vines/stream/server/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +28 -0
- data/lib/vines/stream/server/outbound/final_features.rb +27 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +31 -0
- data/lib/vines/stream/server/ready.rb +20 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/state.rb +55 -0
- data/lib/vines/token_bucket.rb +46 -0
- data/lib/vines/user.rb +124 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/test/config_test.rb +396 -0
- data/test/error_test.rb +59 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/jid_test.rb +71 -0
- data/test/kit_test.rb +21 -0
- data/test/router_test.rb +60 -0
- data/test/stanza/iq/roster_test.rb +198 -0
- data/test/stanza/iq/session_test.rb +30 -0
- data/test/stanza/iq/vcard_test.rb +159 -0
- data/test/stanza/message_test.rb +124 -0
- data/test/stanza/presence/subscribe_test.rb +75 -0
- data/test/storage/couchdb_test.rb +102 -0
- data/test/storage/ldap_test.rb +207 -0
- data/test/storage/local_test.rb +54 -0
- data/test/storage/redis_test.rb +75 -0
- data/test/storage/sql_test.rb +55 -0
- data/test/storage/storage_tests.rb +134 -0
- data/test/storage_test.rb +90 -0
- data/test/stream/client/auth_test.rb +127 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/component/handshake_test.rb +46 -0
- data/test/stream/component/ready_test.rb +105 -0
- data/test/stream/component/start_test.rb +41 -0
- data/test/stream/parser_test.rb +121 -0
- data/test/stream/server/outbound/auth_test.rb +77 -0
- data/test/stream/server/ready_test.rb +100 -0
- data/test/token_bucket_test.rb +24 -0
- data/test/user_test.rb +64 -0
- metadata +318 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class FinalRestart < State
|
|
7
|
+
def initialize(stream, success=Ready)
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def node(node)
|
|
12
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
|
13
|
+
stream.start(node)
|
|
14
|
+
stream.write('<stream:features/>')
|
|
15
|
+
advance
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class Auth < State
|
|
8
|
+
NS = NAMESPACES[:sasl]
|
|
9
|
+
|
|
10
|
+
def initialize(stream, success=AuthResult)
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def node(node)
|
|
15
|
+
raise StreamErrors::NotAuthorized unless external?(node)
|
|
16
|
+
authz = Base64.encode64(stream.domain).chomp
|
|
17
|
+
stream.write(%Q{<auth xmlns="#{NS}" mechanism="EXTERNAL">#{authz}</auth>})
|
|
18
|
+
advance
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def external?(node)
|
|
24
|
+
external = node.xpath("ns:mechanisms/ns:mechanism[text()='EXTERNAL']", 'ns' => NS).any?
|
|
25
|
+
node.name == 'features' && namespace(node) == NAMESPACES[:stream] && external
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class AuthRestart < State
|
|
8
|
+
def initialize(stream, success=Auth)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def node(node)
|
|
13
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
|
14
|
+
advance
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class AuthResult < State
|
|
8
|
+
def initialize(stream, success=FinalRestart)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def node(node)
|
|
13
|
+
raise StreamErrors::NotAuthorized unless namespace(node) == NAMESPACES[:sasl]
|
|
14
|
+
case node.name
|
|
15
|
+
when 'success'
|
|
16
|
+
stream.start(node)
|
|
17
|
+
advance
|
|
18
|
+
when 'failure'
|
|
19
|
+
stream.close_connection
|
|
20
|
+
else
|
|
21
|
+
raise StreamErrors::NotAuthorized
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class FinalFeatures < State
|
|
8
|
+
def initialize(stream, success=Server::Ready)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def node(node)
|
|
13
|
+
raise StreamErrors::NotAuthorized unless empty_features?(node)
|
|
14
|
+
advance
|
|
15
|
+
stream.notify_connected
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def empty_features?(node)
|
|
21
|
+
node.name == 'features' && namespace(node) == NAMESPACES[:stream] && node.elements.empty?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class FinalRestart < State
|
|
8
|
+
def initialize(stream, success=FinalFeatures)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def node(node)
|
|
13
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
|
14
|
+
advance
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class Start < State
|
|
8
|
+
def initialize(stream, success=TLS)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def node(node)
|
|
13
|
+
raise StreamErrors::NotAuthorized unless stream?(node)
|
|
14
|
+
advance
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class TLS < State
|
|
8
|
+
NS = NAMESPACES[:tls]
|
|
9
|
+
|
|
10
|
+
def initialize(stream, success=TLSResult)
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def node(node)
|
|
15
|
+
raise StreamErrors::NotAuthorized unless tls?(node)
|
|
16
|
+
stream.write("<starttls xmlns='#{NS}'/>")
|
|
17
|
+
advance
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def tls?(node)
|
|
23
|
+
tls = node.xpath('ns:starttls', 'ns' => NS).any?
|
|
24
|
+
node.name == 'features' && namespace(node) == NAMESPACES[:stream] && tls
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Outbound
|
|
7
|
+
class TLSResult < State
|
|
8
|
+
NS = NAMESPACES[:tls]
|
|
9
|
+
|
|
10
|
+
def initialize(stream, success=AuthRestart)
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def node(node)
|
|
15
|
+
raise StreamErrors::NotAuthorized unless namespace(node) == NS
|
|
16
|
+
case node.name
|
|
17
|
+
when 'proceed'
|
|
18
|
+
stream.encrypt
|
|
19
|
+
stream.start(node)
|
|
20
|
+
advance
|
|
21
|
+
when 'failure'
|
|
22
|
+
stream.close_connection
|
|
23
|
+
else
|
|
24
|
+
raise StreamErrors::NotAuthorized
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
class Server
|
|
6
|
+
class Ready < State
|
|
7
|
+
def node(node)
|
|
8
|
+
stanza = to_stanza(node)
|
|
9
|
+
raise StreamErrors::UnsupportedStanzaType unless stanza
|
|
10
|
+
to, from = %w[to from].map {|attr| JID.new(stanza[attr] || '') }
|
|
11
|
+
raise StreamErrors::ImproperAddressing if [to, from].any? {|addr| (addr.domain || '').strip.empty? }
|
|
12
|
+
raise StreamErrors::InvalidFrom unless from.domain == stream.remote_domain
|
|
13
|
+
raise StreamErrors::HostUnknown unless to.domain == stream.domain
|
|
14
|
+
stream.user = User.new(:jid => from)
|
|
15
|
+
stanza.process
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stream
|
|
5
|
+
|
|
6
|
+
# The base class of Stream state machines. States know how to process XML
|
|
7
|
+
# nodes and advance to their next valid state or fail the stream.
|
|
8
|
+
class State
|
|
9
|
+
include Nokogiri::XML
|
|
10
|
+
include Vines::Log
|
|
11
|
+
|
|
12
|
+
attr_reader :stream
|
|
13
|
+
|
|
14
|
+
STREAM = 'stream'.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(stream, success=nil)
|
|
17
|
+
@stream, @success = stream, success
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def node(node)
|
|
21
|
+
raise 'subclass must implement'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ==(state)
|
|
25
|
+
self.class == state.class
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def eql?(state)
|
|
29
|
+
state.is_a?(State) && self == state
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def hash
|
|
33
|
+
self.class.hash
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def advance
|
|
39
|
+
stream.advance(@success.new(stream))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stream?(node)
|
|
43
|
+
node.name == STREAM && namespace(node) == NAMESPACES[:stream]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def namespace(node)
|
|
47
|
+
node.namespace ? node.namespace.href : nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_stanza(node)
|
|
51
|
+
Stanza.from_node(node, stream)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
|
|
5
|
+
# The token bucket algorithm is useful for rate limiting.
|
|
6
|
+
# Before an operation can be completed, a token is taken from
|
|
7
|
+
# the bucket. If no tokens are available, the operation fails.
|
|
8
|
+
# The bucket is refilled with tokens at the maximum allowed rate
|
|
9
|
+
# of operations.
|
|
10
|
+
class TokenBucket
|
|
11
|
+
|
|
12
|
+
# Create a full bucket with capacity number of tokens to be filled
|
|
13
|
+
# at the given rate of tokens/second.
|
|
14
|
+
def initialize(capacity, rate)
|
|
15
|
+
raise ArgumentError.new('capacity must be > 0') unless capacity > 0
|
|
16
|
+
raise ArgumentError.new('rate must be > 0') unless rate > 0
|
|
17
|
+
@capacity = capacity
|
|
18
|
+
@tokens = capacity
|
|
19
|
+
@rate = rate
|
|
20
|
+
@timestamp = Time.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns true if tokens can be taken from the bucket.
|
|
24
|
+
def take(tokens)
|
|
25
|
+
raise ArgumentError.new('tokens must be > 0') unless tokens > 0
|
|
26
|
+
if tokens <= fill
|
|
27
|
+
@tokens -= tokens
|
|
28
|
+
true
|
|
29
|
+
else
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def fill
|
|
37
|
+
if @tokens < @capacity
|
|
38
|
+
now = Time.new
|
|
39
|
+
delta = (@rate * (now - @timestamp)).round
|
|
40
|
+
@tokens = [@capacity, @tokens + delta].min
|
|
41
|
+
@timestamp = now
|
|
42
|
+
end
|
|
43
|
+
@tokens
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/vines/user.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class User
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
attr_accessor :name, :password, :roster
|
|
8
|
+
attr_reader :jid
|
|
9
|
+
|
|
10
|
+
def initialize(args={})
|
|
11
|
+
@jid = JID.new(args[:jid])
|
|
12
|
+
raise ArgumentError, 'invalid jid' unless @jid.node && !@jid.domain.empty?
|
|
13
|
+
@name = args[:name]
|
|
14
|
+
@password = args[:password]
|
|
15
|
+
@roster = args[:roster] || []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def <=>(user)
|
|
19
|
+
self.jid.to_s <=> user.jid.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def eql?(user)
|
|
23
|
+
user.is_a?(User) && self == user
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def hash
|
|
27
|
+
jid.to_s.hash
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Update this user's information from the given user object.
|
|
31
|
+
def update_from(user)
|
|
32
|
+
@name = user.name
|
|
33
|
+
@password = user.password
|
|
34
|
+
@roster = user.roster.map {|c| c.clone }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Return true if the jid is on this user's roster.
|
|
38
|
+
def contact?(jid)
|
|
39
|
+
!contact(jid).nil?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns the contact with this jid or nil if not found.
|
|
43
|
+
def contact(jid)
|
|
44
|
+
bare = JID.new(jid).bare
|
|
45
|
+
@roster.find {|c| c.jid.bare == bare }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns true if the user is subscribed to this contact's
|
|
49
|
+
# presence updates.
|
|
50
|
+
def subscribed_to?(jid)
|
|
51
|
+
contact = contact(jid)
|
|
52
|
+
contact && contact.subscribed_to?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns true if the user has a presence subscription from this contact.
|
|
56
|
+
# The contact is subscribed to this user's presence.
|
|
57
|
+
def subscribed_from?(jid)
|
|
58
|
+
contact = contact(jid)
|
|
59
|
+
contact && contact.subscribed_from?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Removes the contact with this jid from the user's roster.
|
|
63
|
+
def remove_contact(jid)
|
|
64
|
+
bare = JID.new(jid).bare
|
|
65
|
+
@roster.reject! {|c| c.jid.bare == bare }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns a list of the contacts to which this user has
|
|
69
|
+
# successfully subscribed.
|
|
70
|
+
def subscribed_to_contacts
|
|
71
|
+
@roster.select {|c| c.subscribed_to? }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns a list of the contacts that are subscribed to this user's
|
|
75
|
+
# presence updates.
|
|
76
|
+
def subscribed_from_contacts
|
|
77
|
+
@roster.select {|c| c.subscribed_from? }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Update the contact's jid on this user's roster to signal that this user
|
|
81
|
+
# has requested the contact's permission to receive their presence updates.
|
|
82
|
+
def request_subscription(jid)
|
|
83
|
+
unless contact = contact(jid)
|
|
84
|
+
contact = Contact.new(:jid => jid)
|
|
85
|
+
@roster << contact
|
|
86
|
+
end
|
|
87
|
+
contact.ask = 'subscribe' if %w[none from].include?(contact.subscription)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Add the user's jid to this contact's roster with a subscription state of
|
|
91
|
+
# 'from.' This signals that this contact has approved a user's subscription.
|
|
92
|
+
def add_subscription_from(jid)
|
|
93
|
+
unless contact = contact(jid)
|
|
94
|
+
contact = Contact.new(:jid => jid)
|
|
95
|
+
@roster << contact
|
|
96
|
+
end
|
|
97
|
+
contact.subscribe_from
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def remove_subscription_to(jid)
|
|
101
|
+
if contact = contact(jid)
|
|
102
|
+
contact.unsubscribe_to
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def remove_subscription_from(jid)
|
|
107
|
+
if contact = contact(jid)
|
|
108
|
+
contact.unsubscribe_from
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns this user's roster contacts as an iq query element.
|
|
113
|
+
def to_roster_xml(id)
|
|
114
|
+
doc = Nokogiri::XML::Document.new
|
|
115
|
+
doc.create_element('iq', 'id' => id, 'type' => 'result') do |el|
|
|
116
|
+
el << doc.create_element('query', 'xmlns' => 'jabber:iq:roster') do |query|
|
|
117
|
+
@roster.sort!.each do |contact|
|
|
118
|
+
query << contact.to_roster_xml
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|