googleauth-extras 0.2.0 → 0.3.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: 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.