googleauth-extras 0.2.0 → 0.3.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: f05045744cb7032513fcf1a41cdc97a82240bebfdd02cc27c6da59eacd40ddf4
4
- data.tar.gz: 377ecac6118f2759184df44bd0b6debd707d6df4e27bc0e67b3ede14d24f08cd
3
+ metadata.gz: 56aa20519132423c5f99a9c67fb011bdf44d575ccb60d7109abd0f2f6db17a09
4
+ data.tar.gz: 6d63aaa0d4dce0256ee828233640f951af9a203a9bf56dc46640ad2994fbe05a
5
5
  SHA512:
6
- metadata.gz: 0ef2fa99d5922b5abbf5cacca3141f12b98986a5d5cb0c881c122a94e8b72286a3d63d1ba048022098d9340be49b5961e07299d632a6a174306715a8411fba18
7
- data.tar.gz: e89598d2311705994ac0f75925e3f1c2bd2a6c4f8c7473b42657ef34169d0f5837dbef6544c75ed681ddee0acc4e166686870a3df38f68687b46a4c6b568d7b8
6
+ metadata.gz: a8c1978e55c6d63353823bf771c49c6c94266963774c2db5b9eef32d23f625f5762b08f30e773f71ef897bf53f07840f9e039a5dada27bc6bf7427b17667e3c1
7
+ data.tar.gz: 78b376acc7c4bfd48b0be54127761a4ef40ec02c28029e0de2d90526116a41f43d8cfe7029b5e877480c582fc9bb46b401f3aa2b8520bc8ce8668a866e523065
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
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Release History
2
2
 
3
+ 0.3.0
4
+ -----
5
+
6
+ - Support impersonation with ID tokens. ([#9](https://github.com/persona-id/googleauth-extras/pull/9))
7
+
8
+ 0.2.1
9
+ -----
10
+
11
+ - Support signet 0.18.0. ([#7](https://github.com/persona-id/googleauth-extras/pull/7))
12
+
3
13
  0.2.0
4
14
  -----
5
15
 
@@ -34,5 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'faraday', '>= 1.0', '< 3.0'
35
35
  spec.add_runtime_dependency 'google-apis-iamcredentials_v1'
36
36
  spec.add_runtime_dependency 'googleauth', '~> 1.3'
37
- spec.add_runtime_dependency 'signet', '~> 0.17.0'
37
+ spec.add_runtime_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,15 @@ 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
9
 
10
10
  # A credential that impersonates a service account.
11
11
  #
12
+ # The `email_address` of the service account to impersonate may be the exact
13
+ # same as the one represented in `base_credentials` for any desired situation
14
+ # but a handy usage is for going from and access token to an ID token (aka
15
+ # using `target_audience`).
16
+ #
12
17
  # @param base_credentials [Hash, String, Signet::OAuth2::Client]
13
18
  # Credentials to use to impersonate the provided email address.
14
19
  #
@@ -19,22 +24,48 @@ module Google
19
24
  # @param email_address [String]
20
25
  # Email of the service account to impersonate.
21
26
  #
27
+ # @param include_email [Boolean]
28
+ # Include the service account email in the token. If set to true, the token will
29
+ # contain email and email_verified claims.
30
+ # Only supported when using a target_audience.
31
+ #
22
32
  # @param lifetime [String]
23
33
  # The desired lifetime (in seconds) of the token before needing to be refreshed.
24
34
  # Defaults to 1h, adjust as needed given a refresh is automatically performed
25
35
  # when the token less than 60s of remaining life and refresh requires an
26
36
  # additional API call.
37
+ # Only supported when not using a target_audience.
27
38
  #
28
39
  # @param scope [String, Array<String>]
29
40
  # The OAuth 2 scopes to request. Can either be formatted as a comma seperated string or array.
41
+ # Only supported when not using a target_audience.
42
+ #
43
+ # @param target_audience [String]
44
+ # The audience for the token, such as the API or account that this token grants access to.
30
45
  #
31
46
  # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
47
+ # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
32
48
  # @see https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated#sa-credentials-permissions
49
+ # @see https://developers.google.com/identity/protocols/oauth2/scopes
33
50
  #
34
- def initialize(email_address:, scope:, base_credentials: nil, delegate_email_addresses: nil, lifetime: nil)
35
- super(scope: scope)
51
+ def initialize(
52
+ email_address:,
53
+ base_credentials: nil,
54
+ delegate_email_addresses: nil,
55
+ include_email: nil,
56
+ lifetime: nil,
57
+ scope: nil,
58
+ target_audience: nil
59
+ )
60
+ super(client_id: target_audience, scope: scope, target_audience: target_audience)
36
61
 
37
- raise MissingScope if self.scope.nil? || self.scope.empty?
62
+ if self.target_audience.nil? || self.target_audience.empty?
63
+ raise(ArgumentError, 'Must provide scope or target_audience') if self.scope.nil? || self.scope.empty?
64
+ elsif self.scope.nil? || self.scope.empty?
65
+ # no-op
66
+ else
67
+ raise ArgumentError, 'Must provide scope or target_audience, not both'
68
+ end
38
69
 
39
70
  @iam_credentials_service = Google::Apis::IamcredentialsV1::IAMCredentialsService.new.tap do |ics|
40
71
  ics.authorization = base_credentials if base_credentials
@@ -44,36 +75,77 @@ module Google
44
75
  transform_email_to_name(email)
45
76
  end
46
77
 
47
- @impersonate_lifetime = lifetime
78
+ # This is true when target_audience is passed
79
+ if token_type == :id_token
80
+ @impersonate_include_email = include_email
81
+ elsif !include_email.nil?
82
+ raise ArgumentError, 'Can only provide include_email when using target_audience'
83
+ end
84
+
85
+ # This is true when scope is passed
86
+ if token_type == :access_token
87
+ @impersonate_lifetime = lifetime
88
+ elsif !lifetime.nil?
89
+ raise ArgumentError, 'Cannot provide lifetime when using target_audience'
90
+ end
48
91
 
49
92
  @impersonate_name = transform_email_to_name(email_address)
50
93
  end
51
94
 
52
95
  def fetch_access_token(*)
53
- access_token_request = Google::Apis::IamcredentialsV1::GenerateAccessTokenRequest.new(
54
- scope: scope,
55
- )
96
+ token_request = if token_type == :id_token
97
+ Google::Apis::IamcredentialsV1::GenerateIdTokenRequest.new(
98
+ audience: target_audience,
99
+ )
100
+ else
101
+ Google::Apis::IamcredentialsV1::GenerateAccessTokenRequest.new(
102
+ scope: scope,
103
+ )
104
+ end
56
105
 
57
106
  # 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?
107
+ token_request.delegates = @impersonate_delegates unless @impersonate_delegates.empty?
108
+ if token_type == :id_token
109
+ token_request.include_email = @impersonate_include_email unless @impersonate_include_email.nil?
110
+ else
111
+ token_request.lifetime = @impersonate_lifetime unless @impersonate_lifetime.nil?
112
+ end
113
+
114
+ if token_type == :id_token
115
+ id_token_response = @iam_credentials_service.generate_service_account_id_token(@impersonate_name, token_request)
60
116
 
61
- access_token_response = @iam_credentials_service.generate_service_account_access_token(@impersonate_name, access_token_request)
117
+ {
118
+ id_token: id_token_response.token,
119
+ }
120
+ else
121
+ access_token_response = @iam_credentials_service.generate_service_account_access_token(@impersonate_name, token_request)
62
122
 
63
- {
64
- access_token: access_token_response.access_token,
65
- expires_at: DateTime.rfc3339(access_token_response.expire_time).to_time,
66
- }
123
+ {
124
+ access_token: access_token_response.access_token,
125
+ expires_at: DateTime.rfc3339(access_token_response.expire_time).to_time,
126
+ }
127
+ end
67
128
  end
68
129
 
69
130
  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
- '>'
131
+ if token_type == :id_token
132
+ "#<#{self.class.name}" \
133
+ " @expires_at=#{expires_at.inspect}" \
134
+ " @id_token=#{@id_token ? '[REDACTED]' : 'nil'}" \
135
+ " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
136
+ " @impersonate_include_email=#{@impersonate_include_email.inspect}" \
137
+ " @impersonate_name=#{@impersonate_name.inspect}" \
138
+ " @target_audience=#{@target_audience.inspect}" \
139
+ '>'
140
+ else
141
+ "#<#{self.class.name}" \
142
+ " @access_token=#{@access_token ? '[REDACTED]' : 'nil'}" \
143
+ " @expires_at=#{expires_at.inspect}" \
144
+ " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
145
+ " @impersonate_lifetime=#{@impersonate_lifetime.inspect}" \
146
+ " @impersonate_name=#{@impersonate_name.inspect}" \
147
+ '>'
148
+ end
77
149
  end
78
150
 
79
151
  private
@@ -3,7 +3,7 @@
3
3
  module Google
4
4
  module Auth
5
5
  module Extras
6
- VERSION = '0.2.0'
6
+ VERSION = '0.3.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,46 @@ 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.
40
52
  #
41
53
  # @param scope [String, Array<String>]
42
- # The OAuth 2 scope(s) to request. Can either be formatted as a comma seperated string or array.
54
+ # The OAuth 2 scopes to request. Can either be formatted as a comma seperated string or array.
55
+ # Only supported when not using a target_audience.
43
56
  #
44
57
  # @return [Google::Auth::Extras::ImpersonatedCredential]
45
58
  #
46
59
  # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
60
+ # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
47
61
  # @see https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated#sa-credentials-permissions
48
62
  # @see https://developers.google.com/identity/protocols/oauth2/scopes
49
63
  #
50
- def impersonated_authorization(email_address:, scope:, base_credentials: nil, delegate_email_addresses: nil, lifetime: nil)
64
+ def impersonated_authorization(
65
+ email_address:,
66
+ base_credentials: nil,
67
+ delegate_email_addresses: nil,
68
+ include_email: nil,
69
+ lifetime: nil,
70
+ scope: nil,
71
+ target_audience: nil
72
+ )
51
73
  ImpersonatedCredential.new(
52
74
  base_credentials: base_credentials,
53
75
  delegate_email_addresses: delegate_email_addresses,
54
76
  email_address: email_address,
77
+ include_email: include_email,
55
78
  lifetime: lifetime,
56
79
  scope: scope,
80
+ target_audience: target_audience,
57
81
  )
58
82
  end
59
83
 
@@ -70,29 +94,47 @@ module Google
70
94
  # @param email_address [String]
71
95
  # Email of the service account to impersonate.
72
96
  #
97
+ # @param include_email [Boolean]
98
+ # Include the service account email in the token. If set to true, the token will
99
+ # contain email and email_verified claims.
100
+ # Only supported when using a target_audience.
101
+ #
73
102
  # @param lifetime [String]
74
103
  # The desired lifetime (in seconds) of the token before needing to be refreshed.
75
104
  # Defaults to 1h, adjust as needed given a refresh is automatically performed
76
105
  # when the token less than 60s of remaining life and refresh requires an
77
106
  # additional API call.
107
+ # Only supported when not using a target_audience.
78
108
  #
79
109
  # @param scope [String, Array<String>]
80
- # The OAuth 2 scope(s) to request. Can either be formatted as a comma seperated string or array.
110
+ # The OAuth 2 scopes to request. Can either be formatted as a comma seperated string or array.
111
+ # Only supported when not using a target_audience.
81
112
  #
82
113
  # @return [Google::Auth::Credential<Google::Auth::Extras::ImpersonatedCredential>]
83
114
  #
84
115
  # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
116
+ # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
85
117
  # @see https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated#sa-credentials-permissions
86
118
  # @see https://developers.google.com/identity/protocols/oauth2/scopes
87
119
  #
88
- def impersonated_credential(email_address:, scope:, base_credentials: nil, delegate_email_addresses: nil, lifetime: nil)
120
+ def impersonated_credential(
121
+ email_address:,
122
+ base_credentials: nil,
123
+ delegate_email_addresses: nil,
124
+ include_email: nil,
125
+ lifetime: nil,
126
+ scope: nil,
127
+ target_audience: nil
128
+ )
89
129
  wrap_authorization(
90
130
  impersonated_authorization(
91
131
  base_credentials: base_credentials,
92
132
  delegate_email_addresses: delegate_email_addresses,
93
133
  email_address: email_address,
134
+ include_email: include_email,
94
135
  lifetime: lifetime,
95
136
  scope: scope,
137
+ target_audience: target_audience,
96
138
  ),
97
139
  )
98
140
  end
@@ -122,7 +164,7 @@ module Google
122
164
  end
123
165
 
124
166
  # Take an authorization and turn it into a credential, primarily used
125
- # for setting up both the old and new style SDK.s
167
+ # for setting up both the old and new style SDKs.
126
168
  #
127
169
  # @param client [Signet::OAuth2::Client]
128
170
  # 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.0
4
+ version: 0.3.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-02-25 00:00:00.000000000 Z
11
+ date: 2024-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -76,16 +76,22 @@ dependencies:
76
76
  name: signet
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - "~>"
79
+ - - ">="
80
80
  - !ruby/object:Gem::Version
81
81
  version: 0.17.0
82
+ - - "<"
83
+ - !ruby/object:Gem::Version
84
+ version: 0.19.0
82
85
  type: :runtime
83
86
  prerelease: false
84
87
  version_requirements: !ruby/object:Gem::Requirement
85
88
  requirements:
86
- - - "~>"
89
+ - - ">="
87
90
  - !ruby/object:Gem::Version
88
91
  version: 0.17.0
92
+ - - "<"
93
+ - !ruby/object:Gem::Version
94
+ version: 0.19.0
89
95
  description:
90
96
  email:
91
97
  - alex.coomans@withpersona.com
@@ -107,6 +113,7 @@ files:
107
113
  - bin/setup
108
114
  - googleauth-extras.gemspec
109
115
  - lib/google/auth/extras.rb
116
+ - lib/google/auth/extras/identity_credential_refresh_patch.rb
110
117
  - lib/google/auth/extras/impersonated_credential.rb
111
118
  - lib/google/auth/extras/static_credential.rb
112
119
  - lib/google/auth/extras/token_info.rb
@@ -136,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
143
  - !ruby/object:Gem::Version
137
144
  version: '0'
138
145
  requirements: []
139
- rubygems_version: 3.1.6
146
+ rubygems_version: 3.4.10
140
147
  signing_key:
141
148
  specification_version: 4
142
149
  summary: Additions to the googleauth gem for unsupported authentication schemes.