nucleus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|