googleauth-0.13.0/ 0000755 0000041 0000041 00000000000 13674353263 013775 5 ustar www-data www-data googleauth-0.13.0/COPYING 0000644 0000041 0000041 00000026116 13674353263 015036 0 ustar www-data www-data
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
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.
googleauth-0.13.0/test/ 0000755 0000041 0000041 00000000000 13674353263 014754 5 ustar www-data www-data googleauth-0.13.0/test/id_tokens/ 0000755 0000041 0000041 00000000000 13674353263 016733 5 ustar www-data www-data googleauth-0.13.0/test/id_tokens/key_sources_test.rb 0000644 0000041 0000041 00000021574 13674353263 022663 0 ustar www-data www-data # Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "helper"
require "openssl"
describe Google::Auth::IDTokens do
describe "StaticKeySource" do
let(:key1) { Google::Auth::IDTokens::KeyInfo.new id: "1234", key: :key1, algorithm: "RS256" }
let(:key2) { Google::Auth::IDTokens::KeyInfo.new id: "5678", key: :key2, algorithm: "ES256" }
let(:keys) { [key1, key2] }
let(:source) { Google::Auth::IDTokens::StaticKeySource.new keys }
it "returns a static set of keys" do
assert_equal keys, source.current_keys
end
it "does not change on refresh" do
assert_equal keys, source.refresh_keys
end
end
describe "HttpKeySource" do
let(:certs_uri) { "https://example.com/my-certs" }
let(:certs_body) { "{}" }
it "raises an error when failing to parse json from the site" do
source = Google::Auth::IDTokens::HttpKeySource.new certs_uri
stub = stub_request(:get, certs_uri).to_return(body: "whoops")
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "Unable to parse JSON", error.message
assert_requested stub
end
it "downloads data but gets no keys" do
source = Google::Auth::IDTokens::HttpKeySource.new certs_uri
stub = stub_request(:get, certs_uri).to_return(body: certs_body)
keys = source.refresh_keys
assert_empty keys
assert_requested stub
end
end
describe "X509CertHttpKeySource" do
let(:certs_uri) { "https://example.com/my-certs" }
let(:key1) { OpenSSL::PKey::RSA.new 2048 }
let(:key2) { OpenSSL::PKey::RSA.new 2048 }
let(:cert1) { generate_cert key1 }
let(:cert2) { generate_cert key2 }
let(:id1) { "1234" }
let(:id2) { "5678" }
let(:certs_body) { JSON.dump({ id1 => cert1.to_pem, id2 => cert2.to_pem }) }
after do
WebMock.reset!
end
def generate_cert key
cert = OpenSSL::X509::Certificate.new
cert.subject = cert.issuer = OpenSSL::X509::Name.parse "/C=BE/O=Test/OU=Test/CN=Test"
cert.not_before = Time.now
cert.not_after = Time.now + 365 * 24 * 60 * 60
cert.public_key = key.public_key
cert.serial = 0x0
cert.version = 2
cert.sign key, OpenSSL::Digest::SHA1.new
cert
end
it "raises an error when failing to reach the site" do
source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
stub = stub_request(:get, certs_uri).to_return(body: "whoops", status: 404)
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "Unable to retrieve data from #{certs_uri}", error.message
assert_requested stub
end
it "raises an error when failing to parse json from the site" do
source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
stub = stub_request(:get, certs_uri).to_return(body: "whoops")
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "Unable to parse JSON", error.message
assert_requested stub
end
it "raises an error when failing to parse x509 from the site" do
source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
stub = stub_request(:get, certs_uri).to_return(body: '{"hi": "whoops"}')
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "Unable to parse X509 certificates", error.message
assert_requested stub
end
it "gets the right certificates" do
source = Google::Auth::IDTokens::X509CertHttpKeySource.new certs_uri
stub = stub_request(:get, certs_uri).to_return(body: certs_body)
keys = source.refresh_keys
assert_equal id1, keys[0].id
assert_equal id2, keys[1].id
assert_equal key1.public_key.to_pem, keys[0].key.to_pem
assert_equal key2.public_key.to_pem, keys[1].key.to_pem
assert_equal "RS256", keys[0].algorithm
assert_equal "RS256", keys[1].algorithm
assert_requested stub
end
end
describe "JwkHttpKeySource" do
let(:jwk_uri) { "https://example.com/my-jwk" }
let(:id1) { "fb8ca5b7d8d9a5c6c6788071e866c6c40f3fc1f9" }
let(:id2) { "LYyP2g" }
let(:jwk1) {
{
alg: "RS256",
e: "AQAB",
kid: id1,
kty: "RSA",
n: "zK8PHf_6V3G5rU-viUOL1HvAYn7q--dxMoUkt7x1rSWX6fimla-lpoYAKhFTLU" \
"ELkRKy_6UDzfybz0P9eItqS2UxVWYpKYmKTQ08HgUBUde4GtO_B0SkSk8iLtGh" \
"653UBBjgXmfzdfQEz_DsaWn7BMtuAhY9hpMtJye8LQlwaS8ibQrsC0j0GZM5KX" \
"RITHwfx06_T1qqC_MOZRA6iJs-J2HNlgeyFuoQVBTY6pRqGXa-qaVsSG3iU-vq" \
"NIciFquIq-xydwxLqZNksRRer5VAsSHf0eD3g2DX-cf6paSy1aM40svO9EfSvG" \
"_07MuHafEE44RFvSZZ4ubEN9U7ALSjdw",
use: "sig"
}
}
let(:jwk2) {
{
alg: "ES256",
crv: "P-256",
kid: id2,
kty: "EC",
use: "sig",
x: "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU",
y: "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI"
}
}
let(:bad_type_jwk) {
{
alg: "RS256",
kid: "hello",
kty: "blah",
use: "sig"
}
}
let(:jwk_body) { JSON.dump({ keys: [jwk1, jwk2] }) }
let(:bad_type_body) { JSON.dump({ keys: [bad_type_jwk] }) }
after do
WebMock.reset!
end
it "raises an error when failing to reach the site" do
source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
stub = stub_request(:get, jwk_uri).to_return(body: "whoops", status: 404)
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "Unable to retrieve data from #{jwk_uri}", error.message
assert_requested stub
end
it "raises an error when failing to parse json from the site" do
source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
stub = stub_request(:get, jwk_uri).to_return(body: "whoops")
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "Unable to parse JSON", error.message
assert_requested stub
end
it "raises an error when the json structure is malformed" do
source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
stub = stub_request(:get, jwk_uri).to_return(body: '{"hi": "whoops"}')
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "No keys found in jwk set", error.message
assert_requested stub
end
it "raises an error when an unrecognized key type is encountered" do
source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
stub = stub_request(:get, jwk_uri).to_return(body: bad_type_body)
error = assert_raises Google::Auth::IDTokens::KeySourceError do
source.refresh_keys
end
assert_equal "Cannot use key type blah", error.message
assert_requested stub
end
it "gets the right keys" do
source = Google::Auth::IDTokens::JwkHttpKeySource.new jwk_uri
stub = stub_request(:get, jwk_uri).to_return(body: jwk_body)
keys = source.refresh_keys
assert_equal id1, keys[0].id
assert_equal id2, keys[1].id
assert_kind_of OpenSSL::PKey::RSA, keys[0].key
assert_kind_of OpenSSL::PKey::EC, keys[1].key
assert_equal "RS256", keys[0].algorithm
assert_equal "ES256", keys[1].algorithm
assert_requested stub
end
end
end
googleauth-0.13.0/test/id_tokens/verifier_test.rb 0000644 0000041 0000041 00000026751 13674353263 022145 0 ustar www-data www-data # Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "helper"
describe Google::Auth::IDTokens::Verifier do
describe "verify_oidc" do
let(:oidc_token) {
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjQ5MjcxMGE3ZmNkYjE1Mzk2MGNlMDFmNzYwNTIwY" \
"TMyYzg0NTVkZmYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwOi8vZXhhbXBsZS5jb20" \
"iLCJhenAiOiI1NDIzMzkzNTc2MzgtY3IwZHNlcnIyZXZnN3N2MW1lZ2hxZXU3MDMyNzRm" \
"M2hAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbCI6IjU0MjMzOTM1N" \
"zYzOC1jcjBkc2VycjJldmc3c3YxbWVnaHFldTcwMzI3NGYzaEBkZXZlbG9wZXIuZ3Nlcn" \
"ZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1OTEzNDI" \
"3NzYsImlhdCI6MTU5MTMzOTE3NiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUu" \
"Y29tIiwic3ViIjoiMTA0MzQxNDczMTMxODI1OTU3NjAzIn0.GGDE_5HoLacyqdufdxnAC" \
"rXxYySKQYAzSQ5qfGjSUriuO3uLm2-rwSPFfLzzBeflEHdVX7XRFFszpxKajuZklF4dXd" \
"0evB1u5i3QeCJ8MSZKKx6qus_ETJv4rtuPNEuyhaRcShB7BwI8RY0IZ4_EDrhYqYInrO2" \
"wQyJGYvc41JcmoKzRoNnEVydN0Qppt9bqevq_lJg-9UjJkJ2QHjPfTgMjwhLIgNptKgtR" \
"qdoRpJmleFlbuUqyPPJfAzv3Tc6h3kw88tEcI8R3n04xmHOSMwERFFQYJdQDMd2F9SSDe" \
"rh40codO_GuPZ7bEUiKq9Lkx2LH5TuhythfsMzIwJpaEA"
}
let(:oidc_jwk_body) {
<<~JWK
{
"keys": [
{
"kid": "fb8ca5b7d8d9a5c6c6788071e866c6c40f3fc1f9",
"e": "AQAB",
"alg": "RS256",
"use": "sig",
"n": "zK8PHf_6V3G5rU-viUOL1HvAYn7q--dxMoUkt7x1rSWX6fimla-lpoYAKhFTLUELkRKy_6UDzfybz0P9eItqS2UxVWYpKYmKTQ08HgUBUde4GtO_B0SkSk8iLtGh653UBBjgXmfzdfQEz_DsaWn7BMtuAhY9hpMtJye8LQlwaS8ibQrsC0j0GZM5KXRITHwfx06_T1qqC_MOZRA6iJs-J2HNlgeyFuoQVBTY6pRqGXa-qaVsSG3iU-vqNIciFquIq-xydwxLqZNksRRer5VAsSHf0eD3g2DX-cf6paSy1aM40svO9EfSvG_07MuHafEE44RFvSZZ4ubEN9U7ALSjdw",
"kty": "RSA"
},
{
"kty": "RSA",
"kid": "492710a7fcdb153960ce01f760520a32c8455dff",
"e": "AQAB",
"alg": "RS256",
"use": "sig",
"n": "wl6TaY_3dsuLczYH_hioeQ5JjcLKLGYb--WImN9_IKMkOj49dgs25wkjsdI9XGJYhhPJLlvfjIfXH49ZGA_XKLx7fggNaBRZcj1y-I3_77tVa9N7An5JLq3HT9XVt0PNTq0mtX009z1Hva4IWZ5IhENx2rWlZOfFAXiMUqhnDc8VY3lG7vr8_VG3cw3XRKvlZQKbb6p2YIMFsUwaDGL2tVF4SkxpxIazUYfOY5lijyVugNTslOBhlEMq_43MZlkznSrbFx8ToQ2bQX4Shj-r9pLyofbo6A7K9mgWnQXGY5rQVLPYYRzUg0ThWDzwHdgxYC5MNxKyQH4RC2LPv3U0LQ"
}
]
}
JWK
}
let(:expected_aud) { "http://example.com" }
let(:expected_azp) { "542339357638-cr0dserr2evg7sv1meghqeu703274f3h@developer.gserviceaccount.com" }
let(:unexpired_test_time) { Time.at 1591339181 }
let(:expired_test_time) { unexpired_test_time + 86400 }
after do
WebMock.reset!
Google::Auth::IDTokens.forget_sources!
end
it "verifies a good token with iss, aud, and azp checks" do
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
Time.stub :now, unexpired_test_time do
Google::Auth::IDTokens.verify_oidc oidc_token, aud: expected_aud, azp: expected_azp
end
end
it "fails to verify a bad token" do
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::SignatureError do
Google::Auth::IDTokens.verify_oidc "#{oidc_token}x"
end
end
end
it "fails to verify a token with the wrong aud" do
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::AudienceMismatchError do
Google::Auth::IDTokens.verify_oidc oidc_token, aud: ["hello", "world"]
end
end
end
it "fails to verify a token with the wrong azp" do
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::AuthorizedPartyMismatchError do
Google::Auth::IDTokens.verify_oidc oidc_token, azp: "hello"
end
end
end
it "fails to verify a token with the wrong issuer" do
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::IssuerMismatchError do
Google::Auth::IDTokens.verify_oidc oidc_token, iss: "hello"
end
end
end
it "fails to verify an expired token" do
stub_request(:get, Google::Auth::IDTokens::OAUTH2_V3_CERTS_URL).to_return(body: oidc_jwk_body)
Time.stub :now, expired_test_time do
assert_raises Google::Auth::IDTokens::ExpiredTokenError do
Google::Auth::IDTokens.verify_oidc oidc_token
end
end
end
end
describe "verify_iap" do
let(:iap_token) {
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjBvZUxjUSJ9.eyJhdWQiOiIvcH" \
"JvamVjdHMvNjUyNTYyNzc2Nzk4L2FwcHMvY2xvdWQtc2FtcGxlcy10ZXN0cy1waHAtaWFwI" \
"iwiZW1haWwiOiJkYXp1bWFAZ29vZ2xlLmNvbSIsImV4cCI6MTU5MTMzNTcyNCwiZ29vZ2xl" \
"Ijp7ImFjY2Vzc19sZXZlbHMiOlsiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2FjY2V" \
"zc0xldmVscy9yZWNlbnRTZWN1cmVDb25uZWN0RGF0YSIsImFjY2Vzc1BvbGljaWVzLzUxOD" \
"U1MTI4MDkyNC9hY2Nlc3NMZXZlbHMvdGVzdE5vT3AiLCJhY2Nlc3NQb2xpY2llcy81MTg1N" \
"TEyODA5MjQvYWNjZXNzTGV2ZWxzL2V2YXBvcmF0aW9uUWFEYXRhRnVsbHlUcnVzdGVkIiwi" \
"YWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2FjY2Vzc0xldmVscy9jYWFfZGlzYWJsZWQ" \
"iLCJhY2Nlc3NQb2xpY2llcy81MTg1NTEyODA5MjQvYWNjZXNzTGV2ZWxzL3JlY2VudE5vbk" \
"1vYmlsZVNlY3VyZUNvbm5lY3REYXRhIiwiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L" \
"2FjY2Vzc0xldmVscy9jb25jb3JkIiwiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2Fj" \
"Y2Vzc0xldmVscy9mdWxseVRydXN0ZWRfY2FuYXJ5RGF0YSIsImFjY2Vzc1BvbGljaWVzLzU" \
"xODU1MTI4MDkyNC9hY2Nlc3NMZXZlbHMvZnVsbHlUcnVzdGVkX3Byb2REYXRhIl19LCJoZC" \
"I6Imdvb2dsZS5jb20iLCJpYXQiOjE1OTEzMzUxMjQsImlzcyI6Imh0dHBzOi8vY2xvdWQuZ" \
"29vZ2xlLmNvbS9pYXAiLCJzdWIiOiJhY2NvdW50cy5nb29nbGUuY29tOjExMzc3OTI1ODA4" \
"MTE5ODAwNDY5NCJ9.2BlagZOoonmX35rNY-KPbONiVzFAdNXKRGkX45uGFXeHryjKgv--K6" \
"siL8syeCFXzHvgmWpJk31sEt4YLxPKvQ"
}
let(:iap_jwk_body) {
<<~JWK
{
"keys" : [
{
"alg" : "ES256",
"crv" : "P-256",
"kid" : "LYyP2g",
"kty" : "EC",
"use" : "sig",
"x" : "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU",
"y" : "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI"
},
{
"alg" : "ES256",
"crv" : "P-256",
"kid" : "mpf0DA",
"kty" : "EC",
"use" : "sig",
"x" : "fHEdeT3a6KaC1kbwov73ZwB_SiUHEyKQwUUtMCEn0aI",
"y" : "QWOjwPhInNuPlqjxLQyhveXpWqOFcQPhZ3t-koMNbZI"
},
{
"alg" : "ES256",
"crv" : "P-256",
"kid" : "b9vTLA",
"kty" : "EC",
"use" : "sig",
"x" : "qCByTAvci-jRAD7uQSEhTdOs8iA714IbcY2L--YzynI",
"y" : "WQY0uCoQyPSozWKGQ0anmFeOH5JNXiZa9i6SNqOcm7w"
},
{
"alg" : "ES256",
"crv" : "P-256",
"kid" : "0oeLcQ",
"kty" : "EC",
"use" : "sig",
"x" : "MdhRXGEoGJLtBjQEIjnYLPkeci9rXnca2TffkI0Kac0",
"y" : "9BoREHfX7g5OK8ELpA_4RcOnFCGSjfR4SGZpBo7juEY"
},
{
"alg" : "ES256",
"crv" : "P-256",
"kid" : "g5X6ig",
"kty" : "EC",
"use" : "sig",
"x" : "115LSuaFVzVROJiGfdPN1kT14Hv3P4RIjthfslZ010s",
"y" : "-FAaRtO4yvrN4uJ89xwGWOEJcSwpLmFOtb0SDJxEAuc"
}
]
}
JWK
}
let(:expected_aud) { "/projects/652562776798/apps/cloud-samples-tests-php-iap" }
let(:unexpired_test_time) { Time.at 1591335143 }
let(:expired_test_time) { unexpired_test_time + 86400 }
after do
WebMock.reset!
Google::Auth::IDTokens.forget_sources!
end
it "verifies a good token with iss and aud checks" do
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
Time.stub :now, unexpired_test_time do
Google::Auth::IDTokens.verify_iap iap_token, aud: expected_aud
end
end
it "fails to verify a bad token" do
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::SignatureError do
Google::Auth::IDTokens.verify_iap "#{iap_token}x"
end
end
end
it "fails to verify a token with the wrong aud" do
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::AudienceMismatchError do
Google::Auth::IDTokens.verify_iap iap_token, aud: ["hello", "world"]
end
end
end
it "fails to verify a token with the wrong azp" do
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::AuthorizedPartyMismatchError do
Google::Auth::IDTokens.verify_iap iap_token, azp: "hello"
end
end
end
it "fails to verify a token with the wrong issuer" do
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
Time.stub :now, unexpired_test_time do
assert_raises Google::Auth::IDTokens::IssuerMismatchError do
Google::Auth::IDTokens.verify_iap iap_token, iss: "hello"
end
end
end
it "fails to verify an expired token" do
stub_request(:get, Google::Auth::IDTokens::IAP_JWK_URL).to_return(body: iap_jwk_body)
Time.stub :now, expired_test_time do
assert_raises Google::Auth::IDTokens::ExpiredTokenError do
Google::Auth::IDTokens.verify_iap iap_token
end
end
end
end
end
googleauth-0.13.0/test/helper.rb 0000644 0000041 0000041 00000003105 13674353263 016557 0 ustar www-data www-data # Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "minitest/autorun"
require "minitest/focus"
require "webmock/minitest"
require "googleauth"
googleauth-0.13.0/.rspec 0000644 0000041 0000041 00000000040 13674353263 015104 0 ustar www-data www-data --colour
--format documentation
googleauth-0.13.0/README.md 0000644 0000041 0000041 00000014741 13674353263 015263 0 ustar www-data www-data # Google Auth Library for Ruby
- Homepage
- http://www.github.com/googleapis/google-auth-library-ruby
- Authors
- Tim Emiola
- Copyright
- Copyright © 2015 Google, Inc.
- License
- Apache 2.0
[](http://badge.fury.io/rb/googleauth)
## Description
This is Google's officially supported ruby client library for using OAuth 2.0
authorization and authentication with Google APIs.
## Alpha
This library is in Alpha. We will make an effort to support the library, but
we reserve the right to make incompatible changes when necessary.
## Install
Be sure `https://rubygems.org/` is in your gem sources.
For normal client usage, this is sufficient:
```bash
$ gem install googleauth
```
## Example Usage
```ruby
require 'googleauth'
# Get the environment configured authorization
scopes = ['https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/compute']
authorization = Google::Auth.get_application_default(scopes)
# Add the the access token obtained using the authorization to a hash, e.g
# headers.
some_headers = {}
authorization.apply(some_headers)
```
## Application Default Credentials
This library provides an implementation of
[application default credentials][application default credentials] for Ruby.
The Application Default Credentials provide a simple way to get authorization
credentials for use in calling Google APIs.
They are best suited for cases when the call needs to have the same identity
and authorization level for the application independent of the user. This is
the recommended approach to authorize calls to Cloud APIs, particularly when
you're building an application that uses Google Compute Engine.
## User Credentials
The library also provides support for requesting and storing user
credentials (3-Legged OAuth2.) Two implementations are currently available,
a generic authorizer useful for command line apps or custom integrations as
well as a web variant tailored toward Rack-based applications.
The authorizers are intended for authorization use cases. For sign-on,
see [Google Identity Platform](https://developers.google.com/identity/)
### Example (Web)
```ruby
require 'googleauth'
require 'googleauth/web_user_authorizer'
require 'googleauth/stores/redis_token_store'
require 'redis'
client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json')
scope = ['https://www.googleapis.com/auth/drive']
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
authorizer = Google::Auth::WebUserAuthorizer.new(
client_id, scope, token_store, '/oauth2callback')
get('/authorize') do
# NOTE: Assumes the user is already authenticated to the app
user_id = request.session['user_id']
credentials = authorizer.get_credentials(user_id, request)
if credentials.nil?
redirect authorizer.get_authorization_url(login_hint: user_id, request: request)
end
# Credentials are valid, can call APIs
# ...
end
get('/oauth2callback') do
target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(
request)
redirect target_url
end
```
### Example (Command Line)
```ruby
require 'googleauth'
require 'googleauth/stores/file_token_store'
OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'
scope = 'https://www.googleapis.com/auth/drive'
client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json')
token_store = Google::Auth::Stores::FileTokenStore.new(
:file => '/path/to/tokens.yaml')
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url(base_url: OOB_URI )
puts "Open #{url} in your browser and enter the resulting code:"
code = gets
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI)
end
# OK to use credentials
```
### Example (Service Account)
```ruby
scope = 'https://www.googleapis.com/auth/androidpublisher'
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open('/path/to/service_account_json_key.json'),
scope: scope)
authorizer.fetch_access_token!
```
### Example (Environment Variables)
```bash
export GOOGLE_ACCOUNT_TYPE=service_account
export GOOGLE_CLIENT_ID=000000000000000000000
export GOOGLE_CLIENT_EMAIL=xxxx@xxxx.iam.gserviceaccount.com
export GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
```
```ruby
require 'googleauth'
require 'google/apis/drive_v3'
Drive = ::Google::Apis::DriveV3
drive = Drive::DriveService.new
# Auths with ENV vars:
# "GOOGLE_CLIENT_ID",
# "GOOGLE_CLIENT_EMAIL",
# "GOOGLE_ACCOUNT_TYPE",
# "GOOGLE_PRIVATE_KEY"
auth = ::Google::Auth::ServiceAccountCredentials
.make_creds(scope: 'https://www.googleapis.com/auth/drive')
drive.authorization = auth
list_files = drive.list_files()
```
### Storage
Authorizers require a storage instance to manage long term persistence of
access and refresh tokens. Two storage implementations are included:
* Google::Auth::Stores::FileTokenStore
* Google::Auth::Stores::RedisTokenStore
Custom storage implementations can also be used. See
[token_store.rb](https://googleapis.dev/ruby/googleauth/latest/Google/Auth/TokenStore.html) for additional details.
## Supported Ruby Versions
This library requires Ruby 2.4 or later.
In general, this library supports Ruby versions that are considered current and
supported by Ruby Core (that is, Ruby versions that are either in normal
maintenance or in security maintenance).
See https://www.ruby-lang.org/en/downloads/branches/ for further details.
## License
This library is licensed under Apache 2.0. Full license text is
available in [COPYING][copying].
## Contributing
See [CONTRIBUTING][contributing].
## Support
Please
[report bugs at the project on Github](https://github.com/google/google-auth-library-ruby/issues). Don't
hesitate to
[ask questions](http://stackoverflow.com/questions/tagged/google-auth-library-ruby)
about the client or APIs on [StackOverflow](http://stackoverflow.com).
[application default credentials]: https://developers.google.com/accounts/docs/application-default-credentials
[contributing]: https://github.com/googleapis/google-auth-library-ruby/tree/master/.github/CONTRIBUTING.md
[copying]: https://github.com/googleapis/google-auth-library-ruby/tree/master/COPYING
googleauth-0.13.0/spec/ 0000755 0000041 0000041 00000000000 13674353263 014727 5 ustar www-data www-data googleauth-0.13.0/spec/googleauth/ 0000755 0000041 0000041 00000000000 13674353263 017065 5 ustar www-data www-data googleauth-0.13.0/spec/googleauth/iam_spec.rb 0000644 0000041 0000041 00000006170 13674353263 021176 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "googleauth/iam"
describe Google::Auth::IAMCredentials do
IAMCredentials = Google::Auth::IAMCredentials
let(:test_selector) { "the-test-selector" }
let(:test_token) { "the-test-token" }
let(:test_creds) { IAMCredentials.new test_selector, test_token }
describe "#apply!" do
it "should update the target hash with the iam values" do
md = { foo: "bar" }
test_creds.apply! md
expect(md[IAMCredentials::SELECTOR_KEY]).to eq test_selector
expect(md[IAMCredentials::TOKEN_KEY]).to eq test_token
expect(md[:foo]).to eq "bar"
end
end
describe "updater_proc" do
it "should provide a proc that updates a hash with the iam values" do
md = { foo: "bar" }
the_proc = test_creds.updater_proc
got = the_proc.call md
expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector
expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token
expect(got[:foo]).to eq "bar"
end
end
describe "#apply" do
it "should not update the original hash with the iam values" do
md = { foo: "bar" }
test_creds.apply md
expect(md[IAMCredentials::SELECTOR_KEY]).to be_nil
expect(md[IAMCredentials::TOKEN_KEY]).to be_nil
expect(md[:foo]).to eq "bar"
end
it "should return a with the iam values" do
md = { foo: "bar" }
got = test_creds.apply md
expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector
expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token
expect(got[:foo]).to eq "bar"
end
end
end
googleauth-0.13.0/spec/googleauth/user_refresh_spec.rb 0000644 0000041 0000041 00000027552 13674353263 023133 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "apply_auth_examples"
require "fakefs/safe"
require "fileutils"
require "googleauth/user_refresh"
require "jwt"
require "multi_json"
require "openssl"
require "spec_helper"
require "tmpdir"
require "os"
include Google::Auth::CredentialsLoader
describe Google::Auth::UserRefreshCredentials do
UserRefreshCredentials = Google::Auth::UserRefreshCredentials
let :cred_json do
{
client_secret: "privatekey",
client_id: "client123",
refresh_token: "refreshtoken",
type: "authorized_user"
}
end
before :example do
@key = OpenSSL::PKey::RSA.new 2048
@client = UserRefreshCredentials.make_creds(
json_key_io: StringIO.new(cred_json_text),
scope: "https://www.googleapis.com/auth/userinfo.profile"
)
end
def make_auth_stubs opts
access_token = opts[:access_token] || ""
body = MultiJson.dump("access_token" => access_token,
"token_type" => "Bearer",
"expires_in" => 3600)
stub_request(:post, "https://oauth2.googleapis.com/token")
.with(body: hash_including("grant_type" => "refresh_token"))
.to_return(body: body,
status: 200,
headers: { "Content-Type" => "application/json" })
end
def cred_json_text missing = nil
cred_json.delete missing.to_sym unless missing.nil?
MultiJson.dump cred_json
end
it_behaves_like "apply/apply! are OK"
describe "#from_env" do
before :example do
@var_name = ENV_VAR
@credential_vars = [
ENV_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] }
@scope = "https://www.googleapis.com/auth/userinfo.profile"
@clz = UserRefreshCredentials
@project_id = "a_project_id"
end
after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
end
it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
ENV.delete @var_name unless ENV[@var_name].nil?
expect(UserRefreshCredentials.from_env(@scope)).to be_nil
end
it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is empty" do
ENV[@var_name] = ""
expect(UserRefreshCredentials.from_env(@scope)).to be_nil
end
it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
ENV.delete @var_name unless ENV[@var_name].nil?
expect(UserRefreshCredentials.from_env(@scope)).to be_nil
Dir.mktmpdir do |dir|
key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path
expect { @clz.from_env @scope }.to raise_error RuntimeError
end
end
it "fails if the GOOGLE_APPLICATION_CREDENTIALS path file is invalid" do
needed = %w[client_id client_secret refresh_token]
needed.each do |missing|
Dir.mktmpdir do |dir|
key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text(missing)
ENV[@var_name] = key_path
expect { @clz.from_env @scope }.to raise_error RuntimeError
end
end
end
it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir|
key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text
ENV[@var_name] = key_path
expect(@clz.from_env(@scope)).to_not be_nil
end
end
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]
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
creds = @clz.from_env @scope
expect(creds).to_not be_nil
expect(creds.client_id).to eq(cred_json[:client_id])
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
before :example do
@home = ENV["HOME"]
@app_data = ENV["APPDATA"]
@scope = "https://www.googleapis.com/auth/userinfo.profile"
@known_path = WELL_KNOWN_PATH
@clz = UserRefreshCredentials
end
after :example do
ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
end
it "is nil if no file exists" do
ENV["HOME"] = File.dirname __FILE__
expect(UserRefreshCredentials.from_well_known_path(@scope)).to be_nil
end
it "fails if the file is invalid" do
needed = %w[client_id client_secret refresh_token]
needed.each do |missing|
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(missing)
ENV["HOME"] = dir
ENV["APPDATA"] = dir
expect { @clz.from_well_known_path @scope }
.to raise_error RuntimeError
end
end
end
it "successfully loads the file when it is present" 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
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(Google::Auth::CredentialsLoader).to receive(:load_gcloud_project_id).with(no_args)
@clz.from_well_known_path @scope
end
end
end
describe "#from_system_default_path" do
before :example do
@scope = "https://www.googleapis.com/auth/userinfo.profile"
@prefix = OS.windows? ? "/etc/Google/Auth/" : "/etc/google/auth/"
@path = File.join @prefix, CREDENTIALS_FILE_NAME
@program_data = ENV["ProgramData"]
@clz = UserRefreshCredentials
end
after :example do
ENV["ProgramData"] = @program_data
end
it "is nil if no file exists" do
FakeFS do
expect(UserRefreshCredentials.from_system_default_path(@scope))
.to be_nil
end
end
it "fails if the file is invalid" do
needed = %w[client_id client_secret refresh_token]
needed.each do |missing|
FakeFS do
ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p File.dirname(@path)
File.write @path, cred_json_text(missing)
expect { @clz.from_system_default_path @scope }
.to raise_error RuntimeError
File.delete @path
end
end
end
it "successfully loads the file when it is present" do
FakeFS do
ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p File.dirname(@path)
File.write @path, cred_json_text
expect(@clz.from_system_default_path(@scope)).to_not be_nil
File.delete @path
end
end
end
shared_examples "revoked token" do
it "should nil the refresh token" do
expect(@client.refresh_token).to be_nil
end
it "should nil the access token" do
expect(@client.access_token).to be_nil
end
it "should mark the token as expired" do
expect(@client.expired?).to be_truthy
end
end
describe "when revoking a refresh token" do
let :stub do
stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including("token" => "refreshtoken"))
.to_return(status: 200,
headers: { "Content-Type" => "application/json" })
end
before :example do
stub
@client.revoke!
end
it_behaves_like "revoked token"
end
describe "when revoking an access token" do
let :stub do
stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including("token" => "accesstoken"))
.to_return(status: 200,
headers: { "Content-Type" => "application/json" })
end
before :example do
stub
@client.refresh_token = nil
@client.access_token = "accesstoken"
@client.revoke!
end
it_behaves_like "revoked token"
end
describe "when revoking an invalid token" do
let :stub do
stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including("token" => "refreshtoken"))
.to_return(status: 400,
headers: { "Content-Type" => "application/json" })
end
it "raises an authorization error" do
stub
expect { @client.revoke! }.to raise_error(
Signet::AuthorizationError
)
end
end
describe "when errors occurred with request" do
it "should fail with Signet::AuthorizationError if request times out" do
allow_any_instance_of(Faraday::Connection).to receive(:post)
.and_raise(Faraday::TimeoutError)
expect { @client.revoke! }
.to raise_error Signet::AuthorizationError
end
it "should fail with Signet::AuthorizationError if request fails" do
allow_any_instance_of(Faraday::Connection).to receive(:post)
.and_raise(Faraday::ConnectionFailed, nil)
expect { @client.revoke! }
.to raise_error Signet::AuthorizationError
end
end
end
googleauth-0.13.0/spec/googleauth/apply_auth_examples.rb 0000644 0000041 0000041 00000013122 13674353263 023455 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "faraday"
require "spec_helper"
shared_examples "apply/apply! are OK" do
let(:auth_key) { :authorization }
# tests that use these examples need to define
#
# @client which should be an auth client
#
# @make_auth_stubs, which should stub out the expected http behaviour of the
# auth client
describe "#fetch_access_token" do
let(:token) { "1/abcdef1234567890" }
let :access_stub do
make_auth_stubs access_token: token
end
let :id_stub do
make_auth_stubs id_token: token
end
it "should set access_token to the fetched value" do
access_stub
@client.fetch_access_token!
expect(@client.access_token).to eq(token)
expect(access_stub).to have_been_requested
end
it "should set id_token to the fetched value" do
skip unless @id_client
id_stub
@id_client.fetch_access_token!
expect(@id_client.id_token).to eq(token)
expect(id_stub).to have_been_requested
end
it "should notify refresh listeners after updating" do
access_stub
expect do |b|
@client.on_refresh(&b)
@client.fetch_access_token!
end.to yield_with_args(have_attributes(
access_token: "1/abcdef1234567890"
))
expect(access_stub).to have_been_requested
end
end
describe "#apply!" do
it "should update the target hash with fetched access token" do
token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token
md = { foo: "bar" }
@client.apply! md
want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(md).to eq(want)
expect(stub).to have_been_requested
end
it "should update the target hash with fetched ID token" do
skip unless @id_client
token = "1/abcdef1234567890"
stub = make_auth_stubs id_token: token
md = { foo: "bar" }
@id_client.apply! md
want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(md).to eq(want)
expect(stub).to have_been_requested
end
end
describe "updater_proc" do
it "should provide a proc that updates a hash with the access token" do
token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token
md = { foo: "bar" }
the_proc = @client.updater_proc
got = the_proc.call md
want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(got).to eq(want)
expect(stub).to have_been_requested
end
end
describe "#apply" do
it "should not update the original hash with the access token" do
token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token
md = { foo: "bar" }
@client.apply md
want = { foo: "bar" }
expect(md).to eq(want)
expect(stub).to have_been_requested
end
it "should add the token to the returned hash" do
token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token
md = { foo: "bar" }
got = @client.apply md
want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(got).to eq(want)
expect(stub).to have_been_requested
end
it "should not fetch a new token if the current is not expired" do
token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token
n = 5 # arbitrary
n.times do |_t|
md = { foo: "bar" }
got = @client.apply md
want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(got).to eq(want)
end
expect(stub).to have_been_requested
end
it "should fetch a new token if the current one is expired" do
token1 = "1/abcdef1234567890"
token2 = "2/abcdef1234567891"
[token1, token2].each do |t|
make_auth_stubs access_token: t
md = { foo: "bar" }
got = @client.apply md
want = { :foo => "bar", auth_key => "Bearer #{t}" }
expect(got).to eq(want)
@client.expires_at -= 3601 # default is to expire in 1hr
end
end
end
end
googleauth-0.13.0/spec/googleauth/signet_spec.rb 0000644 0000041 0000041 00000014050 13674353263 021715 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "apply_auth_examples"
require "googleauth/signet"
require "jwt"
require "openssl"
require "spec_helper"
describe Signet::OAuth2::Client do
before :example do
@key = OpenSSL::PKey::RSA.new 2048
@client = Signet::OAuth2::Client.new(
token_credential_uri: "https://oauth2.googleapis.com/token",
scope: "https://www.googleapis.com/auth/userinfo.profile",
issuer: "app@example.com",
audience: "https://oauth2.googleapis.com/token",
signing_key: @key
)
@id_client = Signet::OAuth2::Client.new(
token_credential_uri: "https://oauth2.googleapis.com/token",
target_audience: "https://pubsub.googleapis.com/",
issuer: "app@example.com",
audience: "https://oauth2.googleapis.com/token",
signing_key: @key
)
end
def make_auth_stubs opts
body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
body_fields["access_token"] = opts[:access_token] if opts[:access_token]
body_fields["id_token"] = opts[:id_token] if opts[:id_token]
body = MultiJson.dump body_fields
blk = proc do |request|
params = Addressable::URI.form_unencode request.body
claim, _header = JWT.decode(params.assoc("assertion").last,
@key.public_key, true,
algorithm: "RS256")
!opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
end
with_params = { body: hash_including(
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
) }
with_params[:headers] = { "User-Agent" => opts[:user_agent] } if opts[:user_agent]
stub_request(:post, "https://oauth2.googleapis.com/token")
.with(with_params, &blk)
.to_return(body: body,
status: 200,
headers: { "Content-Type" => "application/json" })
end
it_behaves_like "apply/apply! are OK"
describe "#configure_connection" do
it "honors default_connection" do
token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/1.0"
conn = Faraday.new headers: { "User-Agent" => "RubyRocks/1.0" }
@client.configure_connection default_connection: conn
md = { foo: "bar" }
@client.apply! md
want = { foo: "bar", authorization: "Bearer #{token}" }
expect(md).to eq(want)
expect(stub).to have_been_requested
end
it "honors connection_builder" do
token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/2.0"
connection_builder = proc do
Faraday.new headers: { "User-Agent" => "RubyRocks/2.0" }
end
@client.configure_connection connection_builder: connection_builder
md = { foo: "bar" }
@client.apply! md
want = { foo: "bar", authorization: "Bearer #{token}" }
expect(md).to eq(want)
expect(stub).to have_been_requested
end
end
describe "#fetch_access_token!" do
it "retries when orig_fetch_access_token! raises Signet::RemoteServerError" do
mocked_responses = [:raise, :raise, "success"]
allow(@client).to receive(:orig_fetch_access_token!).exactly(3).times do
response = mocked_responses.shift
response == :raise ? raise(Signet::RemoteServerError) : response
end
expect(@client.fetch_access_token!).to eq("success")
end
it "raises when the max retry count is exceeded" do
mocked_responses = [:raise, :raise, :raise, :raise, :raise, :raise, "success"]
allow(@client).to receive(:orig_fetch_access_token!).exactly(6).times do
response = mocked_responses.shift
response == :raise ? raise(Signet::RemoteServerError) : response
end
expect { @client.fetch_access_token! }.to raise_error Signet::AuthorizationError
end
it "does not retry and raises right away if it encounters a Signet::AuthorizationError" do
allow(@client).to receive(:orig_fetch_access_token!).at_most(:once)
.and_raise(Signet::AuthorizationError.new("Some Message"))
expect { @client.fetch_access_token! }.to raise_error Signet::AuthorizationError
end
it "does not retry and raises right away if it encounters a Signet::ParseError" do
allow(@client).to receive(:orig_fetch_access_token!).at_most(:once).and_raise(Signet::ParseError)
expect { @client.fetch_access_token! }.to raise_error Signet::ParseError
end
end
end
googleauth-0.13.0/spec/googleauth/client_id_spec.rb 0000644 0000041 0000041 00000010703 13674353263 022357 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "spec_helper"
require "fakefs/safe"
require "googleauth"
describe Google::Auth::ClientId do
shared_examples "it has a valid config" do
it "should include a valid id" do
expect(client_id.id).to eql "abc@example.com"
end
it "should include a valid secret" do
expect(client_id.secret).to eql "notasecret"
end
end
shared_examples "it can successfully load client_id" do
context "loaded from hash" do
let(:client_id) { Google::Auth::ClientId.from_hash config }
it_behaves_like "it has a valid config"
end
context "loaded from file" do
file_path = "/client_secrets.json"
let :client_id do
FakeFS do
content = MultiJson.dump config
File.write file_path, content
Google::Auth::ClientId.from_file file_path
end
end
it_behaves_like "it has a valid config"
end
end
describe "with web config" do
let :config do
{
"web" => {
"client_id" => "abc@example.com",
"client_secret" => "notasecret"
}
}
end
it_behaves_like "it can successfully load client_id"
end
describe "with installed app config" do
let :config do
{
"installed" => {
"client_id" => "abc@example.com",
"client_secret" => "notasecret"
}
}
end
it_behaves_like "it can successfully load client_id"
end
context "with missing top level property" do
let :config do
{
"notvalid" => {
"client_id" => "abc@example.com",
"client_secret" => "notasecret"
}
}
end
it "should raise error" do
expect { Google::Auth::ClientId.from_hash config }.to raise_error(
/Expected top level property/
)
end
end
context "with missing client id" do
let :config do
{
"web" => {
"client_secret" => "notasecret"
}
}
end
it "should raise error" do
expect { Google::Auth::ClientId.from_hash config }.to raise_error(
/Client id can not be nil/
)
end
end
context "with missing client secret" do
let :config do
{
"web" => {
"client_id" => "abc@example.com"
}
}
end
it "should raise error" do
expect { Google::Auth::ClientId.from_hash config }.to raise_error(
/Client secret can not be nil/
)
end
end
context "with cloud sdk credentials" do
let :config do
{
"web" => {
"client_id" => Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID,
"client_secret" => "notasecret"
}
}
end
it "should raise warning" do
expect { Google::Auth::ClientId.from_hash config }.to output(
Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
).to_stderr
end
end
end
googleauth-0.13.0/spec/googleauth/user_authorizer_spec.rb 0000644 0000041 0000041 00000025414 13674353263 023664 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "googleauth"
require "googleauth/user_authorizer"
require "uri"
require "multi_json"
require "spec_helper"
describe Google::Auth::UserAuthorizer do
include TestHelpers
let(:client_id) { Google::Auth::ClientId.new "testclient", "notasecret" }
let(:scope) { %w[email profile] }
let(:token_store) { DummyTokenStore.new }
let(:callback_uri) { "https://www.example.com/oauth/callback" }
let :authorizer do
Google::Auth::UserAuthorizer.new(client_id,
scope,
token_store,
callback_uri)
end
shared_examples "valid authorization url" do
it "should have a valid base URI" do
expect(uri).to match %r{https://accounts.google.com/o/oauth2/auth}
end
it "should request offline access" do
expect(URI(uri).query).to match(/access_type=offline/)
end
it "should request response type code" do
expect(URI(uri).query).to match(/response_type=code/)
end
it "should force approval" do
expect(URI(uri).query).to match(/approval_prompt=force/)
end
it "should include granted scopes" do
expect(URI(uri).query).to match(/include_granted_scopes=true/)
end
it "should include the correct client id" do
expect(URI(uri).query).to match(/client_id=testclient/)
end
it "should not include a client secret" do
expect(URI(uri).query).to_not match(/client_secret/)
end
it "should include the redirect_uri" do
expect(URI(uri).query).to match(
%r{redirect_uri=https://www.example.com/oauth/callback}
)
end
it "should include the scope" do
expect(URI(uri).query).to match(/scope=email%20profile/)
end
end
context "when generating authorization URLs and callback_uri is 'postmessage'" do
let(:callback_uri) { "postmessage" }
let :authorizer do
Google::Auth::UserAuthorizer.new(client_id,
scope,
token_store,
callback_uri)
end
let :uri do
authorizer.get_authorization_url login_hint: "user1", state: "mystate"
end
it "should include the redirect_uri 'postmessage'" do
expect(URI(uri).query).to match(
%r{redirect_uri=postmessage}
)
end
end
context "when generating authorization URLs with user ID & state" do
let :uri do
authorizer.get_authorization_url login_hint: "user1", state: "mystate"
end
it_behaves_like "valid authorization url"
it "includes a login hint" do
expect(URI(uri).query).to match(/login_hint=user1/)
end
it "includes the app state" do
expect(URI(uri).query).to match(/state=mystate/)
end
end
context "when generating authorization URLs with user ID and no state" do
let(:uri) { authorizer.get_authorization_url login_hint: "user1" }
it_behaves_like "valid authorization url"
it "includes a login hint" do
expect(URI(uri).query).to match(/login_hint=user1/)
end
it "does not include the state parameter" do
expect(URI(uri).query).to_not match(/state/)
end
end
context "when generating authorization URLs with no user ID and no state" do
let(:uri) { authorizer.get_authorization_url }
it_behaves_like "valid authorization url"
it "does not include the login hint parameter" do
expect(URI(uri).query).to_not match(/login_hint/)
end
it "does not include the state parameter" do
expect(URI(uri).query).to_not match(/state/)
end
end
context "when retrieving tokens" do
let :token_json do
MultiJson.dump(
access_token: "accesstoken",
refresh_token: "refreshtoken",
expiration_time_millis: 1_441_234_742_000
)
end
context "with a valid user id" do
let :credentials do
token_store.store "user1", token_json
authorizer.get_credentials "user1"
end
it "should return an instance of UserRefreshCredentials" do
expect(credentials).to be_instance_of(
Google::Auth::UserRefreshCredentials
)
end
it "should return credentials with a valid refresh token" do
expect(credentials.refresh_token).to eq "refreshtoken"
end
it "should return credentials with a valid access token" do
expect(credentials.access_token).to eq "accesstoken"
end
it "should return credentials with a valid client ID" do
expect(credentials.client_id).to eq "testclient"
end
it "should return credentials with a valid client secret" do
expect(credentials.client_secret).to eq "notasecret"
end
it "should return credentials with a valid scope" do
expect(credentials.scope).to eq %w[email profile]
end
it "should return credentials with a valid expiration time" do
expect(credentials.expires_at).to eq Time.at(1_441_234_742)
end
end
context "with an invalid user id" do
it "should return nil" do
expect(authorizer.get_credentials("notauser")).to be_nil
end
end
end
context "when saving tokens" do
let(:expiry) { Time.now.to_i }
let :credentials do
Google::Auth::UserRefreshCredentials.new(
client_id: client_id.id,
client_secret: client_id.secret,
scope: scope,
refresh_token: "refreshtoken",
access_token: "accesstoken",
expires_at: expiry
)
end
let :token_json do
authorizer.store_credentials "user1", credentials
token_store.load "user1"
end
it "should persist in the token store" do
expect(token_json).to_not be_nil
end
it "should persist the refresh token" do
expect(MultiJson.load(token_json)["refresh_token"]).to eq "refreshtoken"
end
it "should persist the access token" do
expect(MultiJson.load(token_json)["access_token"]).to eq "accesstoken"
end
it "should persist the client id" do
expect(MultiJson.load(token_json)["client_id"]).to eq "testclient"
end
it "should persist the scope" do
expect(MultiJson.load(token_json)["scope"]).to include("email", "profile")
end
it "should persist the expiry as milliseconds" do
expected_expiry = expiry * 1000
expect(MultiJson.load(token_json)["expiration_time_millis"]).to eql(
expected_expiry
)
end
end
context "with valid authorization code" do
let :token_json do
MultiJson.dump("access_token" => "1/abc123",
"token_type" => "Bearer",
"expires_in" => 3600)
end
before :example do
stub_request(:post, "https://oauth2.googleapis.com/token")
.to_return(body: token_json, status: 200, headers: {
"Content-Type" => "application/json"
})
end
it "should exchange a code for credentials" do
credentials = authorizer.get_credentials_from_code(
user_id: "user1", code: "code"
)
expect(credentials.access_token).to eq "1/abc123"
expect(credentials.redirect_uri.to_s).to eq "https://www.example.com/oauth/callback"
end
it "should not store credentials when get only requested" do
authorizer.get_credentials_from_code user_id: "user1", code: "code"
expect(token_store.load("user1")).to be_nil
end
it "should store credentials when requested" do
authorizer.get_and_store_credentials_from_code(
user_id: "user1", code: "code"
)
expect(token_store.load("user1")).to_not be_nil
end
end
context "with invalid authorization code" do
before :example do
stub_request(:post, "https://oauth2.googleapis.com/token")
.to_return(status: 400)
end
it "should raise an authorization error" do
expect do
authorizer.get_credentials_from_code user_id: "user1", code: "badcode"
end.to raise_error Signet::AuthorizationError
end
it "should not store credentials when exchange fails" do
expect do
authorizer.get_credentials_from_code user_id: "user1", code: "badcode"
end.to raise_error Signet::AuthorizationError
expect(token_store.load("user1")).to be_nil
end
end
context "when reovking authorization" do
let :token_json do
MultiJson.dump(
access_token: "accesstoken",
refresh_token: "refreshtoken",
expiration_time_millis: 1_441_234_742_000
)
end
before :example do
token_store.store "user1", token_json
stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including("token" => "refreshtoken"))
.to_return(status: 200)
end
it "should revoke the grant" do
authorizer.revoke_authorization "user1"
expect(a_request(
:post, "https://oauth2.googleapis.com/revoke"
).with(body: hash_including("token" => "refreshtoken")))
.to have_been_made
end
it "should remove the token from storage" do
authorizer.revoke_authorization "user1"
expect(token_store.load("user1")).to be_nil
end
end
# TODO: - Test that tokens are monitored
# TODO - Test scope enforcement (auth if upgrade required)
end
googleauth-0.13.0/spec/googleauth/stores/ 0000755 0000041 0000041 00000000000 13674353263 020404 5 ustar www-data www-data googleauth-0.13.0/spec/googleauth/stores/redis_token_store_spec.rb 0000644 0000041 0000041 00000003721 13674353263 025470 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "googleauth"
require "googleauth/stores/redis_token_store"
require "spec_helper"
require "fakeredis/rspec"
require "googleauth/stores/store_examples"
describe Google::Auth::Stores::RedisTokenStore do
let :redis do
Redis.new
end
let :store do
Google::Auth::Stores::RedisTokenStore.new redis: redis
end
it_behaves_like "token store"
end
googleauth-0.13.0/spec/googleauth/stores/store_examples.rb 0000644 0000041 0000041 00000004246 13674353263 023771 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "spec_helper"
shared_examples "token store" do
before :each do
store.store "default", "test"
end
it "should return a stored value" do
expect(store.load("default")).to eq "test"
end
it "should return nil for missing tokens" do
expect(store.load("notavalidkey")).to be_nil
end
it "should return nil for deleted tokens" do
store.delete "default"
expect(store.load("default")).to be_nil
end
it "should save overwrite values on store" do
store.store "default", "test2"
expect(store.load("default")).to eq "test2"
end
end
googleauth-0.13.0/spec/googleauth/stores/file_token_store_spec.rb 0000644 0000041 0000041 00000004206 13674353263 025300 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "googleauth"
require "googleauth/stores/file_token_store"
require "spec_helper"
require "fakefs/safe"
require "fakefs/spec_helpers"
require "googleauth/stores/store_examples"
module FakeFS
class File
# FakeFS doesn't implement. And since we don't need to actually lock,
# just stub out...
def flock *; end
end
end
describe Google::Auth::Stores::FileTokenStore do
include FakeFS::SpecHelpers
let :store do
Google::Auth::Stores::FileTokenStore.new file: "/tokens.yaml"
end
it_behaves_like "token store"
end
googleauth-0.13.0/spec/googleauth/compute_engine_spec.rb 0000644 0000041 0000041 00000013141 13674353263 023425 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "apply_auth_examples"
require "faraday"
require "googleauth/compute_engine"
require "spec_helper"
describe Google::Auth::GCECredentials do
MD_ACCESS_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
MD_ID_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://pubsub.googleapis.com/&format=full".freeze
GCECredentials = Google::Auth::GCECredentials
before :example do
@client = GCECredentials.new
@id_client = GCECredentials.new target_audience: "https://pubsub.googleapis.com/"
end
def make_auth_stubs opts
if opts[:access_token]
body = MultiJson.dump("access_token" => opts[:access_token],
"token_type" => "Bearer",
"expires_in" => 3600)
stub_request(:get, MD_ACCESS_URI)
.with(headers: { "Metadata-Flavor" => "Google" })
.to_return(body: body,
status: 200,
headers: { "Content-Type" => "application/json" })
elsif opts[:id_token]
stub_request(:get, MD_ID_URI)
.with(headers: { "Metadata-Flavor" => "Google" })
.to_return(body: opts[:id_token],
status: 200,
headers: { "Content-Type" => "text/html" })
end
end
it_behaves_like "apply/apply! are OK"
context "metadata is unavailable" do
describe "#fetch_access_token" do
it "should fail if the metadata request returns a 404" do
stub = stub_request(:get, MD_ACCESS_URI)
.to_return(status: 404,
headers: { "Metadata-Flavor" => "Google" })
expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError
expect(stub).to have_been_requested
end
it "should fail if the metadata request returns an unexpected code" do
stub = stub_request(:get, MD_ACCESS_URI)
.to_return(status: 503,
headers: { "Metadata-Flavor" => "Google" })
expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError
expect(stub).to have_been_requested
end
it "should fail with Signet::AuthorizationError if request times out" do
allow_any_instance_of(Faraday::Connection).to receive(:get)
.and_raise(Faraday::TimeoutError)
expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError
end
it "should fail with Signet::AuthorizationError if request fails" do
allow_any_instance_of(Faraday::Connection).to receive(:get)
.and_raise(Faraday::ConnectionFailed, nil)
expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError
end
end
end
describe "#on_gce?" do
it "should be true when Metadata-Flavor is Google" do
stub = stub_request(:get, "http://169.254.169.254")
.with(headers: { "Metadata-Flavor" => "Google" })
.to_return(status: 200,
headers: { "Metadata-Flavor" => "Google" })
expect(GCECredentials.on_gce?({}, true)).to eq(true)
expect(stub).to have_been_requested
end
it "should be false when Metadata-Flavor is not Google" do
stub = stub_request(:get, "http://169.254.169.254")
.with(headers: { "Metadata-Flavor" => "Google" })
.to_return(status: 200,
headers: { "Metadata-Flavor" => "NotGoogle" })
expect(GCECredentials.on_gce?({}, true)).to eq(false)
expect(stub).to have_been_requested
end
it "should be false if the response is not 200" do
stub = stub_request(:get, "http://169.254.169.254")
.with(headers: { "Metadata-Flavor" => "Google" })
.to_return(status: 404,
headers: { "Metadata-Flavor" => "NotGoogle" })
expect(GCECredentials.on_gce?({}, true)).to eq(false)
expect(stub).to have_been_requested
end
end
end
googleauth-0.13.0/spec/googleauth/get_application_default_spec.rb 0000644 0000041 0000041 00000024752 13674353263 025304 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
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
let(:options) { |example| { dememoize: example } }
before :example do
@key = OpenSSL::PKey::RSA.new 2048
@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"]
@app_data = ENV["APPDATA"]
@program_data = ENV["ProgramData"]
@scope = "https://www.googleapis.com/auth/userinfo.profile"
end
after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
ENV["ProgramData"] = @program_data unless @program_data == ENV["ProgramData"]
end
shared_examples "it cannot load misconfigured credentials" do
it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
Dir.mktmpdir do |dir|
key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path
expect { Google::Auth.get_application_default @scope, options }
.to raise_error RuntimeError
end
end
it "fails without default file or env if not on compute engine" do
stub = stub_request(:get, "http://169.254.169.254")
.to_return(status: 404,
headers: { "Metadata-Flavor" => "NotGoogle" })
Dir.mktmpdir do |dir|
ENV.delete @var_name unless ENV[@var_name].nil? # no env var
ENV["HOME"] = dir # no config present in this tmp dir
expect do
Google::Auth.get_application_default @scope, options
end.to raise_error RuntimeError
end
expect(stub).to have_been_requested
end
end
shared_examples "it can successfully load credentials" do
it "succeeds if the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir|
key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text
ENV[@var_name] = key_path
expect(Google::Auth.get_application_default(@scope, options))
.to_not be_nil
end
end
it "propagates default_connection option" do
Dir.mktmpdir do |dir|
key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text
ENV[@var_name] = key_path
connection = Faraday.new headers: { "User-Agent" => "hello" }
opts = options.merge default_connection: connection
creds = Google::Auth.get_application_default @scope, opts
expect(creds.build_default_connection).to be connection
end
end
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", 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
expect(Google::Auth.get_application_default(@scope, options))
.to_not be_nil
end
end
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", 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
expect(Google::Auth.get_application_default(nil, options)).to_not be_nil
end
end
it "succeeds without default file or env if on compute engine" do
stub = stub_request(:get, "http://169.254.169.254")
.to_return(status: 200,
headers: { "Metadata-Flavor" => "Google" })
Dir.mktmpdir do |dir|
ENV.delete @var_name unless ENV[@var_name].nil? # no env var
ENV["HOME"] = dir # no config present in this tmp dir
creds = Google::Auth.get_application_default @scope, options
expect(creds).to_not be_nil
end
expect(stub).to have_been_requested
end
it "succeeds with system default file" do
ENV.delete @var_name unless ENV[@var_name].nil?
FakeFS do
ENV["ProgramData"] = "/etc"
prefix = OS.windows? ? "/etc/Google/Auth/" : "/etc/google/auth/"
key_path = File.join prefix, CREDENTIALS_FILE_NAME
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text
expect(Google::Auth.get_application_default(@scope, options))
.to_not be_nil
File.delete key_path
end
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, options))
.to_not be_nil
end
it "warns when using cloud sdk credentials" 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] = Google::Auth::CredentialsLoader::CLOUD_SDK_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] = "a_project_id"
expect { Google::Auth.get_application_default @scope, options }.to output(
Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
).to_stderr
end
end
describe "when credential type is service account" do
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
it_behaves_like "it can successfully load credentials"
it_behaves_like "it cannot load misconfigured credentials"
end
describe "when credential type is authorized_user" do
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
it_behaves_like "it can successfully load credentials"
it_behaves_like "it cannot load misconfigured credentials"
end
describe "when credential type is unknown" do
let :cred_json do
{
client_secret: "privatekey",
refresh_token: "refreshtoken",
client_id: "app.apps.googleusercontent.com",
private_key: @key.to_pem,
client_email: "app@developer.gserviceaccount.com",
type: "not_known_type"
}
end
def cred_json_text
MultiJson.dump cred_json
end
it "fails if the GOOGLE_APPLICATION_CREDENTIALS file contains the creds" do
Dir.mktmpdir do |dir|
key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text
ENV[@var_name] = key_path
expect do
Google::Auth.get_application_default @scope, options
end.to raise_error RuntimeError
end
end
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", 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
expect do
Google::Auth.get_application_default @scope, options
end.to raise_error RuntimeError
end
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
Google::Auth.get_application_default @scope, options
end.to raise_error RuntimeError
end
end
end
googleauth-0.13.0/spec/googleauth/service_account_spec.rb 0000644 0000041 0000041 00000041123 13674353263 023601 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "apply_auth_examples"
require "fakefs/safe"
require "fileutils"
require "googleauth/service_account"
require "jwt"
require "multi_json"
require "openssl"
require "spec_helper"
require "tmpdir"
require "os"
include Google::Auth::CredentialsLoader
shared_examples "jwt header auth" do
context "when jwt_aud_uri is present" do
let(:test_uri) { "https://www.googleapis.com/myservice" }
let(:auth_prefix) { "Bearer " }
let(:auth_key) { ServiceAccountJwtHeaderCredentials::AUTH_METADATA_KEY }
let(:jwt_uri_key) { ServiceAccountJwtHeaderCredentials::JWT_AUD_URI_KEY }
def expect_is_encoded_jwt hdr
expect(hdr).to_not be_nil
expect(hdr.start_with?(auth_prefix)).to be true
authorization = hdr[auth_prefix.length..-1]
payload, = JWT.decode authorization, @key.public_key, true, algorithm: "RS256"
expect(payload["aud"]).to eq(test_uri)
expect(payload["iss"]).to eq(client_email)
end
describe "#apply!" do
it "should update the target hash with a jwt token" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
@client.apply! md
auth_header = md[auth_key]
expect_is_encoded_jwt auth_header
expect(md[jwt_uri_key]).to be_nil
end
end
describe "updater_proc" do
it "should provide a proc that updates a hash with a jwt token" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
the_proc = @client.updater_proc
got = the_proc.call md
auth_header = got[auth_key]
expect_is_encoded_jwt auth_header
expect(got[jwt_uri_key]).to be_nil
expect(md[jwt_uri_key]).to_not be_nil
end
end
describe "#apply" do
it "should not update the original hash with a jwt token" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
the_proc = @client.updater_proc
got = the_proc.call md
auth_header = md[auth_key]
expect(auth_header).to be_nil
expect(got[jwt_uri_key]).to be_nil
expect(md[jwt_uri_key]).to_not be_nil
end
it "should add a jwt token to the returned hash" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
got = @client.apply md
auth_header = got[auth_key]
expect_is_encoded_jwt auth_header
end
end
end
end
describe Google::Auth::ServiceAccountCredentials do
ServiceAccountCredentials = Google::Auth::ServiceAccountCredentials
let(:client_email) { "app@developer.gserviceaccount.com" }
let :cred_json do
{
private_key_id: "a_private_key_id",
private_key: @key.to_pem,
client_email: client_email,
client_id: "app.apps.googleusercontent.com",
type: "service_account",
project_id: "a_project_id",
quota_project_id: "b_project_id"
}
end
before :example do
@key = OpenSSL::PKey::RSA.new 2048
@client = ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(cred_json_text),
scope: "https://www.googleapis.com/auth/userinfo.profile"
)
@id_client = ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(cred_json_text),
target_audience: "https://pubsub.googleapis.com/"
)
end
def make_auth_stubs opts
body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
body_fields["access_token"] = opts[:access_token] if opts[:access_token]
body_fields["id_token"] = opts[:id_token] if opts[:id_token]
body = MultiJson.dump body_fields
blk = proc do |request|
params = Addressable::URI.form_unencode request.body
claim, _header = JWT.decode(params.assoc("assertion").last,
@key.public_key, true,
algorithm: "RS256")
!opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
end
stub_request(:post, "https://www.googleapis.com/oauth2/v4/token")
.with(body: hash_including(
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
), &blk)
.to_return(body: body,
status: 200,
headers: { "Content-Type" => "application/json" })
end
def cred_json_text
MultiJson.dump cred_json
end
it_behaves_like "apply/apply! are OK"
context "when scope is nil" do
before :example do
@client.scope = nil
end
it_behaves_like "jwt header auth"
end
describe "#from_env" do
before :example do
@var_name = ENV_VAR
@credential_vars = [
ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR
]
@original_env_vals = {}
@credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
@scope = "https://www.googleapis.com/auth/userinfo.profile"
@clz = ServiceAccountCredentials
end
after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
end
it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
ENV.delete @var_name unless ENV[@var_name].nil?
expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
end
it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is empty" do
ENV[@var_name] = ""
expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
end
it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
ENV.delete @var_name unless ENV[@var_name].nil?
expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
Dir.mktmpdir do |dir|
key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path
expect { @clz.from_env @scope }.to raise_error RuntimeError
end
end
it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir|
key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text
ENV[@var_name] = key_path
expect(@clz.from_env(@scope)).to_not be_nil
end
end
it "succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are"\
" valid" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
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.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}\""
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
expect(@clz.from_env(@scope)).to_not be_nil
end
it "propagates default_connection option" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
connection = Faraday.new headers: { "User-Agent" => "hello" }
creds = @clz.from_env @scope, default_connection: connection
expect(creds.build_default_connection).to be connection
end
end
describe "#from_well_known_path" do
before :example do
@home = ENV["HOME"]
@app_data = ENV["APPDATA"]
@scope = "https://www.googleapis.com/auth/userinfo.profile"
@known_path = WELL_KNOWN_PATH
@clz = ServiceAccountCredentials
end
after :example do
ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
end
it "is nil if no file exists" do
ENV["HOME"] = File.dirname __FILE__
expect(ServiceAccountCredentials.from_well_known_path(@scope)).to be_nil
end
it "successfully loads the file when it 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
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])
expect(credentials.quota_project_id).to eq(cred_json[:quota_project_id])
end
end
it "propagates default_connection option" 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
connection = Faraday.new headers: { "User-Agent" => "hello" }
creds = @clz.from_well_known_path @scope, default_connection: connection
expect(creds.build_default_connection).to be connection
end
end
end
describe "#from_system_default_path" do
before :example do
@scope = "https://www.googleapis.com/auth/userinfo.profile"
@program_data = ENV["ProgramData"]
@prefix = OS.windows? ? "/etc/Google/Auth/" : "/etc/google/auth/"
@path = File.join @prefix, CREDENTIALS_FILE_NAME
@clz = ServiceAccountCredentials
end
after :example do
ENV["ProgramData"] = @program_data
end
it "is nil if no file exists" do
FakeFS do
expect(ServiceAccountCredentials.from_system_default_path(@scope))
.to be_nil
end
end
it "successfully loads the file when it is present" do
FakeFS do
ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p File.dirname(@path)
File.write @path, cred_json_text
expect(@clz.from_system_default_path(@scope)).to_not be_nil
File.delete @path
end
end
it "propagates default_connection option" do
FakeFS do
ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p File.dirname(@path)
File.write @path, cred_json_text
connection = Faraday.new headers: { "User-Agent" => "hello" }
creds = @clz.from_system_default_path @scope, default_connection: connection
expect(creds.build_default_connection).to be connection
File.delete @path
end
end
end
end
describe Google::Auth::ServiceAccountJwtHeaderCredentials do
ServiceAccountJwtHeaderCredentials =
Google::Auth::ServiceAccountJwtHeaderCredentials
let(:client_email) { "app@developer.gserviceaccount.com" }
let(:clz) { Google::Auth::ServiceAccountJwtHeaderCredentials }
let :cred_json do
{
private_key_id: "a_private_key_id",
private_key: @key.to_pem,
client_email: client_email,
client_id: "app.apps.googleusercontent.com",
type: "service_account",
project_id: "a_project_id"
}
end
before :example do
@key = OpenSSL::PKey::RSA.new 2048
@client = clz.make_creds json_key_io: StringIO.new(cred_json_text)
end
def cred_json_text
MultiJson.dump cred_json
end
it_behaves_like "jwt header auth"
describe "#from_env" do
before :example do
@var_name = ENV_VAR
@credential_vars = [
ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR
]
@original_env_vals = {}
@credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
end
after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
end
it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
ENV.delete @var_name unless ENV[@var_name].nil?
expect(clz.from_env).to be_nil
end
it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is empty" do
ENV[@var_name] = ""
expect(clz.from_env).to be_nil
end
it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
ENV.delete @var_name unless ENV[@var_name].nil?
expect(clz.from_env).to be_nil
Dir.mktmpdir do |dir|
key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path
expect { clz.from_env }.to raise_error RuntimeError
end
end
it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir|
key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p File.dirname(key_path)
File.write key_path, cred_json_text
ENV[@var_name] = key_path
expect(clz.from_env).to_not be_nil
end
end
it "succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are"\
" valid" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
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
before :example do
@home = ENV["HOME"]
@app_data = ENV["APPDATA"]
end
after :example do
ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
end
it "is nil if no file exists" do
ENV["HOME"] = File.dirname __FILE__
expect(clz.from_well_known_path).to be_nil
end
it "successfully loads the file when it 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
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])
expect(credentials.quota_project_id).to be_nil
end
end
end
end
googleauth-0.13.0/spec/googleauth/web_user_authorizer_spec.rb 0000644 0000041 0000041 00000013377 13674353263 024526 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "googleauth"
require "googleauth/web_user_authorizer"
require "uri"
require "multi_json"
require "spec_helper"
require "rack"
describe Google::Auth::WebUserAuthorizer do
include TestHelpers
let(:client_id) { Google::Auth::ClientId.new "testclient", "notasecret" }
let(:scope) { %w[email profile] }
let(:token_store) { DummyTokenStore.new }
let :authorizer do
Google::Auth::WebUserAuthorizer.new client_id, scope, token_store
end
describe "#get_authorization_url" do
let :env do
Rack::MockRequest.env_for(
"http://example.com:8080/test",
"REMOTE_ADDR" => "10.10.10.10"
)
end
let(:request) { Rack::Request.new env }
it "should include current url in state" do
url = authorizer.get_authorization_url request: request
expect(url).to match(
%r{%22current_uri%22:%22http://example.com:8080/test%22}
)
end
it "should allow adding custom state key-value pairs" do
url = authorizer.get_authorization_url request: request, state: { james: "bond", kind: 1 }
expect(url).to match(%r{%22james%22:%22bond%22})
expect(url).to match(%r{%22kind%22:1})
end
it "should include request forgery token in state" do
expect(SecureRandom).to receive(:base64).and_return("aGVsbG8=")
url = authorizer.get_authorization_url request: request
expect(url).to match(/%22session_id%22:%22aGVsbG8=%22/)
end
it "should include request forgery token in session" do
expect(SecureRandom).to receive(:base64).and_return("aGVsbG8=")
authorizer.get_authorization_url request: request
expect(request.session["g-xsrf-token"]).to eq "aGVsbG8="
end
it "should resolve callback against base URL" do
url = authorizer.get_authorization_url request: request
expect(url).to match(
%r{redirect_uri=http://example.com:8080/oauth2callback}
)
end
it "should allow overriding the current URL" do
url = authorizer.get_authorization_url(
request: request,
redirect_to: "/foo"
)
expect(url).to match %r{%22current_uri%22:%22/foo%22}
end
it "should pass through login hint" do
url = authorizer.get_authorization_url(
request: request,
login_hint: "user@example.com"
)
expect(url).to match(/login_hint=user@example.com/)
end
end
shared_examples "handles callback" do
let :token_json do
MultiJson.dump("access_token" => "1/abc123",
"token_type" => "Bearer",
"expires_in" => 3600)
end
before :example do
stub_request(:post, "https://oauth2.googleapis.com/token")
.to_return(body: token_json,
status: 200,
headers: { "Content-Type" => "application/json" })
end
let :env do
Rack::MockRequest.env_for(
"http://example.com:8080/oauth2callback?code=authcode&"\
"state=%7B%22current_uri%22%3A%22%2Ffoo%22%2C%22"\
"session_id%22%3A%22abc%22%7D",
"REMOTE_ADDR" => "10.10.10.10"
)
end
let(:request) { Rack::Request.new env }
before :example do
request.session["g-xsrf-token"] = "abc"
end
it "should return credentials when valid code present" do
expect(credentials).to be_instance_of(
Google::Auth::UserRefreshCredentials
)
end
it "should return next URL to redirect to" do
expect(next_url).to eq "/foo"
end
it "should fail if xrsf token in session and does not match request" do
request.session["g-xsrf-token"] = "123"
expect { credentials }.to raise_error(Signet::AuthorizationError)
end
end
describe "#handle_auth_callback" do
let(:result) { authorizer.handle_auth_callback "user1", request }
let(:credentials) { result[0] }
let(:next_url) { result[1] }
it_behaves_like "handles callback"
end
describe "#handle_auth_callback_deferred and #get_credentials" do
let :next_url do
Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred request
end
let :credentials do
next_url
authorizer.get_credentials "user1", request
end
it_behaves_like "handles callback"
end
end
googleauth-0.13.0/spec/googleauth/scope_util_spec.rb 0000644 0000041 0000041 00000005505 13674353263 022577 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq!
require "googleauth/scope_util"
describe Google::Auth::ScopeUtil do
shared_examples "normalizes scopes" do
let(:normalized) { Google::Auth::ScopeUtil.normalize source }
it "normalizes the email scope" do
expect(normalized).to include(
"https://www.googleapis.com/auth/userinfo.email"
)
expect(normalized).to_not include "email"
end
it "normalizes the profile scope" do
expect(normalized).to include(
"https://www.googleapis.com/auth/userinfo.profile"
)
expect(normalized).to_not include "profile"
end
it "normalizes the openid scope" do
expect(normalized).to include "https://www.googleapis.com/auth/plus.me"
expect(normalized).to_not include "openid"
end
it "leaves other other scopes as-is" do
expect(normalized).to include "https://www.googleapis.com/auth/drive"
end
end
context "with scope as string" do
let :source do
"email profile openid https://www.googleapis.com/auth/drive"
end
it_behaves_like "normalizes scopes"
end
context "with scope as Array" do
let :source do
%w[email profile openid https://www.googleapis.com/auth/drive]
end
it_behaves_like "normalizes scopes"
end
end
googleauth-0.13.0/spec/googleauth/credentials_spec.rb 0000644 0000041 0000041 00000060054 13674353263 022726 0 ustar www-data www-data # Copyright 2017, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth"
# This test is testing the private class Google::Auth::Credentials. We want to
# 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 :default_keyfile_hash do
{
"private_key_id" => "testabc1234567890xyz",
"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",
"project_id" => "a_project_id",
"quota_project_id" => "b_project_id"
}
end
it "uses a default scope" do
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq([])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
Google::Auth::Credentials.new default_keyfile_hash
end
it "uses a custom scope" do
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
Google::Auth::Credentials.new default_keyfile_hash, scope: "http://example.com/scope"
end
describe "using CONSTANTS" do
it "can be subclassed to pass in other env paths" do
test_path_env_val = "/unknown/path/to/file.txt".freeze
test_json_env_val = JSON.generate default_keyfile_hash
ENV["TEST_PATH"] = test_path_env_val
ENV["TEST_JSON_VARS"] = test_json_env_val
class TestCredentials1 < Google::Auth::Credentials
TOKEN_CREDENTIAL_URI = "https://example.com/token".freeze
AUDIENCE = "https://example.com/audience".freeze
SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ["TEST_PATH"].freeze
JSON_ENV_VARS = ["TEST_JSON_VARS"].freeze
end
allow(::File).to receive(:file?).with(test_path_env_val) { false }
allow(::File).to receive(:file?).with(test_json_env_val) { false }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://example.com/token")
expect(options[:audience]).to eq("https://example.com/audience")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials1.default
expect(creds).to be_a_kind_of(TestCredentials1)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses can use PATH_ENV_VARS to get keyfile path" do
class TestCredentials2 < Google::Auth::Credentials
SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = %w[PATH_ENV_DUMMY PATH_ENV_TEST].freeze
JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials2.default
expect(creds).to be_a_kind_of(TestCredentials2)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
test_json_env_val = JSON.generate default_keyfile_hash
class TestCredentials3 < Google::Auth::Credentials
SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
JSON_ENV_VARS = %w[JSON_ENV_DUMMY JSON_ENV_TEST].freeze
DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::File).to receive(:file?).with(test_json_env_val) { false }
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials3.default
expect(creds).to be_a_kind_of(TestCredentials3)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses can use DEFAULT_PATHS to get keyfile path" do
class TestCredentials4 < Google::Auth::Credentials
SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials4.default
expect(creds).to be_a_kind_of(TestCredentials4)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses that find no matches default to Google::Auth.get_application_default" do
class TestCredentials5 < Google::Auth::Credentials
SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id)
allow(Google::Auth).to receive(:get_application_default) do |scope|
expect(scope).to eq([TestCredentials5::SCOPE])
# This should really be a Signet::OAuth2::Client object,
# but mocking is making that difficult, so return a valid hash instead.
default_keyfile_hash
end
allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials5.default
expect(creds).to be_a_kind_of(TestCredentials5)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
end
describe "using class methods" do
it "can be subclassed to pass in other env paths" do
test_path_env_val = "/unknown/path/to/file.txt".freeze
test_json_env_val = JSON.generate default_keyfile_hash
ENV["TEST_PATH"] = test_path_env_val
ENV["TEST_JSON_VARS"] = test_json_env_val
class TestCredentials11 < Google::Auth::Credentials
self.token_credential_uri = "https://example.com/token"
self.audience = "https://example.com/audience"
self.scope = "http://example.com/scope"
self.env_vars = ["TEST_PATH", "TEST_JSON_VARS"]
end
allow(::File).to receive(:file?).with(test_path_env_val) { false }
allow(::File).to receive(:file?).with(test_json_env_val) { false }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://example.com/token")
expect(options[:audience]).to eq("https://example.com/audience")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials11.default
expect(creds).to be_a_kind_of(TestCredentials11)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses can use PATH_ENV_VARS to get keyfile path" do
class TestCredentials12 < Google::Auth::Credentials
self.scope = "http://example.com/scope"
self.env_vars = %w[PATH_ENV_DUMMY PATH_ENV_TEST JSON_ENV_DUMMY]
self.paths = ["~/default/path/to/file.txt"]
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials12.default
expect(creds).to be_a_kind_of(TestCredentials12)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
test_json_env_val = JSON.generate default_keyfile_hash
class TestCredentials13 < Google::Auth::Credentials
self.scope = "http://example.com/scope"
self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY JSON_ENV_TEST]
self.paths = ["~/default/path/to/file.txt"]
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::File).to receive(:file?).with(test_json_env_val) { false }
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { test_json_env_val }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials13.default
expect(creds).to be_a_kind_of(TestCredentials13)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses can use DEFAULT_PATHS to get keyfile path" do
class TestCredentials14 < Google::Auth::Credentials
self.scope = "http://example.com/scope"
self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
self.paths = ["~/default/path/to/file.txt"]
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
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|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials14.default
expect(creds).to be_a_kind_of(TestCredentials14)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
it "subclasses that find no matches default to Google::Auth.get_application_default" do
class TestCredentials15 < Google::Auth::Credentials
self.scope = "http://example.com/scope"
self.env_vars = %w[PATH_ENV_DUMMY JSON_ENV_DUMMY]
self.paths = ["~/default/path/to/file.txt"]
end
allow(::ENV).to receive(:[]).with("GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS") { "true" }
allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id)
allow(Google::Auth).to receive(:get_application_default) do |scope|
expect(scope).to eq(TestCredentials15.scope)
# This should really be a Signet::OAuth2::Client object,
# but mocking is making that difficult, so return a valid hash instead.
default_keyfile_hash
end
allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet
end
creds = TestCredentials15.default
expect(creds).to be_a_kind_of(TestCredentials15)
expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"])
end
end
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(:fetch_access_token!).and_return(true)
allow(Signet::OAuth2::Client).to receive(:new) do |_options|
mocked_signet
end
allow(mocked_signet).to receive(:client_id).and_return(Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID)
expect { Google::Auth::Credentials.new default_keyfile_hash }.to output(
Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
).to_stderr
end
end
googleauth-0.13.0/spec/spec_helper.rb 0000644 0000041 0000041 00000005422 13674353263 017550 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = __dir__
root_dir = File.expand_path File.join(spec_dir, "..")
lib_dir = File.expand_path File.join(root_dir, "lib")
$LOAD_PATH.unshift spec_dir
$LOAD_PATH.unshift lib_dir
$LOAD_PATH.uniq!
# set up coverage
require "simplecov"
require "coveralls"
SimpleCov.formatters = [
Coveralls::SimpleCov::Formatter,
SimpleCov::Formatter::HTMLFormatter
]
SimpleCov.start
require "faraday"
require "rspec"
require "logging"
require "rspec/logging_helper"
require "webmock/rspec"
require "multi_json"
# Preload adapter to work around Rubinius error with FakeFS
MultiJson.use :json_gem
# Allow Faraday to support test stubs
Faraday::Adapter.load_middleware :test
# Configure RSpec to capture log messages for each test. The output from the
# logs will be stored in the @log_output variable. It is a StringIO instance.
RSpec.configure do |config|
include RSpec::LoggingHelper
config.capture_log_messages
config.include WebMock::API
config.filter_run focus: true
config.run_all_when_everything_filtered = true
end
module TestHelpers
include WebMock::API
include WebMock::Matchers
end
class DummyTokenStore
def initialize
@tokens = {}
end
def load id
@tokens[id]
end
def store id, token
@tokens[id] = token
end
def delete id
@tokens.delete id
end
end
googleauth-0.13.0/CHANGELOG.md 0000644 0000041 0000041 00000011013 13674353263 015602 0 ustar www-data www-data ### 0.13.0 / 2020-06-17
* Support for validating ID tokens.
* Fixed header application of ID tokens from service accounts.
### 0.12.0 / 2020-04-08
* Support for ID token credentials.
* Support reading quota_id_project from service account credentials.
### 0.11.0 / 2020-02-24
* Support Faraday 1.x.
* Allow special "postmessage" value for redirect_uri.
### 0.10.0 / 2019-10-09
Note: This release now requires Ruby 2.4 or later
* Increase metadata timeout to improve reliability in some hosting environments
* Support an environment variable to suppress Cloud SDK credentials warnings
* Make the header check case insensitive
* Set instance variables at initialization to avoid spamming warnings
* Pass "Metadata-Flavor" header to metadata server when checking for GCE
### 0.9.0 / 2019-08-05
* Restore compatibility with Ruby 2.0. This is the last release that will work on end-of-lifed versions of Ruby. The 0.10 release will require Ruby 2.4 or later.
* Update Credentials to use methods for values that are intended to be changed by users, replacing constants.
* Add retry on error for fetch_access_token
* Allow specifying custom state key-values
* Add verbosity none to gcloud command
* Make arity of WebUserAuthorizer#get_credentials compatible with the base class
### 0.8.1 / 2019-03-27
* Silence unnecessary gcloud warning
* Treat empty credentials environment variables as unset
### 0.8.0 / 2019-01-02
* Support connection options :default_connection and :connection_builder when creating credentials that need to refresh OAuth tokens. This lets clients provide connection objects with custom settings, such as proxies, needed for the client environment.
* Removed an unnecessary warning about project IDs.
### 0.7.1 / 2018-10-25
* Make load_gcloud_project_id module function.
### 0.7.0 / 2018-10-24
* Add project_id instance variable to UserRefreshCredentials, ServiceAccountCredentials, and Credentials.
### 0.6.7 / 2018-10-16
* Update memoist dependency to ~> 0.16.
### 0.6.6 / 2018-08-22
* Remove ruby version warnings.
### 0.6.5 / 2018-08-16
* Fix incorrect http verb when revoking credentials.
* Warn on EOL ruby versions.
### 0.6.4 / 2018-08-03
* Resolve issue where DefaultCredentials constant was undefined.
### 0.6.3 / 2018-08-02
* Resolve issue where token_store was being written to twice
### 0.6.2 / 2018-08-01
* Add warning when using cloud sdk credentials
### 0.6.1 / 2017-10-18
* Fix file permissions
### 0.6.0 / 2017-10-17
* Support ruby-jwt 2.0
* Add simple credentials class
### 0.5.3 / 2017-07-21
* Fix file permissions on the gem's `.rb` files.
### 0.5.2 / 2017-07-19
* Add retry mechanism when fetching access tokens in `GCECredentials` and `UserRefreshCredentials` classes.
* Update Google API OAuth2 token credential URI to v4.
### 0.5.1 / 2016-01-06
* Change header name emitted by `Client#apply` from "Authorization" to "authorization" ([@murgatroid99][])
* Fix ADC not working on some windows machines ([@vsubramani][])
[#55](https://github.com/google/google-auth-library-ruby/issues/55)
### 0.5.0 / 2015-10-12
* Initial support for user credentials ([@sqrrrl][])
* Update Signet to 0.7
### 0.4.2 / 2015-08-05
* Updated UserRefreshCredentials hash to use string keys ([@haabaato][])
[#36](https://github.com/google/google-auth-library-ruby/issues/36)
* Add support for a system default credentials file. ([@mr-salty][])
[#33](https://github.com/google/google-auth-library-ruby/issues/33)
* Fix bug when loading credentials from ENV ([@dwilkie][])
[#31](https://github.com/google/google-auth-library-ruby/issues/31)
* Relax the constraint of dependent version of multi_json ([@igrep][])
[#30](https://github.com/google/google-auth-library-ruby/issues/30)
* Enables passing credentials via environment variables. ([@haabaato][])
[#27](https://github.com/google/google-auth-library-ruby/issues/27)
### 0.4.1 / 2015-04-25
* Improves handling of --no-scopes GCE authorization ([@tbetbetbe][])
* Refactoring and cleanup ([@joneslee85][])
### 0.4.0 / 2015-03-25
* Adds an implementation of JWT header auth ([@tbetbetbe][])
### 0.3.0 / 2015-03-23
* makes the scope parameter's optional in all APIs. ([@tbetbetbe][])
* changes the scope parameter's position in various constructors. ([@tbetbetbe][])
[@dwilkie]: https://github.com/dwilkie
[@haabaato]: https://github.com/haabaato
[@igrep]: https://github.com/igrep
[@joneslee85]: https://github.com/joneslee85
[@mr-salty]: https://github.com/mr-salty
[@tbetbetbe]: https://github.com/tbetbetbe
[@murgatroid99]: https://github.com/murgatroid99
[@vsubramani]: https://github.com/vsubramani
googleauth-0.13.0/.rubocop.yml 0000644 0000041 0000041 00000000511 13674353263 016244 0 ustar www-data www-data inherit_gem:
google-style: google-style.yml
AllCops:
Exclude:
- "Rakefile"
- "integration/**/*"
- "rakelib/**/*"
- "spec/**/*"
- "test/**/*"
Metrics/ClassLength:
Max: 200
Metrics/ModuleLength:
Max: 110
Metrics/BlockLength:
Exclude:
- "googleauth.gemspec"
Style/SafeNavigation:
Enabled: false
googleauth-0.13.0/.kokoro/ 0000755 0000041 0000041 00000000000 13674353263 015357 5 ustar www-data www-data googleauth-0.13.0/.kokoro/continuous/ 0000755 0000041 0000041 00000000000 13674353263 017565 5 ustar www-data www-data googleauth-0.13.0/.kokoro/continuous/common.cfg 0000644 0000041 0000041 00000001027 13674353263 021536 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
# Build logs will be here
action {
define_artifacts {
regex: "**/*sponge_log.xml"
}
}
# Download trampoline resources.
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
# Download resources for system tests (service account key, etc.)
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-ruby"
env_vars: {
key: "JOB_TYPE"
value: "continuous"
}
env_vars: {
key: "REPO_DIR"
value: "github/google-auth-library-ruby"
}
googleauth-0.13.0/.kokoro/continuous/windows.cfg 0000644 0000041 0000041 00000001121 13674353263 021733 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
build_file: "google-auth-library-ruby/.kokoro/trampoline.bat"
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/yoshi-ruby/windows"
}
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/google-auth-library-ruby/.kokoro/build.bat"
}
env_vars: {
key: "TRAMPOLINE_SCRIPT"
value: "trampoline_windows.py"
}
env_vars: {
key: "REPO_DIR"
value: "google-auth-library-ruby"
}
env_vars: {
key: "OS"
value: "windows"
}
googleauth-0.13.0/.kokoro/continuous/linux.cfg 0000644 0000041 0000041 00000001154 13674353263 021406 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
build_file: "google-auth-library-ruby/.kokoro/trampoline.sh"
# Configure the docker image for kokoro-trampoline.
# Dockerfile is maintained at https://github.com/googleapis/google-cloud-ruby/tree/master/.kokoro/docker/multi
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/yoshi-ruby/multi"
}
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/google-auth-library-ruby/.kokoro/build.sh"
}
env_vars: {
key: "TRAMPOLINE_SCRIPT"
value: "trampoline_v1.py"
}
env_vars: {
key: "OS"
value: "linux"
}
googleauth-0.13.0/.kokoro/continuous/osx.cfg 0000644 0000041 0000041 00000000232 13674353263 021054 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
build_file: "google-auth-library-ruby/.kokoro/osx.sh"
env_vars: {
key: "OS"
value: "osx"
}
googleauth-0.13.0/.kokoro/continuous/post.cfg 0000644 0000041 0000041 00000001253 13674353263 021234 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
build_file: "google-auth-library-ruby/.kokoro/trampoline.sh"
# Configure the docker image for kokoro-trampoline.
# Dockerfile is maintained at https://github.com/googleapis/google-cloud-ruby/tree/master/.kokoro/docker/multi-node
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/yoshi-ruby/multi-node"
}
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/google-auth-library-ruby/.kokoro/build.sh"
}
env_vars: {
key: "TRAMPOLINE_SCRIPT"
value: "trampoline_v1.py"
}
env_vars: {
key: "OS"
value: "linux"
}
env_vars: {
key: "JOB_TYPE"
value: "post"
}
googleauth-0.13.0/.kokoro/trampoline.sh 0000755 0000041 0000041 00000000276 13674353263 020075 0 ustar www-data www-data #!/bin/bash
script_url="https://raw.githubusercontent.com/googleapis/google-cloud-ruby/master/.kokoro/trampoline.sh"
curl -o master-trampoline.sh $script_url && source master-trampoline.sh
googleauth-0.13.0/.kokoro/osx.sh 0000755 0000041 0000041 00000000251 13674353263 016525 0 ustar www-data www-data #!/bin/bash
script_url="https://raw.githubusercontent.com/googleapis/google-cloud-ruby/master/.kokoro/osx.sh"
curl -o master-osx.sh $script_url && source master-osx.sh
googleauth-0.13.0/.kokoro/release.cfg 0000644 0000041 0000041 00000003575 13674353263 017472 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
# Build logs will be here
action {
define_artifacts {
regex: "**/*sponge_log.xml"
}
}
# Fetch the token needed for reporting release status to GitHub
before_action {
fetch_keystore {
keystore_resource {
keystore_config_id: 73713
keyname: "yoshi-automation-github-key"
}
}
}
# Fetch magictoken to use with Magic Github Proxy
before_action {
fetch_keystore {
keystore_resource {
keystore_config_id: 73713
keyname: "releasetool-magictoken"
backend_type: FASTCONFIGPUSH
}
}
}
# Fetch api key to use with Magic Github Proxy
before_action {
fetch_keystore {
keystore_resource {
keystore_config_id: 73713
keyname: "magic-github-proxy-api-key"
backend_type: FASTCONFIGPUSH
}
}
}
before_action {
fetch_keystore {
keystore_resource {
keystore_config_id: 73713
keyname: "docuploader_service_account"
}
}
}
# Download resources for system tests (service account key, etc.)
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-ruby"
# Download trampoline resources.
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
# Use the trampoline script to run in docker.
build_file: "google-auth-library-ruby/.kokoro/trampoline.sh"
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/yoshi-ruby/release"
}
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/google-auth-library-ruby/.kokoro/build.sh"
}
env_vars: {
key: "TRAMPOLINE_SCRIPT"
value: "trampoline_v1.py"
}
env_vars: {
key: "JOB_TYPE"
value: "release"
}
env_vars: {
key: "OS"
value: "linux"
}
env_vars: {
key: "REPO_DIR"
value: "github/google-auth-library-ruby"
}
env_vars: {
key: "PACKAGE"
value: "googleauth"
}
googleauth-0.13.0/.kokoro/build.sh 0000755 0000041 0000041 00000000257 13674353263 017021 0 ustar www-data www-data #!/bin/bash
script_url="https://raw.githubusercontent.com/googleapis/google-cloud-ruby/master/.kokoro/build.sh"
curl -o master-build.sh $script_url && source master-build.sh
googleauth-0.13.0/.kokoro/presubmit/ 0000755 0000041 0000041 00000000000 13674353263 017371 5 ustar www-data www-data googleauth-0.13.0/.kokoro/presubmit/common.cfg 0000644 0000041 0000041 00000001026 13674353263 021341 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
# Build logs will be here
action {
define_artifacts {
regex: "**/*sponge_log.xml"
}
}
# Download trampoline resources.
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
# Download resources for system tests (service account key, etc.)
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-ruby"
env_vars: {
key: "JOB_TYPE"
value: "presubmit"
}
env_vars: {
key: "REPO_DIR"
value: "github/google-auth-library-ruby"
}
googleauth-0.13.0/.kokoro/presubmit/windows.cfg 0000644 0000041 0000041 00000001121 13674353263 021537 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
build_file: "google-auth-library-ruby/.kokoro/trampoline.bat"
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/yoshi-ruby/windows"
}
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/google-auth-library-ruby/.kokoro/build.bat"
}
env_vars: {
key: "TRAMPOLINE_SCRIPT"
value: "trampoline_windows.py"
}
env_vars: {
key: "REPO_DIR"
value: "google-auth-library-ruby"
}
env_vars: {
key: "OS"
value: "windows"
}
googleauth-0.13.0/.kokoro/presubmit/linux.cfg 0000644 0000041 0000041 00000000775 13674353263 021222 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
build_file: "google-auth-library-ruby/.kokoro/trampoline.sh"
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/yoshi-ruby/multi"
}
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/google-auth-library-ruby/.kokoro/build.sh"
}
env_vars: {
key: "TRAMPOLINE_SCRIPT"
value: "trampoline_v1.py"
}
env_vars: {
key: "OS"
value: "linux"
}
googleauth-0.13.0/.kokoro/presubmit/osx.cfg 0000644 0000041 0000041 00000000232 13674353263 020660 0 ustar www-data www-data # Format: //devtools/kokoro/config/proto/build.proto
build_file: "google-auth-library-ruby/.kokoro/osx.sh"
env_vars: {
key: "OS"
value: "osx"
}
googleauth-0.13.0/.kokoro/trampoline.bat 0000644 0000041 0000041 00000000443 13674353263 020222 0 ustar www-data www-data
SET url="https://raw.githubusercontent.com/googleapis/google-cloud-ruby/master/.kokoro/trampoline.bat"
SET "download=powershell -C Invoke-WebRequest -Uri %url% -OutFile master-trampoline.bat"
SET EXIT_STATUS=1
%download% && master-trampoline.bat && SET EXIT_STATUS=0
EXIT %EXIT_STATUS%
googleauth-0.13.0/.kokoro/build.bat 0000644 0000041 0000041 00000001214 13674353263 017144 0 ustar www-data www-data REM This file runs tests for merges, PRs, and nightlies.
REM There are a few rules for what tests are run:
REM * PRs run all non-acceptance tests for every library.
REM * Merges run all non-acceptance tests for every library, and acceptance tests for all altered libraries.
REM * Nightlies run all acceptance tests for every library.
REM Currently only runs tests on 2.5.1
SET url="https://raw.githubusercontent.com/googleapis/google-cloud-ruby/master/.kokoro/build.bat"
SET "download=powershell -C Invoke-WebRequest -Uri %url% -OutFile master-build.bat"
SET EXIT_STATUS=1
%download% && master-build.bat && SET EXIT_STATUS=0
EXIT %EXIT_STATUS%
googleauth-0.13.0/.gitignore 0000644 0000041 0000041 00000001115 13674353263 015763 0 ustar www-data www-data *~
Gemfile.lock
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/test/tmp/
/test/version_tmp/
/tmp/
## Specific to RubyMotion:
.dat*
.repl_history
build/
## Documentation cache and generated files:
/.yardoc/
/_yardoc/
/doc/
/rdoc/
## Environment normalisation:
/.bundle/
/lib/bundler/man/
# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
# .ruby-gemset
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
googleauth-0.13.0/rakelib/ 0000755 0000041 0000041 00000000000 13674353263 015406 5 ustar www-data www-data googleauth-0.13.0/rakelib/link_checker.rb 0000644 0000041 0000041 00000003451 13674353263 020357 0 ustar www-data www-data require "open3"
class LinkChecker
def initialize
@failed = false
end
def run
job_info
git_commit = ENV.fetch "KOKORO_GITHUB_COMMIT", "master"
markdown_files = Dir.glob "**/*.md"
broken_markdown_links = check_links markdown_files,
"https://github.com/googleapis/google-auth-library-ruby/tree/#{git_commit}",
" --skip '^(?!(\\Wruby.*google|.*google.*\\Wruby|.*cloud\\.google\\.com))'"
broken_devsite_links = check_links ["googleauth"],
"https://googleapis.dev/ruby",
"/latest/ --recurse --skip https:.*github.*"
puts_broken_links broken_markdown_links
puts_broken_links broken_devsite_links
end
def check_links location_list, base, tail
broken_links = Hash.new { |h, k| h[k] = [] }
location_list.each do |location|
out, err, st = Open3.capture3 "npx linkinator #{base}/#{location}#{tail}"
puts out
unless st.to_i.zero?
@failed = true
puts err
end
checked_links = out.split "\n"
checked_links.select! { |link| link =~ /\[\d+\]/ && !link.include?("[200]") }
unless checked_links.empty?
@failed = true
broken_links[location] += checked_links
end
end
broken_links
end
def puts_broken_links link_hash
link_hash.each do |location, links|
puts "#{location} contains the following broken links:"
links.each { |link| puts " #{link}" }
puts ""
end
end
def job_info
line_length = "Using Ruby - #{RUBY_VERSION}".length + 8
puts ""
puts "#" * line_length
puts "### Using Ruby - #{RUBY_VERSION} ###"
puts "#" * line_length
puts ""
end
def exit_status
@failed ? 1 : 0
end
end
googleauth-0.13.0/rakelib/devsite_builder.rb 0000644 0000041 0000041 00000002001 13674353263 021075 0 ustar www-data www-data require "pathname"
require_relative "repo_metadata.rb"
class DevsiteBuilder
def initialize master_dir = "."
@master_dir = Pathname.new master_dir
@output_dir = "doc"
@metadata = RepoMetadata.from_source "#{master_dir}/.repo-metadata.json"
end
def build
FileUtils.remove_dir @output_dir if Dir.exist? @output_dir
markup = "--markup markdown"
Dir.chdir @master_dir do
cmds = ["-o #{@output_dir}", markup]
cmd "yard --verbose #{cmds.join ' '}"
end
@metadata.build @master_dir + @output_dir
end
def upload
Dir.chdir @output_dir do
opts = [
"--credentials=#{ENV['KOKORO_KEYSTORE_DIR']}/73713_docuploader_service_account",
"--staging-bucket=#{ENV.fetch 'STAGING_BUCKET', 'docs-staging'}",
"--metadata-file=./docs.metadata"
]
cmd "python3 -m docuploader upload . #{opts.join ' '}"
end
end
def publish
build
upload
end
def cmd line
puts line
output = `#{line}`
puts output
output
end
end
googleauth-0.13.0/rakelib/repo_metadata.rb 0000644 0000041 0000041 00000002230 13674353263 020535 0 ustar www-data www-data require "json"
class RepoMetadata
attr_reader :data
def initialize data
@data = data
normalize_data!
end
def allowed_fields
[
"name", "version", "language", "distribution-name",
"product-page", "github-repository", "issue-tracker"
]
end
def build output_directory
fields = @data.to_a.map { |kv| "--#{kv[0]} #{kv[1]}" }
Dir.chdir output_directory do
cmd "python3 -m docuploader create-metadata #{fields.join ' '}"
end
end
def normalize_data!
require_relative "../lib/googleauth/version.rb"
@data.delete_if { |k, _| !allowed_fields.include?(k) }
@data["version"] = "v#{Google::Auth::VERSION}"
end
def [] key
data[key]
end
def []= key, value
@data[key] = value
end
def cmd line
puts line
output = `#{line}`
puts output
output
end
def self.from_source source
if source.is_a? RepoMetadata
data = source.data
elsif source.is_a? Hash
data = source
elsif File.file? source
data = JSON.parse File.read(source)
else
raise "Source must be a path, hash, or RepoMetadata instance"
end
RepoMetadata.new data
end
end
googleauth-0.13.0/CODE_OF_CONDUCT.md 0000644 0000041 0000041 00000003675 13674353263 016607 0 ustar www-data www-data # Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
googleauth-0.13.0/googleauth.gemspec 0000755 0000041 0000041 00000002541 13674353263 017505 0 ustar www-data www-data # -*- ruby -*-
# encoding: utf-8
$LOAD_PATH.push File.expand_path("lib", __dir__)
require "googleauth/version"
Gem::Specification.new do |gem|
gem.name = "googleauth"
gem.version = Google::Auth::VERSION
gem.authors = ["Tim Emiola"]
gem.email = "temiola@google.com"
gem.homepage = "https://github.com/googleapis/google-auth-library-ruby"
gem.summary = "Google Auth Library for Ruby"
gem.license = "Apache-2.0"
gem.description = <<-DESCRIPTION
Allows simple authorization for accessing Google APIs.
Provide support for Application Default Credentials, as described at
https://developers.google.com/accounts/docs/application-default-credentials
DESCRIPTION
gem.files = `git ls-files`.split "\n"
gem.test_files = `git ls-files -- spec/*`.split "\n"
gem.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f|
File.basename f
end
gem.require_paths = ["lib"]
gem.platform = Gem::Platform::RUBY
gem.required_ruby_version = ">= 2.4.0"
gem.add_dependency "faraday", ">= 0.17.3", "< 2.0"
gem.add_dependency "jwt", ">= 1.4", "< 3.0"
gem.add_dependency "memoist", "~> 0.16"
gem.add_dependency "multi_json", "~> 1.11"
gem.add_dependency "os", ">= 0.9", "< 2.0"
gem.add_dependency "signet", "~> 0.14"
gem.add_development_dependency "yard", "~> 0.9"
end
googleauth-0.13.0/Rakefile 0000755 0000041 0000041 00000006205 13674353263 015450 0 ustar www-data www-data # -*- ruby -*-
require "json"
require "bundler/gem_tasks"
require "rubocop/rake_task"
RuboCop::RakeTask.new
require "rake/testtask"
desc "Run tests."
Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = FileList["test/**/*_test.rb"]
t.warning = false
end
desc "Run integration tests."
Rake::TestTask.new("integration") do |t|
t.libs << "integration"
t.test_files = FileList["integration/**/*_test.rb"]
t.warning = false
end
task :ci do
header "Using Ruby - #{RUBY_VERSION}"
sh "bundle exec rubocop"
Rake::Task["test"].invoke
Rake::Task["integration"].invoke
sh "bundle exec rspec"
end
task :release_gem, :tag do |_t, args|
tag = args[:tag]
raise "You must provide a tag to release." if tag.nil?
# Verify the tag format "vVERSION"
m = tag.match /v(?\S*)/
raise "Tag #{tag} does not match the expected format." if m.nil?
version = m[:version]
raise "You must provide a version." if version.nil?
api_token = ENV["RUBYGEMS_API_TOKEN"]
require "gems"
if api_token
::Gems.configure do |config|
config.key = api_token
end
end
Bundler.with_clean_env do
sh "rm -rf pkg"
sh "bundle update"
sh "bundle exec rake build"
end
path_to_be_pushed = "pkg/googleauth-#{version}.gem"
gem_was_published = nil
if File.file? path_to_be_pushed
begin
response = ::Gems.push File.new(path_to_be_pushed)
puts response
raise unless response.include? "Successfully registered gem:"
gem_was_published = true
puts "Successfully built and pushed googleauth for version #{version}"
rescue StandardError => e
gem_was_published = false
puts "Error while releasing googleauth version #{version}: #{e.message}"
end
else
raise "Cannot build googleauth for version #{version}"
end
Rake::Task["kokoro:publish_docs"].invoke if gem_was_published
end
namespace :kokoro do
task :load_env_vars do
service_account = "#{ENV['KOKORO_GFILE_DIR']}/service-account.json"
ENV["GOOGLE_APPLICATION_CREDENTIALS"] = service_account
filename = "#{ENV['KOKORO_GFILE_DIR']}/env_vars.json"
env_vars = JSON.parse File.read(filename)
env_vars.each { |k, v| ENV[k] = v }
end
task :presubmit do
Rake::Task["ci"].invoke
end
task :continuous do
Rake::Task["ci"].invoke
end
task :post do
require_relative "rakelib/link_checker.rb"
link_checker = LinkChecker.new
link_checker.run
exit link_checker.exit_status
end
task :nightly do
Rake::Task["ci"].invoke
end
task :release do
version = "0.1.0"
Bundler.with_clean_env do
version = `bundle exec gem list`
.split("\n").select { |line| line.include? "googleauth" }
.first.split("(").last.split(")").first || "0.1.0"
end
Rake::Task["kokoro:load_env_vars"].invoke
Rake::Task["release_gem"].invoke "v#{version}"
end
task :publish_docs do
require_relative "rakelib/devsite_builder.rb"
DevsiteBuilder.new(__dir__).publish
end
end
def header str, token = "#"
line_length = str.length + 8
puts ""
puts token * line_length
puts "#{token * 3} #{str} #{token * 3}"
puts token * line_length
puts ""
end
googleauth-0.13.0/lib/ 0000755 0000041 0000041 00000000000 13674353263 014543 5 ustar www-data www-data googleauth-0.13.0/lib/googleauth/ 0000755 0000041 0000041 00000000000 13674353263 016701 5 ustar www-data www-data googleauth-0.13.0/lib/googleauth/client_id.rb 0000644 0000041 0000041 00000007462 13674353263 021171 0 ustar www-data www-data # Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "multi_json"
require "googleauth/credentials_loader"
module Google
module Auth
# Representation of an application's identity for user authorization
# flows.
class ClientId
INSTALLED_APP = "installed".freeze
WEB_APP = "web".freeze
CLIENT_ID = "client_id".freeze
CLIENT_SECRET = "client_secret".freeze
MISSING_TOP_LEVEL_ELEMENT_ERROR =
"Expected top level property 'installed' or 'web' to be present.".freeze
# Text identifier of the client ID
# @return [String]
attr_reader :id
# Secret associated with the client ID
# @return [String]
attr_reader :secret
class << self
attr_accessor :default
end
# Initialize the Client ID
#
# @param [String] id
# Text identifier of the client ID
# @param [String] secret
# Secret associated with the client ID
# @note Direction instantion is discouraged to avoid embedding IDs
# & secrets in source. See {#from_file} to load from
# `client_secrets.json` files.
def initialize id, secret
CredentialsLoader.warn_if_cloud_sdk_credentials id
raise "Client id can not be nil" if id.nil?
raise "Client secret can not be nil" if secret.nil?
@id = id
@secret = secret
end
# Constructs a Client ID from a JSON file downloaded from the
# Google Developers Console.
#
# @param [String, File] file
# Path of file to read from
# @return [Google::Auth::ClientID]
def self.from_file file
raise "File can not be nil." if file.nil?
File.open file.to_s do |f|
json = f.read
config = MultiJson.load json
from_hash config
end
end
# Constructs a Client ID from a previously loaded JSON file. The hash
# structure should
# match the expected JSON format.
#
# @param [hash] config
# Parsed contents of the JSON file
# @return [Google::Auth::ClientID]
def self.from_hash config
raise "Hash can not be nil." if config.nil?
raw_detail = config[INSTALLED_APP] || config[WEB_APP]
raise MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
ClientId.new raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET]
end
end
end
end
googleauth-0.13.0/lib/googleauth/scope_util.rb 0000644 0000041 0000041 00000004444 13674353263 021402 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth/signet"
require "googleauth/credentials_loader"
require "multi_json"
module Google
module Auth
# Small utility for normalizing scopes into canonical form
module ScopeUtil
ALIASES = {
"email" => "https://www.googleapis.com/auth/userinfo.email",
"profile" => "https://www.googleapis.com/auth/userinfo.profile",
"openid" => "https://www.googleapis.com/auth/plus.me"
}.freeze
def self.normalize scope
list = as_array scope
list.map { |item| ALIASES[item] || item }
end
def self.as_array scope
case scope
when Array
scope
when String
scope.split " "
else
raise "Invalid scope value. Must be string or array"
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/version.rb 0000644 0000041 0000041 00000003251 13674353263 020714 0 ustar www-data www-data # Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
VERSION = "0.13.0".freeze
end
end
googleauth-0.13.0/lib/googleauth/id_tokens/ 0000755 0000041 0000041 00000000000 13674353263 020660 5 ustar www-data www-data googleauth-0.13.0/lib/googleauth/id_tokens/verifier.rb 0000644 0000041 0000041 00000013145 13674353263 023024 0 ustar www-data www-data # frozen_string_literal: true
# Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "jwt"
module Google
module Auth
module IDTokens
##
# An object that can verify ID tokens.
#
# A verifier maintains a set of default settings, including the key
# source and fields to verify. However, individual verification calls can
# override any of these settings.
#
class Verifier
##
# Create a verifier.
#
# @param key_source [key source] The default key source to use. All
# verification calls must have a key source, so if no default key
# source is provided here, then calls to {#verify} _must_ provide
# a key source.
# @param aud [String,nil] The default audience (`aud`) check, or `nil`
# for no check.
# @param azp [String,nil] The default authorized party (`azp`) check,
# or `nil` for no check.
# @param iss [String,nil] The default issuer (`iss`) check, or `nil`
# for no check.
#
def initialize key_source: nil,
aud: nil,
azp: nil,
iss: nil
@key_source = key_source
@aud = aud
@azp = azp
@iss = iss
end
##
# Verify the given token.
#
# @param token [String] the ID token to verify.
# @param key_source [key source] If given, override the key source.
# @param aud [String,nil] If given, override the `aud` check.
# @param azp [String,nil] If given, override the `azp` check.
# @param iss [String,nil] If given, override the `iss` check.
#
# @return [Hash] the decoded payload, if verification succeeded.
# @raise [KeySourceError] if the key source failed to obtain public keys
# @raise [VerificationError] if the token verification failed.
# Additional data may be available in the error subclass and message.
#
def verify token,
key_source: :default,
aud: :default,
azp: :default,
iss: :default
key_source = @key_source if key_source == :default
aud = @aud if aud == :default
azp = @azp if azp == :default
iss = @iss if iss == :default
raise KeySourceError, "No key sources" unless key_source
keys = key_source.current_keys
payload = decode_token token, keys, aud, azp, iss
unless payload
keys = key_source.refresh_keys
payload = decode_token token, keys, aud, azp, iss
end
raise SignatureError, "Token not verified as issued by Google" unless payload
payload
end
private
def decode_token token, keys, aud, azp, iss
payload = nil
keys.find do |key|
begin
options = { algorithms: key.algorithm }
decoded_token = JWT.decode token, key.key, true, options
payload = decoded_token.first
rescue JWT::ExpiredSignature
raise ExpiredTokenError, "Token signature is expired"
rescue JWT::DecodeError
nil # Try the next key
end
end
normalize_and_verify_payload payload, aud, azp, iss
end
def normalize_and_verify_payload payload, aud, azp, iss
return nil unless payload
# Map the legacy "cid" claim to the canonical "azp"
payload["azp"] ||= payload["cid"] if payload.key? "cid"
# Payload content validation
if aud && (Array(aud) & Array(payload["aud"])).empty?
raise AudienceMismatchError, "Token aud mismatch: #{payload['aud']}"
end
if azp && (Array(azp) & Array(payload["azp"])).empty?
raise AuthorizedPartyMismatchError, "Token azp mismatch: #{payload['azp']}"
end
if iss && (Array(iss) & Array(payload["iss"])).empty?
raise IssuerMismatchError, "Token iss mismatch: #{payload['iss']}"
end
payload
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/id_tokens/errors.rb 0000644 0000041 0000041 00000004753 13674353263 022532 0 ustar www-data www-data # frozen_string_literal: true
# Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Google
module Auth
module IDTokens
##
# Failed to obtain keys from the key source.
#
class KeySourceError < StandardError; end
##
# Failed to verify a token.
#
class VerificationError < StandardError; end
##
# Failed to verify a token because it is expired.
#
class ExpiredTokenError < VerificationError; end
##
# Failed to verify a token because its signature did not match.
#
class SignatureError < VerificationError; end
##
# Failed to verify a token because its issuer did not match.
#
class IssuerMismatchError < VerificationError; end
##
# Failed to verify a token because its audience did not match.
#
class AudienceMismatchError < VerificationError; end
##
# Failed to verify a token because its authorized party did not match.
#
class AuthorizedPartyMismatchError < VerificationError; end
end
end
end
googleauth-0.13.0/lib/googleauth/id_tokens/key_sources.rb 0000644 0000041 0000041 00000030723 13674353263 023545 0 ustar www-data www-data # frozen_string_literal: true
# Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "base64"
require "json"
require "monitor"
require "net/http"
require "openssl"
require "jwt"
module Google
module Auth
module IDTokens
##
# A public key used for verifying ID tokens.
#
# This includes the public key data, ID, and the algorithm used for
# signature verification. RSA and Elliptical Curve (EC) keys are
# supported.
#
class KeyInfo
##
# Create a public key info structure.
#
# @param id [String] The key ID.
# @param key [OpenSSL::PKey::RSA,OpenSSL::PKey::EC] The key itself.
# @param algorithm [String] The algorithm (normally `RS256` or `ES256`)
#
def initialize id: nil, key: nil, algorithm: nil
@id = id
@key = key
@algorithm = algorithm
end
##
# The key ID.
# @return [String]
#
attr_reader :id
##
# The key itself.
# @return [OpenSSL::PKey::RSA,OpenSSL::PKey::EC]
#
attr_reader :key
##
# The signature algorithm. (normally `RS256` or `ES256`)
# @return [String]
#
attr_reader :algorithm
class << self
##
# Create a KeyInfo from a single JWK, which may be given as either a
# hash or an unparsed JSON string.
#
# @param jwk [Hash,String] The JWK specification.
# @return [KeyInfo]
# @raise [KeySourceError] If the key could not be extracted from the
# JWK.
#
def from_jwk jwk
jwk = symbolize_keys ensure_json_parsed jwk
key = case jwk[:kty]
when "RSA"
extract_rsa_key jwk
when "EC"
extract_ec_key jwk
when nil
raise KeySourceError, "Key type not found"
else
raise KeySourceError, "Cannot use key type #{jwk[:kty]}"
end
new id: jwk[:kid], key: key, algorithm: jwk[:alg]
end
##
# Create an array of KeyInfo from a JWK Set, which may be given as
# either a hash or an unparsed JSON string.
#
# @param jwk [Hash,String] The JWK Set specification.
# @return [Array]
# @raise [KeySourceError] If a key could not be extracted from the
# JWK Set.
#
def from_jwk_set jwk_set
jwk_set = symbolize_keys ensure_json_parsed jwk_set
jwks = jwk_set[:keys]
raise KeySourceError, "No keys found in jwk set" unless jwks
jwks.map { |jwk| from_jwk jwk }
end
private
def ensure_json_parsed input
return input unless input.is_a? String
JSON.parse input
rescue JSON::ParserError
raise KeySourceError, "Unable to parse JSON"
end
def symbolize_keys hash
result = {}
hash.each { |key, val| result[key.to_sym] = val }
result
end
def extract_rsa_key jwk
begin
n_data = Base64.urlsafe_decode64 jwk[:n]
e_data = Base64.urlsafe_decode64 jwk[:e]
rescue ArgumentError
raise KeySourceError, "Badly formatted key data"
end
n_bn = OpenSSL::BN.new n_data, 2
e_bn = OpenSSL::BN.new e_data, 2
rsa_key = OpenSSL::PKey::RSA.new
if rsa_key.respond_to? :set_key
rsa_key.set_key n_bn, e_bn, nil
else
rsa_key.n = n_bn
rsa_key.e = e_bn
end
rsa_key.public_key
end
# @private
CURVE_NAME_MAP = {
"P-256" => "prime256v1",
"P-384" => "secp384r1",
"P-521" => "secp521r1",
"secp256k1" => "secp256k1"
}.freeze
def extract_ec_key jwk
begin
x_data = Base64.urlsafe_decode64 jwk[:x]
y_data = Base64.urlsafe_decode64 jwk[:y]
rescue ArgumentError
raise KeySourceError, "Badly formatted key data"
end
curve_name = CURVE_NAME_MAP[jwk[:crv]]
raise KeySourceError, "Unsupported EC curve #{jwk[:crv]}" unless curve_name
group = OpenSSL::PKey::EC::Group.new curve_name
bn = OpenSSL::BN.new ["04" + x_data.unpack1("H*") + y_data.unpack1("H*")].pack("H*"), 2
key = OpenSSL::PKey::EC.new curve_name
key.public_key = OpenSSL::PKey::EC::Point.new group, bn
key
end
end
end
##
# A key source that contains a static set of keys.
#
class StaticKeySource
##
# Create a static key source with the given keys.
#
# @param keys [Array] The keys
#
def initialize keys
@current_keys = Array(keys)
end
##
# Return the current keys. Does not perform any refresh.
#
# @return [Array]
#
attr_reader :current_keys
alias refresh_keys current_keys
class << self
##
# Create a static key source containing a single key parsed from a
# single JWK, which may be given as either a hash or an unparsed
# JSON string.
#
# @param jwk [Hash,String] The JWK specification.
# @return [StaticKeySource]
#
def from_jwk jwk
new KeyInfo.from_jwk jwk
end
##
# Create a static key source containing multiple keys parsed from a
# JWK Set, which may be given as either a hash or an unparsed JSON
# string.
#
# @param jwk_set [Hash,String] The JWK Set specification.
# @return [StaticKeySource]
#
def from_jwk_set jwk_set
new KeyInfo.from_jwk_set jwk_set
end
end
end
##
# A base key source that downloads keys from a URI. Subclasses should
# override {HttpKeySource#interpret_json} to parse the response.
#
class HttpKeySource
##
# The default interval between retries in seconds (3600s = 1hr).
#
# @return [Integer]
#
DEFAULT_RETRY_INTERVAL = 3600
##
# Create an HTTP key source.
#
# @param uri [String,URI] The URI from which to download keys.
# @param retry_interval [Integer,nil] Override the retry interval in
# seconds. This is the minimum time between retries of failed key
# downloads.
#
def initialize uri, retry_interval: nil
@uri = URI uri
@retry_interval = retry_interval || DEFAULT_RETRY_INTERVAL
@allow_refresh_at = Time.now
@current_keys = []
@monitor = Monitor.new
end
##
# The URI from which to download keys.
# @return [Array]
#
attr_reader :uri
##
# Return the current keys, without attempting to re-download.
#
# @return [Array]
#
attr_reader :current_keys
##
# Attempt to re-download keys (if the retry interval has expired) and
# return the new keys.
#
# @return [Array]
# @raise [KeySourceError] if key retrieval failed.
#
def refresh_keys
@monitor.synchronize do
return @current_keys if Time.now < @allow_refresh_at
@allow_refresh_at = Time.now + @retry_interval
response = Net::HTTP.get_response uri
raise KeySourceError, "Unable to retrieve data from #{uri}" unless response.is_a? Net::HTTPSuccess
data = begin
JSON.parse response.body
rescue JSON::ParserError
raise KeySourceError, "Unable to parse JSON"
end
@current_keys = Array(interpret_json(data))
end
end
protected
def interpret_json _data
nil
end
end
##
# A key source that downloads X509 certificates.
# Used by the legacy OAuth V1 public certs endpoint.
#
class X509CertHttpKeySource < HttpKeySource
##
# Create a key source that downloads X509 certificates.
#
# @param uri [String,URI] The URI from which to download keys.
# @param algorithm [String] The algorithm to use for signature
# verification. Defaults to "`RS256`".
# @param retry_interval [Integer,nil] Override the retry interval in
# seconds. This is the minimum time between retries of failed key
# downloads.
#
def initialize uri, algorithm: "RS256", retry_interval: nil
super uri, retry_interval: retry_interval
@algorithm = algorithm
end
protected
def interpret_json data
data.map do |id, cert_str|
key = OpenSSL::X509::Certificate.new(cert_str).public_key
KeyInfo.new id: id, key: key, algorithm: @algorithm
end
rescue OpenSSL::X509::CertificateError
raise KeySourceError, "Unable to parse X509 certificates"
end
end
##
# A key source that downloads a JWK set.
#
class JwkHttpKeySource < HttpKeySource
##
# Create a key source that downloads a JWT Set.
#
# @param uri [String,URI] The URI from which to download keys.
# @param retry_interval [Integer,nil] Override the retry interval in
# seconds. This is the minimum time between retries of failed key
# downloads.
#
def initialize uri, retry_interval: nil
super uri, retry_interval: retry_interval
end
protected
def interpret_json data
KeyInfo.from_jwk_set data
end
end
##
# A key source that aggregates other key sources. This means it will
# aggregate the keys provided by its constituent sources. Additionally,
# when asked to refresh, it will refresh all its constituent sources.
#
class AggregateKeySource
##
# Create a key source that aggregates other key sources.
#
# @param sources [Array] The key sources to aggregate.
#
def initialize sources
@sources = Array(sources)
end
##
# Return the current keys, without attempting to refresh.
#
# @return [Array]
#
def current_keys
@sources.flat_map(&:current_keys)
end
##
# Attempt to refresh keys and return the new keys.
#
# @return [Array]
# @raise [KeySourceError] if key retrieval failed.
#
def refresh_keys
@sources.flat_map(&:refresh_keys)
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/credentials.rb 0000644 0000041 0000041 00000040275 13674353263 021533 0 ustar www-data www-data # Copyright 2017, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "forwardable"
require "json"
require "signet/oauth_2/client"
require "googleauth/credentials_loader"
module Google
module Auth
##
# Credentials is responsible for representing the authentication when connecting to an API. This
# class is also intended to be inherited by API-specific classes.
class Credentials
##
# The default token credential URI to be used when none is provided during initialization.
TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token".freeze
##
# The default target audience ID to be used when none is provided during initialization.
AUDIENCE = "https://oauth2.googleapis.com/token".freeze
@audience = @scope = @target_audience = @env_vars = @paths = nil
##
# The default token credential URI to be used when none is provided during initialization.
# The URI is the authorization server's HTTP endpoint capable of issuing tokens and
# refreshing expired tokens.
#
# @return [String]
#
def self.token_credential_uri
return @token_credential_uri unless @token_credential_uri.nil?
const_get :TOKEN_CREDENTIAL_URI if const_defined? :TOKEN_CREDENTIAL_URI
end
##
# Set the default token credential URI to be used when none is provided during initialization.
#
# @param [String] new_token_credential_uri
# @return [String]
#
def self.token_credential_uri= new_token_credential_uri
@token_credential_uri = new_token_credential_uri
end
##
# The default target audience ID to be used when none is provided during initialization.
# Used only by the assertion grant type.
#
# @return [String]
#
def self.audience
return @audience unless @audience.nil?
const_get :AUDIENCE if const_defined? :AUDIENCE
end
##
# Sets the default target audience ID to be used when none is provided during initialization.
#
# @param [String] new_audience
# @return [String]
#
def self.audience= new_audience
@audience = new_audience
end
##
# The default scope to be used when none is provided during initialization.
# A scope is an access range defined by the authorization server.
# The scope can be a single value or a list of values.
#
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
# If {#scope} is set, this credential will produce access tokens.
# If {#target_audience} is set, this credential will produce ID tokens.
#
# @return [String, Array]
#
def self.scope
return @scope unless @scope.nil?
Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE
end
##
# Sets the default scope to be used when none is provided during initialization.
#
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
# If {#scope} is set, this credential will produce access tokens.
# If {#target_audience} is set, this credential will produce ID tokens.
#
# @param [String, Array] new_scope
# @return [String, Array]
#
def self.scope= new_scope
new_scope = Array new_scope unless new_scope.nil?
@scope = new_scope
end
##
# The default final target audience for ID tokens, to be used when none
# is provided during initialization.
#
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
# If {#scope} is set, this credential will produce access tokens.
# If {#target_audience} is set, this credential will produce ID tokens.
#
# @return [String]
#
def self.target_audience
@target_audience
end
##
# Sets the default final target audience for ID tokens, to be used when none
# is provided during initialization.
#
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
# If {#scope} is set, this credential will produce access tokens.
# If {#target_audience} is set, this credential will produce ID tokens.
#
# @param [String] new_target_audience
#
def self.target_audience= new_target_audience
@target_audience = new_target_audience
end
##
# The environment variables to search for credentials. Values can either be a file path to the
# credentials file, or the JSON contents of the credentials file.
#
# @return [Array]
#
def self.env_vars
return @env_vars unless @env_vars.nil?
# Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists.
tmp_env_vars = []
tmp_env_vars << const_get(:PATH_ENV_VARS) if const_defined? :PATH_ENV_VARS
tmp_env_vars << const_get(:JSON_ENV_VARS) if const_defined? :JSON_ENV_VARS
tmp_env_vars.flatten.uniq
end
##
# Sets the environment variables to search for credentials.
#
# @param [Array] new_env_vars
# @return [Array]
#
def self.env_vars= new_env_vars
new_env_vars = Array new_env_vars unless new_env_vars.nil?
@env_vars = new_env_vars
end
##
# The file paths to search for credentials files.
#
# @return [Array]
#
def self.paths
return @paths unless @paths.nil?
tmp_paths = []
# Pull in values is the DEFAULT_PATHS constant exists.
tmp_paths << const_get(:DEFAULT_PATHS) if const_defined? :DEFAULT_PATHS
tmp_paths.flatten.uniq
end
##
# Set the file paths to search for credentials files.
#
# @param [Array] new_paths
# @return [Array]
#
def self.paths= new_paths
new_paths = Array new_paths unless new_paths.nil?
@paths = new_paths
end
##
# The Signet::OAuth2::Client object the Credentials instance is using.
#
# @return [Signet::OAuth2::Client]
#
attr_accessor :client
##
# Identifier for the project the client is authenticating with.
#
# @return [String]
#
attr_reader :project_id
##
# Identifier for a separate project used for billing/quota, if any.
#
# @return [String,nil]
#
attr_reader :quota_project_id
# @private Delegate client methods to the client object.
extend Forwardable
##
# @!attribute [r] token_credential_uri
# @return [String] The token credential URI. The URI is the authorization server's HTTP
# endpoint capable of issuing tokens and refreshing expired tokens.
#
# @!attribute [r] audience
# @return [String] The target audience ID when issuing assertions. Used only by the
# assertion grant type.
#
# @!attribute [r] scope
# @return [String, Array] The scope for this client. A scope is an access range
# defined by the authorization server. The scope can be a single value or a list of values.
#
# @!attribute [r] target_audience
# @return [String] The final target audience for ID tokens returned by this credential.
#
# @!attribute [r] issuer
# @return [String] The issuer ID associated with this client.
#
# @!attribute [r] signing_key
# @return [String, OpenSSL::PKey] The signing key associated with this client.
#
# @!attribute [r] updater_proc
# @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
# suitable for passing as a closure.
#
def_delegators :@client,
:token_credential_uri, :audience,
:scope, :issuer, :signing_key, :updater_proc, :target_audience
##
# Creates a new Credentials instance with the provided auth credentials, and with the default
# values configured on the class.
#
# @param [String, Hash, Signet::OAuth2::Client] keyfile
# The keyfile can be provided as one of the following:
#
# * The path to a JSON keyfile (as a +String+)
# * The contents of a JSON keyfile (as a +Hash+)
# * A +Signet::OAuth2::Client+ object
# @param [Hash] options
# The options for configuring the credentials instance. The following is supported:
#
# * +:scope+ - the scope for the client
# * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
# * +:connection_builder+ - the connection builder to use for the client
# * +:default_connection+ - the default connection to use for the client
#
def initialize keyfile, options = {}
verify_keyfile_provided! keyfile
@project_id = options["project_id"] || options["project"]
@quota_project_id = options["quota_project_id"]
if keyfile.is_a? Signet::OAuth2::Client
update_from_signet keyfile
elsif keyfile.is_a? Hash
update_from_hash keyfile, options
else
update_from_filepath keyfile, options
end
CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
@project_id ||= CredentialsLoader.load_gcloud_project_id
@client.fetch_access_token!
@env_vars = nil
@paths = nil
@scope = nil
end
##
# Creates a new Credentials instance with auth credentials acquired by searching the
# environment variables and paths configured on the class, and with the default values
# configured on the class.
#
# The auth credentials are searched for in the following order:
#
# 1. configured environment variables (see {Credentials.env_vars})
# 2. configured default file paths (see {Credentials.paths})
# 3. application default (see {Google::Auth.get_application_default})
#
# @param [Hash] options
# The options for configuring the credentials instance. The following is supported:
#
# * +:scope+ - the scope for the client
# * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
# * +:connection_builder+ - the connection builder to use for the client
# * +:default_connection+ - the default connection to use for the client
#
# @return [Credentials]
#
def self.default options = {}
# First try to find keyfile file or json from environment variables.
client = from_env_vars options
# Second try to find keyfile file from known file paths.
client ||= from_default_paths options
# Finally get instantiated client from Google::Auth
client ||= from_application_default options
client
end
##
# @private Lookup Credentials from environment variables.
def self.from_env_vars options
env_vars.each do |env_var|
str = ENV[env_var]
next if str.nil?
return new str, options if ::File.file? str
return new ::JSON.parse(str), options rescue nil
end
nil
end
##
# @private Lookup Credentials from default file paths.
def self.from_default_paths options
paths
.select { |p| ::File.file? p }
.each do |file|
return new file, options
end
nil
end
##
# @private Lookup Credentials using Google::Auth.get_application_default.
def self.from_application_default options
scope = options[:scope] || self.scope
auth_opts = { target_audience: options[:target_audience] || target_audience }
client = Google::Auth.get_application_default scope, auth_opts
new client, options
end
private_class_method :from_env_vars,
:from_default_paths,
:from_application_default
protected
# Verify that the keyfile argument is provided.
def verify_keyfile_provided! keyfile
return unless keyfile.nil?
raise "The keyfile passed to Google::Auth::Credentials.new was nil."
end
# Verify that the keyfile argument is a file.
def verify_keyfile_exists! keyfile
exists = ::File.file? keyfile
raise "The keyfile '#{keyfile}' is not a valid file." unless exists
end
# Initializes the Signet client.
def init_client keyfile, connection_options = {}
client_opts = client_options keyfile
Signet::OAuth2::Client.new(client_opts)
.configure_connection(connection_options)
end
# returns a new Hash with string keys instead of symbol keys.
def stringify_hash_keys hash
Hash[hash.map { |k, v| [k.to_s, v] }]
end
def client_options options
# Keyfile options have higher priority over constructor defaults
options["token_credential_uri"] ||= self.class.token_credential_uri
options["audience"] ||= self.class.audience
options["scope"] ||= self.class.scope
options["target_audience"] ||= self.class.target_audience
if !Array(options["scope"]).empty? && options["target_audience"]
raise ArgumentError, "Cannot specify both scope and target_audience"
end
needs_scope = options["target_audience"].nil?
# client options for initializing signet client
{ token_credential_uri: options["token_credential_uri"],
audience: options["audience"],
scope: (needs_scope ? Array(options["scope"]) : nil),
target_audience: options["target_audience"],
issuer: options["client_email"],
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
end
def update_from_signet client
@project_id ||= client.project_id if client.respond_to? :project_id
@quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
@client = client
end
def update_from_hash hash, options
hash = stringify_hash_keys hash
hash["scope"] ||= options[:scope]
hash["target_audience"] ||= options[:target_audience]
@project_id ||= (hash["project_id"] || hash["project"])
@quota_project_id ||= hash["quota_project_id"]
@client = init_client hash, options
end
def update_from_filepath path, options
verify_keyfile_exists! path
json = JSON.parse ::File.read(path)
json["scope"] ||= options[:scope]
json["target_audience"] ||= options[:target_audience]
@project_id ||= (json["project_id"] || json["project"])
@quota_project_id ||= json["quota_project_id"]
@client = init_client json, options
end
end
end
end
googleauth-0.13.0/lib/googleauth/application_default.rb 0000644 0000041 0000041 00000007317 13674353263 023245 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth/compute_engine"
require "googleauth/default_credentials"
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
NOT_FOUND_ERROR = <<~ERROR_MESSAGE.freeze
Could not load the default credentials. Browse to
https://developers.google.com/accounts/docs/application-default-credentials
for more information
ERROR_MESSAGE
module_function
# Obtains the default credentials implementation to use in this
# environment.
#
# Use this to obtain the Application Default Credentials for accessing
# Google APIs. Application Default Credentials are described in detail
# at https://cloud.google.com/docs/authentication/production.
#
# If supplied, scope is used to create the credentials instance, when it can
# be applied. E.g, on google compute engine and for user credentials the
# scope is ignored.
#
# @param scope [string|array|nil] the scope(s) to access
# @param options [Hash] Connection options. These may be used to configure
# the `Faraday::Connection` used for outgoing HTTP requests. 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 for token
# refresh requests.
# * `:connection_builder` A `Proc` that creates and returns a
# connection to use for token refresh requests.
# * `:connection` The connection to use to determine whether GCE
# metadata credentials are available.
def get_application_default scope = nil, options = {}
creds = DefaultCredentials.from_env(scope, options) ||
DefaultCredentials.from_well_known_path(scope, options) ||
DefaultCredentials.from_system_default_path(scope, options)
return creds unless creds.nil?
unless GCECredentials.on_gce? options
# Clear cache of the result of GCECredentials.on_gce?
GCECredentials.unmemoize_all
raise NOT_FOUND_ERROR
end
GCECredentials.new
end
end
end
googleauth-0.13.0/lib/googleauth/web_user_authorizer.rb 0000644 0000041 0000041 00000027115 13674353263 023323 0 ustar www-data www-data # Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "multi_json"
require "googleauth/signet"
require "googleauth/user_authorizer"
require "googleauth/user_refresh"
require "securerandom"
module Google
module Auth
# Varation on {Google::Auth::UserAuthorizer} adapted for Rack based
# web applications.
#
# Example usage:
#
# get('/') do
# user_id = request.session['user_email']
# credentials = authorizer.get_credentials(user_id, request)
# if credentials.nil?
# redirect authorizer.get_authorization_url(user_id: user_id,
# request: request)
# end
# # Credentials are valid, can call APIs
# ...
# end
#
# get('/oauth2callback') do
# url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(
# request)
# redirect url
# end
#
# Instead of implementing the callback directly, applications are
# encouraged to use {Google::Auth::Web::AuthCallbackApp} instead.
#
# For rails apps, see {Google::Auth::ControllerHelpers}
#
# @see {Google::Auth::AuthCallbackApp}
# @see {Google::Auth::ControllerHelpers}
# @note Requires sessions are enabled
class WebUserAuthorizer < Google::Auth::UserAuthorizer
STATE_PARAM = "state".freeze
AUTH_CODE_KEY = "code".freeze
ERROR_CODE_KEY = "error".freeze
SESSION_ID_KEY = "session_id".freeze
CALLBACK_STATE_KEY = "g-auth-callback".freeze
CURRENT_URI_KEY = "current_uri".freeze
XSRF_KEY = "g-xsrf-token".freeze
SCOPE_KEY = "scope".freeze
NIL_REQUEST_ERROR = "Request is required.".freeze
NIL_SESSION_ERROR = "Sessions must be enabled".freeze
MISSING_AUTH_CODE_ERROR = "Missing authorization code in request".freeze
AUTHORIZATION_ERROR = "Authorization error: %s".freeze
INVALID_STATE_TOKEN_ERROR =
"State token does not match expected value".freeze
class << self
attr_accessor :default
end
# Handle the result of the oauth callback. This version defers the
# exchange of the code by temporarily stashing the results in the user's
# session. This allows apps to use the generic
# {Google::Auth::WebUserAuthorizer::CallbackApp} handler for the callback
# without any additional customization.
#
# Apps that wish to handle the callback directly should use
# {#handle_auth_callback} instead.
#
# @param [Rack::Request] request
# Current request
def self.handle_auth_callback_deferred request
callback_state, redirect_uri = extract_callback_state request
request.session[CALLBACK_STATE_KEY] = MultiJson.dump callback_state
redirect_uri
end
# Initialize the authorizer
#
# @param [Google::Auth::ClientID] client_id
# Configured ID & secret for this application
# @param [String, Array] scope
# Authorization scope to request
# @param [Google::Auth::Stores::TokenStore] token_store
# Backing storage for persisting user credentials
# @param [String] callback_uri
# URL (either absolute or relative) of the auth callback. Defaults
# to '/oauth2callback'
def initialize client_id, scope, token_store, callback_uri = nil
super client_id, scope, token_store, callback_uri
end
# Handle the result of the oauth callback. Exchanges the authorization
# code from the request and persists to storage.
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @param [Rack::Request] request
# Current request
# @return (Google::Auth::UserRefreshCredentials, String)
# credentials & next URL to redirect to
def handle_auth_callback user_id, request
callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
request
)
WebUserAuthorizer.validate_callback_state callback_state, request
credentials = get_and_store_credentials_from_code(
user_id: user_id,
code: callback_state[AUTH_CODE_KEY],
scope: callback_state[SCOPE_KEY],
base_url: request.url
)
[credentials, redirect_uri]
end
# Build the URL for requesting authorization.
#
# @param [String] login_hint
# Login hint if need to authorize a specific account. Should be a
# user's email address or unique profile ID.
# @param [Rack::Request] request
# Current request
# @param [String] redirect_to
# Optional URL to proceed to after authorization complete. Defaults to
# the current URL.
# @param [String, Array] scope
# Authorization scope to request. Overrides the instance scopes if
# not nil.
# @param [Hash] state
# Optional key-values to be returned to the oauth callback.
# @return [String]
# Authorization url
def get_authorization_url options = {}
options = options.dup
request = options[:request]
raise NIL_REQUEST_ERROR if request.nil?
raise NIL_SESSION_ERROR if request.session.nil?
state = options[:state] || {}
redirect_to = options[:redirect_to] || request.url
request.session[XSRF_KEY] = SecureRandom.base64
options[:state] = MultiJson.dump(state.merge(
SESSION_ID_KEY => request.session[XSRF_KEY],
CURRENT_URI_KEY => redirect_to
))
options[:base_url] = request.url
super options
end
# Fetch stored credentials for the user from the given request session.
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @param [Rack::Request] request
# Current request. Optional. If omitted, this will attempt to fall back
# on the base class behavior of reading from the token store.
# @param [Array, String] scope
# If specified, only returns credentials that have all the \
# requested scopes
# @return [Google::Auth::UserRefreshCredentials]
# Stored credentials, nil if none present
# @raise [Signet::AuthorizationError]
# May raise an error if an authorization code is present in the session
# and exchange of the code fails
def get_credentials user_id, request = nil, scope = nil
if request && request.session.key?(CALLBACK_STATE_KEY)
# Note - in theory, no need to check required scope as this is
# expected to be called immediately after a return from authorization
state_json = request.session.delete CALLBACK_STATE_KEY
callback_state = MultiJson.load state_json
WebUserAuthorizer.validate_callback_state callback_state, request
get_and_store_credentials_from_code(
user_id: user_id,
code: callback_state[AUTH_CODE_KEY],
scope: callback_state[SCOPE_KEY],
base_url: request.url
)
else
super user_id, scope
end
end
def self.extract_callback_state request
state = MultiJson.load(request[STATE_PARAM] || "{}")
redirect_uri = state[CURRENT_URI_KEY]
callback_state = {
AUTH_CODE_KEY => request[AUTH_CODE_KEY],
ERROR_CODE_KEY => request[ERROR_CODE_KEY],
SESSION_ID_KEY => state[SESSION_ID_KEY],
SCOPE_KEY => request[SCOPE_KEY]
}
[callback_state, redirect_uri]
end
# Verifies the results of an authorization callback
#
# @param [Hash] state
# Callback state
# @option state [String] AUTH_CODE_KEY
# The authorization code
# @option state [String] ERROR_CODE_KEY
# Error message if failed
# @param [Rack::Request] request
# Current request
def self.validate_callback_state state, request
raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[AUTH_CODE_KEY].nil?
if state[ERROR_CODE_KEY]
raise Signet::AuthorizationError,
format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
end
end
# Small Rack app which acts as the default callback handler for the app.
#
# To configure in Rails, add to routes.rb:
#
# match '/oauth2callback',
# to: Google::Auth::WebUserAuthorizer::CallbackApp,
# via: :all
#
# With Rackup, add to config.ru:
#
# map '/oauth2callback' do
# run Google::Auth::WebUserAuthorizer::CallbackApp
# end
#
# Or in a classic Sinatra app:
#
# get('/oauth2callback') do
# Google::Auth::WebUserAuthorizer::CallbackApp.call(env)
# end
#
# @see {Google::Auth::WebUserAuthorizer}
class CallbackApp
LOCATION_HEADER = "Location".freeze
REDIR_STATUS = 302
ERROR_STATUS = 500
# Handle a rack request. Simply stores the results the authorization
# in the session temporarily and redirects back to to the previously
# saved redirect URL. Credentials can be later retrieved by calling.
# {Google::Auth::Web::WebUserAuthorizer#get_credentials}
#
# See {Google::Auth::Web::WebUserAuthorizer#get_authorization_uri}
# for how to initiate authorization requests.
#
# @param [Hash] env
# Rack environment
# @return [Array]
# HTTP response
def self.call env
request = Rack::Request.new env
return_url = WebUserAuthorizer.handle_auth_callback_deferred request
if return_url
[REDIR_STATUS, { LOCATION_HEADER => return_url }, []]
else
[ERROR_STATUS, {}, ["No return URL is present in the request."]]
end
end
def call env
self.class.call env
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/signet.rb 0000644 0000041 0000041 00000010320 13674353263 020513 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "signet/oauth_2/client"
module Signet
# OAuth2 supports OAuth2 authentication.
module OAuth2
AUTH_METADATA_KEY = :authorization
# Signet::OAuth2::Client creates an OAuth2 client
#
# This reopens Client to add #apply and #apply! methods which update a
# hash with the fetched authentication token.
class Client
def configure_connection options
@connection_info =
options[:connection_builder] || options[:default_connection]
self
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)
a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
end
# Returns a clone of a_hash updated with the authentication token
def apply a_hash, opts = {}
a_copy = a_hash.clone
apply! a_copy, opts
a_copy
end
# Returns a reference to the #apply method, suitable for passing as
# a closure
def updater_proc
lambda(&method(:apply))
end
def on_refresh &block
@refresh_listeners = [] unless defined? @refresh_listeners
@refresh_listeners << block
end
alias orig_fetch_access_token! fetch_access_token!
def fetch_access_token! options = {}
unless options[:connection]
connection = build_default_connection
options = options.merge connection: connection if connection
end
info = retry_with_error do
orig_fetch_access_token! options
end
notify_refresh_listeners
info
end
def notify_refresh_listeners
listeners = defined?(@refresh_listeners) ? @refresh_listeners : []
listeners.each do |block|
block.call self
end
end
def build_default_connection
if !defined?(@connection_info)
nil
elsif @connection_info.respond_to? :call
@connection_info.call
else
@connection_info
end
end
def retry_with_error max_retry_count = 5
retry_count = 0
begin
yield
rescue StandardError => e
raise e if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
if retry_count < max_retry_count
retry_count += 1
sleep retry_count * 0.3
retry
else
msg = "Unexpected error: #{e.inspect}"
raise Signet::AuthorizationError, msg
end
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/stores/ 0000755 0000041 0000041 00000000000 13674353263 020220 5 ustar www-data www-data googleauth-0.13.0/lib/googleauth/stores/file_token_store.rb 0000644 0000041 0000041 00000004631 13674353263 024104 0 ustar www-data www-data # Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "yaml/store"
require "googleauth/token_store"
module Google
module Auth
module Stores
# Implementation of user token storage backed by a local YAML file
class FileTokenStore < Google::Auth::TokenStore
# Create a new store with the supplied file.
#
# @param [String, File] file
# Path to storage file
def initialize options = {}
path = options[:file]
@store = YAML::Store.new path
end
# (see Google::Auth::Stores::TokenStore#load)
def load id
@store.transaction { @store[id] }
end
# (see Google::Auth::Stores::TokenStore#store)
def store id, token
@store.transaction { @store[id] = token }
end
# (see Google::Auth::Stores::TokenStore#delete)
def delete id
@store.transaction { @store.delete id }
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/stores/redis_token_store.rb 0000644 0000041 0000041 00000006472 13674353263 024300 0 ustar www-data www-data # Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "redis"
require "googleauth/token_store"
module Google
module Auth
module Stores
# Implementation of user token storage backed by Redis. Tokens
# are stored as JSON using the supplied key, prefixed with
# `g-user-token:`
class RedisTokenStore < Google::Auth::TokenStore
DEFAULT_KEY_PREFIX = "g-user-token:".freeze
# Create a new store with the supplied redis client.
#
# @param [::Redis, String] redis
# Initialized redis client to connect to.
# @param [String] prefix
# Prefix for keys in redis. Defaults to 'g-user-token:'
# @note If no redis instance is provided, a new one is created and
# the options passed through. You may include any other keys accepted
# by `Redis.new`
def initialize options = {}
redis = options.delete :redis
prefix = options.delete :prefix
@redis = case redis
when Redis
redis
else
Redis.new options
end
@prefix = prefix || DEFAULT_KEY_PREFIX
end
# (see Google::Auth::Stores::TokenStore#load)
def load id
key = key_for id
@redis.get key
end
# (see Google::Auth::Stores::TokenStore#store)
def store id, token
key = key_for id
@redis.set key, token
end
# (see Google::Auth::Stores::TokenStore#delete)
def delete id
key = key_for id
@redis.del key
end
private
# Generate a redis key from a token ID
#
# @param [String] id
# ID of the token
# @return [String]
# Redis key
def key_for id
@prefix + id
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/id_tokens.rb 0000644 0000041 0000041 00000022735 13674353263 021216 0 ustar www-data www-data # frozen_string_literal: true
# Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth/id_tokens/errors"
require "googleauth/id_tokens/key_sources"
require "googleauth/id_tokens/verifier"
module Google
module Auth
##
# ## Verifying Google ID tokens
#
# This module verifies ID tokens issued by Google. This can be used to
# authenticate signed-in users using OpenID Connect. See
# https://developers.google.com/identity/sign-in/web/backend-auth for more
# information.
#
# ### Basic usage
#
# To verify an ID token issued by Google accounts:
#
# payload = Google::Auth::IDTokens.verify_oidc the_token,
# aud: "my-app-client-id"
#
# If verification succeeds, you will receive the token's payload as a hash.
# If verification fails, an exception (normally a subclass of
# {Google::Auth::IDTokens::VerificationError}) will be raised.
#
# To verify an ID token issued by the Google identity-aware proxy (IAP):
#
# payload = Google::Auth::IDTokens.verify_iap the_token,
# aud: "my-app-client-id"
#
# These methods will automatically download and cache the Google public
# keys necessary to verify these tokens. They will also automatically
# verify the issuer (`iss`) field for their respective types of ID tokens.
#
# ### Advanced usage
#
# If you want to provide your own public keys, either by pointing at a
# custom URI or by providing the key data directly, use the Verifier class
# and pass in a key source.
#
# To point to a custom URI that returns a JWK set:
#
# source = Google::Auth::IDTokens::JwkHttpKeySource.new "https://example.com/jwk"
# verifier = Google::Auth::IDTokens::Verifier.new key_source: source
# payload = verifier.verify the_token, aud: "my-app-client-id"
#
# To provide key data directly:
#
# jwk_data = {
# keys: [
# {
# alg: "ES256",
# crv: "P-256",
# kid: "LYyP2g",
# kty: "EC",
# use: "sig",
# x: "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU",
# y: "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI"
# }
# ]
# }
# source = Google::Auth::IDTokens::StaticKeySource.from_jwk_set jwk_data
# verifier = Google::Auth::IDTokens::Verifier key_source: source
# payload = verifier.verify the_token, aud: "my-app-client-id"
#
module IDTokens
##
# A list of issuers expected for Google OIDC-issued tokens.
#
# @return [Array]
#
OIDC_ISSUERS = ["accounts.google.com", "https://accounts.google.com"].freeze
##
# A list of issuers expected for Google IAP-issued tokens.
#
# @return [Array]
#
IAP_ISSUERS = ["https://cloud.google.com/iap"].freeze
##
# The URL for Google OAuth2 V3 public certs
#
# @return [String]
#
OAUTH2_V3_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs"
##
# The URL for Google IAP public keys
#
# @return [String]
#
IAP_JWK_URL = "https://www.gstatic.com/iap/verify/public_key-jwk"
class << self
##
# The key source providing public keys that can be used to verify
# ID tokens issued by Google OIDC.
#
# @return [Google::Auth::IDTokens::JwkHttpKeySource]
#
def oidc_key_source
@oidc_key_source ||= JwkHttpKeySource.new OAUTH2_V3_CERTS_URL
end
##
# The key source providing public keys that can be used to verify
# ID tokens issued by Google IAP.
#
# @return [Google::Auth::IDTokens::JwkHttpKeySource]
#
def iap_key_source
@iap_key_source ||= JwkHttpKeySource.new IAP_JWK_URL
end
##
# Reset all convenience key sources. Used for testing.
# @private
#
def forget_sources!
@oidc_key_source = @iap_key_source = nil
self
end
##
# A convenience method that verifies a token allegedly issued by Google
# OIDC.
#
# @param token [String] The ID token to verify
# @param aud [String,Array,nil] The expected audience. At least
# one `aud` field in the token must match at least one of the
# provided audiences, or the verification will fail with
# {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the
# default), no audience checking is performed.
# @param azp [String,Array,nil] The expected authorized party
# (azp). At least one `azp` field in the token must match at least
# one of the provided values, or the verification will fail with
# {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil`
# (the default), no azp checking is performed.
# @param aud [String,Array,nil] The expected audience. At least
# one `iss` field in the token must match at least one of the
# provided issuers, or the verification will fail with
# {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer
# checking is performed. Default is to check against {OIDC_ISSUERS}.
#
# @return [Hash] The decoded token payload.
# @raise [KeySourceError] if the key source failed to obtain public keys
# @raise [VerificationError] if the token verification failed.
# Additional data may be available in the error subclass and message.
#
def verify_oidc token,
aud: nil,
azp: nil,
iss: OIDC_ISSUERS
verifier = Verifier.new key_source: oidc_key_source,
aud: aud,
azp: azp,
iss: iss
verifier.verify token
end
##
# A convenience method that verifies a token allegedly issued by Google
# IAP.
#
# @param token [String] The ID token to verify
# @param aud [String,Array,nil] The expected audience. At least
# one `aud` field in the token must match at least one of the
# provided audiences, or the verification will fail with
# {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the
# default), no audience checking is performed.
# @param azp [String,Array,nil] The expected authorized party
# (azp). At least one `azp` field in the token must match at least
# one of the provided values, or the verification will fail with
# {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil`
# (the default), no azp checking is performed.
# @param aud [String,Array,nil] The expected audience. At least
# one `iss` field in the token must match at least one of the
# provided issuers, or the verification will fail with
# {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer
# checking is performed. Default is to check against {IAP_ISSUERS}.
#
# @return [Hash] The decoded token payload.
# @raise [KeySourceError] if the key source failed to obtain public keys
# @raise [VerificationError] if the token verification failed.
# Additional data may be available in the error subclass and message.
#
def verify_iap token,
aud: nil,
azp: nil,
iss: IAP_ISSUERS
verifier = Verifier.new key_source: iap_key_source,
aud: aud,
azp: azp,
iss: iss
verifier.verify token
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/service_account.rb 0000644 0000041 0000041 00000020345 13674353263 022406 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth/signet"
require "googleauth/credentials_loader"
require "googleauth/json_key_reader"
require "jwt"
require "multi_json"
require "stringio"
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
# Authenticates requests using Google's Service Account credentials via an
# OAuth access token.
#
# This class allows authorizing requests for service accounts directly
# from credentials from a json key file downloaded from the developer
# console (via 'Generate new Json Key').
#
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
class ServiceAccountCredentials < Signet::OAuth2::Client
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
extend CredentialsLoader
extend JsonKeyReader
attr_reader :project_id
attr_reader :quota_project_id
# Creates a ServiceAccountCredentials.
#
# @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 self.make_creds options = {}
json_key_io, scope, target_audience = options.values_at :json_key_io, :scope, :target_audience
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
if json_key_io
private_key, client_email, project_id, quota_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]
quota_project_id = nil
end
project_id ||= CredentialsLoader.load_gcloud_project_id
new(token_credential_uri: TOKEN_CRED_URI,
audience: TOKEN_CRED_URI,
scope: scope,
target_audience: target_audience,
issuer: client_email,
signing_key: OpenSSL::PKey::RSA.new(private_key),
project_id: project_id,
quota_project_id: quota_project_id)
.configure_connection(options)
end
# Handles certain escape sequences that sometimes appear in input.
# Specifically, interprets the "\n" sequence for newline, and removes
# enclosing quotes.
def self.unescape str
str = str.gsub '\n', "\n"
str = str[1..-2] if str.start_with?('"') && str.end_with?('"')
str
end
def initialize options = {}
@project_id = options[:project_id]
@quota_project_id = options[:quota_project_id]
super options
end
# Extends the base class.
#
# If scope(s) is not set, it creates a transient
# ServiceAccountJwtHeaderCredentials instance and uses that to
# authenticate instead.
def apply! a_hash, opts = {}
# Use the base implementation if scopes are set
unless scope.nil? && target_audience.nil?
super
return
end
# Use the ServiceAccountJwtHeaderCredentials using the same cred values
# if no scopes are set.
cred_json = {
private_key: @signing_key.to_s,
client_email: @issuer
}
alt_clz = ServiceAccountJwtHeaderCredentials
key_io = StringIO.new MultiJson.dump(cred_json)
alt = alt_clz.make_creds json_key_io: key_io
alt.apply! a_hash
end
end
# Authenticates requests using Google's Service Account credentials via
# JWT Header.
#
# This class allows authorizing requests for service accounts directly
# from credentials from a json key file downloaded from the developer
# console (via 'Generate new Json Key'). It is not part of any OAuth2
# flow, rather it creates a JWT and sends that as a credential.
#
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
class ServiceAccountJwtHeaderCredentials
JWT_AUD_URI_KEY = :jwt_aud_uri
AUTH_METADATA_KEY = Signet::OAuth2::AUTH_METADATA_KEY
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
SIGNING_ALGORITHM = "RS256".freeze
EXPIRY = 60
extend CredentialsLoader
extend JsonKeyReader
attr_reader :project_id
attr_reader :quota_project_id
# make_creds proxies the construction of a credentials instance
#
# make_creds is used by the methods in CredentialsLoader.
#
# By default, it calls #new with 2 args, the second one being an
# optional scope. Here's the constructor only has one param, so
# we modify make_creds to reflect this.
def self.make_creds *args
new json_key_io: args[0][:json_key_io]
end
# Initializes a ServiceAccountJwtHeaderCredentials.
#
# @param json_key_io [IO] an IO from which the JSON key can be read
def initialize options = {}
json_key_io = options[:json_key_io]
if json_key_io
@private_key, @issuer, @project_id, @quota_project_id =
self.class.read_json_key json_key_io
else
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
@quota_project_id = nil
end
@project_id ||= CredentialsLoader.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
# hash.
#
# The jwt token is used as the value of a 'Bearer '.
def apply! a_hash, opts = {}
jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
return a_hash if jwt_aud_uri.nil?
jwt_token = new_jwt_token jwt_aud_uri, opts
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
a_hash
end
# Returns a clone of a_hash updated with the authoriation header
def apply a_hash, opts = {}
a_copy = a_hash.clone
apply! a_copy, opts
a_copy
end
# Returns a reference to the #apply method, suitable for passing as
# a closure
def updater_proc
lambda(&method(:apply))
end
protected
# Creates a jwt uri token.
def new_jwt_token jwt_aud_uri, options = {}
now = Time.new
skew = options[:skew] || 60
assertion = {
"iss" => @issuer,
"sub" => @issuer,
"aud" => jwt_aud_uri,
"exp" => (now + EXPIRY).to_i,
"iat" => (now - skew).to_i
}
JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
end
end
end
end
googleauth-0.13.0/lib/googleauth/default_credentials.rb 0000644 0000041 0000041 00000007050 13674353263 023231 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "multi_json"
require "stringio"
require "googleauth/credentials_loader"
require "googleauth/service_account"
require "googleauth/user_refresh"
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
# DefaultCredentials is used to preload the credentials file, to determine
# which type of credentials should be loaded.
class DefaultCredentials
extend CredentialsLoader
# override CredentialsLoader#make_creds to use the class determined by
# loading the json.
def self.make_creds options = {}
json_key_io = options[:json_key_io]
if json_key_io
json_key, clz = determine_creds_class json_key_io
warn_if_cloud_sdk_credentials json_key["client_id"]
io = StringIO.new MultiJson.dump(json_key)
clz.make_creds options.merge(json_key_io: io)
else
warn_if_cloud_sdk_credentials ENV[CredentialsLoader::CLIENT_ID_VAR]
clz = read_creds
clz.make_creds options
end
end
def self.read_creds
env_var = CredentialsLoader::ACCOUNT_TYPE_VAR
type = ENV[env_var]
raise "#{env_var} is undefined in env" unless type
case type
when "service_account"
ServiceAccountCredentials
when "authorized_user"
UserRefreshCredentials
else
raise "credentials type '#{type}' is not supported"
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
key = "type"
raise "the json is missing the '#{key}' field" unless json_key.key? key
type = json_key[key]
case type
when "service_account"
[json_key, ServiceAccountCredentials]
when "authorized_user"
[json_key, UserRefreshCredentials]
else
raise "credentials type '#{type}' is not supported"
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/token_store.rb 0000644 0000041 0000041 00000005005 13674353263 021562 0 ustar www-data www-data # Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Google
module Auth
# Interface definition for token stores. It is not required that
# implementations inherit from this class. It is provided for documentation
# purposes to illustrate the API contract.
class TokenStore
class << self
attr_accessor :default
end
# Load the token data from storage for the given ID.
#
# @param [String] id
# ID of token data to load.
# @return [String]
# The loaded token data.
def load _id
raise "Not implemented"
end
# Put the token data into storage for the given ID.
#
# @param [String] id
# ID of token data to store.
# @param [String] token
# The token data to store.
def store _id, _token
raise "Not implemented"
end
# Remove the token data from storage for the given ID.
#
# @param [String] id
# ID of the token data to delete
def delete _id
raise "Not implemented"
end
end
end
end
googleauth-0.13.0/lib/googleauth/credentials_loader.rb 0000644 0000041 0000041 00000022135 13674353263 023054 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "memoist"
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
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
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?
return 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}", &: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
googleauth-0.13.0/lib/googleauth/iam.rb 0000644 0000041 0000041 00000005360 13674353263 020000 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth/signet"
require "googleauth/credentials_loader"
require "multi_json"
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
# Authenticates requests using IAM credentials.
class IAMCredentials
SELECTOR_KEY = "x-goog-iam-authority-selector".freeze
TOKEN_KEY = "x-goog-iam-authorization-token".freeze
# Initializes an IAMCredentials.
#
# @param selector the IAM selector.
# @param token the IAM token.
def initialize selector, token
raise TypeError unless selector.is_a? String
raise TypeError unless token.is_a? String
@selector = selector
@token = token
end
# Adds the credential fields to the hash.
def apply! a_hash
a_hash[SELECTOR_KEY] = @selector
a_hash[TOKEN_KEY] = @token
a_hash
end
# Returns a clone of a_hash updated with the authoriation header
def apply a_hash
a_copy = a_hash.clone
apply! a_copy
a_copy
end
# Returns a reference to the #apply method, suitable for passing as
# a closure
def updater_proc
lambda(&method(:apply))
end
end
end
end
googleauth-0.13.0/lib/googleauth/user_refresh.rb 0000644 0000041 0000041 00000012260 13674353263 021723 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth/signet"
require "googleauth/credentials_loader"
require "googleauth/scope_util"
require "multi_json"
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
# Authenticates requests using User Refresh credentials.
#
# This class allows authorizing requests from user refresh tokens.
#
# This the end of the result of a 3LO flow. E.g, the end result of
# 'gcloud auth login' saves a file with these contents in well known
# location
#
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
class UserRefreshCredentials < Signet::OAuth2::Client
TOKEN_CRED_URI = "https://oauth2.googleapis.com/token".freeze
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.
#
# @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 self.make_creds options = {}
json_key_io, scope = options.values_at :json_key_io, :scope
user_creds = 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],
"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)
.configure_connection(options)
end
# Reads the client_id, client_secret and refresh_token fields from the
# JSON key.
def self.read_json_key json_key_io
json_key = MultiJson.load json_key_io.read
wanted = ["client_id", "client_secret", "refresh_token"]
wanted.each do |key|
raise "the json is missing the #{key} field" unless json_key.key? key
end
json_key
end
def initialize options = {}
options ||= {}
options[:token_credential_uri] ||= TOKEN_CRED_URI
options[:authorization_uri] ||= AUTHORIZATION_URI
@project_id = options[:project_id]
@project_id ||= CredentialsLoader.load_gcloud_project_id
super options
end
# Revokes the credential
def revoke! options = {}
c = options[:connection] || Faraday.default_connection
retry_with_error do
resp = c.post(REVOKE_TOKEN_URI, token: refresh_token || access_token)
case resp.status
when 200
self.access_token = nil
self.refresh_token = nil
self.expires_at = 0
else
raise(Signet::AuthorizationError,
"Unexpected error code #{resp.status}")
end
end
end
# Verifies that a credential grants the requested scope
#
# @param [Array, String] required_scope
# Scope to verify
# @return [Boolean]
# True if scope is granted
def includes_scope? required_scope
missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) -
Google::Auth::ScopeUtil.normalize(scope)
missing_scope.empty?
end
end
end
end
googleauth-0.13.0/lib/googleauth/compute_engine.rb 0000644 0000041 0000041 00000011034 13674353263 022226 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "faraday"
require "googleauth/signet"
require "memoist"
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
NO_METADATA_SERVER_ERROR = <<~ERROR.freeze
Error code 404 trying to get security access token
from Compute Engine metadata for the default service account. This
may be because the virtual machine instance does not have permission
scopes specified.
ERROR
UNEXPECTED_ERROR_SUFFIX = <<~ERROR.freeze
trying to get security access token from Compute Engine metadata for
the default service account
ERROR
# Extends Signet::OAuth2::Client so that the auth token is obtained from
# the GCE metadata server.
class GCECredentials < Signet::OAuth2::Client
# The IP Address is used in the URIs to speed up failures on non-GCE
# systems.
COMPUTE_AUTH_TOKEN_URI =
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
COMPUTE_ID_TOKEN_URI =
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
class << self
extend Memoist
# Detect if this appear to be a GCE instance, by checking if metadata
# is available.
def on_gce? options = {}
# TODO: This should use google-cloud-env instead.
c = options[:connection] || Faraday.default_connection
headers = { "Metadata-Flavor" => "Google" }
resp = c.get COMPUTE_CHECK_URI, nil, headers do |req|
req.options.timeout = 1.0
req.options.open_timeout = 0.1
end
return false unless resp.status == 200
resp.headers["Metadata-Flavor"] == "Google"
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
false
end
memoize :on_gce?
end
# Overrides the super class method to change how access tokens are
# fetched.
def fetch_access_token options = {}
c = options[:connection] || Faraday.default_connection
retry_with_error do
uri = target_audience ? COMPUTE_ID_TOKEN_URI : COMPUTE_AUTH_TOKEN_URI
query = target_audience ? { "audience" => target_audience, "format" => "full" } : nil
headers = { "Metadata-Flavor" => "Google" }
resp = c.get uri, query, headers
case resp.status
when 200
content_type = resp.headers["content-type"]
if content_type == "text/html"
{ (target_audience ? "id_token" : "access_token") => resp.body }
else
Signet::OAuth2.parse_credentials resp.body, content_type
end
when 404
raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
else
msg = "Unexpected error code #{resp.status}" \
"#{UNEXPECTED_ERROR_SUFFIX}"
raise Signet::AuthorizationError, msg
end
end
end
end
end
end
googleauth-0.13.0/lib/googleauth/user_authorizer.rb 0000644 0000041 0000041 00000026472 13674353263 022473 0 ustar www-data www-data # Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "uri"
require "multi_json"
require "googleauth/signet"
require "googleauth/user_refresh"
module Google
module Auth
# Handles an interactive 3-Legged-OAuth2 (3LO) user consent authorization.
#
# Example usage for a simple command line app:
#
# credentials = authorizer.get_credentials(user_id)
# if credentials.nil?
# url = authorizer.get_authorization_url(
# base_url: OOB_URI)
# puts "Open the following URL in the browser and enter the " +
# "resulting code after authorization"
# puts url
# code = gets
# credentials = authorizer.get_and_store_credentials_from_code(
# user_id: user_id, code: code, base_url: OOB_URI)
# end
# # Credentials ready to use, call APIs
# ...
class UserAuthorizer
MISMATCHED_CLIENT_ID_ERROR =
"Token client ID of %s does not match configured client id %s".freeze
NIL_CLIENT_ID_ERROR = "Client id can not be nil.".freeze
NIL_SCOPE_ERROR = "Scope can not be nil.".freeze
NIL_USER_ID_ERROR = "User ID can not be nil.".freeze
NIL_TOKEN_STORE_ERROR = "Can not call method if token store is nil".freeze
MISSING_ABSOLUTE_URL_ERROR =
'Absolute base url required for relative callback url "%s"'.freeze
# Initialize the authorizer
#
# @param [Google::Auth::ClientID] client_id
# Configured ID & secret for this application
# @param [String, Array] scope
# Authorization scope to request
# @param [Google::Auth::Stores::TokenStore] token_store
# Backing storage for persisting user credentials
# @param [String] callback_uri
# URL (either absolute or relative) of the auth callback.
# Defaults to '/oauth2callback'
def initialize client_id, scope, token_store, callback_uri = nil
raise NIL_CLIENT_ID_ERROR if client_id.nil?
raise NIL_SCOPE_ERROR if scope.nil?
@client_id = client_id
@scope = Array(scope)
@token_store = token_store
@callback_uri = callback_uri || "/oauth2callback"
end
# Build the URL for requesting authorization.
#
# @param [String] login_hint
# Login hint if need to authorize a specific account. Should be a
# user's email address or unique profile ID.
# @param [String] state
# Opaque state value to be returned to the oauth callback.
# @param [String] base_url
# Absolute URL to resolve the configured callback uri against. Required
# if the configured callback uri is a relative.
# @param [String, Array] scope
# Authorization scope to request. Overrides the instance scopes if not
# nil.
# @return [String]
# Authorization url
def get_authorization_url options = {}
scope = options[:scope] || @scope
credentials = UserRefreshCredentials.new(
client_id: @client_id.id,
client_secret: @client_id.secret,
scope: scope
)
redirect_uri = redirect_uri_for options[:base_url]
url = credentials.authorization_uri(access_type: "offline",
redirect_uri: redirect_uri,
approval_prompt: "force",
state: options[:state],
include_granted_scopes: true,
login_hint: options[:login_hint])
url.to_s
end
# Fetch stored credentials for the user.
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @param [Array, String] scope
# If specified, only returns credentials that have all
# the requested scopes
# @return [Google::Auth::UserRefreshCredentials]
# Stored credentials, nil if none present
def get_credentials user_id, scope = nil
saved_token = stored_token user_id
return nil if saved_token.nil?
data = MultiJson.load saved_token
if data.fetch("client_id", @client_id.id) != @client_id.id
raise format(MISMATCHED_CLIENT_ID_ERROR,
data["client_id"], @client_id.id)
end
credentials = UserRefreshCredentials.new(
client_id: @client_id.id,
client_secret: @client_id.secret,
scope: data["scope"] || @scope,
access_token: data["access_token"],
refresh_token: data["refresh_token"],
expires_at: data.fetch("expiration_time_millis", 0) / 1000
)
scope ||= @scope
return monitor_credentials user_id, credentials if credentials.includes_scope? scope
nil
end
# Exchanges an authorization code returned in the oauth callback
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @param [String] code
# The authorization code from the OAuth callback
# @param [String, Array] scope
# Authorization scope requested. Overrides the instance
# scopes if not nil.
# @param [String] base_url
# Absolute URL to resolve the configured callback uri against.
# Required if the configured
# callback uri is a relative.
# @return [Google::Auth::UserRefreshCredentials]
# Credentials if exchange is successful
def get_credentials_from_code options = {}
user_id = options[:user_id]
code = options[:code]
scope = options[:scope] || @scope
base_url = options[:base_url]
credentials = UserRefreshCredentials.new(
client_id: @client_id.id,
client_secret: @client_id.secret,
redirect_uri: redirect_uri_for(base_url),
scope: scope
)
credentials.code = code
credentials.fetch_access_token!({})
monitor_credentials user_id, credentials
end
# Exchanges an authorization code returned in the oauth callback.
# Additionally, stores the resulting credentials in the token store if
# the exchange is successful.
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @param [String] code
# The authorization code from the OAuth callback
# @param [String, Array] scope
# Authorization scope requested. Overrides the instance
# scopes if not nil.
# @param [String] base_url
# Absolute URL to resolve the configured callback uri against.
# Required if the configured
# callback uri is a relative.
# @return [Google::Auth::UserRefreshCredentials]
# Credentials if exchange is successful
def get_and_store_credentials_from_code options = {}
credentials = get_credentials_from_code options
store_credentials options[:user_id], credentials
end
# Revokes a user's credentials. This both revokes the actual
# grant as well as removes the token from the token store.
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
def revoke_authorization user_id
credentials = get_credentials user_id
if credentials
begin
@token_store.delete user_id
ensure
credentials.revoke!
end
end
nil
end
# Store credentials for a user. Generally not required to be
# called directly, but may be used to migrate tokens from one
# store to another.
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @param [Google::Auth::UserRefreshCredentials] credentials
# Credentials to store.
def store_credentials user_id, credentials
json = MultiJson.dump(
client_id: credentials.client_id,
access_token: credentials.access_token,
refresh_token: credentials.refresh_token,
scope: credentials.scope,
expiration_time_millis: credentials.expires_at.to_i * 1000
)
@token_store.store user_id, json
credentials
end
private
# @private Fetch stored token with given user_id
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @return [String] The saved token from @token_store
def stored_token user_id
raise NIL_USER_ID_ERROR if user_id.nil?
raise NIL_TOKEN_STORE_ERROR if @token_store.nil?
@token_store.load user_id
end
# Begin watching a credential for refreshes so the access token can be
# saved.
#
# @param [String] user_id
# Unique ID of the user for loading/storing credentials.
# @param [Google::Auth::UserRefreshCredentials] credentials
# Credentials to store.
def monitor_credentials user_id, credentials
credentials.on_refresh do |cred|
store_credentials user_id, cred
end
credentials
end
# Resolve the redirect uri against a base.
#
# @param [String] base_url
# Absolute URL to resolve the callback against if necessary.
# @return [String]
# Redirect URI
def redirect_uri_for base_url
return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
URI.join(base_url, @callback_uri).to_s
end
# Check if URI is Google's postmessage flow (not a valid redirect_uri by spec, but allowed)
def uri_is_postmessage? uri
uri.to_s.casecmp("postmessage").zero?
end
end
end
end
googleauth-0.13.0/lib/googleauth/json_key_reader.rb 0000644 0000041 0000041 00000004274 13674353263 022400 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
# JsonKeyReader contains the behaviour used to read private key and
# client email fields from the service account
module JsonKeyReader
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"],
json_key["project_id"],
json_key["quota_project_id"]
]
end
end
end
end
googleauth-0.13.0/lib/googleauth.rb 0000644 0000041 0000041 00000003370 13674353263 017231 0 ustar www-data www-data # Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "googleauth/application_default"
require "googleauth/client_id"
require "googleauth/credentials"
require "googleauth/default_credentials"
require "googleauth/id_tokens"
require "googleauth/user_authorizer"
require "googleauth/web_user_authorizer"
googleauth-0.13.0/Gemfile 0000755 0000041 0000041 00000001156 13674353263 015276 0 ustar www-data www-data source "https://rubygems.org"
# Specify your gem's dependencies in googleauth.gemspec
gemspec
group :development do
gem "bundler", ">= 1.9"
gem "coveralls", "~> 0.7"
gem "fakefs", "~> 0.6"
gem "fakeredis", "~> 0.5"
gem "google-style", "~> 1.24.0"
gem "logging", "~> 2.0"
gem "minitest", "~> 5.14"
gem "minitest-focus", "~> 1.1"
gem "rack-test", "~> 0.6"
gem "rake", "~> 13.0"
gem "redis", "~> 3.2"
gem "rspec", "~> 3.0"
gem "simplecov", "~> 0.9"
gem "sinatra"
gem "webmock", "~> 3.8"
end
platforms :jruby do
group :development do
end
end
gem "faraday", "~> 0.17"
gem "gems", "~> 1.2"
googleauth-0.13.0/.github/ 0000755 0000041 0000041 00000000000 13674353263 015335 5 ustar www-data www-data googleauth-0.13.0/.github/CONTRIBUTING.md 0000644 0000041 0000041 00000007147 13674353263 017577 0 ustar www-data www-data # How to become a contributor and submit your own code
## Contributor License Agreements
We'd love to accept your sample apps and patches! Before we can take them, we
have to jump a couple of legal hurdles.
Please fill out either the individual or corporate Contributor License Agreement
(CLA).
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual CLA].
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA].
[individual CLA]: http://code.google.com/legal/individual-cla-v1.0.html
[corporate CLA]: http://code.google.com/legal/corporate-cla-v1.0.html
Follow either of the two links above to access the appropriate CLA and
instructions for how to sign and return it. Once we receive it, we'll be able to
accept your pull requests.
## Issue reporting
* Check that the issue has not already been reported.
* Check that the issue has not already been fixed in the latest code
(a.k.a. `master`).
* Be clear, concise and precise in your description of the problem.
* Open an issue with a descriptive title and a summary in grammatically correct,
complete sentences.
* Include any relevant code to the issue summary.
## Pull requests
* Read [how to properly contribute to open source projects on Github][2].
* Fork the project.
* Use a topic/feature branch to easily amend a pull request later, if necessary.
* Write [good commit messages][3].
* Use the same coding conventions as the rest of the project.
* Commit and push until you are happy with your contribution.
* Make sure to add tests for it. This is important so I don't break it
in a future version unintentionally.
* Add an entry to the [Changelog](CHANGELOG.md) accordingly. See [changelog entry format](#changelog-entry-format).
* Please try not to mess with the Rakefile, version, or history. If you want to
have your own version, or is otherwise necessary, that is fine, but please
isolate to its own commit so I can cherry-pick around it.
* Make sure the test suite is passing and the code you wrote doesn't produce
RuboCop offenses.
* [Squash related commits together][5].
* Open a [pull request][4] that relates to *only* one subject with a clear title
and description in grammatically correct, complete sentences.
### Changelog entry format
Here are a few examples:
```
* makes the scope parameter's optional in all APIs. (@tbetbetbe[])
* [#14](https://github.com/google/google-auth-library-ruby/issues/14): ADC Support for JWT Service Tokens. ([@tbetbetbe][])
```
* Mark it up in [Markdown syntax][6].
* The entry line should start with `* ` (an asterisk and a space).
* If the change has a related GitHub issue (e.g. a bug fix for a reported issue), put a link to the issue as `[#123](https://github.com/google/google-auth-library-ruby/issues/11): `.
* Describe the brief of the change. The sentence should end with a punctuation.
* At the end of the entry, add an implicit link to your GitHub user page as `([@username][])`.
* If this is your first contribution to google-auth-library-ruby project, add a link definition for the implicit link to the bottom of the changelog as `[@username]: https://github.com/username`.
[1]: https://github.com/google/google-auth-ruby-library/issues
[2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request
[3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[4]: https://help.github.com/articles/using-pull-requests
[5]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html
[6]: http://daringfireball.net/projects/markdown/syntax
googleauth-0.13.0/.github/ISSUE_TEMPLATE/ 0000755 0000041 0000041 00000000000 13674353263 017520 5 ustar www-data www-data googleauth-0.13.0/.github/ISSUE_TEMPLATE/support_request.md 0000644 0000041 0000041 00000000540 13674353263 023325 0 ustar www-data www-data ---
name: Support request
about: If you have a support contract with Google, please create an issue in the Google Cloud Support console.
---
**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response.
googleauth-0.13.0/.github/ISSUE_TEMPLATE/feature_request.md 0000644 0000041 0000041 00000001510 13674353263 023242 0 ustar www-data www-data ---
name: Feature request
about: Suggest an idea for this library
---
Thanks for stopping by to let us know something could be better!
**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
googleauth-0.13.0/.github/ISSUE_TEMPLATE/bug_report.md 0000644 0000041 0000041 00000001711 13674353263 022212 0 ustar www-data www-data ---
name: Bug report
about: Create a report to help us improve
---
Thanks for stopping by to let us know something could be better!
**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response.
Please run down the following list and make sure you've tried the usual "quick fixes":
- Search the issues already opened: https://github.com/googleapis/google-auth-library-ruby/issues
- Search Stack Overflow: https://stackoverflow.com/questions/tagged/google-auth-library-ruby
If you are still having issues, please be sure to include as much information as possible:
#### Environment details
- OS:
- Ruby version:
- Gem name and version:
#### Steps to reproduce
1. ...
#### Code example
```ruby
# example
```
Making sure to follow these steps will guarantee the quickest resolution possible.
Thanks!
googleauth-0.13.0/.repo-metadata.json 0000644 0000041 0000041 00000000133 13674353263 017466 0 ustar www-data www-data {
"name": "googleauth",
"language": "ruby",
"distribution-name": "googleauth"
} googleauth-0.13.0/integration/ 0000755 0000041 0000041 00000000000 13674353263 016320 5 ustar www-data www-data googleauth-0.13.0/integration/id_tokens/ 0000755 0000041 0000041 00000000000 13674353263 020277 5 ustar www-data www-data googleauth-0.13.0/integration/id_tokens/key_source_test.rb 0000644 0000041 0000041 00000005656 13674353263 024047 0 ustar www-data www-data # Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "helper"
describe Google::Auth::IDTokens do
describe "key source" do
let(:legacy_oidc_key_source) {
Google::Auth::IDTokens::X509CertHttpKeySource.new "https://www.googleapis.com/oauth2/v1/certs"
}
let(:oidc_key_source) { Google::Auth::IDTokens.oidc_key_source }
let(:iap_key_source) { Google::Auth::IDTokens.iap_key_source }
it "Gets real keys from the OAuth2 V1 cert URL" do
keys = legacy_oidc_key_source.refresh_keys
refute_empty keys
keys.each do |key|
assert_kind_of OpenSSL::PKey::RSA, key.key
refute key.key.private?
assert_equal "RS256", key.algorithm
end
end
it "Gets real keys from the OAuth2 V3 cert URL" do
keys = oidc_key_source.refresh_keys
refute_empty keys
keys.each do |key|
assert_kind_of OpenSSL::PKey::RSA, key.key
refute key.key.private?
assert_equal "RS256", key.algorithm
end
end
it "Gets the same keys from the OAuth2 V1 and V3 cert URLs" do
keys_v1 = legacy_oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
keys_v3 = oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
assert_equal keys_v1, keys_v3
end
it "Gets real keys from the IAP public key URL" do
keys = iap_key_source.refresh_keys
refute_empty keys
keys.each do |key|
assert_kind_of OpenSSL::PKey::EC, key.key
assert_equal "ES256", key.algorithm
end
end
end
end
googleauth-0.13.0/integration/helper.rb 0000644 0000041 0000041 00000003051 13674353263 020123 0 ustar www-data www-data # Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "minitest/autorun"
require "minitest/focus"
require "googleauth"