aws-sdk-core 3.131.3 → 3.176.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +330 -1
  3. data/VERSION +1 -1
  4. data/lib/aws-defaults/default_configuration.rb +4 -4
  5. data/lib/aws-sdk-core/arn.rb +13 -0
  6. data/lib/aws-sdk-core/binary/encode_handler.rb +12 -1
  7. data/lib/aws-sdk-core/credential_provider.rb +3 -0
  8. data/lib/aws-sdk-core/credential_provider_chain.rb +8 -5
  9. data/lib/aws-sdk-core/ecs_credentials.rb +111 -53
  10. data/lib/aws-sdk-core/endpoints/condition.rb +41 -0
  11. data/lib/aws-sdk-core/endpoints/endpoint.rb +17 -0
  12. data/lib/aws-sdk-core/endpoints/endpoint_rule.rb +75 -0
  13. data/lib/aws-sdk-core/endpoints/error_rule.rb +42 -0
  14. data/lib/aws-sdk-core/endpoints/function.rb +80 -0
  15. data/lib/aws-sdk-core/endpoints/matchers.rb +127 -0
  16. data/lib/aws-sdk-core/endpoints/reference.rb +31 -0
  17. data/lib/aws-sdk-core/endpoints/rule.rb +25 -0
  18. data/lib/aws-sdk-core/endpoints/rule_set.rb +52 -0
  19. data/lib/aws-sdk-core/endpoints/rules_provider.rb +37 -0
  20. data/lib/aws-sdk-core/endpoints/templater.rb +58 -0
  21. data/lib/aws-sdk-core/endpoints/tree_rule.rb +45 -0
  22. data/lib/aws-sdk-core/endpoints/url.rb +60 -0
  23. data/lib/aws-sdk-core/endpoints.rb +78 -0
  24. data/lib/aws-sdk-core/errors.rb +13 -0
  25. data/lib/aws-sdk-core/json/error_handler.rb +20 -1
  26. data/lib/aws-sdk-core/log/formatter.rb +6 -0
  27. data/lib/aws-sdk-core/pageable_response.rb +10 -1
  28. data/lib/aws-sdk-core/plugins/bearer_authorization.rb +67 -0
  29. data/lib/aws-sdk-core/plugins/checksum_algorithm.rb +1 -1
  30. data/lib/aws-sdk-core/plugins/credentials_configuration.rb +24 -0
  31. data/lib/aws-sdk-core/plugins/endpoint_discovery.rb +6 -2
  32. data/lib/aws-sdk-core/plugins/recursion_detection.rb +14 -3
  33. data/lib/aws-sdk-core/plugins/regional_endpoint.rb +5 -0
  34. data/lib/aws-sdk-core/plugins/retries/error_inspector.rb +2 -1
  35. data/lib/aws-sdk-core/plugins/sign.rb +201 -0
  36. data/lib/aws-sdk-core/plugins/signature_v2.rb +1 -0
  37. data/lib/aws-sdk-core/plugins/signature_v4.rb +13 -7
  38. data/lib/aws-sdk-core/plugins/user_agent.rb +117 -14
  39. data/lib/aws-sdk-core/refreshing_credentials.rb +0 -6
  40. data/lib/aws-sdk-core/refreshing_token.rb +71 -0
  41. data/lib/aws-sdk-core/rest/request/headers.rb +2 -6
  42. data/lib/aws-sdk-core/shared_config.rb +78 -6
  43. data/lib/aws-sdk-core/sso_credentials.rb +80 -45
  44. data/lib/aws-sdk-core/sso_token_provider.rb +135 -0
  45. data/lib/aws-sdk-core/static_token_provider.rb +14 -0
  46. data/lib/aws-sdk-core/structure.rb +6 -4
  47. data/lib/aws-sdk-core/token.rb +31 -0
  48. data/lib/aws-sdk-core/token_provider.rb +15 -0
  49. data/lib/aws-sdk-core/token_provider_chain.rb +51 -0
  50. data/lib/aws-sdk-core/waiters/poller.rb +3 -1
  51. data/lib/aws-sdk-core/xml/parser/engines/oga.rb +2 -0
  52. data/lib/aws-sdk-core.rb +14 -0
  53. data/lib/aws-sdk-sso/client.rb +56 -11
  54. data/lib/aws-sdk-sso/endpoint_parameters.rb +66 -0
  55. data/lib/aws-sdk-sso/endpoint_provider.rb +51 -0
  56. data/lib/aws-sdk-sso/endpoints.rb +72 -0
  57. data/lib/aws-sdk-sso/plugins/endpoints.rb +76 -0
  58. data/lib/aws-sdk-sso/types.rb +8 -43
  59. data/lib/aws-sdk-sso.rb +5 -1
  60. data/lib/aws-sdk-ssooidc/client.rb +611 -0
  61. data/lib/aws-sdk-ssooidc/client_api.rb +216 -0
  62. data/lib/aws-sdk-ssooidc/customizations.rb +1 -0
  63. data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +66 -0
  64. data/lib/aws-sdk-ssooidc/endpoint_provider.rb +51 -0
  65. data/lib/aws-sdk-ssooidc/endpoints.rb +58 -0
  66. data/lib/aws-sdk-ssooidc/errors.rb +290 -0
  67. data/lib/aws-sdk-ssooidc/plugins/endpoints.rb +74 -0
  68. data/lib/aws-sdk-ssooidc/resource.rb +26 -0
  69. data/lib/aws-sdk-ssooidc/types.rb +502 -0
  70. data/lib/aws-sdk-ssooidc.rb +59 -0
  71. data/lib/aws-sdk-sts/client.rb +263 -243
  72. data/lib/aws-sdk-sts/endpoint_parameters.rb +78 -0
  73. data/lib/aws-sdk-sts/endpoint_provider.rb +112 -0
  74. data/lib/aws-sdk-sts/endpoints.rb +136 -0
  75. data/lib/aws-sdk-sts/plugins/endpoints.rb +84 -0
  76. data/lib/aws-sdk-sts/presigner.rb +13 -15
  77. data/lib/aws-sdk-sts/types.rb +87 -195
  78. data/lib/aws-sdk-sts.rb +5 -1
  79. data/lib/seahorse/client/async_base.rb +0 -1
  80. data/lib/seahorse/client/configuration.rb +2 -2
  81. data/lib/seahorse/client/h2/connection.rb +12 -11
  82. data/lib/seahorse/client/plugins/request_callback.rb +9 -9
  83. data/lib/seahorse/util.rb +4 -0
  84. metadata +47 -6
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sigv4'
4
+
5
+ module Aws
6
+ module Plugins
7
+ # @api private
8
+ class Sign < Seahorse::Client::Plugin
9
+ # These once had defaults. But now they are used as overrides to
10
+ # new endpoint and auth resolution.
11
+ option(:sigv4_signer)
12
+ option(:sigv4_name)
13
+ option(:sigv4_region)
14
+ option(:unsigned_operations, default: [])
15
+
16
+ supported_auth_types = %w[sigv4 bearer none]
17
+ supported_auth_types += ['sigv4a'] if Aws::Sigv4::Signer.use_crt?
18
+ SUPPORTED_AUTH_TYPES = supported_auth_types.freeze
19
+
20
+ def add_handlers(handlers, cfg)
21
+ operations = cfg.api.operation_names - cfg.unsigned_operations
22
+ handlers.add(Handler, step: :sign, operations: operations)
23
+ end
24
+
25
+ # @api private
26
+ # Return a signer with the `sign(context)` method
27
+ def self.signer_for(auth_scheme, config, region_override = nil)
28
+ case auth_scheme['name']
29
+ when 'sigv4', 'sigv4a'
30
+ SignatureV4.new(auth_scheme, config, region_override)
31
+ when 'bearer'
32
+ Bearer.new
33
+ else
34
+ NullSigner.new
35
+ end
36
+ end
37
+
38
+ class Handler < Seahorse::Client::Handler
39
+ def call(context)
40
+ # Skip signing if using sigv2 signing from s3_signer in S3
41
+ unless v2_signing?(context.config)
42
+ signer = Sign.signer_for(
43
+ context[:auth_scheme],
44
+ context.config,
45
+ context[:sigv4_region]
46
+ )
47
+ signer.sign(context)
48
+ end
49
+ @handler.call(context)
50
+ end
51
+
52
+ private
53
+
54
+ def v2_signing?(config)
55
+ # 's3' is legacy signing, 'v4' is default
56
+ config.respond_to?(:signature_version) &&
57
+ config.signature_version == 's3'
58
+ end
59
+ end
60
+
61
+ # @api private
62
+ class Bearer
63
+ def initialize
64
+ end
65
+
66
+ def sign(context)
67
+ if context.http_request.endpoint.scheme != 'https'
68
+ raise ArgumentError,
69
+ 'Unable to use bearer authorization on non https endpoint.'
70
+ end
71
+
72
+ token_provider = context.config.token_provider
73
+
74
+ raise Errors::MissingBearerTokenError unless token_provider&.set?
75
+
76
+ context.http_request.headers['Authorization'] =
77
+ "Bearer #{token_provider.token.token}"
78
+ end
79
+
80
+ def presign_url(*args)
81
+ raise ArgumentError, 'Bearer auth does not support presigned urls'
82
+ end
83
+
84
+ def sign_event(*args)
85
+ raise ArgumentError, 'Bearer auth does not support event signing'
86
+ end
87
+ end
88
+
89
+ # @api private
90
+ class SignatureV4
91
+ def initialize(auth_scheme, config, region_override = nil)
92
+ scheme_name = auth_scheme['name']
93
+
94
+ unless %w[sigv4 sigv4a].include?(scheme_name)
95
+ raise ArgumentError,
96
+ "Expected sigv4 or sigv4a auth scheme, got #{scheme_name}"
97
+ end
98
+
99
+ region = if scheme_name == 'sigv4a'
100
+ auth_scheme['signingRegionSet'].first
101
+ else
102
+ auth_scheme['signingRegion']
103
+ end
104
+ begin
105
+ @signer = Aws::Sigv4::Signer.new(
106
+ service: config.sigv4_name || auth_scheme['signingName'],
107
+ region: region_override || config.sigv4_region || region,
108
+ credentials_provider: config.credentials,
109
+ signing_algorithm: scheme_name.to_sym,
110
+ uri_escape_path: !!!auth_scheme['disableDoubleEncoding'],
111
+ normalize_path: !!!auth_scheme['disableNormalizePath'],
112
+ unsigned_headers: %w[content-length user-agent x-amzn-trace-id]
113
+ )
114
+ rescue Aws::Sigv4::Errors::MissingCredentialsError
115
+ raise Aws::Errors::MissingCredentialsError
116
+ end
117
+ end
118
+
119
+ def sign(context)
120
+ req = context.http_request
121
+
122
+ apply_authtype(context, req)
123
+ reset_signature(req)
124
+ apply_clock_skew(context, req)
125
+
126
+ # compute the signature
127
+ begin
128
+ signature = @signer.sign_request(
129
+ http_method: req.http_method,
130
+ url: req.endpoint,
131
+ headers: req.headers,
132
+ body: req.body
133
+ )
134
+ rescue Aws::Sigv4::Errors::MissingCredentialsError
135
+ # Necessary for when credentials is explicitly set to nil
136
+ raise Aws::Errors::MissingCredentialsError
137
+ end
138
+ # apply signature headers
139
+ req.headers.update(signature.headers)
140
+
141
+ # add request metadata with signature components for debugging
142
+ context[:canonical_request] = signature.canonical_request
143
+ context[:string_to_sign] = signature.string_to_sign
144
+ end
145
+
146
+ def presign_url(*args)
147
+ @signer.presign_url(*args)
148
+ end
149
+
150
+ def sign_event(*args)
151
+ @signer.sign_event(*args)
152
+ end
153
+
154
+ private
155
+
156
+ def apply_authtype(context, req)
157
+ if context.operation['authtype'].eql?('v4-unsigned-body') &&
158
+ req.endpoint.scheme.eql?('https')
159
+ req.headers['X-Amz-Content-Sha256'] ||= 'UNSIGNED-PAYLOAD'
160
+ end
161
+ end
162
+
163
+ def reset_signature(req)
164
+ # in case this request is being re-signed
165
+ req.headers.delete('Authorization')
166
+ req.headers.delete('X-Amz-Security-Token')
167
+ req.headers.delete('X-Amz-Date')
168
+ req.headers.delete('x-Amz-Region-Set')
169
+ end
170
+
171
+ def apply_clock_skew(context, req)
172
+ if context.config.respond_to?(:clock_skew) &&
173
+ context.config.clock_skew &&
174
+ context.config.correct_clock_skew
175
+
176
+ endpoint = context.http_request.endpoint
177
+ skew = context.config.clock_skew.clock_correction(endpoint)
178
+ if skew.abs.positive?
179
+ req.headers['X-Amz-Date'] =
180
+ (Time.now.utc + skew).strftime('%Y%m%dT%H%M%SZ')
181
+ end
182
+ end
183
+ end
184
+
185
+ end
186
+
187
+ # @api private
188
+ class NullSigner
189
+
190
+ def sign(context)
191
+ end
192
+
193
+ def presign_url(*args)
194
+ end
195
+
196
+ def sign_event(*args)
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -3,6 +3,7 @@
3
3
  module Aws
4
4
  module Plugins
5
5
  # @api private
6
+ # Necessary to keep after Endpoints 2.0
6
7
  class SignatureV2 < Seahorse::Client::Plugin
7
8
 
8
9
  option(:v2_signer) do |cfg|
@@ -5,8 +5,11 @@ require 'aws-sigv4'
5
5
  module Aws
6
6
  module Plugins
7
7
  # @api private
8
+ # Necessary to exist after endpoints 2.0
8
9
  class SignatureV4 < Seahorse::Client::Plugin
9
10
 
11
+ V4_AUTH = %w[v4 v4-unsigned-payload v4-unsigned-body]
12
+
10
13
  option(:sigv4_signer) do |cfg|
11
14
  SignatureV4.build_signer(cfg)
12
15
  end
@@ -32,13 +35,16 @@ module Aws
32
35
  end
33
36
 
34
37
  option(:unsigned_operations) do |cfg|
35
- cfg.api.operation_names.inject([]) do |unsigned, operation_name|
36
- if cfg.api.operation(operation_name)['authtype'] == 'none' ||
37
- cfg.api.operation(operation_name)['authtype'] == 'custom'
38
- # Unsign requests that has custom apigateway authorizer as well
39
- unsigned << operation_name
40
- else
41
- unsigned
38
+ if cfg.api.metadata['signatureVersion'] == 'v4'
39
+ # select operations where authtype is set and is not v4
40
+ cfg.api.operation_names.select do |o|
41
+ cfg.api.operation(o)['authtype'] && !V4_AUTH.include?(cfg.api.operation(o)['authtype'])
42
+ end
43
+ else # service is not v4 auth
44
+ # select all operations where authtype is not v4
45
+ # (includes operations with no explicit authtype)
46
+ cfg.api.operation_names.select do |o|
47
+ !V4_AUTH.include?(cfg.api.operation(o)['authtype'])
42
48
  end
43
49
  end
44
50
  end
@@ -4,7 +4,31 @@ module Aws
4
4
  module Plugins
5
5
  # @api private
6
6
  class UserAgent < Seahorse::Client::Plugin
7
+ # @api private
7
8
  option(:user_agent_suffix)
9
+ # @api private
10
+ option(:user_agent_frameworks, default: [])
11
+
12
+ option(
13
+ :sdk_ua_app_id,
14
+ doc_type: 'String',
15
+ docstring: <<-DOCS) do |cfg|
16
+ A unique and opaque application ID that is appended to the
17
+ User-Agent header as app/<sdk_ua_app_id>. It should have a
18
+ maximum length of 50.
19
+ DOCS
20
+ app_id = ENV['AWS_SDK_UA_APP_ID']
21
+ app_id ||= Aws.shared_config.sdk_ua_app_id(profile: cfg.profile)
22
+ app_id
23
+ end
24
+
25
+ def self.feature(feature, &block)
26
+ Thread.current[:aws_sdk_core_user_agent_feature] ||= []
27
+ Thread.current[:aws_sdk_core_user_agent_feature] << "ft/#{feature}"
28
+ block.call
29
+ ensure
30
+ Thread.current[:aws_sdk_core_user_agent_feature].pop
31
+ end
8
32
 
9
33
  # @api private
10
34
  class Handler < Seahorse::Client::Handler
@@ -14,33 +38,112 @@ module Aws
14
38
  end
15
39
 
16
40
  def set_user_agent(context)
17
- ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
41
+ context.http_request.headers['User-Agent'] = UserAgent.new(context).to_s
42
+ end
43
+
44
+ class UserAgent
45
+ def initialize(context)
46
+ @context = context
47
+ end
48
+
49
+ def to_s
50
+ ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
51
+ ua += ' ua/2.0'
52
+ ua += " #{api_metadata}" if api_metadata
53
+ ua += " #{os_metadata}"
54
+ ua += " #{language_metadata}"
55
+ ua += " #{env_metadata}" if env_metadata
56
+ ua += " #{config_metadata}" if config_metadata
57
+ ua += " #{app_id}" if app_id
58
+ ua += " #{feature_metadata}" if feature_metadata
59
+ ua += " #{framework_metadata}" if framework_metadata
60
+ if @context.config.user_agent_suffix
61
+ ua += " #{@context.config.user_agent_suffix}"
62
+ end
63
+ ua.strip
64
+ end
65
+
66
+ private
18
67
 
19
- begin
20
- ua += " #{RUBY_ENGINE}/#{RUBY_VERSION}"
21
- rescue
22
- ua += " RUBY_ENGINE_NA/#{RUBY_VERSION}"
68
+ # Used to be gem_name/gem_version
69
+ def api_metadata
70
+ service_id = @context.config.api.metadata['serviceId']
71
+ return unless service_id
72
+
73
+ service_id = service_id.gsub(' ', '_').downcase
74
+ gem_version = @context[:gem_version]
75
+ "api/#{service_id}##{gem_version}"
76
+ end
77
+
78
+ # Used to be RUBY_PLATFORM
79
+ def os_metadata
80
+ os =
81
+ case RbConfig::CONFIG['host_os']
82
+ when /mac|darwin/
83
+ 'macos'
84
+ when /linux|cygwin/
85
+ 'linux'
86
+ when /mingw|mswin/
87
+ 'windows'
88
+ else
89
+ 'other'
90
+ end
91
+ metadata = "os/#{os}"
92
+ local_version = Gem::Platform.local.version
93
+ metadata += "##{local_version}" if local_version
94
+ metadata += " md/#{RbConfig::CONFIG['host_cpu']}"
95
+ metadata
23
96
  end
24
97
 
25
- ua += " #{RUBY_PLATFORM}"
98
+ # Used to be RUBY_ENGINE/RUBY_VERSION
99
+ def language_metadata
100
+ "lang/#{RUBY_ENGINE}##{RUBY_ENGINE_VERSION} md/#{RUBY_VERSION}"
101
+ end
102
+
103
+ def env_metadata
104
+ return unless (execution_env = ENV['AWS_EXECUTION_ENV'])
105
+
106
+ "exec-env/#{execution_env}"
107
+ end
26
108
 
27
- if context[:gem_name] && context[:gem_version]
28
- ua += " #{context[:gem_name]}/#{context[:gem_version]}"
109
+ def config_metadata
110
+ "cfg/retry-mode##{@context.config.retry_mode}"
29
111
  end
30
112
 
31
- if (execution_env = ENV['AWS_EXECUTION_ENV'])
32
- ua += " exec-env/#{execution_env}"
113
+ def app_id
114
+ return unless (app_id = @context.config.sdk_ua_app_id)
115
+
116
+ # Sanitize and only allow these characters
117
+ app_id = app_id.gsub(/[^!#$%&'*+\-.^_`|~0-9A-Za-z]/, '-')
118
+ "app/#{app_id}"
33
119
  end
34
120
 
35
- if context.config.user_agent_suffix
36
- ua += " #{context.config.user_agent_suffix}"
121
+ def feature_metadata
122
+ return unless Thread.current[:aws_sdk_core_user_agent_feature]
123
+
124
+ Thread.current[:aws_sdk_core_user_agent_feature].join(' ')
37
125
  end
38
126
 
39
- context.http_request.headers['User-Agent'] = ua.strip
127
+ def framework_metadata
128
+ if (frameworks_cfg = @context.config.user_agent_frameworks).empty?
129
+ return
130
+ end
131
+
132
+ # Frameworks may be aws-record, aws-sdk-rails, etc.
133
+ regex = /gems\/(?<name>#{frameworks_cfg.join('|')})-(?<version>\d+\.\d+\.\d+)/.freeze
134
+ frameworks = {}
135
+ Kernel.caller.each do |line|
136
+ match = line.match(regex)
137
+ next unless match
138
+
139
+ frameworks[match[:name]] = match[:version]
140
+ end
141
+ frameworks.map { |n, v| "lib/#{n}##{v}" }.join(' ')
142
+ end
40
143
  end
41
144
  end
42
145
 
43
- handler(Handler)
146
+ handler(Handler, priority: 1)
44
147
  end
45
148
  end
46
149
  end
@@ -36,12 +36,6 @@ module Aws
36
36
  @credentials
37
37
  end
38
38
 
39
- # @return [Time,nil]
40
- def expiration
41
- refresh_if_near_expiration!
42
- @expiration
43
- end
44
-
45
39
  # Refresh credentials.
46
40
  # @return [void]
47
41
  def refresh!
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ module Aws
6
+
7
+ # Module/mixin used by token provider classes that can be refreshed. This
8
+ # provides basic refresh logic in a thread-safe manner. Classes mixing in
9
+ # this module are expected to implement a #refresh method that populates
10
+ # the following instance variable:
11
+ #
12
+ # * `@token` [Token] - {Aws::Token} object with the `expiration` and `token`
13
+ # fields set.
14
+ #
15
+ # @api private
16
+ module RefreshingToken
17
+
18
+ def initialize(options = {})
19
+ @mutex = Mutex.new
20
+ @before_refresh = options.delete(:before_refresh) if Hash === options
21
+
22
+ @before_refresh.call(self) if @before_refresh
23
+ refresh
24
+ end
25
+
26
+ # @return [Token]
27
+ def token
28
+ refresh_if_near_expiration
29
+ @token
30
+ end
31
+
32
+ # @return [Time,nil]
33
+ def expiration
34
+ refresh_if_near_expiration
35
+ @expiration
36
+ end
37
+
38
+ # Refresh token.
39
+ # @return [void]
40
+ def refresh!
41
+ @mutex.synchronize do
42
+ @before_refresh.call(self) if @before_refresh
43
+ refresh
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # Refreshes token if it is within
50
+ # 5 minutes of expiration.
51
+ def refresh_if_near_expiration
52
+ if near_expiration?
53
+ @mutex.synchronize do
54
+ if near_expiration?
55
+ @before_refresh.call(self) if @before_refresh
56
+ refresh
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def near_expiration?
63
+ if @token && @token.expiration
64
+ # are we within 5 minutes of expiration?
65
+ (Time.now.to_i + 5 * 60) > @token.expiration.to_i
66
+ else
67
+ true
68
+ end
69
+ end
70
+ end
71
+ end
@@ -53,12 +53,8 @@ module Aws
53
53
  return if !value || value.empty?
54
54
  headers[ref.location_name] = value
55
55
  .compact
56
- .map { |s| escape_header_list_string(s.to_s) }
57
- .join(",")
58
- end
59
-
60
- def escape_header_list_string(s)
61
- (s.include?('"') || s.include?(",")) ? "\"#{s.gsub('"', '\"')}\"" : s
56
+ .map { |s| Seahorse::Util.escape_header_list_string(s.to_s) }
57
+ .join(',')
62
58
  end
63
59
 
64
60
  def apply_header_map(headers, ref, values)
@@ -3,7 +3,11 @@
3
3
  module Aws
4
4
  # @api private
5
5
  class SharedConfig
6
- SSO_PROFILE_KEYS = %w[sso_start_url sso_region sso_account_id sso_role_name].freeze
6
+ SSO_CREDENTIAL_PROFILE_KEYS = %w[sso_account_id sso_role_name].freeze
7
+ SSO_PROFILE_KEYS = %w[sso_session sso_start_url sso_region sso_account_id sso_role_name].freeze
8
+ SSO_TOKEN_PROFILE_KEYS = %w[sso_session].freeze
9
+ SSO_SESSION_KEYS = %w[sso_region sso_start_url].freeze
10
+
7
11
 
8
12
  # @return [String]
9
13
  attr_reader :credentials_path
@@ -51,10 +55,12 @@ module Aws
51
55
  @config_enabled = options[:config_enabled]
52
56
  @credentials_path = options[:credentials_path] ||
53
57
  determine_credentials_path
58
+ @credentials_path = File.expand_path(@credentials_path) if @credentials_path
54
59
  @parsed_credentials = {}
55
60
  load_credentials_file if loadable?(@credentials_path)
56
61
  if @config_enabled
57
62
  @config_path = options[:config_path] || determine_config_path
63
+ @config_path = File.expand_path(@config_path) if @config_path
58
64
  load_config_file if loadable?(@config_path)
59
65
  end
60
66
  end
@@ -149,6 +155,18 @@ module Aws
149
155
  credentials
150
156
  end
151
157
 
158
+ # Attempts to load from shared config or shared credentials file.
159
+ # Will always attempt first to load from the shared credentials
160
+ # file, if present.
161
+ def sso_token_from_config(opts = {})
162
+ p = opts[:profile] || @profile_name
163
+ token = sso_token_from_profile(@parsed_credentials, p)
164
+ if @parsed_config
165
+ token ||= sso_token_from_profile(@parsed_config, p)
166
+ end
167
+ token
168
+ end
169
+
152
170
  # Add an accessor method (similar to attr_reader) to return a configuration value
153
171
  # Uses the get_config_value below to control where
154
172
  # values are loaded from
@@ -179,7 +197,8 @@ module Aws
179
197
  :s3_use_arn_region,
180
198
  :s3_us_east_1_regional_endpoint,
181
199
  :s3_disable_multiregion_access_points,
182
- :defaults_mode
200
+ :defaults_mode,
201
+ :sdk_ua_app_id
183
202
  )
184
203
 
185
204
  private
@@ -314,13 +333,66 @@ module Aws
314
333
  def sso_credentials_from_profile(cfg, profile)
315
334
  if @parsed_config &&
316
335
  (prof_config = cfg[profile]) &&
317
- !(prof_config.keys & SSO_PROFILE_KEYS).empty?
336
+ !(prof_config.keys & SSO_CREDENTIAL_PROFILE_KEYS).empty?
337
+
338
+ if sso_session_name = prof_config['sso_session']
339
+ sso_session = cfg["sso-session #{sso_session_name}"]
340
+ unless sso_session
341
+ raise ArgumentError,
342
+ "sso-session #{sso_session_name} must be defined in the config file. " \
343
+ "Referenced by profile #{profile}"
344
+ end
345
+ sso_region = sso_session['sso_region']
346
+ sso_start_url = sso_session['sso_start_url']
347
+
348
+ # validate sso_region and sso_start_url don't conflict if set on profile and session
349
+ if prof_config['sso_region'] && prof_config['sso_region'] != sso_region
350
+ raise ArgumentError,
351
+ "sso-session #{sso_session_name}'s sso_region (#{sso_region}) " \
352
+ "does not match the profile #{profile}'s sso_region (#{prof_config['sso_region']}'"
353
+ end
354
+ if prof_config['sso_start_url'] && prof_config['sso_start_url'] != sso_start_url
355
+ raise ArgumentError,
356
+ "sso-session #{sso_session_name}'s sso_start_url (#{sso_start_url}) " \
357
+ "does not match the profile #{profile}'s sso_start_url (#{prof_config['sso_start_url']}'"
358
+ end
359
+ else
360
+ sso_region = prof_config['sso_region']
361
+ sso_start_url = prof_config['sso_start_url']
362
+ end
318
363
 
319
364
  SSOCredentials.new(
320
- sso_start_url: prof_config['sso_start_url'],
321
- sso_region: prof_config['sso_region'],
322
365
  sso_account_id: prof_config['sso_account_id'],
323
- sso_role_name: prof_config['sso_role_name']
366
+ sso_role_name: prof_config['sso_role_name'],
367
+ sso_session: prof_config['sso_session'],
368
+ sso_region: sso_region,
369
+ sso_start_url: prof_config['sso_start_url']
370
+ )
371
+ end
372
+ end
373
+
374
+ # If the required sso_ profile values are present, attempt to construct
375
+ # SSOTokenProvider
376
+ def sso_token_from_profile(cfg, profile)
377
+ if @parsed_config &&
378
+ (prof_config = cfg[profile]) &&
379
+ !(prof_config.keys & SSO_TOKEN_PROFILE_KEYS).empty?
380
+
381
+ sso_session_name = prof_config['sso_session']
382
+ sso_session = cfg["sso-session #{sso_session_name}"]
383
+ unless sso_session
384
+ raise ArgumentError,
385
+ "sso-session #{sso_session_name} must be defined in the config file." \
386
+ "Referenced by profile #{profile}"
387
+ end
388
+
389
+ unless sso_session['sso_region']
390
+ raise ArgumentError, "sso-session #{sso_session_name} missing required parameter: sso_region"
391
+ end
392
+
393
+ SSOTokenProvider.new(
394
+ sso_session: sso_session_name,
395
+ sso_region: sso_session['sso_region']
324
396
  )
325
397
  end
326
398
  end