googleauth 0.9.0 → 0.17.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.
- checksums.yaml +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +113 -21
- data/README.md +13 -15
- data/SECURITY.md +7 -0
- data/lib/googleauth/application_default.rb +9 -9
- data/lib/googleauth/compute_engine.rb +55 -30
- data/lib/googleauth/credentials.rb +253 -64
- data/lib/googleauth/credentials_loader.rb +15 -16
- data/lib/googleauth/iam.rb +1 -1
- data/{spec/googleauth/stores/store_examples.rb → lib/googleauth/id_tokens/errors.rb} +36 -23
- data/lib/googleauth/id_tokens/key_sources.rb +396 -0
- data/lib/googleauth/id_tokens/verifier.rb +142 -0
- data/lib/googleauth/id_tokens.rb +233 -0
- data/lib/googleauth/json_key_reader.rb +6 -2
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +61 -36
- data/lib/googleauth/signet.rb +9 -7
- data/lib/googleauth/stores/file_token_store.rb +1 -0
- data/lib/googleauth/stores/redis_token_store.rb +1 -0
- data/lib/googleauth/user_authorizer.rb +8 -3
- data/lib/googleauth/user_refresh.rb +1 -1
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +5 -8
- data/lib/googleauth.rb +1 -0
- metadata +33 -76
- data/.github/CONTRIBUTING.md +0 -74
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -36
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -21
- data/.github/ISSUE_TEMPLATE/support_request.md +0 -7
- data/.gitignore +0 -36
- data/.kokoro/build.bat +0 -16
- data/.kokoro/build.sh +0 -4
- data/.kokoro/continuous/common.cfg +0 -24
- data/.kokoro/continuous/linux.cfg +0 -15
- data/.kokoro/continuous/osx.cfg +0 -3
- data/.kokoro/continuous/windows.cfg +0 -19
- data/.kokoro/osx.sh +0 -4
- data/.kokoro/presubmit/common.cfg +0 -24
- data/.kokoro/presubmit/linux.cfg +0 -14
- data/.kokoro/presubmit/osx.cfg +0 -3
- data/.kokoro/presubmit/windows.cfg +0 -19
- data/.kokoro/release.cfg +0 -53
- data/.kokoro/trampoline.bat +0 -10
- data/.kokoro/trampoline.sh +0 -4
- data/.rspec +0 -2
- data/.rubocop.yml +0 -42
- data/Gemfile +0 -25
- data/Rakefile +0 -89
- data/googleauth.gemspec +0 -35
- data/spec/googleauth/apply_auth_examples.rb +0 -148
- data/spec/googleauth/client_id_spec.rb +0 -160
- data/spec/googleauth/compute_engine_spec.rb +0 -122
- data/spec/googleauth/credentials_spec.rb +0 -459
- data/spec/googleauth/get_application_default_spec.rb +0 -286
- data/spec/googleauth/iam_spec.rb +0 -80
- data/spec/googleauth/scope_util_spec.rb +0 -77
- data/spec/googleauth/service_account_spec.rb +0 -482
- data/spec/googleauth/signet_spec.rb +0 -134
- data/spec/googleauth/stores/file_token_store_spec.rb +0 -57
- data/spec/googleauth/stores/redis_token_store_spec.rb +0 -50
- data/spec/googleauth/user_authorizer_spec.rb +0 -323
- data/spec/googleauth/user_refresh_spec.rb +0 -359
- data/spec/googleauth/web_user_authorizer_spec.rb +0 -172
- data/spec/spec_helper.rb +0 -92
- /data/{COPYING → LICENSE} +0 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2020 Google LLC
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
# * Redistributions in binary form must reproduce the above
|
12
|
+
# copyright notice, this list of conditions and the following disclaimer
|
13
|
+
# in the documentation and/or other materials provided with the
|
14
|
+
# distribution.
|
15
|
+
# * Neither the name of Google Inc. nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products derived from
|
17
|
+
# this software without specific prior written permission.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
20
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
21
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
22
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
23
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
24
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
25
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
26
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
27
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
|
31
|
+
require "googleauth/id_tokens/errors"
|
32
|
+
require "googleauth/id_tokens/key_sources"
|
33
|
+
require "googleauth/id_tokens/verifier"
|
34
|
+
|
35
|
+
module Google
|
36
|
+
module Auth
|
37
|
+
##
|
38
|
+
# ## Verifying Google ID tokens
|
39
|
+
#
|
40
|
+
# This module verifies ID tokens issued by Google. This can be used to
|
41
|
+
# authenticate signed-in users using OpenID Connect. See
|
42
|
+
# https://developers.google.com/identity/sign-in/web/backend-auth for more
|
43
|
+
# information.
|
44
|
+
#
|
45
|
+
# ### Basic usage
|
46
|
+
#
|
47
|
+
# To verify an ID token issued by Google accounts:
|
48
|
+
#
|
49
|
+
# payload = Google::Auth::IDTokens.verify_oidc the_token,
|
50
|
+
# aud: "my-app-client-id"
|
51
|
+
#
|
52
|
+
# If verification succeeds, you will receive the token's payload as a hash.
|
53
|
+
# If verification fails, an exception (normally a subclass of
|
54
|
+
# {Google::Auth::IDTokens::VerificationError}) will be raised.
|
55
|
+
#
|
56
|
+
# To verify an ID token issued by the Google identity-aware proxy (IAP):
|
57
|
+
#
|
58
|
+
# payload = Google::Auth::IDTokens.verify_iap the_token,
|
59
|
+
# aud: "my-app-client-id"
|
60
|
+
#
|
61
|
+
# These methods will automatically download and cache the Google public
|
62
|
+
# keys necessary to verify these tokens. They will also automatically
|
63
|
+
# verify the issuer (`iss`) field for their respective types of ID tokens.
|
64
|
+
#
|
65
|
+
# ### Advanced usage
|
66
|
+
#
|
67
|
+
# If you want to provide your own public keys, either by pointing at a
|
68
|
+
# custom URI or by providing the key data directly, use the Verifier class
|
69
|
+
# and pass in a key source.
|
70
|
+
#
|
71
|
+
# To point to a custom URI that returns a JWK set:
|
72
|
+
#
|
73
|
+
# source = Google::Auth::IDTokens::JwkHttpKeySource.new "https://example.com/jwk"
|
74
|
+
# verifier = Google::Auth::IDTokens::Verifier.new key_source: source
|
75
|
+
# payload = verifier.verify the_token, aud: "my-app-client-id"
|
76
|
+
#
|
77
|
+
# To provide key data directly:
|
78
|
+
#
|
79
|
+
# jwk_data = {
|
80
|
+
# keys: [
|
81
|
+
# {
|
82
|
+
# alg: "ES256",
|
83
|
+
# crv: "P-256",
|
84
|
+
# kid: "LYyP2g",
|
85
|
+
# kty: "EC",
|
86
|
+
# use: "sig",
|
87
|
+
# x: "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU",
|
88
|
+
# y: "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI"
|
89
|
+
# }
|
90
|
+
# ]
|
91
|
+
# }
|
92
|
+
# source = Google::Auth::IDTokens::StaticKeySource.from_jwk_set jwk_data
|
93
|
+
# verifier = Google::Auth::IDTokens::Verifier key_source: source
|
94
|
+
# payload = verifier.verify the_token, aud: "my-app-client-id"
|
95
|
+
#
|
96
|
+
module IDTokens
|
97
|
+
##
|
98
|
+
# A list of issuers expected for Google OIDC-issued tokens.
|
99
|
+
#
|
100
|
+
# @return [Array<String>]
|
101
|
+
#
|
102
|
+
OIDC_ISSUERS = ["accounts.google.com", "https://accounts.google.com"].freeze
|
103
|
+
|
104
|
+
##
|
105
|
+
# A list of issuers expected for Google IAP-issued tokens.
|
106
|
+
#
|
107
|
+
# @return [Array<String>]
|
108
|
+
#
|
109
|
+
IAP_ISSUERS = ["https://cloud.google.com/iap"].freeze
|
110
|
+
|
111
|
+
##
|
112
|
+
# The URL for Google OAuth2 V3 public certs
|
113
|
+
#
|
114
|
+
# @return [String]
|
115
|
+
#
|
116
|
+
OAUTH2_V3_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs"
|
117
|
+
|
118
|
+
##
|
119
|
+
# The URL for Google IAP public keys
|
120
|
+
#
|
121
|
+
# @return [String]
|
122
|
+
#
|
123
|
+
IAP_JWK_URL = "https://www.gstatic.com/iap/verify/public_key-jwk"
|
124
|
+
|
125
|
+
class << self
|
126
|
+
##
|
127
|
+
# The key source providing public keys that can be used to verify
|
128
|
+
# ID tokens issued by Google OIDC.
|
129
|
+
#
|
130
|
+
# @return [Google::Auth::IDTokens::JwkHttpKeySource]
|
131
|
+
#
|
132
|
+
def oidc_key_source
|
133
|
+
@oidc_key_source ||= JwkHttpKeySource.new OAUTH2_V3_CERTS_URL
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# The key source providing public keys that can be used to verify
|
138
|
+
# ID tokens issued by Google IAP.
|
139
|
+
#
|
140
|
+
# @return [Google::Auth::IDTokens::JwkHttpKeySource]
|
141
|
+
#
|
142
|
+
def iap_key_source
|
143
|
+
@iap_key_source ||= JwkHttpKeySource.new IAP_JWK_URL
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Reset all convenience key sources. Used for testing.
|
148
|
+
# @private
|
149
|
+
#
|
150
|
+
def forget_sources!
|
151
|
+
@oidc_key_source = @iap_key_source = nil
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# A convenience method that verifies a token allegedly issued by Google
|
157
|
+
# OIDC.
|
158
|
+
#
|
159
|
+
# @param token [String] The ID token to verify
|
160
|
+
# @param aud [String,Array<String>,nil] The expected audience. At least
|
161
|
+
# one `aud` field in the token must match at least one of the
|
162
|
+
# provided audiences, or the verification will fail with
|
163
|
+
# {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the
|
164
|
+
# default), no audience checking is performed.
|
165
|
+
# @param azp [String,Array<String>,nil] The expected authorized party
|
166
|
+
# (azp). At least one `azp` field in the token must match at least
|
167
|
+
# one of the provided values, or the verification will fail with
|
168
|
+
# {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil`
|
169
|
+
# (the default), no azp checking is performed.
|
170
|
+
# @param aud [String,Array<String>,nil] The expected audience. At least
|
171
|
+
# one `iss` field in the token must match at least one of the
|
172
|
+
# provided issuers, or the verification will fail with
|
173
|
+
# {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer
|
174
|
+
# checking is performed. Default is to check against {OIDC_ISSUERS}.
|
175
|
+
#
|
176
|
+
# @return [Hash] The decoded token payload.
|
177
|
+
# @raise [KeySourceError] if the key source failed to obtain public keys
|
178
|
+
# @raise [VerificationError] if the token verification failed.
|
179
|
+
# Additional data may be available in the error subclass and message.
|
180
|
+
#
|
181
|
+
def verify_oidc token,
|
182
|
+
aud: nil,
|
183
|
+
azp: nil,
|
184
|
+
iss: OIDC_ISSUERS
|
185
|
+
|
186
|
+
verifier = Verifier.new key_source: oidc_key_source,
|
187
|
+
aud: aud,
|
188
|
+
azp: azp,
|
189
|
+
iss: iss
|
190
|
+
verifier.verify token
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# A convenience method that verifies a token allegedly issued by Google
|
195
|
+
# IAP.
|
196
|
+
#
|
197
|
+
# @param token [String] The ID token to verify
|
198
|
+
# @param aud [String,Array<String>,nil] The expected audience. At least
|
199
|
+
# one `aud` field in the token must match at least one of the
|
200
|
+
# provided audiences, or the verification will fail with
|
201
|
+
# {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the
|
202
|
+
# default), no audience checking is performed.
|
203
|
+
# @param azp [String,Array<String>,nil] The expected authorized party
|
204
|
+
# (azp). At least one `azp` field in the token must match at least
|
205
|
+
# one of the provided values, or the verification will fail with
|
206
|
+
# {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil`
|
207
|
+
# (the default), no azp checking is performed.
|
208
|
+
# @param aud [String,Array<String>,nil] The expected audience. At least
|
209
|
+
# one `iss` field in the token must match at least one of the
|
210
|
+
# provided issuers, or the verification will fail with
|
211
|
+
# {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer
|
212
|
+
# checking is performed. Default is to check against {IAP_ISSUERS}.
|
213
|
+
#
|
214
|
+
# @return [Hash] The decoded token payload.
|
215
|
+
# @raise [KeySourceError] if the key source failed to obtain public keys
|
216
|
+
# @raise [VerificationError] if the token verification failed.
|
217
|
+
# Additional data may be available in the error subclass and message.
|
218
|
+
#
|
219
|
+
def verify_iap token,
|
220
|
+
aud: nil,
|
221
|
+
azp: nil,
|
222
|
+
iss: IAP_ISSUERS
|
223
|
+
|
224
|
+
verifier = Verifier.new key_source: iap_key_source,
|
225
|
+
aud: aud,
|
226
|
+
azp: azp,
|
227
|
+
iss: iss
|
228
|
+
verifier.verify token
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -38,8 +38,12 @@ module Google
|
|
38
38
|
json_key = MultiJson.load json_key_io.read
|
39
39
|
raise "missing client_email" unless json_key.key? "client_email"
|
40
40
|
raise "missing private_key" unless json_key.key? "private_key"
|
41
|
-
|
42
|
-
|
41
|
+
[
|
42
|
+
json_key["private_key"],
|
43
|
+
json_key["client_email"],
|
44
|
+
json_key["project_id"],
|
45
|
+
json_key["quota_project_id"]
|
46
|
+
]
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
@@ -45,34 +45,47 @@ module Google
|
|
45
45
|
# from credentials from a json key file downloaded from the developer
|
46
46
|
# console (via 'Generate new Json Key').
|
47
47
|
#
|
48
|
-
# cf [Application Default Credentials](
|
48
|
+
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
49
49
|
class ServiceAccountCredentials < Signet::OAuth2::Client
|
50
50
|
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
|
51
51
|
extend CredentialsLoader
|
52
52
|
extend JsonKeyReader
|
53
53
|
attr_reader :project_id
|
54
|
+
attr_reader :quota_project_id
|
55
|
+
|
56
|
+
def enable_self_signed_jwt?
|
57
|
+
@enable_self_signed_jwt
|
58
|
+
end
|
54
59
|
|
55
60
|
# Creates a ServiceAccountCredentials.
|
56
61
|
#
|
57
62
|
# @param json_key_io [IO] an IO from which the JSON key can be read
|
58
63
|
# @param scope [string|array|nil] the scope(s) to access
|
59
64
|
def self.make_creds options = {}
|
60
|
-
json_key_io, scope
|
65
|
+
json_key_io, scope, enable_self_signed_jwt, target_audience, audience, token_credential_uri =
|
66
|
+
options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience,
|
67
|
+
:audience, :token_credential_uri
|
68
|
+
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
69
|
+
|
61
70
|
if json_key_io
|
62
|
-
private_key, client_email, project_id = read_json_key json_key_io
|
71
|
+
private_key, client_email, project_id, quota_project_id = read_json_key json_key_io
|
63
72
|
else
|
64
73
|
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
65
74
|
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
66
75
|
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
76
|
+
quota_project_id = nil
|
67
77
|
end
|
68
78
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
69
79
|
|
70
|
-
new(token_credential_uri: TOKEN_CRED_URI,
|
71
|
-
audience:
|
72
|
-
scope:
|
73
|
-
|
74
|
-
|
75
|
-
|
80
|
+
new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI,
|
81
|
+
audience: audience || TOKEN_CRED_URI,
|
82
|
+
scope: scope,
|
83
|
+
enable_self_signed_jwt: enable_self_signed_jwt,
|
84
|
+
target_audience: target_audience,
|
85
|
+
issuer: client_email,
|
86
|
+
signing_key: OpenSSL::PKey::RSA.new(private_key),
|
87
|
+
project_id: project_id,
|
88
|
+
quota_project_id: quota_project_id)
|
76
89
|
.configure_connection(options)
|
77
90
|
end
|
78
91
|
|
@@ -87,30 +100,36 @@ module Google
|
|
87
100
|
|
88
101
|
def initialize options = {}
|
89
102
|
@project_id = options[:project_id]
|
103
|
+
@quota_project_id = options[:quota_project_id]
|
104
|
+
@enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false
|
90
105
|
super options
|
91
106
|
end
|
92
107
|
|
93
|
-
# Extends the base class
|
94
|
-
#
|
95
|
-
# If scope(s) is not set, it creates a transient
|
96
|
-
# ServiceAccountJwtHeaderCredentials instance and uses that to
|
97
|
-
# authenticate instead.
|
108
|
+
# Extends the base class to use a transient
|
109
|
+
# ServiceAccountJwtHeaderCredentials for certain cases.
|
98
110
|
def apply! a_hash, opts = {}
|
99
|
-
# Use
|
100
|
-
|
111
|
+
# Use a self-singed JWT if there's no information that can be used to
|
112
|
+
# obtain an OAuth token, OR if there are scopes but also an assertion
|
113
|
+
# that they are default scopes that shouldn't be used to fetch a token.
|
114
|
+
if target_audience.nil? && (scope.nil? || enable_self_signed_jwt?)
|
115
|
+
apply_self_signed_jwt! a_hash
|
116
|
+
else
|
101
117
|
super
|
102
|
-
return
|
103
118
|
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
104
122
|
|
123
|
+
def apply_self_signed_jwt! a_hash
|
105
124
|
# Use the ServiceAccountJwtHeaderCredentials using the same cred values
|
106
|
-
# if no scopes are set.
|
107
125
|
cred_json = {
|
108
|
-
private_key:
|
109
|
-
client_email: @issuer
|
126
|
+
private_key: @signing_key.to_s,
|
127
|
+
client_email: @issuer,
|
128
|
+
project_id: @project_id,
|
129
|
+
quota_project_id: @quota_project_id
|
110
130
|
}
|
111
|
-
alt_clz = ServiceAccountJwtHeaderCredentials
|
112
131
|
key_io = StringIO.new MultiJson.dump(cred_json)
|
113
|
-
alt =
|
132
|
+
alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io, scope: scope
|
114
133
|
alt.apply! a_hash
|
115
134
|
end
|
116
135
|
end
|
@@ -123,7 +142,7 @@ module Google
|
|
123
142
|
# console (via 'Generate new Json Key'). It is not part of any OAuth2
|
124
143
|
# flow, rather it creates a JWT and sends that as a credential.
|
125
144
|
#
|
126
|
-
# cf [Application Default Credentials](
|
145
|
+
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
127
146
|
class ServiceAccountJwtHeaderCredentials
|
128
147
|
JWT_AUD_URI_KEY = :jwt_aud_uri
|
129
148
|
AUTH_METADATA_KEY = Signet::OAuth2::AUTH_METADATA_KEY
|
@@ -133,16 +152,15 @@ module Google
|
|
133
152
|
extend CredentialsLoader
|
134
153
|
extend JsonKeyReader
|
135
154
|
attr_reader :project_id
|
155
|
+
attr_reader :quota_project_id
|
136
156
|
|
137
|
-
#
|
157
|
+
# Create a ServiceAccountJwtHeaderCredentials.
|
138
158
|
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
def self.make_creds *args
|
145
|
-
new json_key_io: args[0][:json_key_io]
|
159
|
+
# @param json_key_io [IO] an IO from which the JSON key can be read
|
160
|
+
# @param scope [string|array|nil] the scope(s) to access
|
161
|
+
def self.make_creds options = {}
|
162
|
+
json_key_io, scope = options.values_at :json_key_io, :scope
|
163
|
+
new json_key_io: json_key_io, scope: scope
|
146
164
|
end
|
147
165
|
|
148
166
|
# Initializes a ServiceAccountJwtHeaderCredentials.
|
@@ -151,15 +169,17 @@ module Google
|
|
151
169
|
def initialize options = {}
|
152
170
|
json_key_io = options[:json_key_io]
|
153
171
|
if json_key_io
|
154
|
-
@private_key, @issuer, @project_id =
|
172
|
+
@private_key, @issuer, @project_id, @quota_project_id =
|
155
173
|
self.class.read_json_key json_key_io
|
156
174
|
else
|
157
175
|
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
158
176
|
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
159
177
|
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
178
|
+
@quota_project_id = nil
|
160
179
|
end
|
161
180
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
162
181
|
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
182
|
+
@scope = options[:scope]
|
163
183
|
end
|
164
184
|
|
165
185
|
# Construct a jwt token if the JWT_AUD_URI key is present in the input
|
@@ -168,7 +188,7 @@ module Google
|
|
168
188
|
# The jwt token is used as the value of a 'Bearer '.
|
169
189
|
def apply! a_hash, opts = {}
|
170
190
|
jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
|
171
|
-
return a_hash if jwt_aud_uri.nil?
|
191
|
+
return a_hash if jwt_aud_uri.nil? && @scope.nil?
|
172
192
|
jwt_token = new_jwt_token jwt_aud_uri, opts
|
173
193
|
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
|
174
194
|
a_hash
|
@@ -184,22 +204,27 @@ module Google
|
|
184
204
|
# Returns a reference to the #apply method, suitable for passing as
|
185
205
|
# a closure
|
186
206
|
def updater_proc
|
187
|
-
|
207
|
+
proc { |a_hash, opts = {}| apply a_hash, opts }
|
188
208
|
end
|
189
209
|
|
190
210
|
protected
|
191
211
|
|
192
212
|
# Creates a jwt uri token.
|
193
|
-
def new_jwt_token jwt_aud_uri, options = {}
|
213
|
+
def new_jwt_token jwt_aud_uri = nil, options = {}
|
194
214
|
now = Time.new
|
195
215
|
skew = options[:skew] || 60
|
196
216
|
assertion = {
|
197
217
|
"iss" => @issuer,
|
198
218
|
"sub" => @issuer,
|
199
|
-
"aud" => jwt_aud_uri,
|
200
219
|
"exp" => (now + EXPIRY).to_i,
|
201
220
|
"iat" => (now - skew).to_i
|
202
221
|
}
|
222
|
+
|
223
|
+
jwt_aud_uri = nil if @scope
|
224
|
+
|
225
|
+
assertion["scope"] = Array(@scope).join " " if @scope
|
226
|
+
assertion["aud"] = jwt_aud_uri if jwt_aud_uri
|
227
|
+
|
203
228
|
JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
|
204
229
|
end
|
205
230
|
end
|
data/lib/googleauth/signet.rb
CHANGED
@@ -48,8 +48,9 @@ module Signet
|
|
48
48
|
def apply! a_hash, opts = {}
|
49
49
|
# fetch the access token there is currently not one, or if the client
|
50
50
|
# has expired
|
51
|
-
|
52
|
-
|
51
|
+
token_type = target_audience ? :id_token : :access_token
|
52
|
+
fetch_access_token! opts if send(token_type).nil? || expires_within?(60)
|
53
|
+
a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
|
53
54
|
end
|
54
55
|
|
55
56
|
# Returns a clone of a_hash updated with the authentication token
|
@@ -62,11 +63,11 @@ module Signet
|
|
62
63
|
# Returns a reference to the #apply method, suitable for passing as
|
63
64
|
# a closure
|
64
65
|
def updater_proc
|
65
|
-
|
66
|
+
proc { |a_hash, opts = {}| apply a_hash, opts }
|
66
67
|
end
|
67
68
|
|
68
69
|
def on_refresh &block
|
69
|
-
@refresh_listeners
|
70
|
+
@refresh_listeners = [] unless defined? @refresh_listeners
|
70
71
|
@refresh_listeners << block
|
71
72
|
end
|
72
73
|
|
@@ -84,15 +85,16 @@ module Signet
|
|
84
85
|
end
|
85
86
|
|
86
87
|
def notify_refresh_listeners
|
87
|
-
listeners = @refresh_listeners
|
88
|
+
listeners = defined?(@refresh_listeners) ? @refresh_listeners : []
|
88
89
|
listeners.each do |block|
|
89
90
|
block.call self
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
93
94
|
def build_default_connection
|
94
|
-
|
95
|
-
|
95
|
+
if !defined?(@connection_info)
|
96
|
+
nil
|
97
|
+
elsif @connection_info.respond_to? :call
|
96
98
|
@connection_info.call
|
97
99
|
else
|
98
100
|
@connection_info
|
@@ -129,8 +129,8 @@ module Google
|
|
129
129
|
data = MultiJson.load saved_token
|
130
130
|
|
131
131
|
if data.fetch("client_id", @client_id.id) != @client_id.id
|
132
|
-
raise
|
133
|
-
|
132
|
+
raise format(MISMATCHED_CLIENT_ID_ERROR,
|
133
|
+
data["client_id"], @client_id.id)
|
134
134
|
end
|
135
135
|
|
136
136
|
credentials = UserRefreshCredentials.new(
|
@@ -271,10 +271,15 @@ module Google
|
|
271
271
|
# @return [String]
|
272
272
|
# Redirect URI
|
273
273
|
def redirect_uri_for base_url
|
274
|
-
return @callback_uri
|
274
|
+
return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
|
275
275
|
raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
|
276
276
|
URI.join(base_url, @callback_uri).to_s
|
277
277
|
end
|
278
|
+
|
279
|
+
# Check if URI is Google's postmessage flow (not a valid redirect_uri by spec, but allowed)
|
280
|
+
def uri_is_postmessage? uri
|
281
|
+
uri.to_s.casecmp("postmessage").zero?
|
282
|
+
end
|
278
283
|
end
|
279
284
|
end
|
280
285
|
end
|
@@ -44,7 +44,7 @@ module Google
|
|
44
44
|
# 'gcloud auth login' saves a file with these contents in well known
|
45
45
|
# location
|
46
46
|
#
|
47
|
-
# cf [Application Default Credentials](
|
47
|
+
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
48
48
|
class UserRefreshCredentials < Signet::OAuth2::Client
|
49
49
|
TOKEN_CRED_URI = "https://oauth2.googleapis.com/token".freeze
|
50
50
|
AUTHORIZATION_URI = "https://accounts.google.com/o/oauth2/auth".freeze
|
data/lib/googleauth/version.rb
CHANGED
@@ -58,12 +58,9 @@ module Google
|
|
58
58
|
# end
|
59
59
|
#
|
60
60
|
# Instead of implementing the callback directly, applications are
|
61
|
-
# encouraged to use {Google::Auth::
|
61
|
+
# encouraged to use {Google::Auth::WebUserAuthorizer::CallbackApp} instead.
|
62
62
|
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# @see {Google::Auth::AuthCallbackApp}
|
66
|
-
# @see {Google::Auth::ControllerHelpers}
|
63
|
+
# @see CallbackApp
|
67
64
|
# @note Requires sessions are enabled
|
68
65
|
class WebUserAuthorizer < Google::Auth::UserAuthorizer
|
69
66
|
STATE_PARAM = "state".freeze
|
@@ -171,7 +168,7 @@ module Google
|
|
171
168
|
options[:state] = MultiJson.dump(state.merge(
|
172
169
|
SESSION_ID_KEY => request.session[XSRF_KEY],
|
173
170
|
CURRENT_URI_KEY => redirect_to
|
174
|
-
|
171
|
+
))
|
175
172
|
options[:base_url] = request.url
|
176
173
|
super options
|
177
174
|
end
|
@@ -192,7 +189,7 @@ module Google
|
|
192
189
|
# May raise an error if an authorization code is present in the session
|
193
190
|
# and exchange of the code fails
|
194
191
|
def get_credentials user_id, request = nil, scope = nil
|
195
|
-
if request
|
192
|
+
if request&.session&.key? CALLBACK_STATE_KEY
|
196
193
|
# Note - in theory, no need to check required scope as this is
|
197
194
|
# expected to be called immediately after a return from authorization
|
198
195
|
state_json = request.session.delete CALLBACK_STATE_KEY
|
@@ -261,7 +258,7 @@ module Google
|
|
261
258
|
# Google::Auth::WebUserAuthorizer::CallbackApp.call(env)
|
262
259
|
# end
|
263
260
|
#
|
264
|
-
# @see
|
261
|
+
# @see Google::Auth::WebUserAuthorizer
|
265
262
|
class CallbackApp
|
266
263
|
LOCATION_HEADER = "Location".freeze
|
267
264
|
REDIR_STATUS = 302
|
data/lib/googleauth.rb
CHANGED
@@ -31,5 +31,6 @@ require "googleauth/application_default"
|
|
31
31
|
require "googleauth/client_id"
|
32
32
|
require "googleauth/credentials"
|
33
33
|
require "googleauth/default_credentials"
|
34
|
+
require "googleauth/id_tokens"
|
34
35
|
require "googleauth/user_authorizer"
|
35
36
|
require "googleauth/web_user_authorizer"
|