From 96e4ecc8595919ac1a93eda1c7103d9a2d814cf1 Mon Sep 17 00:00:00 2001 From: Matt Muller <53055821+mullermp@users.noreply.github.com> Date: Thu, 24 Aug 2023 19:56:36 -0400 Subject: [PATCH] Add sign middleware and other improvements (#158) --- .../lib/high_score_service/auth.rb | 12 + .../lib/high_score_service/builders.rb | 36 ++ .../lib/high_score_service/client.rb | 324 +++++++++++++++++- .../lib/high_score_service/config.rb | 27 ++ .../lib/high_score_service/params.rb | 72 ++++ .../lib/high_score_service/parsers.rb | 36 ++ .../lib/high_score_service/stubs.rb | 80 +++++ .../lib/high_score_service/types.rb | 56 +++ .../lib/high_score_service/validators.rb | 48 +++ .../sig/high_score_service/client.rbs | 4 + .../sig/high_score_service/types.rbs | 16 + .../high_score_service/spec/protocol_spec.rb | 16 + .../rails_json/lib/rails_json/client.rb | 36 ++ .../projections/weather/lib/weather/client.rb | 6 + .../white_label/lib/white_label/client.rb | 20 ++ .../white_label/lib/white_label/config.rb | 6 +- .../projections/white_label/spec/auth_spec.rb | 125 +++---- .../integration-specs/auth_spec.rb | 125 +++---- .../amazon/smithy/ruby/codegen/Hearth.java | 5 + .../generators/AuthResolverGenerator.java | 38 +- .../codegen/middleware/MiddlewareBuilder.java | 2 + .../factories/AuthMiddlewareFactory.java | 22 +- .../factories/SignMiddlewareFactory.java | 33 ++ .../model/high-score-service.smithy | 31 +- hearth/lib/hearth/auth_schemes.rb | 1 + hearth/lib/hearth/auth_schemes/anonymous.rb | 5 + hearth/lib/hearth/client_stubs.rb | 4 +- hearth/lib/hearth/context.rb | 22 +- hearth/lib/hearth/http/error_inspector.rb | 2 +- hearth/lib/hearth/http/field.rb | 1 + hearth/lib/hearth/http/fields.rb | 11 +- hearth/lib/hearth/http/request.rb | 28 +- hearth/lib/hearth/http/response.rb | 1 - hearth/lib/hearth/identities.rb | 1 + hearth/lib/hearth/middleware.rb | 1 + hearth/lib/hearth/middleware/auth.rb | 31 +- hearth/lib/hearth/middleware/retry.rb | 23 +- hearth/lib/hearth/middleware/send.rb | 4 +- hearth/lib/hearth/middleware/sign.rb | 45 ++- hearth/lib/hearth/request.rb | 1 - hearth/lib/hearth/response.rb | 3 +- hearth/lib/hearth/retry/adaptive.rb | 2 +- hearth/lib/hearth/retry/standard.rb | 2 +- hearth/lib/hearth/signers.rb | 9 +- hearth/lib/hearth/signers/http_api_key.rb | 23 +- hearth/lib/hearth/signers/http_basic.rb | 17 +- hearth/lib/hearth/signers/http_bearer.rb | 13 +- hearth/lib/hearth/signers/http_digest.rb | 11 +- .../hearth/auth_schemes/anonymous_spec.rb | 11 +- hearth/spec/hearth/context_spec.rb | 23 +- .../spec/hearth/http/error_inspector_spec.rb | 8 +- hearth/spec/hearth/http/fields_spec.rb | 2 +- hearth/spec/hearth/http/request_spec.rb | 45 ++- hearth/spec/hearth/middleware/auth_spec.rb | 14 +- hearth/spec/hearth/middleware/retry_spec.rb | 138 +++++--- hearth/spec/hearth/middleware/send_spec.rb | 157 ++++++--- hearth/spec/hearth/middleware/sign_spec.rb | 110 ++++++ hearth/spec/hearth/response_spec.rb | 10 + .../spec/hearth/signers/http_api_key_spec.rb | 71 ++++ hearth/spec/hearth/signers/http_basic_spec.rb | 37 ++ .../spec/hearth/signers/http_bearer_spec.rb | 32 ++ .../spec/hearth/signers/http_digest_spec.rb | 35 ++ .../app/controllers/auth_controller.rb | 33 ++ sample-service/config/routes.rb | 5 + 64 files changed, 1788 insertions(+), 380 deletions(-) create mode 100644 codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/SignMiddlewareFactory.java create mode 100644 hearth/spec/hearth/middleware/sign_spec.rb create mode 100644 hearth/spec/hearth/signers/http_api_key_spec.rb create mode 100644 hearth/spec/hearth/signers/http_basic_spec.rb create mode 100644 hearth/spec/hearth/signers/http_bearer_spec.rb create mode 100644 hearth/spec/hearth/signers/http_digest_spec.rb create mode 100644 sample-service/app/controllers/auth_controller.rb diff --git a/codegen/projections/high_score_service/lib/high_score_service/auth.rb b/codegen/projections/high_score_service/lib/high_score_service/auth.rb index c82990b3d..e4e2db264 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/auth.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/auth.rb @@ -12,6 +12,10 @@ module Auth Params = Struct.new(:operation_name, keyword_init: true) SCHEMES = [ + Hearth::AuthSchemes::HTTPApiKey.new, + Hearth::AuthSchemes::HTTPBasic.new, + Hearth::AuthSchemes::HTTPBearer.new, + Hearth::AuthSchemes::HTTPDigest.new, Hearth::AuthSchemes::Anonymous.new ].freeze @@ -20,10 +24,18 @@ class Resolver def resolve(auth_params) options = [] case auth_params.operation_name + when :api_key_auth + options << Hearth::AuthOption.new(scheme_id: 'smithy.api#httpApiKeyAuth', signer_properties: { name: 'Authorization', in: 'header' }) + when :basic_auth + options << Hearth::AuthOption.new(scheme_id: 'smithy.api#httpBasicAuth') + when :bearer_auth + options << Hearth::AuthOption.new(scheme_id: 'smithy.api#httpBearerAuth') when :create_high_score options << Hearth::AuthOption.new(scheme_id: 'smithy.api#noAuth') when :delete_high_score options << Hearth::AuthOption.new(scheme_id: 'smithy.api#noAuth') + when :digest_auth + options << Hearth::AuthOption.new(scheme_id: 'smithy.api#httpDigestAuth') when :get_high_score options << Hearth::AuthOption.new(scheme_id: 'smithy.api#noAuth') when :list_high_scores diff --git a/codegen/projections/high_score_service/lib/high_score_service/builders.rb b/codegen/projections/high_score_service/lib/high_score_service/builders.rb index 84014d897..83b66f10a 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/builders.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/builders.rb @@ -11,6 +11,33 @@ module HighScoreService # @api private module Builders + class ApiKeyAuth + def self.build(http_req, input:) + http_req.http_method = 'GET' + http_req.append_path('/api_key_auth') + params = Hearth::Query::ParamList.new + http_req.append_query_param_list(params) + end + end + + class BasicAuth + def self.build(http_req, input:) + http_req.http_method = 'GET' + http_req.append_path('/basic_auth') + params = Hearth::Query::ParamList.new + http_req.append_query_param_list(params) + end + end + + class BearerAuth + def self.build(http_req, input:) + http_req.http_method = 'GET' + http_req.append_path('/bearer_auth') + params = Hearth::Query::ParamList.new + http_req.append_query_param_list(params) + end + end + class CreateHighScore def self.build(http_req, input:) http_req.http_method = 'POST' @@ -41,6 +68,15 @@ def self.build(http_req, input:) end end + class DigestAuth + def self.build(http_req, input:) + http_req.http_method = 'GET' + http_req.append_path('/digest_auth') + params = Hearth::Query::ParamList.new + http_req.append_query_param_list(params) + end + end + class GetHighScore def self.build(http_req, input:) http_req.http_method = 'GET' diff --git a/codegen/projections/high_score_service/lib/high_score_service/client.rb b/codegen/projections/high_score_service/lib/high_score_service/client.rb index e68dbd404..fa56ccacc 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/client.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/client.rb @@ -35,6 +35,219 @@ def initialize(config = HighScoreService::Config.new, options = {}) # @return [Config] config attr_reader :config + # @param [Hash] params + # See {Types::ApiKeyAuthInput}. + # + # @return [Types::ApiKeyAuthOutput] + # + # @example Request syntax with placeholder values + # + # resp = client.api_key_auth() + # + # @example Response structure + # + # resp.data #=> Types::ApiKeyAuthOutput + # + def api_key_auth(params = {}, options = {}, &block) + config = operation_config(options) + stack = Hearth::MiddlewareStack.new + input = Params::ApiKeyAuthInput.build(params, context: 'params') + response_body = ::StringIO.new + stack.use(Hearth::Middleware::Initialize) + stack.use(Hearth::Middleware::Validate, + validator: Validators::ApiKeyAuthInput, + validate_input: config.validate_input + ) + stack.use(Hearth::Middleware::Build, + builder: Builders::ApiKeyAuth + ) + stack.use(Hearth::HTTP::Middleware::ContentLength) + stack.use(Hearth::Middleware::Retry, + retry_strategy: config.retry_strategy, + error_inspector_class: Hearth::HTTP::ErrorInspector + ) + stack.use(Hearth::Middleware::Auth, + auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), + auth_params: Auth::Params.new(operation_name: :api_key_auth), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) + ) + stack.use(Hearth::Middleware::Sign) + stack.use(Hearth::Middleware::Parse, + error_parser: Hearth::HTTP::ErrorParser.new( + error_module: Errors, + success_status: 200, + errors: [] + ), + data_parser: Parsers::ApiKeyAuth + ) + stack.use(Middleware::RequestId) + stack.use(Hearth::Middleware::Send, + stub_responses: config.stub_responses, + client: options.fetch(:http_client, config.http_client), + stub_error_classes: [], + stub_data_class: Stubs::ApiKeyAuth, + stubs: @stubs + ) + resp = stack.run( + input: input, + context: Hearth::Context.new( + request: Hearth::HTTP::Request.new(uri: URI(options.fetch(:endpoint, config.endpoint))), + response: Hearth::HTTP::Response.new(body: response_body), + params: params, + logger: config.logger, + operation_name: :api_key_auth, + interceptors: config.interceptors + ) + ) + raise resp.error if resp.error + resp + end + + # @param [Hash] params + # See {Types::BasicAuthInput}. + # + # @return [Types::BasicAuthOutput] + # + # @example Request syntax with placeholder values + # + # resp = client.basic_auth() + # + # @example Response structure + # + # resp.data #=> Types::BasicAuthOutput + # + def basic_auth(params = {}, options = {}, &block) + config = operation_config(options) + stack = Hearth::MiddlewareStack.new + input = Params::BasicAuthInput.build(params, context: 'params') + response_body = ::StringIO.new + stack.use(Hearth::Middleware::Initialize) + stack.use(Hearth::Middleware::Validate, + validator: Validators::BasicAuthInput, + validate_input: config.validate_input + ) + stack.use(Hearth::Middleware::Build, + builder: Builders::BasicAuth + ) + stack.use(Hearth::HTTP::Middleware::ContentLength) + stack.use(Hearth::Middleware::Retry, + retry_strategy: config.retry_strategy, + error_inspector_class: Hearth::HTTP::ErrorInspector + ) + stack.use(Hearth::Middleware::Auth, + auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), + auth_params: Auth::Params.new(operation_name: :basic_auth), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) + ) + stack.use(Hearth::Middleware::Sign) + stack.use(Hearth::Middleware::Parse, + error_parser: Hearth::HTTP::ErrorParser.new( + error_module: Errors, + success_status: 200, + errors: [] + ), + data_parser: Parsers::BasicAuth + ) + stack.use(Middleware::RequestId) + stack.use(Hearth::Middleware::Send, + stub_responses: config.stub_responses, + client: options.fetch(:http_client, config.http_client), + stub_error_classes: [], + stub_data_class: Stubs::BasicAuth, + stubs: @stubs + ) + resp = stack.run( + input: input, + context: Hearth::Context.new( + request: Hearth::HTTP::Request.new(uri: URI(options.fetch(:endpoint, config.endpoint))), + response: Hearth::HTTP::Response.new(body: response_body), + params: params, + logger: config.logger, + operation_name: :basic_auth, + interceptors: config.interceptors + ) + ) + raise resp.error if resp.error + resp + end + + # @param [Hash] params + # See {Types::BearerAuthInput}. + # + # @return [Types::BearerAuthOutput] + # + # @example Request syntax with placeholder values + # + # resp = client.bearer_auth() + # + # @example Response structure + # + # resp.data #=> Types::BearerAuthOutput + # + def bearer_auth(params = {}, options = {}, &block) + config = operation_config(options) + stack = Hearth::MiddlewareStack.new + input = Params::BearerAuthInput.build(params, context: 'params') + response_body = ::StringIO.new + stack.use(Hearth::Middleware::Initialize) + stack.use(Hearth::Middleware::Validate, + validator: Validators::BearerAuthInput, + validate_input: config.validate_input + ) + stack.use(Hearth::Middleware::Build, + builder: Builders::BearerAuth + ) + stack.use(Hearth::HTTP::Middleware::ContentLength) + stack.use(Hearth::Middleware::Retry, + retry_strategy: config.retry_strategy, + error_inspector_class: Hearth::HTTP::ErrorInspector + ) + stack.use(Hearth::Middleware::Auth, + auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), + auth_params: Auth::Params.new(operation_name: :bearer_auth), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) + ) + stack.use(Hearth::Middleware::Sign) + stack.use(Hearth::Middleware::Parse, + error_parser: Hearth::HTTP::ErrorParser.new( + error_module: Errors, + success_status: 200, + errors: [] + ), + data_parser: Parsers::BearerAuth + ) + stack.use(Middleware::RequestId) + stack.use(Hearth::Middleware::Send, + stub_responses: config.stub_responses, + client: options.fetch(:http_client, config.http_client), + stub_error_classes: [], + stub_data_class: Stubs::BearerAuth, + stubs: @stubs + ) + resp = stack.run( + input: input, + context: Hearth::Context.new( + request: Hearth::HTTP::Request.new(uri: URI(options.fetch(:endpoint, config.endpoint))), + response: Hearth::HTTP::Response.new(body: response_body), + params: params, + logger: config.logger, + operation_name: :bearer_auth, + interceptors: config.interceptors + ) + ) + raise resp.error if resp.error + resp + end + # Create a new high score # # @param [Hash] params @@ -84,10 +297,14 @@ def create_high_score(params = {}, options = {}, &block) error_inspector_class: Hearth::HTTP::ErrorInspector ) stack.use(Hearth::Middleware::Auth, - auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), - auth_params: Auth::Params.new(operation_name: :create_high_score) + auth_params: Auth::Params.new(operation_name: :create_high_score), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -158,10 +375,14 @@ def delete_high_score(params = {}, options = {}, &block) error_inspector_class: Hearth::HTTP::ErrorInspector ) stack.use(Hearth::Middleware::Auth, - auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), - auth_params: Auth::Params.new(operation_name: :delete_high_score) + auth_params: Auth::Params.new(operation_name: :delete_high_score), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -193,6 +414,77 @@ def delete_high_score(params = {}, options = {}, &block) resp end + # @param [Hash] params + # See {Types::DigestAuthInput}. + # + # @return [Types::DigestAuthOutput] + # + # @example Request syntax with placeholder values + # + # resp = client.digest_auth() + # + # @example Response structure + # + # resp.data #=> Types::DigestAuthOutput + # + def digest_auth(params = {}, options = {}, &block) + config = operation_config(options) + stack = Hearth::MiddlewareStack.new + input = Params::DigestAuthInput.build(params, context: 'params') + response_body = ::StringIO.new + stack.use(Hearth::Middleware::Initialize) + stack.use(Hearth::Middleware::Validate, + validator: Validators::DigestAuthInput, + validate_input: config.validate_input + ) + stack.use(Hearth::Middleware::Build, + builder: Builders::DigestAuth + ) + stack.use(Hearth::HTTP::Middleware::ContentLength) + stack.use(Hearth::Middleware::Retry, + retry_strategy: config.retry_strategy, + error_inspector_class: Hearth::HTTP::ErrorInspector + ) + stack.use(Hearth::Middleware::Auth, + auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), + auth_params: Auth::Params.new(operation_name: :digest_auth), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) + ) + stack.use(Hearth::Middleware::Sign) + stack.use(Hearth::Middleware::Parse, + error_parser: Hearth::HTTP::ErrorParser.new( + error_module: Errors, + success_status: 200, + errors: [] + ), + data_parser: Parsers::DigestAuth + ) + stack.use(Middleware::RequestId) + stack.use(Hearth::Middleware::Send, + stub_responses: config.stub_responses, + client: options.fetch(:http_client, config.http_client), + stub_error_classes: [], + stub_data_class: Stubs::DigestAuth, + stubs: @stubs + ) + resp = stack.run( + input: input, + context: Hearth::Context.new( + request: Hearth::HTTP::Request.new(uri: URI(options.fetch(:endpoint, config.endpoint))), + response: Hearth::HTTP::Response.new(body: response_body), + params: params, + logger: config.logger, + operation_name: :digest_auth, + interceptors: config.interceptors + ) + ) + raise resp.error if resp.error + resp + end + # Get a high score # # @param [Hash] params @@ -238,10 +530,14 @@ def get_high_score(params = {}, options = {}, &block) error_inspector_class: Hearth::HTTP::ErrorInspector ) stack.use(Hearth::Middleware::Auth, - auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), - auth_params: Auth::Params.new(operation_name: :get_high_score) + auth_params: Auth::Params.new(operation_name: :get_high_score), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -314,10 +610,14 @@ def list_high_scores(params = {}, options = {}, &block) error_inspector_class: Hearth::HTTP::ErrorInspector ) stack.use(Hearth::Middleware::Auth, - auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), - auth_params: Auth::Params.new(operation_name: :list_high_scores) + auth_params: Auth::Params.new(operation_name: :list_high_scores), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -401,10 +701,14 @@ def update_high_score(params = {}, options = {}, &block) error_inspector_class: Hearth::HTTP::ErrorInspector ) stack.use(Hearth::Middleware::Auth, - auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), - auth_params: Auth::Params.new(operation_name: :update_high_score) + auth_params: Auth::Params.new(operation_name: :update_high_score), + http_api_key_identity_resolver: options.fetch(:http_api_key_identity_resolver, config.http_api_key_identity_resolver), + auth_resolver: options.fetch(:auth_resolver, config.auth_resolver), + http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), + http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, diff --git a/codegen/projections/high_score_service/lib/high_score_service/config.rb b/codegen/projections/high_score_service/lib/high_score_service/config.rb index fbf3dae91..cfcbf829a 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/config.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/config.rb @@ -25,9 +25,18 @@ module HighScoreService # @option args [String] :endpoint # Endpoint of the service # + # @option args [Hearth::IdentityResolver] :http_api_key_identity_resolver + # A Hearth::IdentityResolver that returns a Hearth::Identities::HTTPApiKey for operations modeled with the httpApiKeyAuth auth scheme. + # + # @option args [Hearth::IdentityResolver] :http_bearer_identity_resolver + # A Hearth::IdentityResolver that returns a Hearth::Identities::HTTPBearer for operations modeled with the httpBearerAuth auth scheme. + # # @option args [Hearth::HTTP::Client] :http_client (Hearth::HTTP::Client.new) # The HTTP Client to use for request transport. # + # @option args [Hearth::IdentityResolver] :http_login_identity_resolver + # A Hearth::IdentityResolver that returns a Hearth::Identities::HTTPLogin for operations modeled with the httpBasicAuth auth scheme. + # # @option args [Hearth::InterceptorList] :interceptors (Hearth::InterceptorList.new) # A list of Interceptors to apply to the client. Interceptors are a generic extension point that allows injecting logic at specific stages of execution within the SDK. Logic injection is done with hooks that the interceptor implements. Hooks are either read-only or read/write. Read-only hooks allow an interceptor to read the input, transport request, transport response or output messages. Read/write hooks allow an interceptor to modify one of these messages. # @@ -70,9 +79,18 @@ module HighScoreService # @!attribute endpoint # @return [String] # + # @!attribute http_api_key_identity_resolver + # @return [Hearth::IdentityResolver] + # + # @!attribute http_bearer_identity_resolver + # @return [Hearth::IdentityResolver] + # # @!attribute http_client # @return [Hearth::HTTP::Client] # + # @!attribute http_login_identity_resolver + # @return [Hearth::IdentityResolver] + # # @!attribute interceptors # @return [Hearth::InterceptorList] # @@ -99,7 +117,10 @@ module HighScoreService :auth_schemes, :disable_host_prefix, :endpoint, + :http_api_key_identity_resolver, + :http_bearer_identity_resolver, :http_client, + :http_login_identity_resolver, :interceptors, :log_level, :logger, @@ -118,7 +139,10 @@ def validate! Hearth::Validator.validate_types!(auth_schemes, Array, context: 'config[:auth_schemes]') Hearth::Validator.validate_types!(disable_host_prefix, TrueClass, FalseClass, context: 'config[:disable_host_prefix]') Hearth::Validator.validate_types!(endpoint, String, context: 'config[:endpoint]') + Hearth::Validator.validate_types!(http_api_key_identity_resolver, Hearth::IdentityResolver, context: 'config[:http_api_key_identity_resolver]') + Hearth::Validator.validate_types!(http_bearer_identity_resolver, Hearth::IdentityResolver, context: 'config[:http_bearer_identity_resolver]') Hearth::Validator.validate_types!(http_client, Hearth::HTTP::Client, context: 'config[:http_client]') + Hearth::Validator.validate_types!(http_login_identity_resolver, Hearth::IdentityResolver, context: 'config[:http_login_identity_resolver]') Hearth::Validator.validate_types!(interceptors, Hearth::InterceptorList, context: 'config[:interceptors]') Hearth::Validator.validate_types!(log_level, Symbol, context: 'config[:log_level]') Hearth::Validator.validate_types!(logger, Logger, context: 'config[:logger]') @@ -134,7 +158,10 @@ def self.defaults auth_schemes: [proc { Auth::SCHEMES }], disable_host_prefix: [false], endpoint: [proc { |cfg| cfg[:stub_responses] ? 'http://localhost' : nil }], + http_api_key_identity_resolver: [proc { |cfg| cfg[:stub_responses] ? Hearth::IdentityResolver.new(proc { Hearth::Identities::HTTPApiKey.new(key: 'stubbed api key') }) : nil }], + http_bearer_identity_resolver: [proc { |cfg| cfg[:stub_responses] ? Hearth::IdentityResolver.new(proc { Hearth::Identities::HTTPBearer.new(token: 'stubbed bearer') }) : nil }], http_client: [proc { |cfg| Hearth::HTTP::Client.new(logger: cfg[:logger]) }], + http_login_identity_resolver: [proc { |cfg| cfg[:stub_responses] ? Hearth::IdentityResolver.new(proc { Hearth::Identities::HTTPLogin.new(username: 'stubbed username', password: 'stubbed password') }) : nil }], interceptors: [proc { Hearth::InterceptorList.new }], log_level: [:info], logger: [proc { |cfg| Logger.new($stdout, level: cfg[:log_level]) }], diff --git a/codegen/projections/high_score_service/lib/high_score_service/params.rb b/codegen/projections/high_score_service/lib/high_score_service/params.rb index 969475647..1989b6300 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/params.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/params.rb @@ -11,6 +11,24 @@ module HighScoreService # @api private module Params + module ApiKeyAuthInput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::ApiKeyAuthInput, context: context) + type = Types::ApiKeyAuthInput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + + module ApiKeyAuthOutput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::ApiKeyAuthOutput, context: context) + type = Types::ApiKeyAuthOutput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + module AttributeErrors def self.build(params, context:) Hearth::Validator.validate_types!(params, ::Hash, context: context) @@ -22,6 +40,42 @@ def self.build(params, context:) end end + module BasicAuthInput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::BasicAuthInput, context: context) + type = Types::BasicAuthInput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + + module BasicAuthOutput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::BasicAuthOutput, context: context) + type = Types::BasicAuthOutput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + + module BearerAuthInput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::BearerAuthInput, context: context) + type = Types::BearerAuthInput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + + module BearerAuthOutput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::BearerAuthOutput, context: context) + type = Types::BearerAuthOutput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + module CreateHighScoreInput def self.build(params, context:) Hearth::Validator.validate_types!(params, ::Hash, Types::CreateHighScoreInput, context: context) @@ -62,6 +116,24 @@ def self.build(params, context:) end end + module DigestAuthInput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::DigestAuthInput, context: context) + type = Types::DigestAuthInput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + + module DigestAuthOutput + def self.build(params, context:) + Hearth::Validator.validate_types!(params, ::Hash, Types::DigestAuthOutput, context: context) + type = Types::DigestAuthOutput.new + Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash) + type + end + end + module ErrorMessages def self.build(params, context:) Hearth::Validator.validate_types!(params, ::Array, context: context) diff --git a/codegen/projections/high_score_service/lib/high_score_service/parsers.rb b/codegen/projections/high_score_service/lib/high_score_service/parsers.rb index 7c8972146..b9f0ec5a9 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/parsers.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/parsers.rb @@ -11,6 +11,15 @@ module HighScoreService # @api private module Parsers + # Operation Parser for ApiKeyAuth + class ApiKeyAuth + def self.parse(http_resp) + data = Types::ApiKeyAuthOutput.new + map = Hearth::JSON.load(http_resp.body) + data + end + end + class AttributeErrors def self.parse(map) data = {} @@ -21,6 +30,24 @@ def self.parse(map) end end + # Operation Parser for BasicAuth + class BasicAuth + def self.parse(http_resp) + data = Types::BasicAuthOutput.new + map = Hearth::JSON.load(http_resp.body) + data + end + end + + # Operation Parser for BearerAuth + class BearerAuth + def self.parse(http_resp) + data = Types::BearerAuthOutput.new + map = Hearth::JSON.load(http_resp.body) + data + end + end + # Operation Parser for CreateHighScore class CreateHighScore def self.parse(http_resp) @@ -41,6 +68,15 @@ def self.parse(http_resp) end end + # Operation Parser for DigestAuth + class DigestAuth + def self.parse(http_resp) + data = Types::DigestAuthOutput.new + map = Hearth::JSON.load(http_resp.body) + data + end + end + class ErrorMessages def self.parse(list) list.map do |value| diff --git a/codegen/projections/high_score_service/lib/high_score_service/stubs.rb b/codegen/projections/high_score_service/lib/high_score_service/stubs.rb index 2ccc70c50..14944450b 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/stubs.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/stubs.rb @@ -11,6 +11,26 @@ module HighScoreService # @api private module Stubs + class ApiKeyAuth + def self.build(params, context:) + Params::ApiKeyAuthOutput.build(params, context: context) + end + + def self.validate!(output, context:) + Validators::ApiKeyAuthOutput.validate!(output, context: context) + end + + def self.default(visited = []) + { + } + end + + def self.stub(http_resp, stub:) + data = {} + http_resp.status = 200 + end + end + class AttributeErrors def self.default(visited = []) return nil if visited.include?('AttributeErrors') @@ -30,6 +50,46 @@ def self.stub(stub) end end + class BasicAuth + def self.build(params, context:) + Params::BasicAuthOutput.build(params, context: context) + end + + def self.validate!(output, context:) + Validators::BasicAuthOutput.validate!(output, context: context) + end + + def self.default(visited = []) + { + } + end + + def self.stub(http_resp, stub:) + data = {} + http_resp.status = 200 + end + end + + class BearerAuth + def self.build(params, context:) + Params::BearerAuthOutput.build(params, context: context) + end + + def self.validate!(output, context:) + Validators::BearerAuthOutput.validate!(output, context: context) + end + + def self.default(visited = []) + { + } + end + + def self.stub(http_resp, stub:) + data = {} + http_resp.status = 200 + end + end + class CreateHighScore def self.build(params, context:) Params::CreateHighScoreOutput.build(params, context: context) @@ -76,6 +136,26 @@ def self.stub(http_resp, stub:) end end + class DigestAuth + def self.build(params, context:) + Params::DigestAuthOutput.build(params, context: context) + end + + def self.validate!(output, context:) + Validators::DigestAuthOutput.validate!(output, context: context) + end + + def self.default(visited = []) + { + } + end + + def self.stub(http_resp, stub:) + data = {} + http_resp.status = 200 + end + end + class ErrorMessages def self.default(visited = []) return nil if visited.include?('ErrorMessages') diff --git a/codegen/projections/high_score_service/lib/high_score_service/types.rb b/codegen/projections/high_score_service/lib/high_score_service/types.rb index fbe74bd5b..063b9ca2f 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/types.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/types.rb @@ -10,6 +10,48 @@ module HighScoreService module Types + ApiKeyAuthInput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + + ApiKeyAuthOutput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + + BasicAuthInput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + + BasicAuthOutput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + + BearerAuthInput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + + BearerAuthOutput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + # Input structure for CreateHighScore # # @!attribute high_score @@ -67,6 +109,20 @@ module Types include Hearth::Structure end + DigestAuthInput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + + DigestAuthOutput = ::Struct.new( + nil, + keyword_init: true + ) do + include Hearth::Structure + end + # Input structure for GetHighScore # # @!attribute id diff --git a/codegen/projections/high_score_service/lib/high_score_service/validators.rb b/codegen/projections/high_score_service/lib/high_score_service/validators.rb index 2c63c915e..0e92736cc 100644 --- a/codegen/projections/high_score_service/lib/high_score_service/validators.rb +++ b/codegen/projections/high_score_service/lib/high_score_service/validators.rb @@ -13,6 +13,18 @@ module HighScoreService # @api private module Validators + class ApiKeyAuthInput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::ApiKeyAuthInput, context: context) + end + end + + class ApiKeyAuthOutput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::ApiKeyAuthOutput, context: context) + end + end + class AttributeErrors def self.validate!(input, context:) Hearth::Validator.validate_types!(input, ::Hash, context: context) @@ -23,6 +35,30 @@ def self.validate!(input, context:) end end + class BasicAuthInput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::BasicAuthInput, context: context) + end + end + + class BasicAuthOutput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::BasicAuthOutput, context: context) + end + end + + class BearerAuthInput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::BearerAuthInput, context: context) + end + end + + class BearerAuthOutput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::BearerAuthOutput, context: context) + end + end + class CreateHighScoreInput def self.validate!(input, context:) Hearth::Validator.validate_types!(input, Types::CreateHighScoreInput, context: context) @@ -53,6 +89,18 @@ def self.validate!(input, context:) end end + class DigestAuthInput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::DigestAuthInput, context: context) + end + end + + class DigestAuthOutput + def self.validate!(input, context:) + Hearth::Validator.validate_types!(input, Types::DigestAuthOutput, context: context) + end + end + class ErrorMessages def self.validate!(input, context:) Hearth::Validator.validate_types!(input, ::Array, context: context) diff --git a/codegen/projections/high_score_service/sig/high_score_service/client.rbs b/codegen/projections/high_score_service/sig/high_score_service/client.rbs index 31f91be52..b8291f18b 100644 --- a/codegen/projections/high_score_service/sig/high_score_service/client.rbs +++ b/codegen/projections/high_score_service/sig/high_score_service/client.rbs @@ -18,8 +18,12 @@ module HighScoreService def initialize: (?untyped config, ?::Hash[untyped, untyped] options) -> void attr_reader config: untyped + def api_key_auth: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped + def basic_auth: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped + def bearer_auth: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped def create_high_score: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped def delete_high_score: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped + def digest_auth: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped def get_high_score: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped def list_high_scores: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped def update_high_score: (?::Hash[untyped, untyped] params, ?::Hash[untyped, untyped] options){ () -> untyped } -> untyped diff --git a/codegen/projections/high_score_service/sig/high_score_service/types.rbs b/codegen/projections/high_score_service/sig/high_score_service/types.rbs index 8983337fe..9f82c1b63 100644 --- a/codegen/projections/high_score_service/sig/high_score_service/types.rbs +++ b/codegen/projections/high_score_service/sig/high_score_service/types.rbs @@ -10,6 +10,18 @@ module HighScoreService module Types + ApiKeyAuthInput: untyped + + ApiKeyAuthOutput: untyped + + BasicAuthInput: untyped + + BasicAuthOutput: untyped + + BearerAuthInput: untyped + + BearerAuthOutput: untyped + CreateHighScoreInput: untyped CreateHighScoreOutput: untyped @@ -18,6 +30,10 @@ module HighScoreService DeleteHighScoreOutput: untyped + DigestAuthInput: untyped + + DigestAuthOutput: untyped + GetHighScoreInput: untyped GetHighScoreOutput: untyped diff --git a/codegen/projections/high_score_service/spec/protocol_spec.rb b/codegen/projections/high_score_service/spec/protocol_spec.rb index 58d7a3362..81538818b 100644 --- a/codegen/projections/high_score_service/spec/protocol_spec.rb +++ b/codegen/projections/high_score_service/spec/protocol_spec.rb @@ -45,6 +45,18 @@ def read_after_transmit(context) end end + describe '#api_key_auth' do + + end + + describe '#basic_auth' do + + end + + describe '#bearer_auth' do + + end + describe '#create_high_score' do end @@ -53,6 +65,10 @@ def read_after_transmit(context) end + describe '#digest_auth' do + + end + describe '#get_high_score' do end diff --git a/codegen/projections/rails_json/lib/rails_json/client.rb b/codegen/projections/rails_json/lib/rails_json/client.rb index 4fa073ee6..f2874f8a4 100644 --- a/codegen/projections/rails_json/lib/rails_json/client.rb +++ b/codegen/projections/rails_json/lib/rails_json/client.rb @@ -110,6 +110,7 @@ def all_query_string_types(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :all_query_string_types) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -184,6 +185,7 @@ def constant_and_variable_query_string(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :constant_and_variable_query_string) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -258,6 +260,7 @@ def constant_query_string(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :constant_query_string) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -339,6 +342,7 @@ def document_type(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :document_type) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -418,6 +422,7 @@ def document_type_as_payload(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :document_type_as_payload) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -485,6 +490,7 @@ def empty_operation(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :empty_operation) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -556,6 +562,7 @@ def endpoint_operation(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :endpoint_operation) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -629,6 +636,7 @@ def endpoint_with_host_label_operation(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :endpoint_with_host_label_operation) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -706,6 +714,7 @@ def greeting_with_errors(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :greeting_with_errors) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -783,6 +792,7 @@ def http_payload_traits(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_payload_traits) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -858,6 +868,7 @@ def http_payload_traits_with_media_type(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_payload_traits_with_media_type) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -938,6 +949,7 @@ def http_payload_with_structure(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_payload_with_structure) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1017,6 +1029,7 @@ def http_prefix_headers(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_prefix_headers) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1088,6 +1101,7 @@ def http_prefix_headers_in_response(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_prefix_headers_in_response) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1158,6 +1172,7 @@ def http_request_with_float_labels(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_request_with_float_labels) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1228,6 +1243,7 @@ def http_request_with_greedy_label_in_path(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_request_with_greedy_label_in_path) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1313,6 +1329,7 @@ def http_request_with_labels(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_request_with_labels) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1391,6 +1408,7 @@ def http_request_with_labels_and_timestamp_format(params = {}, options = {}, &bl auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_request_with_labels_and_timestamp_format) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1459,6 +1477,7 @@ def http_response_code(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :http_response_code) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1531,6 +1550,7 @@ def ignore_query_params_in_response(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :ignore_query_params_in_response) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1652,6 +1672,7 @@ def input_and_output_with_headers(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :input_and_output_with_headers) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1743,6 +1764,7 @@ def json_enums(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :json_enums) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1861,6 +1883,7 @@ def json_maps(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :json_maps) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1970,6 +1993,7 @@ def json_unions(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :json_unions) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2145,6 +2169,7 @@ def kitchen_sink_operation(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :kitchen_sink_operation) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2217,6 +2242,7 @@ def media_type_header(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :media_type_header) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2289,6 +2315,7 @@ def nested_attributes_operation(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :nested_attributes_operation) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2370,6 +2397,7 @@ def null_and_empty_headers_client(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :null_and_empty_headers_client) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2450,6 +2478,7 @@ def null_operation(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :null_operation) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2522,6 +2551,7 @@ def omits_null_serializes_empty_string(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :omits_null_serializes_empty_string) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2592,6 +2622,7 @@ def operation_with_optional_input_output(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :operation_with_optional_input_output) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2665,6 +2696,7 @@ def query_idempotency_token_auto_fill(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :query_idempotency_token_auto_fill) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2739,6 +2771,7 @@ def query_params_as_string_list_map(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :query_params_as_string_list_map) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2808,6 +2841,7 @@ def streaming_operation(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :streaming_operation) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2892,6 +2926,7 @@ def timestamp_format_headers(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :timestamp_format_headers) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -2966,6 +3001,7 @@ def operation____789_bad_name(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :operation____789_bad_name) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, diff --git a/codegen/projections/weather/lib/weather/client.rb b/codegen/projections/weather/lib/weather/client.rb index 61d0a1ce0..f77c9d919 100644 --- a/codegen/projections/weather/lib/weather/client.rb +++ b/codegen/projections/weather/lib/weather/client.rb @@ -80,6 +80,7 @@ def get_city(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :get_city) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -158,6 +159,7 @@ def get_city_image(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :get_city_image) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -225,6 +227,7 @@ def get_current_time(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :get_current_time) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -309,6 +312,7 @@ def get_forecast(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :get_forecast) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -398,6 +402,7 @@ def list_cities(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :list_cities) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -472,6 +477,7 @@ def operation____789_bad_name(params = {}, options = {}, &block) auth_schemes: options.fetch(:auth_schemes, config.auth_schemes), auth_params: Auth::Params.new(operation_name: :operation____789_bad_name) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, diff --git a/codegen/projections/white_label/lib/white_label/client.rb b/codegen/projections/white_label/lib/white_label/client.rb index 367778b36..22fe33f7b 100644 --- a/codegen/projections/white_label/lib/white_label/client.rb +++ b/codegen/projections/white_label/lib/white_label/client.rb @@ -153,6 +153,7 @@ def defaults_test(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -229,6 +230,7 @@ def endpoint_operation(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -307,6 +309,7 @@ def endpoint_with_host_label_operation(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -379,6 +382,7 @@ def http_api_key_auth(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -451,6 +455,7 @@ def http_basic_auth(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -523,6 +528,7 @@ def http_bearer_auth(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -595,6 +601,7 @@ def http_digest_auth(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -834,6 +841,7 @@ def kitchen_sink(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -910,6 +918,7 @@ def mixin_test(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -982,6 +991,7 @@ def no_auth(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1054,6 +1064,7 @@ def optional_auth(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1126,6 +1137,7 @@ def ordered_auth(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1203,6 +1215,7 @@ def paginators_test(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1280,6 +1293,7 @@ def paginators_test_with_items(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1360,6 +1374,7 @@ def request_compression_operation(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1439,6 +1454,7 @@ def request_compression_streaming_operation(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1513,6 +1529,7 @@ def streaming_operation(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1587,6 +1604,7 @@ def streaming_with_length(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1662,6 +1680,7 @@ def waiters_test(params = {}, options = {}, &block) http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, @@ -1740,6 +1759,7 @@ def operation____paginators_test_with_bad_names(params = {}, options = {}, &bloc http_bearer_identity_resolver: options.fetch(:http_bearer_identity_resolver, config.http_bearer_identity_resolver), http_login_identity_resolver: options.fetch(:http_login_identity_resolver, config.http_login_identity_resolver) ) + stack.use(Hearth::Middleware::Sign) stack.use(Hearth::Middleware::Parse, error_parser: Hearth::HTTP::ErrorParser.new( error_module: Errors, diff --git a/codegen/projections/white_label/lib/white_label/config.rb b/codegen/projections/white_label/lib/white_label/config.rb index ef579ea22..7e06b19e5 100644 --- a/codegen/projections/white_label/lib/white_label/config.rb +++ b/codegen/projections/white_label/lib/white_label/config.rb @@ -29,16 +29,16 @@ module WhiteLabel # Endpoint of the service # # @option args [Hearth::IdentityResolver] :http_api_key_identity_resolver - # A Hearth::IdentityResolver for the smithy.api httpApiKeyAuth auth scheme. + # A Hearth::IdentityResolver that returns a Hearth::Identities::HTTPApiKey for operations modeled with the httpApiKeyAuth auth scheme. # # @option args [Hearth::IdentityResolver] :http_bearer_identity_resolver - # A Hearth::IdentityResolver for the smithy.api httpBearerAuth auth scheme. + # A Hearth::IdentityResolver that returns a Hearth::Identities::HTTPBearer for operations modeled with the httpBearerAuth auth scheme. # # @option args [Hearth::HTTP::Client] :http_client (Hearth::HTTP::Client.new) # The HTTP Client to use for request transport. # # @option args [Hearth::IdentityResolver] :http_login_identity_resolver - # A Hearth::IdentityResolver for the smithy.api httpBasicAuth auth scheme. + # A Hearth::IdentityResolver that returns a Hearth::Identities::HTTPLogin for operations modeled with the httpBasicAuth auth scheme. # # @option args [Hearth::InterceptorList] :interceptors (Hearth::InterceptorList.new) # A list of Interceptors to apply to the client. Interceptors are a generic extension point that allows injecting logic at specific stages of execution within the SDK. Logic injection is done with hooks that the interceptor implements. Hooks are either read-only or read/write. Read-only hooks allow an interceptor to read the input, transport request, transport response or output messages. Read/write hooks allow an interceptor to modify one of these messages. diff --git a/codegen/projections/white_label/spec/auth_spec.rb b/codegen/projections/white_label/spec/auth_spec.rb index 877de5fbf..4d60ba8ee 100644 --- a/codegen/projections/white_label/spec/auth_spec.rb +++ b/codegen/projections/white_label/spec/auth_spec.rb @@ -21,7 +21,7 @@ module Auth 'smithy.api#httpDigestAuth', 'smithy.api#noAuth' ] - expect(actual).to eq(expected) + expect(actual).to match_array(expected) end end @@ -194,18 +194,6 @@ module Auth let(:client) { Client.new(config) } - let(:before_sign) do - Class.new do - def initialize(&block) - @block = block - end - - def read_before_signing(context) - @block.call(context) - end - end - end - describe '#http_api_key_auth' do let(:config_hash) do { http_api_key_identity_resolver: identity_resolver } @@ -216,15 +204,13 @@ def read_before_signing(context) end it 'resolves httpApiKeyAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPApiKey) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpApiKeyAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_api_key_auth({}, interceptors: [interceptor]) + client.http_api_key_auth({}) end end @@ -238,15 +224,13 @@ def read_before_signing(context) end it 'resolves httpBasicAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPBasic) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpBasicAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_basic_auth({}, interceptors: [interceptor]) + client.http_basic_auth({}) end end @@ -260,15 +244,13 @@ def read_before_signing(context) end it 'resolves httpBearerAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPBearer) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpBearerAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_bearer_auth({}, interceptors: [interceptor]) + client.http_bearer_auth({}) end end @@ -282,15 +264,15 @@ def read_before_signing(context) end it 'resolves httpDigestAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPDigest) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpDigestAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_basic_auth({}, interceptors: [interceptor]) + # temporarily disabled because not implemented + expect_any_instance_of(Hearth::Signers::HTTPDigest).to receive(:sign) + client.http_digest_auth({}) end end @@ -305,14 +287,13 @@ def read_before_signing(context) end it 'resolves noAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::Anonymous) - expect(auth_scheme.identity).to be_a(Hearth::Identities::Anonymous) - expect(auth_scheme.auth_option.scheme_id).to eq('smithy.api#noAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to be_a(Hearth::Identities::Anonymous) + resolved end - - client.optional_auth({}, interceptors: [interceptor]) + client.optional_auth({}) end end @@ -323,14 +304,13 @@ def read_before_signing(context) end it 'resolves noAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::Anonymous) - expect(auth_scheme.identity).to be_a(Hearth::Identities::Anonymous) - expect(auth_scheme.auth_option.scheme_id).to eq('smithy.api#noAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to be_a(Hearth::Identities::Anonymous) + resolved end - - client.optional_auth({}, interceptors: [interceptor]) + client.optional_auth({}) end end end @@ -340,14 +320,13 @@ def read_before_signing(context) let(:config_hash) { {} } it 'resolves noAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::Anonymous) - expect(auth_scheme.identity).to be_a(Hearth::Identities::Anonymous) - expect(auth_scheme.auth_option.scheme_id).to eq('smithy.api#noAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to be_a(Hearth::Identities::Anonymous) + resolved end - - client.no_auth({}, interceptors: [interceptor]) + client.no_auth({}) end end @@ -365,15 +344,13 @@ def read_before_signing(context) end it 'resolves httpDigestAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPDigest) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpDigestAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.ordered_auth({}, interceptors: [interceptor]) + client.ordered_auth({}) end end end diff --git a/codegen/smithy-ruby-codegen-test/integration-specs/auth_spec.rb b/codegen/smithy-ruby-codegen-test/integration-specs/auth_spec.rb index 877de5fbf..4d60ba8ee 100644 --- a/codegen/smithy-ruby-codegen-test/integration-specs/auth_spec.rb +++ b/codegen/smithy-ruby-codegen-test/integration-specs/auth_spec.rb @@ -21,7 +21,7 @@ module Auth 'smithy.api#httpDigestAuth', 'smithy.api#noAuth' ] - expect(actual).to eq(expected) + expect(actual).to match_array(expected) end end @@ -194,18 +194,6 @@ module Auth let(:client) { Client.new(config) } - let(:before_sign) do - Class.new do - def initialize(&block) - @block = block - end - - def read_before_signing(context) - @block.call(context) - end - end - end - describe '#http_api_key_auth' do let(:config_hash) do { http_api_key_identity_resolver: identity_resolver } @@ -216,15 +204,13 @@ def read_before_signing(context) end it 'resolves httpApiKeyAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPApiKey) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpApiKeyAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_api_key_auth({}, interceptors: [interceptor]) + client.http_api_key_auth({}) end end @@ -238,15 +224,13 @@ def read_before_signing(context) end it 'resolves httpBasicAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPBasic) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpBasicAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_basic_auth({}, interceptors: [interceptor]) + client.http_basic_auth({}) end end @@ -260,15 +244,13 @@ def read_before_signing(context) end it 'resolves httpBearerAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPBearer) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpBearerAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_bearer_auth({}, interceptors: [interceptor]) + client.http_bearer_auth({}) end end @@ -282,15 +264,15 @@ def read_before_signing(context) end it 'resolves httpDigestAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPDigest) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpDigestAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.http_basic_auth({}, interceptors: [interceptor]) + # temporarily disabled because not implemented + expect_any_instance_of(Hearth::Signers::HTTPDigest).to receive(:sign) + client.http_digest_auth({}) end end @@ -305,14 +287,13 @@ def read_before_signing(context) end it 'resolves noAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::Anonymous) - expect(auth_scheme.identity).to be_a(Hearth::Identities::Anonymous) - expect(auth_scheme.auth_option.scheme_id).to eq('smithy.api#noAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to be_a(Hearth::Identities::Anonymous) + resolved end - - client.optional_auth({}, interceptors: [interceptor]) + client.optional_auth({}) end end @@ -323,14 +304,13 @@ def read_before_signing(context) end it 'resolves noAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::Anonymous) - expect(auth_scheme.identity).to be_a(Hearth::Identities::Anonymous) - expect(auth_scheme.auth_option.scheme_id).to eq('smithy.api#noAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to be_a(Hearth::Identities::Anonymous) + resolved end - - client.optional_auth({}, interceptors: [interceptor]) + client.optional_auth({}) end end end @@ -340,14 +320,13 @@ def read_before_signing(context) let(:config_hash) { {} } it 'resolves noAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::Anonymous) - expect(auth_scheme.identity).to be_a(Hearth::Identities::Anonymous) - expect(auth_scheme.auth_option.scheme_id).to eq('smithy.api#noAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to be_a(Hearth::Identities::Anonymous) + resolved end - - client.no_auth({}, interceptors: [interceptor]) + client.no_auth({}) end end @@ -365,15 +344,13 @@ def read_before_signing(context) end it 'resolves httpDigestAuth' do - interceptor = before_sign.new do |context| - auth_scheme = context.auth_scheme - expect(auth_scheme).to be_a(Hearth::AuthSchemes::HTTPDigest) - expect(auth_scheme.identity).to be(identity) - expect(auth_scheme.auth_option.scheme_id) - .to eq('smithy.api#httpDigestAuth') + expect_any_instance_of(Hearth::IdentityResolver).to receive(:identity) + .and_wrap_original do |m, *args| + resolved = m.call(*args) + expect(resolved).to eq(identity) + resolved end - - client.ordered_auth({}, interceptors: [interceptor]) + client.ordered_auth({}) end end end diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java index 49e13d4ed..a731dfb3f 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java @@ -167,6 +167,11 @@ public final class Hearth { .name("Auth") .build(); + public static final Symbol SIGN_MIDDLEWARE = Symbol.builder() + .namespace("Hearth::Middleware", "::") + .name("Sign") + .build(); + public static final Symbol PARSE_MIDDLEWARE = Symbol.builder() .namespace("Hearth::Middleware", "::") .name("Parse") diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/AuthResolverGenerator.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/AuthResolverGenerator.java index ee82d7683..2971919e2 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/AuthResolverGenerator.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/AuthResolverGenerator.java @@ -15,6 +15,8 @@ package software.amazon.smithy.ruby.codegen.generators; +import java.util.Comparator; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -81,24 +83,30 @@ private void renderAuthParamsClass(RubyCodeWriter writer) { } private void renderAuthSchemesConstant(RubyCodeWriter writer) { + Set authTraitsSet = new HashSet<>(); + if (serviceAuthSchemes.isEmpty()) { + authTraitsSet.add(new OptionalAuthTrait()); + } else { + serviceAuthSchemes.forEach((shapeId, trait) -> { + authTraitsSet.add(trait); + }); + } + operations.stream().forEach(operation -> { + ServiceIndex.of(model).getEffectiveAuthSchemes(service, operation).forEach((shapeId, trait) -> { + authTraitsSet.add(trait); + }); + if (operation.hasTrait(OptionalAuthTrait.class)) { + OptionalAuthTrait trait = operation.getTrait(OptionalAuthTrait.class).get(); + authTraitsSet.add(trait); + } + }); + writer .openBlock("SCHEMES = [") .call(() -> { - if (serviceAuthSchemes.isEmpty()) { - renderAuthScheme(writer, new OptionalAuthTrait()); - } else { - serviceAuthSchemes.forEach((shapeId, trait) -> { - renderAuthScheme(writer, trait); - }); - // if any operation is optional, also add anonymous auth scheme - operations.stream().forEach(operation -> { - boolean isOptional = operation.hasTrait(OptionalAuthTrait.class); - if (isOptional) { - OptionalAuthTrait trait = operation.getTrait(OptionalAuthTrait.class).get(); - renderAuthScheme(writer, trait); - } - }); - } + authTraitsSet.stream() + .sorted(Comparator.comparing(Trait::toShapeId)) + .forEach(trait -> renderAuthScheme(writer, trait)); }) .unwrite(",\n") .write("") diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/MiddlewareBuilder.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/MiddlewareBuilder.java index 0522e7fc8..9b2ada385 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/MiddlewareBuilder.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/MiddlewareBuilder.java @@ -38,6 +38,7 @@ import software.amazon.smithy.ruby.codegen.middleware.factories.InitializeMiddlewareFactory; import software.amazon.smithy.ruby.codegen.middleware.factories.RetryMiddlewareFactory; import software.amazon.smithy.ruby.codegen.middleware.factories.SendMiddlewareFactory; +import software.amazon.smithy.ruby.codegen.middleware.factories.SignMiddlewareFactory; import software.amazon.smithy.ruby.codegen.middleware.factories.ValidateMiddlewareFactory; import software.amazon.smithy.utils.SmithyInternalApi; @@ -175,6 +176,7 @@ public void addDefaultMiddleware(GenerationContext context) { register(HostPrefixMiddlewareFactory.build(context)); register(RetryMiddlewareFactory.build(context)); register(AuthMiddlewareFactory.build(context)); + register(SignMiddlewareFactory.build(context)); register(SendMiddlewareFactory.build(context)); register(transport.defaultMiddleware(context)); diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/AuthMiddlewareFactory.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/AuthMiddlewareFactory.java index 947be4a43..14ab83207 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/AuthMiddlewareFactory.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/AuthMiddlewareFactory.java @@ -42,17 +42,21 @@ private AuthMiddlewareFactory() { public static Middleware build(GenerationContext context) { SymbolProvider symbolProvider = context.symbolProvider(); Map serviceAuthSchemes = - ServiceIndex.of(context.model()).getEffectiveAuthSchemes(context.service()); + ServiceIndex.of(context.model()).getAuthSchemes(context.service()); Set clientConfigSet = new HashSet<>(); String identityResolverDocumentation = """ - A %s for the %s auth scheme. + A %s that returns a %s for operations modeled with the %s auth scheme. """; serviceAuthSchemes.forEach((shapeId, trait) -> { clientConfigSet.add(ClientConfig.builder() .name(identityResolverConfigNameForAuthTrait(trait)) .type(Hearth.IDENTITY_RESOLVER.toString()) - .documentation(identityResolverDocumentation.formatted(Hearth.IDENTITY_RESOLVER, shapeId)) + .documentation( + identityResolverDocumentation.formatted( + Hearth.IDENTITY_RESOLVER, + hearthIdentityForAuthTrait(trait), + shapeId.getName())) .defaultDynamicValue(defaultIdentityResolverForAuthTrait(trait)) .allowOperationOverride() .build()); @@ -130,4 +134,16 @@ private static String defaultIdentityResolverForAuthTrait(Trait trait) { return "proc { |cfg| cfg[:stub_responses] ? %s.new(proc { %s }) : nil }" .formatted(Hearth.IDENTITY_RESOLVER, identity); } + + private static String hearthIdentityForAuthTrait(Trait trait) { + if (trait instanceof HttpApiKeyAuthTrait) { + return Hearth.IDENTITIES + "::HTTPApiKey"; + } else if (trait instanceof HttpBearerAuthTrait) { + return Hearth.IDENTITIES + "::HTTPBearer"; + } else if (trait instanceof HttpBasicAuthTrait || trait instanceof HttpDigestAuthTrait) { + return Hearth.IDENTITIES + "::HTTPLogin"; + } else { + throw new IllegalStateException("Unknown auth trait: " + trait); + } + } } diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/SignMiddlewareFactory.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/SignMiddlewareFactory.java new file mode 100644 index 000000000..79fcecd98 --- /dev/null +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/middleware/factories/SignMiddlewareFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package software.amazon.smithy.ruby.codegen.middleware.factories; + +import software.amazon.smithy.ruby.codegen.GenerationContext; +import software.amazon.smithy.ruby.codegen.Hearth; +import software.amazon.smithy.ruby.codegen.middleware.Middleware; +import software.amazon.smithy.ruby.codegen.middleware.MiddlewareStackStep; + +public final class SignMiddlewareFactory { + private SignMiddlewareFactory() { + } + + public static Middleware build(GenerationContext context) { + return Middleware.builder() + .klass(Hearth.SIGN_MIDDLEWARE) + .step(MiddlewareStackStep.FINALIZE) + .build(); + } +} diff --git a/codegen/smithy-ruby-rails-codegen-test/model/high-score-service.smithy b/codegen/smithy-ruby-rails-codegen-test/model/high-score-service.smithy index 2cfb28813..62b0f06f8 100644 --- a/codegen/smithy-ruby-rails-codegen-test/model/high-score-service.smithy +++ b/codegen/smithy-ruby-rails-codegen-test/model/high-score-service.smithy @@ -8,9 +8,20 @@ use smithy.ruby.protocols#UnprocessableEntityError /// Rails High Score example from their generator docs @railsJson @title("High Score Sample Rails Service") +@httpBasicAuth +@httpDigestAuth +@httpBearerAuth +@httpApiKeyAuth(name: "Authorization", in: "header") +@auth([]) service HighScoreService { version: "2021-02-15", - resources: [HighScore] + resources: [HighScore], + operations: [ + BasicAuth, + DigestAuth, + BearerAuth, + ApiKeyAuth + ] } /// Rails default scaffold operations @@ -157,4 +168,20 @@ structure ListHighScoresOutput { list HighScores { member: HighScoreAttributes -} \ No newline at end of file +} + +@auth([httpBasicAuth]) +@http(method: "GET", uri: "/basic_auth") +operation BasicAuth {} + +@auth([httpDigestAuth]) +@http(method: "GET", uri: "/digest_auth") +operation DigestAuth {} + +@auth([httpBearerAuth]) +@http(method: "GET", uri: "/bearer_auth") +operation BearerAuth {} + +@auth([httpApiKeyAuth]) +@http(method: "GET", uri: "/api_key_auth") +operation ApiKeyAuth {} \ No newline at end of file diff --git a/hearth/lib/hearth/auth_schemes.rb b/hearth/lib/hearth/auth_schemes.rb index 012065b41..7131180c0 100644 --- a/hearth/lib/hearth/auth_schemes.rb +++ b/hearth/lib/hearth/auth_schemes.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module Hearth + # Namespace for all AuthScheme classes. module AuthSchemes # Base class for all AuthScheme classes. class Base diff --git a/hearth/lib/hearth/auth_schemes/anonymous.rb b/hearth/lib/hearth/auth_schemes/anonymous.rb index ea2579baf..0c3824f3f 100644 --- a/hearth/lib/hearth/auth_schemes/anonymous.rb +++ b/hearth/lib/hearth/auth_schemes/anonymous.rb @@ -11,6 +11,11 @@ def initialize identity_type: Identities::Anonymous ) end + + # @return [IdentityResolver, nil] + def identity_resolver(_identity_resolvers = {}) + Hearth::IdentityResolver.new(proc { Identities::Anonymous.new }) + end end end end diff --git a/hearth/lib/hearth/client_stubs.rb b/hearth/lib/hearth/client_stubs.rb index b0d6754cc..59e8931b1 100644 --- a/hearth/lib/hearth/client_stubs.rb +++ b/hearth/lib/hearth/client_stubs.rb @@ -65,10 +65,10 @@ module ClientStubs # ## Dynamic Stubbing # # In addition to creating static stubs, it's also possible to generate - # stubs dynamically based on the parameters with which operations were + # stubs dynamically based on the input with which operations were # called, by passing a `Proc` object: # - # client.stub_responses(:operation, -> (input, context) { + # client.stub_responses(:operation, -> (input) { # if input[:param] == 'foo' # # return a data stub # { data: { param1: [{ name: 'value1'}]} } diff --git a/hearth/lib/hearth/context.rb b/hearth/lib/hearth/context.rb index 197eb6193..87a092717 100644 --- a/hearth/lib/hearth/context.rb +++ b/hearth/lib/hearth/context.rb @@ -13,7 +13,6 @@ def initialize(options = {}) @params = options[:params] @metadata = options[:metadata] || {} @interceptors = options[:interceptors] || InterceptorList.new - @interceptor_attributes = {} end # @return [Symbol] Name of the API operation called. @@ -37,8 +36,22 @@ def initialize(options = {}) # @return [Array] An ordered list of interceptors attr_reader :interceptors - # @return [SelectedAuthScheme, nil] The auth scheme for the request. - attr_accessor :auth_scheme + # @return [ResolvedAuth, nil] The resolved auth for the request. + attr_accessor :auth + + # Returns the metadata for the given `key`. + # @param [Symbol] key + # @return [Object] + def [](key) + @metadata[key] + end + + # Sets metadata for the given `key`. + # @param [Symbol] key + # @param [Object] value + def []=(key, value) + @metadata[key] = value + end # @api private def interceptor_context(input, output) @@ -46,8 +59,7 @@ def interceptor_context(input, output) input: input, request: request, response: response, - output: output, - attributes: @interceptor_attributes + output: output ) end end diff --git a/hearth/lib/hearth/http/error_inspector.rb b/hearth/lib/hearth/http/error_inspector.rb index b65d590b8..0da647da4 100644 --- a/hearth/lib/hearth/http/error_inspector.rb +++ b/hearth/lib/hearth/http/error_inspector.rb @@ -37,7 +37,7 @@ def error_type def hints hints = {} if (retry_after = retry_after_hint) - hints[:retry_after_hint] = retry_after + hints[:retry_after] = retry_after end hints end diff --git a/hearth/lib/hearth/http/field.rb b/hearth/lib/hearth/http/field.rb index ac9eb43e1..e12c34515 100644 --- a/hearth/lib/hearth/http/field.rb +++ b/hearth/lib/hearth/http/field.rb @@ -49,6 +49,7 @@ def trailer? @kind == :trailer end + # @return [Hash] def to_h { @name => value } end diff --git a/hearth/lib/hearth/http/fields.rb b/hearth/lib/hearth/http/fields.rb index 967132a1f..7e3147172 100644 --- a/hearth/lib/hearth/http/fields.rb +++ b/hearth/lib/hearth/http/fields.rb @@ -10,8 +10,8 @@ class Fields # @param [Array] fields # @param [String] encoding def initialize(fields = [], encoding: 'utf-8') - unless fields.is_a?(Array) - raise ArgumentError, 'fields must be an Array' + unless fields.is_a?(Enumerable) + raise ArgumentError, 'fields must be an Enumerable of Field' end @entries = {} @@ -49,9 +49,8 @@ def delete(key) # @return [Enumerable] def each(&block) - @entries.each(&block) + @entries.values.each(&block) end - alias each_pair each # @return [Integer] Returns the number of Field entries. def size @@ -98,8 +97,8 @@ def delete(key) # @return [Enumerable] def each(&block) - @fields.filter { |_k, v| v.kind == @kind } - .to_h { |_k, v| [v.name, v.value(@fields.encoding)] } + @fields.filter { |f| f.kind == @kind } + .to_h { |f| [f.name, f.value(@fields.encoding)] } .each(&block) end alias each_pair each diff --git a/hearth/lib/hearth/http/request.rb b/hearth/lib/hearth/http/request.rb index 1cf7479bf..d1acd430e 100755 --- a/hearth/lib/hearth/http/request.rb +++ b/hearth/lib/hearth/http/request.rb @@ -3,7 +3,6 @@ module Hearth module HTTP # Represents an HTTP request. - # @api private class Request < Hearth::Request # @param [String] http_method # @param [Fields] fields @@ -119,6 +118,33 @@ def append_query_param_list(param_list) uri.query = uri.query ? "#{uri.query}&#{param_list}" : param_list.to_s end + # Remove querystring parameter from the HTTP request URI. + # + # http_req.uri = "https://example.com" + # http_req.append_query_param('query') + # http_req.append_query_param('empty', '') + # http_req.append_query_param('deleteme', 'true') + # http_req.append_query_param('key', 'value') + # #=> "https://example.com?query&empty=&deleteme=true&key=value" + # + # http_req.remove_query_param('deleteme') + # #=> "query&empty=&key=value" + # + # http_req.uri.to_s + # #=> "https://example.com?query&empty=&key=value" + # + # @param [String] name The name of the querystring parameter to remove. + def remove_query_param(name) + parsed = CGI.parse(uri.query) + parsed.delete(name) + # encode_www_form ignores query params without values + # (CGI parses these as empty lists) + parsed.each do |key, values| + parsed[key] = values.empty? ? nil : values + end + uri.query = URI.encode_www_form(parsed) + end + # Append a host prefix to the HTTP request URI. # # http_req.uri = "https://example.com" diff --git a/hearth/lib/hearth/http/response.rb b/hearth/lib/hearth/http/response.rb index d3c8a87b5..eb26551bf 100755 --- a/hearth/lib/hearth/http/response.rb +++ b/hearth/lib/hearth/http/response.rb @@ -3,7 +3,6 @@ module Hearth module HTTP # Represents an HTTP Response. - # @api private class Response < Hearth::Response # @param [Integer] status # @param [String, nil] reason diff --git a/hearth/lib/hearth/identities.rb b/hearth/lib/hearth/identities.rb index 9f426f70f..8ffa4fa69 100644 --- a/hearth/lib/hearth/identities.rb +++ b/hearth/lib/hearth/identities.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module Hearth + # Namespace for all Identity classes. module Identities # Base class for all Identity classes. class Base diff --git a/hearth/lib/hearth/middleware.rb b/hearth/lib/hearth/middleware.rb index 0a2596810..7ed6a4f4b 100644 --- a/hearth/lib/hearth/middleware.rb +++ b/hearth/lib/hearth/middleware.rb @@ -6,6 +6,7 @@ require_relative 'middleware/parse' require_relative 'middleware/retry' require_relative 'middleware/send' +require_relative 'middleware/sign' require_relative 'middleware/validate' require_relative 'middleware/initialize' diff --git a/hearth/lib/hearth/middleware/auth.rb b/hearth/lib/hearth/middleware/auth.rb index b993ce11b..9886c94bb 100644 --- a/hearth/lib/hearth/middleware/auth.rb +++ b/hearth/lib/hearth/middleware/auth.rb @@ -13,9 +13,7 @@ def initialize(app, auth_resolver:, auth_params:, @auth_params = auth_params @auth_schemes = auth_schemes.to_h { |s| [s.scheme_id, s] } - @identity_resolvers = { - Identities::Anonymous => anonymous_identity_resolver - } + @identity_resolvers = {} kwargs.each do |key, value| next unless key.end_with?('_identity_resolver') @@ -28,17 +26,19 @@ def initialize(app, auth_resolver:, auth_params:, # @return [Output] def call(input, context) auth_options = @auth_resolver.resolve(@auth_params) - context.auth_scheme = select_auth_scheme(auth_options) + context.auth = resolve_auth(auth_options) @app.call(input, context) end private - SelectedAuthScheme = Struct.new(:identity, :signer, :auth_option) - - def anonymous_identity_resolver - Hearth::IdentityResolver.new(proc { Identities::Anonymous.new }) - end + ResolvedAuth = Struct.new( + :signer, + :signer_properties, + :identity, + :identity_properties, + keyword_init: true + ) def identity_type_for(config_key) case config_key @@ -53,18 +53,18 @@ def identity_type_for(config_key) end end - def select_auth_scheme(auth_options) + def resolve_auth(auth_options) failures = [] auth_options.each do |auth_option| auth_scheme = @auth_schemes[auth_option.scheme_id] - selected_auth_scheme = try_load_auth_scheme( + resolved_auth = try_load_auth_scheme( auth_option, auth_scheme, failures ) - return selected_auth_scheme if selected_auth_scheme + return resolved_auth if resolved_auth end raise failures.join("\n") @@ -88,7 +88,12 @@ def try_load_auth_scheme(auth_option, auth_scheme, failures) identity_properties = auth_option.identity_properties identity = identity_resolver.identity(identity_properties) - SelectedAuthScheme.new(identity, auth_scheme.signer, auth_option) + ResolvedAuth.new( + identity: identity, + identity_properties: auth_option.identity_properties, + signer: auth_scheme.signer, + signer_properties: auth_option.signer_properties + ) end end end diff --git a/hearth/lib/hearth/middleware/retry.rb b/hearth/lib/hearth/middleware/retry.rb index d12d2ff0f..3739ee1b6 100755 --- a/hearth/lib/hearth/middleware/retry.rb +++ b/hearth/lib/hearth/middleware/retry.rb @@ -6,7 +6,7 @@ module Middleware # @api private class Retry # @param [Class] app The next middleware in the stack. - # @param [Standard|Adaptive] retry_strategy (Standard) The retry strategy + # @param [Strategy] retry_strategy (Standard) The retry strategy # to use. Hearth has two built in classes, Standard and Adaptive. # * `Retry::Standard` - A standardized set of retry rules across # the AWS SDKs. This includes support for retry quotas, which limit @@ -17,11 +17,9 @@ class Retry # behavior in the future. def initialize(app, retry_strategy:, error_inspector_class:) @app = app - @retry_strategy = retry_strategy - # undocumented - protocol specific @error_inspector_class = error_inspector_class - # instance state + @retries = 0 end @@ -73,7 +71,8 @@ def call(input, context) break unless token && output.error - reset_request(context, output) + reset_request(context) + reset_response(context, output) @retries += 1 end output @@ -81,8 +80,18 @@ def call(input, context) private - def reset_request(context, output) - context.request.body.rewind + def reset_request(context) + request = context.request + request.body.rewind if request.body.respond_to?(:rewind) + + auth = context.auth + auth.signer.reset( + request: request, + properties: auth.signer_properties + ) + end + + def reset_response(context, output) context.response.reset output.error = nil end diff --git a/hearth/lib/hearth/middleware/send.rb b/hearth/lib/hearth/middleware/send.rb index ce60bd23c..43793fbe8 100755 --- a/hearth/lib/hearth/middleware/send.rb +++ b/hearth/lib/hearth/middleware/send.rb @@ -90,8 +90,8 @@ def send_request(context, output) def apply_stub(stub, input, context, output) case stub when Proc - stub = stub.call(input, context) - apply_stub(stub, input, context, output) if stub + stub = stub.call(input) + apply_stub(stub, input, context, output) when Exception, ApiError output.error = stub when Hash diff --git a/hearth/lib/hearth/middleware/sign.rb b/hearth/lib/hearth/middleware/sign.rb index becca0120..5dfd3325d 100644 --- a/hearth/lib/hearth/middleware/sign.rb +++ b/hearth/lib/hearth/middleware/sign.rb @@ -14,8 +14,49 @@ def initialize(app) # @param context # @return [Output] def call(input, context) - # TODO: do signing - @app.call(input, context) + interceptor_error = context.interceptors.apply( + hook: Interceptor::Hooks::MODIFY_BEFORE_SIGNING, + input: input, + context: context, + output: nil, + aggregate_errors: false + ) + return Hearth::Output.new(error: interceptor_error) if interceptor_error + + interceptor_error = context.interceptors.apply( + hook: Interceptor::Hooks::READ_BEFORE_SIGNING, + input: input, + context: context, + output: nil, + aggregate_errors: true + ) + return Hearth::Output.new(error: interceptor_error) if interceptor_error + + sign_request(context) + output = @app.call(input, context) + + interceptor_error = context.interceptors.apply( + hook: Interceptor::Hooks::READ_AFTER_SIGNING, + input: input, + context: context, + output: output, + aggregate_errors: true + ) + output.error = interceptor_error if interceptor_error + + output + end + + private + + def sign_request(context) + auth = context.auth + + auth.signer.sign( + request: context.request, + identity: auth.identity, + properties: auth.signer_properties + ) end end end diff --git a/hearth/lib/hearth/request.rb b/hearth/lib/hearth/request.rb index 9b87cc029..7c40a14a0 100644 --- a/hearth/lib/hearth/request.rb +++ b/hearth/lib/hearth/request.rb @@ -5,7 +5,6 @@ module Hearth # Represents a base request. - # @api private class Request # @param [URI] uri (URI('')) # @param [IO] body (StringIO.new) diff --git a/hearth/lib/hearth/response.rb b/hearth/lib/hearth/response.rb index 3907272ae..2ed1f9b76 100644 --- a/hearth/lib/hearth/response.rb +++ b/hearth/lib/hearth/response.rb @@ -4,7 +4,6 @@ module Hearth # Represents a base response. - # @api private class Response # @param [IO] body (StringIO.new) def initialize(body: StringIO.new) @@ -27,7 +26,7 @@ def replace(other) # Resets the response. # @return [Response] def reset - @body.truncate(0) + @body.truncate(0) if @body.respond_to?(:truncate) self end end diff --git a/hearth/lib/hearth/retry/adaptive.rb b/hearth/lib/hearth/retry/adaptive.rb index 0f7612196..05d3d63d7 100644 --- a/hearth/lib/hearth/retry/adaptive.rb +++ b/hearth/lib/hearth/retry/adaptive.rb @@ -43,7 +43,7 @@ def refresh_retry_token(retry_token, error_info) @capacity_amount = @retry_quota.checkout_capacity(error_info) return unless @capacity_amount.positive? - delay = error_info.hints[:retry_after_hint] + delay = error_info.hints[:retry_after] delay ||= @backoff.call(retry_token.retry_count) retry_token.retry_count += 1 retry_token.retry_delay = delay diff --git a/hearth/lib/hearth/retry/standard.rb b/hearth/lib/hearth/retry/standard.rb index 3903d196d..58fe42fe4 100644 --- a/hearth/lib/hearth/retry/standard.rb +++ b/hearth/lib/hearth/retry/standard.rb @@ -30,7 +30,7 @@ def refresh_retry_token(retry_token, error_info) @capacity_amount = @retry_quota.checkout_capacity(error_info) return unless @capacity_amount.positive? - delay = error_info.hints[:retry_after_hint] + delay = error_info.hints[:retry_after] delay ||= @backoff.call(retry_token.retry_count) retry_token.retry_count += 1 retry_token.retry_delay = delay diff --git a/hearth/lib/hearth/signers.rb b/hearth/lib/hearth/signers.rb index 54a3ddfc4..c8f49cd2b 100644 --- a/hearth/lib/hearth/signers.rb +++ b/hearth/lib/hearth/signers.rb @@ -1,11 +1,16 @@ # frozen_string_literal: true module Hearth + # Namespace for all Signer classes. module Signers # Base class for all Signer classes. class Base - def sign(request:, identity:, properties: {}) - # do nothing + def sign(request:, identity:, properties:) + # Do nothing by default + end + + def reset(request:, properties:) + # Do nothing by default end end end diff --git a/hearth/lib/hearth/signers/http_api_key.rb b/hearth/lib/hearth/signers/http_api_key.rb index 9a0bc6413..f169eda36 100644 --- a/hearth/lib/hearth/signers/http_api_key.rb +++ b/hearth/lib/hearth/signers/http_api_key.rb @@ -2,10 +2,27 @@ module Hearth module Signers - # A signer that signs requests using the HTTP API Key scheme. + # A signer that signs requests using the HTTP API Key Auth scheme. class HTTPApiKey < Signers::Base - def sign(request:, identity:, properties: {}) - # TODO + def sign(request:, identity:, properties:) + case properties[:in] + when 'header' + value = "#{properties[:scheme]} #{identity.key}".strip + request.headers[properties[:name]] = value + when 'query' + name = properties[:name] + request.append_query_param(name, identity.key) + end + end + + def reset(request:, properties:) + case properties[:in] + when 'header' + request.headers.delete(properties[:name]) + when 'query' + name = properties[:name] + request.remove_query_param(name) + end end end end diff --git a/hearth/lib/hearth/signers/http_basic.rb b/hearth/lib/hearth/signers/http_basic.rb index 50dcf68fb..be2add4e2 100644 --- a/hearth/lib/hearth/signers/http_basic.rb +++ b/hearth/lib/hearth/signers/http_basic.rb @@ -1,12 +1,23 @@ # frozen_string_literal: true +require 'base64' + module Hearth module Signers # A signer that signs requests using the HTTP Basic Auth scheme. - class HTTPBasic < Signers::Base - def sign(request:, identity:, properties: {}) - # TODO + class HTTPBasic + # rubocop:disable Lint/UnusedMethodArgument + def sign(request:, identity:, properties:) + # TODO: does not handle realm or other properties + identity_string = "#{identity.username}:#{identity.password}" + encoded = Base64.strict_encode64(identity_string) + request.headers['Authorization'] = "Basic #{encoded}" + end + + def reset(request:, properties:) + request.headers.delete('Authorization') end + # rubocop:enable Lint/UnusedMethodArgument end end end diff --git a/hearth/lib/hearth/signers/http_bearer.rb b/hearth/lib/hearth/signers/http_bearer.rb index 2e5a8c248..a3a2191aa 100644 --- a/hearth/lib/hearth/signers/http_bearer.rb +++ b/hearth/lib/hearth/signers/http_bearer.rb @@ -2,11 +2,18 @@ module Hearth module Signers - # A signer that signs requests using the HTTP Bearer scheme. + # A signer that signs requests using the HTTP Bearer Auth scheme. class HTTPBearer < Signers::Base - def sign(request:, identity:, properties: {}) - # TODO + # rubocop:disable Lint/UnusedMethodArgument + def sign(request:, identity:, properties:) + # TODO: does not handle realm or other properties + request.headers['Authorization'] = "Bearer #{identity.token}" end + + def reset(request:, properties:) + request.headers.delete('Authorization') + end + # rubocop:enable Lint/UnusedMethodArgument end end end diff --git a/hearth/lib/hearth/signers/http_digest.rb b/hearth/lib/hearth/signers/http_digest.rb index 8cb02e35b..6410ce34b 100644 --- a/hearth/lib/hearth/signers/http_digest.rb +++ b/hearth/lib/hearth/signers/http_digest.rb @@ -4,8 +4,15 @@ module Hearth module Signers # A signer that signs requests using the HTTP Digest Auth scheme. class HTTPDigest < Signers::Base - def sign(request:, identity:, properties: {}) - # TODO + def sign(request:, identity:, properties:) + # TODO: requires a nonce from the server - this cannot + # be implemented unless we rescue from a 401 and retry + # with the nonce + raise NotImplementedError + end + + def reset(request:, properties:) + raise NotImplementedError end end end diff --git a/hearth/spec/hearth/auth_schemes/anonymous_spec.rb b/hearth/spec/hearth/auth_schemes/anonymous_spec.rb index fb9976b11..1e6f8633f 100644 --- a/hearth/spec/hearth/auth_schemes/anonymous_spec.rb +++ b/hearth/spec/hearth/auth_schemes/anonymous_spec.rb @@ -20,12 +20,11 @@ module AuthSchemes end describe '#identity_resolver' do - let(:identity_resolver) { double('identity_resolver') } - - it 'returns an identity resolver using identity_type' do - identity_resolvers = { Identities::Anonymous => identity_resolver } - resolver = subject.identity_resolver(identity_resolvers) - expect(resolver).to eq(identity_resolver) + it 'returns a static identity resolver' do + resolver = subject.identity_resolver({}) + expect(resolver).to be_a(Hearth::IdentityResolver) + identity = resolver.identity + expect(identity).to be_a(Identities::Anonymous) end end end diff --git a/hearth/spec/hearth/context_spec.rb b/hearth/spec/hearth/context_spec.rb index b6b5e7d1b..e13de7926 100644 --- a/hearth/spec/hearth/context_spec.rb +++ b/hearth/spec/hearth/context_spec.rb @@ -39,6 +39,14 @@ module Hearth end end + describe '#auth' do + it 'allows for auth to be set' do + resolved_auth = Hearth::Middleware::Auth::ResolvedAuth.new + subject.auth = resolved_auth + expect(subject.auth).to eq(resolved_auth) + end + end + describe '#interceptor_context' do let(:input) { double('input') } let(:output) { double('output') } @@ -52,11 +60,16 @@ module Hearth end end - describe '#auth_scheme' do - it 'allows for auth_scheme to be set' do - auth_scheme = Hearth::Middleware::Auth::SelectedAuthScheme.new - subject.auth_scheme = auth_scheme - expect(subject.auth_scheme).to eq(auth_scheme) + describe '#[]' do + it 'returns the metadata for the given key' do + expect(subject[:foo]).to eq('bar') + end + end + + describe '#[]=' do + it 'sets the metadata for the given key' do + subject[:foo] = 'baz' + expect(subject[:foo]).to eq('baz') end end end diff --git a/hearth/spec/hearth/http/error_inspector_spec.rb b/hearth/spec/hearth/http/error_inspector_spec.rb index 8830555e3..3cc2e76fe 100644 --- a/hearth/spec/hearth/http/error_inspector_spec.rb +++ b/hearth/spec/hearth/http/error_inspector_spec.rb @@ -129,7 +129,7 @@ module HTTP context 'header is an integer' do it 'hint returns an integer delay' do http_resp.headers['retry-after'] = '123' - expect(subject.hints[:retry_after_hint]).to eq(123) + expect(subject.hints[:retry_after]).to eq(123) end end @@ -140,21 +140,21 @@ module HTTP it 'hint returns an integer delay' do http_resp.headers['retry-after'] = retry_after_time allow(Time).to receive(:now).and_return(time) - expect(subject.hints[:retry_after_hint]).to eq(123) + expect(subject.hints[:retry_after]).to eq(123) end end context 'header is nil' do it 'no hint' do http_resp.headers['retry-after'] = nil - expect(subject.hints.key?(:retry_after_hint)).to eq(false) + expect(subject.hints.key?(:retry_after)).to eq(false) end end context 'header is an empty string' do it 'no hint' do http_resp.headers['retry-after'] = '' - expect(subject.hints.key?(:retry_after_hint)).to eq(false) + expect(subject.hints.key?(:retry_after)).to eq(false) end end end diff --git a/hearth/spec/hearth/http/fields_spec.rb b/hearth/spec/hearth/http/fields_spec.rb index 35a1181d1..56e1d715b 100644 --- a/hearth/spec/hearth/http/fields_spec.rb +++ b/hearth/spec/hearth/http/fields_spec.rb @@ -72,7 +72,7 @@ module HTTP end it 'enumerates over its contents' do - fields.each { |k, v| expect(fields[k]).to eq(v) } + fields.each { |field| expect(fields[field.name]).to eq(field) } end end diff --git a/hearth/spec/hearth/http/request_spec.rb b/hearth/spec/hearth/http/request_spec.rb index e5044a2a3..dfa62642b 100644 --- a/hearth/spec/hearth/http/request_spec.rb +++ b/hearth/spec/hearth/http/request_spec.rb @@ -29,33 +29,29 @@ module HTTP describe '#headers' do it 'allows setting of headers' do - request = Request.new - request.headers['name'] = 'value' - expect(request.fields['name'].value).to eq('value') - expect(request.fields['name'].kind).to eq(:header) + subject.headers['name'] = 'value' + expect(subject.fields['name'].value).to eq('value') + expect(subject.fields['name'].kind).to eq(:header) end it 'lets you get a hash of only the headers' do - request = Request.new - request.headers['name'] = 'value' - request.trailers['trailer'] = 'trailer-value' - expect(request.headers.to_h).to eq('name' => 'value') + subject.headers['name'] = 'value' + subject.trailers['trailer'] = 'trailer-value' + expect(subject.headers.to_h).to eq('name' => 'value') end end describe '#trailers' do it 'allows setting of trailers' do - request = Request.new - request.trailers['name'] = 'value' - expect(request.fields['name'].value).to eq('value') - expect(request.fields['name'].kind).to eq(:trailer) + subject.trailers['name'] = 'value' + expect(subject.fields['name'].value).to eq('value') + expect(subject.fields['name'].kind).to eq(:trailer) end it 'lets you get a hash of only the trailers' do - request = Request.new - request.trailers['name'] = 'value' - request.headers['header'] = 'header-value' - expect(request.trailers.to_h).to eq('name' => 'value') + subject.trailers['name'] = 'value' + subject.headers['header'] = 'header-value' + expect(subject.trailers.to_h).to eq('name' => 'value') end end @@ -115,8 +111,9 @@ module HTTP params['key 3'] = 'value' params['key 4'] = %w[value value2] subject.append_query_param_list(params) - expect(subject.uri.to_s) - .to eq('http://example.com?original&key%201&key%202=&key%203=value&key%204=value&key%204=value2') + expected = 'http://example.com?original&key%201&key%202=&' \ + 'key%203=value&key%204=value&key%204=value2' + expect(subject.uri.to_s).to eq(expected) end it 'does not append empty param lists' do @@ -126,6 +123,18 @@ module HTTP end end + describe '#remove_query_param' do + it 'removes a single value' do + subject.append_query_param('query') + subject.append_query_param('empty', '') + subject.append_query_param('deleteme', 'true') + subject.append_query_param('key', 'value') + subject.remove_query_param('deleteme') + expected = 'http://example.com?query&empty=&key=value' + expect(subject.uri.to_s).to eq(expected) + end + end + describe '#prefix_host' do it 'prefixes the host' do subject.prefix_host('data.') diff --git a/hearth/spec/hearth/middleware/auth_spec.rb b/hearth/spec/hearth/middleware/auth_spec.rb index 699ab1648..ca62b0451 100644 --- a/hearth/spec/hearth/middleware/auth_spec.rb +++ b/hearth/spec/hearth/middleware/auth_spec.rb @@ -50,10 +50,6 @@ module Middleware double('http_bearer_identity_resolver') end - let(:anonymous_identity_resolver) do - double('anonymous_identity_resolver') - end - subject do Auth.new( app, @@ -64,11 +60,6 @@ module Middleware ) end - before do - expect(IdentityResolver).to receive(:new) - .and_return(anonymous_identity_resolver) - end - describe '#initialize' do it 'converts auth schemes to a hash for lookup' do schemes = subject.instance_variable_get(:@auth_schemes) @@ -81,13 +72,11 @@ module Middleware resolvers = subject.instance_variable_get(:@identity_resolvers) expect(resolvers).to be_a(Hash) expect(resolvers.keys).to eq [ - Identities::Anonymous, Identities::HTTPApiKey, Identities::HTTPBearer, Identities::HTTPLogin ] expect(resolvers.values).to eq [ - anonymous_identity_resolver, identity_resolvers[:http_api_key_identity_resolver], identity_resolvers[:http_bearer_identity_resolver], identity_resolvers[:http_login_identity_resolver] @@ -104,8 +93,7 @@ module Middleware ) resolvers = auth.instance_variable_get(:@identity_resolvers) expect(resolvers).to be_a(Hash) - expect(resolvers.keys).to eq([Identities::Anonymous]) - expect(resolvers.values).to eq([anonymous_identity_resolver]) + expect(resolvers).to be_empty end it 'raises an error for unknown identity types' do diff --git a/hearth/spec/hearth/middleware/retry_spec.rb b/hearth/spec/hearth/middleware/retry_spec.rb index 77f9ce2f1..46eaaa17e 100644 --- a/hearth/spec/hearth/middleware/retry_spec.rb +++ b/hearth/spec/hearth/middleware/retry_spec.rb @@ -115,6 +115,93 @@ module Middleware before { allow(error).to receive(:retryable?).and_return(true) } + let(:token) { double('token', retry_delay: 0) } + let(:retry_strategy) do + double( + 'retry', + acquire_initial_retry_token: token, + record_success: nil, + refresh_retry_token: nil + ) + end + let(:app) { double('app') } + let(:output) { double('output', error: error) } + let(:subject) do + Hearth::Middleware::Retry.new( + app, + error_inspector_class: Hearth::HTTP::ErrorInspector, + retry_strategy: retry_strategy + ) + end + + let(:signer) { double('signer', reset: nil) } + let(:identity) { double('identity') } + let(:auth_option) do + double( + 'auth_option', + signer_properties: {}, + identity_properties: {} + ) + end + let(:resolved_auth) do + double( + 'resolved_auth', + signer: signer, + signer_properties: auth_option.signer_properties, + identity: identity, + identity_properties: auth_option.identity_properties + ) + end + + before { context.auth = resolved_auth } + + it 'calls all of the interceptor hooks and the app' do + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::READ_BEFORE_ATTEMPT + )).ordered + expect(app).to receive(:call).and_return(output).ordered + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::MODIFY_BEFORE_ATTEMPT_COMPLETION + )).ordered + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::READ_AFTER_ATTEMPT + )).ordered + + subject.call(input, context) + end + + context 'read_before_attempt error' do + it 'returns output with the error and does not call app' do + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::READ_BEFORE_ATTEMPT + )).and_return(error) + expect(app).not_to receive(:call) + + resp = subject.call(input, context) + + expect(resp.error).to eq(error) + end + end + + it 'resets request, response, and output error' do + expect(app).to receive(:call).and_return(output).twice + expect(retry_strategy).to receive(:refresh_retry_token) + .and_return(token, nil) + + expect(request.body).to receive(:rewind) + expect(response).to receive(:reset) + expect(output).to receive(:error=).with(nil) + expect(signer).to receive(:reset).with( + request: request, + properties: auth_option.signer_properties + ) + subject.call(input, context) + end + context 'standard mode' do let(:retry_strategy) { Hearth::Retry::Standard.new } let(:retry_quota) do @@ -364,57 +451,6 @@ def throttle(timestamp, measured_tx_rate, fill_rate) success(3.4, 4.32, 3.45), middleware_args end end - - context 'interceptors' do - let(:token) { double('token') } - let(:retry_strategy) do - double('retry', - acquire_initial_retry_token: token, - record_success: nil, - refresh_retry_token: nil) - end - let(:app) { double('app') } - let(:output) { double('output', error: nil) } - let(:subject) do - Hearth::Middleware::Retry.new( - app, - error_inspector_class: Hearth::HTTP::ErrorInspector, - retry_strategy: retry_strategy - ) - end - - it 'calls all of the interceptor hooks and the app' do - expect(interceptors).to receive(:apply) - .with(hash_including( - hook: Interceptor::Hooks::READ_BEFORE_ATTEMPT - )).ordered - expect(app).to receive(:call).and_return(output).ordered - expect(interceptors).to receive(:apply) - .with(hash_including( - hook: Interceptor::Hooks::MODIFY_BEFORE_ATTEMPT_COMPLETION - )).ordered - expect(interceptors).to receive(:apply) - .with(hash_including( - hook: Interceptor::Hooks::READ_AFTER_ATTEMPT - )).ordered - - subject.call(input, context) - end - - context 'read_before_attempt error' do - it 'returns output with the error and does not call app' do - expect(interceptors).to receive(:apply) - .with(hash_including( - hook: Interceptor::Hooks::READ_BEFORE_ATTEMPT - )).and_return(error) - expect(app).not_to receive(:call) - - resp = subject.call(input, context) - - expect(resp.error).to eq(error) - end - end - end end end end diff --git a/hearth/spec/hearth/middleware/send_spec.rb b/hearth/spec/hearth/middleware/send_spec.rb index 963c16d48..2b4232fe1 100644 --- a/hearth/spec/hearth/middleware/send_spec.rb +++ b/hearth/spec/hearth/middleware/send_spec.rb @@ -131,46 +131,6 @@ def self.stub(resp, stub:); end subject.call(input, context) end - context 'stub is a proc' do - before { stubs.add_stubs(operation, [stub_proc]) } - - context 'proc returns a stub' do - let(:exception) { Exception.new } - let(:stub_proc) { proc { exception } } - - it 'calls the stub and applies it' do - expect(stub_proc).to receive(:call) - .with(input, context).and_call_original - output = subject.call(input, context) - expect(output.error).to be(exception) - end - end - - context 'proc returns nil' do - let(:url) { 'https://example.com' } - let(:status) { 418 } - let(:more_context) { 'more_context' } - let(:response) { Hearth::HTTP::Response.new } - - let(:stub_proc) do - lambda do |_input, context| - context.response.status = status - context.metadata[:more_context] = more_context - nil - end - end - - it 'allows stubbing of request, response, and context' do - expect(stub_proc).to receive(:call) - .with(input, context).and_call_original - expect(Stubs::StubData).to_not receive(:stub) - subject.call(input, context) - expect(response.status).to eq status - expect(context.metadata[:more_context]).to eq(more_context) - end - end - end - context 'stub is an Exception' do let(:error) { Exception.new } @@ -180,6 +140,17 @@ def self.stub(resp, stub:); end output = subject.call(input, context) expect(output.error).to be(error) end + + context 'as a proc' do + let(:stub_proc) { proc { error } } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'sets the output error as the exception' do + output = subject.call(input, context) + expect(output.error).to be(error) + end + end end context 'stub is an ApiError' do @@ -191,6 +162,17 @@ def self.stub(resp, stub:); end output = subject.call(input, context) expect(output.error).to be(error) end + + context 'as a proc' do + let(:stub_proc) { proc { error } } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'sets the output error as the error' do + output = subject.call(input, context) + expect(output.error).to be(error) + end + end end context 'stub is a hash' do @@ -210,6 +192,23 @@ def self.stub(resp, stub:); end .with(response, stub: stub_data) subject.call(input, context) end + + context 'as a proc' do + let(:stub_proc) { proc { { data: stub_hash } } } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'uses the data hash to stub the response' do + expect(Stubs::StubData).to receive(:build) + .with(stub_hash, context: 'stub') + .and_return(stub_data) + expect(Stubs::StubData).to receive(:validate!) + .with(stub_data, context: 'stub') + expect(Stubs::StubData).to receive(:stub) + .with(response, stub: stub_data) + subject.call(input, context) + end + end end context 'error stub' do @@ -232,6 +231,23 @@ def self.stub(resp, stub:); end subject.call(input, context) end + context 'as a proc' do + let(:stub_proc) { proc { { error: stub_hash } } } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'uses the error hash to stub the response' do + expect(Stubs::StubError).to receive(:build) + .with(stub_error_data, context: 'stub') + .and_return(stub_error) + expect(Stubs::StubError).to receive(:validate!) + .with(stub_error, context: 'stub') + expect(Stubs::StubError).to receive(:stub) + .with(response, stub: stub_error) + subject.call(input, context) + end + end + it 'raises when missing an error class' do stub_hash.delete(:class) expect do @@ -299,6 +315,24 @@ def self.stub(resp, stub:); end .with(response, stub: stub_data) subject.call(input, context) end + + context 'as a proc' do + let(:stub_proc) { proc {} } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'uses the stub class default' do + expect(Stubs::StubData).to receive(:default) + .and_return(stub_hash) + expect(Stubs::StubData).to receive(:build) + .with(stub_hash, context: 'stub') + .and_return(stub_data) + expect(Stubs::StubData).to receive(:validate!) + expect(Stubs::StubData).to receive(:stub) + .with(response, stub: stub_data) + subject.call(input, context) + end + end end context 'stub is a Hearth::Structure' do @@ -313,6 +347,20 @@ def self.stub(resp, stub:); end .with(response, stub: stub_data) subject.call(input, context) end + + context 'as a proc' do + let(:stub_proc) { proc { stub_data } } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'uses the stub class to stub the response' do + expect(Stubs::StubData).to receive(:validate!) + .with(stub_data, context: 'stub') + expect(Stubs::StubData).to receive(:stub) + .with(response, stub: stub_data) + subject.call(input, context) + end + end end context 'stub is a Hearth::Response' do @@ -321,11 +369,22 @@ def self.stub(resp, stub:); end before { stubs.add_stubs(operation, [stub_response]) } it 'sets the response to the stub' do - expect(context.response).to receive(:replace) - .with(stub_response) - + expect(context.response) + .to receive(:replace).with(stub_response) subject.call(input, context) end + + context 'as a proc' do + let(:stub_proc) { proc { stub_response } } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'sets the response to the stub' do + expect(context.response) + .to receive(:replace).with(stub_response) + subject.call(input, context) + end + end end context 'stub is something else' do @@ -336,6 +395,18 @@ def self.stub(resp, stub:); end subject.call(input, context) end.to raise_error(ArgumentError) end + + context 'as a proc' do + let(:stub_proc) { proc { 'some string' } } + + before { stubs.add_stubs(operation, [stub_proc]) } + + it 'raises a ArgumentError' do + expect do + subject.call(input, context) + end.to raise_error(ArgumentError) + end + end end end end diff --git a/hearth/spec/hearth/middleware/sign_spec.rb b/hearth/spec/hearth/middleware/sign_spec.rb new file mode 100644 index 000000000..dbdf165e7 --- /dev/null +++ b/hearth/spec/hearth/middleware/sign_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module Hearth + module Middleware + describe Build do + let(:app) { double('app', call: output) } + + subject { Sign.new(app) } + + describe '#call' do + let(:input) { double('input') } + let(:output) { double('output') } + let(:request) { double('request') } + let(:response) { double('response') } + let(:interceptors) { double('interceptors', apply: nil) } + let(:context) do + Context.new( + request: request, + response: response, + interceptors: interceptors + ) + end + + let(:signer) { double('signer', sign: nil) } + let(:identity) { double('identity') } + let(:auth_option) do + double( + 'auth_option', + signer_properties: {}, + identity_properties: {} + ) + end + let(:resolved_auth) do + double( + 'resolved_auth', + signer: signer, + signer_properties: auth_option.signer_properties, + identity: identity, + identity_properties: auth_option.identity_properties + ) + end + + before { context.auth = resolved_auth } + + it 'signs then calls the next middleware' do + expect(signer).to receive(:sign) + .with(request: request, identity: identity, properties: {}) + .ordered + expect(app).to receive(:call).with(input, context).ordered + + resp = subject.call(input, context) + expect(resp).to be output + end + + it 'calls before_signing interceptors before sign' do + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::MODIFY_BEFORE_SIGNING + )).ordered + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::READ_BEFORE_SIGNING + )).ordered + expect(signer).to receive(:sign).ordered + expect(app).to receive(:call).ordered + + subject.call(input, context) + end + + context 'modify_before_signing error' do + let(:error) { StandardError.new } + + it 'returns output with the error and skips signing' do + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::MODIFY_BEFORE_SIGNING + )) + .and_return(error) + + expect(signer).not_to receive(:sign) + expect(app).not_to receive(:call) + + resp = subject.call(input, context) + + expect(resp.error).to eq(error) + end + end + + context 'read_before_signing error' do + let(:error) { StandardError.new } + + it 'returns output with the error and skips signing' do + expect(interceptors).to receive(:apply) + .with(hash_including( + hook: Interceptor::Hooks::READ_BEFORE_SIGNING + )) + .and_return(error) + + expect(signer).not_to receive(:sign) + expect(app).not_to receive(:call) + + resp = subject.call(input, context) + + expect(resp.error).to eq(error) + end + end + end + end + end +end diff --git a/hearth/spec/hearth/response_spec.rb b/hearth/spec/hearth/response_spec.rb index 56d89b79d..9e759128c 100644 --- a/hearth/spec/hearth/response_spec.rb +++ b/hearth/spec/hearth/response_spec.rb @@ -25,6 +25,16 @@ module Hearth response.reset expect(response.body.string).to eq('') end + + it 'does not truncate IO' do + read, write = IO.pipe + write.write('foo') + response = Response.new(body: read) + response.reset + write.close + expect(read.read).to eq('foo') + read.close + end end end end diff --git a/hearth/spec/hearth/signers/http_api_key_spec.rb b/hearth/spec/hearth/signers/http_api_key_spec.rb new file mode 100644 index 000000000..b3ba653e5 --- /dev/null +++ b/hearth/spec/hearth/signers/http_api_key_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Hearth + module Signers + describe HTTPApiKey do + let(:request) { HTTP::Request.new } + let(:identity) { Identities::HTTPApiKey.new(key: key) } + let(:key) { 'key' } + + describe '#sign' do + context 'header' do + let(:properties) do + { name: 'X-Api-Key', in: 'header', scheme: 'OAuth' } + end + + it 'signs the request' do + expect(request.headers['x-api-key']).to be_nil + subject.sign( + request: request, + identity: identity, + properties: properties + ) + expect(request.headers['x-api-key']).to eq("OAuth #{key}") + end + end + + context 'query' do + let(:properties) do + { name: 'api_key', in: 'query' } + end + + it 'signs the request' do + expect(request.uri.query).to be_nil + subject.sign( + request: request, + identity: identity, + properties: properties + ) + expect(request.uri.query).to eq("api_key=#{key}") + end + end + end + + describe '#reset' do + context 'header' do + let(:properties) do + { name: 'X-Api-Key', in: 'header', scheme: 'OAuth' } + end + + it 'resets the request' do + request.headers['x-api-key'] = 'OAuth key' + subject.reset(request: request, properties: properties) + expect(request.headers['x-api-key']).to be_nil + end + end + + context 'query' do + let(:properties) do + { name: 'api_key', in: 'query' } + end + + it 'resets the request' do + request.uri.query = 'api_key=key' + subject.reset(request: request, properties: properties) + expect(request.uri.query).to eq('') + end + end + end + end + end +end diff --git a/hearth/spec/hearth/signers/http_basic_spec.rb b/hearth/spec/hearth/signers/http_basic_spec.rb new file mode 100644 index 000000000..136aa5b3e --- /dev/null +++ b/hearth/spec/hearth/signers/http_basic_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Hearth + module Signers + describe HTTPBasic do + let(:request) { HTTP::Request.new } + let(:identity) do + Identities::HTTPLogin.new(username: username, password: password) + end + let(:username) { 'username' } + let(:password) { 'password' } + let(:properties) { {} } + + describe '#sign' do + it 'signs the request' do + expect(request.headers['authorization']).to be_nil + subject.sign( + request: request, + identity: identity, + properties: properties + ) + encoded = Base64.strict_encode64("#{username}:#{password}") + expect(request.headers['authorization']).to eq("Basic #{encoded}") + end + end + + describe '#reset' do + it 'resets the request' do + encoded = Base64.strict_encode64("#{username}:#{password}") + request.headers['authorization'] = "Basic #{encoded}" + subject.reset(request: request, properties: properties) + expect(request.headers['authorization']).to be_nil + end + end + end + end +end diff --git a/hearth/spec/hearth/signers/http_bearer_spec.rb b/hearth/spec/hearth/signers/http_bearer_spec.rb new file mode 100644 index 000000000..5929ee2df --- /dev/null +++ b/hearth/spec/hearth/signers/http_bearer_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Hearth + module Signers + describe HTTPBearer do + let(:request) { HTTP::Request.new } + let(:identity) { Identities::HTTPBearer.new(token: token) } + let(:token) { 'token' } + let(:properties) { {} } + + describe '#sign' do + it 'signs the request' do + expect(request.headers['authorization']).to be_nil + subject.sign( + request: request, + identity: identity, + properties: properties + ) + expect(request.headers['authorization']).to eq("Bearer #{token}") + end + end + + describe '#reset' do + it 'resets the request' do + request.headers['authorization'] = "Bearer #{token}" + subject.reset(request: request, properties: properties) + expect(request.headers['authorization']).to be_nil + end + end + end + end +end diff --git a/hearth/spec/hearth/signers/http_digest_spec.rb b/hearth/spec/hearth/signers/http_digest_spec.rb new file mode 100644 index 000000000..a973e6b63 --- /dev/null +++ b/hearth/spec/hearth/signers/http_digest_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Hearth + module Signers + describe HTTPDigest do + let(:request) { HTTP::Request.new } + let(:identity) do + Identities::HTTPLogin.new(username: username, password: password) + end + let(:username) { 'username' } + let(:password) { 'password' } + let(:properties) { {} } + + describe '#sign' do + it 'signs the request' do + expect do + subject.sign( + request: request, + identity: identity, + properties: properties + ) + end.to raise_error(NotImplementedError) + end + end + + describe '#reset' do + it 'resets the request' do + expect do + subject.reset(request: request, properties: properties) + end.to raise_error(NotImplementedError) + end + end + end + end +end diff --git a/sample-service/app/controllers/auth_controller.rb b/sample-service/app/controllers/auth_controller.rb new file mode 100644 index 000000000..090f03b29 --- /dev/null +++ b/sample-service/app/controllers/auth_controller.rb @@ -0,0 +1,33 @@ +class AuthController < ApplicationController + include ActionController::HttpAuthentication::Basic::ControllerMethods + include ActionController::HttpAuthentication::Digest::ControllerMethods + include ActionController::HttpAuthentication::Token::ControllerMethods + + http_basic_authenticate_with name: 'basic', password: 'basic', realm: 'Basic Auth', only: :basic_auth + before_action :authenticate_with_digest, only: :digest_auth + before_action :authenticate_with_token, only: [:bearer_auth, :api_key_auth] + + def basic_auth + end + + def digest_auth + end + + def bearer_auth + end + + def api_key_auth + end + + private + + def authenticate_with_digest + authenticate_or_request_with_http_digest('Digest Auth') { |username| 'digest' } + end + + def authenticate_with_token + authenticate_or_request_with_http_token do |token, options| + ActiveSupport::SecurityUtils.secure_compare(token, 'token') + end + end +end diff --git a/sample-service/config/routes.rb b/sample-service/config/routes.rb index f11ec15f1..eede247e1 100644 --- a/sample-service/config/routes.rb +++ b/sample-service/config/routes.rb @@ -1,4 +1,9 @@ Rails.application.routes.draw do resources :high_scores # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + + get 'basic_auth', to: 'auth#basic_auth' + get 'digest_auth', to: 'auth#digest_auth' + get 'bearer_auth', to: 'auth#bearer_auth' + get 'api_key_auth', to: 'auth#api_key_auth' end