Add project_id instance variable (#167)

This commit is contained in:
Graham Paye 2018-10-24 09:23:37 -07:00 committed by GitHub
parent 818a031f5a
commit f0b0c6f8e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 142 additions and 22 deletions

View File

@ -1,3 +1,7 @@
## 0.7.0 (2018/10/23)
* Add project_id instance variable to UserRefreshCredentials, ServiceAccountCredentials, and Credentials.
## 0.6.7 (2018/10/16)
* Update memoist dependency to ~> 0.16.

View File

@ -27,6 +27,8 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, MethodLength
require 'forwardable'
require 'json'
require 'signet/oauth_2/client'
@ -46,6 +48,7 @@ module Google
DEFAULT_PATHS = [].freeze
attr_accessor :client
attr_reader :project_id
# Delegate client methods to the client object.
extend Forwardable
@ -56,19 +59,24 @@ module Google
def initialize(keyfile, options = {})
scope = options[:scope]
verify_keyfile_provided! keyfile
@project_id = options['project_id'] || options['project']
if keyfile.is_a? Signet::OAuth2::Client
@client = keyfile
@project_id ||= keyfile.project_id if keyfile.respond_to? :project_id
elsif keyfile.is_a? Hash
hash = stringify_hash_keys keyfile
hash['scope'] ||= scope
@client = init_client hash
@project_id ||= (hash['project_id'] || hash['project'])
else
verify_keyfile_exists! keyfile
json = JSON.parse ::File.read(keyfile)
json['scope'] ||= scope
@project_id ||= (json['project_id'] || json['project'])
@client = init_client json
end
CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
@project_id ||= CredentialsLoader.load_gcloud_project_id
@client.fetch_access_token!
end

View File

@ -39,14 +39,17 @@ module Google
# credentials files on the file system.
module CredentialsLoader
extend Memoist
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
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'.freeze
CREDENTIALS_FILE_NAME = 'application_default_credentials.json'.freeze
NOT_FOUND_ERROR =
@ -136,6 +139,15 @@ module Google
end
module_function :warn_if_cloud_sdk_credentials
def load_gcloud_project_id
gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows?
gcloud = GCLOUD_POSIX_COMMAND unless OS.windows?
config = MultiJson.load(`#{gcloud} #{GCLOUD_CONFIG_COMMAND}`)
config['configuration']['properties']['core']['project']
rescue
warn 'Unable to determine project id.'
end
private
def service_account_env_vars?

View File

@ -38,7 +38,8 @@ module Google
json_key = MultiJson.load(json_key_io.read)
raise 'missing client_email' unless json_key.key?('client_email')
raise 'missing private_key' unless json_key.key?('private_key')
[json_key['private_key'], json_key['client_email']]
project_id = json_key['project_id']
[json_key['private_key'], json_key['client_email'], project_id]
end
end
end

View File

@ -50,6 +50,7 @@ module Google
TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v4/token'.freeze
extend CredentialsLoader
extend JsonKeyReader
attr_reader :project_id
# Creates a ServiceAccountCredentials.
#
@ -58,17 +59,20 @@ module Google
def self.make_creds(options = {})
json_key_io, scope = options.values_at(:json_key_io, :scope)
if json_key_io
private_key, client_email = read_json_key(json_key_io)
private_key, client_email, project_id = read_json_key(json_key_io)
else
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
end
project_id ||= self.class.load_gcloud_project_id
new(token_credential_uri: TOKEN_CRED_URI,
audience: TOKEN_CRED_URI,
scope: scope,
issuer: client_email,
signing_key: OpenSSL::PKey::RSA.new(private_key))
signing_key: OpenSSL::PKey::RSA.new(private_key),
project_id: project_id)
end
# Handles certain escape sequences that sometimes appear in input.
@ -81,6 +85,7 @@ module Google
end
def initialize(options = {})
@project_id = options[:project_id]
super(options)
end
@ -126,6 +131,7 @@ module Google
EXPIRY = 60
extend CredentialsLoader
extend JsonKeyReader
attr_reader :project_id
# make_creds proxies the construction of a credentials instance
#
@ -144,14 +150,15 @@ module Google
def initialize(options = {})
json_key_io = options[:json_key_io]
if json_key_io
private_key, client_email = self.class.read_json_key(json_key_io)
@private_key, @issuer, @project_id =
self.class.read_json_key(json_key_io)
else
private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
end
@private_key = private_key
@issuer = client_email
@signing_key = OpenSSL::PKey::RSA.new(private_key)
@project_id ||= self.class.load_gcloud_project_id
@signing_key = OpenSSL::PKey::RSA.new(@private_key)
end
# Construct a jwt token if the JWT_AUD_URI key is present in the input

View File

@ -50,6 +50,7 @@ module Google
AUTHORIZATION_URI = 'https://accounts.google.com/o/oauth2/auth'.freeze
REVOKE_TOKEN_URI = 'https://oauth2.googleapis.com/revoke'.freeze
extend CredentialsLoader
attr_reader :project_id
# Create a UserRefreshCredentials.
#
@ -61,13 +62,15 @@ module Google
user_creds ||= {
'client_id' => ENV[CredentialsLoader::CLIENT_ID_VAR],
'client_secret' => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
'refresh_token' => ENV[CredentialsLoader::REFRESH_TOKEN_VAR]
'refresh_token' => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
'project_id' => ENV[CredentialsLoader::PROJECT_ID_VAR]
}
new(token_credential_uri: TOKEN_CRED_URI,
client_id: user_creds['client_id'],
client_secret: user_creds['client_secret'],
refresh_token: user_creds['refresh_token'],
project_id: user_creds['project_id'],
scope: scope)
end
@ -86,6 +89,8 @@ module Google
options ||= {}
options[:token_credential_uri] ||= TOKEN_CRED_URI
options[:authorization_uri] ||= AUTHORIZATION_URI
@project_id = options[:project_id]
@project_id ||= self.class.load_gcloud_project_id
super(options)
end

View File

@ -31,6 +31,6 @@ module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
VERSION = '0.6.7'.freeze
VERSION = '0.7.0'.freeze
end
end

View File

@ -40,7 +40,8 @@ describe Google::Auth::Credentials, :private do
'private_key' => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n",
'client_email' => 'credz-testabc1234567890xyz@developer.gserviceaccount.com',
'client_id' => 'credz-testabc1234567890xyz.apps.googleusercontent.com',
'type' => 'service_account'
'type' => 'service_account',
'project_id' => 'a_project_id'
}
end
@ -110,6 +111,7 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id'])
end
it 'subclasses can use PATH_ENV_VARS to get keyfile path' do
@ -142,6 +144,7 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id'])
end
it 'subclasses can use JSON_ENV_VARS to get keyfile contents' do
@ -173,6 +176,7 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id'])
end
it 'subclasses can use DEFAULT_PATHS to get keyfile path' do
@ -205,6 +209,7 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id'])
end
it 'subclasses that find no matches default to Google::Auth.get_application_default' do
@ -243,6 +248,7 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id'])
end
it 'warns when cloud sdk credentials are used' do

View File

@ -35,6 +35,7 @@ require 'faraday'
require 'fakefs/safe'
require 'googleauth'
require 'spec_helper'
require 'os'
describe '#get_application_default' do
# Pass unique options each time to bypass memoization
@ -173,6 +174,7 @@ describe '#get_application_default' do
ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret]
ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token]
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
ENV[PROJECT_ID_VAR] = 'a_project_id'
expect { Google::Auth.get_application_default @scope, options }.to output(
Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
).to_stderr
@ -260,6 +262,7 @@ describe '#get_application_default' do
end
it 'fails if env vars are set' do
ENV[ENV_VAR] = nil
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
expect do

View File

@ -116,7 +116,8 @@ describe Google::Auth::ServiceAccountCredentials do
private_key: @key.to_pem,
client_email: client_email,
client_id: 'app.apps.googleusercontent.com',
type: 'service_account'
type: 'service_account',
project_id: 'a_project_id'
}
end
@ -213,6 +214,15 @@ describe Google::Auth::ServiceAccountCredentials do
expect(@clz.from_env(@scope)).to_not be_nil
end
it 'sets project_id when the PROJECT_ID_VAR env var is set' do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
ENV[PROJECT_ID_VAR] = cred_json[:project_id]
ENV[ENV_VAR] = nil
credentials = @clz.from_env(@scope)
expect(credentials.project_id).to eq(cred_json[:project_id])
end
it 'succeeds when GOOGLE_PRIVATE_KEY is escaped' do
escaped_key = cred_json[:private_key].gsub "\n", '\n'
ENV[PRIVATE_KEY_VAR] = "\"#{escaped_key}\""
@ -251,6 +261,19 @@ describe Google::Auth::ServiceAccountCredentials do
expect(@clz.from_well_known_path(@scope)).to_not be_nil
end
end
it 'successfully sets project_id when file is present' do
Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path)
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path))
File.write(key_path, cred_json_text)
ENV['HOME'] = dir
ENV['APPDATA'] = dir
credentials = @clz.from_well_known_path(@scope)
expect(credentials.project_id).to eq(cred_json[:project_id])
end
end
end
describe '#from_system_default_path' do
@ -297,7 +320,8 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
private_key: @key.to_pem,
client_email: client_email,
client_id: 'app.apps.googleusercontent.com',
type: 'service_account'
type: 'service_account',
project_id: 'a_project_id'
}
end
@ -358,6 +382,16 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
expect(clz.from_env(@scope)).to_not be_nil
end
it 'sets project_id when the PROJECT_ID_VAR env var is set' do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
ENV[PROJECT_ID_VAR] = cred_json[:project_id]
ENV[ENV_VAR] = nil
credentials = clz.from_env(@scope)
expect(credentials).to_not be_nil
expect(credentials.project_id).to eq(cred_json[:project_id])
end
end
describe '#from_well_known_path' do
@ -387,5 +421,18 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
expect(clz.from_well_known_path).to_not be_nil
end
end
it 'successfully sets project_id when file is present' do
Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', WELL_KNOWN_PATH)
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path))
File.write(key_path, cred_json_text)
ENV['HOME'] = dir
ENV['APPDATA'] = dir
credentials = clz.from_well_known_path(@scope)
expect(credentials.project_id).to eq(cred_json[:project_id])
end
end
end
end

View File

@ -94,6 +94,7 @@ describe Google::Auth::UserRefreshCredentials do
@credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
@scope = 'https://www.googleapis.com/auth/userinfo.profile'
@clz = UserRefreshCredentials
@project_id = 'a_project_id'
end
after(:example) do
@ -140,6 +141,7 @@ describe Google::Auth::UserRefreshCredentials do
it 'succeeds when GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and '\
'GOOGLE_REFRESH_TOKEN env vars are valid' do
ENV[ENV_VAR] = nil
ENV[CLIENT_ID_VAR] = cred_json[:client_id]
ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret]
ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token]
@ -150,6 +152,17 @@ describe Google::Auth::UserRefreshCredentials do
expect(creds.client_secret).to eq(cred_json[:client_secret])
expect(creds.refresh_token).to eq(cred_json[:refresh_token])
end
it 'sets project_id when the PROJECT_ID_VAR env var is set' do
ENV[ENV_VAR] = nil
ENV[CLIENT_ID_VAR] = cred_json[:client_id]
ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret]
ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token]
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
ENV[PROJECT_ID_VAR] = @project_id
creds = @clz.from_env(@scope)
expect(creds.project_id).to eq(@project_id)
end
end
describe '#from_well_known_path' do
@ -198,6 +211,20 @@ describe Google::Auth::UserRefreshCredentials do
expect(@clz.from_well_known_path(@scope)).to_not be_nil
end
end
it 'checks gcloud config for project_id if none was provided' do
Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path)
key_path = File.join(dir, @known_path) if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path))
File.write(key_path, cred_json_text)
ENV['HOME'] = dir
ENV['APPDATA'] = dir
ENV[PROJECT_ID_VAR] = nil
expect(@clz).to receive(:load_gcloud_project_id).with(no_args)
@clz.from_well_known_path(@scope)
end
end
end
describe '#from_system_default_path' do