Skip to content

Commit

Permalink
feat: Authorization header is not forwarded when redirect leads to …
Browse files Browse the repository at this point in the history
…a different domain (#89)
  • Loading branch information
bednar authored Aug 31, 2021
1 parent 346f213 commit c9a3f0a
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 11 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 |

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions lib/influxdb2/client/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
26 changes: 19 additions & 7 deletions lib/influxdb2/client/default_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/influxdb2/client/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
# THE SOFTWARE.

module InfluxDB2
VERSION = '1.18.0'.freeze
VERSION = '2.0.0'.freeze
end
145 changes: 145 additions & 0 deletions test/influxdb/redirect_test.rb
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions test/influxdb/write_api_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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',
Expand Down

0 comments on commit c9a3f0a

Please sign in to comment.