googleauth-0.13.0/0000755000004100000410000000000013674353263013775 5ustar www-datawww-datagoogleauth-0.13.0/COPYING0000644000004100000410000002611613674353263015036 0ustar www-datawww-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/0000755000004100000410000000000013674353263014754 5ustar www-datawww-datagoogleauth-0.13.0/test/id_tokens/0000755000004100000410000000000013674353263016733 5ustar www-datawww-datagoogleauth-0.13.0/test/id_tokens/key_sources_test.rb0000644000004100000410000002157413674353263022663 0ustar www-datawww-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.rb0000644000004100000410000002675113674353263022145 0ustar www-datawww-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.rb0000644000004100000410000000310513674353263016557 0ustar www-datawww-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/.rspec0000644000004100000410000000004013674353263015104 0ustar www-datawww-data--colour --format documentation googleauth-0.13.0/README.md0000644000004100000410000001474113674353263015263 0ustar www-datawww-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
[![Gem Version](https://badge.fury.io/rb/googleauth.svg)](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/0000755000004100000410000000000013674353263014727 5ustar www-datawww-datagoogleauth-0.13.0/spec/googleauth/0000755000004100000410000000000013674353263017065 5ustar www-datawww-datagoogleauth-0.13.0/spec/googleauth/iam_spec.rb0000644000004100000410000000617013674353263021176 0ustar www-datawww-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.rb0000644000004100000410000002755213674353263023133 0ustar www-datawww-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.rb0000644000004100000410000001312213674353263023455 0ustar www-datawww-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.rb0000644000004100000410000001405013674353263021715 0ustar www-datawww-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.rb0000644000004100000410000001070313674353263022357 0ustar www-datawww-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.rb0000644000004100000410000002541413674353263023664 0ustar www-datawww-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/0000755000004100000410000000000013674353263020404 5ustar www-datawww-datagoogleauth-0.13.0/spec/googleauth/stores/redis_token_store_spec.rb0000644000004100000410000000372113674353263025470 0ustar www-datawww-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.rb0000644000004100000410000000424613674353263023771 0ustar www-datawww-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.rb0000644000004100000410000000420613674353263025300 0ustar www-datawww-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.rb0000644000004100000410000001314113674353263023425 0ustar www-datawww-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.rb0000644000004100000410000002475213674353263025304 0ustar www-datawww-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.rb0000644000004100000410000004112313674353263023601 0ustar www-datawww-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.rb0000644000004100000410000001337713674353263024526 0ustar www-datawww-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.rb0000644000004100000410000000550513674353263022577 0ustar www-datawww-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.rb0000644000004100000410000006005413674353263022726 0ustar www-datawww-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.rb0000644000004100000410000000542213674353263017550 0ustar www-datawww-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.md0000644000004100000410000001101313674353263015602 0ustar www-datawww-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.yml0000644000004100000410000000051113674353263016244 0ustar www-datawww-datainherit_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/0000755000004100000410000000000013674353263015357 5ustar www-datawww-datagoogleauth-0.13.0/.kokoro/continuous/0000755000004100000410000000000013674353263017565 5ustar www-datawww-datagoogleauth-0.13.0/.kokoro/continuous/common.cfg0000644000004100000410000000102713674353263021536 0ustar www-datawww-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.cfg0000644000004100000410000000112113674353263021733 0ustar www-datawww-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.cfg0000644000004100000410000000115413674353263021406 0ustar www-datawww-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.cfg0000644000004100000410000000023213674353263021054 0ustar www-datawww-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.cfg0000644000004100000410000000125313674353263021234 0ustar www-datawww-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.sh0000755000004100000410000000027613674353263020075 0ustar www-datawww-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.sh0000755000004100000410000000025113674353263016525 0ustar www-datawww-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.cfg0000644000004100000410000000357513674353263017472 0ustar www-datawww-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.sh0000755000004100000410000000025713674353263017021 0ustar www-datawww-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/0000755000004100000410000000000013674353263017371 5ustar www-datawww-datagoogleauth-0.13.0/.kokoro/presubmit/common.cfg0000644000004100000410000000102613674353263021341 0ustar www-datawww-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.cfg0000644000004100000410000000112113674353263021537 0ustar www-datawww-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.cfg0000644000004100000410000000077513674353263021222 0ustar www-datawww-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.cfg0000644000004100000410000000023213674353263020660 0ustar www-datawww-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.bat0000644000004100000410000000044313674353263020222 0ustar www-datawww-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.bat0000644000004100000410000000121413674353263017144 0ustar www-datawww-dataREM 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/.gitignore0000644000004100000410000000111513674353263015763 0ustar www-datawww-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/0000755000004100000410000000000013674353263015406 5ustar www-datawww-datagoogleauth-0.13.0/rakelib/link_checker.rb0000644000004100000410000000345113674353263020357 0ustar www-datawww-datarequire "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.rb0000644000004100000410000000200113674353263021075 0ustar www-datawww-datarequire "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.rb0000644000004100000410000000223013674353263020535 0ustar www-datawww-datarequire "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.md0000644000004100000410000000367513674353263016607 0ustar www-datawww-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.gemspec0000755000004100000410000000254113674353263017505 0ustar www-datawww-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/Rakefile0000755000004100000410000000620513674353263015450 0ustar www-datawww-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/0000755000004100000410000000000013674353263014543 5ustar www-datawww-datagoogleauth-0.13.0/lib/googleauth/0000755000004100000410000000000013674353263016701 5ustar www-datawww-datagoogleauth-0.13.0/lib/googleauth/client_id.rb0000644000004100000410000000746213674353263021171 0ustar www-datawww-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.rb0000644000004100000410000000444413674353263021402 0ustar www-datawww-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.rb0000644000004100000410000000325113674353263020714 0ustar www-datawww-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/0000755000004100000410000000000013674353263020660 5ustar www-datawww-datagoogleauth-0.13.0/lib/googleauth/id_tokens/verifier.rb0000644000004100000410000001314513674353263023024 0ustar www-datawww-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.rb0000644000004100000410000000475313674353263022532 0ustar www-datawww-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.rb0000644000004100000410000003072313674353263023545 0ustar www-datawww-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.rb0000644000004100000410000004027513674353263021533 0ustar www-datawww-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.rb0000644000004100000410000000731713674353263023245 0ustar www-datawww-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.rb0000644000004100000410000002711513674353263023323 0ustar www-datawww-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.rb0000644000004100000410000001032013674353263020513 0ustar www-datawww-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/0000755000004100000410000000000013674353263020220 5ustar www-datawww-datagoogleauth-0.13.0/lib/googleauth/stores/file_token_store.rb0000644000004100000410000000463113674353263024104 0ustar www-datawww-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.rb0000644000004100000410000000647213674353263024300 0ustar www-datawww-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.rb0000644000004100000410000002273513674353263021216 0ustar www-datawww-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.rb0000644000004100000410000002034513674353263022406 0ustar www-datawww-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.rb0000644000004100000410000000705013674353263023231 0ustar www-datawww-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.rb0000644000004100000410000000500513674353263021562 0ustar www-datawww-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.rb0000644000004100000410000002213513674353263023054 0ustar www-datawww-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.rb0000644000004100000410000000536013674353263020000 0ustar www-datawww-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.rb0000644000004100000410000001226013674353263021723 0ustar www-datawww-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.rb0000644000004100000410000001103413674353263022226 0ustar www-datawww-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.rb0000644000004100000410000002647213674353263022473 0ustar www-datawww-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.rb0000644000004100000410000000427413674353263022400 0ustar www-datawww-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.rb0000644000004100000410000000337013674353263017231 0ustar www-datawww-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/Gemfile0000755000004100000410000000115613674353263015276 0ustar www-datawww-datasource "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/0000755000004100000410000000000013674353263015335 5ustar www-datawww-datagoogleauth-0.13.0/.github/CONTRIBUTING.md0000644000004100000410000000714713674353263017577 0ustar www-datawww-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/0000755000004100000410000000000013674353263017520 5ustar www-datawww-datagoogleauth-0.13.0/.github/ISSUE_TEMPLATE/support_request.md0000644000004100000410000000054013674353263023325 0ustar www-datawww-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.md0000644000004100000410000000151013674353263023242 0ustar www-datawww-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.md0000644000004100000410000000171113674353263022212 0ustar www-datawww-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.json0000644000004100000410000000013313674353263017466 0ustar www-datawww-data{ "name": "googleauth", "language": "ruby", "distribution-name": "googleauth" }googleauth-0.13.0/integration/0000755000004100000410000000000013674353263016320 5ustar www-datawww-datagoogleauth-0.13.0/integration/id_tokens/0000755000004100000410000000000013674353263020277 5ustar www-datawww-datagoogleauth-0.13.0/integration/id_tokens/key_source_test.rb0000644000004100000410000000565613674353263024047 0ustar www-datawww-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.rb0000644000004100000410000000305113674353263020123 0ustar www-datawww-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"