spec/acceptance/rest/auth_spec.rb in ably-0.8.15 vs spec/acceptance/rest/auth_spec.rb in ably-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 @@ -443,12 +443,12 @@ it 'uses the token request returned from the callback when requesting a new token' do expect(request_token.client_id).to eql(client_id) end - context 'when authorised' do - before { auth.authorise(token_params, auth_callback: auth_callback) } + context 'when authorized' do + before { auth.authorize(token_params, auth_callback: auth_callback) } it "sets Auth#client_id to the new token's client_id" do expect(auth.client_id).to eql(client_id) end @@ -498,12 +498,12 @@ expect(token_details.expires).to be_within(1).of(expires) expect(token_details.issued).to be_within(1).of(issued) expect(token_details.capability).to eql(capability) end - context 'when authorised' do - before { auth.authorise(token_params, auth_callback: auth_callback) } + context 'when authorized' do + before { auth.authorize(token_params, auth_callback: auth_callback) } it "sets Auth#client_id to the new token's client_id" do expect(auth.client_id).to eql(client_id) end @@ -581,83 +581,112 @@ expect(token_details.client_id).to eql(client_id) end end end - context 'before #authorise has been called' do + context 'before #authorize has been called' do it 'has no current_token_details' do expect(auth.current_token_details).to be_nil end end - describe '#authorise' do + describe '#authorize (#RSA10, #RSA10j)' do context 'when called for the first time since the client has been instantiated' do let(:auth_options) do { auth_url: 'http://somewhere.com/' } end let(:token_params) do { ttl: 55 } end it 'passes all auth_options and token_params to #request_token' do expect(auth).to receive(:request_token).with(token_params, auth_options) - auth.authorise token_params, auth_options + auth.authorize token_params, auth_options end it 'returns a valid token' do - expect(auth.authorise).to be_a(Ably::Models::TokenDetails) + expect(auth.authorize).to be_a(Ably::Models::TokenDetails) end - it 'issues a new token if option :force => true' do - expect { auth.authorise(force: true) }.to change { auth.current_token_details } + 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.authorise({}, query_time: true) - auth.authorise({}, force: true) + 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.authorise default_token_params + auth.authorize default_token_params end it 'has no effect on the defaults when null and TokenParam defaults remain the same' do old_token = auth.current_token_details - auth.authorise(nil, force: true) + 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.authorise({ client_id: 'bob' }, { force: true }) + 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.authorise({ client_id: 'bob' }, { force: true }) + 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 @@ -667,72 +696,79 @@ 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 - auth.authorise(nil, default_auth_options) + auth.authorize(nil, default_auth_options) @old_token = auth.current_token_details sleep token_ttl + 0.5 end it 'has no effect on the defaults when null and AuthOptions defaults remain the same' do - auth.authorise(nil, nil) + 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 - auth.authorise(nil, auth_method: :post) + 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.authorise(nil, auth_callback: Proc.new { '1231232.12321:12321312' }) + 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.authorise + auth.authorize expect(auth.current_token_details).to_not be_expired end - it 'does not request a token if current_token_details has not expired' do - expect(auth).to_not receive(:request_token) - auth.authorise - end - it 'requests a new token if token is expired' do allow(auth.current_token_details).to receive(:expired?).and_return(true) expect(auth).to receive(:request_token) - expect { auth.authorise }.to change { auth.current_token_details } + expect { auth.authorize }.to change { auth.current_token_details } end - it 'issues a new token if option :force => true' do - expect { auth.authorise({}, force: true) }.to change { auth.current_token_details } + it 'issues a new token every time #authorize is called' do + expect { auth.authorize({}) }.to change { auth.current_token_details } end end - it 'updates the persisted token params that are then used for subsequent authorise requests' do + it 'updates the persisted token params that are then used for subsequent authorize requests' do expect(auth.token_params[:ttl]).to_not eql(26) - auth.authorise(ttl: 26) + auth.authorize(ttl: 26) expect(auth.token_params[:ttl]).to eql(26) end - it 'updates the persisted auth options that are then used for subsequent authorise requests' do + it 'updates the persisted auth options that are then used for subsequent authorize requests' do expect(auth.options[:authUrl]).to be_nil - auth.authorise({}, authUrl: 'http://foo.com') + auth.authorize({}, authUrl: 'http://foo.com') expect(auth.options[:authUrl]).to eql('http://foo.com') end context 'with a Proc for the :auth_callback option' do let(:client_id) { random_str } let!(:token) do - auth.authorise({}, auth_callback: Proc.new do + auth.authorize({}, auth_callback: Proc.new do @block_called ||= 0 @block_called += 1 auth.create_token_request(client_id: client_id) end) end @@ -771,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) @@ -798,27 +834,27 @@ context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do let(:auth_token_object) { auth_client.auth.request_token } it 'rejects a TokenDetails object with an incompatible client_id and raises an exception' do - expect { client.auth.authorise({}, force: true) }.to raise_error Ably::Exceptions::IncompatibleClientId + expect { client.auth.authorize({}) }.to raise_error Ably::Exceptions::IncompatibleClientId end end context 'and an incompatible client_id in a TokenRequest object passed to the auth callback and raises an exception' do let(:auth_token_object) { auth_client.auth.create_token_request } it 'rejects a TokenRequests object with an incompatible client_id and raises an exception' do - expect { client.auth.authorise({}, force: true) }.to raise_error Ably::Exceptions::IncompatibleClientId + expect { client.auth.authorize({}) }.to raise_error Ably::Exceptions::IncompatibleClientId end end context 'and a token string without any retrievable client_id' do let(:auth_token_object) { auth_client.auth.request_token(client_id: 'different').token } it 'rejects a TokenRequests object with an incompatible client_id and raises an exception' do - client.auth.authorise({}, force: true) + client.auth.authorize({}) expect(client.client_id).to eql(client_id) end end end end @@ -836,32 +872,33 @@ it 'returns a TokenRequest that can be passed to a client that can use it for authentication without an API key' do auth_callback = Proc.new { subject } client_without_api_key = Ably::Rest::Client.new(default_options.merge(auth_callback: auth_callback)) expect(client_without_api_key.auth).to be_using_token_auth - expect { client_without_api_key.auth.authorise }.to_not raise_error + expect { client_without_api_key.auth.authorize }.to_not raise_error end 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'] } @@ -895,11 +932,11 @@ end it 'uses these capabilities when Ably issues an actual token' do auth_callback = Proc.new { subject } client_without_api_key = Ably::Rest::Client.new(default_options.merge(auth_callback: auth_callback)) - client_without_api_key.auth.authorise + client_without_api_key.auth.authorize expect(client_without_api_key.auth.current_token_details.capability).to eql(capability) end end context 'with additional invalid attributes' do @@ -1006,11 +1043,11 @@ it 'is valid when used for authentication' do auth_callback = Proc.new do auth.create_token_request(token_attributes) end client = Ably::Rest::Client.new(auth_callback: auth_callback, environment: environment, protocol: protocol) - client.auth.authorise + client.auth.authorize end end end end @@ -1051,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 @@ -1093,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 @@ -1198,9 +1319,51 @@ expect(auth.key).to eql(api_key) end specify '#using_basic_auth? is true' do 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, 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, &block| + message_val = [message] + message_val << block.call if block + + @messages << [severity, message_val.compact.join(' ')] + end + end + + def logs + @messages + end + + def level + 1 + end + + def level=(new_level) + end + end + end + let(:custom_logger_object) { custom_logger.new } + + it 'logs a deprecation warning (#RSA10l)' do + client.auth.authorise + expect(custom_logger_object.logs.find { |severity, message| message.match(/authorise.*deprecated/i)} ).to_not be_nil + end + + it 'returns a valid token (#RSA10l)' do + response = client.auth.authorise + expect(response).to be_a(Ably::Models::TokenDetails) end end end end