Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enables passing credentials via environment variables instead of json file #28

Merged
merged 1 commit into from
Jun 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This configuration was generated by `rubocop --auto-gen-config`
# on 2015-04-23 11:18:24 -0700 using RuboCop version 0.30.0.
# on 2015-05-18 09:38:28 -0700 using RuboCop version 0.31.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -9,7 +9,17 @@
Metrics/AbcSize:
Max: 24

# Offense count: 6
# Offense count: 10
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 13

# Offense count: 1
# Cop supports --auto-correct.
Performance/ParallelAssignment:
Enabled: false

# Offense count: 1
# Cop supports --auto-correct.
Style/TrailingUnderscoreVariable:
Enabled: false
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### Changes

* Enables passing credentials via environment variables. ([@haabaato][])
[#27](https://github.com/google/google-auth-library-ruby/issues/27)

## 0.4.1 (25/04/2015)

### Changes
Expand All @@ -20,3 +25,4 @@

[@tbetbetbe]: https://github.com/tbetbetbe
[@joneslee85]: https://github.com/joneslee85
[@haabaato]: https://github.com/haabaato
32 changes: 27 additions & 5 deletions lib/googleauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,38 @@ class DefaultCredentials

# override CredentialsLoader#make_creds to use the class determined by
# loading the json.
def self.make_creds(json_key_io, scope = nil)
json_key, clz = determine_creds_class(json_key_io)
clz.new(StringIO.new(MultiJson.dump(json_key)), scope)
def self.make_creds(options = {})
json_key_io, scope = options.values_at(:json_key_io, :scope)
if json_key_io
json_key, clz = determine_creds_class(json_key_io)
clz.new(json_key_io: StringIO.new(MultiJson.dump(json_key)),
scope: scope)
else
clz = read_creds
clz.new(scope: scope)
end
end

def self.read_creds
env_var = CredentialsLoader::ACCOUNT_TYPE_VAR
type = ENV[env_var]

This comment was marked as spam.

fail "#{ACCOUNT_TYPE_VAR} is undefined in env" unless type
case type
when 'service_account'
ServiceAccountCredentials
when 'authorized_user'
UserRefreshCredentials
else
fail "credentials type '#{type}' is not supported"

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

end
end

# Reads the input json and determines which creds class to use.
def self.determine_creds_class(json_key_io)
json_key = MultiJson.load(json_key_io.read)
fail "the json is missing the #{key} field" unless json_key.key?('type')
type = json_key['type']
key = 'type'
fail "the json is missing the '#{key}' field" unless json_key.key?(key)
type = json_key[key]
case type
when 'service_account'
[json_key, ServiceAccountCredentials]
Expand Down
34 changes: 28 additions & 6 deletions lib/googleauth/credentials_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ module Auth
module CredentialsLoader
extend Memoist
ENV_VAR = 'GOOGLE_APPLICATION_CREDENTIALS'

PRIVATE_KEY_VAR = 'GOOGLE_PRIVATE_KEY'
CLIENT_EMAIL_VAR = 'GOOGLE_CLIENT_EMAIL'
CLIENT_ID_VAR = 'GOOGLE_CLIENT_ID'
CLIENT_SECRET_VAR = 'GOOGLE_CLIENT_SECRET'
REFRESH_TOKEN_VAR = 'GOOGLE_REFRESH_TOKEN'
ACCOUNT_TYPE_VAR = 'GOOGLE_ACCOUNT_TYPE'

NOT_FOUND_ERROR =
"Unable to read the credential file specified by #{ENV_VAR}"
WELL_KNOWN_PATH = 'gcloud/application_default_credentials.json'
Expand All @@ -63,11 +71,14 @@ def make_creds(*args)
#
# @param scope [string|array|nil] the scope(s) to access
def from_env(scope = nil)
return nil unless ENV.key?(ENV_VAR)
path = ENV[ENV_VAR]
fail 'file #{path} does not exist' unless File.exist?(path)
File.open(path) do |f|
return make_creds(f, scope)
if ENV.key?(ENV_VAR)
path = ENV[ENV_VAR]
fail "file #{path} does not exist" unless File.exist?(path)
File.open(path) do |f|
return make_creds(json_key_io: f, scope: scope)
end
elsif service_account_env_vars? || authorized_user_env_vars?
return make_creds(scope: scope)
end
rescue StandardError => e
raise "#{NOT_FOUND_ERROR}: #{e}"
Expand All @@ -83,11 +94,22 @@ def from_well_known_path(scope = nil)
path = File.join(root, base)
return nil unless File.exist?(path)
File.open(path) do |f|
return make_creds(f, scope)
return make_creds(json_key_io: f, scope: scope)
end
rescue StandardError => e
raise "#{WELL_KNOWN_ERROR}: #{e}"
end

private

def service_account_env_vars?
([PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR] - ENV.keys).empty?
end

def authorized_user_env_vars?
([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] -
ENV.keys).empty?
end
end
end
end
25 changes: 19 additions & 6 deletions lib/googleauth/service_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,15 @@ def self.read_json_key(json_key_io)
#
# @param json_key_io [IO] an IO from which the JSON key can be read
# @param scope [string|array|nil] the scope(s) to access
def initialize(json_key_io, scope = nil)
private_key, client_email = self.class.read_json_key(json_key_io)
def initialize(options = {})
json_key_io, scope = options.values_at(:json_key_io, :scope)
if json_key_io
private_key, client_email = self.class.read_json_key(json_key_io)
else
private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
end

super(token_credential_uri: TOKEN_CRED_URI,
audience: TOKEN_CRED_URI,
scope: scope,
Expand All @@ -90,7 +97,7 @@ def apply!(a_hash, opts = {})
client_email: @issuer
}
alt_clz = ServiceAccountJwtHeaderCredentials
alt = alt_clz.new(StringIO.new(MultiJson.dump(cred_json)))
alt = alt_clz.new(json_key_io: StringIO.new(MultiJson.dump(cred_json)))
alt.apply!(a_hash)
end
end
Expand Down Expand Up @@ -120,7 +127,7 @@ class ServiceAccountJwtHeaderCredentials
# optional scope. Here's the constructor only has one param, so
# we modify make_creds to reflect this.
def self.make_creds(*args)
new(args[0])
new(json_key_io: args[0][:json_key_io])
end

# Reads the private key and client email fields from the service account
Expand All @@ -135,8 +142,14 @@ def self.read_json_key(json_key_io)
# Initializes a ServiceAccountJwtHeaderCredentials.
#
# @param json_key_io [IO] an IO from which the JSON key can be read
def initialize(json_key_io)
private_key, client_email = self.class.read_json_key(json_key_io)
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)
else
private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
end

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@private_key = private_key
@issuer = client_email
@signing_key = OpenSSL::PKey::RSA.new(private_key)
Expand Down
11 changes: 9 additions & 2 deletions lib/googleauth/user_refresh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,15 @@ def self.read_json_key(json_key_io)
#
# @param json_key_io [IO] an IO from which the JSON key can be read
# @param scope [string|array|nil] the scope(s) to access
def initialize(json_key_io, scope = nil)
user_creds = self.class.read_json_key(json_key_io)
def initialize(options = {})
json_key_io, scope = options.values_at(:json_key_io, :scope)
user_creds = self.class.read_json_key(json_key_io) if json_key_io
user_creds ||= {
client_id: ENV[CredentialsLoader::CLIENT_ID_VAR],
client_secret: ENV[CredentialsLoader::CLIENT_SECRET_VAR],
refresh_token: ENV[CredentialsLoader::REFRESH_TOKEN_VAR]
}

super(token_credential_uri: TOKEN_CRED_URI,
client_id: user_creds['client_id'],
client_secret: user_creds['client_secret'],
Expand Down
60 changes: 45 additions & 15 deletions spec/googleauth/get_application_default_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,18 @@
describe '#get_application_default' do
before(:example) do
@key = OpenSSL::PKey::RSA.new(2048)
@var_name = CredentialsLoader::ENV_VAR
@orig = ENV[@var_name]
@var_name = ENV_VAR
@credential_vars = [
ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, CLIENT_ID_VAR,
CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR, ACCOUNT_TYPE_VAR]
@original_env_vals = {}
@credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
@home = ENV['HOME']
@scope = 'https://www.googleapis.com/auth/userinfo.profile'
end

after(:example) do
ENV[@var_name] = @orig unless @orig.nil?
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
ENV['HOME'] = @home unless @home == ENV['HOME']
end

Expand Down Expand Up @@ -95,8 +99,7 @@
it 'succeeds with default file without GOOGLE_APPLICATION_CREDENTIALS' do
ENV.delete(@var_name) unless ENV[@var_name].nil?
Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config',
CredentialsLoader::WELL_KNOWN_PATH)
key_path = File.join(dir, '.config', WELL_KNOWN_PATH)
FileUtils.mkdir_p(File.dirname(key_path))
File.write(key_path, cred_json_text)
ENV['HOME'] = dir
Expand All @@ -107,8 +110,7 @@
it 'succeeds with default file without a scope' do
ENV.delete(@var_name) unless ENV[@var_name].nil?
Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config',
CredentialsLoader::WELL_KNOWN_PATH)
key_path = File.join(dir, '.config', WELL_KNOWN_PATH)
FileUtils.mkdir_p(File.dirname(key_path))
File.write(key_path, cred_json_text)
ENV['HOME'] = dir
Expand Down Expand Up @@ -137,17 +139,31 @@
end
stubs.verify_stubbed_calls
end

it 'succeeds if environment vars are valid' do
ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
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]
expect(Google::Auth.get_application_default(@scope)).to_not be_nil
end
end

describe 'when credential type is service account' do
def cred_json_text
cred_json = {
let(:cred_json) do
{
private_key_id: 'a_private_key_id',
private_key: @key.to_pem,
client_email: 'app@developer.gserviceaccount.com',
client_id: 'app.apps.googleusercontent.com',
type: 'service_account'
}
end

def cred_json_text
MultiJson.dump(cred_json)
end

Expand All @@ -156,13 +172,16 @@ def cred_json_text
end

describe 'when credential type is authorized_user' do
def cred_json_text
cred_json = {
let(:cred_json) do
{
client_secret: 'privatekey',
refresh_token: 'refreshtoken',
client_id: 'app.apps.googleusercontent.com',
type: 'authorized_user'
}
end

def cred_json_text
MultiJson.dump(cred_json)
end

Expand All @@ -171,13 +190,16 @@ def cred_json_text
end

describe 'when credential type is unknown' do
def cred_json_text
cred_json = {
let(:cred_json) do
{
client_secret: 'privatekey',
refresh_token: 'refreshtoken',
client_id: 'app.apps.googleusercontent.com',
type: 'not_known_type'
}
end

def cred_json_text
MultiJson.dump(cred_json)
end

Expand All @@ -197,8 +219,7 @@ def cred_json_text
it 'fails if the well known file contains the creds' do
ENV.delete(@var_name) unless ENV[@var_name].nil?
Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config',
CredentialsLoader::WELL_KNOWN_PATH)
key_path = File.join(dir, '.config', WELL_KNOWN_PATH)
FileUtils.mkdir_p(File.dirname(key_path))
File.write(key_path, cred_json_text)
ENV['HOME'] = dir
Expand All @@ -208,5 +229,14 @@ def cred_json_text
expect(&blk).to raise_error RuntimeError
end
end

it 'fails if env vars are set' do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
blk = proc do
Google::Auth.get_application_default(@scope)
end
expect(&blk).to raise_error RuntimeError
end
end
end
Loading