diff --git a/gems/smithy-client/lib/smithy-client.rb b/gems/smithy-client/lib/smithy-client.rb index 8d4bc86e6..9ff8b5322 100644 --- a/gems/smithy-client/lib/smithy-client.rb +++ b/gems/smithy-client/lib/smithy-client.rb @@ -46,7 +46,6 @@ # identity and auth -require_relative 'smithy-client/auth_option' require_relative 'smithy-client/auth_scheme' require_relative 'smithy-client/identity' require_relative 'smithy-client/refreshing_identity_provider' diff --git a/gems/smithy-client/lib/smithy-client/anonymous_provider.rb b/gems/smithy-client/lib/smithy-client/anonymous_provider.rb index a3ed9444d..4455f6006 100644 --- a/gems/smithy-client/lib/smithy-client/anonymous_provider.rb +++ b/gems/smithy-client/lib/smithy-client/anonymous_provider.rb @@ -4,7 +4,7 @@ module Smithy module Client # @api private class AnonymousProvider - def identity(_properties) + def identity Identities::Anonymous.new end end diff --git a/gems/smithy-client/lib/smithy-client/auth_option.rb b/gems/smithy-client/lib/smithy-client/auth_option.rb deleted file mode 100644 index d60ca9d97..000000000 --- a/gems/smithy-client/lib/smithy-client/auth_option.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Smithy - module Client - # Object that represents an auth option, returned by Auth Resolvers. - class AuthOption - def initialize(scheme_id:, identity_properties: {}, signer_properties: {}) - @scheme_id = scheme_id - @identity_properties = identity_properties - @signer_properties = signer_properties - end - - # @return [String] - attr_reader :scheme_id - - # @return [Hash] - attr_reader :identity_properties - - # @return [Hash] - attr_reader :signer_properties - end - end -end diff --git a/gems/smithy-client/lib/smithy-client/auth_schemes/auth.rb b/gems/smithy-client/lib/smithy-client/auth_schemes/auth.rb new file mode 100644 index 000000000..4ed3b5a97 --- /dev/null +++ b/gems/smithy-client/lib/smithy-client/auth_schemes/auth.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Smithy + module Client + module AuthSchemes + # @api private + class Handler < Smithy::Client::Handler + def call(context) + # TODO: apply endpoint auth properties if present + auth_options = context.config.auth_resolver.resolve(context) + context.auth = resolve_auth(context, auth_options) + @handler.call(context) + end + + private + + def resolve_auth(context, auth_options) + failures = [] + + raise 'No auth options were resolved' if auth_options.empty? + + identity_providers = context.client.class.identity_providers(context) + + auth_options.each do |auth_option| + auth_scheme = context.config.auth_schemes[auth_option] + resolved_auth = try_load_auth_scheme( + auth_option, + auth_scheme, + identity_providers, + failures + ) + + return resolved_auth if resolved_auth + end + + raise failures.join("\n") + end + + def try_load_auth_scheme(auth_option, auth_scheme, identity_providers, failures) + scheme_id = auth_option + unless auth_scheme + failures << "Auth scheme #{scheme_id} was not enabled " \ + 'for this request' + return + end + + identity_provider = auth_scheme.identity_provider(identity_providers) + unless identity_provider + failures << "Auth scheme #{scheme_id} did not have an " \ + 'identity resolver configured' + return + end + + { + scheme_id: scheme_id, + identity: identity_provider.identity, + signer: auth_scheme.signer + } + end + end + end + end +end diff --git a/gems/smithy-client/lib/smithy-client/handler_context.rb b/gems/smithy-client/lib/smithy-client/handler_context.rb index c03d35a27..22212c453 100644 --- a/gems/smithy-client/lib/smithy-client/handler_context.rb +++ b/gems/smithy-client/lib/smithy-client/handler_context.rb @@ -11,6 +11,7 @@ class HandlerContext # @option options [Configuration] :config (nil) # @option options [HTTP::Request] :http_request (HTTP::Request.new) # @option options [HTTP::Response] :http_response (HTTP::Response.new) + # @option options [Hash] :auth (nil) # @option options [Hash] :metadata ({}) def initialize(options = {}) @operation_name = options[:operation_name] @@ -20,6 +21,7 @@ def initialize(options = {}) @config = options[:config] @http_request = options[:http_request] || HTTP::Request.new @http_response = options[:http_response] || HTTP::Response.new + @auth = options[:auth] @retries = 0 @metadata = {} end @@ -45,6 +47,9 @@ def initialize(options = {}) # @return [HTTP::Response] attr_accessor :http_response + # @return [Hash] + attr_accessor :auth + # @return [Integer] attr_accessor :retries diff --git a/gems/smithy-client/lib/smithy-client/http_api_key_provider.rb b/gems/smithy-client/lib/smithy-client/http_api_key_provider.rb index 271aa57d1..d37794f84 100644 --- a/gems/smithy-client/lib/smithy-client/http_api_key_provider.rb +++ b/gems/smithy-client/lib/smithy-client/http_api_key_provider.rb @@ -10,9 +10,7 @@ def initialize(key) end # @return [Identities::HttpApiKey] - def identity(_properties) - @identity - end + attr_reader :identity end end end diff --git a/gems/smithy-client/lib/smithy-client/http_bearer_provider.rb b/gems/smithy-client/lib/smithy-client/http_bearer_provider.rb index 54e06bfad..5c9934acf 100644 --- a/gems/smithy-client/lib/smithy-client/http_bearer_provider.rb +++ b/gems/smithy-client/lib/smithy-client/http_bearer_provider.rb @@ -10,9 +10,7 @@ def initialize(token) end # @return [Identities::HttpBearer] - def identity(_properties) - @identity - end + attr_reader :identity end end end diff --git a/gems/smithy-client/lib/smithy-client/http_login_provider.rb b/gems/smithy-client/lib/smithy-client/http_login_provider.rb index b64fbf3c6..2b8c52001 100644 --- a/gems/smithy-client/lib/smithy-client/http_login_provider.rb +++ b/gems/smithy-client/lib/smithy-client/http_login_provider.rb @@ -11,9 +11,7 @@ def initialize(username, password) end # @return [Identities::HttpLogin] - def identity(_properties) - @identity - end + attr_reader :identity end end end diff --git a/gems/smithy-client/lib/smithy-client/plugins/http_api_key_auth.rb b/gems/smithy-client/lib/smithy-client/plugins/http_api_key_auth.rb index 868ae7c1e..e310d9140 100644 --- a/gems/smithy-client/lib/smithy-client/plugins/http_api_key_auth.rb +++ b/gems/smithy-client/lib/smithy-client/plugins/http_api_key_auth.rb @@ -23,7 +23,7 @@ class HttpApiKeyAuth < Plugin doc_type: HttpApiKeyProvider, docstring: <<~DOCS) do |config| An API key identity provider. This can be an instance of a {Smithy::Client::HttpApiKeyProvider} or any - class that responds to #identity(properties) and returns a {Smithy::Client::Identities::HttpApiKey}. + class that responds to #identity and returns a {Smithy::Client::Identities::HttpApiKey}. DOCS HttpApiKeyProvider.new(config.http_api_key) if config.http_api_key end diff --git a/gems/smithy-client/lib/smithy-client/plugins/http_basic_auth.rb b/gems/smithy-client/lib/smithy-client/plugins/http_basic_auth.rb index db030288f..5154e2d5b 100644 --- a/gems/smithy-client/lib/smithy-client/plugins/http_basic_auth.rb +++ b/gems/smithy-client/lib/smithy-client/plugins/http_basic_auth.rb @@ -31,7 +31,7 @@ class HttpBasicAuth < Plugin doc_type: Smithy::Client::HttpLoginProvider, docstring: <<~DOCS) do |config| A login identity provider. This can be an instance of a {Smithy::Client::HttpLoginProvider} or any - class that responds to #identity(properties) and returns a {Smithy::Client::Identities::HttpLogin}. + class that responds to #identity and returns a {Smithy::Client::Identities::HttpLogin}. DOCS if config.http_login_username && config.http_login_password Smithy::Client::HttpLoginProvider.new(config.http_login_username, config.http_login_password) diff --git a/gems/smithy-client/lib/smithy-client/plugins/http_bearer_auth.rb b/gems/smithy-client/lib/smithy-client/plugins/http_bearer_auth.rb index 32323ba34..88bfe92c3 100644 --- a/gems/smithy-client/lib/smithy-client/plugins/http_bearer_auth.rb +++ b/gems/smithy-client/lib/smithy-client/plugins/http_bearer_auth.rb @@ -23,7 +23,7 @@ class HttpBearerAuth < Plugin doc_type: Smithy::Client::HttpBearerProvider, docstring: <<~DOCS) do |config| A bearer token identity provider. This can be an instance of a {Smithy::Client::HttpBearerProvider} or any - class that responds to #identity(properties) and returns a {Smithy::Client::Identities::HttpBearer}. + class that responds to #identity and returns a {Smithy::Client::Identities::HttpBearer}. DOCS Smithy::Client::HttpBearerProvider.new(config.http_bearer_token) if config.http_bearer_token end diff --git a/gems/smithy-client/lib/smithy-client/plugins/http_digest_auth.rb b/gems/smithy-client/lib/smithy-client/plugins/http_digest_auth.rb index f1d30210c..40a70f87a 100644 --- a/gems/smithy-client/lib/smithy-client/plugins/http_digest_auth.rb +++ b/gems/smithy-client/lib/smithy-client/plugins/http_digest_auth.rb @@ -44,7 +44,7 @@ class HttpDigestAuth < Plugin doc_type: Smithy::Client::HttpLoginProvider, docstring: <<~DOCS) do |config| A login identity provider. This can be an instance of a {Smithy::Client::HttpLoginProvider} or any - class that responds to #identity(properties) and returns a {Smithy::Client::Identities::HttpLogin}. + class that responds to #identity and returns a {Smithy::Client::Identities::HttpLogin}. DOCS if config.http_login_username && config.http_login_password Smithy::Client::HttpLoginProvider.new(config.http_login_username, config.http_login_password) diff --git a/gems/smithy-client/lib/smithy-client/plugins/sign_requests.rb b/gems/smithy-client/lib/smithy-client/plugins/sign_requests.rb index 8142dc949..38fa9f7e4 100644 --- a/gems/smithy-client/lib/smithy-client/plugins/sign_requests.rb +++ b/gems/smithy-client/lib/smithy-client/plugins/sign_requests.rb @@ -8,11 +8,7 @@ class SignRequests < Plugin # @api private class Handler < Client::Handler def call(context) - context[:auth].signer.sign( - request: context.http_request, - identity: context[:auth].identity, - properties: context[:auth].signer_properties - ) + context.auth[:signer].sign(context) @handler.call(context) end end diff --git a/gems/smithy-client/lib/smithy-client/refreshing_identity_provider.rb b/gems/smithy-client/lib/smithy-client/refreshing_identity_provider.rb index f3a89aa60..53ae29878 100644 --- a/gems/smithy-client/lib/smithy-client/refreshing_identity_provider.rb +++ b/gems/smithy-client/lib/smithy-client/refreshing_identity_provider.rb @@ -3,8 +3,8 @@ module Smithy module Client # A module that can be included in a class to provide a #identity method. - # The class must implement #refresh(properties) that sets @identity. The - # refresh method will be called when #identity is called and the identity + # The class must implement #refresh that sets @identity. The refresh + # method will be called when #identity is called and the identity # is nil or near expiration. module RefreshingIdentityProvider SYNC_EXPIRATION_LENGTH = 300 # 5 minutes @@ -15,11 +15,11 @@ def initialize end # @return [Identities::Base] - def identity(properties = {}) + def identity if @identity - refresh_if_near_expiration!(properties) + refresh_if_near_expiration! else # initialization - @mutex.synchronize { refresh(properties) } + @mutex.synchronize { refresh } end @identity end @@ -38,19 +38,19 @@ def async_expiration_length # If we are near to expiration, block while refreshing the identity. # Otherwise, if we're approaching expiration, use the existing identity # but attempt a refresh in the background. - def refresh_if_near_expiration!(properties) + def refresh_if_near_expiration! # NOTE: This check is an optimization. Rather than acquire the mutex on # every #refresh_if_near_expiration call, we check before doing so, and # then we check within the mutex to avoid a race condition. if near_expiration?(sync_expiration_length) @mutex.synchronize do - refresh(properties) if near_expiration?(sync_expiration_length) + refresh if near_expiration?(sync_expiration_length) end elsif @async_refresh && near_expiration?(async_expiration_length) unless @mutex.locked? Thread.new do @mutex.synchronize do - refresh(properties) if near_expiration?(async_expiration_length) + refresh if near_expiration?(async_expiration_length) end end end diff --git a/gems/smithy-client/lib/smithy-client/signers/anonymous.rb b/gems/smithy-client/lib/smithy-client/signers/anonymous.rb index 63c256186..49ce05855 100644 --- a/gems/smithy-client/lib/smithy-client/signers/anonymous.rb +++ b/gems/smithy-client/lib/smithy-client/signers/anonymous.rb @@ -5,8 +5,8 @@ module Client module Signers # A signer that does not sign requests. class Anonymous < Signer - def sign(**); end - def reset(**); end + def sign(context); end + def reset(context); end end end end diff --git a/gems/smithy-client/lib/smithy-client/signers/http_api_key.rb b/gems/smithy-client/lib/smithy-client/signers/http_api_key.rb index b1c21e9f6..056b9ae8d 100644 --- a/gems/smithy-client/lib/smithy-client/signers/http_api_key.rb +++ b/gems/smithy-client/lib/smithy-client/signers/http_api_key.rb @@ -5,7 +5,11 @@ module Client module Signers # A signer that signs requests using the HTTP API Key Auth scheme. class HttpApiKey < Signer - def sign(request:, identity:, properties:) + def sign(context) # rubocop:disable Metrics/AbcSize + reset(context) + request = context.http_request + identity = context.auth[:identity] + properties = context.config.service.traits['smithy.api#httpApiKeyAuth'] case properties['in'] when 'header' value = "#{properties['scheme']} #{identity.key}".strip @@ -16,7 +20,9 @@ def sign(request:, identity:, properties:) end end - def reset(request:, properties:) + def reset(context) + request = context.http_request + properties = context.config.service.traits['smithy.api#httpApiKeyAuth'] case properties['in'] when 'header' request.headers.delete(properties['name']) @@ -37,6 +43,8 @@ def append_query_param(request, name, value) end def remove_query_param(request, name) + return unless request.endpoint.query + parsed = CGI.parse(request.endpoint.query) parsed.delete(name) # encode_www_form ignores query params without values diff --git a/gems/smithy-client/lib/smithy-client/signers/http_basic.rb b/gems/smithy-client/lib/smithy-client/signers/http_basic.rb index 475e76b29..b91fdf8a3 100644 --- a/gems/smithy-client/lib/smithy-client/signers/http_basic.rb +++ b/gems/smithy-client/lib/smithy-client/signers/http_basic.rb @@ -7,15 +7,17 @@ module Client module Signers # A signer that signs requests using the HTTP Basic Auth scheme. class HttpBasic < Signer - def sign(request:, identity:, **_options) + def sign(context) + reset(context) # TODO: does not handle realm or other properties + identity = context.auth[:identity] identity_string = "#{identity.username}:#{identity.password}" encoded = Base64.strict_encode64(identity_string) - request.headers['Authorization'] = "Basic #{encoded}" + context.http_request.headers['Authorization'] = "Basic #{encoded}" end - def reset(request:, **_options) - request.headers.delete('Authorization') + def reset(context) + context.http_request.headers.delete('Authorization') end end end diff --git a/gems/smithy-client/lib/smithy-client/signers/http_bearer.rb b/gems/smithy-client/lib/smithy-client/signers/http_bearer.rb index a9239d158..65ea57e67 100644 --- a/gems/smithy-client/lib/smithy-client/signers/http_bearer.rb +++ b/gems/smithy-client/lib/smithy-client/signers/http_bearer.rb @@ -5,13 +5,14 @@ module Client module Signers # A signer that signs requests using the HTTP Bearer Auth scheme. class HttpBearer < Signer - def sign(request:, identity:, **_options) + def sign(context) + reset(context) # TODO: does not handle realm or other properties - request.headers['Authorization'] = "Bearer #{identity.token}" + context.http_request.headers['Authorization'] = "Bearer #{context.auth[:identity].token}" end - def reset(request:, **_options) - request.headers.delete('Authorization') + def reset(context) + context.http_request.headers.delete('Authorization') end end end diff --git a/gems/smithy-client/lib/smithy-client/signers/http_digest.rb b/gems/smithy-client/lib/smithy-client/signers/http_digest.rb index 64fb145e4..13b52018d 100644 --- a/gems/smithy-client/lib/smithy-client/signers/http_digest.rb +++ b/gems/smithy-client/lib/smithy-client/signers/http_digest.rb @@ -5,14 +5,14 @@ module Client module Signers # A signer that signs requests using the HTTP Digest Auth scheme. class HttpDigest < Signer - def sign(request:, identity:, **_options) + def sign(context) # 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:, **_options) + def reset(context) raise NotImplementedError end end diff --git a/gems/smithy-client/sig/smithy-client/auth_option.rbs b/gems/smithy-client/sig/smithy-client/auth_option.rbs deleted file mode 100644 index 256b58d54..000000000 --- a/gems/smithy-client/sig/smithy-client/auth_option.rbs +++ /dev/null @@ -1,10 +0,0 @@ -module Smithy - module Client - class AuthOption - def initialize: (scheme_id: String, ?identity_properties: Hash[String, untyped], ?signer_properties: Hash[String, untyped]) -> void - attr_reader scheme_id: String - attr_reader identity_properties: Hash[String, untyped] - attr_reader signer_properties: Hash[String, untyped] - end - end -end diff --git a/gems/smithy-client/sig/smithy-client/handler_context.rbs b/gems/smithy-client/sig/smithy-client/handler_context.rbs index 3ab425591..34eff9394 100644 --- a/gems/smithy-client/sig/smithy-client/handler_context.rbs +++ b/gems/smithy-client/sig/smithy-client/handler_context.rbs @@ -9,6 +9,7 @@ module Smithy attr_accessor config: Struct[untyped]? attr_accessor http_request: HTTP::Request | untyped attr_accessor http_response: HTTP::Response | untyped + attr_accessor auth: Hash[Symbol, untyped] attr_accessor retries: Integer attr_reader metadata: Hash[untyped, untyped] def []: (untyped) -> untyped diff --git a/gems/smithy-client/sig/smithy-client/interfaces.rbs b/gems/smithy-client/sig/smithy-client/interfaces.rbs index 9167ee7f3..ed0a2a5d8 100644 --- a/gems/smithy-client/sig/smithy-client/interfaces.rbs +++ b/gems/smithy-client/sig/smithy-client/interfaces.rbs @@ -3,7 +3,7 @@ module Smithy type endpoint_url = String | URI::HTTP | URI::HTTPS interface _IdentityProvider - def identity: (Hash[untyped, untyped] properties) -> Identity + def identity: () -> Identity end interface _ReadableIO diff --git a/gems/smithy-client/spec/smithy-client/auth_option_spec.rb b/gems/smithy-client/spec/smithy-client/auth_option_spec.rb deleted file mode 100644 index 3975a6e7e..000000000 --- a/gems/smithy-client/spec/smithy-client/auth_option_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require_relative '../spec_helper' - -module Smithy - module Client - describe AuthOption do - let(:scheme_id) { 'scheme_id' } - let(:identity_properties) { { 'identity' => 'property' } } - let(:signer_properties) { { 'signer' => 'property' } } - - subject do - AuthOption.new( - scheme_id: scheme_id, - identity_properties: identity_properties, - signer_properties: signer_properties - ) - end - - describe '#scheme_id' do - it 'returns the scheme_id' do - expect(subject.scheme_id).to eq(scheme_id) - end - end - - describe '#identity_properties' do - it 'returns the identity_properties' do - expect(subject.identity_properties).to eq(identity_properties) - end - end - - describe '#signer_properties' do - it 'returns the signer_properties' do - expect(subject.signer_properties).to eq(signer_properties) - end - end - end - end -end diff --git a/gems/smithy-client/spec/smithy-client/auth_scheme_spec.rb b/gems/smithy-client/spec/smithy-client/auth_scheme_spec.rb index 1a2f40417..e0ca26661 100644 --- a/gems/smithy-client/spec/smithy-client/auth_scheme_spec.rb +++ b/gems/smithy-client/spec/smithy-client/auth_scheme_spec.rb @@ -10,7 +10,7 @@ module Client let(:identity_type) { Identity } let(:identity_provider_class) do Class.new do - def identity(_properties) + def identity Identity.new end end diff --git a/gems/smithy-client/spec/smithy-client/plugins/checksum_required_spec.rb b/gems/smithy-client/spec/smithy-client/plugins/checksum_required_spec.rb index 93c0633bf..ac27c83b3 100644 --- a/gems/smithy-client/spec/smithy-client/plugins/checksum_required_spec.rb +++ b/gems/smithy-client/spec/smithy-client/plugins/checksum_required_spec.rb @@ -44,9 +44,9 @@ module Plugins it 'calculates checksums before signing' do signer = Signers::Anonymous.new - expect(signer).to receive(:sign).and_wrap_original do |method, **args| - expect(args[:request].headers['Content-Md5']).to_not be_nil - method.call(**args) + expect(signer).to receive(:sign).and_wrap_original do |method, context| + expect(context.http_request.headers['Content-Md5']).to_not be_nil + method.call(context) end auth_scheme = AuthSchemes::Anonymous.new(signer: signer) client = client_class.new(stub_responses: true, anonymous_auth_scheme: auth_scheme) diff --git a/gems/smithy-client/spec/smithy-client/plugins/http_api_key_auth_spec.rb b/gems/smithy-client/spec/smithy-client/plugins/http_api_key_auth_spec.rb index ede6822d2..4380b391c 100644 --- a/gems/smithy-client/spec/smithy-client/plugins/http_api_key_auth_spec.rb +++ b/gems/smithy-client/spec/smithy-client/plugins/http_api_key_auth_spec.rb @@ -51,14 +51,14 @@ module Plugins it 'has a default :http_api_key_provider when :stub_responses is true' do provider = client.config.http_api_key_provider expect(provider).to be_a(HttpApiKeyProvider) - expect(provider.identity({}).key).to eq('stubbed-api-key') + expect(provider.identity.key).to eq('stubbed-api-key') end it 'defaults a :http_api_key_provider when :http_api_key is set' do client = client_class.new(http_api_key: 'api-key') provider = client.config.http_api_key_provider expect(provider).to be_a(HttpApiKeyProvider) - expect(provider.identity({}).key).to eq('api-key') + expect(provider.identity.key).to eq('api-key') end context 'signing' do diff --git a/gems/smithy-client/spec/smithy-client/plugins/http_basic_auth_spec.rb b/gems/smithy-client/spec/smithy-client/plugins/http_basic_auth_spec.rb index 169c9d666..a502160e8 100644 --- a/gems/smithy-client/spec/smithy-client/plugins/http_basic_auth_spec.rb +++ b/gems/smithy-client/spec/smithy-client/plugins/http_basic_auth_spec.rb @@ -57,7 +57,7 @@ module Plugins it 'has a default :http_login_provider when :stub_responses is true' do provider = client.config.http_login_provider expect(provider).to be_a(HttpLoginProvider) - identity = provider.identity({}) + identity = provider.identity expect(identity.username).to eq('stubbed-username') expect(identity.password).to eq('stubbed-password') end @@ -66,7 +66,7 @@ module Plugins client = client_class.new(http_login_username: 'username', http_login_password: 'password') provider = client.config.http_login_provider expect(provider).to be_a(HttpLoginProvider) - identity = provider.identity({}) + identity = provider.identity expect(identity.username).to eq('username') expect(identity.password).to eq('password') end diff --git a/gems/smithy-client/spec/smithy-client/plugins/http_bearer_auth_spec.rb b/gems/smithy-client/spec/smithy-client/plugins/http_bearer_auth_spec.rb index 7db21623f..aa5af8816 100644 --- a/gems/smithy-client/spec/smithy-client/plugins/http_bearer_auth_spec.rb +++ b/gems/smithy-client/spec/smithy-client/plugins/http_bearer_auth_spec.rb @@ -51,14 +51,14 @@ module Plugins it 'has a default :http_bearer_provider when :stub_responses is true' do provider = client.config.http_bearer_provider expect(provider).to be_a(HttpBearerProvider) - expect(provider.identity({}).token).to eq('stubbed-bearer-token') + expect(provider.identity.token).to eq('stubbed-bearer-token') end it 'defaults a :http_bearer_provider when :http_bearer_token is set' do client = client_class.new(http_bearer_token: 'bearer') provider = client.config.http_bearer_provider expect(provider).to be_a(HttpBearerProvider) - expect(provider.identity({}).token).to eq('bearer') + expect(provider.identity.token).to eq('bearer') end context 'signing' do diff --git a/gems/smithy-client/spec/smithy-client/plugins/http_digest_auth_spec.rb b/gems/smithy-client/spec/smithy-client/plugins/http_digest_auth_spec.rb index 258d23819..528f41092 100644 --- a/gems/smithy-client/spec/smithy-client/plugins/http_digest_auth_spec.rb +++ b/gems/smithy-client/spec/smithy-client/plugins/http_digest_auth_spec.rb @@ -57,7 +57,7 @@ module Plugins it 'has a default :http_login_provider when :stub_responses is true' do provider = client.config.http_login_provider expect(provider).to be_a(HttpLoginProvider) - identity = provider.identity({}) + identity = provider.identity expect(identity.username).to eq('stubbed-username') expect(identity.password).to eq('stubbed-password') end @@ -66,7 +66,7 @@ module Plugins client = client_class.new(http_login_username: 'username', http_login_password: 'password') provider = client.config.http_login_provider expect(provider).to be_a(HttpLoginProvider) - identity = provider.identity({}) + identity = provider.identity expect(identity.username).to eq('username') expect(identity.password).to eq('password') end diff --git a/gems/smithy-client/spec/smithy-client/refreshing_identity_provider_spec.rb b/gems/smithy-client/spec/smithy-client/refreshing_identity_provider_spec.rb index b1085fdc1..eb16a8925 100644 --- a/gems/smithy-client/spec/smithy-client/refreshing_identity_provider_spec.rb +++ b/gems/smithy-client/spec/smithy-client/refreshing_identity_provider_spec.rb @@ -15,8 +15,8 @@ def initialize(proc) super() end - def refresh(properties = {}) - @identity = @proc.call(properties) + def refresh + @identity = @proc.call end end end @@ -27,17 +27,17 @@ def refresh(properties = {}) end let(:properties) { { foo: 'bar' } } - let(:proc) { ->(_properties) {} } + let(:proc) { -> {} } subject { identity_resolver.new(proc) } describe '#identity' do it 'initializes the identity' do - expect(proc).to receive(:call).with(properties).and_return(refreshed_expiration_identity) - expect(subject).to receive(:refresh).with(properties).and_call_original + expect(proc).to receive(:call).and_return(refreshed_expiration_identity) + expect(subject).to receive(:refresh).and_call_original expect(subject.instance_variable_get(:@identity)).to be_nil - identity = subject.identity(properties) + identity = subject.identity expect(identity).to eq(refreshed_expiration_identity) expect(subject.instance_variable_get(:@identity)).to eq(identity) end @@ -51,13 +51,13 @@ def refresh(properties = {}) it 'refreshes synchronously' do expect(Thread).not_to receive(:new) expect(proc).to receive(:call) - .with(properties).and_return(near_sync_expiration_identity) + .and_return(near_sync_expiration_identity) expect(proc).to receive(:call) - .with(properties).and_return(refreshed_expiration_identity) + .and_return(refreshed_expiration_identity) - identity = subject.identity(properties) # initialize + identity = subject.identity # initialize expect(identity).to eq(near_sync_expiration_identity) - identity = subject.identity(properties) # refreshing + identity = subject.identity # refreshing expect(identity).to eq(refreshed_expiration_identity) end end @@ -71,12 +71,12 @@ def refresh(properties = {}) it 'refreshes asynchronously' do expect(Thread).to receive(:new).and_yield expect(proc).to receive(:call) - .with(properties).and_return(near_async_expiration_identity) + .and_return(near_async_expiration_identity) expect(proc).to receive(:call) - .with(properties).and_return(refreshed_expiration_identity) - identity = subject.identity(properties) # initialize + .and_return(refreshed_expiration_identity) + identity = subject.identity # initialize expect(identity).to eq(near_async_expiration_identity) - identity = subject.identity(properties) # refreshing + identity = subject.identity # refreshing expect(identity).to eq(refreshed_expiration_identity) end end diff --git a/gems/smithy/lib/smithy/templates/client/auth_plugin.erb b/gems/smithy/lib/smithy/templates/client/auth_plugin.erb index b8ef3b1f2..ff1354193 100644 --- a/gems/smithy/lib/smithy/templates/client/auth_plugin.erb +++ b/gems/smithy/lib/smithy/templates/client/auth_plugin.erb @@ -2,6 +2,8 @@ # This is generated code! +require 'smithy-client/auth_schemes/auth' + module <%= module_name %> module Plugins # @api private @@ -30,86 +32,7 @@ module <%= module_name %> } end - # @api private - class Handler < Smithy::Client::Handler - def call(context) - # TODO: apply endpoint auth properties if present - auth_params = AuthParameters.create(context) - auth_options = context.config.auth_resolver.resolve(auth_params) - context[:auth] = resolve_auth(context, auth_options) - @handler.call(context) - end - - private - - def resolve_auth(context, auth_options) - failures = [] - - raise 'No auth options were resolved' if auth_options.empty? - - identity_providers = { -<% identity_providers.each do |k, v| -%> - <%= k %> => context.config.<%= v %>, -<% end -%> - } - - auth_options.each do |auth_option| - auth_scheme = context.config.auth_schemes[auth_option.scheme_id] - resolved_auth = try_load_auth_scheme( - auth_option, - auth_scheme, - identity_providers, - failures - ) - - return resolved_auth if resolved_auth - end - - raise failures.join("\n") - end - - def try_load_auth_scheme(auth_option, auth_scheme, identity_providers, failures) - scheme_id = auth_option.scheme_id - unless auth_scheme - failures << "Auth scheme #{scheme_id} was not enabled " \ - 'for this request' - return - end - - identity_provider = auth_scheme.identity_provider(identity_providers) - unless identity_provider - failures << "Auth scheme #{scheme_id} did not have an " \ - 'identity resolver configured' - return - end - - identity_properties = auth_option.identity_properties - identity = identity_provider.identity(identity_properties) - - ResolvedAuth.new( - scheme_id: scheme_id, - identity: identity, - identity_properties: auth_option.identity_properties, - signer: auth_scheme.signer, - signer_properties: auth_option.signer_properties - ) - end - - # @api private - class ResolvedAuth - def initialize(options = {}) - @scheme_id = options[:scheme_id] - @signer = options[:signer] - @signer_properties = options[:signer_properties] - @identity = options[:identity] - @identity_properties = options[:identity_properties] - end - - attr_accessor :scheme_id, :signer, :signer_properties, :identity, :identity_properties - end - end - - handler(Handler, step: :sign, priority: 70) + handler(Smithy::Client::AuthSchemes::Handler, step: :sign, priority: 70) end end end diff --git a/gems/smithy/lib/smithy/templates/client/auth_resolver.erb b/gems/smithy/lib/smithy/templates/client/auth_resolver.erb index 2288eabe1..b97e33c06 100644 --- a/gems/smithy/lib/smithy/templates/client/auth_resolver.erb +++ b/gems/smithy/lib/smithy/templates/client/auth_resolver.erb @@ -5,9 +5,10 @@ module <%= module_name %> # Resolves the auth scheme from {AuthParameters}. class AuthResolver - # @param [AuthParameters] parameters - # @return [Smithy::Client::AuthOption] - def resolve(parameters) + # @param [HandlerContext] context + # @return [String] + def resolve(context) + parameters = AuthParameters.create(context) <% auth_rules_code.each do |line| -%> <%= line %> <% end -%> diff --git a/gems/smithy/lib/smithy/templates/client/auth_resolver_rbs.erb b/gems/smithy/lib/smithy/templates/client/auth_resolver_rbs.erb index b615ea66f..5ed256110 100644 --- a/gems/smithy/lib/smithy/templates/client/auth_resolver_rbs.erb +++ b/gems/smithy/lib/smithy/templates/client/auth_resolver_rbs.erb @@ -1,5 +1,5 @@ module <%= module_name %> class AuthResolver - def resolve: (AuthParameters) -> Array[Smithy::Client::AuthOption] + def resolve: (Smithy::Client::HandlerContext) -> Array[String] end end diff --git a/gems/smithy/lib/smithy/templates/client/client.erb b/gems/smithy/lib/smithy/templates/client/client.erb index 3926d10e4..bb451640c 100644 --- a/gems/smithy/lib/smithy/templates/client/client.erb +++ b/gems/smithy/lib/smithy/templates/client/client.erb @@ -133,6 +133,16 @@ module <%= module_name %> <%= protocols %> end + + # @api private + def identity_providers(context) + { +<% identity_providers.each do |k, v| -%> + <%= k %> => context.config.<%= v %>, +<% end -%> + } + end + # @api private def errors_module Errors diff --git a/gems/smithy/lib/smithy/views/client/auth_plugin.rb b/gems/smithy/lib/smithy/views/client/auth_plugin.rb index 0e74d47d8..28d9c3a74 100644 --- a/gems/smithy/lib/smithy/views/client/auth_plugin.rb +++ b/gems/smithy/lib/smithy/views/client/auth_plugin.rb @@ -19,10 +19,6 @@ def auth_schemes @auth_schemes.transform_values { |v| v[:auth_scheme_config_option] } end - def identity_providers - @auth_schemes.to_h { |_, v| [v[:identity_type], v[:identity_provider_config_option]] } - end - private def weld_auth_schemes(welds) diff --git a/gems/smithy/lib/smithy/views/client/auth_resolver.rb b/gems/smithy/lib/smithy/views/client/auth_resolver.rb index 9b356d782..1677853f7 100644 --- a/gems/smithy/lib/smithy/views/client/auth_resolver.rb +++ b/gems/smithy/lib/smithy/views/client/auth_resolver.rb @@ -26,7 +26,7 @@ def auth_rules_code auth_operations = operations_with_auth_traits if auth_operations.empty? service_auth_schemes.each do |auth_scheme| - lines << "options << #{render_auth_option(auth_scheme)}" + lines << "options << '#{auth_scheme}'" end else lines << 'case parameters.operation_name' @@ -34,12 +34,12 @@ def auth_rules_code operation_name = Model::Shape.name(id).underscore lines << "when :#{operation_name}" operation_auth_schemes(operation).each do |auth_scheme| - lines << " options << #{render_auth_option(auth_scheme)}" + lines << " options << '#{auth_scheme}'" end end lines << 'else' service_auth_schemes.each do |auth_scheme| - lines << " options << #{render_auth_option(auth_scheme)}" + lines << " options << '#{auth_scheme}'" end lines << 'end' end @@ -114,11 +114,6 @@ def add_registered_auth_schemes(auth_schemes, traits) auth_schemes << auth_scheme if traits.key?(auth_scheme) end end - - def render_auth_option(auth_scheme) - properties = @service_traits.fetch(auth_scheme, {}) - "Smithy::Client::AuthOption.new(scheme_id: '#{auth_scheme}', signer_properties: #{properties})" - end end end end diff --git a/gems/smithy/lib/smithy/views/client/client.rb b/gems/smithy/lib/smithy/views/client/client.rb index b41965b76..73a277b51 100644 --- a/gems/smithy/lib/smithy/views/client/client.rb +++ b/gems/smithy/lib/smithy/views/client/client.rb @@ -67,6 +67,11 @@ def protocols @protocols ||= @plan.welds.map(&:protocols).reduce({}, :merge) end + def identity_providers + auth_schemes = weld_auth_schemes(@plan.welds) + auth_schemes.to_h { |_, v| [v[:identity_type], v[:identity_provider_config_option]] } + end + def waiters waiters = Views::Client::Waiters.new(@plan).waiters return ['{}'] if waiters.empty? @@ -107,6 +112,11 @@ def option_default(option) default.gsub('', protocols.keys.first || 'nil') end + def weld_auth_schemes(welds) + weld_auth_schemes = welds.map(&:add_auth_schemes).reduce({}, :merge) + weld_auth_schemes.except(*welds.map(&:remove_auth_schemes).reduce([], :+)) + end + # @api private class Operation def initialize(service, model, id, operation) diff --git a/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb b/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb index a89608e8f..bb8bc8ded 100644 --- a/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb +++ b/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb @@ -13,16 +13,16 @@ describe '#resolve' do it 'returns the auth options alphabetically by default' do - params = NoAuthTrait::AuthParameters.new(operation_name: :operation_a) - auth_options = subject.resolve(params) + context = Smithy::Client::HandlerContext.new(operation_name: :operation_a) + auth_options = subject.resolve(context) expected = %w[smithy.api#httpBasicAuth smithy.api#httpBearerAuth smithy.api#httpDigestAuth] - expect(auth_options.map(&:scheme_id)).to eq(expected) + expect(auth_options).to eq(expected) end it 'returns the auth options for the operation with the auth trait' do - params = NoAuthTrait::AuthParameters.new(operation_name: :operation_b) - auth_options = subject.resolve(params) - expect(auth_options.map(&:scheme_id)).to eq(['smithy.api#httpDigestAuth']) + context = Smithy::Client::HandlerContext.new(operation_name: :operation_b) + auth_options = subject.resolve(context) + expect(auth_options).to eq(['smithy.api#httpDigestAuth']) end end end @@ -34,22 +34,22 @@ describe '#resolve' do it 'returns the auth options with the service auth trait' do - params = AuthTrait::AuthParameters.new(operation_name: :operation_c) - auth_options = subject.resolve(params) + context = Smithy::Client::HandlerContext.new(operation_name: :operation_c) + auth_options = subject.resolve(context) expected = %w[smithy.api#httpBasicAuth smithy.api#httpDigestAuth] - expect(auth_options.map(&:scheme_id)).to eq(expected) + expect(auth_options).to eq(expected) end it 'returns the auth options for the operation overriding the service auth trait' do - params = AuthTrait::AuthParameters.new(operation_name: :operation_d) - auth_options = subject.resolve(params) - expect(auth_options.map(&:scheme_id)).to eq(['smithy.api#httpBearerAuth']) + context = Smithy::Client::HandlerContext.new(operation_name: :operation_d) + auth_options = subject.resolve(context) + expect(auth_options).to eq(['smithy.api#httpBearerAuth']) end it 'returns a noAuth option when the auth trait is empty' do - params = AuthTrait::AuthParameters.new(operation_name: :operation_e) - auth_options = subject.resolve(params) - expect(auth_options.map(&:scheme_id)).to eq(['smithy.api#noAuth']) + context = Smithy::Client::HandlerContext.new(operation_name: :operation_e) + auth_options = subject.resolve(context) + expect(auth_options).to eq(['smithy.api#noAuth']) end end end diff --git a/projections/shapes/lib/shapes/auth_resolver.rb b/projections/shapes/lib/shapes/auth_resolver.rb index 073fd3a86..4e84e0f01 100644 --- a/projections/shapes/lib/shapes/auth_resolver.rb +++ b/projections/shapes/lib/shapes/auth_resolver.rb @@ -5,11 +5,12 @@ module ShapeService # Resolves the auth scheme from {AuthParameters}. class AuthResolver - # @param [AuthParameters] parameters - # @return [Smithy::Client::AuthOption] - def resolve(parameters) + # @param [HandlerContext] context + # @return [String] + def resolve(context) + parameters = AuthParameters.create(context) options = [] - options << Smithy::Client::AuthOption.new(scheme_id: 'smithy.api#noAuth', signer_properties: {}) + options << 'smithy.api#noAuth' options end end diff --git a/projections/shapes/lib/shapes/client.rb b/projections/shapes/lib/shapes/client.rb index 4d521a4a4..0c320ab24 100644 --- a/projections/shapes/lib/shapes/client.rb +++ b/projections/shapes/lib/shapes/client.rb @@ -361,6 +361,14 @@ def protocols {} end + + # @api private + def identity_providers(context) + { + Smithy::Client::Identities::Anonymous => context.config.anonymous_provider, + } + end + # @api private def errors_module Errors diff --git a/projections/shapes/lib/shapes/plugins/auth.rb b/projections/shapes/lib/shapes/plugins/auth.rb index b0b0038b3..72f262019 100644 --- a/projections/shapes/lib/shapes/plugins/auth.rb +++ b/projections/shapes/lib/shapes/plugins/auth.rb @@ -2,6 +2,8 @@ # This is generated code! +require 'smithy-client/auth_schemes/auth' + module ShapeService module Plugins # @api private @@ -28,84 +30,7 @@ class Auth < Smithy::Client::Plugin } end - # @api private - class Handler < Smithy::Client::Handler - def call(context) - # TODO: apply endpoint auth properties if present - auth_params = AuthParameters.create(context) - auth_options = context.config.auth_resolver.resolve(auth_params) - context[:auth] = resolve_auth(context, auth_options) - @handler.call(context) - end - - private - - def resolve_auth(context, auth_options) - failures = [] - - raise 'No auth options were resolved' if auth_options.empty? - - identity_providers = { - Smithy::Client::Identities::Anonymous => context.config.anonymous_provider, - } - - auth_options.each do |auth_option| - auth_scheme = context.config.auth_schemes[auth_option.scheme_id] - resolved_auth = try_load_auth_scheme( - auth_option, - auth_scheme, - identity_providers, - failures - ) - - return resolved_auth if resolved_auth - end - - raise failures.join("\n") - end - - def try_load_auth_scheme(auth_option, auth_scheme, identity_providers, failures) - scheme_id = auth_option.scheme_id - unless auth_scheme - failures << "Auth scheme #{scheme_id} was not enabled " \ - 'for this request' - return - end - - identity_provider = auth_scheme.identity_provider(identity_providers) - unless identity_provider - failures << "Auth scheme #{scheme_id} did not have an " \ - 'identity resolver configured' - return - end - - identity_properties = auth_option.identity_properties - identity = identity_provider.identity(identity_properties) - - ResolvedAuth.new( - scheme_id: scheme_id, - identity: identity, - identity_properties: auth_option.identity_properties, - signer: auth_scheme.signer, - signer_properties: auth_option.signer_properties - ) - end - - # @api private - class ResolvedAuth - def initialize(options = {}) - @scheme_id = options[:scheme_id] - @signer = options[:signer] - @signer_properties = options[:signer_properties] - @identity = options[:identity] - @identity_properties = options[:identity_properties] - end - - attr_accessor :scheme_id, :signer, :signer_properties, :identity, :identity_properties - end - end - - handler(Handler, step: :sign, priority: 70) + handler(Smithy::Client::AuthSchemes::Handler, step: :sign, priority: 70) end end end diff --git a/projections/shapes/sig/shapes/auth_resolver.rbs b/projections/shapes/sig/shapes/auth_resolver.rbs index e050dd722..18d7c38bb 100644 --- a/projections/shapes/sig/shapes/auth_resolver.rbs +++ b/projections/shapes/sig/shapes/auth_resolver.rbs @@ -1,5 +1,5 @@ module ShapeService class AuthResolver - def resolve: (AuthParameters) -> Array[Smithy::Client::AuthOption] + def resolve: (Smithy::Client::HandlerContext) -> Array[String] end end diff --git a/projections/weather/lib/weather/auth_resolver.rb b/projections/weather/lib/weather/auth_resolver.rb index 3f9e14769..7e158383e 100644 --- a/projections/weather/lib/weather/auth_resolver.rb +++ b/projections/weather/lib/weather/auth_resolver.rb @@ -5,11 +5,12 @@ module Weather # Resolves the auth scheme from {AuthParameters}. class AuthResolver - # @param [AuthParameters] parameters - # @return [Smithy::Client::AuthOption] - def resolve(parameters) + # @param [HandlerContext] context + # @return [String] + def resolve(context) + parameters = AuthParameters.create(context) options = [] - options << Smithy::Client::AuthOption.new(scheme_id: 'smithy.api#noAuth', signer_properties: {}) + options << 'smithy.api#noAuth' options end end diff --git a/projections/weather/lib/weather/client.rb b/projections/weather/lib/weather/client.rb index 9c0d5e19e..392a5faff 100644 --- a/projections/weather/lib/weather/client.rb +++ b/projections/weather/lib/weather/client.rb @@ -351,6 +351,14 @@ def protocols {} end + + # @api private + def identity_providers(context) + { + Smithy::Client::Identities::Anonymous => context.config.anonymous_provider, + } + end + # @api private def errors_module Errors diff --git a/projections/weather/lib/weather/plugins/auth.rb b/projections/weather/lib/weather/plugins/auth.rb index f3b45dc1b..1d73285d8 100644 --- a/projections/weather/lib/weather/plugins/auth.rb +++ b/projections/weather/lib/weather/plugins/auth.rb @@ -2,6 +2,8 @@ # This is generated code! +require 'smithy-client/auth_schemes/auth' + module Weather module Plugins # @api private @@ -28,84 +30,7 @@ class Auth < Smithy::Client::Plugin } end - # @api private - class Handler < Smithy::Client::Handler - def call(context) - # TODO: apply endpoint auth properties if present - auth_params = AuthParameters.create(context) - auth_options = context.config.auth_resolver.resolve(auth_params) - context[:auth] = resolve_auth(context, auth_options) - @handler.call(context) - end - - private - - def resolve_auth(context, auth_options) - failures = [] - - raise 'No auth options were resolved' if auth_options.empty? - - identity_providers = { - Smithy::Client::Identities::Anonymous => context.config.anonymous_provider, - } - - auth_options.each do |auth_option| - auth_scheme = context.config.auth_schemes[auth_option.scheme_id] - resolved_auth = try_load_auth_scheme( - auth_option, - auth_scheme, - identity_providers, - failures - ) - - return resolved_auth if resolved_auth - end - - raise failures.join("\n") - end - - def try_load_auth_scheme(auth_option, auth_scheme, identity_providers, failures) - scheme_id = auth_option.scheme_id - unless auth_scheme - failures << "Auth scheme #{scheme_id} was not enabled " \ - 'for this request' - return - end - - identity_provider = auth_scheme.identity_provider(identity_providers) - unless identity_provider - failures << "Auth scheme #{scheme_id} did not have an " \ - 'identity resolver configured' - return - end - - identity_properties = auth_option.identity_properties - identity = identity_provider.identity(identity_properties) - - ResolvedAuth.new( - scheme_id: scheme_id, - identity: identity, - identity_properties: auth_option.identity_properties, - signer: auth_scheme.signer, - signer_properties: auth_option.signer_properties - ) - end - - # @api private - class ResolvedAuth - def initialize(options = {}) - @scheme_id = options[:scheme_id] - @signer = options[:signer] - @signer_properties = options[:signer_properties] - @identity = options[:identity] - @identity_properties = options[:identity_properties] - end - - attr_accessor :scheme_id, :signer, :signer_properties, :identity, :identity_properties - end - end - - handler(Handler, step: :sign, priority: 70) + handler(Smithy::Client::AuthSchemes::Handler, step: :sign, priority: 70) end end end diff --git a/projections/weather/sig/weather/auth_resolver.rbs b/projections/weather/sig/weather/auth_resolver.rbs index 1a1009086..349008743 100644 --- a/projections/weather/sig/weather/auth_resolver.rbs +++ b/projections/weather/sig/weather/auth_resolver.rbs @@ -1,5 +1,5 @@ module Weather class AuthResolver - def resolve: (AuthParameters) -> Array[Smithy::Client::AuthOption] + def resolve: (Smithy::Client::HandlerContext) -> Array[String] end end