Skip to content

Commit

Permalink
feat: Support short-lived tokens in Credentials
Browse files Browse the repository at this point in the history
* Skip Client#fetch_access_token! in Credentials constructor when token is already present.
* Add Signet::OAuth2::Client#token_type
* Add Signet::OAuth2::Client#needs_access_token?
  • Loading branch information
quartzmo committed Oct 15, 2021
1 parent c4395b8 commit 9d7051c
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 3 deletions.
2 changes: 1 addition & 1 deletion lib/googleauth/credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ def initialize keyfile, options = {}
end
CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
@project_id ||= CredentialsLoader.load_gcloud_project_id
@client.fetch_access_token!
@client.fetch_access_token! if @client.needs_access_token?
@env_vars = nil
@paths = nil
@scope = nil
Expand Down
13 changes: 11 additions & 2 deletions lib/googleauth/signet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,21 @@ def configure_connection options
self
end

# The token type as symbol, either :id_token or :access_token
def token_type
target_audience ? :id_token : :access_token
end

# Whether the id_token or access_token is missing or about to expire.
def needs_access_token?
send(token_type).nil? || expires_within?(60)
end

# Updates a_hash updated with the authentication token
def apply! a_hash, opts = {}
# fetch the access token there is currently not one, or if the client
# has expired
token_type = target_audience ? :id_token : :access_token
fetch_access_token! opts if send(token_type).nil? || expires_within?(60)
fetch_access_token! opts if needs_access_token?
a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
end

Expand Down
9 changes: 9 additions & 0 deletions spec/googleauth/credentials_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# make sure that the passed in scope propogates to the Signet object. This means
# testing the private API, which is generally frowned on.
describe Google::Auth::Credentials, :private do
let(:token) { "1/abcdef1234567890" }
let :default_keyfile_hash do
{
"private_key_id" => "testabc1234567890xyz",
Expand All @@ -34,6 +35,7 @@
def mock_signet
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:needs_access_token?).and_return(true)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id)
allow(Signet::OAuth2::Client).to receive(:new) do |options|
Expand Down Expand Up @@ -573,6 +575,7 @@ class TestCredentials19 < TestCredentials18
it "warns when cloud sdk credentials are used" do
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:needs_access_token?).and_return(true)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(Signet::OAuth2::Client).to receive(:new) do |_options|
mocked_signet
Expand All @@ -582,4 +585,10 @@ class TestCredentials19 < TestCredentials18
Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
).to_stderr
end

it "does not fetch access token when initialized with a Signet::OAuth2::Client object that already has a token" do
signet = Signet::OAuth2::Client.new access_token: token # Client#needs_access_token? will return false
creds = Google::Auth::Credentials.new signet
expect(creds.client).to eq(signet)
end
end
75 changes: 75 additions & 0 deletions spec/googleauth/signet_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,81 @@ def make_auth_stubs opts
end
end

describe "token helpers" do
let(:token) { "1/abcdef1234567890" }
before :example do
@access_token_client = Signet::OAuth2::Client.new(
access_token: token
)
@id_token_client = Signet::OAuth2::Client.new(
target_audience: "https://pubsub.googleapis.com/",
id_token: token
)
@unexpired_id_token_client = Signet::OAuth2::Client.new(
target_audience: "https://pubsub.googleapis.com/",
id_token: token,
expires_in: 3600
)
@unexpired_access_token_client = Signet::OAuth2::Client.new(
access_token: token,
expires_in: 3600
)
@expired_id_token_client = Signet::OAuth2::Client.new(
target_audience: "https://pubsub.googleapis.com/",
id_token: token,
expires_in: 30
)
@expired_access_token_client = Signet::OAuth2::Client.new(
access_token: token,
expires_in: 30
)
end

describe "#token_type" do
it "returns :access_token if target_audience is missing" do
expect(@access_token_client.token_type).to eq(:access_token)
end

it "returns :id_token if target_audience is present" do
expect(@id_token_client.token_type).to eq(:id_token)
end
end

describe "#needs_access_token?" do
it "returns true if target_audience and access_token are missing" do
expect(@client.needs_access_token?).to be true
end

it "returns true if target_audience is present and id_token is missing" do
expect(@id_client.needs_access_token?).to be true
end

it "returns true if access_token and expires_at are present and expires within 60s" do
expect(@expired_access_token_client.needs_access_token?).to be true
end

it "returns true if target_audience, id_token and expires_at are present and expires within 60s" do
expect(@expired_id_token_client.needs_access_token?).to be true
end

it "returns false if access_token is present" do
expect(@access_token_client.needs_access_token?).to be false
end

it "returns false if target_audience and id_token is present" do
expect(@id_token_client.needs_access_token?).to be false
end

it "returns false if access_token and expires_at are present and expires in more than 60s" do
expect(@unexpired_access_token_client.needs_access_token?).to be false
end

it "returns false if target_audience, id_token and expires_at are present and expires in more than 60s" do
expect(@unexpired_id_token_client.needs_access_token?).to be false
end
end
end

describe "#fetch_access_token!" do
it "retries when orig_fetch_access_token! raises Signet::RemoteServerError" do
mocked_responses = [:raise, :raise, "success"]
Expand Down

0 comments on commit 9d7051c

Please sign in to comment.