Skip to content

Commit

Permalink
Add project_id instance variable (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRoyalTnetennba committed Oct 24, 2018
1 parent 818a031 commit f0b0c6f
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 22 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
8 changes: 8 additions & 0 deletions lib/googleauth/credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -46,6 +48,7 @@ class Credentials
DEFAULT_PATHS = [].freeze

attr_accessor :client
attr_reader :project_id

# Delegate client methods to the client object.
extend Forwardable
Expand All @@ -56,19 +59,24 @@ class Credentials
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

Expand Down
28 changes: 20 additions & 8 deletions lib/googleauth/credentials_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,17 @@ module Auth
# 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 =
Expand Down Expand Up @@ -136,6 +139,15 @@ def warn_if_cloud_sdk_credentials(client_id)
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?
Expand Down
3 changes: 2 additions & 1 deletion lib/googleauth/json_key_reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def read_json_key(json_key_io)
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
Expand Down
23 changes: 15 additions & 8 deletions lib/googleauth/service_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class ServiceAccountCredentials < Signet::OAuth2::Client
TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v4/token'.freeze
extend CredentialsLoader
extend JsonKeyReader
attr_reader :project_id

# Creates a ServiceAccountCredentials.
#
Expand All @@ -58,17 +59,20 @@ class ServiceAccountCredentials < Signet::OAuth2::Client
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.
Expand All @@ -81,6 +85,7 @@ def self.unescape(str)
end

def initialize(options = {})
@project_id = options[:project_id]
super(options)
end

Expand Down Expand Up @@ -126,6 +131,7 @@ class ServiceAccountJwtHeaderCredentials
EXPIRY = 60
extend CredentialsLoader
extend JsonKeyReader
attr_reader :project_id

# make_creds proxies the construction of a credentials instance
#
Expand All @@ -144,14 +150,15 @@ def self.make_creds(*args)
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
Expand Down
7 changes: 6 additions & 1 deletion lib/googleauth/user_refresh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class UserRefreshCredentials < Signet::OAuth2::Client
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.
#
Expand All @@ -61,13 +62,15 @@ def self.make_creds(options = {})
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

Expand All @@ -86,6 +89,8 @@ def initialize(options = {})
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

Expand Down
2 changes: 1 addition & 1 deletion lib/googleauth/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 7 additions & 1 deletion spec/googleauth/credentials_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
'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

Expand Down Expand Up @@ -110,6 +111,7 @@ class TestCredentials < Google::Auth::Credentials
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
Expand Down Expand Up @@ -142,6 +144,7 @@ class TestCredentials < Google::Auth::Credentials
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
Expand Down Expand Up @@ -173,6 +176,7 @@ class TestCredentials < Google::Auth::Credentials
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
Expand Down Expand Up @@ -205,6 +209,7 @@ class TestCredentials < Google::Auth::Credentials
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
Expand Down Expand Up @@ -243,6 +248,7 @@ class TestCredentials < Google::Auth::Credentials
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
Expand Down
3 changes: 3 additions & 0 deletions spec/googleauth/get_application_default_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
require 'fakefs/safe'
require 'googleauth'
require 'spec_helper'
require 'os'

describe '#get_application_default' do
# Pass unique options each time to bypass memoization
Expand Down Expand Up @@ -173,6 +174,7 @@
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
Expand Down Expand Up @@ -260,6 +262,7 @@ def cred_json_text
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
Expand Down
51 changes: 49 additions & 2 deletions spec/googleauth/service_account_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ def expect_is_encoded_jwt(hdr)
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

Expand Down Expand Up @@ -213,6 +214,15 @@ def cred_json_text
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}\""
Expand Down Expand Up @@ -251,6 +261,19 @@ def cred_json_text
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
Expand Down Expand Up @@ -297,7 +320,8 @@ def cred_json_text
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

Expand Down Expand Up @@ -358,6 +382,16 @@ def cred_json_text
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
Expand Down Expand Up @@ -387,5 +421,18 @@ def cred_json_text
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

0 comments on commit f0b0c6f

Please sign in to comment.