ably 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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