spec/acceptance/rest/message_spec.rb in ably-0.6.2 vs spec/acceptance/rest/message_spec.rb in ably-0.7.0

- old
+ new

@@ -1,222 +1,245 @@ +# encoding: utf-8 require 'spec_helper' require 'securerandom' -describe 'Ably::Rest Message' do +describe Ably::Rest::Channel, 'messages' do include Ably::Modules::Conversions - [:msgpack, :json].each do |protocol| - context "over #{protocol}" do - let(:default_client_options) { { api_key: api_key, environment: environment, protocol: protocol } } - let(:client) { Ably::Rest::Client.new(default_client_options) } - let(:other_client) { Ably::Rest::Client.new(default_client_options) } + vary_by_protocol do + let(:default_client_options) { { api_key: api_key, environment: environment, protocol: protocol } } + let(:client_options) { default_client_options } + let(:client) { Ably::Rest::Client.new(client_options) } + let(:other_client) { Ably::Rest::Client.new(client_options) } + let(:channel) { client.channel('test') } - describe 'encryption and encoding' do - let(:channel_name) { "persisted:#{SecureRandom.hex(4)}" } - let(:cipher_options) { { key: SecureRandom.hex(32) } } - let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } + context 'publishing with an ASCII_8BIT message name' do + let(:message_name) { random_str.encode(Encoding::ASCII_8BIT) } - context 'encoding and decoding encrypted messages' do - shared_examples 'an Ably encrypter and decrypter' do |item, data| - let(:algorithm) { data['algorithm'].upcase } - let(:mode) { data['mode'].upcase } - let(:key_length) { data['keylength'] } - let(:secret_key) { Base64.decode64(data['key']) } - let(:iv) { Base64.decode64(data['iv']) } + it 'is converted into UTF_8' do + channel.publish message_name, 'example' + message = channel.history.first + expect(message.name.encoding).to eql(Encoding::UTF_8) + expect(message.name.encode(Encoding::ASCII_8BIT)).to eql(message_name) + end + end - let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } } + describe 'encryption and encoding' do + let(:channel_name) { "persisted:#{random_str}" } + let(:cipher_options) { { key: random_str(32) } } + let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } - context 'publish & subscribe' do - let(:encoded) { item['encoded'] } - let(:encoded_data) { encoded['data'] } - let(:encoded_encoding) { encoded['encoding'] } - let(:encoded_data_decoded) do - if encoded_encoding == 'json' - JSON.parse(encoded_data) - elsif encoded_encoding == 'base64' - Base64.decode64(encoded_data) - else - encoded_data - end - end + context 'with #publish and #history' do + shared_examples 'an Ably encrypter and decrypter' do |item, data| + let(:algorithm) { data['algorithm'].upcase } + let(:mode) { data['mode'].upcase } + let(:key_length) { data['keylength'] } + let(:secret_key) { Base64.decode64(data['key']) } + let(:iv) { Base64.decode64(data['iv']) } - let(:encrypted) { item['encrypted'] } - let(:encrypted_data) { encrypted['data'] } - let(:encrypted_encoding) { encrypted['encoding'] } - let(:encrypted_data_decoded) do - if encrypted_encoding.match(%r{/base64$}) - Base64.decode64(encrypted_data) - else - encrypted_data - end - end + let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } } - it 'encrypts message automatically when published' do - expect(client).to receive(:post) do |path, message| - if protocol == :json - expect(message['encoding']).to eql(encrypted_encoding) - expect(Base64.decode64(message['data'])).to eql(encrypted_data_decoded) - else - # Messages sent over binary protocol will not have Base64 encoded data - expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, '')) - expect(message['data']).to eql(encrypted_data_decoded) - end - end.and_return(double('Response', status: 201)) - - encrypted_channel.publish 'example', encoded_data_decoded - end - - it 'sends and receives messages that are encrypted & decrypted by the Ably library' do - encrypted_channel.publish 'example', encoded_data_decoded - - message = encrypted_channel.history.first - expect(message.data).to eql(encoded_data_decoded) - expect(message.encoding).to be_nil - end + let(:encoded) { item['encoded'] } + let(:encoded_data) { encoded['data'] } + let(:encoded_encoding) { encoded['encoding'] } + let(:encoded_data_decoded) do + if encoded_encoding == 'json' + JSON.parse(encoded_data) + elsif encoded_encoding == 'base64' + Base64.decode64(encoded_data) + else + encoded_data end end - resources_root = File.expand_path('../../../resources', __FILE__) - - def self.add_tests_for_data(data) - data['items'].each_with_index do |item, index| - context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do - it_behaves_like 'an Ably encrypter and decrypter', item, data - end + let(:encrypted) { item['encrypted'] } + let(:encrypted_data) { encrypted['data'] } + let(:encrypted_encoding) { encrypted['encoding'] } + let(:encrypted_data_decoded) do + if encrypted_encoding.match(%r{/base64$}) + Base64.decode64(encrypted_data) + else + encrypted_data end end - context 'with AES-128-CBC' do - data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json'))) - add_tests_for_data data - end + it 'encrypts message automatically when published' do + expect(client).to receive(:post) do |path, message| + if protocol == :json + expect(message['encoding']).to eql(encrypted_encoding) + expect(Base64.decode64(message['data'])).to eql(encrypted_data_decoded) + else + # Messages sent over binary protocol will not have Base64 encoded data + expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, '')) + expect(message['data']).to eql(encrypted_data_decoded) + end + end.and_return(double('Response', status: 201)) - context 'with AES-256-CBC' do - data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json'))) - add_tests_for_data data + encrypted_channel.publish 'example', encoded_data_decoded end - context 'multiple messages' do - let(:data) { MessagePack.pack({ 'key' => SecureRandom.hex }) } - let(:message_count) { 20 } + it 'sends and retrieves messages that are encrypted & decrypted by the Ably library' do + encrypted_channel.publish 'example', encoded_data_decoded - it 'encrypt and decrypt messages' do - message_count.times do |index| - encrypted_channel.publish index.to_s, "#{index}-#{data}" - end + message = encrypted_channel.history.first + expect(message.data).to eql(encoded_data_decoded) + expect(message.encoding).to be_nil + end + end - messages = encrypted_channel.history + resources_root = File.expand_path('../../../resources', __FILE__) - expect(messages.count).to eql(message_count) - messages.each do |message| - expect(message.data).to eql("#{message.name}-#{data}") - expect(message.encoding).to be_nil - end + def self.add_tests_for_data(data) + data['items'].each_with_index do |item, index| + context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do + it_behaves_like 'an Ably encrypter and decrypter', item, data end end + end - context "sending using protocol #{protocol} and retrieving with a different protocol" do - let(:other_protocol) { protocol == :msgpack ? :json : :msgpack } - let(:other_client) { Ably::Rest::Client.new(default_client_options.merge(protocol: other_protocol)) } - let(:other_client_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } + context 'with AES-128-CBC using crypto-data-128.json fixtures' do + data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json'))) + add_tests_for_data data + end - before do - expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?) - end + context 'with AES-256-CBC using crypto-data-256.json fixtures' do + data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json'))) + add_tests_for_data data + end - [MessagePack.pack({ 'key' => SecureRandom.hex }), '€ unicode', { 'key' => SecureRandom.hex }].each do |payload| - payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}" + context 'when publishing lots of messages' do + let(:data) { MessagePack.pack({ 'key' => random_str }) } + let(:message_count) { 20 } - specify "delivers a #{payload_description} payload to the receiver" do - encrypted_channel.publish 'example', payload + it 'encrypts on #publish and decrypts on #history' do + message_count.times do |index| + encrypted_channel.publish index.to_s, "#{index}-#{data}" + end - message = other_client_channel.history.first - expect(message.data).to eql(payload) - expect(message.encoding).to be_nil - end + messages = encrypted_channel.history + + expect(messages.count).to eql(message_count) + messages.each do |message| + expect(message.data).to eql("#{message.name}-#{data}") + expect(message.encoding).to be_nil end end + end - context 'publishing on an unencrypted channel and retrieving on an encrypted channel' do - let(:unencrypted_channel) { client.channel(channel_name) } - let(:other_client_encrypted_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } + context 'when retrieving #history with a different protocol' do + let(:other_protocol) { protocol == :msgpack ? :json : :msgpack } + let(:other_client) { Ably::Rest::Client.new(default_client_options.merge(protocol: other_protocol)) } + let(:other_client_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } - let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) } + before do + expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?) + end - it 'does not attempt to decrypt the message' do - unencrypted_channel.publish 'example', payload + [MessagePack.pack({ 'key' => SecureRandom.hex }), 'ã unicode', { 'key' => SecureRandom.hex }].each do |payload| + payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}" - message = other_client_encrypted_channel.history.first + specify "delivers a #{payload_description} payload to the receiver" do + encrypted_channel.publish 'example', payload + + message = other_client_channel.history.first expect(message.data).to eql(payload) expect(message.encoding).to be_nil end end + end - context 'publishing on an encrypted channel and retrieving on an unencrypted channel' do - let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } - let(:other_client_unencrypted_channel) { other_client.channel(channel_name) } + context 'when publishing on an unencrypted channel and retrieving with #history on an encrypted channel' do + let(:unencrypted_channel) { client.channel(channel_name) } + let(:other_client_encrypted_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } - let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) } + let(:payload) { MessagePack.pack({ 'key' => random_str }) } - skip 'delivers the message but still encrypted' do - # TODO: Decide if we should raise an exception or allow the message through - encrypted_channel.publish 'example', payload + it 'does not attempt to decrypt the message' do + unencrypted_channel.publish 'example', payload - message = other_client_unencrypted_channel.history.first - expect(message.data).to_not eql(payload) - expect(message.encoding).to match(/^cipher\+aes-256-cbc/) - end + message = other_client_encrypted_channel.history.first + expect(message.data).to eql(payload) + expect(message.encoding).to be_nil + end + end - it 'triggers a Cipher exception' do - encrypted_channel.publish 'example', payload - expect { other_client_unencrypted_channel.history }.to raise_error Ably::Exceptions::CipherError, /Message cannot be decrypted/ + context 'when publishing on an encrypted channel and retrieving with #history on an unencrypted channel' do + let(:client_options) { default_client_options.merge(log_level: :fatal) } + let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } } + let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) } + let(:other_client_unencrypted_channel) { other_client.channel(channel_name) } + + let(:payload) { MessagePack.pack({ 'key' => random_str }) } + + before do + encrypted_channel.publish 'example', payload + end + + it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do + message = other_client_unencrypted_channel.history.first + expect(message.data).to_not eql(payload) + expect(message.encoding).to match(/^cipher\+aes-256-cbc/) + end + + it 'logs a Cipher exception' do + expect(other_client.logger).to receive(:error) do |message| + expect(message).to match(/Message cannot be decrypted/) end + other_client_unencrypted_channel.history end + end - context 'publishing on an encrypted channel and subscribing with a different algorithm on another client' do - let(:cipher_options_client1) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } } - let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) } - let(:cipher_options_client2) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } } - let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) } + context 'publishing on an encrypted channel and retrieving #history with a different algorithm on another client' do + let(:client_options) { default_client_options.merge(log_level: :fatal) } + let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } } + let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) } + let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } } + let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) } - let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) } + let(:payload) { MessagePack.pack({ 'key' => random_str }) } - skip 'delivers the message but still encrypted' do - # TODO: Decide if we should raise an exception or allow the message through - encrypted_channel.publish 'example', payload + before do + encrypted_channel_client1.publish 'example', payload + end - message = other_client_unencrypted_channel.history.first - expect(message.data).to_not eql(payload) - expect(message.encoding).to match(/^cipher\+aes-256-cbc/) - end + it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do + message = encrypted_channel_client2.history.first + expect(message.data).to_not eql(payload) + expect(message.encoding).to match(/^cipher\+aes-256-cbc/) + end - it 'triggers a Cipher exception' do - encrypted_channel_client1.publish 'example', payload - expect { encrypted_channel_client2.history }.to raise_error Ably::Exceptions::CipherError, /Cipher algorithm [\w\d-]+ does not match/ + it 'logs a Cipher exception' do + expect(other_client.logger).to receive(:error) do |message| + expect(message).to match(/Cipher algorithm [\w-]+ does not match/) end + encrypted_channel_client2.history end + end - context 'publishing on an encrypted channel and subscribing with a different key on another client' do - let(:cipher_options_client1) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } } - let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) } - let(:cipher_options_client2) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } } - let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) } + context 'publishing on an encrypted channel and subscribing with a different key on another client' do + let(:client_options) { default_client_options.merge(log_level: :fatal) } + let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } } + let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) } + let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } } + let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) } - let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) } + let(:payload) { MessagePack.pack({ 'key' => random_str }) } - skip 'delivers the message but still encrypted' do - # TODO: Decide if we should raise an exception or allow the message through - encrypted_channel.publish 'example', payload + before do + encrypted_channel_client1.publish 'example', payload + end - message = other_client_unencrypted_channel.history.first - expect(message.data).to_not eql(payload) - expect(message.encoding).to match(/^cipher\+aes-256-cbc/) - end + it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do + message = encrypted_channel_client2.history.first + expect(message.data).to_not eql(payload) + expect(message.encoding).to match(/^cipher\+aes-256-cbc/) + end - it 'triggers a Cipher exception' do - encrypted_channel_client1.publish 'example', payload - expect { encrypted_channel_client2.history }.to raise_error Ably::Exceptions::CipherError, /CipherError decrypting data/ + it 'logs a Cipher exception' do + expect(other_client.logger).to receive(:error) do |message| + expect(message).to match(/CipherError decrypting data/) end + encrypted_channel_client2.history end end end end end