googleauth 0.5.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +5 -4
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  6. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  7. data/.kokoro/build.bat +16 -0
  8. data/.kokoro/build.sh +4 -0
  9. data/.kokoro/continuous/common.cfg +24 -0
  10. data/.kokoro/continuous/linux.cfg +25 -0
  11. data/.kokoro/continuous/osx.cfg +8 -0
  12. data/.kokoro/continuous/post.cfg +30 -0
  13. data/.kokoro/continuous/windows.cfg +29 -0
  14. data/.kokoro/osx.sh +4 -0
  15. data/.kokoro/presubmit/common.cfg +24 -0
  16. data/.kokoro/presubmit/linux.cfg +24 -0
  17. data/.kokoro/presubmit/osx.cfg +8 -0
  18. data/.kokoro/presubmit/windows.cfg +29 -0
  19. data/.kokoro/release.cfg +94 -0
  20. data/.kokoro/trampoline.bat +10 -0
  21. data/.kokoro/trampoline.sh +4 -0
  22. data/.repo-metadata.json +5 -0
  23. data/.rubocop.yml +19 -1
  24. data/CHANGELOG.md +112 -19
  25. data/CODE_OF_CONDUCT.md +43 -0
  26. data/Gemfile +19 -13
  27. data/{COPYING → LICENSE} +0 -0
  28. data/README.md +58 -18
  29. data/Rakefile +126 -9
  30. data/googleauth.gemspec +28 -25
  31. data/integration/helper.rb +31 -0
  32. data/integration/id_tokens/key_source_test.rb +74 -0
  33. data/lib/googleauth.rb +7 -96
  34. data/lib/googleauth/application_default.rb +81 -0
  35. data/lib/googleauth/client_id.rb +21 -19
  36. data/lib/googleauth/compute_engine.rb +70 -43
  37. data/lib/googleauth/credentials.rb +442 -0
  38. data/lib/googleauth/credentials_loader.rb +117 -43
  39. data/lib/googleauth/default_credentials.rb +93 -0
  40. data/lib/googleauth/iam.rb +11 -11
  41. data/lib/googleauth/id_tokens.rb +233 -0
  42. data/lib/googleauth/id_tokens/errors.rb +71 -0
  43. data/lib/googleauth/id_tokens/key_sources.rb +394 -0
  44. data/lib/googleauth/id_tokens/verifier.rb +144 -0
  45. data/lib/googleauth/json_key_reader.rb +50 -0
  46. data/lib/googleauth/scope_util.rb +12 -12
  47. data/lib/googleauth/service_account.rb +74 -63
  48. data/lib/googleauth/signet.rb +55 -13
  49. data/lib/googleauth/stores/file_token_store.rb +8 -8
  50. data/lib/googleauth/stores/redis_token_store.rb +22 -22
  51. data/lib/googleauth/token_store.rb +6 -6
  52. data/lib/googleauth/user_authorizer.rb +80 -68
  53. data/lib/googleauth/user_refresh.rb +44 -35
  54. data/lib/googleauth/version.rb +1 -1
  55. data/lib/googleauth/web_user_authorizer.rb +77 -68
  56. data/rakelib/devsite_builder.rb +45 -0
  57. data/rakelib/link_checker.rb +64 -0
  58. data/rakelib/repo_metadata.rb +59 -0
  59. data/spec/googleauth/apply_auth_examples.rb +74 -50
  60. data/spec/googleauth/client_id_spec.rb +75 -55
  61. data/spec/googleauth/compute_engine_spec.rb +98 -46
  62. data/spec/googleauth/credentials_spec.rb +478 -0
  63. data/spec/googleauth/get_application_default_spec.rb +149 -111
  64. data/spec/googleauth/iam_spec.rb +25 -25
  65. data/spec/googleauth/scope_util_spec.rb +26 -24
  66. data/spec/googleauth/service_account_spec.rb +269 -144
  67. data/spec/googleauth/signet_spec.rb +101 -30
  68. data/spec/googleauth/stores/file_token_store_spec.rb +12 -13
  69. data/spec/googleauth/stores/redis_token_store_spec.rb +11 -11
  70. data/spec/googleauth/stores/store_examples.rb +16 -16
  71. data/spec/googleauth/user_authorizer_spec.rb +153 -124
  72. data/spec/googleauth/user_refresh_spec.rb +186 -121
  73. data/spec/googleauth/web_user_authorizer_spec.rb +82 -69
  74. data/spec/spec_helper.rb +21 -19
  75. data/test/helper.rb +33 -0
  76. data/test/id_tokens/key_sources_test.rb +240 -0
  77. data/test/id_tokens/verifier_test.rb +269 -0
  78. metadata +87 -34
  79. data/.rubocop_todo.yml +0 -32
  80. data/.travis.yml +0 -37
@@ -1,35 +1,38 @@
1
1
  # -*- ruby -*-
2
2
  # encoding: utf-8
3
- $LOAD_PATH.push File.expand_path('../lib', __FILE__)
4
- require 'googleauth/version'
5
3
 
6
- Gem::Specification.new do |s|
7
- s.name = 'googleauth'
8
- s.version = Google::Auth::VERSION
9
- s.authors = ['Tim Emiola']
10
- s.email = 'temiola@google.com'
11
- s.homepage = 'https://github.com/google/google-auth-library-ruby'
12
- s.summary = 'Google Auth Library for Ruby'
13
- s.license = 'Apache-2.0'
14
- s.description = <<-eos
4
+ $LOAD_PATH.push File.expand_path("lib", __dir__)
5
+ require "googleauth/version"
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = "googleauth"
9
+ gem.version = Google::Auth::VERSION
10
+ gem.authors = ["Tim Emiola"]
11
+ gem.email = "temiola@google.com"
12
+ gem.homepage = "https://github.com/googleapis/google-auth-library-ruby"
13
+ gem.summary = "Google Auth Library for Ruby"
14
+ gem.license = "Apache-2.0"
15
+ gem.description = <<-DESCRIPTION
15
16
  Allows simple authorization for accessing Google APIs.
16
17
  Provide support for Application Default Credentials, as described at
17
18
  https://developers.google.com/accounts/docs/application-default-credentials
18
- eos
19
+ DESCRIPTION
19
20
 
20
- s.files = `git ls-files`.split("\n")
21
- s.test_files = `git ls-files -- spec/*`.split("\n")
22
- s.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f|
23
- File.basename(f)
21
+ gem.files = `git ls-files`.split "\n"
22
+ gem.test_files = `git ls-files -- spec/*`.split "\n"
23
+ gem.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f|
24
+ File.basename f
24
25
  end
25
- s.require_paths = ['lib']
26
- s.platform = Gem::Platform::RUBY
26
+ gem.require_paths = ["lib"]
27
+ gem.platform = Gem::Platform::RUBY
28
+ gem.required_ruby_version = ">= 2.4.0"
29
+
30
+ gem.add_dependency "faraday", ">= 0.17.3", "< 2.0"
31
+ gem.add_dependency "jwt", ">= 1.4", "< 3.0"
32
+ gem.add_dependency "memoist", "~> 0.16"
33
+ gem.add_dependency "multi_json", "~> 1.11"
34
+ gem.add_dependency "os", ">= 0.9", "< 2.0"
35
+ gem.add_dependency "signet", "~> 0.14"
27
36
 
28
- s.add_dependency 'faraday', '~> 0.9'
29
- s.add_dependency 'logging', '~> 2.0'
30
- s.add_dependency 'jwt', '~> 1.4'
31
- s.add_dependency 'memoist', '~> 0.12'
32
- s.add_dependency 'multi_json', '~> 1.11'
33
- s.add_dependency 'os', '~> 0.9'
34
- s.add_dependency 'signet', '~> 0.7'
37
+ gem.add_development_dependency "yard", "~> 0.9"
35
38
  end
@@ -0,0 +1,31 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are
5
+ # met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above
10
+ # copyright notice, this list of conditions and the following disclaimer
11
+ # in the documentation and/or other materials provided with the
12
+ # distribution.
13
+ # * Neither the name of Google Inc. nor the names of its
14
+ # contributors may be used to endorse or promote products derived from
15
+ # this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ require "minitest/autorun"
30
+ require "minitest/focus"
31
+ require "googleauth"
@@ -0,0 +1,74 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are
5
+ # met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above
10
+ # copyright notice, this list of conditions and the following disclaimer
11
+ # in the documentation and/or other materials provided with the
12
+ # distribution.
13
+ # * Neither the name of Google Inc. nor the names of its
14
+ # contributors may be used to endorse or promote products derived from
15
+ # this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ require "helper"
30
+
31
+ describe Google::Auth::IDTokens do
32
+ describe "key source" do
33
+ let(:legacy_oidc_key_source) {
34
+ Google::Auth::IDTokens::X509CertHttpKeySource.new "https://www.googleapis.com/oauth2/v1/certs"
35
+ }
36
+ let(:oidc_key_source) { Google::Auth::IDTokens.oidc_key_source }
37
+ let(:iap_key_source) { Google::Auth::IDTokens.iap_key_source }
38
+
39
+ it "Gets real keys from the OAuth2 V1 cert URL" do
40
+ keys = legacy_oidc_key_source.refresh_keys
41
+ refute_empty keys
42
+ keys.each do |key|
43
+ assert_kind_of OpenSSL::PKey::RSA, key.key
44
+ refute key.key.private?
45
+ assert_equal "RS256", key.algorithm
46
+ end
47
+ end
48
+
49
+ it "Gets real keys from the OAuth2 V3 cert URL" do
50
+ keys = oidc_key_source.refresh_keys
51
+ refute_empty keys
52
+ keys.each do |key|
53
+ assert_kind_of OpenSSL::PKey::RSA, key.key
54
+ refute key.key.private?
55
+ assert_equal "RS256", key.algorithm
56
+ end
57
+ end
58
+
59
+ it "Gets the same keys from the OAuth2 V1 and V3 cert URLs" do
60
+ keys_v1 = legacy_oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
61
+ keys_v3 = oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
62
+ assert_equal keys_v1, keys_v3
63
+ end
64
+
65
+ it "Gets real keys from the IAP public key URL" do
66
+ keys = iap_key_source.refresh_keys
67
+ refute_empty keys
68
+ keys.each do |key|
69
+ assert_kind_of OpenSSL::PKey::EC, key.key
70
+ assert_equal "ES256", key.algorithm
71
+ end
72
+ end
73
+ end
74
+ end
@@ -27,99 +27,10 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
- require 'multi_json'
31
- require 'stringio'
32
-
33
- require 'googleauth/credentials_loader'
34
- require 'googleauth/compute_engine'
35
- require 'googleauth/service_account'
36
- require 'googleauth/user_refresh'
37
- require 'googleauth/client_id'
38
- require 'googleauth/user_authorizer'
39
- require 'googleauth/web_user_authorizer'
40
-
41
- module Google
42
- # Module Auth provides classes that provide Google-specific authorization
43
- # used to access Google APIs.
44
- module Auth
45
- NOT_FOUND_ERROR = <<END
46
- Could not load the default credentials. Browse to
47
- https://developers.google.com/accounts/docs/application-default-credentials
48
- for more information
49
- END
50
-
51
- # DefaultCredentials is used to preload the credentials file, to determine
52
- # which type of credentials should be loaded.
53
- class DefaultCredentials
54
- extend CredentialsLoader
55
-
56
- # override CredentialsLoader#make_creds to use the class determined by
57
- # loading the json.
58
- def self.make_creds(options = {})
59
- json_key_io, scope = options.values_at(:json_key_io, :scope)
60
- if json_key_io
61
- json_key, clz = determine_creds_class(json_key_io)
62
- clz.make_creds(json_key_io: StringIO.new(MultiJson.dump(json_key)),
63
- scope: scope)
64
- else
65
- clz = read_creds
66
- clz.make_creds(scope: scope)
67
- end
68
- end
69
-
70
- def self.read_creds
71
- env_var = CredentialsLoader::ACCOUNT_TYPE_VAR
72
- type = ENV[env_var]
73
- fail "#{ACCOUNT_TYPE_VAR} is undefined in env" unless type
74
- case type
75
- when 'service_account'
76
- ServiceAccountCredentials
77
- when 'authorized_user'
78
- UserRefreshCredentials
79
- else
80
- fail "credentials type '#{type}' is not supported"
81
- end
82
- end
83
-
84
- # Reads the input json and determines which creds class to use.
85
- def self.determine_creds_class(json_key_io)
86
- json_key = MultiJson.load(json_key_io.read)
87
- key = 'type'
88
- fail "the json is missing the '#{key}' field" unless json_key.key?(key)
89
- type = json_key[key]
90
- case type
91
- when 'service_account'
92
- [json_key, ServiceAccountCredentials]
93
- when 'authorized_user'
94
- [json_key, UserRefreshCredentials]
95
- else
96
- fail "credentials type '#{type}' is not supported"
97
- end
98
- end
99
- end
100
-
101
- # Obtains the default credentials implementation to use in this
102
- # environment.
103
- #
104
- # Use this to obtain the Application Default Credentials for accessing
105
- # Google APIs. Application Default Credentials are described in detail
106
- # at http://goo.gl/IUuyuX.
107
- #
108
- # If supplied, scope is used to create the credentials instance, when it can
109
- # be applied. E.g, on google compute engine and for user credentials the
110
- # scope is ignored.
111
- #
112
- # @param scope [string|array|nil] the scope(s) to access
113
- # @param options [hash] allows override of the connection being used
114
- def get_application_default(scope = nil, options = {})
115
- creds = DefaultCredentials.from_env(scope) ||
116
- DefaultCredentials.from_well_known_path(scope) ||
117
- DefaultCredentials.from_system_default_path(scope)
118
- return creds unless creds.nil?
119
- fail NOT_FOUND_ERROR unless GCECredentials.on_gce?(options)
120
- GCECredentials.new
121
- end
122
-
123
- module_function :get_application_default
124
- end
125
- end
30
+ require "googleauth/application_default"
31
+ require "googleauth/client_id"
32
+ require "googleauth/credentials"
33
+ require "googleauth/default_credentials"
34
+ require "googleauth/id_tokens"
35
+ require "googleauth/user_authorizer"
36
+ require "googleauth/web_user_authorizer"
@@ -0,0 +1,81 @@
1
+ # Copyright 2015, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require "googleauth/compute_engine"
31
+ require "googleauth/default_credentials"
32
+
33
+ module Google
34
+ # Module Auth provides classes that provide Google-specific authorization
35
+ # used to access Google APIs.
36
+ module Auth
37
+ NOT_FOUND_ERROR = <<~ERROR_MESSAGE.freeze
38
+ Could not load the default credentials. Browse to
39
+ https://developers.google.com/accounts/docs/application-default-credentials
40
+ for more information
41
+ ERROR_MESSAGE
42
+
43
+ module_function
44
+
45
+ # Obtains the default credentials implementation to use in this
46
+ # environment.
47
+ #
48
+ # Use this to obtain the Application Default Credentials for accessing
49
+ # Google APIs. Application Default Credentials are described in detail
50
+ # at https://cloud.google.com/docs/authentication/production.
51
+ #
52
+ # If supplied, scope is used to create the credentials instance, when it can
53
+ # be applied. E.g, on google compute engine and for user credentials the
54
+ # scope is ignored.
55
+ #
56
+ # @param scope [string|array|nil] the scope(s) to access
57
+ # @param options [Hash] Connection options. These may be used to configure
58
+ # the `Faraday::Connection` used for outgoing HTTP requests. For
59
+ # example, if a connection proxy must be used in the current network,
60
+ # you may provide a connection with with the needed proxy options.
61
+ # The following keys are recognized:
62
+ # * `:default_connection` The connection object to use for token
63
+ # refresh requests.
64
+ # * `:connection_builder` A `Proc` that creates and returns a
65
+ # connection to use for token refresh requests.
66
+ # * `:connection` The connection to use to determine whether GCE
67
+ # metadata credentials are available.
68
+ def get_application_default scope = nil, options = {}
69
+ creds = DefaultCredentials.from_env(scope, options) ||
70
+ DefaultCredentials.from_well_known_path(scope, options) ||
71
+ DefaultCredentials.from_system_default_path(scope, options)
72
+ return creds unless creds.nil?
73
+ unless GCECredentials.on_gce? options
74
+ # Clear cache of the result of GCECredentials.on_gce?
75
+ GCECredentials.unmemoize_all
76
+ raise NOT_FOUND_ERROR
77
+ end
78
+ GCECredentials.new scope: scope
79
+ end
80
+ end
81
+ end
@@ -27,19 +27,20 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
- require 'multi_json'
30
+ require "multi_json"
31
+ require "googleauth/credentials_loader"
31
32
 
32
33
  module Google
33
34
  module Auth
34
35
  # Representation of an application's identity for user authorization
35
36
  # flows.
36
37
  class ClientId
37
- INSTALLED_APP = 'installed'
38
- WEB_APP = 'web'
39
- CLIENT_ID = 'client_id'
40
- CLIENT_SECRET = 'client_secret'
38
+ INSTALLED_APP = "installed".freeze
39
+ WEB_APP = "web".freeze
40
+ CLIENT_ID = "client_id".freeze
41
+ CLIENT_SECRET = "client_secret".freeze
41
42
  MISSING_TOP_LEVEL_ELEMENT_ERROR =
42
- "Expected top level property 'installed' or 'web' to be present."
43
+ "Expected top level property 'installed' or 'web' to be present.".freeze
43
44
 
44
45
  # Text identifier of the client ID
45
46
  # @return [String]
@@ -62,25 +63,26 @@ module Google
62
63
  # @note Direction instantion is discouraged to avoid embedding IDs
63
64
  # & secrets in source. See {#from_file} to load from
64
65
  # `client_secrets.json` files.
65
- def initialize(id, secret)
66
- fail 'Client id can not be nil' if id.nil?
67
- fail 'Client secret can not be nil' if secret.nil?
66
+ def initialize id, secret
67
+ CredentialsLoader.warn_if_cloud_sdk_credentials id
68
+ raise "Client id can not be nil" if id.nil?
69
+ raise "Client secret can not be nil" if secret.nil?
68
70
  @id = id
69
71
  @secret = secret
70
72
  end
71
73
 
72
- # Constructs a Client ID from a JSON file downloaed from the
74
+ # Constructs a Client ID from a JSON file downloaded from the
73
75
  # Google Developers Console.
74
76
  #
75
77
  # @param [String, File] file
76
78
  # Path of file to read from
77
79
  # @return [Google::Auth::ClientID]
78
- def self.from_file(file)
79
- fail 'File can not be nil.' if file.nil?
80
- File.open(file.to_s) do |f|
80
+ def self.from_file file
81
+ raise "File can not be nil." if file.nil?
82
+ File.open file.to_s do |f|
81
83
  json = f.read
82
- config = MultiJson.load(json)
83
- from_hash(config)
84
+ config = MultiJson.load json
85
+ from_hash config
84
86
  end
85
87
  end
86
88
 
@@ -91,11 +93,11 @@ module Google
91
93
  # @param [hash] config
92
94
  # Parsed contents of the JSON file
93
95
  # @return [Google::Auth::ClientID]
94
- def self.from_hash(config)
95
- fail 'Hash can not be nil.' if config.nil?
96
+ def self.from_hash config
97
+ raise "Hash can not be nil." if config.nil?
96
98
  raw_detail = config[INSTALLED_APP] || config[WEB_APP]
97
- fail MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
98
- ClientId.new(raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET])
99
+ raise MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
100
+ ClientId.new raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET]
99
101
  end
100
102
  end
101
103
  end
@@ -27,57 +27,74 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
- require 'faraday'
31
- require 'googleauth/signet'
32
- require 'memoist'
30
+ require "faraday"
31
+ require "googleauth/signet"
32
+ require "memoist"
33
33
 
34
34
  module Google
35
35
  # Module Auth provides classes that provide Google-specific authorization
36
36
  # used to access Google APIs.
37
37
  module Auth
38
- NO_METADATA_SERVER_ERROR = <<END
39
- Error code 404 trying to get security access token
40
- from Compute Engine metadata for the default service account. This
41
- may be because the virtual machine instance does not have permission
42
- scopes specified.
43
- END
44
- UNEXPECTED_ERROR_SUFFIX = <<END
45
- trying to get security access token from Compute Engine metadata for
46
- the default service account
47
- END
38
+ NO_METADATA_SERVER_ERROR = <<~ERROR.freeze
39
+ Error code 404 trying to get security access token
40
+ from Compute Engine metadata for the default service account. This
41
+ may be because the virtual machine instance does not have permission
42
+ scopes specified.
43
+ ERROR
44
+ UNEXPECTED_ERROR_SUFFIX = <<~ERROR.freeze
45
+ trying to get security access token from Compute Engine metadata for
46
+ the default service account
47
+ ERROR
48
48
 
49
49
  # Extends Signet::OAuth2::Client so that the auth token is obtained from
50
50
  # the GCE metadata server.
51
51
  class GCECredentials < Signet::OAuth2::Client
52
52
  # The IP Address is used in the URIs to speed up failures on non-GCE
53
53
  # systems.
54
- COMPUTE_AUTH_TOKEN_URI = 'http://169.254.169.254/computeMetadata/v1/'\
55
- 'instance/service-accounts/default/token'
56
- COMPUTE_CHECK_URI = 'http://169.254.169.254'
54
+ DEFAULT_METADATA_HOST = "169.254.169.254".freeze
55
+
56
+ # @private Unused and deprecated
57
+ COMPUTE_AUTH_TOKEN_URI =
58
+ "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
59
+ # @private Unused and deprecated
60
+ COMPUTE_ID_TOKEN_URI =
61
+ "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
62
+ # @private Unused and deprecated
63
+ COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
57
64
 
58
65
  class << self
59
66
  extend Memoist
60
67
 
68
+ def metadata_host
69
+ ENV.fetch "GCE_METADATA_HOST", DEFAULT_METADATA_HOST
70
+ end
71
+
72
+ def compute_check_uri
73
+ "http://#{metadata_host}".freeze
74
+ end
75
+
76
+ def compute_auth_token_uri
77
+ "#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/token".freeze
78
+ end
79
+
80
+ def compute_id_token_uri
81
+ "#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/identity".freeze
82
+ end
83
+
61
84
  # Detect if this appear to be a GCE instance, by checking if metadata
62
- # is available
63
- def on_gce?(options = {})
85
+ # is available.
86
+ def on_gce? options = {}
87
+ # TODO: This should use google-cloud-env instead.
64
88
  c = options[:connection] || Faraday.default_connection
65
- resp = c.get(COMPUTE_CHECK_URI) do |req|
66
- # Comment from: oauth2client/client.py
67
- #
68
- # Note: the explicit `timeout` below is a workaround. The underlying
69
- # issue is that resolving an unknown host on some networks will take
70
- # 20-30 seconds; making this timeout short fixes the issue, but
71
- # could lead to false negatives in the event that we are on GCE, but
72
- # the metadata resolution was particularly slow. The latter case is
73
- # "unlikely".
74
- req.options.timeout = 0.1
89
+ headers = { "Metadata-Flavor" => "Google" }
90
+ resp = c.get compute_check_uri, nil, headers do |req|
91
+ req.options.timeout = 1.0
92
+ req.options.open_timeout = 0.1
75
93
  end
76
94
  return false unless resp.status == 200
77
- return false unless resp.headers.key?('Metadata-Flavor')
78
- return resp.headers['Metadata-Flavor'] == 'Google'
95
+ resp.headers["Metadata-Flavor"] == "Google"
79
96
  rescue Faraday::TimeoutError, Faraday::ConnectionFailed
80
- return false
97
+ false
81
98
  end
82
99
 
83
100
  memoize :on_gce?
@@ -85,19 +102,29 @@ END
85
102
 
86
103
  # Overrides the super class method to change how access tokens are
87
104
  # fetched.
88
- def fetch_access_token(options = {})
105
+ def fetch_access_token options = {}
89
106
  c = options[:connection] || Faraday.default_connection
90
- c.headers = { 'Metadata-Flavor' => 'Google' }
91
- resp = c.get(COMPUTE_AUTH_TOKEN_URI)
92
- case resp.status
93
- when 200
94
- Signet::OAuth2.parse_credentials(resp.body,
95
- resp.headers['content-type'])
96
- when 404
97
- fail(Signet::AuthorizationError, NO_METADATA_SERVER_ERROR)
98
- else
99
- msg = "Unexpected error code #{resp.status}" + UNEXPECTED_ERROR_SUFFIX
100
- fail(Signet::AuthorizationError, msg)
107
+ retry_with_error do
108
+ uri = target_audience ? GCECredentials.compute_id_token_uri : GCECredentials.compute_auth_token_uri
109
+ query = target_audience ? { "audience" => target_audience, "format" => "full" } : {}
110
+ query[:scopes] = Array(scope).join "," if scope
111
+ headers = { "Metadata-Flavor" => "Google" }
112
+ resp = c.get uri, query, headers
113
+ case resp.status
114
+ when 200
115
+ content_type = resp.headers["content-type"]
116
+ if content_type == "text/html"
117
+ { (target_audience ? "id_token" : "access_token") => resp.body }
118
+ else
119
+ Signet::OAuth2.parse_credentials resp.body, content_type
120
+ end
121
+ when 404
122
+ raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
123
+ else
124
+ msg = "Unexpected error code #{resp.status}" \
125
+ "#{UNEXPECTED_ERROR_SUFFIX}"
126
+ raise Signet::AuthorizationError, msg
127
+ end
101
128
  end
102
129
  end
103
130
  end