googleauth-extras 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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