googleauth-extras 0.2.1 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad8095b3525757ef2de9dc247ff7d0c73b33934e7499bcf014b6c4195584e263
4
- data.tar.gz: d358b9edd0c828c64caade73dfdd5f005949b33701f5e6422ff46cd21f2e113f
3
+ metadata.gz: 29ad90b986d4915e183e4592f58eedff84badd6ee70900337981d3173f0c64b2
4
+ data.tar.gz: 0c69256ea700526a9891d9434338954fec778d99b6f3c927654739dd690f03dd
5
5
  SHA512:
6
- metadata.gz: 7635394f3775d86163f9da920cba3f03f94c1081dbe8ca1c402190e1cb5a5f4539cc0068e17b4386e4724786fdbc7579eb737ee6806c92f45447d005f9152a2a
7
- data.tar.gz: 07f10d26bbc5db9a065d72160b02cba39b506b49badb3826c99122ef05cbfefa76c87357f7d84ef1f5ff53f3870f65106f6d8c57d44def8375737697045bf5d2
6
+ metadata.gz: 9b3de1122f1a208f337134b30023fef63eb171c5ad3c888a8cc0cb81bc72aa5ae0d476f1c22ff659d4ce4637795ffff5ae1a5e07eee64dd68f9a5f887e9f02bb
7
+ data.tar.gz: 7eb79ba62b30a7b7a5cc2bab1d00f1079d19f57fd99df98ad52fec2d46d5e77eae781ddb50960c1832e77462e6b2e9d4c729e860db25610b2014432df6505f0d
data/.rubocop.yml CHANGED
@@ -19,12 +19,18 @@ Layout/LineLength:
19
19
  Metrics/AbcSize:
20
20
  Enabled: false
21
21
 
22
+ Metrics/CyclomaticComplexity:
23
+ Enabled: false
24
+
22
25
  Metrics/MethodLength:
23
- Max: 20
26
+ Max: 30
24
27
 
25
28
  Metrics/ParameterLists:
26
29
  Enabled: false
27
30
 
31
+ Metrics/PerceivedComplexity:
32
+ Enabled: false
33
+
28
34
  RSpec/ContextWording:
29
35
  Enabled: false
30
36
 
@@ -43,6 +49,9 @@ RSpec/MultipleMemoizedHelpers:
43
49
  RSpec/NamedSubject:
44
50
  Enabled: false
45
51
 
52
+ RSpec/NestedGroups:
53
+ Max: 5
54
+
46
55
  Style/ModuleFunction:
47
56
  EnforcedStyle: extend_self
48
57
 
data/CHANGELOG.md CHANGED
@@ -1,9 +1,21 @@
1
1
  # Release History
2
2
 
3
+ 0.4.0
4
+ ----------
5
+
6
+ - Support setting a quota project. ([#11](https://github.com/persona-id/googleauth-extras/pull/11))
7
+
8
+ - Update gemspec for new RuboCop settings. ([#12](https://github.com/persona-id/googleauth-extras/pull/12))
9
+
10
+ 0.3.0
11
+ -----
12
+
13
+ - Support impersonation with ID tokens. ([#9](https://github.com/persona-id/googleauth-extras/pull/9))
14
+
3
15
  0.2.1
4
16
  -----
5
17
 
6
- - Support signet 0.18.0 ([#7](https://github.com/persona-id/googleauth-extras/pull/7))
18
+ - Support signet 0.18.0. ([#7](https://github.com/persona-id/googleauth-extras/pull/7))
7
19
 
8
20
  0.2.0
9
21
  -----
@@ -30,9 +30,9 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
32
 
33
- spec.add_runtime_dependency 'addressable', '~> 2.8'
34
- spec.add_runtime_dependency 'faraday', '>= 1.0', '< 3.0'
35
- spec.add_runtime_dependency 'google-apis-iamcredentials_v1'
36
- spec.add_runtime_dependency 'googleauth', '~> 1.3'
37
- spec.add_runtime_dependency 'signet', '>= 0.17.0', '< 0.19.0'
33
+ spec.add_dependency 'addressable', '~> 2.8'
34
+ spec.add_dependency 'faraday', '>= 1.0', '< 3.0'
35
+ spec.add_dependency 'google-apis-iamcredentials_v1'
36
+ spec.add_dependency 'googleauth', '~> 1.3'
37
+ spec.add_dependency 'signet', '>= 0.17.0', '< 0.19.0'
38
38
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Google
4
+ module Auth
5
+ module Extras
6
+ # This module fixes an issue with ID tokens not automatically refreshing
7
+ # because their expiration is encoded in the JWT.
8
+ module IdentityCredentialRefreshPatch
9
+ def update_token!(*)
10
+ super.tap do
11
+ self.expires_at = decoded_id_token['exp'] if id_token
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,10 +5,17 @@ module Google
5
5
  module Extras
6
6
  # This credential impersonates a service account.
7
7
  class ImpersonatedCredential < Signet::OAuth2::Client
8
- class MissingScope < StandardError; end
8
+ include IdentityCredentialRefreshPatch
9
+
10
+ attr_reader :quota_project_id
9
11
 
10
12
  # A credential that impersonates a service account.
11
13
  #
14
+ # The `email_address` of the service account to impersonate may be the exact
15
+ # same as the one represented in `base_credentials` for any desired situation
16
+ # but a handy usage is for going from and access token to an ID token (aka
17
+ # using `target_audience`).
18
+ #
12
19
  # @param base_credentials [Hash, String, Signet::OAuth2::Client]
13
20
  # Credentials to use to impersonate the provided email address.
14
21
  #
@@ -19,22 +26,53 @@ module Google
19
26
  # @param email_address [String]
20
27
  # Email of the service account to impersonate.
21
28
  #
29
+ # @param include_email [Boolean]
30
+ # Include the service account email in the token. If set to true, the token will
31
+ # contain email and email_verified claims.
32
+ # Only supported when using a target_audience.
33
+ #
22
34
  # @param lifetime [String]
23
35
  # The desired lifetime (in seconds) of the token before needing to be refreshed.
24
36
  # Defaults to 1h, adjust as needed given a refresh is automatically performed
25
37
  # when the token less than 60s of remaining life and refresh requires an
26
38
  # additional API call.
39
+ # Only supported when not using a target_audience.
40
+ #
41
+ # @param quota_project_id [String]
42
+ # The project ID used for quota and billing. This project may be different from
43
+ # the project used to create the credentials.
27
44
  #
28
45
  # @param scope [String, Array<String>]
29
46
  # The OAuth 2 scopes to request. Can either be formatted as a comma seperated string or array.
47
+ # Only supported when not using a target_audience.
48
+ #
49
+ # @param target_audience [String]
50
+ # The audience for the token, such as the API or account that this token grants access to.
30
51
  #
31
52
  # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
53
+ # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
32
54
  # @see https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated#sa-credentials-permissions
55
+ # @see https://developers.google.com/identity/protocols/oauth2/scopes
33
56
  #
34
- def initialize(email_address:, scope:, base_credentials: nil, delegate_email_addresses: nil, lifetime: nil)
35
- super(scope: scope)
57
+ def initialize(
58
+ email_address:,
59
+ base_credentials: nil,
60
+ delegate_email_addresses: nil,
61
+ include_email: nil,
62
+ lifetime: nil,
63
+ quota_project_id: nil,
64
+ scope: nil,
65
+ target_audience: nil
66
+ )
67
+ super(client_id: target_audience, scope: scope, target_audience: target_audience)
36
68
 
37
- raise MissingScope if self.scope.nil? || self.scope.empty?
69
+ if self.target_audience.nil? || self.target_audience.empty?
70
+ raise(ArgumentError, 'Must provide scope or target_audience') if self.scope.nil? || self.scope.empty?
71
+ elsif self.scope.nil? || self.scope.empty?
72
+ # no-op
73
+ else
74
+ raise ArgumentError, 'Must provide scope or target_audience, not both'
75
+ end
38
76
 
39
77
  @iam_credentials_service = Google::Apis::IamcredentialsV1::IAMCredentialsService.new.tap do |ics|
40
78
  ics.authorization = base_credentials if base_credentials
@@ -44,36 +82,81 @@ module Google
44
82
  transform_email_to_name(email)
45
83
  end
46
84
 
47
- @impersonate_lifetime = lifetime
85
+ # This is true when target_audience is passed
86
+ if token_type == :id_token
87
+ @impersonate_include_email = include_email
88
+ elsif !include_email.nil?
89
+ raise ArgumentError, 'Can only provide include_email when using target_audience'
90
+ end
91
+
92
+ # This is true when scope is passed
93
+ if token_type == :access_token
94
+ @impersonate_lifetime = lifetime
95
+ elsif !lifetime.nil?
96
+ raise ArgumentError, 'Cannot provide lifetime when using target_audience'
97
+ end
48
98
 
49
99
  @impersonate_name = transform_email_to_name(email_address)
100
+
101
+ @quota_project_id = quota_project_id
50
102
  end
51
103
 
52
104
  def fetch_access_token(*)
53
- access_token_request = Google::Apis::IamcredentialsV1::GenerateAccessTokenRequest.new(
54
- scope: scope,
55
- )
105
+ token_request = if token_type == :id_token
106
+ Google::Apis::IamcredentialsV1::GenerateIdTokenRequest.new(
107
+ audience: target_audience,
108
+ )
109
+ else
110
+ Google::Apis::IamcredentialsV1::GenerateAccessTokenRequest.new(
111
+ scope: scope,
112
+ )
113
+ end
56
114
 
57
115
  # The Google SDK doesn't like nil repeated values, but be careful with others as well.
58
- access_token_request.delegates = @impersonate_delegates unless @impersonate_delegates.empty?
59
- access_token_request.lifetime = @impersonate_lifetime unless @impersonate_lifetime.nil?
116
+ token_request.delegates = @impersonate_delegates unless @impersonate_delegates.empty?
117
+ if token_type == :id_token
118
+ token_request.include_email = @impersonate_include_email unless @impersonate_include_email.nil?
119
+ else
120
+ token_request.lifetime = @impersonate_lifetime unless @impersonate_lifetime.nil?
121
+ end
60
122
 
61
- access_token_response = @iam_credentials_service.generate_service_account_access_token(@impersonate_name, access_token_request)
123
+ if token_type == :id_token
124
+ id_token_response = @iam_credentials_service.generate_service_account_id_token(@impersonate_name, token_request)
62
125
 
63
- {
64
- access_token: access_token_response.access_token,
65
- expires_at: DateTime.rfc3339(access_token_response.expire_time).to_time,
66
- }
126
+ {
127
+ id_token: id_token_response.token,
128
+ }
129
+ else
130
+ access_token_response = @iam_credentials_service.generate_service_account_access_token(@impersonate_name, token_request)
131
+
132
+ {
133
+ access_token: access_token_response.access_token,
134
+ expires_at: DateTime.rfc3339(access_token_response.expire_time).to_time,
135
+ }
136
+ end
67
137
  end
68
138
 
69
139
  def inspect
70
- "#<#{self.class.name}" \
71
- " @access_token=#{@access_token ? '[REDACTED]' : 'nil'}" \
72
- " @expires_at=#{expires_at.inspect}" \
73
- " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
74
- " @impersonate_lifetime=#{@impersonate_lifetime.inspect}" \
75
- " @impersonate_name=#{@impersonate_name.inspect}" \
76
- '>'
140
+ if token_type == :id_token
141
+ "#<#{self.class.name}" \
142
+ " @expires_at=#{expires_at.inspect}" \
143
+ " @id_token=#{@id_token ? '[REDACTED]' : 'nil'}" \
144
+ " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
145
+ " @impersonate_include_email=#{@impersonate_include_email.inspect}" \
146
+ " @impersonate_name=#{@impersonate_name.inspect}" \
147
+ " @quota_project_id=#{@quota_project_id.inspect}" \
148
+ " @target_audience=#{@target_audience.inspect}" \
149
+ '>'
150
+ else
151
+ "#<#{self.class.name}" \
152
+ " @access_token=#{@access_token ? '[REDACTED]' : 'nil'}" \
153
+ " @expires_at=#{expires_at.inspect}" \
154
+ " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
155
+ " @impersonate_lifetime=#{@impersonate_lifetime.inspect}" \
156
+ " @impersonate_name=#{@impersonate_name.inspect}" \
157
+ " @quota_project_id=#{@quota_project_id.inspect}" \
158
+ '>'
159
+ end
77
160
  end
78
161
 
79
162
  private
@@ -7,17 +7,25 @@ module Google
7
7
  class StaticCredential < Signet::OAuth2::Client
8
8
  class AuthorizationExpired < StandardError; end
9
9
 
10
+ attr_reader :quota_project_id
11
+
10
12
  # A credential using a static access token.
11
13
  #
12
14
  # @param access_token [String]
13
15
  # The access token to use.
14
16
  #
15
- def initialize(access_token:)
17
+ # @param quota_project_id [String]
18
+ # The project ID used for quota and billing. This project may be different from
19
+ # the project used to create the credentials.
20
+ #
21
+ def initialize(access_token:, quota_project_id: nil)
16
22
  super(
17
23
  access_token: access_token,
18
24
  expires_at: TokenInfo.lookup_access_token(access_token).fetch('exp'),
19
25
  issued_at: nil,
20
26
  )
27
+
28
+ @quota_project_id = quota_project_id
21
29
  end
22
30
 
23
31
  def fetch_access_token(*)
@@ -28,7 +36,11 @@ module Google
28
36
  end
29
37
 
30
38
  def inspect
31
- "#<#{self.class.name} @access_token=[REDACTED] @expires_at=#{expires_at.inspect}>"
39
+ "#<#{self.class.name}" \
40
+ ' @access_token=[REDACTED]' \
41
+ " @expires_at=#{expires_at.inspect}" \
42
+ " @quota_project_id=#{@quota_project_id.inspect}" \
43
+ '>'
32
44
  end
33
45
  end
34
46
  end
@@ -3,7 +3,7 @@
3
3
  module Google
4
4
  module Auth
5
5
  module Extras
6
- VERSION = '0.2.1'
6
+ VERSION = '0.4.0'
7
7
  end
8
8
  end
9
9
  end
@@ -4,6 +4,7 @@ require 'date'
4
4
  require 'google/apis/iamcredentials_v1'
5
5
  require 'signet/oauth_2/client'
6
6
 
7
+ require 'google/auth/extras/identity_credential_refresh_patch'
7
8
  require 'google/auth/extras/impersonated_credential'
8
9
  require 'google/auth/extras/static_credential'
9
10
  require 'google/auth/extras/token_info'
@@ -22,6 +23,11 @@ module Google
22
23
  # A credential that impersonates a service account. For usage with the
23
24
  # older style GCP Ruby SDKs from the google-apis-* gems.
24
25
  #
26
+ # The `email_address` of the service account to impersonate may be the exact
27
+ # same as the one represented in `base_credentials` for any desired situation
28
+ # but a handy usage is for going from and access token to an ID token (aka
29
+ # using `target_audience`).
30
+ #
25
31
  # @param base_credentials [Hash, String, Signet::OAuth2::Client]
26
32
  # Credentials to use to impersonate the provided email address.
27
33
  #
@@ -32,28 +38,52 @@ module Google
32
38
  # @param email_address [String]
33
39
  # Email of the service account to impersonate.
34
40
  #
41
+ # @param include_email [Boolean]
42
+ # Include the service account email in the token. If set to true, the token will
43
+ # contain email and email_verified claims.
44
+ # Only supported when using a target_audience.
45
+ #
35
46
  # @param lifetime [String]
36
47
  # The desired lifetime (in seconds) of the token before needing to be refreshed.
37
48
  # Defaults to 1h, adjust as needed given a refresh is automatically performed
38
49
  # when the token less than 60s of remaining life and refresh requires an
39
50
  # additional API call.
51
+ # Only supported when not using a target_audience.
52
+ #
53
+ # @param quota_project_id [String]
54
+ # The project ID used for quota and billing. This project may be different from
55
+ # the project used to create the credentials.
40
56
  #
41
57
  # @param scope [String, Array<String>]
42
- # The OAuth 2 scope(s) to request. Can either be formatted as a comma seperated string or array.
58
+ # The OAuth 2 scopes to request. Can either be formatted as a comma seperated string or array.
59
+ # Only supported when not using a target_audience.
43
60
  #
44
61
  # @return [Google::Auth::Extras::ImpersonatedCredential]
45
62
  #
46
63
  # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
64
+ # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
47
65
  # @see https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated#sa-credentials-permissions
48
66
  # @see https://developers.google.com/identity/protocols/oauth2/scopes
49
67
  #
50
- def impersonated_authorization(email_address:, scope:, base_credentials: nil, delegate_email_addresses: nil, lifetime: nil)
68
+ def impersonated_authorization(
69
+ email_address:,
70
+ base_credentials: nil,
71
+ delegate_email_addresses: nil,
72
+ include_email: nil,
73
+ lifetime: nil,
74
+ quota_project_id: nil,
75
+ scope: nil,
76
+ target_audience: nil
77
+ )
51
78
  ImpersonatedCredential.new(
52
79
  base_credentials: base_credentials,
53
80
  delegate_email_addresses: delegate_email_addresses,
54
81
  email_address: email_address,
82
+ include_email: include_email,
55
83
  lifetime: lifetime,
84
+ quota_project_id: quota_project_id,
56
85
  scope: scope,
86
+ target_audience: target_audience,
57
87
  )
58
88
  end
59
89
 
@@ -70,29 +100,53 @@ module Google
70
100
  # @param email_address [String]
71
101
  # Email of the service account to impersonate.
72
102
  #
103
+ # @param include_email [Boolean]
104
+ # Include the service account email in the token. If set to true, the token will
105
+ # contain email and email_verified claims.
106
+ # Only supported when using a target_audience.
107
+ #
73
108
  # @param lifetime [String]
74
109
  # The desired lifetime (in seconds) of the token before needing to be refreshed.
75
110
  # Defaults to 1h, adjust as needed given a refresh is automatically performed
76
111
  # when the token less than 60s of remaining life and refresh requires an
77
112
  # additional API call.
113
+ # Only supported when not using a target_audience.
114
+ #
115
+ # @param quota_project_id [String]
116
+ # The project ID used for quota and billing. This project may be different from
117
+ # the project used to create the credentials.
78
118
  #
79
119
  # @param scope [String, Array<String>]
80
- # The OAuth 2 scope(s) to request. Can either be formatted as a comma seperated string or array.
120
+ # The OAuth 2 scopes to request. Can either be formatted as a comma seperated string or array.
121
+ # Only supported when not using a target_audience.
81
122
  #
82
123
  # @return [Google::Auth::Credential<Google::Auth::Extras::ImpersonatedCredential>]
83
124
  #
84
125
  # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
126
+ # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
85
127
  # @see https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated#sa-credentials-permissions
86
128
  # @see https://developers.google.com/identity/protocols/oauth2/scopes
87
129
  #
88
- def impersonated_credential(email_address:, scope:, base_credentials: nil, delegate_email_addresses: nil, lifetime: nil)
130
+ def impersonated_credential(
131
+ email_address:,
132
+ base_credentials: nil,
133
+ delegate_email_addresses: nil,
134
+ include_email: nil,
135
+ lifetime: nil,
136
+ quota_project_id: nil,
137
+ scope: nil,
138
+ target_audience: nil
139
+ )
89
140
  wrap_authorization(
90
141
  impersonated_authorization(
91
142
  base_credentials: base_credentials,
92
143
  delegate_email_addresses: delegate_email_addresses,
93
144
  email_address: email_address,
145
+ include_email: include_email,
94
146
  lifetime: lifetime,
147
+ quota_project_id: quota_project_id,
95
148
  scope: scope,
149
+ target_audience: target_audience,
96
150
  ),
97
151
  )
98
152
  end
@@ -103,10 +157,15 @@ module Google
103
157
  # @param token [String]
104
158
  # The access token to use.
105
159
  #
160
+ # @param quota_project_id [String]
161
+ # The project ID used for quota and billing. This project may be different from
162
+ # the project used to create the credentials.
163
+ #
164
+ #
106
165
  # @return [Google::Auth::Extras::StaticCredential]
107
166
  #
108
- def static_authorization(token)
109
- StaticCredential.new(access_token: token)
167
+ def static_authorization(token, quota_project_id: nil)
168
+ StaticCredential.new(access_token: token, quota_project_id: quota_project_id)
110
169
  end
111
170
 
112
171
  # A credential using a static access token. For usage with the newer
@@ -115,14 +174,18 @@ module Google
115
174
  # @param token [String]
116
175
  # The access token to use.
117
176
  #
177
+ # @param quota_project_id [String]
178
+ # The project ID used for quota and billing. This project may be different from
179
+ # the project used to create the credentials.
180
+ #
118
181
  # @return [Google::Auth::Credential<Google::Auth::Extras::StaticCredential>]
119
182
  #
120
- def static_credential(token)
121
- wrap_authorization(static_authorization(token))
183
+ def static_credential(token, quota_project_id: nil)
184
+ wrap_authorization(static_authorization(token, quota_project_id: quota_project_id))
122
185
  end
123
186
 
124
187
  # Take an authorization and turn it into a credential, primarily used
125
- # for setting up both the old and new style SDK.s
188
+ # for setting up both the old and new style SDKs.
126
189
  #
127
190
  # @param client [Signet::OAuth2::Client]
128
191
  # Authorization credential to wrap.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: googleauth-extras
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Persona Identities
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-06 00:00:00.000000000 Z
11
+ date: 2024-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -113,6 +113,7 @@ files:
113
113
  - bin/setup
114
114
  - googleauth-extras.gemspec
115
115
  - lib/google/auth/extras.rb
116
+ - lib/google/auth/extras/identity_credential_refresh_patch.rb
116
117
  - lib/google/auth/extras/impersonated_credential.rb
117
118
  - lib/google/auth/extras/static_credential.rb
118
119
  - lib/google/auth/extras/token_info.rb