ably 1.0.6 → 1.0.7

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.
@@ -631,7 +631,7 @@ module Ably
631
631
  uri = URI.parse(auth_url)
632
632
  connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
633
633
  method = auth_options[:auth_method] || options[:auth_method] || :get
634
- params = (auth_options[:auth_params] || options[:auth_method] || {}).merge(token_params)
634
+ params = (auth_options[:auth_params] || options[:auth_params] || {}).merge(token_params)
635
635
 
636
636
  response = connection.public_send(method) do |request|
637
637
  request.url uri.path
@@ -643,7 +643,7 @@ module Ably
643
643
  end
644
644
  end
645
645
 
646
- if !response.body.kind_of?(Hash) && !response.headers['Content-Type'].to_s.match(%r{text/plain}i)
646
+ if !response.body.kind_of?(Hash) && !response.headers['Content-Type'].to_s.match(%r{text/plain|application/jwt}i)
647
647
  raise Ably::Exceptions::InvalidResponseBody,
648
648
  "Content Type #{response.headers['Content-Type']} is not supported by this client library"
649
649
  end
@@ -117,6 +117,7 @@ module Ably
117
117
  #
118
118
  # @option options [Boolean] :fallback_hosts_use_default (false) When true, forces the user of fallback hosts even if a non-default production endpoint is being used
119
119
  # @option options [Array<String>] :fallback_hosts When an array of fallback hosts are provided, these fallback hosts are always used if a request fails to the primary endpoint. If an empty array is provided, the fallback host functionality is disabled
120
+ # @option options [Boolean] :add_request_ids (false) When true, adds a unique request_id to each request sent to Ably servers. This is handy when reporting issues, because you can refer to a specific request.
120
121
  #
121
122
  # @return [Ably::Rest::Client]
122
123
  #
@@ -1,5 +1,5 @@
1
1
  module Ably
2
- VERSION = '1.0.6'
2
+ VERSION = '1.0.7'
3
3
  PROTOCOL_VERSION = '1.0'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
@@ -1031,5 +1031,232 @@ describe Ably::Realtime::Auth, :event_machine do
1031
1031
  end
1032
1032
  end
1033
1033
  end
1034
+
1035
+ context 'when using JWT' do
1036
+ let(:auth_url) { 'https://echo.ably.io/createJWT' }
1037
+ let(:auth_params) { { keyName: key_name, keySecret: key_secret } }
1038
+ let(:channel_name) { "test_JWT_#{random_str}" }
1039
+ let(:message_name) { 'message_JWT' }
1040
+
1041
+ # RSA8g
1042
+ context 'when using auth_url' do
1043
+ let(:client_options) { default_options.merge(auth_url: auth_url, auth_params: auth_params) }
1044
+
1045
+ context 'when credentials are valid' do
1046
+ it 'client successfully fetches a channel and publishes a message' do
1047
+ channel = client.channels.get(channel_name)
1048
+ channel.subscribe do |message|
1049
+ expect(message.name).to eql(message_name)
1050
+ stop_reactor
1051
+ end
1052
+ channel.publish message_name
1053
+ end
1054
+ end
1055
+
1056
+ context 'when credentials are wrong' do
1057
+ let(:auth_params) { { keyName: key_name, keySecret: 'invalid' } }
1058
+
1059
+ it 'disconnected includes and invalid signature message' do
1060
+ client.connection.once(:disconnected) do |state_change|
1061
+ expect(state_change.reason.message.match(/invalid signature/i)).to_not be_nil
1062
+ expect(state_change.reason.code).to eql(40144)
1063
+ stop_reactor
1064
+ end
1065
+ client.connect
1066
+ end
1067
+ end
1068
+
1069
+ context 'when token is expired' do
1070
+ let(:token_duration) { 5 }
1071
+ let(:auth_params) { { keyName: key_name, keySecret: key_secret, expiresIn: token_duration } }
1072
+ it 'receives a 40142 error from the server' do
1073
+ client.connection.once(:connected) do
1074
+ client.connection.once(:disconnected) do |state_change|
1075
+ expect(state_change.reason).to be_a(Ably::Models::ErrorInfo)
1076
+ expect(state_change.reason.message).to match(/(expire)/i)
1077
+ expect(state_change.reason.code).to eql(40142)
1078
+ stop_reactor
1079
+ end
1080
+ end
1081
+ end
1082
+ end
1083
+ end
1084
+
1085
+ # RSA8g
1086
+ context 'when using auth_callback' do
1087
+ let(:token_callback) do
1088
+ lambda do |token_params|
1089
+ Ably::Rest::Client.new(default_options).auth.request_token({}, { auth_url: auth_url, auth_params: auth_params }).token
1090
+ end
1091
+ end
1092
+ let(:client_options) { default_options.merge(auth_callback: token_callback) }
1093
+ WebMock.allow_net_connect!
1094
+ WebMock.disable!
1095
+ context 'when credentials are valid' do
1096
+
1097
+ it 'authentication succeeds and client can post a message' do
1098
+ channel = client.channels.get(channel_name)
1099
+ channel.subscribe do |message|
1100
+ expect(message.name).to eql(message_name)
1101
+ stop_reactor
1102
+ end
1103
+ channel.publish(message_name) do
1104
+ # assert_requested :get, Addressable::Template.new("#{auth_url}{?keyName,keySecret}")
1105
+ end
1106
+ end
1107
+ end
1108
+
1109
+ context 'when credentials are invalid' do
1110
+ let(:auth_params) { { keyName: key_name, keySecret: 'invalid' } }
1111
+
1112
+ it 'authentication fails and reason for disconnection is invalid signature' do
1113
+ client.connection.once(:disconnected) do |state_change|
1114
+ expect(state_change.reason.message.match(/invalid signature/i)).to_not be_nil
1115
+ expect(state_change.reason.code).to eql(40144)
1116
+ stop_reactor
1117
+ end
1118
+ client.connect
1119
+ end
1120
+ end
1121
+ end
1122
+
1123
+ context 'when the client is initialized with ClientOptions and the token is a JWT token' do
1124
+ let(:client_options) { { token: token, environment: environment, protocol: protocol } }
1125
+
1126
+ context 'when credentials are valid' do
1127
+ let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body }
1128
+
1129
+ it 'posts successfully to a channel' do
1130
+ channel = client.channels.get(channel_name)
1131
+ channel.subscribe do |message|
1132
+ expect(message.name).to eql(message_name)
1133
+ stop_reactor
1134
+ end
1135
+ channel.publish(message_name)
1136
+ end
1137
+ end
1138
+
1139
+ context 'when credentials are invalid' do
1140
+ let(:key_secret) { 'invalid' }
1141
+ let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body }
1142
+
1143
+ it 'fails with an invalid signature error' do
1144
+ client.connection.once(:disconnected) do |state_change|
1145
+ expect(state_change.reason.message.match(/invalid signature/i)).to_not be_nil
1146
+ expect(state_change.reason.code).to eql(40144)
1147
+ stop_reactor
1148
+ end
1149
+ client.connect
1150
+ end
1151
+ end
1152
+ end
1153
+
1154
+ context 'when JWT token expires' do
1155
+ before do
1156
+ stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire
1157
+ stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue
1158
+ end
1159
+ let(:token_callback) do
1160
+ lambda do |token_params|
1161
+ # Ably in all environments other than production will send AUTH 5 seconds before expiry, so
1162
+ # we generate a JWT that expires in 5s so that the window for Realtime to send has passed
1163
+ tokenResponse = Faraday.get "#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}&expiresIn=5"
1164
+ tokenResponse.body
1165
+ end
1166
+ end
1167
+ let(:client_options) { default_options.merge(use_token_auth: true, auth_callback: token_callback) }
1168
+
1169
+ # RTC8a
1170
+ it 'client disconnects, a new token is requested via auth_callback and the client gets reconnected' do
1171
+ client.connection.once(:connected) do
1172
+ original_token = auth.current_token_details
1173
+ original_conn_id = client.connection.id
1174
+
1175
+ client.connection.once(:disconnected) do |state_change|
1176
+ expect(state_change.reason.code).to eql(40142)
1177
+
1178
+ client.connection.once(:connected) do
1179
+ expect(original_token).to_not eql(auth.current_token_details)
1180
+ expect(original_conn_id).to eql(client.connection.id)
1181
+ stop_reactor
1182
+ end
1183
+ end
1184
+ end
1185
+ end
1186
+
1187
+ context 'and an AUTH procol message is received' do
1188
+ let(:token_callback) do
1189
+ lambda do |token_params|
1190
+ # Ably in all environments other than local will send AUTH 30 seconds before expiry
1191
+ # We set the TTL to 35s so there's room to receive an AUTH protocol message
1192
+ tokenResponse = Faraday.get "#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}&expiresIn=35"
1193
+ tokenResponse.body
1194
+ end
1195
+ end
1196
+
1197
+ # RTC8a, RTC8a4
1198
+ it 'client reauths correctly without going through a disconnection' do
1199
+ client.connection.once(:connected) do
1200
+ original_token = client.auth.current_token_details
1201
+ received_auth = false
1202
+
1203
+ client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
1204
+ received_auth = true if protocol_message.action == :auth
1205
+ end
1206
+
1207
+ client.connection.once(:update) do
1208
+ expect(received_auth).to be_truthy
1209
+ expect(original_token).to_not eql(client.auth.current_token_details)
1210
+ stop_reactor
1211
+ end
1212
+ end
1213
+ end
1214
+ end
1215
+ end
1216
+
1217
+ context 'when the JWT token request includes a client_id' do
1218
+ let(:client_id) { random_str }
1219
+ let(:auth_callback) do
1220
+ lambda do |token_params|
1221
+ Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}&client_id=#{client_id}").body
1222
+ end
1223
+ end
1224
+ let(:client_options) { default_options.merge(auth_callback: auth_callback) }
1225
+
1226
+ it 'the client_id is the same that was specified in the auth_callback that generated the JWT token' do
1227
+ client.connection.once(:connected) do
1228
+ expect(client.auth.client_id).to eql(client_id)
1229
+ stop_reactor
1230
+ end
1231
+ end
1232
+ end
1233
+
1234
+ context 'when the JWT token request includes a subscribe-only capability' do
1235
+ let(:channel_with_publish_permissions) { "test_JWT_with_publish_#{random_str}" }
1236
+ let(:basic_capability) { JSON.dump(channel_name => ['subscribe'], channel_with_publish_permissions => ['publish']) }
1237
+ let(:auth_callback) do
1238
+ lambda do |token_params|
1239
+ Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}&capability=#{URI.escape(basic_capability)}").body
1240
+ end
1241
+ end
1242
+ let(:client_options) { default_options.merge(auth_callback: auth_callback) }
1243
+
1244
+ it 'client fails to publish to a channel with subscribe-only capability and publishes successfully on a channel with permissions' do
1245
+ client.connection.once(:connected) do
1246
+ forbidden_channel = client.channels.get(channel_name)
1247
+ allowed_channel = client.channels.get(channel_with_publish_permissions)
1248
+ forbidden_channel.publish('not-allowed').errback do |error|
1249
+ expect(error.code).to eql(40160)
1250
+ expect(error.message).to match(/permission denied/)
1251
+
1252
+ allowed_channel.publish(message_name) do |message|
1253
+ expect(message.name).to eql(message_name)
1254
+ stop_reactor
1255
+ end
1256
+ end
1257
+ end
1258
+ end
1259
+ end
1260
+ end
1034
1261
  end
1035
1262
  end
@@ -1338,5 +1338,42 @@ describe Ably::Auth do
1338
1338
  expect(response).to be_a(Ably::Models::TokenDetails)
1339
1339
  end
1340
1340
  end
1341
+
1342
+ # RSC1, RSC1a, RSA3c, RSA3d
1343
+ context 'when using JWT' do
1344
+ let(:auth_url) { 'https://echo.ably.io/createJWT' }
1345
+ let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body }
1346
+ let(:client) { Ably::Rest::Client.new(token: token, environment: environment, protocol: protocol) }
1347
+
1348
+ it 'authenticates correctly using the JWT token generated by the echo server' do
1349
+ expect(client.stats).to_not be_nil()
1350
+ end
1351
+
1352
+ context 'when the JWT embeds an Ably token' do
1353
+ let(:token) { Faraday.post(auth_url, { keyName: key_name, keySecret: key_secret, jwtType: :embedded }).body }
1354
+
1355
+ it 'authenticates correctly using the embedded token' do
1356
+ expect(client.stats).to_not be_nil()
1357
+ end
1358
+
1359
+ context 'and the requested token is encrypted' do
1360
+ let(:token) { Faraday.post(auth_url, { keyName: key_name, keySecret: key_secret, jwtType: :embedded, encrypted: 1 }).body }
1361
+
1362
+ it 'authenticates correctly using the embedded token' do
1363
+ expect(client.stats).to_not be_nil()
1364
+ end
1365
+ end
1366
+ end
1367
+
1368
+ # RSA4f, RSA8c
1369
+ context 'when the token requested is returned with application/jwt content type' do
1370
+ let(:auth_rest_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
1371
+ let(:auth_params) { { keyName: key_name, keySecret: key_secret, returnType: 'jwt' } }
1372
+ let(:token) { auth_rest_client.auth.request_token({ }, { auth_url: auth_url, auth_params: auth_params }).token }
1373
+ it 'authenticates correctly and pulls stats' do
1374
+ expect(client.stats).to_not be_nil()
1375
+ end
1376
+ end
1377
+ end
1341
1378
  end
1342
1379
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ably
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lewis Marshall
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-05-01 00:00:00.000000000 Z
12
+ date: 2018-06-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine