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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
# Copyright 2015 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 "os"
require "rbconfig"
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
# CredentialsLoader contains the behaviour used to locate and find default
# credentials files on the file system.
module CredentialsLoader
ENV_VAR = "GOOGLE_APPLICATION_CREDENTIALS".freeze
PRIVATE_KEY_VAR = "GOOGLE_PRIVATE_KEY".freeze
CLIENT_EMAIL_VAR = "GOOGLE_CLIENT_EMAIL".freeze
CLIENT_ID_VAR = "GOOGLE_CLIENT_ID".freeze
CLIENT_SECRET_VAR = "GOOGLE_CLIENT_SECRET".freeze
REFRESH_TOKEN_VAR = "GOOGLE_REFRESH_TOKEN".freeze
ACCOUNT_TYPE_VAR = "GOOGLE_ACCOUNT_TYPE".freeze
PROJECT_ID_VAR = "GOOGLE_PROJECT_ID".freeze
GCLOUD_POSIX_COMMAND = "gcloud".freeze
GCLOUD_WINDOWS_COMMAND = "gcloud.cmd".freeze
GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none".freeze
CREDENTIALS_FILE_NAME = "application_default_credentials.json".freeze
NOT_FOUND_ERROR = "Unable to read the credential file specified by #{ENV_VAR}".freeze
WELL_KNOWN_PATH = "gcloud/#{CREDENTIALS_FILE_NAME}".freeze
WELL_KNOWN_ERROR = "Unable to read the default credential file".freeze
SYSTEM_DEFAULT_ERROR = "Unable to read the system default credential file".freeze
CLOUD_SDK_CLIENT_ID = "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.app" \
"s.googleusercontent.com".freeze
CLOUD_SDK_CREDENTIALS_WARNING =
"Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most " \
"server applications use service accounts instead. If your application continues to use end user credentials " \
'from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about ' \
"service accounts, see https://cloud.google.com/docs/authentication/. To suppress this message, set the " \
"GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS environment variable.".freeze
# make_creds proxies the construction of a credentials instance
#
# By default, it calls #new on the current class, but this behaviour can
# be modified, allowing different instances to be created.
def make_creds *args
creds = new(*args)
creds = creds.configure_connection args[0] if creds.respond_to?(:configure_connection) && args.size == 1
creds
end
# Creates an instance from the path specified in an environment
# variable.
#
# @param scope [string|array|nil] the scope(s) to access
# @param options [Hash] Connection options. These may be used to configure
# how OAuth tokens are retrieved, by providing a suitable
# `Faraday::Connection`. For example, if a connection proxy must be
# used in the current network, you may provide a connection with
# with the needed proxy options.
# The following keys are recognized:
# * `:default_connection` The connection object to use.
# * `:connection_builder` A `Proc` that returns a connection.
def from_env scope = nil, options = {}
options = interpret_options scope, options
if ENV.key?(ENV_VAR) && !ENV[ENV_VAR].empty?
path = ENV[ENV_VAR]
raise "file #{path} does not exist" unless File.exist? path
File.open path do |f|
return make_creds options.merge(json_key_io: f)
end
elsif service_account_env_vars? || authorized_user_env_vars?
make_creds options
end
rescue StandardError => e
raise "#{NOT_FOUND_ERROR}: #{e}"
end
# Creates an instance from a well known path.
#
# @param scope [string|array|nil] the scope(s) to access
# @param options [Hash] Connection options. These may be used to configure
# how OAuth tokens are retrieved, by providing a suitable
# `Faraday::Connection`. For example, if a connection proxy must be
# used in the current network, you may provide a connection with
# with the needed proxy options.
# The following keys are recognized:
# * `:default_connection` The connection object to use.
# * `:connection_builder` A `Proc` that returns a connection.
def from_well_known_path scope = nil, options = {}
options = interpret_options scope, options
home_var = OS.windows? ? "APPDATA" : "HOME"
base = WELL_KNOWN_PATH
root = ENV[home_var].nil? ? "" : ENV[home_var]
base = File.join ".config", base unless OS.windows?
path = File.join root, base
return nil unless File.exist? path
File.open path do |f|
return make_creds options.merge(json_key_io: f)
end
rescue StandardError => e
raise "#{WELL_KNOWN_ERROR}: #{e}"
end
# Creates an instance from the system default path
#
# @param scope [string|array|nil] the scope(s) to access
# @param options [Hash] Connection options. These may be used to configure
# how OAuth tokens are retrieved, by providing a suitable
# `Faraday::Connection`. For example, if a connection proxy must be
# used in the current network, you may provide a connection with
# with the needed proxy options.
# The following keys are recognized:
# * `:default_connection` The connection object to use.
# * `:connection_builder` A `Proc` that returns a connection.
def from_system_default_path scope = nil, options = {}
options = interpret_options scope, options
if OS.windows?
return nil unless ENV["ProgramData"]
prefix = File.join ENV["ProgramData"], "Google/Auth"
else
prefix = "/etc/google/auth/"
end
path = File.join prefix, CREDENTIALS_FILE_NAME
return nil unless File.exist? path
File.open path do |f|
return make_creds options.merge(json_key_io: f)
end
rescue StandardError => e
raise "#{SYSTEM_DEFAULT_ERROR}: #{e}"
end
module_function
# Issues warning if cloud sdk client id is used
def warn_if_cloud_sdk_credentials client_id
return if ENV["GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS"]
warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID
end
# Finds project_id from gcloud CLI configuration
def load_gcloud_project_id
gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows?
gcloud = GCLOUD_POSIX_COMMAND unless OS.windows?
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", in: :close, err: :close, &:read)
config = MultiJson.load gcloud_json
config["configuration"]["properties"]["core"]["project"]
rescue StandardError
nil
end
private
def interpret_options scope, options
if scope.is_a? Hash
options = scope
scope = nil
end
return options.merge scope: scope if scope && !options[:scope]
options
end
def service_account_env_vars?
([PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR] - ENV.keys).empty? &&
!ENV.to_h.fetch_values(PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR).join(" ").empty?
end
def authorized_user_env_vars?
([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] - ENV.keys).empty? &&
!ENV.to_h.fetch_values(CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR).join(" ").empty?
end
end
end
end
|