vines 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,119 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Storage
|
5
|
+
class CouchDB < Storage
|
6
|
+
register :couchdb
|
7
|
+
|
8
|
+
%w[host port database tls username password].each do |name|
|
9
|
+
define_method(name) do |*args|
|
10
|
+
if args.first
|
11
|
+
@config[name.to_sym] = args.first
|
12
|
+
else
|
13
|
+
@config[name.to_sym]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(&block)
|
19
|
+
@config = {}
|
20
|
+
instance_eval(&block)
|
21
|
+
[:host, :port, :database].each {|key| raise "Must provide #{key}" unless @config[key] }
|
22
|
+
@url = url(@config)
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_user(jid)
|
26
|
+
jid = JID.new(jid || '').bare.to_s
|
27
|
+
if jid.empty? then yield; return end
|
28
|
+
get("user:#{jid}") do |doc|
|
29
|
+
user = if doc && doc['type'] == 'User'
|
30
|
+
User.new(:jid => jid).tap do |user|
|
31
|
+
user.name, user.password = doc.values_at('name', 'password')
|
32
|
+
(doc['roster'] || {}).each_pair do |jid, props|
|
33
|
+
user.roster << Contact.new(
|
34
|
+
:jid => jid,
|
35
|
+
:name => props['name'],
|
36
|
+
:subscription => props['subscription'],
|
37
|
+
:ask => props['ask'],
|
38
|
+
:groups => props['groups'] || [])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
yield user
|
43
|
+
end
|
44
|
+
end
|
45
|
+
fiber :find_user
|
46
|
+
|
47
|
+
def save_user(user, &callback)
|
48
|
+
id = "user:#{user.jid.bare}"
|
49
|
+
get(id) do |doc|
|
50
|
+
doc ||= {'_id' => id}
|
51
|
+
doc['type'] = 'User'
|
52
|
+
doc['name'] = user.name
|
53
|
+
doc['password'] = user.password
|
54
|
+
doc['roster'] = {}
|
55
|
+
user.roster.each do |contact|
|
56
|
+
doc['roster'][contact.jid.bare.to_s] = contact.to_h
|
57
|
+
end
|
58
|
+
save_doc(doc, &callback)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
fiber :save_user
|
62
|
+
|
63
|
+
def find_vcard(jid)
|
64
|
+
jid = JID.new(jid || '').bare.to_s
|
65
|
+
if jid.empty? then yield; return end
|
66
|
+
get("vcard:#{jid}") do |doc|
|
67
|
+
card = if doc && doc['type'] == 'Vcard'
|
68
|
+
Nokogiri::XML(doc['card']).root rescue nil
|
69
|
+
end
|
70
|
+
yield card
|
71
|
+
end
|
72
|
+
end
|
73
|
+
fiber :find_vcard
|
74
|
+
|
75
|
+
def save_vcard(jid, card, &callback)
|
76
|
+
jid = JID.new(jid).bare.to_s
|
77
|
+
id = "vcard:#{jid}"
|
78
|
+
get(id) do |doc|
|
79
|
+
doc ||= {'_id' => id}
|
80
|
+
doc['type'] = 'Vcard'
|
81
|
+
doc['card'] = card.to_xml
|
82
|
+
save_doc(doc, &callback)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
fiber :save_vcard
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def url(config)
|
90
|
+
scheme = config[:tls] ? 'https' : 'http'
|
91
|
+
user, password = config.values_at(:username, :password)
|
92
|
+
credentials = empty?(user, password) ? '' : "%s:%s@" % [user, password]
|
93
|
+
"%s://%s%s:%s/%s" % [scheme, credentials, *config.values_at(:host, :port, :database)]
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(jid)
|
97
|
+
http = EM::HttpRequest.new("#{@url}/#{escape(jid)}").get
|
98
|
+
http.errback { yield }
|
99
|
+
http.callback do
|
100
|
+
doc = if http.response_header.status == 200
|
101
|
+
JSON.parse(http.response) rescue nil
|
102
|
+
end
|
103
|
+
yield doc
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def save_doc(doc, &callback)
|
108
|
+
http = EM::HttpRequest.new(@url).post(
|
109
|
+
:head => {'Content-Type' => 'application/json'}, :body => doc.to_json)
|
110
|
+
http.callback(&callback) if callback
|
111
|
+
http.errback(&callback) if callback
|
112
|
+
end
|
113
|
+
|
114
|
+
def escape(jid)
|
115
|
+
URI.escape(jid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Storage
|
5
|
+
|
6
|
+
# Authenticates usernames and passwords against an LDAP directory. This can
|
7
|
+
# provide authentication logic for the other, full-featured Storage
|
8
|
+
# implementations while they store and retrieve the rest of the user
|
9
|
+
# information.
|
10
|
+
class Ldap
|
11
|
+
@@required = [:host, :port]
|
12
|
+
%w[tls dn password basedn object_class user_attr name_attr].each do |name|
|
13
|
+
@@required << name.to_sym
|
14
|
+
define_method name do |*args|
|
15
|
+
@config[name.to_sym] = args.first
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(host='localhost', port=636, &block)
|
20
|
+
@config = {:host => host, :port => port}
|
21
|
+
instance_eval(&block)
|
22
|
+
@@required.each {|key| raise "Must provide #{key}" if @config[key].nil? }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Validates a username and password by binding to the LDAP instance with
|
26
|
+
# those credentials. If the bind succeeds, the user's attributes are
|
27
|
+
# retrieved.
|
28
|
+
def authenticate(username, password)
|
29
|
+
return if [username, password].any? {|arg| (arg || '').strip.empty? }
|
30
|
+
|
31
|
+
clas = Net::LDAP::Filter.eq('objectClass', @config[:object_class])
|
32
|
+
uid = Net::LDAP::Filter.eq(@config[:user_attr], username)
|
33
|
+
filter = clas & uid
|
34
|
+
attrs = [@config[:name_attr], 'mail']
|
35
|
+
|
36
|
+
ldap = connect(@config[:dn], @config[:password])
|
37
|
+
entries = ldap.search(:attributes => attrs, :filter => filter)
|
38
|
+
return unless entries && entries.size == 1
|
39
|
+
|
40
|
+
user = if connect(entries.first.dn, password).bind
|
41
|
+
name = entries.first[@config[:name_attr]].first
|
42
|
+
User.new(:jid => username, :name => name.to_s, :roster => [])
|
43
|
+
end
|
44
|
+
user
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def connect(dn, password)
|
50
|
+
options = [:host, :port, :base].zip(
|
51
|
+
@config.values_at(:host, :port, :basedn))
|
52
|
+
Net::LDAP.new(Hash[options]).tap do |ldap|
|
53
|
+
ldap.encryption(:simple_tls) if @config[:tls]
|
54
|
+
ldap.auth(dn, password)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Storage
|
5
|
+
|
6
|
+
# A storage implementation that persists data to YAML files on the
|
7
|
+
# local file system.
|
8
|
+
class Local < Storage
|
9
|
+
register :fs
|
10
|
+
|
11
|
+
def initialize(&block)
|
12
|
+
instance_eval(&block)
|
13
|
+
unless @dir && File.directory?(@dir) && File.writable?(@dir)
|
14
|
+
raise 'Must provide a writable storage directory'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def dir(dir=nil)
|
19
|
+
dir ? @dir = File.expand_path(dir) : @dir
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_user(jid)
|
23
|
+
jid = JID.new(jid || '').bare.to_s
|
24
|
+
file = File.join(@dir, "#{jid}.user") unless jid.empty?
|
25
|
+
record = YAML.load_file(file) rescue nil
|
26
|
+
return User.new(:jid => jid).tap do |user|
|
27
|
+
user.name, user.password = record.values_at('name', 'password')
|
28
|
+
(record['roster'] || {}).each_pair do |jid, props|
|
29
|
+
user.roster << Contact.new(
|
30
|
+
:jid => jid,
|
31
|
+
:name => props['name'],
|
32
|
+
:subscription => props['subscription'],
|
33
|
+
:ask => props['ask'],
|
34
|
+
:groups => props['groups'] || [])
|
35
|
+
end
|
36
|
+
end if record
|
37
|
+
end
|
38
|
+
|
39
|
+
def save_user(user)
|
40
|
+
record = {'name' => user.name, 'password' => user.password, 'roster' => {}}
|
41
|
+
user.roster.each do |contact|
|
42
|
+
record['roster'][contact.jid.bare.to_s] = contact.to_h
|
43
|
+
end
|
44
|
+
file = File.join(@dir, "#{user.jid.bare.to_s}.user")
|
45
|
+
File.open(file, 'w') do |f|
|
46
|
+
YAML.dump(record, f)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_vcard(jid)
|
51
|
+
jid = JID.new(jid || '').bare.to_s
|
52
|
+
return if jid.empty?
|
53
|
+
file = File.join(@dir, "#{jid}.vcard")
|
54
|
+
Nokogiri::XML(File.read(file)).root rescue nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def save_vcard(jid, card)
|
58
|
+
jid = JID.new(jid).bare.to_s
|
59
|
+
file = File.join(@dir, "#{jid}.vcard")
|
60
|
+
File.open(file, 'w') do |f|
|
61
|
+
f.write(card.to_xml)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Storage
|
5
|
+
class Redis < Storage
|
6
|
+
register :redis
|
7
|
+
|
8
|
+
%w[host port database password].each do |name|
|
9
|
+
define_method(name) do |*args|
|
10
|
+
if args.first
|
11
|
+
@config[name.to_sym] = args.first
|
12
|
+
else
|
13
|
+
@config[name.to_sym]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(&block)
|
19
|
+
@config = {}
|
20
|
+
instance_eval(&block)
|
21
|
+
@config[:db] = @config.delete(:database) if @config.key?(:database)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_user(jid)
|
25
|
+
jid = JID.new(jid || '').bare.to_s
|
26
|
+
if jid.empty? then yield; return end
|
27
|
+
find_roster(jid) do |contacts|
|
28
|
+
redis.get("user:#{jid}") do |response|
|
29
|
+
user = if response
|
30
|
+
doc = JSON.parse(response) rescue nil
|
31
|
+
User.new(:jid => jid).tap do |user|
|
32
|
+
user.name, user.password = doc.values_at('name', 'password')
|
33
|
+
user.roster = contacts
|
34
|
+
end if doc
|
35
|
+
end
|
36
|
+
yield user
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
fiber :find_user
|
41
|
+
|
42
|
+
def save_user(user)
|
43
|
+
doc = {:name => user.name, :password => user.password}
|
44
|
+
contacts = user.roster.map {|c| [c.jid.to_s, c.to_h.to_json] }.flatten
|
45
|
+
roster = "roster:#{user.jid.bare}"
|
46
|
+
|
47
|
+
redis.set("user:#{user.jid.bare}", doc.to_json) do
|
48
|
+
redis.del(roster) do
|
49
|
+
contacts.empty? ? yield : redis.hmset(roster, *contacts) { yield }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
fiber :save_user
|
54
|
+
|
55
|
+
def find_vcard(jid)
|
56
|
+
jid = JID.new(jid || '').bare.to_s
|
57
|
+
if jid.empty? then yield; return end
|
58
|
+
redis.get("vcard:#{jid}") do |response|
|
59
|
+
card = if response
|
60
|
+
doc = JSON.parse(response) rescue nil
|
61
|
+
Nokogiri::XML(doc['card']).root rescue nil
|
62
|
+
end
|
63
|
+
yield card
|
64
|
+
end
|
65
|
+
end
|
66
|
+
fiber :find_vcard
|
67
|
+
|
68
|
+
def save_vcard(jid, card)
|
69
|
+
jid = JID.new(jid).bare.to_s
|
70
|
+
doc = {:card => card.to_xml}
|
71
|
+
redis.set("vcard:#{jid}", doc.to_json) do
|
72
|
+
yield
|
73
|
+
end
|
74
|
+
end
|
75
|
+
fiber :save_vcard
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Retrieve the hash stored at roster:jid and yield an array of
|
80
|
+
# +Vines::Contact+ objects to the provided block.
|
81
|
+
#
|
82
|
+
# find_roster('alice@wonderland.lit') do |contacts|
|
83
|
+
# puts contacts.size
|
84
|
+
# end
|
85
|
+
def find_roster(jid)
|
86
|
+
jid = JID.new(jid).bare
|
87
|
+
redis.hgetall("roster:#{jid}") do |roster|
|
88
|
+
contacts = roster.map do |jid, json|
|
89
|
+
contact = JSON.parse(json) rescue nil
|
90
|
+
Contact.new(
|
91
|
+
:jid => jid,
|
92
|
+
:name => contact['name'],
|
93
|
+
:subscription => contact['subscription'],
|
94
|
+
:ask => contact['ask'],
|
95
|
+
:groups => contact['groups'] || []) if contact
|
96
|
+
end.compact
|
97
|
+
yield contacts
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Cache and return a redis connection object. The em-redis gem reconnects
|
102
|
+
# in unbind so only create one connection.
|
103
|
+
def redis
|
104
|
+
@redis ||= EM::Protocols::Redis.connect(@config)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Storage
|
5
|
+
class Sql < Storage
|
6
|
+
register :sql
|
7
|
+
|
8
|
+
class Contact < ActiveRecord::Base
|
9
|
+
belongs_to :user
|
10
|
+
end
|
11
|
+
class Group < ActiveRecord::Base; end
|
12
|
+
class User < ActiveRecord::Base
|
13
|
+
has_many :contacts
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[adapter host port database username password pool].each do |name|
|
17
|
+
define_method(name) do |*args|
|
18
|
+
if args.first
|
19
|
+
@config[name.to_sym] = args.first
|
20
|
+
else
|
21
|
+
@config[name.to_sym]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(&block)
|
27
|
+
@config = {}
|
28
|
+
instance_eval(&block)
|
29
|
+
required = [:adapter, :database]
|
30
|
+
required << [:host, :port] unless @config[:adapter] == 'sqlite3'
|
31
|
+
required.flatten.each {|key| raise "Must provide #{key}" unless @config[key] }
|
32
|
+
[:username, :password].each {|key| @config.delete(key) if empty?(@config[key]) }
|
33
|
+
establish_connection
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_user(jid)
|
37
|
+
ActiveRecord::Base.clear_reloadable_connections!
|
38
|
+
|
39
|
+
jid = JID.new(jid || '').bare.to_s
|
40
|
+
return if jid.empty?
|
41
|
+
xuser = by_jid(jid)
|
42
|
+
return Vines::User.new(:jid => jid).tap do |user|
|
43
|
+
user.name, user.password = xuser.name, xuser.password
|
44
|
+
xuser.contacts.each do |contact|
|
45
|
+
groups = contact.groups.map {|group| group.name }
|
46
|
+
user.roster << Vines::Contact.new(
|
47
|
+
:jid => contact.jid,
|
48
|
+
:name => contact.name,
|
49
|
+
:subscription => contact.subscription,
|
50
|
+
:ask => contact.ask,
|
51
|
+
:groups => groups)
|
52
|
+
end
|
53
|
+
end if xuser
|
54
|
+
end
|
55
|
+
defer :find_user
|
56
|
+
|
57
|
+
def save_user(user)
|
58
|
+
ActiveRecord::Base.clear_reloadable_connections!
|
59
|
+
|
60
|
+
xuser = by_jid(user.jid) || Sql::User.new(:jid => user.jid.bare.to_s)
|
61
|
+
xuser.name = user.name
|
62
|
+
xuser.password = user.password
|
63
|
+
|
64
|
+
# remove deleted contacts from roster
|
65
|
+
xuser.contacts.delete(xuser.contacts.select do |contact|
|
66
|
+
!user.contact?(contact.jid)
|
67
|
+
end)
|
68
|
+
|
69
|
+
# update contacts
|
70
|
+
xuser.contacts.each do |contact|
|
71
|
+
fresh = user.contact(contact.jid)
|
72
|
+
contact.update_attributes(
|
73
|
+
:name => fresh.name,
|
74
|
+
:ask => fresh.ask,
|
75
|
+
:subscription => fresh.subscription,
|
76
|
+
:groups => groups(fresh))
|
77
|
+
end
|
78
|
+
|
79
|
+
# add new contacts to roster
|
80
|
+
jids = xuser.contacts.map {|c| c.jid }
|
81
|
+
user.roster.select {|contact| !jids.include?(contact.jid.bare.to_s) }
|
82
|
+
.each do |contact|
|
83
|
+
xuser.contacts.build(
|
84
|
+
:user => xuser,
|
85
|
+
:jid => contact.jid.bare.to_s,
|
86
|
+
:name => contact.name,
|
87
|
+
:ask => contact.ask,
|
88
|
+
:subscription => contact.subscription,
|
89
|
+
:groups => groups(contact))
|
90
|
+
end
|
91
|
+
xuser.save
|
92
|
+
end
|
93
|
+
defer :save_user
|
94
|
+
|
95
|
+
def find_vcard(jid)
|
96
|
+
ActiveRecord::Base.clear_reloadable_connections!
|
97
|
+
|
98
|
+
jid = JID.new(jid || '').bare.to_s
|
99
|
+
return if jid.empty?
|
100
|
+
if xuser = by_jid(jid)
|
101
|
+
Nokogiri::XML(xuser.vcard).root rescue nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
defer :find_vcard
|
105
|
+
|
106
|
+
def save_vcard(jid, card)
|
107
|
+
ActiveRecord::Base.clear_reloadable_connections!
|
108
|
+
|
109
|
+
xuser = by_jid(jid)
|
110
|
+
if xuser
|
111
|
+
xuser.vcard = card.to_xml
|
112
|
+
xuser.save
|
113
|
+
end
|
114
|
+
end
|
115
|
+
defer :save_vcard
|
116
|
+
|
117
|
+
# Create the tables and indexes used by this storage engine.
|
118
|
+
def create_schema(args={})
|
119
|
+
ActiveRecord::Base.clear_reloadable_connections!
|
120
|
+
|
121
|
+
args[:force] ||= false
|
122
|
+
|
123
|
+
ActiveRecord::Schema.define do
|
124
|
+
create_table :users, :force => args[:force] do |t|
|
125
|
+
t.string :jid, :limit => 1000, :null => false
|
126
|
+
t.string :name, :limit => 1000, :null => true
|
127
|
+
t.string :password, :limit => 1000, :null => true
|
128
|
+
t.text :vcard, :null => true
|
129
|
+
end
|
130
|
+
add_index :users, :jid, :unique => true
|
131
|
+
|
132
|
+
create_table :contacts, :force => args[:force] do |t|
|
133
|
+
t.integer :user_id, :null => false
|
134
|
+
t.string :jid, :limit => 1000, :null => false
|
135
|
+
t.string :name, :limit => 1000, :null => true
|
136
|
+
t.string :ask, :limit => 1000, :null => true
|
137
|
+
t.string :subscription, :limit => 1000, :null => false
|
138
|
+
end
|
139
|
+
add_index :contacts, [:user_id, :jid], :unique => true
|
140
|
+
|
141
|
+
create_table :groups, :force => args[:force] do |t|
|
142
|
+
t.string :name, :limit => 1000, :null => false
|
143
|
+
end
|
144
|
+
add_index :groups, :name, :unique => true
|
145
|
+
|
146
|
+
create_table :contacts_groups, :id => false, :force => args[:force] do |t|
|
147
|
+
t.integer :contact_id, :null => false
|
148
|
+
t.integer :group_id, :null => false
|
149
|
+
end
|
150
|
+
add_index :contacts_groups, [:contact_id, :group_id], :unique => true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def establish_connection
|
157
|
+
ActiveRecord::Base.establish_connection(@config)
|
158
|
+
# has_and_belongs_to_many requires a connection so configure the
|
159
|
+
# associations here rather than in the class definitions above.
|
160
|
+
Sql::Contact.has_and_belongs_to_many :groups
|
161
|
+
Sql::Group.has_and_belongs_to_many :contacts
|
162
|
+
end
|
163
|
+
|
164
|
+
def by_jid(jid)
|
165
|
+
jid = JID.new(jid).bare.to_s
|
166
|
+
Sql::User.find_by_jid(jid, :include => {:contacts => :groups})
|
167
|
+
end
|
168
|
+
|
169
|
+
def groups(contact)
|
170
|
+
contact.groups.map {|name| Sql::Group.find_or_create_by_name(name.strip) }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|