nucleus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +1 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +13 -0
- data/Gemfile +16 -0
- data/Guardfile +22 -0
- data/LICENSE +21 -0
- data/README.md +675 -0
- data/Rakefile +137 -0
- data/bin/nucleus +91 -0
- data/bin/nucleus.bat +1 -0
- data/config.ru +18 -0
- data/config/adapters/cloud_control.yml +32 -0
- data/config/adapters/cloud_foundry_v2.yml +61 -0
- data/config/adapters/heroku.yml +13 -0
- data/config/adapters/openshift_v2.yml +20 -0
- data/config/nucleus_config.rb +47 -0
- data/lib/nucleus.rb +13 -0
- data/lib/nucleus/adapter_resolver.rb +115 -0
- data/lib/nucleus/adapters/base_adapter.rb +109 -0
- data/lib/nucleus/adapters/buildpack_translator.rb +79 -0
- data/lib/nucleus/adapters/v1/cloud_control/application.rb +108 -0
- data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_control/buildpacks.rb +23 -0
- data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +153 -0
- data/lib/nucleus/adapters/v1/cloud_control/data.rb +76 -0
- data/lib/nucleus/adapters/v1/cloud_control/domains.rb +68 -0
- data/lib/nucleus/adapters/v1/cloud_control/lifecycle.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_control/log_poller.rb +71 -0
- data/lib/nucleus/adapters/v1/cloud_control/logs.rb +103 -0
- data/lib/nucleus/adapters/v1/cloud_control/regions.rb +32 -0
- data/lib/nucleus/adapters/v1/cloud_control/scaling.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_control/semantic_errors.rb +31 -0
- data/lib/nucleus/adapters/v1/cloud_control/services.rb +162 -0
- data/lib/nucleus/adapters/v1/cloud_control/token.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_control/vars.rb +88 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/app_states.rb +28 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/application.rb +111 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/authentication.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/buildpacks.rb +23 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/cloud_foundry_v2.rb +141 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/data.rb +97 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +149 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/lifecycle.rb +41 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +303 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/regions.rb +33 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/scaling.rb +15 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/semantic_errors.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +286 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/vars.rb +80 -0
- data/lib/nucleus/adapters/v1/heroku/app_states.rb +57 -0
- data/lib/nucleus/adapters/v1/heroku/application.rb +93 -0
- data/lib/nucleus/adapters/v1/heroku/authentication.rb +27 -0
- data/lib/nucleus/adapters/v1/heroku/buildpacks.rb +27 -0
- data/lib/nucleus/adapters/v1/heroku/data.rb +78 -0
- data/lib/nucleus/adapters/v1/heroku/domains.rb +43 -0
- data/lib/nucleus/adapters/v1/heroku/heroku.rb +146 -0
- data/lib/nucleus/adapters/v1/heroku/lifecycle.rb +51 -0
- data/lib/nucleus/adapters/v1/heroku/logs.rb +108 -0
- data/lib/nucleus/adapters/v1/heroku/regions.rb +42 -0
- data/lib/nucleus/adapters/v1/heroku/scaling.rb +28 -0
- data/lib/nucleus/adapters/v1/heroku/semantic_errors.rb +23 -0
- data/lib/nucleus/adapters/v1/heroku/services.rb +168 -0
- data/lib/nucleus/adapters/v1/heroku/vars.rb +65 -0
- data/lib/nucleus/adapters/v1/openshift_v2/app_states.rb +68 -0
- data/lib/nucleus/adapters/v1/openshift_v2/application.rb +108 -0
- data/lib/nucleus/adapters/v1/openshift_v2/authentication.rb +21 -0
- data/lib/nucleus/adapters/v1/openshift_v2/data.rb +96 -0
- data/lib/nucleus/adapters/v1/openshift_v2/domains.rb +37 -0
- data/lib/nucleus/adapters/v1/openshift_v2/lifecycle.rb +60 -0
- data/lib/nucleus/adapters/v1/openshift_v2/logs.rb +106 -0
- data/lib/nucleus/adapters/v1/openshift_v2/openshift_v2.rb +125 -0
- data/lib/nucleus/adapters/v1/openshift_v2/regions.rb +58 -0
- data/lib/nucleus/adapters/v1/openshift_v2/scaling.rb +39 -0
- data/lib/nucleus/adapters/v1/openshift_v2/semantic_errors.rb +40 -0
- data/lib/nucleus/adapters/v1/openshift_v2/services.rb +173 -0
- data/lib/nucleus/adapters/v1/openshift_v2/vars.rb +49 -0
- data/lib/nucleus/adapters/v1/stub_adapter.rb +464 -0
- data/lib/nucleus/core/adapter_authentication_inductor.rb +62 -0
- data/lib/nucleus/core/adapter_extensions/auth/auth_client.rb +44 -0
- data/lib/nucleus/core/adapter_extensions/auth/authentication_retry_wrapper.rb +79 -0
- data/lib/nucleus/core/adapter_extensions/auth/expiring_token_auth_client.rb +53 -0
- data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +37 -0
- data/lib/nucleus/core/adapter_extensions/auth/o_auth2_auth_client.rb +95 -0
- data/lib/nucleus/core/adapter_extensions/auth/token_auth_client.rb +36 -0
- data/lib/nucleus/core/adapter_extensions/http_client.rb +177 -0
- data/lib/nucleus/core/adapter_extensions/http_tail_client.rb +26 -0
- data/lib/nucleus/core/adapter_extensions/tail_stopper.rb +25 -0
- data/lib/nucleus/core/common/errors/ambiguous_adapter_error.rb +7 -0
- data/lib/nucleus/core/common/errors/file_existence_error.rb +7 -0
- data/lib/nucleus/core/common/errors/startup_error.rb +12 -0
- data/lib/nucleus/core/common/exit_codes.rb +25 -0
- data/lib/nucleus/core/common/files/application_repo_sanitizer.rb +52 -0
- data/lib/nucleus/core/common/files/archive_extractor.rb +112 -0
- data/lib/nucleus/core/common/files/archiver.rb +91 -0
- data/lib/nucleus/core/common/link_generator.rb +46 -0
- data/lib/nucleus/core/common/logging/logging.rb +52 -0
- data/lib/nucleus/core/common/logging/multi_logger.rb +59 -0
- data/lib/nucleus/core/common/logging/request_log_formatter.rb +48 -0
- data/lib/nucleus/core/common/ssh_handler.rb +108 -0
- data/lib/nucleus/core/common/stream_callback.rb +27 -0
- data/lib/nucleus/core/common/thread_config_accessor.rb +85 -0
- data/lib/nucleus/core/common/url_converter.rb +28 -0
- data/lib/nucleus/core/enums/application_states.rb +26 -0
- data/lib/nucleus/core/enums/logfile_types.rb +28 -0
- data/lib/nucleus/core/error_messages.rb +127 -0
- data/lib/nucleus/core/errors/adapter_error.rb +13 -0
- data/lib/nucleus/core/errors/adapter_missing_implementation_error.rb +12 -0
- data/lib/nucleus/core/errors/adapter_request_error.rb +10 -0
- data/lib/nucleus/core/errors/adapter_resource_not_found_error.rb +10 -0
- data/lib/nucleus/core/errors/endpoint_authentication_error.rb +10 -0
- data/lib/nucleus/core/errors/platform_specific_semantic_error.rb +12 -0
- data/lib/nucleus/core/errors/platform_timeout_error.rb +10 -0
- data/lib/nucleus/core/errors/platform_unavailable_error.rb +10 -0
- data/lib/nucleus/core/errors/semantic_adapter_request_error.rb +19 -0
- data/lib/nucleus/core/errors/unknown_adapter_call_error.rb +10 -0
- data/lib/nucleus/core/file_handling/archive_converter.rb +29 -0
- data/lib/nucleus/core/file_handling/file_manager.rb +64 -0
- data/lib/nucleus/core/file_handling/git_deployer.rb +133 -0
- data/lib/nucleus/core/file_handling/git_repo_analyzer.rb +23 -0
- data/lib/nucleus/core/import/adapter_configuration.rb +53 -0
- data/lib/nucleus/core/import/vendor_parser.rb +28 -0
- data/lib/nucleus/core/import/version_detector.rb +18 -0
- data/lib/nucleus/core/models/abstract_model.rb +29 -0
- data/lib/nucleus/core/models/endpoint.rb +30 -0
- data/lib/nucleus/core/models/provider.rb +26 -0
- data/lib/nucleus/core/models/vendor.rb +22 -0
- data/lib/nucleus/ext/kernel.rb +5 -0
- data/lib/nucleus/ext/regexp.rb +49 -0
- data/lib/nucleus/os.rb +15 -0
- data/lib/nucleus/root_dir.rb +13 -0
- data/lib/nucleus/scripts/finalize.rb +8 -0
- data/lib/nucleus/scripts/initialize.rb +9 -0
- data/lib/nucleus/scripts/initialize_config_defaults.rb +26 -0
- data/lib/nucleus/scripts/load.rb +17 -0
- data/lib/nucleus/scripts/load_dependencies.rb +43 -0
- data/lib/nucleus/scripts/setup_config.rb +28 -0
- data/lib/nucleus/scripts/shutdown.rb +11 -0
- data/lib/nucleus/version.rb +3 -0
- data/nucleus.gemspec +88 -0
- data/public/robots.txt +2 -0
- data/public/swagger-ui/css/reset.css +125 -0
- data/public/swagger-ui/css/screen.css +1224 -0
- data/public/swagger-ui/images/apple-touch-icon-114x114.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-120x120.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-144x144.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-152x152.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-57x57.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-60x60.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-72x72.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-76x76.png +0 -0
- data/public/swagger-ui/images/explorer_icons.png +0 -0
- data/public/swagger-ui/images/favicon-128.png +0 -0
- data/public/swagger-ui/images/favicon-16x16.png +0 -0
- data/public/swagger-ui/images/favicon-196x196.png +0 -0
- data/public/swagger-ui/images/favicon-32x32.png +0 -0
- data/public/swagger-ui/images/favicon-96x96.png +0 -0
- data/public/swagger-ui/images/favicon.ico +0 -0
- data/public/swagger-ui/images/logo_small.png +0 -0
- data/public/swagger-ui/images/mstile-144x144.png +0 -0
- data/public/swagger-ui/images/mstile-150x150.png +0 -0
- data/public/swagger-ui/images/mstile-310x150.png +0 -0
- data/public/swagger-ui/images/mstile-310x310.png +0 -0
- data/public/swagger-ui/images/mstile-70x70.png +0 -0
- data/public/swagger-ui/images/pet_store_api.png +0 -0
- data/public/swagger-ui/images/throbber.gif +0 -0
- data/public/swagger-ui/images/wordnik_api.png +0 -0
- data/public/swagger-ui/index.html +107 -0
- data/public/swagger-ui/lib/backbone-min.js +38 -0
- data/public/swagger-ui/lib/handlebars-1.0.0.js +2278 -0
- data/public/swagger-ui/lib/highlight.7.3.pack.js +1 -0
- data/public/swagger-ui/lib/jquery-1.8.0.min.js +2 -0
- data/public/swagger-ui/lib/jquery.ba-bbq.min.js +18 -0
- data/public/swagger-ui/lib/jquery.slideto.min.js +1 -0
- data/public/swagger-ui/lib/jquery.wiggle.min.js +8 -0
- data/public/swagger-ui/lib/shred.bundle.js +2765 -0
- data/public/swagger-ui/lib/shred/content.js +193 -0
- data/public/swagger-ui/lib/swagger-oauth.js +211 -0
- data/public/swagger-ui/lib/swagger.js +1653 -0
- data/public/swagger-ui/lib/underscore-min.js +32 -0
- data/public/swagger-ui/o2c.html +15 -0
- data/public/swagger-ui/redirect.html +14 -0
- data/public/swagger-ui/swagger-ui.js +2324 -0
- data/public/swagger-ui/swagger-ui.min.js +1 -0
- data/schemas/api.adapter.schema.yml +31 -0
- data/schemas/api.requirements.schema.yml +17 -0
- data/spec/factories/models.rb +61 -0
- data/spec/integration/api/auth_spec.rb +58 -0
- data/spec/integration/api/endpoints_spec.rb +167 -0
- data/spec/integration/api/errors_spec.rb +47 -0
- data/spec/integration/api/providers_spec.rb +157 -0
- data/spec/integration/api/swagger_schema_spec.rb +64 -0
- data/spec/integration/api/vendors_spec.rb +45 -0
- data/spec/integration/integration_spec_helper.rb +27 -0
- data/spec/integration/test_data_generator.rb +55 -0
- data/spec/nucleus_git_key.pem +51 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/shared_example_request_types.rb +99 -0
- data/spec/test_suites.rake +31 -0
- data/spec/unit/adapters/archive_converter_spec.rb +25 -0
- data/spec/unit/adapters/file_manager_spec.rb +93 -0
- data/spec/unit/adapters/git_deployer_spec.rb +262 -0
- data/spec/unit/adapters/v1/stub_spec.rb +14 -0
- data/spec/unit/common/helpers/auth_helper_spec.rb +73 -0
- data/spec/unit/common/oauth2_auth_client_spec.rb +108 -0
- data/spec/unit/common/regexp_spec.rb +33 -0
- data/spec/unit/common/request_log_formatter_spec.rb +108 -0
- data/spec/unit/common/thread_config_accessor_spec.rb +97 -0
- data/spec/unit/models/endpoint_spec.rb +83 -0
- data/spec/unit/models/provider_spec.rb +102 -0
- data/spec/unit/models/vendor_spec.rb +100 -0
- data/spec/unit/schemas/adapter_schema_spec.rb +16 -0
- data/spec/unit/schemas/adapter_validation_spec.rb +56 -0
- data/spec/unit/schemas/requirements_schema_spec.rb +16 -0
- data/spec/unit/unit_spec_helper.rb +11 -0
- data/tasks/compatibility.rake +113 -0
- data/tasks/evaluation.rake +162 -0
- data/wiki/adapter_tests.md +99 -0
- data/wiki/implement_new_adapter.md +155 -0
- metadata +836 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
module Nucleus
|
2
|
+
# The {AdapterAuthenticationInductor} patches adapter classes so that each method that is defined in the AdapterStub
|
3
|
+
# of the current API version is wrapped with the {Nucleus::Adapters::AuthenticationRetryWrapper}.
|
4
|
+
module AdapterAuthenticationInductor
|
5
|
+
include Nucleus::Logging
|
6
|
+
|
7
|
+
# Patch the adapter instance and use the authentication information of the environment.
|
8
|
+
# @param [Nucleus::Adapters::BaseAdapter] adapter_instance the adapter implementation instance to patch
|
9
|
+
# @param [Hash<String, String>] env environment which includes the HTTP authentication header
|
10
|
+
# @return [void]
|
11
|
+
def self.patch(adapter_instance, env)
|
12
|
+
stub_class(adapter_instance).instance_methods(false).each do |method_to_wrap|
|
13
|
+
# wrap method with authentication repetition call
|
14
|
+
patch_method(adapter_instance, method_to_wrap, env)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Patch the actual method that is defined in an API version stub.
|
21
|
+
# The method shall than be able to update the authentication token if the initial authentication expired.<br>
|
22
|
+
# Only major authentication issues, e.g. if the credentials are repeatedly rejected,
|
23
|
+
# will be thrown to the adapter caller.
|
24
|
+
# @param [Nucleus::Adapters::BaseAdapter] adapter the adapter implementation to patch
|
25
|
+
# @param [Symbol] method_to_wrap method that shall be patched
|
26
|
+
# @param [Hash<String, String>] env environment which includes the HTTP authentication header
|
27
|
+
# @return [void]
|
28
|
+
# @private
|
29
|
+
def self.patch_method(adapter, method_to_wrap, env)
|
30
|
+
with_wrapper = :"#{method_to_wrap}_with_before_each_method_call"
|
31
|
+
without_wrapper = :"#{method_to_wrap}_without_before_each_method_call"
|
32
|
+
# patching should be done only once, return if method is already patched (!)
|
33
|
+
return if adapter.respond_to?(with_wrapper) && adapter.respond_to?(without_wrapper)
|
34
|
+
|
35
|
+
@__last_methods_added = [method_to_wrap, with_wrapper, without_wrapper]
|
36
|
+
# wrap the method call
|
37
|
+
adapter.class.send :define_method, with_wrapper do |*args, &block|
|
38
|
+
log.debug "Calling adapter method '#{method_to_wrap}' against #{endpoint_url}"
|
39
|
+
# use the AuthenticationRetryWrapper to retry calls if tokens expired, ...
|
40
|
+
Nucleus::Adapters::AuthenticationRetryWrapper.with_authentication(adapter, env) do
|
41
|
+
return send without_wrapper, *args, &block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
# now do the actual method re-assignment
|
45
|
+
adapter.class.send :define_method, without_wrapper, adapter.method(method_to_wrap)
|
46
|
+
adapter.class.send :define_method, method_to_wrap, adapter.method(with_wrapper)
|
47
|
+
@__last_methods_added = nil
|
48
|
+
end
|
49
|
+
private_class_method :patch_method
|
50
|
+
|
51
|
+
# @private
|
52
|
+
def self.stub_class(adapter)
|
53
|
+
parent = adapter.class
|
54
|
+
loop do
|
55
|
+
break if parent.superclass == Adapters::BaseAdapter
|
56
|
+
parent = parent.superclass
|
57
|
+
end
|
58
|
+
parent
|
59
|
+
end
|
60
|
+
private_class_method :stub_class
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
class AuthClient
|
4
|
+
attr_reader :verify_ssl
|
5
|
+
|
6
|
+
# Create a new instance of an {AuthClient}.
|
7
|
+
# @param [Boolean] check_certificates true if SSL certificates are to be validated,
|
8
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
9
|
+
def initialize(check_certificates = true)
|
10
|
+
@verify_ssl = check_certificates
|
11
|
+
end
|
12
|
+
|
13
|
+
# Perform authentication with the given username and password at the desired endpoint.
|
14
|
+
# @param[String] username username to use for authentication
|
15
|
+
# @param[String] password password to the username, which is to be used for authentication
|
16
|
+
# @raise[Nucleus::Errors::EndpointAuthenticationError] if authentication failed
|
17
|
+
# @raise[Nucleus::Errors::UnknownAdapterCallError] if the generic AuthClient did expect the endpoint
|
18
|
+
# to behave differently, usually indicates implementation issues
|
19
|
+
# @return[Nucleus::Adapters::AuthClient] current AuthClient instance
|
20
|
+
def authenticate(username, password)
|
21
|
+
fail Errors::EndpointAuthenticationError, 'Authentication client does not support authentication'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get the authentication header for the current AuthClient instance that must be used to execute requests
|
25
|
+
# against the endpoint.<br>
|
26
|
+
# If the authentication is known to be expired, a refresh will be made first.
|
27
|
+
# @raise[Nucleus::Errors::EndpointAuthenticationError] if the refresh failed
|
28
|
+
# @return[Hash<String, String>] authentication header that enables requests against the endpoint
|
29
|
+
def auth_header
|
30
|
+
fail Errors::EndpointAuthenticationError,
|
31
|
+
'Authentication client does not support to create the authentication header'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Refresh a rejected authentication and generate a new authentication header.<br>
|
35
|
+
# Should be called if the authentication is known to be expired, or when a request is rejected with an
|
36
|
+
# authentication header that used to be accepted.
|
37
|
+
# @raise [Nucleus::Errors::EndpointAuthenticationError] if token refresh failed or authentication never succeeded
|
38
|
+
# @return [Nucleus::Adapters::AuthClient] current AuthClient instance
|
39
|
+
def refresh
|
40
|
+
fail Errors::EndpointAuthenticationError, 'Authentication client does not support refresh'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
# The AuthenticationRetryWrapper module can be used to invoke commands in a block that repeats its execution in case
|
4
|
+
# the first attempt raises an {Nucleus::Errors::EndpointAuthenticationError}.
|
5
|
+
module AuthenticationRetryWrapper
|
6
|
+
extend Nucleus::Logging
|
7
|
+
|
8
|
+
# Executes a block, which should be an adapter call, using the authentication information.
|
9
|
+
# If the first call fails due to cached authentication information, the cache is going to get evicted,
|
10
|
+
# authentication repeated and finally the call will be executed again.
|
11
|
+
#
|
12
|
+
# @param [Nucleus::Adapters::BaseAdapter] adapter adapter that is used for the ongoing request
|
13
|
+
# @param [Hash<String, String>] env Rack environment, shall contain HTTP authentication information
|
14
|
+
# @return [Hash, void] result of the yield block execution, usually a Hash matching the Grape::Entity to represent
|
15
|
+
def self.with_authentication(adapter, env)
|
16
|
+
begin
|
17
|
+
response = yield
|
18
|
+
rescue Errors::EndpointAuthenticationError
|
19
|
+
# first attempt with actually valid credentials failed, try to refresh token based clients
|
20
|
+
username, password = username_password(env)
|
21
|
+
begin
|
22
|
+
auth_client = adapter.cached(adapter.cache_key(username, password))
|
23
|
+
auth_client.refresh
|
24
|
+
response = yield
|
25
|
+
rescue Errors::EndpointAuthenticationError
|
26
|
+
# refresh failed, too
|
27
|
+
log.debug 'Call failed (401), start repetition by removing outdated cache entry'
|
28
|
+
re_authenticate(adapter, env)
|
29
|
+
log.debug 'Repeating call block...'
|
30
|
+
response = yield
|
31
|
+
log.debug '... the repetition did pass just fine!'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
response
|
35
|
+
end
|
36
|
+
|
37
|
+
# Try to refresh a token based authentication that can be renewed.
|
38
|
+
# The method shall only be invoked when there are cached authentication information
|
39
|
+
# that appear to be outdated.<br>
|
40
|
+
# If the refresh fails, a complete re-authentication will be forced.
|
41
|
+
#
|
42
|
+
# @param [Nucleus::Adapters::AuthClient] auth_client platform specific version of the authentication client
|
43
|
+
# @raise [Nucleus::Errors::EndpointAuthenticationError] if both, refresh and authentication fail
|
44
|
+
# @return [void]
|
45
|
+
def self.refresh_token(auth_client)
|
46
|
+
# we first try to renew our token before invalidating the cache
|
47
|
+
log.debug 'Call failed (401), start refreshing auth token'
|
48
|
+
auth_client.refresh unless auth_client.nil?
|
49
|
+
log.debug '... the auth token refresh succeeded'
|
50
|
+
end
|
51
|
+
|
52
|
+
# Re-authenticate the user with the help of the current adapter.
|
53
|
+
# The method shall only be invoked when there are cached authentication information that appear to be outdated.
|
54
|
+
# It calls the authentication for the current user to override the cached authentication headers.
|
55
|
+
#
|
56
|
+
# @param [Nucleus::Adapters::BaseAdapter] adapter adapter that is used for the ongoing request
|
57
|
+
# @param [Hash<String, String>] env Rack environment, shall contain HTTP authentication information
|
58
|
+
# @raise [Nucleus::Errors::EndpointAuthenticationError] if authentication at the endpoint fails
|
59
|
+
# @return [void]
|
60
|
+
def self.re_authenticate(adapter, env)
|
61
|
+
log.debug('Invoked re-authentication')
|
62
|
+
username, password = username_password(env)
|
63
|
+
auth_client = adapter.cached(adapter.cache_key(username, password))
|
64
|
+
# raises 401 if the authentication did not only expire, but became completely invalid
|
65
|
+
auth_client.authenticate(username, password)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Extract the username and password from the current HTTP request.
|
69
|
+
# @param [Hash<String, String>] env Rack environment, shall contain HTTP authentication information
|
70
|
+
# @return [Array<String>] username at response[0], password at response[1]
|
71
|
+
def self.username_password(env)
|
72
|
+
# resolve username & password for authentication request
|
73
|
+
auth_keys = %w(HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION)
|
74
|
+
authorization_key = auth_keys.detect { |k| env.key?(k) }
|
75
|
+
env[authorization_key].split(' ', 2).last.unpack('m*').first.split(/:/, 2)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
class ExpiringTokenAuthClient < TokenAuthClient
|
4
|
+
# Create a new instance of an {ExpiringTokenAuthClient}. An expiring token knows when it starts to be invalid,
|
5
|
+
# saving requests to the endpoint that would fail anyways.
|
6
|
+
# @param [Boolean] check_certificates true if SSL certificates are to be validated,
|
7
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
8
|
+
# @yield [verify_ssl, username, password] Auth credentials token parser block,
|
9
|
+
# must provide the API token and its expiration date, usually retrieved from an HTTP call to the endpoint.
|
10
|
+
# @yieldparam [Boolean] verify_ssl true if SSL certificates are to be validated,
|
11
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
12
|
+
# @yieldparam [String] username username to be used to retrieve the API token
|
13
|
+
# @yieldparam [String] password password to be used to retrieve the API token
|
14
|
+
# @yieldreturn [Array<String>] Array with 2 contents:
|
15
|
+
# [0] = API token to be used for authenticated API requests,
|
16
|
+
# nil if authentication failed, e.g. due to bad credentials
|
17
|
+
# [1] = Expiration time until the token is valid
|
18
|
+
def initialize(check_certificates = true, &token_expiration_parser)
|
19
|
+
@token_expiration_parser = token_expiration_parser
|
20
|
+
super(check_certificates)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @see AuthClient#authenticate
|
24
|
+
def authenticate(username, password)
|
25
|
+
token, expiration_time = @token_expiration_parser.call(verify_ssl, username, password)
|
26
|
+
fail Errors::EndpointAuthenticationError, 'Authentication failed, credentials seem to be invalid' unless token
|
27
|
+
# verification passed, credentials are valid
|
28
|
+
@api_token = token
|
29
|
+
@expires = expiration_time
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# @see AuthClient#auth_header
|
34
|
+
def auth_header
|
35
|
+
fail Errors::EndpointAuthenticationError, 'Authentication client was not authenticated yet' unless @api_token
|
36
|
+
fail Errors::EndpointAuthenticationError, 'Cached authentication token expired' if expired?
|
37
|
+
{ 'Authorization' => "Bearer #{api_token}" }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Checks if the token is expired.
|
43
|
+
# @return [true, false] true if the token is expired, false if it is still valid
|
44
|
+
def expired?
|
45
|
+
@expires >= Time.now
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
api_token
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
# Implementation of the AuthClient that works with the HTTP basic authentication.
|
4
|
+
class HttpBasicAuthClient < AuthClient
|
5
|
+
# Create a new instance of an {HttpBasicAuthClient}.
|
6
|
+
# @param [Boolean] check_certificates true if SSL certificates are to be validated,
|
7
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
8
|
+
# @yield [verify_ssl, username, password] Auth credentials verification block,
|
9
|
+
# must check if the combination of username and password is accepted by the endpoint.
|
10
|
+
# @yieldparam [Hash<String,String>] headers headers for an HTTP request,
|
11
|
+
# including the authentication header to be tested
|
12
|
+
# @yieldreturn [Boolean] true if the authentication was verified to be ok,
|
13
|
+
# false if an error occurred, e.g. with bad credentials
|
14
|
+
def initialize(check_certificates = true, &verification)
|
15
|
+
@verification = verification
|
16
|
+
super(check_certificates)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @see AuthClient#authenticate
|
20
|
+
def authenticate(username, password)
|
21
|
+
packed_credentials = ["#{username}:#{password}"].pack('m*').gsub(/\n/, '')
|
22
|
+
valid = @verification.call(verify_ssl, 'Authorization' => "Basic #{packed_credentials}")
|
23
|
+
fail Errors::EndpointAuthenticationError, 'Authentication failed, credentials seem to be invalid' unless valid
|
24
|
+
# verification passed, credentials are valid
|
25
|
+
@packed_credentials = packed_credentials
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see AuthClient#auth_header
|
30
|
+
def auth_header
|
31
|
+
fail Errors::EndpointAuthenticationError,
|
32
|
+
'Authentication client was not authenticated yet' unless @packed_credentials
|
33
|
+
{ 'Authorization' => "Basic #{@packed_credentials}" }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
class OAuth2AuthClient < AuthClient
|
4
|
+
include Nucleus::Logging
|
5
|
+
|
6
|
+
# Create a new instance of an {OAuth2AuthClient}, which uses the standardized OAuth2 authentication method.
|
7
|
+
# @param [Boolean] check_certificates true if SSL certificates are to be validated,
|
8
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
9
|
+
# @param [String] auth_url URL to the OAuth2 endpoint
|
10
|
+
def initialize(auth_url, check_certificates = true)
|
11
|
+
@auth_url = auth_url
|
12
|
+
super(check_certificates)
|
13
|
+
end
|
14
|
+
|
15
|
+
def authenticate(username, password)
|
16
|
+
return self if @access_token
|
17
|
+
response = post(query: { grant_type: 'password', username: username, password: password })
|
18
|
+
body = body(response)
|
19
|
+
extract(body)
|
20
|
+
# refresh token is not included in later updates
|
21
|
+
@refresh_token = body[:refresh_token]
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def auth_header
|
26
|
+
fail Errors::EndpointAuthenticationError, 'Authentication client was not authenticated yet' unless @access_token
|
27
|
+
if expired?
|
28
|
+
log.debug('OAuth2 access_token is expired, trigger refresh before returning auth_header')
|
29
|
+
# token is expired, renew first
|
30
|
+
refresh
|
31
|
+
end
|
32
|
+
# then return the authorization header
|
33
|
+
header
|
34
|
+
end
|
35
|
+
|
36
|
+
def refresh
|
37
|
+
if @refresh_token.nil?
|
38
|
+
fail Errors::EndpointAuthenticationError, "Can't refresh token before initial authentication"
|
39
|
+
end
|
40
|
+
log.debug("Attempt to refresh the access_token with our refresh_token: '#{@refresh_token}'")
|
41
|
+
response = post(query: { grant_type: 'refresh_token', refresh_token: @refresh_token })
|
42
|
+
extract(body(response))
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def post(params)
|
49
|
+
middleware = Excon.defaults[:middlewares].dup
|
50
|
+
middleware << Excon::Middleware::Decompress
|
51
|
+
middleware << Excon::Middleware::RedirectFollower
|
52
|
+
# explicitly allow redirects, otherwise they would cause an error
|
53
|
+
# TODO: Basic Y2Y6 could be cloud-foundry specific
|
54
|
+
request_params = { expects: [200, 301, 302, 303, 307, 308], middlewares: middleware.uniq,
|
55
|
+
headers: { 'Authorization' => 'Basic Y2Y6',
|
56
|
+
'Content-Type' => 'application/x-www-form-urlencoded',
|
57
|
+
'Accept' => 'application/json' } }.merge(params)
|
58
|
+
# execute the post request and return the response
|
59
|
+
Excon.new(@auth_url, ssl_verify_peer: verify_ssl).post(request_params)
|
60
|
+
rescue Excon::Errors::HTTPStatusError => e
|
61
|
+
log.debug "OAuth2 authentication failed: #{e}"
|
62
|
+
case e.response.status
|
63
|
+
when 403
|
64
|
+
log.error("OAuth2 for '#{@auth_url}' failed with status 403 (access denied), indicating an adapter issue")
|
65
|
+
raise Errors::UnknownAdapterCallError, 'Access to resource denied, probably the adapter must be updated'
|
66
|
+
when 400, 401
|
67
|
+
raise Errors::EndpointAuthenticationError, body(e.response)[:error_description]
|
68
|
+
end
|
69
|
+
# re-raise all unhandled exception, indicating adapter issues
|
70
|
+
raise Errors::UnknownAdapterCallError, 'OAuth2 call failed unexpectedly, probably the adapter must be updated'
|
71
|
+
end
|
72
|
+
|
73
|
+
def header
|
74
|
+
{ 'Authorization' => "#{@token_type} #{@access_token}" }
|
75
|
+
end
|
76
|
+
|
77
|
+
def expired?
|
78
|
+
return true if @expiration.nil?
|
79
|
+
Time.now >= @expiration
|
80
|
+
end
|
81
|
+
|
82
|
+
def body(response)
|
83
|
+
Oj.load(response.body, symbol_keys: true)
|
84
|
+
end
|
85
|
+
|
86
|
+
def extract(body)
|
87
|
+
@access_token = body[:access_token]
|
88
|
+
# number of seconds until expiration, deduct processing buffer
|
89
|
+
seconds_left = body[:expires_in] - 30
|
90
|
+
@expiration = Time.now + seconds_left
|
91
|
+
@token_type = body[:token_type]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
class TokenAuthClient < AuthClient
|
4
|
+
attr_reader :api_token
|
5
|
+
|
6
|
+
# Create a new instance of an {TokenAuthClient}.
|
7
|
+
# @param [Boolean] check_certificates true if SSL certificates are to be validated,
|
8
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
9
|
+
# @yield [verify_ssl, username, password] Auth credentials token parser block,
|
10
|
+
# must provide the API token, usually retrieved from an HTTP call to the endpoint.
|
11
|
+
# @yieldparam [Boolean] verify_ssl true if SSL certificates are to be validated,
|
12
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
13
|
+
# @yieldparam [String] username username to be used to retrieve the API token
|
14
|
+
# @yieldparam [String] password password to be used to retrieve the API token
|
15
|
+
# @yieldreturn [String] API token to be used for authenticated API requests,
|
16
|
+
# nil if authentication failed, e.g. due to bad credentials
|
17
|
+
def initialize(check_certificates = true, &token_parser)
|
18
|
+
@token_parser = token_parser
|
19
|
+
super(check_certificates)
|
20
|
+
end
|
21
|
+
|
22
|
+
def authenticate(username, password)
|
23
|
+
token = @token_parser.call(verify_ssl, username, password)
|
24
|
+
fail Errors::EndpointAuthenticationError, 'Authentication failed, credentials seem to be invalid' unless token
|
25
|
+
# verification passed, credentials are valid
|
26
|
+
@api_token = token
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def auth_header
|
31
|
+
fail Errors::EndpointAuthenticationError, 'Authentication client was not authenticated yet' unless @api_token
|
32
|
+
{ 'Authorization' => "Bearer #{api_token}" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module HttpClient
|
4
|
+
# Executes a HEAD request to the given URL.
|
5
|
+
#
|
6
|
+
# @param [String] path path to add to the endpoint URL
|
7
|
+
# @param [Hash] params options to call the post request with
|
8
|
+
# @option params [Array<int>] :expects ([200]) http status code that is expected
|
9
|
+
# @option params [Hash] :headers request headers to use with the request
|
10
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
11
|
+
# unprocessed response
|
12
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
13
|
+
def head(path, params = {})
|
14
|
+
execute_request(:head, [200], path, params, params.delete(:native_call) { false })
|
15
|
+
end
|
16
|
+
|
17
|
+
# Executes a GET request to the given URL.
|
18
|
+
#
|
19
|
+
# @param [String] path path to add to the endpoint URL
|
20
|
+
# @param [Hash] params options to call the post request with
|
21
|
+
# @option params [Array<int>] :expects ([200]) http status code that is expected
|
22
|
+
# @option params [Hash] :headers request headers to use with the request
|
23
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
24
|
+
# unprocessed response
|
25
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
26
|
+
def get(path, params = {})
|
27
|
+
execute_request(:get, [200], path, params, params.delete(:native_call) { false })
|
28
|
+
end
|
29
|
+
|
30
|
+
# Executes a POST request to the given URL.
|
31
|
+
#
|
32
|
+
# @param [String] path path to add to the endpoint URL
|
33
|
+
# @param [Hash] params options to call the post request with
|
34
|
+
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
35
|
+
# @option params [Hash] :body request body, will be converted to json format
|
36
|
+
# @option params [Hash] :headers request headers to use with the request
|
37
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
38
|
+
# unprocessed response
|
39
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
40
|
+
def post(path, params = {})
|
41
|
+
execute_request(:post, [200, 201], path, params, params.delete(:native_call) { false })
|
42
|
+
end
|
43
|
+
|
44
|
+
# Executes a PATCH request to the given URL.
|
45
|
+
#
|
46
|
+
# @param [String] path path to add to the endpoint URL
|
47
|
+
# @param [Hash] params options to call the post request with
|
48
|
+
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
49
|
+
# @option params [Hash] :body request body, will be converted to json format
|
50
|
+
# @option params [Hash] :headers request headers to use with the request
|
51
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
52
|
+
# unprocessed response
|
53
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
54
|
+
def patch(path, params = {})
|
55
|
+
execute_request(:patch, [200, 201], path, params, params.delete(:native_call) { false })
|
56
|
+
end
|
57
|
+
|
58
|
+
# Executes a PUT request to the given URL.
|
59
|
+
#
|
60
|
+
# @param [String] path path to add to the endpoint URL
|
61
|
+
# @param [Hash] params options to call the post request with
|
62
|
+
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
63
|
+
# @option params [Hash] :body request body, will be converted to json format
|
64
|
+
# @option params [Hash] :headers request headers to use with the request
|
65
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
66
|
+
# unprocessed response
|
67
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
68
|
+
def put(path, params = {})
|
69
|
+
execute_request(:put, [200, 201], path, params, params.delete(:native_call) { false })
|
70
|
+
end
|
71
|
+
|
72
|
+
# Executes a DELETE request to the given URL.
|
73
|
+
#
|
74
|
+
# @param [String] path path to add to the endpoint URL
|
75
|
+
# @param [Hash] params options to call the post request with
|
76
|
+
# @option params [Array<int>] :expects ([200,204]) http status code that is expected
|
77
|
+
# @option params [Hash] :headers request headers to use with the request
|
78
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
79
|
+
# unprocessed response
|
80
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
81
|
+
def delete(path, params = {})
|
82
|
+
execute_request(:delete, [200, 204], path, params, params.delete(:native_call) { false })
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def execute_request(method, default_expect, path, params, native_call = false)
|
88
|
+
params[:expects] = default_expect unless params.key? :expects
|
89
|
+
params[:method] = method
|
90
|
+
|
91
|
+
url = Regexp::PERFECT_URL_PATTERN =~ path ? path : to_url(path)
|
92
|
+
response = Excon.new(url, excon_connection_params(params)).request(add_common_request_params(params))
|
93
|
+
# we never want the JSON string, but always the hash representation
|
94
|
+
response.body = hash_of(response.body)
|
95
|
+
response
|
96
|
+
rescue Excon::Errors::HTTPStatusError => e
|
97
|
+
handle_execute_request_error(e, url, native_call)
|
98
|
+
end
|
99
|
+
|
100
|
+
def handle_execute_request_error(e, url, native_call)
|
101
|
+
log.debug 'ERROR, Excon could not execute the request.'
|
102
|
+
# transform json response to Hash object
|
103
|
+
e.response.body = hash_of(e.response.body)
|
104
|
+
|
105
|
+
# if this is a native API call, do not further process the error
|
106
|
+
return e.response if native_call
|
107
|
+
|
108
|
+
# fail with adapter specific error handling
|
109
|
+
handle_error(e.response) if respond_to?(:handle_error)
|
110
|
+
fallback_error_handling(e, url)
|
111
|
+
end
|
112
|
+
|
113
|
+
def fallback_error_handling(e, url)
|
114
|
+
error_status = e.response.status
|
115
|
+
# arriving here, error could not be processed --> use fallback errors
|
116
|
+
if e.is_a? Excon::Errors::ServerError
|
117
|
+
fail Errors::UnknownAdapterCallError, e.message
|
118
|
+
elsif error_status == 404
|
119
|
+
log.error("Resource not found (404) at '#{url}', indicating an adapter issue")
|
120
|
+
fail Errors::UnknownAdapterCallError, 'Resource not found, probably the adapter must be updated'
|
121
|
+
elsif error_status == 401
|
122
|
+
fail Errors::EndpointAuthenticationError,
|
123
|
+
'Auth. failed, probably cache is outdated or permissions were revoked?'
|
124
|
+
else
|
125
|
+
log.error("Fallback error handling (#{error_status}) at '#{url}', indicating an adapter issue")
|
126
|
+
fail Errors::UnknownAdapterCallError, e.message
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_url(path)
|
131
|
+
# insert missing slash, prevent double slashes
|
132
|
+
return "#{@endpoint_url}/#{path}" unless @endpoint_url.end_with?('/') || path.start_with?('/')
|
133
|
+
"#{@endpoint_url}#{path}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def excon_connection_params(params)
|
137
|
+
middleware = Excon.defaults[:middlewares].dup
|
138
|
+
|
139
|
+
if params[:follow_redirects] == false
|
140
|
+
middleware = [Excon::Middleware::ResponseParser, Excon::Middleware::Decompress].push(*middleware).uniq
|
141
|
+
else
|
142
|
+
middleware = [Excon::Middleware::ResponseParser, Excon::Middleware::RedirectFollower,
|
143
|
+
Excon::Middleware::Decompress].push(*middleware).uniq
|
144
|
+
end
|
145
|
+
{ middlewares: middleware, ssl_verify_peer: @check_certificates }
|
146
|
+
end
|
147
|
+
|
148
|
+
def hash_of(message_body)
|
149
|
+
return {} if message_body.nil? || message_body.empty?
|
150
|
+
begin
|
151
|
+
return Oj.load(message_body, symbol_keys: true)
|
152
|
+
rescue Oj::Error
|
153
|
+
# parsing failed, content probably is no valid JSON content
|
154
|
+
message_body
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def add_common_request_params(params)
|
159
|
+
common_params = { connection_timeout: 610, write_timeout: 600, read_timeout: 600 }
|
160
|
+
# allow to follow redirects in the APIs
|
161
|
+
allowed_status_codes = params.key?(:expects) ? [*params[:expects]] : []
|
162
|
+
unless params[:follow_redirects] == false
|
163
|
+
allowed_status_codes.push(*[301, 302, 303, 307, 308])
|
164
|
+
end
|
165
|
+
|
166
|
+
params[:expects] = allowed_status_codes.uniq
|
167
|
+
# use default or customized headers
|
168
|
+
params[:headers] = headers unless params[:headers]
|
169
|
+
# specify encoding if not done yet: use only gzip since deflate does cause issues with VCR cassettes in tests
|
170
|
+
params[:headers]['Accept-Encoding'] = 'gzip' unless params[:headers].key? 'Accept-Encoding'
|
171
|
+
params[:body] = params[:body].to_json if params.key? :body
|
172
|
+
# merge and return
|
173
|
+
common_params.merge params
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|