From c9a3f0a04d27ef90e19ee0730930c77779b33136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bedn=C3=A1=C5=99?= Date: Tue, 31 Aug 2021 10:52:09 +0200 Subject: [PATCH] feat: `Authorization` header is not forwarded when redirect leads to a different domain (#89) --- CHANGELOG.md | 12 ++- README.md | 16 +++ lib/influxdb2/client/client.rb | 2 + lib/influxdb2/client/default_api.rb | 26 +++-- lib/influxdb2/client/version.rb | 2 +- test/influxdb/redirect_test.rb | 145 ++++++++++++++++++++++++++++ test/influxdb/write_api_test.rb | 4 +- 7 files changed, 196 insertions(+), 11 deletions(-) create mode 100644 test/influxdb/redirect_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d499699..f15c0128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ -## 1.18.0 [unreleased] +## 2.0.0 [unreleased] +### Breaking Changes +Due to a security reason `Authorization` header is not forwarded when redirect leads to a different domain. +To overcome this limitation you have to set the client property `redirect_forward_authorization` to `true`. + +### Features +1. [#89](https://github.com/influxdata/influxdb-client-ruby/pull/89): `Authorization` header is not forwarded when redirect leads to a different domain + +### Bug Fixes +1. [#89](https://github.com/influxdata/influxdb-client-ruby/pull/89): Correct redirect location + ## 1.17.0 [2021-08-20] ### Bug Fixes diff --git a/README.md b/README.md index a5574b7b..027ae29b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This repository contains the reference Ruby client for the InfluxDB 2.0. - [Management API](#management-api) - [Advanced Usage](#advanced-usage) - [Default Tags](#default-tags) + - [Proxy configuration](#proxy-configuration) - [Contributing](#contributing) - [License](#license) @@ -93,6 +94,7 @@ client = InfluxDB2::Client.new('https://localhost:8086', 'my-token') | write_timeout | Number of seconds to wait for one block of data to be written | Integer | 10 | | read_timeout | Number of seconds to wait for one block of data to be read | Integer | 10 | | max_redirect_count | Maximal number of followed HTTP redirects | Integer | 10 | +| redirect_forward_authorization | Pass Authorization header to different domain during HTTP redirect. | bool | false | | use_ssl | Turn on/off SSL for HTTP communication | bool | true | | verify_mode | Sets the flags for the certification verification at beginning of SSL/TLS session. | `OpenSSL::SSL::VERIFY_NONE` or `OpenSSL::SSL::VERIFY_PEER` | none | @@ -400,6 +402,20 @@ client.close! Server availability can be checked using the `client.health` method. That is equivalent of the [influx ping](https://v2.docs.influxdata.com/v2.0/reference/cli/influx/ping/). +### Proxy configuration + +You can configure the client to tunnel requests through an HTTP proxy. To configure the proxy use a `http_proxy` environment variable. + +```ruby +ENV['HTTP_PROXY'] = 'http://my-user:my-password@my-proxy:8099' +``` + +Client automatically follows HTTP redirects. The default redirect policy is to follow up to 10 consecutive requests. +You can configure redirect counts by the client property: `max_redirect_count`. + +Due to a security reason `Authorization` header is not forwarded when redirect leads to a different domain. +To overcome this limitation you have to set the client property `redirect_forward_authorization` to `true`. + ### InfluxDB 1.8 API compatibility [InfluxDB 1.8.0 introduced forward compatibility APIs](https://docs.influxdata.com/influxdb/v1.8/tools/api/#influxdb-2-0-api-compatibility-endpoints) for InfluxDB 2.0. This allow you to easily move from InfluxDB 1.x to InfluxDB 2.0 Cloud or open source. diff --git a/lib/influxdb2/client/client.rb b/lib/influxdb2/client/client.rb index 100c41df..31e83fa8 100644 --- a/lib/influxdb2/client/client.rb +++ b/lib/influxdb2/client/client.rb @@ -42,6 +42,8 @@ class Client # @option options [Integer] :write_timeout Number of seconds to wait for one block of data to be written # @option options [Integer] :read_timeout Number of seconds to wait for one block of data to be read # @option options [Integer] :max_redirect_count Maximal number of followed HTTP redirects + # @option options [bool] :redirect_forward_authorization Pass Authorization header to different domain + # during HTTP redirect. # @option options [bool] :use_ssl Turn on/off SSL for HTTP communication # @option options [Integer] :verify_mode Sets the flags for the certification verification # at beginning of SSL/TLS session. Could be one of `OpenSSL::SSL::VERIFY_NONE` or `OpenSSL::SSL::VERIFY_PEER`. diff --git a/lib/influxdb2/client/default_api.rb b/lib/influxdb2/client/default_api.rb index 6da4aae4..08d5122a 100644 --- a/lib/influxdb2/client/default_api.rb +++ b/lib/influxdb2/client/default_api.rb @@ -73,21 +73,24 @@ def _post_text(payload, uri, headers: {}) _post(payload, uri, headers: headers.merge(HEADER_CONTENT_TYPE => 'text/plain')) end - def _post(payload, uri, limit: @max_redirect_count, headers: {}) - _request(payload, uri, limit: limit, headers: headers, request: Net::HTTP::Post) + def _post(payload, uri, limit: @max_redirect_count, add_authorization: true, headers: {}) + _request(payload, uri, limit: limit, add_authorization: add_authorization, + headers: headers, request: Net::HTTP::Post) end - def _get(uri, limit: @max_redirect_count, headers: {}) - _request(nil, uri, limit: limit, headers: headers.merge('Accept' => 'application/json'), request: Net::HTTP::Get) + def _get(uri, limit: @max_redirect_count, add_authorization: true, headers: {}) + _request(nil, uri, limit: limit, add_authorization: add_authorization, + headers: headers.merge('Accept' => 'application/json'), request: Net::HTTP::Get) end - def _request(payload, uri, limit: @max_redirect_count, headers: {}, request: Net::HTTP::Post) + def _request(payload, uri, limit: @max_redirect_count, add_authorization: true, headers: {}, + request: Net::HTTP::Post) raise InfluxError.from_message("Too many HTTP redirects. Exceeded limit: #{@max_redirect_count}") if limit.zero? http = _prepare_http_client(uri) request = request.new(uri.request_uri) - request['Authorization'] = "Token #{@options[:token]}" + request['Authorization'] = "Token #{@options[:token]}" if add_authorization request['User-Agent'] = "influxdb-client-ruby/#{InfluxDB2::VERSION}" headers.each { |k, v| request[k] = v } @@ -100,7 +103,16 @@ def _request(payload, uri, limit: @max_redirect_count, headers: {}, request: Net response when Net::HTTPRedirection then location = response['location'] - _post(payload, URI.parse(location), limit: limit - 1, headers: headers) + redirect_forward_authorization = @options[:redirect_forward_authorization] || false + + uri_redirect = URI.parse(location) + uri_redirect.query = uri.query + uri_redirect.path = File.join(uri_redirect.path, uri.path) + + redirect_forward_authorization ||= (uri_redirect.host == uri.host) && (uri_redirect.port == uri.port) + + _post(payload, uri_redirect, limit: limit - 1, add_authorization: redirect_forward_authorization, + headers: headers) else raise InfluxError.from_response(response) end diff --git a/lib/influxdb2/client/version.rb b/lib/influxdb2/client/version.rb index e6ca5069..9d00aad7 100644 --- a/lib/influxdb2/client/version.rb +++ b/lib/influxdb2/client/version.rb @@ -19,5 +19,5 @@ # THE SOFTWARE. module InfluxDB2 - VERSION = '1.18.0'.freeze + VERSION = '2.0.0'.freeze end diff --git a/test/influxdb/redirect_test.rb b/test/influxdb/redirect_test.rb new file mode 100644 index 00000000..f458645a --- /dev/null +++ b/test/influxdb/redirect_test.rb @@ -0,0 +1,145 @@ +# The MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +require 'test_helper' + +class WriteApiTest < MiniTest::Test + def setup + WebMock.disable_net_connect! + end + + def test_redirect_same + stub_request(:any, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 307, headers: + { 'location' => 'http://localhost:8086' }) + .then.to_return(status: 204) + stub_request(:any, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 204) + + client = InfluxDB2::Client.new('http://localhost:8086', 'my-token', + bucket: 'my-bucket', + org: 'my-org', + precision: InfluxDB2::WritePrecision::NANOSECOND, + use_ssl: false) + + client.create_write_api.write(data: 'h2o,location=west value=33i 15') + + headers = { + 'Authorization' => 'Token my-token', + 'User-Agent' => "influxdb-client-ruby/#{InfluxDB2::VERSION}", + 'Content-Type' => 'text/plain' + } + + assert_requested(:post, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15', headers: headers) + end + + def test_redirect_301 + stub_request(:any, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 301, headers: + { 'location' => 'http://localhost:9090/' }) + .then.to_return(status: 204) + stub_request(:any, 'http://localhost:9090/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 204) + + client = InfluxDB2::Client.new('http://localhost:8086', 'my-token', + bucket: 'my-bucket', + org: 'my-org', + precision: InfluxDB2::WritePrecision::NANOSECOND, + use_ssl: false) + + client.create_write_api.write(data: 'h2o,location=west value=33i 15') + + headers = { + 'Authorization' => 'Token my-token', + 'User-Agent' => "influxdb-client-ruby/#{InfluxDB2::VERSION}", + 'Content-Type' => 'text/plain' + } + + assert_requested(:post, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15', headers: headers) + + assert_not_requested(:post, 'http://localhost:9090/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15', headers: headers) + + assert_requested(:post, 'http://localhost:9090/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15') + end + + def test_redirect_301_allow + stub_request(:any, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 301, headers: + { 'location' => 'http://localhost:9090/' }) + .then.to_return(status: 204) + stub_request(:any, 'http://localhost:9090/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 204) + + client = InfluxDB2::Client.new('http://localhost:8086', 'my-token', + bucket: 'my-bucket', + org: 'my-org', + precision: InfluxDB2::WritePrecision::NANOSECOND, + use_ssl: false, + redirect_forward_authorization: true) + + client.create_write_api.write(data: 'h2o,location=west value=33i 15') + + headers = { + 'Authorization' => 'Token my-token', + 'User-Agent' => "influxdb-client-ruby/#{InfluxDB2::VERSION}", + 'Content-Type' => 'text/plain' + } + + assert_requested(:post, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15', headers: headers) + + assert_requested(:post, 'http://localhost:9090/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15', headers: headers) + end + + def test_redirect_different_path + stub_request(:any, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 301, headers: + { 'location' => 'http://localhost:8086/influxdb/' }) + .then.to_return(status: 204) + stub_request(:any, 'http://localhost:8086/influxdb/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') + .to_return(status: 204) + + client = InfluxDB2::Client.new('http://localhost:8086', 'my-token', + bucket: 'my-bucket', + org: 'my-org', + precision: InfluxDB2::WritePrecision::NANOSECOND, + use_ssl: false, + redirect_forward_authorization: true) + + client.create_write_api.write(data: 'h2o,location=west value=33i 15') + + headers = { + 'Authorization' => 'Token my-token', + 'User-Agent' => "influxdb-client-ruby/#{InfluxDB2::VERSION}", + 'Content-Type' => 'text/plain' + } + + assert_requested(:post, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15', headers: headers) + + assert_requested(:post, 'http://localhost:8086/influxdb/api/v2/write?bucket=my-bucket&org=my-org&precision=ns', + times: 1, body: 'h2o,location=west value=33i 15', headers: headers) + end +end diff --git a/test/influxdb/write_api_test.rb b/test/influxdb/write_api_test.rb index c1cb6f55..0f895d70 100644 --- a/test/influxdb/write_api_test.rb +++ b/test/influxdb/write_api_test.rb @@ -201,7 +201,7 @@ def test_influx_exception def test_follow_redirect stub_request(:any, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') .to_return(status: 307, headers: - { 'location' => 'http://localhost:9090/api/v2/write?bucket=my-bucket&org=my-org&precision=ns' }) + { 'location' => 'http://localhost:9090/' }) .then.to_return(status: 204) stub_request(:any, 'http://localhost:9090/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') .to_return(status: 204) @@ -223,7 +223,7 @@ def test_follow_redirect def test_follow_redirect_max stub_request(:any, 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns') .to_return(status: 307, headers: - { 'location' => 'http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=ns' }) + { 'location' => 'http://localhost:8086/' }) client = InfluxDB2::Client.new('http://localhost:8086', 'my-token', bucket: 'my-bucket',