selfsdk 0.0.216 → 0.0.218
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.
- checksums.yaml +4 -4
- data/lib/client.rb +1 -1
- data/lib/crypto.rb +94 -113
- data/lib/messages/message.rb +1 -1
- data/lib/messaging.rb +14 -67
- data/lib/selfsdk.rb +4 -1
- data/lib/services/messaging.rb +0 -35
- data/lib/services/requester.rb +0 -3
- data/lib/storage.rb +236 -0
- metadata +16 -2
- data/lib/acl.rb +0 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bff9337b676123f000fdea33206ecb138fff8656f2d77e182212334228d996e
|
4
|
+
data.tar.gz: e543e7a2040a6fcc218c4e187fd8304de0189e272e95926a35e438cfd13fe5e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13edf88e46cc57be0503755f7b54cf080e261bad472ff2e8820277bdaea0ed1775f88f5fe46f407ddbf4df0d3b1f566c734e4d19a55cfb0a0ba4f6bc07d88cc3
|
7
|
+
data.tar.gz: e25a198896eca2e236c96405b143343323a35638b78b680ee8e96abdf426b10838d1c40786460decf44f856f03802c6e77857c10062fd1c62679a6bb288d95e7
|
data/lib/client.rb
CHANGED
data/lib/crypto.rb
CHANGED
@@ -1,37 +1,43 @@
|
|
1
1
|
# Copyright 2020 Self Group Ltd. All Rights Reserved.
|
2
2
|
|
3
3
|
require 'self_crypto'
|
4
|
+
require_relative 'storage'
|
4
5
|
|
5
6
|
module SelfSDK
|
6
7
|
class Crypto
|
7
|
-
def initialize(client, device,
|
8
|
+
def initialize(client, device, storage, storage_key)
|
8
9
|
@client = client
|
9
10
|
@device = device
|
10
11
|
@storage_key = storage_key
|
11
|
-
@storage_folder = "#{storage_folder}/#{@client.jwt.key_id}"
|
12
12
|
@lock_strategy = true
|
13
13
|
@mode = "r+"
|
14
|
+
@storage = storage
|
15
|
+
@keys = {}
|
16
|
+
@mutex = Mutex.new
|
17
|
+
|
18
|
+
@storage.tx do
|
19
|
+
::SelfSDK.logger.debug('Checking if account exists...')
|
20
|
+
if @storage.account_exists?
|
21
|
+
# 1a) if alice's account file exists load the pickle from the file
|
22
|
+
@account = SelfCrypto::Account.from_pickle(@storage.account_olm, @storage_key)
|
23
|
+
else
|
24
|
+
# 1b-i) if create a new account for alice if one doesn't exist already
|
25
|
+
@account = SelfCrypto::Account.from_seed(@client.jwt.key)
|
14
26
|
|
15
|
-
|
16
|
-
|
17
|
-
@account = SelfCrypto::Account.from_pickle(File.read(account_path), @storage_key)
|
18
|
-
else
|
19
|
-
# 1b-i) if create a new account for alice if one doesn't exist already
|
20
|
-
@account = SelfCrypto::Account.from_seed(@client.jwt.key)
|
21
|
-
|
22
|
-
# 1b-ii) generate some keys for alice and publish them
|
23
|
-
@account.gen_otk(100)
|
27
|
+
# 1b-ii) generate some keys for alice and publish them
|
28
|
+
@account.gen_otk(100)
|
24
29
|
|
25
|
-
|
26
|
-
|
30
|
+
# 1b-iii) convert those keys to json
|
31
|
+
@keys = @account.otk['curve25519'].map{|k,v| {id: k, key: v}}
|
32
|
+
keys = @keys.to_json
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
|
34
|
+
# 1b-iv) post those keys to POST /v1/identities/<selfid>/devices/1/pre_keys/
|
35
|
+
res = @client.post("/v1/apps/#{@client.jwt.id}/devices/#{@device}/pre_keys", keys)
|
36
|
+
raise 'unable to push prekeys, please try in a few minutes' if res.code != 200
|
31
37
|
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
# 1b-v) store the account to a file
|
39
|
+
@storage.account_create(@account.to_pickle(@storage_key))
|
40
|
+
end
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
@@ -43,119 +49,79 @@ module SelfSDK
|
|
43
49
|
gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
|
44
50
|
|
45
51
|
sessions = {}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
if File.exist?(session_file_name)
|
58
|
-
# Lock the session file
|
59
|
-
locks[session_file_name] = File.open(session_file_name, @mode)
|
60
|
-
locks[session_file_name].flock(File::LOCK_EX)
|
61
|
-
end
|
62
|
-
session_with_bob = get_outbound_session_with_bob(locks[session_file_name], r[:id], r[:device_id])
|
52
|
+
ct = ""
|
53
|
+
tx do
|
54
|
+
gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
|
55
|
+
recipients.each do |r|
|
56
|
+
sid = @storage.sid(r[:id], r[:device_id])
|
57
|
+
next if sid == @storage.app_id
|
58
|
+
|
59
|
+
session_with_bob = get_outbound_session_with_bob(@storage.session_get_olm(sid), r[:id], r[:device_id])
|
60
|
+
::SelfSDK.logger.debug("- [crypto] adding group participant #{r[:id]}:#{r[:device_id]}")
|
61
|
+
gs.add_participant("#{r[:id]}:#{r[:device_id]}", session_with_bob)
|
62
|
+
sessions[sid] = session_with_bob
|
63
63
|
rescue => e
|
64
64
|
::SelfSDK.logger.warn("- [crypto] there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
|
65
65
|
::SelfSDK.logger.warn("- [crypto] #{e}")
|
66
66
|
next
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
# 5) encrypt a message
|
75
|
-
::SelfSDK.logger.debug("- [crypto] group encrypting message")
|
76
|
-
ct = gs.encrypt(message).to_s
|
69
|
+
# 5) encrypt a message
|
70
|
+
::SelfSDK.logger.debug("- [crypto] group encrypting message")
|
71
|
+
ct = gs.encrypt(message).to_s
|
77
72
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
locks[session_file_name].rewind
|
84
|
-
locks[session_file_name].write(pickle)
|
85
|
-
locks[session_file_name].truncate(locks[session_file_name].pos)
|
86
|
-
else
|
87
|
-
File.write(session_file_name, pickle)
|
73
|
+
# 6) store the session to a file
|
74
|
+
::SelfSDK.logger.debug("- [crypto] storing sessions")
|
75
|
+
sessions.each do |sid, session_with_bob|
|
76
|
+
pickle = session_with_bob.to_pickle(@storage_key)
|
77
|
+
@storage.session_update(sid, pickle)
|
88
78
|
end
|
89
79
|
end
|
90
|
-
|
91
80
|
ct
|
92
|
-
ensure
|
93
|
-
locks.each do |session_file_name, lock|
|
94
|
-
# Unlock the file
|
95
|
-
if lock
|
96
|
-
lock.flock(File::LOCK_UN)
|
97
|
-
end
|
98
|
-
end
|
99
81
|
end
|
100
82
|
|
101
|
-
def decrypt(message, sender, sender_device)
|
102
|
-
|
103
|
-
|
104
|
-
session_file_name = session_path(sender, sender_device)
|
83
|
+
def decrypt(message, sender, sender_device, offset)
|
84
|
+
::SelfSDK.logger.debug("- [crypto] decrypting a message #{message}")
|
85
|
+
sid = @storage.sid(sender, sender_device)
|
105
86
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
::SelfSDK.logger.debug("- [crypto] loading sessions")
|
113
|
-
session_with_bob = get_inbound_session_with_bob(f, message)
|
87
|
+
pt = ""
|
88
|
+
tx do
|
89
|
+
::SelfSDK.logger.debug("- [crypto] loading sessions for #{sid}")
|
90
|
+
::SelfSDK.logger.debug("- [crypto] #{@account.one_time_keys["curve25519"]}")
|
91
|
+
session_with_bob = get_inbound_session_with_bob(@storage.session_get_olm(sid), message)
|
114
92
|
|
115
|
-
|
116
|
-
|
117
|
-
|
93
|
+
# 8) create a group session and set the identity of the account you're using
|
94
|
+
::SelfSDK.logger.debug("- [crypto] create a group session and set the identity of the account #{@client.jwt.id}:#{@device}")
|
95
|
+
gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
|
118
96
|
|
119
|
-
|
120
|
-
|
121
|
-
|
97
|
+
# 9) add all recipients and their sessions
|
98
|
+
::SelfSDK.logger.debug("- [crypto] add all recipients and their sessions #{sender}:#{sender_device}")
|
99
|
+
gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
|
122
100
|
|
123
|
-
|
124
|
-
|
125
|
-
|
101
|
+
# 10) decrypt the message ciphertext
|
102
|
+
::SelfSDK.logger.debug("- [crypto] decrypt the message ciphertext")
|
103
|
+
pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
|
126
104
|
|
127
|
-
|
128
|
-
|
105
|
+
# 11) store the session to a file
|
106
|
+
::SelfSDK.logger.debug("- [crypto] store the session to a file")
|
129
107
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
f.write(pickle)
|
134
|
-
f.truncate(f.pos)
|
135
|
-
else
|
136
|
-
File.write(session_file_name, pickle)
|
108
|
+
pickle = session_with_bob.to_pickle(@storage_key)
|
109
|
+
@storage.session_update(sid, pickle)
|
110
|
+
@storage.account_set_offset(offset)
|
137
111
|
end
|
138
|
-
|
139
112
|
pt
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
113
|
+
rescue => e
|
114
|
+
::SelfSDK.logger.debug("- [crypto] ERROR DECRYPTING: original message: #{message}")
|
115
|
+
::SelfSDK.logger.debug("- [crypto] ERROR DECRYPTING: exception message: #{e.message}")
|
116
|
+
::SelfSDK.logger.debug("- [crypto] ERROR DECRYPTING: exception backtrace: #{e.backtrace}")
|
117
|
+
@storage.account_set_offset(offset)
|
118
|
+
pt
|
144
119
|
end
|
145
120
|
|
146
121
|
private
|
147
122
|
|
148
|
-
def
|
149
|
-
|
150
|
-
end
|
151
|
-
|
152
|
-
def session_path(selfid, device)
|
153
|
-
"#{@storage_folder}/#{selfid}:#{device}-session.pickle"
|
154
|
-
end
|
155
|
-
|
156
|
-
def get_outbound_session_with_bob(f, recipient, recipient_device)
|
157
|
-
if !f.nil?
|
158
|
-
pickle = f.read
|
123
|
+
def get_outbound_session_with_bob(pickle, recipient, recipient_device)
|
124
|
+
if !pickle.nil?
|
159
125
|
# 2a) if bob's session file exists load the pickle from the file
|
160
126
|
session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
|
161
127
|
else
|
@@ -184,18 +150,24 @@ module SelfSDK
|
|
184
150
|
session_with_bob
|
185
151
|
end
|
186
152
|
|
187
|
-
def get_inbound_session_with_bob(
|
188
|
-
if !
|
189
|
-
pickle = f.read
|
153
|
+
def get_inbound_session_with_bob(pickle, message)
|
154
|
+
if !pickle.nil?
|
190
155
|
# 7a) if carol's session file exists load the pickle from the file
|
191
156
|
session_with_bob = SelfCrypto::Session.from_pickle(pickle, @storage_key)
|
192
157
|
end
|
193
158
|
|
159
|
+
::SelfSDK.logger.debug("- [crypto] pickle nil? #{pickle.nil?}")
|
160
|
+
::SelfSDK.logger.debug("- [crypto] getting one time message for #{@client.jwt.id}:#{@device}")
|
161
|
+
|
194
162
|
# 7b-i) if you have not previously sent or received a message to/from bob,
|
195
163
|
# you should extract the initial message from the group message intended
|
196
164
|
# for your account id.
|
197
165
|
m = SelfCrypto::GroupMessage.new(message.to_s).get_message("#{@client.jwt.id}:#{@device}")
|
198
166
|
|
167
|
+
::SelfSDK.logger.debug("- [crypto] one time message #{m}")
|
168
|
+
::SelfSDK.logger.debug("- [crypto] session is nil? #{session_with_bob.nil?}")
|
169
|
+
::SelfSDK.logger.debug("- [crypto] is prekey message? #{m.instance_of?(SelfCrypto::PreKeyMessage)}")
|
170
|
+
|
199
171
|
# if there is no session, create one
|
200
172
|
# if there is an existing session and we are sent a one time key message, check
|
201
173
|
# if it belongs to this current session and create a new inbound session if it doesn't
|
@@ -204,6 +176,7 @@ module SelfSDK
|
|
204
176
|
session_with_bob = @account.inbound_session(m)
|
205
177
|
|
206
178
|
# 7b-iii) remove the session's prekey from the account
|
179
|
+
::SelfSDK.logger.debug "- [crypto] removing one time keys for bob #{session_with_bob}"
|
207
180
|
@account.remove_one_time_keys(session_with_bob)
|
208
181
|
|
209
182
|
current_one_time_keys = @account.otk['curve25519']
|
@@ -221,12 +194,20 @@ module SelfSDK
|
|
221
194
|
res = @client.post("/v1/apps/#{@client.jwt.id}/devices/#{@device}/pre_keys", keys.to_json)
|
222
195
|
raise 'unable to push prekeys, please try in a few minutes' if res.code != 200
|
223
196
|
end
|
224
|
-
|
225
|
-
|
197
|
+
::SelfSDK.logger.debug "- [crypto] updating account with removed keys"
|
198
|
+
@storage.account_update(@account.to_pickle(@storage_key))
|
226
199
|
end
|
227
200
|
|
228
201
|
session_with_bob
|
229
202
|
end
|
230
203
|
|
204
|
+
def tx
|
205
|
+
@mutex.lock
|
206
|
+
@storage.tx do
|
207
|
+
yield
|
208
|
+
end
|
209
|
+
ensure
|
210
|
+
@mutex.unlock
|
211
|
+
end
|
231
212
|
end
|
232
213
|
end
|
data/lib/messages/message.rb
CHANGED
@@ -31,7 +31,7 @@ module SelfSDK
|
|
31
31
|
else
|
32
32
|
envelope = input
|
33
33
|
issuer = input.sender.split(":")
|
34
|
-
messaging.encryption_client.decrypt(input.ciphertext, issuer.first, issuer.last)
|
34
|
+
messaging.encryption_client.decrypt(input.ciphertext, issuer.first, issuer.last, input.offset)
|
35
35
|
end
|
36
36
|
|
37
37
|
jwt = JSON.parse(body, symbolize_names: true)
|
data/lib/messaging.rb
CHANGED
@@ -13,6 +13,7 @@ require_relative 'messages/message'
|
|
13
13
|
module SelfSDK
|
14
14
|
class WebsocketClient
|
15
15
|
ON_DEMAND_CLOSE_CODE=3999
|
16
|
+
CONNECTION_SUPERCEDED=1011
|
16
17
|
|
17
18
|
attr_accessor :ws
|
18
19
|
|
@@ -42,7 +43,7 @@ module SelfSDK
|
|
42
43
|
@ws.on :close do |event|
|
43
44
|
SelfSDK.logger.debug "connection closed detected : #{event.code} #{event.reason}"
|
44
45
|
|
45
|
-
if event.code
|
46
|
+
if not [ON_DEMAND_CLOSE_CODE, CONNECTION_SUPERCEDED].include? event.code
|
46
47
|
raise StandardError('websocket connection closed') if !@auto_reconnect
|
47
48
|
|
48
49
|
if !@reconnection_delay.nil?
|
@@ -59,6 +60,7 @@ module SelfSDK
|
|
59
60
|
|
60
61
|
# Sends a closing message to the websocket client.
|
61
62
|
def close
|
63
|
+
SelfSDK.logger.debug "connection closed by the client"
|
62
64
|
@ws.close(ON_DEMAND_CLOSE_CODE, "connection closed by the client")
|
63
65
|
end
|
64
66
|
|
@@ -80,7 +82,6 @@ module SelfSDK
|
|
80
82
|
class MessagingClient
|
81
83
|
DEFAULT_DEVICE="1"
|
82
84
|
DEFAULT_AUTO_RECONNECT=true
|
83
|
-
DEFAULT_STORAGE_DIR="./.self_storage"
|
84
85
|
|
85
86
|
PRIORITIES = {
|
86
87
|
'chat.invite': SelfSDK::Messages::PRIORITY_VISIBLE,
|
@@ -104,12 +105,12 @@ module SelfSDK
|
|
104
105
|
#
|
105
106
|
# @param url [string] self-messaging url
|
106
107
|
# @params client [Object] SelfSDK::Client object
|
107
|
-
# @option opts [string] :
|
108
|
+
# @option opts [string] :storage the library used to persist session data.
|
108
109
|
# @params storage_key [String] seed to encrypt messaging
|
109
110
|
# @params storage_folder [String] folder to perist messaging encryption
|
110
111
|
# @option opts [Bool] :auto_reconnect Automatically reconnects to websocket if connection is lost (defaults to true).
|
111
112
|
# @option opts [String] :device_id The device id to be used by the app defaults to "1".
|
112
|
-
def initialize(url, client, storage_key, options = {})
|
113
|
+
def initialize(url, client, storage_key, storage, options = {})
|
113
114
|
@mon = Monitor.new
|
114
115
|
@messages = {}
|
115
116
|
@acks = {}
|
@@ -121,18 +122,13 @@ module SelfSDK
|
|
121
122
|
@timeout = 120 # seconds
|
122
123
|
@auth_id = SecureRandom.uuid
|
123
124
|
@device_id = options.fetch(:device_id, DEFAULT_DEVICE)
|
124
|
-
@raw_storage_dir = options.fetch(:storage_dir, DEFAULT_STORAGE_DIR)
|
125
|
-
@storage_dir = "#{@raw_storage_dir}/apps/#{@jwt.id}/devices/#{@device_id}"
|
126
|
-
FileUtils.mkdir_p @storage_dir unless File.exist? @storage_dir
|
127
|
-
@offset_file = "#{@storage_dir}/#{@jwt.id}:#{@device_id}.offset"
|
128
|
-
@offset = read_offset
|
129
125
|
@source = SelfSDK::Sources.new("#{__dir__}/sources.json")
|
126
|
+
@storage = storage
|
130
127
|
|
131
128
|
unless options.include? :no_crypto
|
132
|
-
|
133
|
-
FileUtils.mkdir_p crypto_path unless File.exist? crypto_path
|
134
|
-
@encryption_client = Crypto.new(@client, @device_id, crypto_path, storage_key)
|
129
|
+
@encryption_client = Crypto.new(@client, @device_id, @storage, storage_key)
|
135
130
|
end
|
131
|
+
@offset = @storage.account_offset
|
136
132
|
|
137
133
|
@ws = if options.include? :ws
|
138
134
|
options[:ws]
|
@@ -246,17 +242,6 @@ module SelfSDK
|
|
246
242
|
end
|
247
243
|
end
|
248
244
|
|
249
|
-
# Sends a command to list ACL rules.
|
250
|
-
def list_acl_rules
|
251
|
-
wait_for 'acl_list' do
|
252
|
-
a = SelfMsg::Acl.new
|
253
|
-
a.id = SecureRandom.uuid
|
254
|
-
a.command = SelfMsg::AclCommandLIST
|
255
|
-
|
256
|
-
@ws.send a
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
245
|
# Sends a message and waits for the response
|
261
246
|
#
|
262
247
|
# @params msg [SelfMsg::Message] message object to be sent
|
@@ -363,7 +348,7 @@ module SelfSDK
|
|
363
348
|
|
364
349
|
SelfSDK.logger.debug " - notifying by type"
|
365
350
|
SelfSDK.logger.debug " - #{message.typ}"
|
366
|
-
SelfSDK.logger.debug " - #{message}"
|
351
|
+
SelfSDK.logger.debug " - #{message.payload}"
|
367
352
|
SelfSDK.logger.debug " - #{@type_observer.keys.join(',')}"
|
368
353
|
|
369
354
|
# Return if there is no observer setup for this kind of message
|
@@ -382,12 +367,12 @@ module SelfSDK
|
|
382
367
|
|
383
368
|
def subscribe(type, &block)
|
384
369
|
type = @source.message_type(type) if type.is_a? Symbol
|
370
|
+
SelfSDK.logger.debug "Subscribing to messages by type: #{type}"
|
385
371
|
@type_observer[type] = { block: block }
|
386
372
|
end
|
387
373
|
|
388
374
|
private
|
389
375
|
|
390
|
-
|
391
376
|
# Cleans expired messages
|
392
377
|
def clean_timeouts
|
393
378
|
clean_observers
|
@@ -413,7 +398,6 @@ module SelfSDK
|
|
413
398
|
@ws.start
|
414
399
|
end
|
415
400
|
|
416
|
-
|
417
401
|
# Process an event when it arrives through the websocket connection.
|
418
402
|
def on_message(event)
|
419
403
|
data = event.data.pack('c*')
|
@@ -438,29 +422,13 @@ module SelfSDK
|
|
438
422
|
@messages[hdr.id][:response] = {error: e.error}
|
439
423
|
mark_as_acknowledged(hdr.id)
|
440
424
|
mark_as_arrived(hdr.id)
|
441
|
-
when SelfMsg::MsgTypeACL
|
442
|
-
SelfSDK.logger.debug "#{hdr.id} ACL received"
|
443
|
-
a = SelfMsg::Acl.new(data: data)
|
444
|
-
process_incomming_acl a
|
445
425
|
end
|
446
426
|
rescue TypeError
|
447
427
|
SelfSDK.logger.debug "invalid array message"
|
448
428
|
end
|
449
429
|
|
450
|
-
def process_incomming_acl(input)
|
451
|
-
list = JSON.parse(input.payload)
|
452
|
-
|
453
|
-
@messages['acl_list'][:response] = list
|
454
|
-
mark_as_arrived 'acl_list'
|
455
|
-
rescue StandardError => e
|
456
|
-
p "Error processing incoming ACL #{input.id} #{input.payload}"
|
457
|
-
SelfSDK.logger.debug e
|
458
|
-
SelfSDK.logger.debug e.backtrace
|
459
|
-
nil
|
460
|
-
end
|
461
|
-
|
462
430
|
def process_incomming_message(input)
|
463
|
-
message =
|
431
|
+
message = parse(input)
|
464
432
|
|
465
433
|
if @messages.include? message.id
|
466
434
|
message.validate! @messages[message.id][:original_message]
|
@@ -479,21 +447,17 @@ module SelfSDK
|
|
479
447
|
nil
|
480
448
|
end
|
481
449
|
|
482
|
-
def
|
450
|
+
def parse(input)
|
483
451
|
msg = SelfSDK::Messages.parse(input, self)
|
484
|
-
write_offset(input.offset)
|
485
|
-
# Avoid catching any other decryption errors.
|
486
452
|
msg
|
487
|
-
rescue SelfSDK::Messages::UnmappedMessage
|
488
|
-
# this is an ummapped message, let's ignore it but write the offset.
|
489
|
-
write_offset(input.offset)
|
453
|
+
rescue SelfSDK::Messages::UnmappedMessage
|
490
454
|
nil
|
491
455
|
end
|
492
456
|
|
493
457
|
# Authenticates current client on the websocket server.
|
494
458
|
def authenticate
|
495
459
|
@auth_id = SecureRandom.uuid if @auth_id.nil?
|
496
|
-
@offset =
|
460
|
+
@offset = @storage.account_offset
|
497
461
|
|
498
462
|
SelfSDK.logger.debug "authenticating with offset (#{@offset})"
|
499
463
|
|
@@ -529,23 +493,6 @@ module SelfSDK
|
|
529
493
|
end
|
530
494
|
end
|
531
495
|
|
532
|
-
def read_offset
|
533
|
-
return 0 unless File.exist? @offset_file
|
534
|
-
|
535
|
-
File.open(@offset_file, 'rb') do |f|
|
536
|
-
return f.read.to_i
|
537
|
-
end
|
538
|
-
end
|
539
|
-
|
540
|
-
def write_offset(offset)
|
541
|
-
File.open(@offset_file, 'wb') do |f|
|
542
|
-
f.flock(File::LOCK_EX)
|
543
|
-
f.write(offset.to_s.rjust(19, "0"))
|
544
|
-
end
|
545
|
-
SelfSDK.logger.debug "offset written #{offset}"
|
546
|
-
@offset = offset
|
547
|
-
end
|
548
|
-
|
549
496
|
def select_priority(mtype)
|
550
497
|
PRIORITIES[mtype] || SelfSDK::Messages::PRIORITY_VISIBLE
|
551
498
|
end
|
data/lib/selfsdk.rb
CHANGED
@@ -14,7 +14,6 @@ require_relative 'client'
|
|
14
14
|
require_relative 'messaging'
|
15
15
|
require_relative 'ntptime'
|
16
16
|
require_relative 'authenticated'
|
17
|
-
require_relative 'acl'
|
18
17
|
require_relative 'sources'
|
19
18
|
require_relative 'services/auth'
|
20
19
|
require_relative 'services/requester'
|
@@ -50,6 +49,7 @@ module SelfSDK
|
|
50
49
|
# @option opts [String] :messaging_url The messaging self provider url.
|
51
50
|
# @option opts [Bool] :auto_reconnect Automatically reconnects to websocket if connection is lost (defaults to true).
|
52
51
|
# @option opts [Symbol] :env The environment to be used, defaults to ":production".
|
52
|
+
# @option opts [String] :storage the library used to persist session data.
|
53
53
|
def initialize(app_id, app_key, storage_key, storage_dir, opts = {})
|
54
54
|
app_key = cleanup_key(app_key)
|
55
55
|
|
@@ -61,9 +61,12 @@ module SelfSDK
|
|
61
61
|
messaging_url = messaging_url(opts)
|
62
62
|
@started = false
|
63
63
|
unless messaging_url.nil?
|
64
|
+
device_id = opts.fetch(:device_id, MessagingClient::DEFAULT_DEVICE)
|
65
|
+
storage = opts.fetch(:storage, SelfSDK::Storage.new(@client.jwt.id, device_id, storage_dir, storage_key))
|
64
66
|
@messaging_client = MessagingClient.new(messaging_url,
|
65
67
|
@client,
|
66
68
|
storage_key,
|
69
|
+
storage,
|
67
70
|
storage_dir: storage_dir,
|
68
71
|
auto_reconnect: opts.fetch(:auto_reconnect, MessagingClient::DEFAULT_AUTO_RECONNECT),
|
69
72
|
device_id: opts.fetch(:device_id, MessagingClient::DEFAULT_DEVICE))
|
data/lib/services/messaging.rb
CHANGED
@@ -32,37 +32,6 @@ module SelfSDK
|
|
32
32
|
@client.subscribe(type, &block)
|
33
33
|
end
|
34
34
|
|
35
|
-
# Permits incoming messages from the a identity.
|
36
|
-
#
|
37
|
-
# @param [String] selfid to be allowed.
|
38
|
-
# @return [Boolean] success / failure
|
39
|
-
def permit_connection(selfid)
|
40
|
-
acl.allow selfid
|
41
|
-
end
|
42
|
-
|
43
|
-
# Lists app allowed connections.
|
44
|
-
# @return [Array] array of self ids allowed to connect to your app.
|
45
|
-
def allowed_connections
|
46
|
-
acl.list
|
47
|
-
end
|
48
|
-
|
49
|
-
# Checks if you're permitting messages from a specific self identifier
|
50
|
-
# @return [Boolean] yes|no
|
51
|
-
def is_permitted?(id)
|
52
|
-
conns = allowed_connections
|
53
|
-
return true if conns.include? "*"
|
54
|
-
return true if conns.include? id
|
55
|
-
return false
|
56
|
-
end
|
57
|
-
|
58
|
-
# Revokes incoming messages from the given identity.
|
59
|
-
#
|
60
|
-
# @param [String] selfid to be denied
|
61
|
-
# @return [Boolean] success / failure
|
62
|
-
def revoke_connection(selfid)
|
63
|
-
acl.deny selfid
|
64
|
-
end
|
65
|
-
|
66
35
|
# Gets the device id for the authenticated app.
|
67
36
|
#
|
68
37
|
# @return [String] device_id of the running app.
|
@@ -101,10 +70,6 @@ module SelfSDK
|
|
101
70
|
end
|
102
71
|
|
103
72
|
private
|
104
|
-
|
105
|
-
def acl
|
106
|
-
@acl ||= ACL.new(@client)
|
107
|
-
end
|
108
73
|
end
|
109
74
|
end
|
110
75
|
end
|
data/lib/services/requester.rb
CHANGED
@@ -48,9 +48,6 @@ module SelfSDK
|
|
48
48
|
def request(selfid, facts, opts = {}, &block)
|
49
49
|
SelfSDK.logger.info "authenticating #{selfid}"
|
50
50
|
rq = opts.fetch(:request, true)
|
51
|
-
if rq
|
52
|
-
raise "You're not permitting connections from #{selfid}" unless @messaging_service.is_permitted?(selfid)
|
53
|
-
end
|
54
51
|
|
55
52
|
req = SelfSDK::Messages::FactRequest.new(@messaging)
|
56
53
|
req.populate(selfid, prepare_facts(facts), opts)
|
data/lib/storage.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
# Copyright 2020 Self Group Ltd. All Rights Reserved.
|
2
|
+
require 'sqlite3'
|
3
|
+
|
4
|
+
module SelfSDK
|
5
|
+
class Storage
|
6
|
+
attr_accessor :app_id
|
7
|
+
|
8
|
+
def initialize(app_id, app_device, storage_folder, _key_id)
|
9
|
+
@app_id = sid(app_id, app_device)
|
10
|
+
|
11
|
+
# Create the storage folder if it does not exist
|
12
|
+
create_directory_skel("#{storage_folder}/identities/")
|
13
|
+
|
14
|
+
# Create the database
|
15
|
+
@db = SQLite3::Database.new(File.join("#{storage_folder}/identities/", 'self.db'))
|
16
|
+
set_pragmas
|
17
|
+
create_accounts_table
|
18
|
+
create_sessions_table
|
19
|
+
m = StorageMigrator.new(@db, "#{storage_folder}/apps", @app_id)
|
20
|
+
m.migrate
|
21
|
+
end
|
22
|
+
|
23
|
+
def tx
|
24
|
+
@db.transaction
|
25
|
+
yield
|
26
|
+
@db.commit
|
27
|
+
rescue SQLite3::Exception => e
|
28
|
+
@db.rollback
|
29
|
+
rescue => e
|
30
|
+
@db.rollback
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
|
34
|
+
def account_exists?
|
35
|
+
row = @db.execute("SELECT olm_account FROM accounts WHERE as_identifier = ?", [@app_id.encode("UTF-8")]).first
|
36
|
+
!row.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def account_create(olm)
|
40
|
+
@db.execute("INSERT INTO accounts (as_identifier, offset, olm_account) VALUES (?, ?, ?);", [ @app_id.encode("UTF-8"), 0, olm.encode("UTF-8") ])
|
41
|
+
rescue
|
42
|
+
end
|
43
|
+
|
44
|
+
def account_update(olm)
|
45
|
+
@db.execute("UPDATE accounts SET olm_account = ? WHERE as_identifier = ?", [ olm.encode("UTF-8"), @app_id.encode("UTF-8") ])
|
46
|
+
end
|
47
|
+
|
48
|
+
def account_olm
|
49
|
+
row = @db.execute("SELECT olm_account FROM accounts WHERE as_identifier = ?;", [@app_id.encode("UTF-8")]).first
|
50
|
+
return nil unless row
|
51
|
+
|
52
|
+
row.first
|
53
|
+
end
|
54
|
+
|
55
|
+
def account_offset
|
56
|
+
row = @db.execute("SELECT offset FROM accounts WHERE as_identifier = ?;", @app_id.encode("UTF-8")).first
|
57
|
+
return nil unless row
|
58
|
+
|
59
|
+
row.first
|
60
|
+
end
|
61
|
+
|
62
|
+
def account_set_offset(offset)
|
63
|
+
@db.execute("UPDATE accounts SET offset = ? WHERE as_identifier = ?;", [ offset, @app_id.encode("UTF-8") ])
|
64
|
+
end
|
65
|
+
|
66
|
+
def session_create(sid, olm)
|
67
|
+
@db.execute("INSERT INTO sessions (as_identifier, with_identifier, olm_session) VALUES (?, ?, ?);", [ @app_id.encode("UTF-8"), sid.encode("UTF-8"), olm.encode("UTF-8") ])
|
68
|
+
end
|
69
|
+
|
70
|
+
def session_update(sid, olm)
|
71
|
+
row = @db.execute("SELECT olm_session FROM sessions WHERE as_identifier = ? AND with_identifier = ?", [@app_id.encode("UTF-8"), sid.encode("UTF-8")]).first
|
72
|
+
if row.nil?
|
73
|
+
session_create(sid, olm)
|
74
|
+
else
|
75
|
+
@db.execute("UPDATE sessions SET olm_session = ? WHERE as_identifier = ? AND with_identifier = ?;", [ olm.encode("UTF-8"), @app_id.encode("UTF-8"), sid.encode("UTF-8") ])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def session_get_olm(sid)
|
80
|
+
row = @db.execute("SELECT olm_session FROM sessions WHERE as_identifier = ? AND with_identifier = ?", [@app_id.encode("UTF-8"), sid.encode("UTF-8")]).first
|
81
|
+
return nil if row.nil?
|
82
|
+
|
83
|
+
row.first
|
84
|
+
end
|
85
|
+
|
86
|
+
def sid(selfid, device)
|
87
|
+
"#{selfid}:#{device}"
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Create a folder if it does not exist
|
93
|
+
def create_directory_skel(storage_folder)
|
94
|
+
FileUtils.mkdir_p storage_folder unless File.exist? storage_folder
|
95
|
+
rescue Errno::ENOENT
|
96
|
+
raise ERR_INVALID_DIRECTORY
|
97
|
+
rescue
|
98
|
+
end
|
99
|
+
|
100
|
+
def set_pragmas
|
101
|
+
pragma_statement = <<~SQL
|
102
|
+
PRAGMA synchronous = NORMAL;
|
103
|
+
PRAGMA journal_mode = WAL;
|
104
|
+
PRAGMA temp_store = MEMORY;
|
105
|
+
SQL
|
106
|
+
|
107
|
+
@db.execute_batch(pragma_statement)
|
108
|
+
rescue SQLite3::Exception => e
|
109
|
+
puts "Exception occurred: #{e}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_accounts_table
|
113
|
+
accounts_table_statement = <<~SQL
|
114
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
115
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
116
|
+
as_identifier TEXT NOT NULL,
|
117
|
+
offset INTEGER NOT NULL,
|
118
|
+
olm_account BLOB NOT NULL
|
119
|
+
);
|
120
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_as_identifier
|
121
|
+
ON accounts (as_identifier);
|
122
|
+
SQL
|
123
|
+
|
124
|
+
@db.execute_batch(accounts_table_statement)
|
125
|
+
rescue SQLite3::Exception => e
|
126
|
+
puts "Exception occurred: #{e}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_sessions_table
|
130
|
+
# TODO we could deduplicate as_identifier and with_identifier here
|
131
|
+
# by creating a record for each on a new identifier table,
|
132
|
+
# but this is only temporary
|
133
|
+
session_table_statement = <<~SQL
|
134
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
135
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
136
|
+
as_identifier TEXT NOT NULL,
|
137
|
+
with_identifier TEXT NOT NULL,
|
138
|
+
olm_session BLOB NOT NULL
|
139
|
+
);
|
140
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_with_identifier
|
141
|
+
ON sessions (as_identifier, with_identifier);
|
142
|
+
SQL
|
143
|
+
|
144
|
+
@db.execute_batch(session_table_statement)
|
145
|
+
rescue SQLite3::Exception => e
|
146
|
+
puts "Exception occurred: #{e}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class StorageMigrator
|
151
|
+
def initialize(db, storage_folder, app_id)
|
152
|
+
@db = db
|
153
|
+
# Old versions of the sdk using that same storage folder shouldn't be affected in any way
|
154
|
+
|
155
|
+
@base_path = "#{storage_folder}/#{app_id.split(':').first}"
|
156
|
+
@app_id = app_id
|
157
|
+
end
|
158
|
+
|
159
|
+
def migrate
|
160
|
+
return unless File.exist?(@base_path)
|
161
|
+
|
162
|
+
# Parse the account information.
|
163
|
+
accounts = parse_accounts
|
164
|
+
|
165
|
+
persist_accounts(accounts)
|
166
|
+
|
167
|
+
# Depreciate the base path.
|
168
|
+
File.rename("#{@base_path}", "#{@base_path}-depreciated")
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def parse_accounts
|
174
|
+
accounts = {}
|
175
|
+
|
176
|
+
Dir.glob(File.join(@base_path, "**/*")).each do |path|
|
177
|
+
if File.directory?(path)
|
178
|
+
next
|
179
|
+
end
|
180
|
+
|
181
|
+
case File.extname(path)
|
182
|
+
when ".offset"
|
183
|
+
file_name = File.basename(path, ".offset")
|
184
|
+
offset = File.read(path)[0, 19].to_i
|
185
|
+
|
186
|
+
accounts[file_name] = {} unless accounts.key? file_name
|
187
|
+
accounts[file_name][:offset] = offset
|
188
|
+
when ".pickle"
|
189
|
+
file_name = File.basename(path, ".pickle")
|
190
|
+
content = File.read(path)
|
191
|
+
|
192
|
+
accounts[@app_id] = {} unless accounts.key? @app_id
|
193
|
+
if file_name == "account"
|
194
|
+
accounts[@app_id][:account] = content
|
195
|
+
else
|
196
|
+
if accounts.key? @app_id
|
197
|
+
accounts[@app_id][:sessions] = [] unless accounts[@app_id].key? :sessions
|
198
|
+
accounts[@app_id][:sessions] << {
|
199
|
+
with: file_name.sub("-session", ""),
|
200
|
+
session: content
|
201
|
+
}
|
202
|
+
next
|
203
|
+
end
|
204
|
+
accounts[@app_id][:account] = content
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
accounts
|
210
|
+
end
|
211
|
+
|
212
|
+
def persist_accounts(accounts)
|
213
|
+
@db.transaction
|
214
|
+
accounts.each do |inbox_id, account|
|
215
|
+
@db.execute(
|
216
|
+
"INSERT INTO accounts (as_identifier, offset, olm_account) VALUES ($1, $2, $3)",
|
217
|
+
[inbox_id, account[:offset], account[:account]]
|
218
|
+
)
|
219
|
+
|
220
|
+
account[:sessions].each do |session|
|
221
|
+
@db.execute(
|
222
|
+
"INSERT INTO sessions (as_identifier, with_identifier, olm_session) VALUES ($1, $2, $3)",
|
223
|
+
[inbox_id, session[:with], session[:session]]
|
224
|
+
)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
@db.commit
|
229
|
+
rescue SQLite3::Exception => e
|
230
|
+
puts "Exception occurred"
|
231
|
+
puts e
|
232
|
+
puts e.backtrace
|
233
|
+
@db.rollback
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: selfsdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.218
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Self Group Ltd.
|
@@ -178,6 +178,20 @@ dependencies:
|
|
178
178
|
- - ">="
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: sqlite3
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
181
195
|
- !ruby/object:Gem::Dependency
|
182
196
|
name: bundler
|
183
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -350,7 +364,6 @@ executables: []
|
|
350
364
|
extensions: []
|
351
365
|
extra_rdoc_files: []
|
352
366
|
files:
|
353
|
-
- lib/acl.rb
|
354
367
|
- lib/authenticated.rb
|
355
368
|
- lib/chat/file_object.rb
|
356
369
|
- lib/chat/group.rb
|
@@ -396,6 +409,7 @@ files:
|
|
396
409
|
- lib/signature_graph.rb
|
397
410
|
- lib/source_definition.rb
|
398
411
|
- lib/sources.rb
|
412
|
+
- lib/storage.rb
|
399
413
|
homepage: https://www.joinself.com/
|
400
414
|
licenses:
|
401
415
|
- MIT
|
data/lib/acl.rb
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
# Copyright 2020 Self Group Ltd. All Rights Reserved.
|
2
|
-
|
3
|
-
# frozen_string_literal: true
|
4
|
-
|
5
|
-
require 'date'
|
6
|
-
|
7
|
-
# Namespace for classes and modules that handle Self interactions.
|
8
|
-
module SelfSDK
|
9
|
-
# Access control list
|
10
|
-
class ACL
|
11
|
-
def initialize(messaging)
|
12
|
-
@messaging = messaging
|
13
|
-
@jwt = @messaging.jwt
|
14
|
-
@acl_rules = []
|
15
|
-
end
|
16
|
-
|
17
|
-
# Lists allowed connections.
|
18
|
-
def list
|
19
|
-
SelfSDK.logger.info "Listing allowed connections"
|
20
|
-
@acl_rules = @messaging.list_acl_rules if @acl_rules.empty?
|
21
|
-
@acl_rules
|
22
|
-
end
|
23
|
-
|
24
|
-
# Allows incomming messages from the given identity.
|
25
|
-
def allow(id)
|
26
|
-
@acl_rules << id
|
27
|
-
SelfSDK.logger.info "Allowing connections from #{id}"
|
28
|
-
payload = @jwt.prepare(jti: SecureRandom.uuid,
|
29
|
-
cid: SecureRandom.uuid,
|
30
|
-
typ: 'acl.permit',
|
31
|
-
iss: @jwt.id,
|
32
|
-
sub: @jwt.id,
|
33
|
-
iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
|
34
|
-
exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
|
35
|
-
acl_source: id,
|
36
|
-
acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339)
|
37
|
-
|
38
|
-
a = SelfMsg::Acl.new
|
39
|
-
a.id = SecureRandom.uuid
|
40
|
-
a.command = SelfMsg::AclCommandPERMIT
|
41
|
-
a.payload = payload
|
42
|
-
|
43
|
-
@messaging.send_message a
|
44
|
-
end
|
45
|
-
|
46
|
-
# Deny incomming messages from the given identity.
|
47
|
-
def deny(id)
|
48
|
-
@acl_rules.delete(id)
|
49
|
-
SelfSDK.logger.info "Denying connections from #{id}"
|
50
|
-
payload = @jwt.prepare(jti: SecureRandom.uuid,
|
51
|
-
cid: SecureRandom.uuid,
|
52
|
-
typ: 'acl.revoke',
|
53
|
-
iss: @jwt.id,
|
54
|
-
sub: @jwt.id,
|
55
|
-
iat: (SelfSDK::Time.now - 5).strftime('%FT%TZ'),
|
56
|
-
exp: (SelfSDK::Time.now + 60).strftime('%FT%TZ'),
|
57
|
-
acl_source: id,
|
58
|
-
acl_exp: (SelfSDK::Time.now + 360_000).to_datetime.rfc3339)
|
59
|
-
|
60
|
-
a = SelfMsg::Acl.new
|
61
|
-
a.id = SecureRandom.uuid
|
62
|
-
a.command = SelfMsg::AclCommandREVOKE
|
63
|
-
a.payload = payload
|
64
|
-
@messaging.send_message a
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|