lib/google/auth/extras/impersonated_credential.rb in googleauth-extras-0.2.1 vs lib/google/auth/extras/impersonated_credential.rb in googleauth-extras-0.3.0
- old
+ new
@@ -3,78 +3,150 @@
module Google
module Auth
module Extras
# This credential impersonates a service account.
class ImpersonatedCredential < Signet::OAuth2::Client
- class MissingScope < StandardError; end
+ include IdentityCredentialRefreshPatch
# A credential that impersonates a service account.
#
+ # The `email_address` of the service account to impersonate may be the exact
+ # same as the one represented in `base_credentials` for any desired situation
+ # but a handy usage is for going from and access token to an ID token (aka
+ # using `target_audience`).
+ #
# @param base_credentials [Hash, String, Signet::OAuth2::Client]
# Credentials to use to impersonate the provided email address.
#
# @param delegate_email_addresses [String, Array<String>]
# The list of email address if there are intermediate service accounts that
# need to be impersonated using delegation.
#
# @param email_address [String]
# Email of the service account to impersonate.
#
+ # @param include_email [Boolean]
+ # Include the service account email in the token. If set to true, the token will
+ # contain email and email_verified claims.
+ # Only supported when using a target_audience.
+ #
# @param lifetime [String]
# The desired lifetime (in seconds) of the token before needing to be refreshed.
# Defaults to 1h, adjust as needed given a refresh is automatically performed
# when the token less than 60s of remaining life and refresh requires an
# additional API call.
+ # Only supported when not using a target_audience.
#
# @param scope [String, Array<String>]
# The OAuth 2 scopes to request. Can either be formatted as a comma seperated string or array.
+ # Only supported when not using a target_audience.
#
+ # @param target_audience [String]
+ # The audience for the token, such as the API or account that this token grants access to.
+ #
# @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
+ # @see https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
# @see https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated#sa-credentials-permissions
+ # @see https://developers.google.com/identity/protocols/oauth2/scopes
#
- def initialize(email_address:, scope:, base_credentials: nil, delegate_email_addresses: nil, lifetime: nil)
- super(scope: scope)
+ def initialize(
+ email_address:,
+ base_credentials: nil,
+ delegate_email_addresses: nil,
+ include_email: nil,
+ lifetime: nil,
+ scope: nil,
+ target_audience: nil
+ )
+ super(client_id: target_audience, scope: scope, target_audience: target_audience)
- raise MissingScope if self.scope.nil? || self.scope.empty?
+ if self.target_audience.nil? || self.target_audience.empty?
+ raise(ArgumentError, 'Must provide scope or target_audience') if self.scope.nil? || self.scope.empty?
+ elsif self.scope.nil? || self.scope.empty?
+ # no-op
+ else
+ raise ArgumentError, 'Must provide scope or target_audience, not both'
+ end
@iam_credentials_service = Google::Apis::IamcredentialsV1::IAMCredentialsService.new.tap do |ics|
ics.authorization = base_credentials if base_credentials
end
@impersonate_delegates = Array(delegate_email_addresses).map do |email|
transform_email_to_name(email)
end
- @impersonate_lifetime = lifetime
+ # This is true when target_audience is passed
+ if token_type == :id_token
+ @impersonate_include_email = include_email
+ elsif !include_email.nil?
+ raise ArgumentError, 'Can only provide include_email when using target_audience'
+ end
+ # This is true when scope is passed
+ if token_type == :access_token
+ @impersonate_lifetime = lifetime
+ elsif !lifetime.nil?
+ raise ArgumentError, 'Cannot provide lifetime when using target_audience'
+ end
+
@impersonate_name = transform_email_to_name(email_address)
end
def fetch_access_token(*)
- access_token_request = Google::Apis::IamcredentialsV1::GenerateAccessTokenRequest.new(
- scope: scope,
- )
+ token_request = if token_type == :id_token
+ Google::Apis::IamcredentialsV1::GenerateIdTokenRequest.new(
+ audience: target_audience,
+ )
+ else
+ Google::Apis::IamcredentialsV1::GenerateAccessTokenRequest.new(
+ scope: scope,
+ )
+ end
# The Google SDK doesn't like nil repeated values, but be careful with others as well.
- access_token_request.delegates = @impersonate_delegates unless @impersonate_delegates.empty?
- access_token_request.lifetime = @impersonate_lifetime unless @impersonate_lifetime.nil?
+ token_request.delegates = @impersonate_delegates unless @impersonate_delegates.empty?
+ if token_type == :id_token
+ token_request.include_email = @impersonate_include_email unless @impersonate_include_email.nil?
+ else
+ token_request.lifetime = @impersonate_lifetime unless @impersonate_lifetime.nil?
+ end
- access_token_response = @iam_credentials_service.generate_service_account_access_token(@impersonate_name, access_token_request)
+ if token_type == :id_token
+ id_token_response = @iam_credentials_service.generate_service_account_id_token(@impersonate_name, token_request)
- {
- access_token: access_token_response.access_token,
- expires_at: DateTime.rfc3339(access_token_response.expire_time).to_time,
- }
+ {
+ id_token: id_token_response.token,
+ }
+ else
+ access_token_response = @iam_credentials_service.generate_service_account_access_token(@impersonate_name, token_request)
+
+ {
+ access_token: access_token_response.access_token,
+ expires_at: DateTime.rfc3339(access_token_response.expire_time).to_time,
+ }
+ end
end
def inspect
- "#<#{self.class.name}" \
- " @access_token=#{@access_token ? '[REDACTED]' : 'nil'}" \
- " @expires_at=#{expires_at.inspect}" \
- " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
- " @impersonate_lifetime=#{@impersonate_lifetime.inspect}" \
- " @impersonate_name=#{@impersonate_name.inspect}" \
- '>'
+ if token_type == :id_token
+ "#<#{self.class.name}" \
+ " @expires_at=#{expires_at.inspect}" \
+ " @id_token=#{@id_token ? '[REDACTED]' : 'nil'}" \
+ " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
+ " @impersonate_include_email=#{@impersonate_include_email.inspect}" \
+ " @impersonate_name=#{@impersonate_name.inspect}" \
+ " @target_audience=#{@target_audience.inspect}" \
+ '>'
+ else
+ "#<#{self.class.name}" \
+ " @access_token=#{@access_token ? '[REDACTED]' : 'nil'}" \
+ " @expires_at=#{expires_at.inspect}" \
+ " @impersonate_delegates=#{@impersonate_delegates.inspect}" \
+ " @impersonate_lifetime=#{@impersonate_lifetime.inspect}" \
+ " @impersonate_name=#{@impersonate_name.inspect}" \
+ '>'
+ end
end
private
def transform_email_to_name(email)