lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb in ably-rest-0.9.3 vs lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb in ably-rest-1.0.0

- old
+ new

@@ -180,11 +180,11 @@ end context 'with :query_time option' do let(:options) { { query_time: true } } - it 'queries the server for the time' do + it 'queries the server for the time (#RSA10k)' do expect(client).to receive(:time).and_call_original auth.request_token({}, options) end end @@ -402,22 +402,22 @@ let!(:auth_url_request_stub) do stub_request(auth_method, auth_url).to_return(:status => 500) end it 'raises ServerError' do - expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::ServerError) + expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::AuthenticationFailed) end end context 'XML' do let!(:auth_url_request_stub) do stub_request(auth_method, auth_url). to_return(:status => 201, :body => '<xml></xml>', :headers => { 'Content-Type' => 'application/xml' }) end it 'raises InvalidResponseBody' do - expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::InvalidResponseBody) + expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::AuthenticationFailed, /Content Type.*not supported/) end end end end @@ -610,28 +610,49 @@ it 'issues a new token every time (#RSA10a)' do expect { auth.authorize }.to change { auth.current_token_details } end end - context 'query_time: true' do + context 'query_time: true with authorize' do let(:local_time) { @now - 60 } let(:server_time) { @now } before do @now = Time.now allow(Time).to receive(:now).and_return(local_time) end - it 'only queries the server time once and then works out the offset, query_time option is never persisted' do + it 'only queries the server time once and then works out the offset, query_time option is never persisted (#RSA10k)' do expect(client).to receive(:time).once.and_return(server_time) auth.authorize({}, query_time: true) auth.authorize({}) expect(auth.auth_options).to_not have_key(:query_time) end end + context 'query_time: true ClientOption when instanced' do + let(:local_time) { @now - 60 } + let(:server_time) { @now } + + let(:client_options) { default_options.merge(key: api_key, query_time: true) } + + before do + @now = Time.now + allow(Time).to receive(:now).and_return(local_time) + end + + it 'only queries the server time once and then works out the offset, query_time option is never persisted (#RSA10k)' do + expect(client).to receive(:time).once.and_return(server_time) + + auth.authorize({}) + auth.authorize({}) + auth.authorize({}) + expect(auth.auth_options).to_not have_key(:query_time) + end + end + context 'TokenParams argument' do let(:default_token_params) { { ttl: 23 } } before do auth.authorize default_token_params @@ -642,22 +663,30 @@ auth.authorize expect(old_token).to_not eql(auth.current_token_details) expect(auth.token_params[:ttl]).to eql(23) end - it 'updates defaults when present and all previous configured TokenParams are discarded' do + it 'updates defaults when present and all previous configured TokenParams are discarded (#RSA10g)' do old_token = auth.current_token_details auth.authorize({ client_id: 'bob' }) expect(old_token).to_not eql(auth.current_token_details) - expect(auth.token_params[:ttl]).to_not eql(23) + expect(auth.token_params[:ttl]).to_not eq(23) expect(auth.token_params[:client_id]).to eql('bob') end it 'updates Auth#token_params attribute with an immutable hash' do auth.authorize({ client_id: 'bob' }) expect { auth.token_params['key_name'] = 'new_name' }.to raise_error RuntimeError, /can't modify frozen.*Hash/ end + + it 'uses TokenParams#timestamp for this request but obtains a new timestamp for subsequence requests (#RSA10g)' do + timestamp = Time.now.to_i + expect(auth).to receive(:create_token_request).with({ timestamp: Time.now.to_i }, {}).once.and_call_original + expect(auth).to receive(:create_token_request).with({}, {}).once.and_call_original + auth.authorize(timestamp: Time.now.to_i) + auth.authorize + end end context 'AuthOptions argument' do let(:token_ttl) { 2 } let(:auth_callback) { Proc.new do @@ -678,21 +707,33 @@ auth.authorize(nil, nil) expect(@old_token).to_not eql(auth.current_token_details) expect(auth.options[:auth_callback]).to eql(auth_callback) end - it 'updates defaults when present and all previous configured AuthOptions are discarded' do + it 'updates defaults when present and all previous configured AuthOptions are discarded (#RSA10g)' do auth.authorize(nil, auth_method: :post) expect(@old_token).to_not eql(auth.current_token_details) expect(auth.options[:auth_callback]).to be_nil expect(auth.options[:auth_method]).to eql(:post) end it 'updates Auth#options attribute with an immutable hash' do auth.authorize(nil, auth_callback: Proc.new { '1231232.12321:12321312' }) expect { auth.options['key_name'] = 'new_name' }.to raise_error RuntimeError, /can't modify frozen.*Hash/ end + + it 'uses AuthOptions#query_time for this request and will not query_time for subsequent requests (#RSA10g)' do + expect(client).to receive(:time).once.and_call_original + auth.authorize({}, query_time: true) + auth.authorize + end + + it 'uses AuthOptions#query_time for this request and will query_time again if provided subsequently' do + expect(client).to receive(:time).twice.and_call_original + auth.authorize({}, query_time: true) + auth.authorize({}, query_time: true) + end end context 'with previous authorisation' do before do auth.authorize @@ -766,11 +807,11 @@ old_token_defaults = Ably::Auth::TOKEN_DEFAULTS stub_const 'Ably::Auth::TOKEN_DEFAULTS', old_token_defaults.merge(renew_token_buffer: 0) @block_called = 0 end - let(:token_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, token_params: { ttl: 3 })) } + let(:token_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, default_token_params: { ttl: 3 })) } let(:client_options) { default_options.merge(token: token_client.auth.request_token.token, auth_callback: Proc.new do @block_called += 1 token_client.auth.create_token_request end) @@ -838,25 +879,26 @@ it 'uses the key name from the client' do expect(subject['keyName']).to eql(key_name) end - it 'uses the default TTL' do - expect(subject['ttl']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl) * 1000) + it 'specifies no TTL (#RSA5)' do + expect(subject['ttl']).to be_nil end context 'with a :ttl option below the Token expiry buffer that ensures tokens are renewed 15s before they expire as they are considered expired' do - let(:ttl) { 1 } + let(:ttl) { 1 } + let(:token_params) { { ttl: ttl } } it 'uses the Token expiry buffer default + 10s to allow for a token request in flight' do - expect(subject.ttl).to be > 1 - expect(subject.ttl).to be > Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER + expect(subject['ttl']).to be > 1 + expect(subject['ttl']).to be > Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER end end - it 'uses the default capability' do - expect(subject['capability']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:capability).to_json) + it 'specifies no capability (#RSA6)' do + expect(subject['capability']).to be_nil end context 'the nonce' do it 'is unique for every request' do unique_nonces = 100.times.map { auth.create_token_request['nonce'] } @@ -1046,10 +1088,46 @@ end it 'cannot be renewed automatically' do expect(token_auth_client.auth).to_not be_token_renewable end + + context 'and the token expires' do + let(:ttl) { 1 } + + before do + stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire + stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue + + @token = auth.request_token(ttl: ttl) + WebMock.enable! + WebMock.disable_net_connect! + + token_expired = { + "error" => { + "statusCode" => 401, + "code" => 40140, + "message" => "Token expired" + } + } + + stub_request(:post, "https://#{environment}-rest.ably.io/channels/foo/publish"). + to_return(status: 401, body: token_expired.to_json, headers: { 'Content-Type' => 'application/json' }) + end + + after do + WebMock.allow_net_connect! + WebMock.disable! + end + + let(:token) { @token.token } + + it 'should indicate an error and not retry the request (#RSA4a)' do + sleep ttl + 1 + expect { token_auth_client.channels.get('foo').publish 'event' }.to raise_error(Ably::Exceptions::TokenExpired) + end + end end context 'when implicit as a result of using :client_id' do let(:client_id) { '999' } let(:client) do @@ -1088,27 +1166,75 @@ it 'when a message is published' do expect(client.channel('foo').publish('event', 'data')).to be_truthy end - it 'with capability and TTL defaults' do + it 'with capability and TTL defaults (#TK2a, #TK2b)' do client.channel('foo').publish('event', 'data') expect(token).to be_a(Ably::Models::TokenDetails) - capability_with_str_key = Ably::Auth::TOKEN_DEFAULTS.fetch(:capability) + capability_with_str_key = { "*" => ["*"] } # Ably default is all capabilities capability = Hash[capability_with_str_key.keys.map(&:to_s).zip(capability_with_str_key.values)] expect(token.capability).to eq(capability) - expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl)) + expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + 60 * 60) # Ably default is 1hr expect(token.client_id).to eq(client_id) end specify '#client_id contains the client_id' do expect(client.auth.client_id).to eql(client_id) end end end + context 'when token expires' do + before do + stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire + stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue + end + + after do + WebMock.allow_net_connect! + WebMock.disable! + end + + let(:client_options) { default_options.merge(use_token_auth: true, key: api_key, query_time: true, default_token_params: { ttl: 2 }) } + let(:channel) { client.channels.get(random_str) } + let(:token_expired_response) do + { + "error" => { + "statusCode" => 401, + "code" => 40140, + "message" => "Token expired" + } + } + end + + it 'automatically renews the token (#RSA4b)' do + expect(auth.current_token_details).to be_nil + channel.publish 'event' + token = auth.current_token_details + expect(token).to_not be_nil + sleep 2.5 + channel.publish 'event' + expect(auth.current_token_details).to_not eql(token) + end + + it 'fails if the token renewal fails (#RSA4b)' do + expect(auth.current_token_details).to be_nil + channel.publish 'event' + token = auth.current_token_details + expect(token).to_not be_nil + sleep 2.5 + WebMock.enable! + WebMock.disable_net_connect! + stub_request(:post, "https://#{environment}-rest.ably.io/keys/#{TestApp.instance.key_name}/requestToken"). + to_return(status: 401, body: token_expired_response.to_json, headers: { 'Content-Type' => 'application/json' }) + expect { channel.publish 'event' }.to raise_error Ably::Exceptions::TokenExpired + expect(auth.current_token_details).to eql(token) + end + end + context 'when :client_id is provided in a token' do let(:client_id) { '123' } let(:token) do Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol).auth.request_token(client_id: client_id) end @@ -1197,19 +1323,22 @@ expect(auth).to be_using_basic_auth end end context 'deprecated #authorise' do - let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object) } + let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, use_token_auth: true) } let(:custom_logger) do Class.new do def initialize @messages = [] end [:fatal, :error, :warn, :info, :debug].each do |severity| - define_method severity do |message| - @messages << [severity, message] + define_method severity do |message, &block| + message_val = [message] + message_val << block.call if block + + @messages << [severity, message_val.compact.join(' ')] end end def logs @messages