googleauth-0.8.0/0000755000004100000410000000000013437515753013722 5ustar www-datawww-datagoogleauth-0.8.0/COPYING0000644000004100000410000002611613437515753014763 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.8.0/.travis.yml0000644000004100000410000000132013437515753016027 0ustar www-datawww-datasudo: false language: ruby rvm: - 2.5.1 - 2.4.4 - 2.3.7 - 2.2.10 - 2.1.10 - 2.0.0 - 1.9.3 - rbx-2 - jruby-9.1.9.0 matrix: allow_failures: - rvm: rbx-2 # See rubinius/rubinius#3485 - rubocop segfaults script: "bundle exec rake" addons: apt: packages: - idn - build-essential # this and below attempt allow rubinius to be setup ok - bison - ruby-dev - rake zlib1g-dev - libyaml-dev - libssl-dev - libreadline-dev - libncurses5-dev - llvm - llvm-dev - libeditline-dev - libedit-dev before_install: - gem update bundler notifications: email: recipients: - ruby-cloud-eng@google.com on_success: change on_failure: change googleauth-0.8.0/.rspec0000644000004100000410000000004013437515753015031 0ustar www-datawww-data--colour --format documentation googleauth-0.8.0/README.md0000644000004100000410000001500213437515753015177 0ustar www-datawww-data# Google Auth Library for Ruby
Homepage
http://www.github.com/google/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) [![Build Status](https://secure.travis-ci.org/google/google-auth-library-ruby.svg)](http://travis-ci.org/google/google-auth-library-ruby) [![Coverage Status](https://coveralls.io/repos/google/google-auth-library-ruby/badge.svg)](https://coveralls.io/r/google/google-auth-library-ruby) ## 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" ``` ### 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](lib/googleauth/token_store.rb) for additional details. ## Supported Ruby Versions This library is currently supported on Ruby 1.9+. However, Ruby 2.4 or later is strongly recommended, as earlier releases have reached or are nearing end-of-life. After March 31, 2019, Google will provide official support only for 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). [google-apis-ruby-client]: (https://github.com/google/google-api-ruby-client) [application default credentials]: (https://developers.google.com/accounts/docs/application-default-credentials) [contributing]: https://github.com/google/google-auth-library-ruby/tree/master/CONTRIBUTING.md [copying]: https://github.com/google/google-auth-library-ruby/tree/master/COPYING googleauth-0.8.0/spec/0000755000004100000410000000000013437515753014654 5ustar www-datawww-datagoogleauth-0.8.0/spec/googleauth/0000755000004100000410000000000013437515753017012 5ustar www-datawww-datagoogleauth-0.8.0/spec/googleauth/iam_spec.rb0000644000004100000410000000617713437515753021132 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.8.0/spec/googleauth/user_refresh_spec.rb0000644000004100000410000002733713437515753023061 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 '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.8.0/spec/googleauth/apply_auth_examples.rb0000644000004100000410000001160413437515753023405 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(:stub) do make_auth_stubs access_token: token end it 'should set access_token to the fetched value' do stub @client.fetch_access_token! expect(@client.access_token).to eq(token) expect(stub).to have_been_requested end it 'should notify refresh listeners after updating' do stub expect do |b| @client.on_refresh(&b) @client.fetch_access_token! end.to yield_with_args(have_attributes( access_token: '1/abcdef1234567890' )) expect(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 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.8.0/spec/googleauth/signet_spec.rb0000644000004100000410000001012513437515753021641 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 ) end def make_auth_stubs(opts) access_token = opts[:access_token] || '' body = MultiJson.dump('access_token' => access_token, 'token_type' => 'Bearer', 'expires_in' => 3600) 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') end with_params = {body: hash_including( "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer")} if opts[:user_agent] with_params[:headers] = {"User-Agent" => opts[:user_agent]} end 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 end googleauth-0.8.0/spec/googleauth/client_id_spec.rb0000644000004100000410000001067713437515753022316 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.8.0/spec/googleauth/user_authorizer_spec.rb0000644000004100000410000002407613437515753023614 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 callback 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 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' 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.8.0/spec/googleauth/stores/0000755000004100000410000000000013437515753020331 5ustar www-datawww-datagoogleauth-0.8.0/spec/googleauth/stores/redis_token_store_spec.rb0000644000004100000410000000372613437515753025422 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.8.0/spec/googleauth/stores/store_examples.rb0000644000004100000410000000425413437515753023715 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.8.0/spec/googleauth/stores/file_token_store_spec.rb0000644000004100000410000000421313437515753025223 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.8.0/spec/googleauth/compute_engine_spec.rb0000644000004100000410000001157413437515753023362 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_URI = 'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token'.freeze GCECredentials = Google::Auth::GCECredentials before(:example) do @client = GCECredentials.new 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(:get, MD_URI) .with(headers: { 'Metadata-Flavor' => 'Google' }) .to_return(body: body, status: 200, headers: { 'Content-Type' => 'application/json' }) 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_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_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') .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') .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') .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.8.0/spec/googleauth/get_application_default_spec.rb0000644000004100000410000002474513437515753025233 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.8.0/spec/googleauth/service_account_spec.rb0000644000004100000410000003752513437515753023541 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' } 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' ) end def make_auth_stubs(opts = {}) access_token = opts[:access_token] || '' body = MultiJson.dump('access_token' => access_token, 'token_type' => 'Bearer', 'expires_in' => 3600) 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') 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 '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]) 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 '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]) end end end end googleauth-0.8.0/spec/googleauth/web_user_authorizer_spec.rb0000644000004100000410000001277213437515753024451 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 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.8.0/spec/googleauth/scope_util_spec.rb0000644000004100000410000000551213437515753022522 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.8.0/spec/googleauth/credentials_spec.rb0000644000004100000410000003231213437515753022647 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' } 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 it 'can be subclassed to pass in other env paths' do TEST_PATH_ENV_VAR = 'TEST_PATH'.freeze TEST_PATH_ENV_VAL = '/unknown/path/to/file.txt'.freeze TEST_JSON_ENV_VAR = 'TEST_JSON_VARS'.freeze ENV[TEST_PATH_ENV_VAR] = TEST_PATH_ENV_VAL ENV[TEST_JSON_ENV_VAR] = JSON.generate(default_keyfile_hash) class TestCredentials < Google::Auth::Credentials SCOPE = 'http://example.com/scope'.freeze PATH_ENV_VARS = [TEST_PATH_ENV_VAR].freeze JSON_ENV_VARS = [TEST_JSON_ENV_VAR].freeze end allow(::File).to receive(:file?).with(TEST_PATH_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://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 = TestCredentials.default expect(creds).to be_a_kind_of(TestCredentials) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash['project_id']) end it 'subclasses can use PATH_ENV_VARS to get keyfile path' do class TestCredentials < Google::Auth::Credentials SCOPE = 'http://example.com/scope'.freeze PATH_ENV_VARS = ['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('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 = TestCredentials.default expect(creds).to be_a_kind_of(TestCredentials) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash['project_id']) end it 'subclasses can use JSON_ENV_VARS to get keyfile contents' do class TestCredentials < Google::Auth::Credentials SCOPE = 'http://example.com/scope'.freeze PATH_ENV_VARS = ['PATH_ENV_DUMMY'].freeze JSON_ENV_VARS = ['JSON_ENV_DUMMY', 'JSON_ENV_TEST'].freeze DEFAULT_PATHS = ['~/default/path/to/file.txt'].freeze end 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(::ENV).to receive(:[]).with('JSON_ENV_TEST') { 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 = TestCredentials.default expect(creds).to be_a_kind_of(TestCredentials) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash['project_id']) end it 'subclasses can use DEFAULT_PATHS to get keyfile path' do class TestCredentials < 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('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 = TestCredentials.default expect(creds).to be_a_kind_of(TestCredentials) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash['project_id']) end it 'subclasses that find no matches default to Google::Auth.get_application_default' do class TestCredentials < 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('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(TestCredentials::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 = TestCredentials.default expect(creds).to be_a_kind_of(TestCredentials) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash['project_id']) end it 'warns when cloud sdk credentials are used' do 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.8.0/spec/spec_helper.rb0000644000004100000410000000547513437515753017505 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.dirname(__FILE__)) 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.8.0/CHANGELOG.md0000644000004100000410000000640213437515753015535 0ustar www-datawww-data## 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) ### Changes * Support ruby-jwt 2.0 * Add simple credentials class ## 0.5.3 (2017/07/21) ### Changes * Fix file permissions on the gem's `.rb` files. ## 0.5.2 (2017/07/19) ### Changes * 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) ### Changes * 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) ### Changes * Initial support for user credentials ([@sqrrrl][]) * Update Signet to 0.7 ## 0.4.2 (2015/08/05) ### Changes * 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) ### Changes * Enables passing credentials via environment variables. ([@haabaato][]) [#27](https://github.com/google/google-auth-library-ruby/issues/27) ## 0.4.1 (2015/04/25) ### Changes * Improves handling of --no-scopes GCE authorization ([@tbetbetbe][]) * Refactoring and cleanup ([@joneslee85][]) ## 0.4.0 (2015/03/25) ### Changes * Adds an implementation of JWT header auth ([@tbetbetbe][]) ## 0.3.0 (2015/03/23) ### Changes * 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.8.0/.rubocop.yml0000644000004100000410000000100713437515753016172 0ustar www-datawww-dataAllCops: Exclude: - "spec/**/*" Metrics/AbcSize: Max: 25 Metrics/BlockLength: Exclude: - "googleauth.gemspec" Metrics/CyclomaticComplexity: Max: 8 Metrics/MethodLength: Max: 20 Metrics/ModuleLength: Max: 150 Metrics/ClassLength: Enabled: false Layout/IndentHeredoc: Enabled: false Style/FormatString: Enabled: false Style/GuardClause: Enabled: false Style/PercentLiteralDelimiters: # Contradicting rule Enabled: false Style/SymbolArray: # Undefined syntax in Ruby 1.9.3 Enabled: false googleauth-0.8.0/.kokoro/0000755000004100000410000000000013437515753015304 5ustar www-datawww-datagoogleauth-0.8.0/.kokoro/continuous/0000755000004100000410000000000013437515753017512 5ustar www-datawww-datagoogleauth-0.8.0/.kokoro/continuous/common.cfg0000644000004100000410000000070713437515753021467 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" } googleauth-0.8.0/.kokoro/continuous/windows.cfg0000644000004100000410000000015713437515753021670 0ustar www-datawww-data# Format: //devtools/kokoro/config/proto/build.proto build_file: "google-auth-library-ruby/.kokoro/build.bat" googleauth-0.8.0/.kokoro/continuous/linux.cfg0000644000004100000410000000101213437515753021324 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/ruby-multi env_vars: { key: "TRAMPOLINE_IMAGE" value: "gcr.io/cloud-devrel-kokoro-resources/google-cloud-ruby/ruby-multi-ubuntu" } env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/google-auth-library-ruby/.kokoro/build.sh" } googleauth-0.8.0/.kokoro/continuous/osx.cfg0000644000004100000410000000015413437515753021004 0ustar www-datawww-data# Format: //devtools/kokoro/config/proto/build.proto build_file: "google-auth-library-ruby/.kokoro/osx.sh" googleauth-0.8.0/.kokoro/trampoline.sh0000755000004100000410000000161313437515753020016 0ustar www-datawww-data#!/bin/bash # Copyright 2017 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. set -eo pipefail # Always run the cleanup script, regardless of the success of bouncing into # the container. function cleanup() { chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh echo "cleanup"; } trap cleanup EXIT python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" googleauth-0.8.0/.kokoro/common.cfg0000755000004100000410000000122713437515753017262 0ustar www-datawww-data# Format: //devtools/kokoro/config/proto/build.proto # Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # All builds use the trampoline script to run in docker. build_file: "google-auth-library-ruby/.kokoro/trampoline.sh" # Download secrets from Cloud Storage. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-ruby" # Tell the trampoline which build file to use. env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/google-auth-library-ruby/.kokoro/build.sh" } action { define_artifacts { regex: "**/*sponge_log.xml" } } googleauth-0.8.0/.kokoro/osx.sh0000755000004100000410000000153213437515753016455 0ustar www-datawww-data#!/bin/bash # This file runs tests for merges, PRs, and nightlies. # There are a few rules for what tests are run: # * PRs run all non-acceptance tests for every library. # * Merges run all non-acceptance tests for every library, and acceptance tests for all altered libraries. # * Nightlies run all acceptance tests for every library. # * Currently only runs tests on 2.5.0 set -eo pipefail # Debug: show build environment env | grep KOKORO cd github/google-auth-library-ruby/ # Print out Ruby version ruby --version # Temporary workaround for a known bundler+docker issue: # https://github.com/bundler/bundler/issues/6154 export BUNDLE_GEMFILE= # Capture failures EXIT_STATUS=0 # everything passed function set_failed_status { EXIT_STATUS=1 } gem install bundle (bundle update && bundle exec rake) || set_failed_status exit $EXIT_STATUS googleauth-0.8.0/.kokoro/build.sh0000755000004100000410000000162413437515753016745 0ustar www-datawww-data#!/bin/bash # This file runs tests for merges, PRs, and nightlies. # There are a few rules for what tests are run: # * PRs run all non-acceptance tests for every library. # * Merges run all non-acceptance tests for every library, and acceptance tests for all altered libraries. # * Nightlies run all acceptance tests for every library. set -eo pipefail # Debug: show build environment env | grep KOKORO cd github/google-auth-library-ruby/ # Print out Ruby version ruby --version # Temporary workaround for a known bundler+docker issue: # https://github.com/bundler/bundler/issues/6154 export BUNDLE_GEMFILE= RUBY_VERSIONS=("2.3.8" "2.4.5" "2.5.3") # Capture failures EXIT_STATUS=0 # everything passed function set_failed_status { EXIT_STATUS=1 } for version in "${RUBY_VERSIONS[@]}"; do rbenv global "$version" (bundle update && bundle exec rake) || set_failed_status done exit $EXIT_STATUS googleauth-0.8.0/.kokoro/presubmit/0000755000004100000410000000000013437515753017316 5ustar www-datawww-datagoogleauth-0.8.0/.kokoro/presubmit/common.cfg0000644000004100000410000000070613437515753021272 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" } googleauth-0.8.0/.kokoro/presubmit/windows.cfg0000644000004100000410000000015713437515753021474 0ustar www-datawww-data# Format: //devtools/kokoro/config/proto/build.proto build_file: "google-auth-library-ruby/.kokoro/build.bat" googleauth-0.8.0/.kokoro/presubmit/linux.cfg0000644000004100000410000000062613437515753021142 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/google-cloud-ruby/ruby-multi-ubuntu" } env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/google-auth-library-ruby/.kokoro/build.sh" } googleauth-0.8.0/.kokoro/presubmit/osx.cfg0000644000004100000410000000015413437515753020610 0ustar www-datawww-data# Format: //devtools/kokoro/config/proto/build.proto build_file: "google-auth-library-ruby/.kokoro/osx.sh" googleauth-0.8.0/.kokoro/windows.sh0000644000004100000410000000150513437515753017333 0ustar www-datawww-data#!/bin/bash # This file runs tests for merges, PRs, and nightlies. # There are a few rules for what tests are run: # * PRs run all non-acceptance tests for every library. # * Merges run all non-acceptance tests for every library, and acceptance tests for all altered libraries. # * Nightlies run all acceptance tests for every library. # * Currently only runs tests on 2.5.1 set -eo pipefail # Debug: show build environment env | grep KOKORO cd github/google-auth-library-ruby/ # Print out Ruby version ruby --version # Temporary workaround for a known bundler+docker issue: # https://github.com/bundler/bundler/issues/6154 export BUNDLE_GEMFILE= # Capture failures EXIT_STATUS=0 # everything passed function set_failed_status { EXIT_STATUS=1 } (bundle update && bundle exec rake) || set_failed_status exit $EXIT_STATUS googleauth-0.8.0/.kokoro/build.bat0000644000004100000410000000072013437515753017072 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 "C:\Program Files\Git\bin\bash.exe" github/google-auth-library-ruby/.kokoro/windows.sh googleauth-0.8.0/.gitignore0000644000004100000410000000111513437515753015710 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.8.0/CODE_OF_CONDUCT.md0000644000004100000410000000367513437515753016534 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.8.0/googleauth.gemspec0000755000004100000410000000232413437515753017431 0ustar www-datawww-data# -*- ruby -*- # encoding: utf-8 $LOAD_PATH.push File.expand_path('../lib', __FILE__) require 'googleauth/version' Gem::Specification.new do |s| s.name = 'googleauth' s.version = Google::Auth::VERSION s.authors = ['Tim Emiola'] s.email = 'temiola@google.com' s.homepage = 'https://github.com/google/google-auth-library-ruby' s.summary = 'Google Auth Library for Ruby' s.license = 'Apache-2.0' s.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 s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- spec/*`.split("\n") s.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f| File.basename(f) end s.require_paths = ['lib'] s.platform = Gem::Platform::RUBY s.add_dependency 'faraday', '~> 0.12' s.add_dependency 'jwt', '>= 1.4', '< 3.0' s.add_dependency 'memoist', '~> 0.16' s.add_dependency 'multi_json', '~> 1.11' s.add_dependency 'os', '>= 0.9', '< 2.0' s.add_dependency 'signet', '~> 0.7' end googleauth-0.8.0/Rakefile0000755000004100000410000000050213437515753015367 0ustar www-datawww-data# -*- ruby -*- require 'rspec/core/rake_task' require 'rubocop/rake_task' require 'bundler/gem_tasks' desc 'Run Rubocop to check for style violations' RuboCop::RakeTask.new desc 'Run rake task' RSpec::Core::RakeTask.new(:spec) desc 'Does rubocop lint and runs the specs' task all: [:rubocop, :spec] task default: :all googleauth-0.8.0/lib/0000755000004100000410000000000013437515753014470 5ustar www-datawww-datagoogleauth-0.8.0/lib/googleauth/0000755000004100000410000000000013437515753016626 5ustar www-datawww-datagoogleauth-0.8.0/lib/googleauth/client_id.rb0000644000004100000410000000747013437515753021115 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.8.0/lib/googleauth/scope_util.rb0000644000004100000410000000444513437515753021330 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.8.0/lib/googleauth/version.rb0000644000004100000410000000325013437515753020640 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.8.0'.freeze end end googleauth-0.8.0/lib/googleauth/credentials.rb0000644000004100000410000001547313437515753021462 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. # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, MethodLength require 'forwardable' require 'json' require 'signet/oauth_2/client' require 'googleauth/credentials_loader' module Google module Auth # This class is intended to be inherited by API-specific classes # which overrides the SCOPE constant. class Credentials TOKEN_CREDENTIAL_URI = 'https://oauth2.googleapis.com/token'.freeze AUDIENCE = 'https://oauth2.googleapis.com/token'.freeze SCOPE = [].freeze PATH_ENV_VARS = [].freeze JSON_ENV_VARS = [].freeze DEFAULT_PATHS = [].freeze attr_accessor :client attr_reader :project_id # Delegate client methods to the client object. extend Forwardable def_delegators :@client, :token_credential_uri, :audience, :scope, :issuer, :signing_key, :updater_proc def initialize(keyfile, options = {}) scope = options[:scope] verify_keyfile_provided! keyfile @project_id = options['project_id'] || options['project'] if keyfile.is_a? Signet::OAuth2::Client @client = keyfile @project_id ||= keyfile.project_id if keyfile.respond_to? :project_id elsif keyfile.is_a? Hash hash = stringify_hash_keys keyfile hash['scope'] ||= scope @client = init_client hash, options @project_id ||= (hash['project_id'] || hash['project']) else verify_keyfile_exists! keyfile json = JSON.parse ::File.read(keyfile) json['scope'] ||= scope @project_id ||= (json['project_id'] || json['project']) @client = init_client json, options end CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id @project_id ||= CredentialsLoader.load_gcloud_project_id @client.fetch_access_token! end # Returns the default credentials checking, in this order, the path env # evironment variables, json environment variables, default paths. If the # previously stated locations do not contain keyfile information, # this method defaults to use the application default. def self.default(options = {}) # First try to find keyfile file from environment variables. client = from_path_vars options # Second try to find keyfile json from environment variables. client ||= from_json_vars options # Third 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 def self.from_path_vars(options) self::PATH_ENV_VARS .map { |v| ENV[v] } .compact .select { |p| ::File.file? p } .each do |file| return new file, options end nil end def self.from_json_vars(options) json = lambda do |v| unless ENV[v].nil? begin JSON.parse ENV[v] rescue nil end end end self::JSON_ENV_VARS.map(&json).compact.each do |hash| return new hash, options end nil end def self.from_default_paths(options) self::DEFAULT_PATHS .select { |p| ::File.file? p } .each do |file| return new file, options end nil end def self.from_application_default(options) scope = options[:scope] || self::SCOPE client = Google::Auth.get_application_default scope new client, options end private_class_method :from_path_vars, :from_json_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 # client options for initializing signet client { token_credential_uri: options['token_credential_uri'], audience: options['audience'], scope: Array(options['scope']), issuer: options['client_email'], signing_key: OpenSSL::PKey::RSA.new(options['private_key']) } end end end end googleauth-0.8.0/lib/googleauth/application_default.rb0000644000004100000410000000726013437515753023167 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 = <] 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. # @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? redirect_to = options[:redirect_to] || request.url request.session[XSRF_KEY] = SecureRandom.base64 options[:state] = MultiJson.dump( 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. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Rack::Request] request # Current request # @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, scope = nil) if 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) if state[AUTH_CODE_KEY].nil? raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR elsif state[ERROR_CODE_KEY] raise Signet::AuthorizationError, sprintf(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.8.0/lib/googleauth/signet.rb0000644000004100000410000001006713437515753020450 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 fetch_access_token!(opts) if access_token.nil? || expires_within?(60) a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}" 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 ||= [] @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 = orig_fetch_access_token!(options) notify_refresh_listeners info end def notify_refresh_listeners 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 => e if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError) raise e end 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.8.0/lib/googleauth/stores/0000755000004100000410000000000013437515753020145 5ustar www-datawww-datagoogleauth-0.8.0/lib/googleauth/stores/file_token_store.rb0000644000004100000410000000463713437515753024037 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.8.0/lib/googleauth/stores/redis_token_store.rb0000644000004100000410000000651013437515753024216 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.8.0/lib/googleauth/service_account.rb0000644000004100000410000001720513437515753022334 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](http://goo.gl/mkAHpZ) class ServiceAccountCredentials < Signet::OAuth2::Client TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v4/token'.freeze extend CredentialsLoader extend JsonKeyReader attr_reader :project_id # Creates a ServiceAccountCredentials. # # @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) if json_key_io private_key, client_email, project_id = read_json_key(json_key_io) else private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR] client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] project_id = ENV[CredentialsLoader::PROJECT_ID_VAR] end project_id ||= CredentialsLoader.load_gcloud_project_id new(token_credential_uri: TOKEN_CRED_URI, audience: TOKEN_CRED_URI, scope: scope, issuer: client_email, signing_key: OpenSSL::PKey::RSA.new(private_key), project_id: 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] 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? 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](http://goo.gl/mkAHpZ) 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 # 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 = 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] 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.8.0/lib/googleauth/default_credentials.rb0000644000004100000410000000705713437515753023165 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.8.0/lib/googleauth/token_store.rb0000644000004100000410000000501013437515753021503 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.8.0/lib/googleauth/credentials_loader.rb0000644000004100000410000002144413437515753023003 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'.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/.'.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) if creds.respond_to?(:configure_connection) && args.size == 1 creds = creds.configure_connection(args[0]) end 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) 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 # Issues warning if cloud sdk client id is used def warn_if_cloud_sdk_credentials(client_id) warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID end module_function :warn_if_cloud_sdk_credentials # 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? config = MultiJson.load(`#{gcloud} #{GCLOUD_CONFIG_COMMAND}`) config['configuration']['properties']['core']['project'] rescue nil end module_function :load_gcloud_project_id private def interpret_options(scope, options) if scope.is_a? Hash options = scope scope = nil end if scope && !options[:scope] options.merge(scope: scope) else options end end def service_account_env_vars? ([PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR] - ENV.keys).empty? end def authorized_user_env_vars? ([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] - ENV.keys).empty? end end end end googleauth-0.8.0/lib/googleauth/iam.rb0000644000004100000410000000536413437515753017731 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.8.0/lib/googleauth/user_refresh.rb0000644000004100000410000001214213437515753021647 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](http://goo.gl/mkAHpZ) 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 = %w(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.8.0/lib/googleauth/compute_engine.rb0000644000004100000410000001071413437515753022157 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 = < 'Google' } resp = c.get(COMPUTE_AUTH_TOKEN_URI, nil, headers) case resp.status when 200 Signet::OAuth2.parse_credentials(resp.body, resp.headers['content-type']) 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.8.0/lib/googleauth/user_authorizer.rb0000644000004100000410000002601513437515753022411 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 sprintf(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 if credentials.includes_scope?(scope) return monitor_credentials(user_id, credentials) end 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 unless URI(@callback_uri).scheme.nil? if base_url.nil? || URI(base_url).scheme.nil? raise sprintf(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) end URI.join(base_url, @callback_uri).to_s end end end end googleauth-0.8.0/lib/googleauth/json_key_reader.rb0000644000004100000410000000422013437515753022314 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') project_id = json_key['project_id'] [json_key['private_key'], json_key['client_email'], project_id] end end end end googleauth-0.8.0/lib/googleauth.rb0000644000004100000410000000333113437515753017153 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/user_authorizer' require 'googleauth/web_user_authorizer' googleauth-0.8.0/Gemfile0000755000004100000410000000100613437515753015215 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 'logging', '~> 2.0' gem 'rack-test', '~> 0.6' gem 'rake', '~> 10.0' gem 'redis', '~> 3.2' gem 'rspec', '~> 3.0' gem 'rubocop', '>= 0.41', '< 0.50' gem 'simplecov', '~> 0.9' gem 'sinatra' gem 'webmock', '~> 1.21' end platforms :jruby do group :development do end end googleauth-0.8.0/.github/0000755000004100000410000000000013437515753015262 5ustar www-datawww-datagoogleauth-0.8.0/.github/CONTRIBUTING.md0000644000004100000410000000714713437515753017524 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.8.0/.github/ISSUE_TEMPLATE/0000755000004100000410000000000013437515753017445 5ustar www-datawww-datagoogleauth-0.8.0/.github/ISSUE_TEMPLATE/support_request.md0000644000004100000410000000054013437515753023252 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.8.0/.github/ISSUE_TEMPLATE/feature_request.md0000644000004100000410000000151013437515753023167 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.8.0/.github/ISSUE_TEMPLATE/bug_report.md0000644000004100000410000000171113437515753022137 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!