Skip to content

Commit

Permalink
[GEM] Extracts api_key from elasticsearch-transport into elasticsearch
Browse files Browse the repository at this point in the history
  • Loading branch information
picandocodigo committed Jun 7, 2021
1 parent 3ddbfc4 commit 7973f8a
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 87 deletions.
1 change: 0 additions & 1 deletion benchmarks/actions/002_info.rb
Expand Up @@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

require_relative '../lib/benchmarks'

Benchmarks.register \
Expand Down
22 changes: 2 additions & 20 deletions elasticsearch-transport/lib/elasticsearch/transport/client.rb
Expand Up @@ -113,8 +113,6 @@ class Client
# The default is false. Responses will automatically be inflated if they are compressed.
# If a custom transport object is used, it must handle the request compression and response inflation.
#
# @option api_key [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
# joined by a colon as a String, or a hash with the `id` and `api_key` values.
# @option opaque_id_prefix [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client.
# This will be prepended to the id you set before each request
# if you're using X-Opaque-Id
Expand All @@ -124,7 +122,7 @@ class Client
# @yield [faraday] Access and configure the `Faraday::Connection` instance directly with a block
#
def initialize(arguments = {}, &block)
@options = arguments.each_with_object({}) { |(k, v), args| args[k.to_sym] = v }
@options = arguments.transform_keys(&:to_sym)
@arguments = @options
@arguments[:logger] ||= @arguments[:log] ? DEFAULT_LOGGER.call() : nil
@arguments[:tracer] ||= @arguments[:trace] ? DEFAULT_TRACER.call() : nil
Expand All @@ -134,11 +132,9 @@ def initialize(arguments = {}, &block)
@arguments[:randomize_hosts] ||= false
@arguments[:transport_options] ||= {}
@arguments[:http] ||= {}
@arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header) { true }
@arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header, true)
@options[:http] ||= {}

set_api_key if (@api_key = @arguments[:api_key])

@hosts = __extract_hosts(@arguments[:hosts] ||
@arguments[:host] ||
@arguments[:url] ||
Expand Down Expand Up @@ -184,13 +180,6 @@ def perform_request(method, path, params = {}, body = nil, headers = nil)

private

def set_api_key
@api_key = __encode(@api_key) if @api_key.is_a? Hash
add_header('Authorization' => "ApiKey #{@api_key}")
@arguments.delete(:user)
@arguments.delete(:password)
end

def add_header(header)
headers = @arguments[:transport_options]&.[](:headers) || {}
headers.merge!(header)
Expand Down Expand Up @@ -297,13 +286,6 @@ def __auto_detect_adapter
::Faraday.default_adapter
end
end

# Encode credentials for the Authorization Header
# Credentials is the base64 encoding of id and api_key joined by a colon
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html
def __encode(api_key)
Base64.strict_encode64([api_key[:id], api_key[:api_key]].join(':'))
end
end
end
end
Expand Up @@ -70,48 +70,6 @@
end
end

context 'when an encoded api_key is provided' do
let(:client) do
described_class.new(api_key: 'an_api_key')
end
let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

it 'Adds the ApiKey header to the connection' do
expect(authorization_header).to eq('ApiKey an_api_key')
end
end

context 'when an un-encoded api_key is provided' do
let(:client) do
described_class.new(api_key: { id: 'my_id', api_key: 'my_api_key' })
end
let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

it 'Adds the ApiKey header to the connection' do
expect(authorization_header).to eq("ApiKey #{Base64.strict_encode64('my_id:my_api_key')}")
end
end

context 'when basic auth and api_key are provided' do
let(:client) do
described_class.new(
api_key: { id: 'my_id', api_key: 'my_api_key' },
host: 'http://elastic:password@localhost:9200'
)
end
let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

it 'removes basic auth credentials' do
expect(authorization_header).not_to match(/^Basic/)
expect(authorization_header).to match(/^ApiKey/)
end
end

context 'when a user-agent header is specified as client option in lower-case' do

Expand Down
Expand Up @@ -78,22 +78,6 @@ def meta_version
end
end

context 'when using API Key' do
let(:client) do
described_class.new(api_key: 'an_api_key', adapter: adapter)
end

let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

it 'adds the ApiKey header to the connection' do
expect(authorization_header).to eq('ApiKey an_api_key')
expect(subject['x-elastic-client-meta']).to match(regexp)
expect(subject).to include('x-elastic-client-meta' => meta_header)
end
end

context 'adapters' do
let(:meta_header) do
if jruby?
Expand Down
25 changes: 19 additions & 6 deletions elasticsearch/lib/elasticsearch.rb
Expand Up @@ -32,8 +32,10 @@ class Client
# Create a client connected to an Elasticsearch cluster.
#
# @option arguments [String] :cloud_id - The Cloud ID to connect to Elastic Cloud
#
# @option api_key [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
# joined by a colon as a String, or a hash with the `id` and `api_key` values.
def initialize(arguments = {}, &block)
api_key(arguments) if arguments[:api_key]
if arguments[:cloud_id]
arguments[:hosts] = setup_cloud_host(
arguments[:cloud_id],
Expand Down Expand Up @@ -73,11 +75,22 @@ def setup_cloud_host(cloud_id, user, password, port)
[{ scheme: 'https', user: user, password: password, host: host, port: port.to_i }]
end

def set_api_key
@api_key = encode(@api_key) if @api_key.is_a? Hash
add_header('Authorization' => "ApiKey #{@api_key}")
@arguments.delete(:user)
@arguments.delete(:password)
def api_key(arguments)
api_key = if arguments[:api_key].is_a? Hash
encode(arguments[:api_key])
else
arguments[:api_key]
end
arguments.delete(:user)
arguments.delete(:password)
authorization = { 'Authorization' => "ApiKey #{api_key}" }
if (headers = arguments.dig(:transport_options, :headers))
headers.merge!(authorization)
else
arguments[:transport_options] = {
headers: authorization
}
end
end

# Encode credentials for the Authorization Header
Expand Down
103 changes: 103 additions & 0 deletions elasticsearch/spec/unit/api_key_spec.rb
@@ -0,0 +1,103 @@
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require 'spec_helper'

describe Elasticsearch::Client do
context 'when using API Key' do
let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

context 'when an encoded api_key is provided' do
let(:client) do
described_class.new(api_key: 'an_api_key')
end

it 'Adds the ApiKey header to the connection' do
expect(authorization_header).to eq('ApiKey an_api_key')
end
end

context 'when an un-encoded api_key is provided' do
let(:client) do
described_class.new(api_key: { id: 'my_id', api_key: 'my_api_key' })
end

it 'Adds the ApiKey header to the connection' do
expect(authorization_header).to eq("ApiKey #{Base64.strict_encode64('my_id:my_api_key')}")
end
end

context 'when basic auth and api_key are provided' do
let(:client) do
described_class.new(
api_key: { id: 'my_id', api_key: 'my_api_key' },
host: 'http://elastic:password@localhost:9200'
)
end

it 'removes basic auth credentials' do
expect(authorization_header).not_to match(/^Basic/)
expect(authorization_header).to match(/^ApiKey/)
end
end

context 'when other headers where specified' do
let(:client) do
described_class.new(
api_key: 'elasticsearch_api_key',
transport_options: { headers: { 'x-test-header' => 'test' } }
)
end

it 'Adds the ApiKey header to the connection and keeps the header' do
header = client.transport.connections.first.connection.headers
expect(header['Authorization']).to eq('ApiKey elasticsearch_api_key')
expect(header['X-Test-Header']).to eq('test')
end
end

context 'Metaheader' do
let(:adapter_code) { "nh=#{defined?(Net::HTTP::VERSION) ? Net::HTTP::VERSION : Net::HTTP::HTTPVersion}" }
let(:meta_header) do
if defined?(JRUBY)
"es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION},jv=#{ENV_JAVA['java.version']},jr=#{JRUBY_VERSION},fd=#{Faraday::VERSION},#{adapter_code}"
else
"es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION},fd=#{Faraday::VERSION},#{adapter_code}"
end
end

def meta_version
client.send(:client_meta_version, Elasticsearch::VERSION)
end
context 'when using API Key' do
let(:client) do
described_class.new(api_key: 'an_api_key')
end

let(:headers) do
client.transport.connections.first.connection.headers
end

it 'adds the ApiKey header to the connection' do
expect(authorization_header).to eq('ApiKey an_api_key')
expect(headers).to include('x-elastic-client-meta' => meta_header)
end
end
end
end
end
2 changes: 1 addition & 1 deletion elasticsearch/spec/unit/cloud_credentials_spec.rb
Expand Up @@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require 'elasticsearch'
require 'spec_helper'

describe Elasticsearch::Client do
context 'when cloud credentials are provided' do
Expand Down
2 changes: 1 addition & 1 deletion elasticsearch/spec/unit/wrapper_gem_spec.rb
Expand Up @@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require 'elasticsearch'
require 'spec_helper'

describe 'Elasticsearch: wrapper gem' do
it 'requires all neccessary subgems' do
Expand Down

0 comments on commit 7973f8a

Please sign in to comment.