selfsdk 0.0.179 → 0.0.184

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f79c82e418ad27688b7d9f0f2635937a69f7c805a71611a02529a8d10ced9709
4
- data.tar.gz: aa77a30f37b6a71acf1aeed6a77112ca08505f91598b462962fb68f45e483c31
3
+ metadata.gz: 2c16f1734a708b0ebd37ca4fa0cea0e48db09344d4efb0654e5b4df6a37fd2d4
4
+ data.tar.gz: 32313fa1c28a57370c43d7100bdacc06bad28f0722ba0589a63c24bd0327bfd2
5
5
  SHA512:
6
- metadata.gz: ef6bb03cab8de8e44af7fad1f19a623345c6cd6fe00a4c1e0f35ab526e6b6523e2d8cd668c66d17cd85b5cf254f31a9e0dad4bbfc9c0a4301c3d67a044910d2f
7
- data.tar.gz: e7d3ce5591ea749537a6a2ee580b240b7033a6b4f6f2ba8fa3d64d11814d722844862e40a06c495a447bda01888be2f9b4e085fedb5534a5a89a75c7cfa4e965
6
+ metadata.gz: 9a015baa1ed364efcc422af5de6a54f87cbb953cdb45de7f397aa8cd2273b99c8ce4f6fbba0fc62f0ef803f38edf8155fbb1e9e14042b4f4ff76b391147da614
7
+ data.tar.gz: dfec1973db212e016774292d57bda452727cadd201ecdb80ab0f507a97ff3df244887ffde06704a618de63e0d101c75b7e8c879ff983654892d9496a50db7889
@@ -0,0 +1,97 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+ require 'open-uri'
5
+
6
+ module SelfSDK
7
+ module Chat
8
+ class FileObject
9
+ attr_accessor :link, :mime, :content, :key, :nonce, :ciphertext
10
+
11
+ def initialize(token, url)
12
+ @token = token
13
+ @url = url
14
+ end
15
+
16
+ def build_from_data(name, data, mime)
17
+ @key = SelfCrypto::Util.aead_xchacha20poly1305_ietf_keygen
18
+ @nonce = SelfCrypto::Util.aead_xchacha20poly1305_ietf_nonce
19
+ @content = data
20
+ @name = name
21
+ @mime = mime
22
+
23
+ # encrypt the object
24
+ @ciphertext = SelfCrypto::Util.aead_xchacha20poly1305_ietf_encrypt(@key, @nonce, @content)
25
+
26
+ # Upload
27
+ remote_object = upload(ciphertext)
28
+ @link = "#{@url}/v1/objects/#{remote_object["id"]}"
29
+ @expires = remote_object["expires"]
30
+
31
+ self
32
+ end
33
+
34
+ # Incoming objects
35
+ def build_from_object(input)
36
+ # Download from CDN
37
+ ciphertext = URI.open(input[:link], "Authorization" => "Bearer #{@token}").read
38
+
39
+ @content = ciphertext
40
+ @key = nil
41
+ @nonce = nil
42
+ if input.key?(:key) && !input[:key].empty?
43
+ # Decrypt
44
+ composed_key = extract_key(input[:key])
45
+ @key = composed_key[:key]
46
+ @nonce = composed_key[:nonce]
47
+
48
+ @content = SelfCrypto::Util.aead_xchacha20poly1305_ietf_decrypt(@key, @nonce, ciphertext)
49
+ end
50
+
51
+ @name = input[:name]
52
+ @link = input[:link]
53
+ @mime = input[:mime]
54
+ @expires = input[:expires]
55
+
56
+ self
57
+ end
58
+
59
+ def to_payload
60
+ {
61
+ name: @name,
62
+ link: @link,
63
+ key: build_key(@key, @nonce),
64
+ mime: @mime,
65
+ expires: @expires
66
+ }
67
+ end
68
+
69
+ def save(path)
70
+ File.open(path, 'wb') { |file| file.write(@content) }
71
+ end
72
+
73
+ private
74
+
75
+ def upload(ciphertext)
76
+ uri = URI.parse("#{@url}/v1/objects")
77
+ https = Net::HTTP.new(uri.host, uri.port)
78
+ https.use_ssl = true
79
+ req = Net::HTTP::Post.new(uri.path)
80
+ req["Authorization"] = "Bearer #{@token}"
81
+ req.body = ciphertext.force_encoding("UTF-8")
82
+ res = https.request(req)
83
+ JSON.parse(res.body)
84
+ end
85
+
86
+ def build_key(key, nonce)
87
+ Base64.urlsafe_encode64("#{key}#{nonce}", padding: false)
88
+ end
89
+
90
+ def extract_key(shareable_key)
91
+ k = Base64.urlsafe_decode64(shareable_key)
92
+ { key: k[0, 32],
93
+ nonce: k[32, (k.length - 32)] }
94
+ end
95
+ end
96
+ end
97
+ end
data/lib/chat/group.rb ADDED
@@ -0,0 +1,51 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module SelfSDK
6
+ module Chat
7
+ class Group
8
+ attr_accessor :gid, :name, :members, :payload
9
+
10
+ def initialize(chat, payload)
11
+ @chat = chat
12
+ @payload = payload
13
+ @gid = payload[:gid]
14
+ @members = payload[:members]
15
+ @name = payload[:name]
16
+ @link = payload[:link] if payload.key? :link
17
+ @key = payload[:key] if payload.key? :key
18
+ @mime = payload[:mime] if payload.key? :mime
19
+ end
20
+
21
+ # Sends an invitation to the specified user to join
22
+ # the group.
23
+ #
24
+ # @param user [String] user to be invited.
25
+ def invite(user)
26
+ raise "invalid input" if user.empty?
27
+
28
+ @members << user
29
+ @chat.invite(@gid, @name, @members)
30
+ end
31
+
32
+ # Sends a message to leave the current group.
33
+ def leave
34
+ @chat.leave(@gid, @members)
35
+ end
36
+
37
+ # Sends a confi¡rmation message that has joined the group.
38
+ def join
39
+ @chat.join(@gid, @members)
40
+ end
41
+
42
+ # Sends a message to the current group
43
+ #
44
+ # @param body [String] message body to be sent.
45
+ def message(body, opts = {})
46
+ opts[:gid] = @gid
47
+ @chat.message(@members, body, opts)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,101 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module SelfSDK
6
+ module Chat
7
+ class Message
8
+ attr_accessor :gid, :body, :from, :payload, :recipients, :objects
9
+
10
+ def initialize(chat, recipients, payload, auth_token, self_url)
11
+ @chat = chat
12
+ @recipients = recipients
13
+ @recipients = [@recipients] if @recipients.is_a? String
14
+ @gid = payload[:gid] if payload.key? :gid
15
+ @payload = payload
16
+ @payload[:jti] = SecureRandom.uuid unless @payload.include?(:jti)
17
+ @body = @payload[:msg]
18
+ @from = @payload[:iss]
19
+ return unless @payload.key?(:objects)
20
+
21
+ @objects = []
22
+ @payload[:objects].each do |o|
23
+ @objects << if o.key? :link
24
+ SelfSDK::Chat::FileObject.new(auth_token, self_url).build_from_object(o)
25
+ else
26
+ SelfSDK::Chat::FileObject.new(auth_token, self_url).build_from_data(o[:name], o[:data], o[:mime])
27
+ end
28
+ end
29
+ @payload[:objects] = []
30
+ @payload[:objects] = payload[:raw_objects] if payload[:raw_objects]
31
+ @objects.each do |o|
32
+ @payload[:objects] << o.to_payload
33
+ end
34
+ end
35
+
36
+ # delete! deletes the current message from the conversation.
37
+ def delete!
38
+ @chat.delete(@recipients, @payload[:jti], @payload[:gid])
39
+ end
40
+
41
+ # edit changes the current message body for all participants.
42
+ #
43
+ # @param body [string] the new message body.
44
+ def edit(body)
45
+ return if @recipients == [@chat.app_id]
46
+
47
+ @body = body
48
+ @chat.edit(@recipients, @payload[:jti], body, @payload[:gid])
49
+ end
50
+
51
+ # mark_as_delivered marks the current message as delivered if
52
+ # it comes from another recipient.
53
+ def mark_as_delivered
54
+ return if @recipients != [@chat.app_id]
55
+
56
+ @chat.delivered(@payload[:iss], @payload[:jti], @payload[:gid])
57
+ end
58
+
59
+ # mark_as_read marks the current message as read if it comes from
60
+ # another recipient.
61
+ def mark_as_read
62
+ return if @recipients != [@chat.app_id]
63
+
64
+ @chat.read(@payload[:iss], @payload[:jti], @payload[:gid])
65
+ end
66
+
67
+ # respond sends a direct response to the current message.
68
+ #
69
+ # @param body [string] the new message body.
70
+ #
71
+ # @return ChatMessage
72
+ def respond(body)
73
+ opts = {}
74
+ opts[:aud] = @payload[:gid] if @payload.key? :gid
75
+ opts[:gid] = @payload[:gid] if @payload.key? :gid
76
+ opts[:rid] = @payload[:jti]
77
+
78
+ to = @recipients
79
+ to = [@payload[:iss]] if @recipients == [@chat.app_id]
80
+
81
+ @chat.message(to, body, opts)
82
+ end
83
+
84
+ # message sends a new message to the same conversation as the current message.
85
+ #
86
+ # @param body [string] the new message body.
87
+ #
88
+ # @return ChatMessage
89
+ def message(body, opts = {})
90
+ opts[:aud] = @payload[:gid] if @payload.key? :gid
91
+ opts[:gid] = @payload[:gid] if @payload.key? :gid
92
+
93
+ to = opts[:recipients] if opts.key? :recipients
94
+ to = [@payload[:iss]] if @recipients == [@chat.app_id]
95
+
96
+ @chat.message(to, body, opts)
97
+ end
98
+
99
+ end
100
+ end
101
+ end
data/lib/crypto.rb CHANGED
@@ -32,9 +32,82 @@ module SelfSDK
32
32
  end
33
33
  end
34
34
 
35
- def encrypt(message, recipient, recipient_device)
36
- session_file_name = session_path(recipient, recipient_device)
35
+ def encrypt(message, recipients)
36
+ ::SelfSDK.logger.debug('encrypting a message')
37
37
 
38
+ # create a group session and set the identity of the account youre using
39
+ ::SelfSDK.logger.debug('create a group session and set the identity of the account youre using')
40
+ gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
41
+
42
+ sessions = {}
43
+ ::SelfSDK.logger.debug('managing sessions with all recipients')
44
+ recipients.each do |r|
45
+ session_file_name = session_path(r[:id], r[:device_id])
46
+ session_with_bob = nil
47
+
48
+ begin
49
+ session_with_bob = get_outbound_session_with_bob(r[:id], r[:device_id], session_file_name)
50
+ rescue => e
51
+ ::SelfSDK.logger.warn(" there is a problem adding group participant #{r[:id]}:#{r[:device_id]}, skipping...")
52
+ ::SelfSDK.logger.warn(e)
53
+ next
54
+ end
55
+
56
+ ::SelfSDK.logger.debug(" adding group participant #{r[:id]}:#{r[:device_id]}")
57
+ gs.add_participant("#{r[:id]}:#{r[:device_id]}", session_with_bob)
58
+ sessions[session_file_name] = session_with_bob
59
+ end
60
+
61
+ # 5) encrypt a message
62
+ ::SelfSDK.logger.debug("group encrypting message")
63
+ ct = gs.encrypt(message).to_s
64
+
65
+ # 6) store the session to a file
66
+ ::SelfSDK.logger.debug("storing sessions")
67
+ sessions.each do |session_file_name, session_with_bob|
68
+ File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
69
+ end
70
+
71
+ ct
72
+ end
73
+
74
+ def decrypt(message, sender, sender_device)
75
+ ::SelfSDK.logger.debug("decrypting a message")
76
+ session_file_name = session_path(sender, sender_device)
77
+
78
+ ::SelfSDK.logger.debug("loading sessions")
79
+ session_with_bob = get_inbound_session_with_bob(message, session_file_name)
80
+
81
+ # 8) create a group session and set the identity of the account you're using
82
+ ::SelfSDK.logger.debug("create a group session and set the identity of the account #{@client.jwt.id}:#{@device}")
83
+ gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
84
+
85
+ # 9) add all recipients and their sessions
86
+ ::SelfSDK.logger.debug("add all recipients and their sessions #{@sender}:#{@sender_device}")
87
+ gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
88
+
89
+ # 10) decrypt the message ciphertext
90
+ ::SelfSDK.logger.debug("decrypt the message ciphertext")
91
+ pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
92
+
93
+ # 11) store the session to a file
94
+ ::SelfSDK.logger.debug("store the session to a file")
95
+ File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
96
+
97
+ pt
98
+ end
99
+
100
+ private
101
+
102
+ def account_path
103
+ "#{@storage_folder}/account.pickle"
104
+ end
105
+
106
+ def session_path(selfid, device)
107
+ "#{@storage_folder}/#{selfid}:#{device}-session.pickle"
108
+ end
109
+
110
+ def get_outbound_session_with_bob(recipient, recipient_device, session_file_name)
38
111
  if File.exist?(session_file_name)
39
112
  # 2a) if bob's session file exists load the pickle from the file
40
113
  session_with_bob = SelfCrypto::Session.from_pickle(File.read(session_file_name), @storage_key)
@@ -61,24 +134,10 @@ module SelfSDK
61
134
  session_with_bob = @account.outbound_session(curve25519_identity_key, one_time_key)
62
135
  end
63
136
 
64
- # 3) create a group session and set the identity of the account youre using
65
- gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
66
-
67
- # 4) add all recipients and their sessions
68
- gs.add_participant("#{recipient}:#{recipient_device}", session_with_bob)
69
-
70
- # 5) encrypt a message
71
- ct = gs.encrypt(message).to_s
72
-
73
- # 6) store the session to a file
74
- File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
75
-
76
- ct
137
+ session_with_bob
77
138
  end
78
139
 
79
- def decrypt(message, sender, sender_device)
80
- session_file_name = session_path(sender, sender_device)
81
-
140
+ def get_inbound_session_with_bob(message, session_file_name)
82
141
  if File.exist?(session_file_name)
83
142
  # 7a) if carol's session file exists load the pickle from the file
84
143
  session_with_bob = SelfCrypto::Session.from_pickle(File.read(session_file_name), @storage_key)
@@ -113,29 +172,8 @@ module SelfSDK
113
172
  File.write(account_path, @account.to_pickle(@storage_key))
114
173
  end
115
174
 
116
- # 8) create a group session and set the identity of the account you're using
117
- gs = SelfCrypto::GroupSession.new("#{@client.jwt.id}:#{@device}")
118
-
119
- # 9) add all recipients and their sessions
120
- gs.add_participant("#{sender}:#{sender_device}", session_with_bob)
121
-
122
- # 10) decrypt the message ciphertext
123
- pt = gs.decrypt("#{sender}:#{sender_device}", message).to_s
124
-
125
- # 11) store the session to a file
126
- File.write(session_file_name, session_with_bob.to_pickle(@storage_key))
127
-
128
- pt
175
+ session_with_bob
129
176
  end
130
177
 
131
- private
132
-
133
- def account_path
134
- "#{@storage_folder}/account.pickle"
135
- end
136
-
137
- def session_path(selfid, device)
138
- "#{@storage_folder}/#{selfid}:#{device}-session.pickle"
139
- end
140
178
  end
141
179
  end
@@ -43,10 +43,10 @@ module SelfSDK
43
43
 
44
44
  def proto(to_device)
45
45
  m = SelfMsg::Message.new
46
- m.id = SecureRandom.uuid
46
+ m.id = @id
47
47
  m.sender = "#{@jwt.id}:#{@messaging.device_id}"
48
48
  m.recipient = "#{@to}:#{to_device}"
49
- m.ciphertext = encrypt_message(@jwt.prepare(body), @to, to_device)
49
+ m.ciphertext = encrypt_message(@jwt.prepare(body), [{ id: @to, device_id: to_device }])
50
50
  m
51
51
  end
52
52
 
data/lib/messages/base.rb CHANGED
@@ -10,6 +10,7 @@ module SelfSDK
10
10
  :description, :sub, :exp_timeout
11
11
 
12
12
  def initialize(messaging)
13
+ @intermediary = nil
13
14
  @client = messaging.client
14
15
  @jwt = @client.jwt
15
16
  @messaging = messaging
@@ -44,8 +45,8 @@ module SelfSDK
44
45
  res.first
45
46
  end
46
47
 
47
- def encrypt_message(message, recipient, recipient_device)
48
- @messaging.encryption_client.encrypt(message, recipient, recipient_device)
48
+ def encrypt_message(message, recipients)
49
+ @messaging.encryption_client.encrypt(message, recipients)
49
50
  end
50
51
 
51
52
  def unauthorized?
@@ -0,0 +1,28 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'self_msgproto'
6
+ require_relative 'base'
7
+ require_relative '../ntptime'
8
+
9
+ module SelfSDK
10
+ module Messages
11
+ class Chat < Base
12
+ def parse(input, envelope=nil)
13
+ @input = input
14
+ @payload = get_payload input
15
+ @id = @payload[:jti]
16
+ @from = @payload[:iss]
17
+ @to = @payload[:sub]
18
+ @audience = payload[:aud]
19
+ @expires = @payload[:exp]
20
+
21
+ if envelope
22
+ issuer = envelope.sender.split(":")
23
+ @from_device = issuer.last
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatInvite < Chat
10
+ MSG_TYPE = "chat.invite"
11
+
12
+ def parse(input, envelope=nil)
13
+ super
14
+ @typ = MSG_TYPE
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatJoin < Chat
10
+ MSG_TYPE = "chat.join"
11
+
12
+ def parse(input, envelope=nil)
13
+ super
14
+ @typ = MSG_TYPE
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatMessage < Chat
10
+ MSG_TYPE = "chat.message"
11
+ DEFAULT_EXP_TIMEOUT = 900
12
+
13
+ attr_accessor :body
14
+
15
+ def parse(input, envelope=nil)
16
+ super
17
+ @typ = MSG_TYPE
18
+ @body = @payload[:msg]
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'self_msgproto'
6
+ require_relative 'chat'
7
+ require_relative '../ntptime'
8
+
9
+ module SelfSDK
10
+ module Messages
11
+ class ChatMessageDelivered < Chat
12
+ MSG_TYPE = "chat.message.delivered"
13
+ DEFAULT_EXP_TIMEOUT = 900
14
+
15
+ def parse(input, envelope=nil)
16
+ super
17
+ @typ = MSG_TYPE
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatMessageRead < Chat
10
+ MSG_TYPE = "chat.message.read"
11
+ DEFAULT_EXP_TIMEOUT = 900
12
+
13
+ def parse(input, envelope=nil)
14
+ super
15
+ @typ = MSG_TYPE
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'chat'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class ChatRemove < Chat
10
+ MSG_TYPE = "chat.remove"
11
+
12
+ def parse(input, envelope=nil)
13
+ super
14
+ @typ = MSG_TYPE
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -34,6 +34,7 @@ module SelfSDK
34
34
  @options = opts.fetch(:options, false)
35
35
  @description = opts.include?(:description) ? opts[:description] : nil
36
36
  @exp_timeout = opts.fetch(:exp_timeout, DEFAULT_EXP_TIMEOUT)
37
+ @allowed_for = opts.fetch(:allowed_for, nil)
37
38
 
38
39
  @intermediary = if opts.include?(:intermediary)
39
40
  opts[:intermediary]
@@ -89,6 +90,7 @@ module SelfSDK
89
90
  }
90
91
  b[:options] = @options unless (@options.nil? || @options == false)
91
92
  b[:description] = @description unless (@description.nil? || @description.empty?)
93
+ b[:allowed_until] = (SelfSDK::Time.now + @allowed_for).strftime('%FT%TZ') unless @allowed_for.nil?
92
94
  b
93
95
  end
94
96
 
@@ -98,14 +100,14 @@ module SelfSDK
98
100
  @to_device = to_device
99
101
  if @intermediary.nil?
100
102
  recipient = "#{@to}:#{@to_device}"
101
- ciphertext = encrypt_message(@jwt.prepare(body), @to, @to_device)
103
+ ciphertext = encrypt_message(@jwt.prepare(body), [{id: @to, device_id: @to_device}])
102
104
  else
103
105
  recipient = "#{@intermediary}:#{@to_device}"
104
- ciphertext = encrypt_message(@jwt.prepare(body), @intermediary, @to_device)
106
+ ciphertext = encrypt_message(@jwt.prepare(body), [{id: @intermediary, device_id: @to_device}])
105
107
  end
106
108
 
107
109
  m = SelfMsg::Message.new
108
- m.id = SecureRandom.uuid
110
+ m.id = @id
109
111
  m.sender = "#{@jwt.id}:#{@messaging.device_id}"
110
112
  m.recipient = recipient
111
113
  m.ciphertext = ciphertext
@@ -91,7 +91,7 @@ module SelfSDK
91
91
  id: SecureRandom.uuid,
92
92
  sender: "#{@jwt.id}:#{@messaging.device_id}",
93
93
  recipient: "#{@to}:#{@to_device}",
94
- ciphertext: encrypt_message(@jwt.prepare(body), @to, @to_device)
94
+ ciphertext: encrypt_message(@jwt.prepare(body), [{id: @to, device_id: @to_device}])
95
95
  )
96
96
  end
97
97
  end
@@ -6,6 +6,12 @@ require_relative "fact_request"
6
6
  require_relative "fact_response"
7
7
  require_relative "authentication_resp"
8
8
  require_relative "authentication_req"
9
+ require_relative "chat_message"
10
+ require_relative "chat_message_read"
11
+ require_relative "chat_message_delivered"
12
+ require_relative "chat_invite"
13
+ require_relative "chat_join"
14
+ require_relative "chat_remove"
9
15
 
10
16
  module SelfSDK
11
17
  module Messages
@@ -35,8 +41,26 @@ module SelfSDK
35
41
  when "identities.authenticate.req"
36
42
  m = AuthenticationReq.new(messaging)
37
43
  m.parse(body, envelope)
44
+ when SelfSDK::Messages::ChatMessage::MSG_TYPE
45
+ m = ChatMessage.new(messaging)
46
+ m.parse(body, envelope)
47
+ when SelfSDK::Messages::ChatMessageDelivered::MSG_TYPE
48
+ m = ChatMessageDelivered.new(messaging)
49
+ m.parse(body, envelope)
50
+ when SelfSDK::Messages::ChatMessageRead::MSG_TYPE
51
+ m = ChatMessageRead.new(messaging)
52
+ m.parse(body, envelope)
53
+ when SelfSDK::Messages::ChatInvite::MSG_TYPE
54
+ m = ChatInvite.new(messaging)
55
+ m.parse(body, envelope)
56
+ when SelfSDK::Messages::ChatRemove::MSG_TYPE
57
+ m = ChatRemove.new(messaging)
58
+ m.parse(body, envelope)
59
+ when SelfSDK::Messages::ChatJoin::MSG_TYPE
60
+ m = ChatJoin.new(messaging)
61
+ m.parse(body, envelope)
38
62
  else
39
- raise StandardError.new("Invalid message type.")
63
+ raise StandardError.new("Invalid message type #{payload[:typ]}.")
40
64
  end
41
65
  return m
42
66
  end
data/lib/messaging.rb CHANGED
@@ -86,38 +86,56 @@ module SelfSDK
86
86
  m.id = SecureRandom.uuid
87
87
  m.sender = "#{@jwt.id}:#{@device_id}"
88
88
  m.recipient = "#{recipient}:#{recipient_device}"
89
+ # TODO: this is unencrypted!!!
89
90
  m.ciphertext = @jwt.prepare(request)
90
91
 
91
92
  send_message m
92
93
  end
93
94
 
95
+ def session?(identifier, device)
96
+ path = @encryption_client.session_path(identifier, device)
97
+ File.file?(path)
98
+ end
99
+
94
100
  # Send custom mmessage
95
101
  #
96
102
  # @param recipient [string] selfID to be requested
97
103
  # @param type [string] message type
98
104
  # @param request [hash] original message requesing information
99
- def send_custom(recipient, request_body)
100
- @client.devices(recipient).each do |to_device|
105
+ def send_custom(recipients, request_body)
106
+ # convert argument into an array if is a string
107
+ recipients = [recipients] if recipients.is_a? String
108
+
109
+ # send to current identity devices except the current one.
110
+ recipients |= [@jwt.id]
111
+
112
+ # build recipients list
113
+ recs = []
114
+ recipients.each do |r|
115
+ @client.devices(r).each do |to_device|
116
+ recs << { id: r, device_id: to_device }
117
+ end
118
+ end
119
+
120
+ SelfSDK.logger.info "sending custom message #{request_body.to_json}"
121
+ current_device = "#{@jwt.id}:#{@device_id}"
122
+
123
+ recs.each do |r|
124
+ next if current_device == "#{r[:id]}:#{r[:device_id]}"
125
+
126
+ request_body[:sub] = r[:id]
127
+ request_body[:aud] = r[:id] unless request_body.key?(:aud)
128
+ ciphertext = @encryption_client.encrypt(@jwt.prepare(request_body), recs)
129
+
101
130
  m = SelfMsg::Message.new
102
131
  m.id = SecureRandom.uuid
103
- m.sender = "#{@jwt.id}:#{@device_id}"
104
- m.recipient = "#{recipient}:#{to_device}"
105
- m.ciphertext = @jwt.prepare(request_body)
132
+ m.sender = current_device
133
+ m.recipient = "#{r[:id]}:#{r[:device_id]}"
134
+ m.ciphertext = ciphertext
106
135
 
136
+ SelfSDK.logger.info " -> to #{m.recipient}"
107
137
  send_message m
108
138
  end
109
-
110
- @client.devices(@jwt.id).each do |to_device|
111
- if to_device != @device_id
112
- m = SelfMsg::Message.new
113
- m.id = SecureRandom.uuid
114
- m.sender = "#{@jwt.id}:#{@device_id}"
115
- m.recipient = "#{recipient}:#{to_device}"
116
- m.ciphertext = @jwt.prepare(request_body)
117
-
118
- send_message m
119
- end
120
- end
121
139
  end
122
140
 
123
141
  # Allows incomming messages from the given identity
@@ -159,6 +177,7 @@ module SelfSDK
159
177
  #
160
178
  # @params msg [SelfMsg::Message] message object to be sent
161
179
  def send_and_wait_for_response(msgs, original)
180
+ SelfSDK.logger.info "sending/wait for #{msgs.first.id}"
162
181
  wait_for msgs.first.id, original do
163
182
  msgs.each do |msg|
164
183
  send_message msg
@@ -414,7 +433,7 @@ module SelfSDK
414
433
  rescue StandardError => e
415
434
  p "Error processing incoming message #{input.to_json}"
416
435
  SelfSDK.logger.info e
417
- p e.backtrace
436
+ # p e.backtrace
418
437
  nil
419
438
  end
420
439
 
data/lib/selfsdk.rb CHANGED
@@ -20,6 +20,7 @@ require_relative 'services/auth'
20
20
  require_relative 'services/facts'
21
21
  require_relative 'services/identity'
22
22
  require_relative 'services/messaging'
23
+ require_relative 'services/chat'
23
24
 
24
25
  # Namespace for classes and modules that handle Self interactions.
25
26
  module SelfSDK
@@ -82,6 +83,11 @@ module SelfSDK
82
83
  @messaging ||= SelfSDK::Services::Messaging.new(@messaging_client)
83
84
  end
84
85
 
86
+ # Provides access to SelfSDK::Services::Chat service
87
+ def chat
88
+ @chat ||= SelfSDK::Services::Chat.new(messaging, identity)
89
+ end
90
+
85
91
  def app_id
86
92
  client.jwt.id
87
93
  end
@@ -0,0 +1,210 @@
1
+ # Copyright 2020 Self Group Ltd. All Rights Reserved.
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'self_crypto'
6
+ require_relative '../chat/file_object'
7
+ require_relative '../chat/group'
8
+ require_relative '../chat/message'
9
+ module SelfSDK
10
+ module Services
11
+ class Chat
12
+ attr_accessor :app_id
13
+
14
+ def initialize(messaging, client)
15
+ @messaging = messaging
16
+ @client = client
17
+ @app_id = @messaging.client.client.jwt.id
18
+ @auth_token = @messaging.client.client.jwt.auth_token
19
+ @self_url = @messaging.client.client.self_url
20
+ end
21
+
22
+ # Sends a message to a list of recipients.
23
+ #
24
+ # @param recipients [array] list of recipients to send the message to.
25
+ # @param body [string] the message content to be sent
26
+ def message(recipients, body, opts = {})
27
+ payload = {
28
+ typ: "chat.message",
29
+ msg: body,
30
+ }
31
+ payload[:jti] = opts[:jti] if opts.key? :jti
32
+ payload[:aud] = opts[:gid] if opts.key? :gid
33
+ payload[:gid] = opts[:gid] if opts.key? :gid
34
+ payload[:rid] = opts[:rid] if opts.key? :rid
35
+ payload[:objects] = opts[:objects] if opts.key? :objects
36
+
37
+ m = SelfSDK::Chat::Message.new(self, recipients, payload, @auth_token, @self_url)
38
+ _req = send(m.recipients, m.payload)
39
+
40
+ m
41
+ end
42
+
43
+ # Subscribes to an incoming chat message
44
+ def on_message(opts = {}, &block)
45
+ @messaging.subscribe :chat_message do |msg|
46
+ cm = SelfSDK::Chat::Message.new(self, msg.payload[:aud], msg.payload, @auth_token, @self_url)
47
+
48
+ cm.mark_as_delivered unless opts[:mark_as_delivered] == false
49
+ cm.mark_as_read if opts[:mark_as_read] == true
50
+
51
+ block.call(cm)
52
+ end
53
+ end
54
+
55
+ # Sends a message to confirm a list of messages (identified by it's cids)
56
+ # have been delivered.
57
+ #
58
+ # @param recipients [array] list of recipients to send the message to.
59
+ # @param cids [array] list of message cids to be marked as delivered.
60
+ # @param gid [string] group id where the conversation ids are referenced.
61
+ def delivered(recipients, cids, gid = nil)
62
+ confirm('delivered', recipients, cids, gid)
63
+ end
64
+
65
+ # Sends a message to confirm a list of messages (identified by it's cids)
66
+ # have been read.
67
+ #
68
+ # @param recipients [array] list of recipients to send the message to.
69
+ # @param cids [array] list of message cids to be marked as read.
70
+ # @param gid [string] group id where the conversation ids are referenced.
71
+ def read(recipients, cids, gid = nil)
72
+ confirm('read', recipients, cids, gid)
73
+ end
74
+
75
+ # Modifies a previously sent message
76
+ #
77
+ # @param recipients [array] list of recipients to send the message to.
78
+ # @param cids [array] list of message cids to be marked as read.
79
+ # @param body [string] the new body to replace the previous one.
80
+ # @param gid [string] group id where the conversation ids are referenced.
81
+ def edit(recipients, cid, body, gid = nil)
82
+ send(recipients, typ: "chat.message.edit",
83
+ cid: cid,
84
+ msg: body,
85
+ gid: gid)
86
+ end
87
+
88
+ # Sends a message to delete a specific message.
89
+ #
90
+ # @param recipient [string] the recipient of the message
91
+ # @param cid [string] message cid to be marked as read.
92
+ # @param gid [string] group id where the conversation ids are referenced.
93
+ def delete(recipients, cids, gid = nil)
94
+ cids = [cids] if cids.is_a? String
95
+ send(recipients, typ: "chat.message.delete",
96
+ cids: cids,
97
+ gid: gid)
98
+ end
99
+
100
+ def on_invite(&block)
101
+ @messaging.subscribe :chat_invite do |msg|
102
+ g = SelfSDK::Chat::Group.new(self, msg.payload)
103
+ block.call(g)
104
+ end
105
+ end
106
+
107
+ def on_join(&block)
108
+ @messaging.subscribe :chat_join do |msg|
109
+ block.call(iss: msg.payload[:iss], gid: msg.payload[:gid])
110
+ end
111
+ end
112
+
113
+ def on_leave(&block)
114
+ @messaging.subscribe :chat_remove do |msg|
115
+ block.call(iss: msg.payload[:iss], gid: msg.payload[:gid])
116
+ end
117
+ end
118
+
119
+ # Invite sends group invitation to a list of members.
120
+ #
121
+ # @param gid [string] group id.
122
+ # @param name [string] name of the group.
123
+ # @param members [array] list of group members.
124
+ def invite(gid, name, members, opts = {})
125
+ payload = { typ: "chat.invite",
126
+ gid: gid,
127
+ name: name,
128
+ members: members }
129
+
130
+ if opts.key? :data
131
+ obj = SelfSDK::Chat::FileObject.new(@auth_token, @self_url)
132
+ obj_payload = obj.build_from_data("", opts[:data], opts[:mime]).to_payload
133
+ obj_payload.delete(:name)
134
+ payload.merge! obj_payload
135
+ end
136
+
137
+ @messaging.send(members, payload)
138
+ SelfSDK::Chat::Group.new(self, payload)
139
+ end
140
+
141
+ # Join a group
142
+ #
143
+ # @param gid [string] group id.
144
+ # @param members [array] list of group members.
145
+ def join(gid, members)
146
+ # Allow incoming connections from the given members
147
+
148
+ # Create missing sessions with group members.
149
+ create_missing_sessions(members)
150
+
151
+ # Send joining confirmation.
152
+ send(members, typ: 'chat.join', gid: gid, aud: gid)
153
+ end
154
+
155
+ # Leaves a group
156
+ #
157
+ # @param gid [string] group id.
158
+ # @members members [array] list of group members.
159
+ def leave(gid, members)
160
+ send(members, typ: "chat.remove", gid: gid )
161
+ end
162
+
163
+ private
164
+
165
+ # sends a confirmation for a list of messages to a list of recipients.
166
+ def confirm(action, recipients, cids, gid = nil)
167
+ cids = [cids] if cids.is_a? String
168
+ gid = recipients if gid.nil? || gid.empty?
169
+ send(recipients, typ: "chat.message.#{action}",
170
+ cids: cids,
171
+ gid: gid)
172
+ end
173
+
174
+ # sends a message to a list of recipients.
175
+ def send(recipients, body)
176
+ recipients = [recipients] if recipients.is_a? String
177
+ m = []
178
+ recipients.each do |r|
179
+ m << @messaging.send(r, body)
180
+ end
181
+ m
182
+ end
183
+
184
+ # Group invites may come with members of the group we haven't set up a session
185
+ # previously, for those identitiese need to establish a session, but only if
186
+ # our identity appears before the others in the list members.
187
+ def create_missing_sessions(members)
188
+ return if members.empty?
189
+
190
+ posterior_members = false
191
+ requests = []
192
+
193
+ members.each do |m|
194
+ if posterior_members
195
+ @client.devices(m).each do |d|
196
+ continue unless @messaging.client.session?(m, d)
197
+
198
+ requests << @messaging.send("#{m}:#{d}", {
199
+ typ: 'sessions.create',
200
+ aud: m
201
+ })
202
+ end
203
+ end
204
+
205
+ posterior_members = true if m == @app_id
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -40,6 +40,7 @@ module SelfSDK
40
40
  # @param [Hash] opts the options to authenticate.
41
41
  # @option opts [String] :cid The unique identifier of the authentication request.
42
42
  # @option opts [Integer] :exp_timeout timeout in seconds to expire the request.
43
+ # @option opts [Integer] :allowed_for number of seconds for enabling recurrent requests.
43
44
  # @return [Object] SelfSDK:::Messages::FactRequest
44
45
  def request(selfid, facts, opts = {}, &block)
45
46
  SelfSDK.logger.info "authenticating #{selfid}"
@@ -77,21 +77,20 @@ module SelfSDK
77
77
  @client.uuid_observer[cid]
78
78
  end
79
79
 
80
-
81
80
  # Send custom mmessage
82
81
  #
83
- # @param recipient [string] recipient for the message
82
+ # @param recipients [String|array] recipient for the message
84
83
  # @param type [string] message type
85
84
  # @param request [hash] message to be sent
86
- def send(recipient, request)
87
- request[:jti] = SecureRandom.uuid
85
+ def send(recipients, request)
86
+ request[:jti] = SecureRandom.uuid unless request.include?(:jti)
88
87
  request[:iss] = @client.jwt.id
89
- request[:sub] = recipient
90
- request[:iat] = SelfSDK::Time.now.strftime('%FT%TZ'),
91
- request[:exp] = (SelfSDK::Time.now + 300).strftime('%FT%TZ'),
92
- request[:cid] = SecureRandom.uuid unless request.include? :cid
88
+ request[:iat] = SelfSDK::Time.now.strftime('%FT%TZ')
89
+ request[:exp] = (SelfSDK::Time.now + 300).strftime('%FT%TZ')
90
+ request[:cid] = SecureRandom.uuid unless request.include?(:cid)
93
91
 
94
- @client.send_custom(recipient, request)
92
+ @client.send_custom(recipients, request)
93
+ request
95
94
  end
96
95
 
97
96
  def notify(recipient, message)
data/lib/sources.rb CHANGED
@@ -33,8 +33,15 @@ module SelfSDK
33
33
  types = { authentication_request: SelfSDK::Messages::AuthenticationReq::MSG_TYPE,
34
34
  authentication_response: SelfSDK::Messages::AuthenticationResp::MSG_TYPE,
35
35
  fact_request: SelfSDK::Messages::FactRequest::MSG_TYPE,
36
- fact_response: SelfSDK::Messages::FactResponse::MSG_TYPE }
37
- raise "invalid message type" unless types.key? s
36
+ fact_response: SelfSDK::Messages::FactResponse::MSG_TYPE,
37
+ chat_message: SelfSDK::Messages::ChatMessage::MSG_TYPE,
38
+ chat_message_deivered: SelfSDK::Messages::ChatMessageDelivered::MSG_TYPE,
39
+ chat_message_read: SelfSDK::Messages::ChatMessageRead::MSG_TYPE,
40
+ chat_invite: SelfSDK::Messages::ChatInvite::MSG_TYPE,
41
+ chat_join: SelfSDK::Messages::ChatJoin::MSG_TYPE,
42
+ chat_remove: SelfSDK::Messages::ChatRemove::MSG_TYPE,
43
+ }
44
+ raise "invalid message type '#{s}'" unless types.key? s
38
45
  return types[s]
39
46
  end
40
47
 
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.179
4
+ version: 0.0.184
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aldgate Ventures
@@ -226,14 +226,14 @@ dependencies:
226
226
  requirements:
227
227
  - - "~>"
228
228
  - !ruby/object:Gem::Version
229
- version: '12.3'
229
+ version: '13.0'
230
230
  type: :development
231
231
  prerelease: false
232
232
  version_requirements: !ruby/object:Gem::Requirement
233
233
  requirements:
234
234
  - - "~>"
235
235
  - !ruby/object:Gem::Version
236
- version: '12.3'
236
+ version: '13.0'
237
237
  - !ruby/object:Gem::Dependency
238
238
  name: rubocop
239
239
  requirement: !ruby/object:Gem::Requirement
@@ -326,6 +326,9 @@ extra_rdoc_files: []
326
326
  files:
327
327
  - lib/acl.rb
328
328
  - lib/authenticated.rb
329
+ - lib/chat/file_object.rb
330
+ - lib/chat/group.rb
331
+ - lib/chat/message.rb
329
332
  - lib/client.rb
330
333
  - lib/crypto.rb
331
334
  - lib/jwt_service.rb
@@ -335,6 +338,13 @@ files:
335
338
  - lib/messages/authentication_req.rb
336
339
  - lib/messages/authentication_resp.rb
337
340
  - lib/messages/base.rb
341
+ - lib/messages/chat.rb
342
+ - lib/messages/chat_invite.rb
343
+ - lib/messages/chat_join.rb
344
+ - lib/messages/chat_message.rb
345
+ - lib/messages/chat_message_delivered.rb
346
+ - lib/messages/chat_message_read.rb
347
+ - lib/messages/chat_remove.rb
338
348
  - lib/messages/fact.rb
339
349
  - lib/messages/fact_request.rb
340
350
  - lib/messages/fact_response.rb
@@ -343,6 +353,7 @@ files:
343
353
  - lib/ntptime.rb
344
354
  - lib/selfsdk.rb
345
355
  - lib/services/auth.rb
356
+ - lib/services/chat.rb
346
357
  - lib/services/facts.rb
347
358
  - lib/services/identity.rb
348
359
  - lib/services/messaging.rb