1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
# Copyright 2023 Google, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "time"
require "googleauth/errors"
require "googleauth/external_account/base_credentials"
require "googleauth/external_account/external_account_utils"
module Google
# Module Auth provides classes that provide Google-specific authorization used to access Google APIs.
module Auth
module ExternalAccount
# This module handles the retrieval of credentials from Google Cloud by utilizing the any 3PI
# provider then exchanging the credentials for a short-lived Google Cloud access token.
class IdentityPoolCredentials
include Google::Auth::ExternalAccount::BaseCredentials
include Google::Auth::ExternalAccount::ExternalAccountUtils
extend CredentialsLoader
# Will always be nil, but method still gets used.
attr_reader :client_id
# Initialize from options map.
#
# @param [Hash] options Configuration options
# @option options [String] :audience The audience for the token
# @option options [Hash{Symbol => Object}] :credential_source A hash containing either source file or url.
# credential_source_format is either text or json to define how to parse the credential response.
# @raise [Google::Auth::InitializationError] If credential_source format is invalid, field_name is missing,
# contains ambiguous sources, or is missing required fields
#
def initialize options = {}
base_setup options
@audience = options[:audience]
@credential_source = options[:credential_source] || {}
@credential_source_file = @credential_source[:file]
@credential_source_url = @credential_source[:url]
@credential_source_headers = @credential_source[:headers] || {}
@credential_source_format = @credential_source[:format] || {}
@credential_source_format_type = @credential_source_format[:type] || "text"
validate_credential_source
end
# Implementation of BaseCredentials retrieve_subject_token!
#
# @return [String] The subject token
# @raise [Google::Auth::CredentialsError] If the token can't be parsed from JSON or is missing
def retrieve_subject_token!
content, resource_name = token_data
if @credential_source_format_type == "text"
token = content
else
begin
response_data = MultiJson.load content, symbolize_keys: true
token = response_data[@credential_source_field_name.to_sym]
rescue StandardError
raise CredentialsError, "Unable to parse subject_token from JSON resource #{resource_name} " \
"using key #{@credential_source_field_name}"
end
end
raise CredentialsError, "Missing subject_token in the credential_source file/response." unless token
token
end
private
# Validates input
#
# @raise [Google::Auth::InitializationError] If credential_source format is invalid, field_name is missing,
# contains ambiguous sources, or is missing required fields
def validate_credential_source
# `environment_id` is only supported in AWS or dedicated future external account credentials.
unless @credential_source[:environment_id].nil?
raise InitializationError, "Invalid Identity Pool credential_source field 'environment_id'"
end
unless ["json", "text"].include? @credential_source_format_type
raise InitializationError, "Invalid credential_source format #{@credential_source_format_type}"
end
# for JSON types, get the required subject_token field name.
@credential_source_field_name = @credential_source_format[:subject_token_field_name]
if @credential_source_format_type == "json" && @credential_source_field_name.nil?
raise InitializationError, "Missing subject_token_field_name for JSON credential_source format"
end
# check file or url must be fulfilled and mutually exclusiveness.
if @credential_source_file && @credential_source_url
raise InitializationError, "Ambiguous credential_source. 'file' is mutually exclusive with 'url'."
end
return unless (@credential_source_file || @credential_source_url).nil?
raise InitializationError, "Missing credential_source. A 'file' or 'url' must be provided."
end
def token_data
@credential_source_file.nil? ? url_data : file_data
end
# Reads data from a file source
#
# @return [Array(String, String)] The file content and file path
# @raise [Google::Auth::CredentialsError] If the source file doesn't exist
def file_data
unless File.exist? @credential_source_file
raise CredentialsError,
"File #{@credential_source_file} was not found."
end
content = File.read @credential_source_file, encoding: "utf-8"
[content, @credential_source_file]
end
# Fetches data from a URL source
#
# @return [Array(String, String)] The response body and URL
# @raise [Google::Auth::CredentialsError] If there's an error retrieving data from the URL
# or if the response is not successful
def url_data
begin
response = connection.get @credential_source_url do |req|
req.headers.merge! @credential_source_headers
end
rescue Faraday::Error => e
raise CredentialsError, "Error retrieving from credential url: #{e}"
end
unless response.success?
raise CredentialsError,
"Unable to retrieve Identity Pool subject token #{response.body}"
end
[response.body, @credential_source_url]
end
end
end
end
end
|