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 e3940e4a3..edaab218b 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 @@ -8,7 +8,7 @@ class HttpApiKeyProvider # @param [String] key def initialize(key) - @identity = Identities::HttpApiKey.new(key: key) + @identity = Identities::ApiKey.new(key: key) 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 4a7d298e8..0a79cd621 100644 --- a/gems/smithy-client/lib/smithy-client/http_bearer_provider.rb +++ b/gems/smithy-client/lib/smithy-client/http_bearer_provider.rb @@ -2,13 +2,13 @@ module Smithy module Client - # Returns an HTTP Bearer identity + # Returns a Token identity class HttpBearerProvider include IdentityProvider # @param [String] token def initialize(token) - @identity = Identities::HttpBearer.new(token: token) + @identity = Identities::Token.new(token: token) end end end diff --git a/gems/smithy-client/lib/smithy-client/identities/api_key.rb b/gems/smithy-client/lib/smithy-client/identities/api_key.rb new file mode 100644 index 000000000..4aa6d4bdc --- /dev/null +++ b/gems/smithy-client/lib/smithy-client/identities/api_key.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Smithy + module Client + module Identities + # Identity class for API Key authentication. + class ApiKey < Identity + def initialize(key:, **) + @key = key + super(**) + end + + # @return [String, nil] + attr_reader :key + + # Removing the key from the default inspect string. + # @api private + def inspect + super.gsub(/@key="(\\"|[^"])*"/, '@key=[FILTERED]') + end + end + end + end +end diff --git a/gems/smithy-client/lib/smithy-client/identities/http_api_key.rb b/gems/smithy-client/lib/smithy-client/identities/http_api_key.rb deleted file mode 100644 index 4b31385fb..000000000 --- a/gems/smithy-client/lib/smithy-client/identities/http_api_key.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Smithy - module Client - module Identities - # Identity class for HTTP API Key authentication. - class HttpApiKey < Identity - def initialize(key:, **) - @key = key - super(**) - end - - # @return [String, nil] - attr_reader :key - end - end - end -end diff --git a/gems/smithy-client/lib/smithy-client/identities/http_bearer.rb b/gems/smithy-client/lib/smithy-client/identities/token.rb similarity index 50% rename from gems/smithy-client/lib/smithy-client/identities/http_bearer.rb rename to gems/smithy-client/lib/smithy-client/identities/token.rb index 93ac9cfc8..8907ef043 100644 --- a/gems/smithy-client/lib/smithy-client/identities/http_bearer.rb +++ b/gems/smithy-client/lib/smithy-client/identities/token.rb @@ -3,8 +3,8 @@ module Smithy module Client module Identities - # Identity class for HTTP Bearer token authentication. - class HttpBearer < Identity + # Identity class for token authentication. + class Token < Identity def initialize(token:, **) @token = token super(**) @@ -12,6 +12,12 @@ def initialize(token:, **) # @return [String, nil] attr_reader :token + + # Removing the token from the default inspect string. + # @api private + def inspect + super.gsub(/@token="(\\"|[^"])*"/, '@token=[FILTERED]') + end 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 ffecda72e..40563e628 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true require_relative '../http_api_key_provider' -require_relative '../identities/http_api_key' +require_relative '../identities/api_key' module Smithy module Client 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 5d19f54a2..45b352735 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true require_relative '../http_bearer_provider' -require_relative '../identities/http_bearer' +require_relative '../identities/token' module Smithy module Client diff --git a/gems/smithy-client/sig/smithy-client/identities/http_api_key.rbs b/gems/smithy-client/sig/smithy-client/identities/api_key.rbs similarity index 100% rename from gems/smithy-client/sig/smithy-client/identities/http_api_key.rbs rename to gems/smithy-client/sig/smithy-client/identities/api_key.rbs diff --git a/gems/smithy-client/sig/smithy-client/identities/http_bearer.rbs b/gems/smithy-client/sig/smithy-client/identities/token.rbs similarity index 84% rename from gems/smithy-client/sig/smithy-client/identities/http_bearer.rbs rename to gems/smithy-client/sig/smithy-client/identities/token.rbs index 64ff48d08..7443db992 100644 --- a/gems/smithy-client/sig/smithy-client/identities/http_bearer.rbs +++ b/gems/smithy-client/sig/smithy-client/identities/token.rbs @@ -1,7 +1,7 @@ module Smithy module Client module Identities - class HttpBearer < Identity + class Token < Identity def initialize: (token: String, **untyped options) -> void attr_reader token: String end diff --git a/gems/smithy-client/spec/smithy-client/plugins/resolve_auth_spec.rb b/gems/smithy-client/spec/smithy-client/plugins/resolve_auth_spec.rb index c7817c007..3ab0b569b 100644 --- a/gems/smithy-client/spec/smithy-client/plugins/resolve_auth_spec.rb +++ b/gems/smithy-client/spec/smithy-client/plugins/resolve_auth_spec.rb @@ -36,7 +36,7 @@ module Plugins client_class.add_plugin(HttpApiKeyAuth) resp = client.operation expect(resp.context.auth[:scheme_id]).to equal('smithy.api#httpApiKeyAuth') - expect(resp.context.auth[:identity]).to be_a(Identities::HttpApiKey) + expect(resp.context.auth[:identity]).to be_a(Identities::ApiKey) end it 'resolves auth for http basic auth' do @@ -52,7 +52,7 @@ module Plugins client_class.add_plugin(HttpBearerAuth) resp = client.operation expect(resp.context.auth[:scheme_id]).to equal('smithy.api#httpBearerAuth') - expect(resp.context.auth[:identity]).to be_a(Identities::HttpBearer) + expect(resp.context.auth[:identity]).to be_a(Identities::Token) end it 'resolves auth for http digest auth' do diff --git a/gems/smithy/lib/smithy/views/client/auth_resolver.rb b/gems/smithy/lib/smithy/views/client/auth_resolver.rb index 5765d69fa..725b71d32 100644 --- a/gems/smithy/lib/smithy/views/client/auth_resolver.rb +++ b/gems/smithy/lib/smithy/views/client/auth_resolver.rb @@ -18,39 +18,45 @@ def module_name @plan.module_name end - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength def auth_rules_code lines = [] lines << 'options = []' auth_operations = operations_with_auth_traits if auth_operations.empty? - service_auth_schemes.each do |auth_scheme| - lines << "options << '#{auth_scheme}'" - end + add_service_auth_schemes_to_code(lines) else - lines << 'case parameters.operation_name' - auth_operations.each do |id, operation| - operation_name = Model::Shape.name(id).underscore - lines << "when :#{operation_name}" - operation_auth_schemes(operation).each do |auth_scheme| - lines << " options << '#{auth_scheme}'" - end - end - lines << 'else' - service_auth_schemes.each do |auth_scheme| - lines << " options << '#{auth_scheme}'" - end - lines << 'end' + add_operation_case_to_code(lines, auth_operations) end lines << 'options' lines end - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/AbcSize private + def add_service_auth_schemes_to_code(lines) + service_auth_schemes.each do |auth_scheme| + lines << "options << '#{auth_scheme}'" + end + end + + def add_operation_case_to_code(lines, auth_operations) + lines << 'case parameters.operation_name' + auth_operations.each do |id, operation| + operation_name = Model::Shape.name(id).underscore + lines << "when :#{operation_name}" + add_operation_auth_options_to_code(lines, operation) + end + lines << 'else' + add_service_auth_schemes_to_code(lines) + lines << 'end' + end + + def add_operation_auth_options_to_code(lines, operation) + operation_auth_schemes(operation).each do |auth_scheme| + lines << " options << '#{auth_scheme}'" + end + end + def auth_schemes(welds) weld_auth_schemes = welds.map(&:add_auth_schemes).reduce([], :+) weld_auth_schemes -= welds.map(&:remove_auth_schemes).reduce([], :+) @@ -89,18 +95,32 @@ def optional_operation_auth?(operation) def operation_auth_schemes(operation) operation_traits = operation.fetch('traits', {}) + auth_schemes = build_operation_auth_schemes(operation, operation_traits) + auth_schemes << 'smithy.api#noAuth' if auth_schemes.empty? || optional_operation_auth?(operation) + auth_schemes + end + + def build_operation_auth_schemes(operation, operation_traits) auth_schemes = [] if operation_auth?(operation) - operation_auth = operation_traits.fetch('smithy.api#auth', []) - add_auth_schemes_from_auth_trait(auth_schemes, operation_auth) + add_explicit_operation_auth_schemes(auth_schemes, operation_traits) else - add_registered_auth_schemes(auth_schemes, operation_traits) + add_service_auth_schemes_for_operation(auth_schemes) end - auth_schemes << 'smithy.api#optionalAuth' if operation_traits.key?('smithy.api#optionalAuth') - auth_schemes << 'smithy.api#noAuth' if auth_schemes.empty? auth_schemes end + def add_explicit_operation_auth_schemes(auth_schemes, operation_traits) + operation_auth = operation_traits.fetch('smithy.api#auth', []) + add_auth_schemes_from_auth_trait(auth_schemes, operation_auth) + end + + def add_service_auth_schemes_for_operation(auth_schemes) + service_auth_schemes.each do |auth_scheme| + auth_schemes << auth_scheme unless auth_scheme == 'smithy.api#noAuth' + end + end + def add_auth_schemes_from_auth_trait(auth_schemes, auth_trait) auth_trait.each do |auth_scheme| auth_schemes << auth_scheme if @auth_schemes.include?(auth_scheme) diff --git a/gems/smithy/spec/fixtures/auth/auth_trait/model.json b/gems/smithy/spec/fixtures/auth/auth_trait/model.json index 8c0af79fa..71893a018 100644 --- a/gems/smithy/spec/fixtures/auth/auth_trait/model.json +++ b/gems/smithy/spec/fixtures/auth/auth_trait/model.json @@ -36,6 +36,18 @@ "smithy.api#auth": [] } }, + "smithy.ruby.tests#OperationF": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + }, + "traits": { + "smithy.api#optionalAuth": {} + } + }, "smithy.ruby.tests#ServiceWithAuthTrait": { "type": "service", "version": "2020-01-29", @@ -48,6 +60,9 @@ }, { "target": "smithy.ruby.tests#OperationE" + }, + { + "target": "smithy.ruby.tests#OperationF" } ], "traits": { diff --git a/gems/smithy/spec/fixtures/auth/auth_trait/model.smithy b/gems/smithy/spec/fixtures/auth/auth_trait/model.smithy index 7e0898300..7b4282855 100644 --- a/gems/smithy/spec/fixtures/auth/auth_trait/model.smithy +++ b/gems/smithy/spec/fixtures/auth/auth_trait/model.smithy @@ -12,6 +12,7 @@ service ServiceWithAuthTrait { OperationC OperationD OperationE + OperationF ] } @@ -29,4 +30,10 @@ operation OperationD {} // This operation has the @auth trait and is bound to a service with the // @auth trait. This operation does not support any authentication schemes. @auth([]) -operation OperationE {} \ No newline at end of file +operation OperationE {} + +// This operation has the @optionalAuth trait and is bound to a service +// with the @auth trait. The effective set of authentication schemes it +// supports are: httpBasicAuth, httpDigestAuth, and noAuth +@optionalAuth +operation OperationF {} \ No newline at end of file diff --git a/gems/smithy/spec/fixtures/auth/no_auth/model.json b/gems/smithy/spec/fixtures/auth/no_auth/model.json new file mode 100644 index 000000000..27cb305f6 --- /dev/null +++ b/gems/smithy/spec/fixtures/auth/no_auth/model.json @@ -0,0 +1,38 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.ruby.tests#OperationH": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + } + }, + "smithy.ruby.tests#OperationI": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + }, + "traits": { + "smithy.api#optionalAuth": {} + } + }, + "smithy.ruby.tests#ServiceWithNoAuth": { + "type": "service", + "version": "2020-01-29", + "operations": [ + { + "target": "smithy.ruby.tests#OperationH" + }, + { + "target": "smithy.ruby.tests#OperationI" + } + ] + } + } +} diff --git a/gems/smithy/spec/fixtures/auth/no_auth/model.smithy b/gems/smithy/spec/fixtures/auth/no_auth/model.smithy new file mode 100644 index 000000000..0a84d58f4 --- /dev/null +++ b/gems/smithy/spec/fixtures/auth/no_auth/model.smithy @@ -0,0 +1,20 @@ +$version: "2.0" + +namespace smithy.ruby.tests + +service ServiceWithNoAuth { + version: "2020-01-29" + operations: [ + OperationH + OperationI + ] +} + +// This operation does not have the @auth trait and is bound to a service +// without auth. This operation does not support any authentication schemes. +operation OperationH {} + +// This operation has the @optionalAuth trait and is bound to a service +// without auth. This operation does not support any authentication schemes. +@optionalAuth +operation OperationI {} \ No newline at end of file diff --git a/gems/smithy/spec/fixtures/auth/no_auth_trait/model.json b/gems/smithy/spec/fixtures/auth/no_auth_trait/model.json index f74113c77..288b7c742 100644 --- a/gems/smithy/spec/fixtures/auth/no_auth_trait/model.json +++ b/gems/smithy/spec/fixtures/auth/no_auth_trait/model.json @@ -24,6 +24,18 @@ ] } }, + "smithy.ruby.tests#OperationG": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + }, + "traits": { + "smithy.api#optionalAuth": {} + } + }, "smithy.ruby.tests#ServiceWithNoAuthTrait": { "type": "service", "version": "2020-01-29", @@ -33,6 +45,9 @@ }, { "target": "smithy.ruby.tests#OperationB" + }, + { + "target": "smithy.ruby.tests#OperationG" } ], "traits": { diff --git a/gems/smithy/spec/fixtures/auth/no_auth_trait/model.smithy b/gems/smithy/spec/fixtures/auth/no_auth_trait/model.smithy index 7f3290486..3400d275f 100644 --- a/gems/smithy/spec/fixtures/auth/no_auth_trait/model.smithy +++ b/gems/smithy/spec/fixtures/auth/no_auth_trait/model.smithy @@ -10,6 +10,7 @@ service ServiceWithNoAuthTrait { operations: [ OperationA OperationB + OperationG ] } @@ -23,3 +24,9 @@ operation OperationA {} // supports are: httpDigestAuth. @auth([httpDigestAuth]) operation OperationB {} + +// This operation has the @optionalAuth trait and is bound to a service +// without the @auth trait. The effective set of authentication schemes it +// supports are: httpBasicAuth, httpDigestAuth, httpBearerAuth, and noAuth +@optionalAuth +operation OperationG {} \ No newline at end of file diff --git a/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb b/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb index 2ca811ab5..16c442bb3 100644 --- a/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb +++ b/gems/smithy/spec/interfaces/client/auth_resolver_spec.rb @@ -24,6 +24,19 @@ auth_options = subject.resolve(params) expect(auth_options).to eq(['smithy.api#httpDigestAuth']) end + + it 'returns the auth options for the operation with the optionalAuth trait' do + params = NoAuthTrait::AuthParameters.new(operation_name: :operation_g) + auth_options = subject.resolve(params) + expect(auth_options).to eq( + %w[ + smithy.api#httpBasicAuth + smithy.api#httpBearerAuth + smithy.api#httpDigestAuth + smithy.api#noAuth + ] + ) + end end end @@ -51,6 +64,32 @@ auth_options = subject.resolve(params) expect(auth_options).to eq(['smithy.api#noAuth']) end + + it 'returns the auth options for the operation with the optionalAuth trait' do + params = AuthTrait::AuthParameters.new(operation_name: :operation_f) + auth_options = subject.resolve(params) + expect(auth_options).to eq(%w[smithy.api#httpBasicAuth smithy.api#httpDigestAuth smithy.api#noAuth]) + end + end + end + + context context do + include_context context, 'NoAuth', fixture: 'auth/no_auth' + + subject { NoAuth::AuthResolver.new } + + describe '#resolve' do + it 'returns the auth options for the operation with no auth traits' do + params = NoAuth::AuthParameters.new(operation_name: :operation_h) + auth_options = subject.resolve(params) + expect(auth_options).to eq(['smithy.api#noAuth']) + end + + it 'returns the auth options for the operation with the optionalAuth trait' do + params = NoAuth::AuthParameters.new(operation_name: :operation_i) + auth_options = subject.resolve(params) + expect(auth_options).to eq(['smithy.api#noAuth']) + end end end end diff --git a/projections/shapes/lib/shapes/endpoint_provider.rb b/projections/shapes/lib/shapes/endpoint_provider.rb index e9d5257c5..ff367ab9f 100644 --- a/projections/shapes/lib/shapes/endpoint_provider.rb +++ b/projections/shapes/lib/shapes/endpoint_provider.rb @@ -13,6 +13,7 @@ def resolve(parameters) return Smithy::Client::EndpointRules::Endpoint.new(uri: parameters.endpoint) end raise ArgumentError, "Endpoint is not set - you must configure an endpoint." + end end end diff --git a/projections/weather/lib/weather/endpoint_provider.rb b/projections/weather/lib/weather/endpoint_provider.rb index 5dac5fa9d..0417f4fa5 100644 --- a/projections/weather/lib/weather/endpoint_provider.rb +++ b/projections/weather/lib/weather/endpoint_provider.rb @@ -13,6 +13,7 @@ def resolve(parameters) return Smithy::Client::EndpointRules::Endpoint.new(uri: parameters.endpoint) end raise ArgumentError, "Endpoint is not set - you must configure an endpoint." + end end end