googleauth-1.3.0/0000755000004100000410000000000014333131016013674 5ustar www-datawww-datagoogleauth-1.3.0/README.md0000644000004100000410000001712214333131016015156 0ustar www-datawww-data# Google Auth Library for Ruby
Homepage
http://www.github.com/googleapis/google-auth-library-ruby
Authors
Tim Emiola
Copyright
Copyright © 2015 Google, Inc.
License
Apache 2.0
[![Gem Version](https://badge.fury.io/rb/googleauth.svg)](http://badge.fury.io/rb/googleauth) ## Description This is Google's officially supported ruby client library for using OAuth 2.0 authorization and authentication with Google APIs. ## 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) user_id = ENV['USER'] 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! ``` You can also use a JSON keyfile by setting the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. ```bash export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account_json_key.json ``` ```ruby require 'googleauth' require 'google/apis/drive_v3' Drive = ::Google::Apis::DriveV3 drive = Drive::DriveService.new scope = 'https://www.googleapis.com/auth/drive' authorizer = Google::Auth::ServiceAccountCredentials.from_env(scope: scope) drive.authorization = authorizer list_files = drive.list_files() ``` ### 3-Legged OAuth with a Service Account This is similar to regular service account authorization (see [this answer](https://support.google.com/a/answer/2538798?hl=en) for more details on the differences), but you'll need to indicate which user your service account is impersonating by manually updating the `sub` field. ```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.update!(sub: "email-to-impersonate@your-domain.com") authorizer.fetch_access_token! ``` ### Example (Environment Variables) ```bash export GOOGLE_ACCOUNT_TYPE=service_account export GOOGLE_CLIENT_ID=000000000000000000000 export GOOGLE_CLIENT_EMAIL=xxxx@xxxx.iam.gserviceaccount.com export GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" ``` ```ruby require 'googleauth' require 'google/apis/drive_v3' Drive = ::Google::Apis::DriveV3 drive = Drive::DriveService.new # Auths with ENV vars: # "GOOGLE_CLIENT_ID", # "GOOGLE_CLIENT_EMAIL", # "GOOGLE_ACCOUNT_TYPE", # "GOOGLE_PRIVATE_KEY" auth = ::Google::Auth::ServiceAccountCredentials .make_creds(scope: 'https://www.googleapis.com/auth/drive') drive.authorization = auth list_files = drive.list_files() ``` ### Storage Authorizers require a storage instance to manage long term persistence of access and refresh tokens. Two storage implementations are included: * Google::Auth::Stores::FileTokenStore * Google::Auth::Stores::RedisTokenStore Custom storage implementations can also be used. See [token_store.rb](https://googleapis.dev/ruby/googleauth/latest/Google/Auth/TokenStore.html) for additional details. ## Supported Ruby Versions This library is supported on Ruby 2.6+. Google provides official support for Ruby versions that are actively supported by Ruby Core—that is, Ruby versions that are either in normal maintenance or in security maintenance, and not end of life. Older versions of Ruby _may_ still work, but are unsupported and not recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details about the Ruby support schedule. ## License This library is licensed under Apache 2.0. Full license text is available in [LICENSE][license]. ## Contributing See [CONTRIBUTING][contributing]. ## Support Please [report bugs at the project on Github](https://github.com/google/google-auth-library-ruby/issues). Don't hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-auth-library-ruby) about the client or APIs on [StackOverflow](http://stackoverflow.com). [application default credentials]: https://developers.google.com/accounts/docs/application-default-credentials [contributing]: https://github.com/googleapis/google-auth-library-ruby/tree/main/.github/CONTRIBUTING.md [license]: https://github.com/googleapis/google-auth-library-ruby/tree/main/LICENSE googleauth-1.3.0/CHANGELOG.md0000644000004100000410000001405014333131016015505 0ustar www-datawww-data# Release History ### 1.3.0 (2022-10-18) #### Features * Use OpenSSL 3.0 compatible interfaces for IDTokens ([#397](https://github.com/googleapis/google-auth-library-ruby/issues/397)) ### 1.2.0 (2022-06-23) * Updated minimum Ruby version to 2.6 ### 1.1.3 (2022-04-20) #### Documentation * Add README instructions for 3-Legged OAuth with a service account ### 1.1.2 (2022-02-22) #### Bug Fixes * Support Faraday 2 ### 1.1.1 (2022-02-14) #### Bug Fixes * add quota_project to user refresh credentials ### 1.1.0 (2021-10-24) #### Features * Support short-lived tokens in Credentials ### 1.0.0 (2021-09-27) Bumped version to 1.0.0. Releases from this point will follow semver. * Allow dependency on future 1.x versions of signet * Prevented gcloud from authenticating on the console when getting the gcloud project ### 0.17.1 (2021-09-01) * Updates to gem metadata ### 0.17.0 (2021-07-30) * Allow scopes to be self-signed into jwts ### 0.16.2 (2021-04-28) * Stop attempting to get the project from gcloud when applying self-signed JWTs ### 0.16.1 (2021-04-01) * Accept application/text content-type for plain idtoken response ### 0.16.0 (2021-03-04) * Drop support for Ruby 2.4 and add support for Ruby 3.0 ### 0.15.1 (2021-02-08) * Fix crash when using a client credential without any paths or env_vars set ### 0.15.0 (2021-01-26) * Credential parameters inherit from superclasses * Service accounts apply a self-signed JWT if scopes are marked as default * Retry fetch_access_token when GCE metadata server returns unexpected errors * Support correct service account and user refresh behavior for custom credential env variables ### 0.14.0 / 2020-10-09 * Honor GCE_METADATA_HOST environment variable * Fix errors in some environments when requesting an access token for multiple scopes ### 0.13.1 / 2020-07-30 * Support scopes when using GCE Metadata Server authentication ([@ball-hayden][]) ### 0.13.0 / 2020-06-17 * Support for validating ID tokens. * Fixed header application of ID tokens from service accounts. ### 0.12.0 / 2020-04-08 * Support for ID token credentials. * Support reading quota_id_project from service account credentials. ### 0.11.0 / 2020-02-24 * Support Faraday 1.x. * Allow special "postmessage" value for redirect_uri. ### 0.10.0 / 2019-10-09 Note: This release now requires Ruby 2.4 or later * Increase metadata timeout to improve reliability in some hosting environments * Support an environment variable to suppress Cloud SDK credentials warnings * Make the header check case insensitive * Set instance variables at initialization to avoid spamming warnings * Pass "Metadata-Flavor" header to metadata server when checking for GCE ### 0.9.0 / 2019-08-05 * Restore compatibility with Ruby 2.0. This is the last release that will work on end-of-lifed versions of Ruby. The 0.10 release will require Ruby 2.4 or later. * Update Credentials to use methods for values that are intended to be changed by users, replacing constants. * Add retry on error for fetch_access_token * Allow specifying custom state key-values * Add verbosity none to gcloud command * Make arity of WebUserAuthorizer#get_credentials compatible with the base class ### 0.8.1 / 2019-03-27 * Silence unnecessary gcloud warning * Treat empty credentials environment variables as unset ### 0.8.0 / 2019-01-02 * Support connection options :default_connection and :connection_builder when creating credentials that need to refresh OAuth tokens. This lets clients provide connection objects with custom settings, such as proxies, needed for the client environment. * Removed an unnecessary warning about project IDs. ### 0.7.1 / 2018-10-25 * Make load_gcloud_project_id module function. ### 0.7.0 / 2018-10-24 * Add project_id instance variable to UserRefreshCredentials, ServiceAccountCredentials, and Credentials. ### 0.6.7 / 2018-10-16 * Update memoist dependency to ~> 0.16. ### 0.6.6 / 2018-08-22 * Remove ruby version warnings. ### 0.6.5 / 2018-08-16 * Fix incorrect http verb when revoking credentials. * Warn on EOL ruby versions. ### 0.6.4 / 2018-08-03 * Resolve issue where DefaultCredentials constant was undefined. ### 0.6.3 / 2018-08-02 * Resolve issue where token_store was being written to twice ### 0.6.2 / 2018-08-01 * Add warning when using cloud sdk credentials ### 0.6.1 / 2017-10-18 * Fix file permissions ### 0.6.0 / 2017-10-17 * Support ruby-jwt 2.0 * Add simple credentials class ### 0.5.3 / 2017-07-21 * Fix file permissions on the gem's `.rb` files. ### 0.5.2 / 2017-07-19 * Add retry mechanism when fetching access tokens in `GCECredentials` and `UserRefreshCredentials` classes. * Update Google API OAuth2 token credential URI to v4. ### 0.5.1 / 2016-01-06 * Change header name emitted by `Client#apply` from "Authorization" to "authorization" ([@murgatroid99][]) * Fix ADC not working on some windows machines ([@vsubramani][]) ### 0.5.0 / 2015-10-12 * Initial support for user credentials ([@sqrrrl][]) * Update Signet to 0.7 ### 0.4.2 / 2015-08-05 * Updated UserRefreshCredentials hash to use string keys ([@haabaato][]) * Add support for a system default credentials file. ([@mr-salty][]) * Fix bug when loading credentials from ENV ([@dwilkie][]) * Relax the constraint of dependent version of multi_json ([@igrep][]) * Enables passing credentials via environment variables. ([@haabaato][]) ### 0.4.1 / 2015-04-25 * Improves handling of --no-scopes GCE authorization ([@tbetbetbe][]) * Refactoring and cleanup ([@joneslee85][]) ### 0.4.0 / 2015-03-25 * Adds an implementation of JWT header auth ([@tbetbetbe][]) ### 0.3.0 / 2015-03-23 * makes the scope parameter's optional in all APIs. ([@tbetbetbe][]) * changes the scope parameter's position in various constructors. ([@tbetbetbe][]) [@dwilkie]: https://github.com/dwilkie [@haabaato]: https://github.com/haabaato [@igrep]: https://github.com/igrep [@joneslee85]: https://github.com/joneslee85 [@mr-salty]: https://github.com/mr-salty [@tbetbetbe]: https://github.com/tbetbetbe [@murgatroid99]: https://github.com/murgatroid99 [@vsubramani]: https://github.com/vsubramani [@ball-hayden]: https://github.com/ball-hayden googleauth-1.3.0/CODE_OF_CONDUCT.md0000644000004100000410000000367514333131016016506 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-1.3.0/LICENSE0000644000004100000410000002611614333131016014707 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-1.3.0/googleauth.gemspec0000644000004100000410000000637714333131016017414 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: googleauth 1.3.0 ruby lib Gem::Specification.new do |s| s.name = "googleauth".freeze s.version = "1.3.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/googleapis/google-auth-library-ruby/issues", "changelog_uri" => "https://github.com/googleapis/google-auth-library-ruby/blob/main/CHANGELOG.md", "source_code_uri" => "https://github.com/googleapis/google-auth-library-ruby" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Tim Emiola".freeze] s.date = "2022-10-18" s.description = "Implements simple authorization for accessing Google APIs, and provides support for Application Default Credentials.".freeze s.email = ["temiola@google.com".freeze] s.files = [".yardopts".freeze, "CHANGELOG.md".freeze, "CODE_OF_CONDUCT.md".freeze, "LICENSE".freeze, "README.md".freeze, "SECURITY.md".freeze, "lib/googleauth.rb".freeze, "lib/googleauth/application_default.rb".freeze, "lib/googleauth/client_id.rb".freeze, "lib/googleauth/compute_engine.rb".freeze, "lib/googleauth/credentials.rb".freeze, "lib/googleauth/credentials_loader.rb".freeze, "lib/googleauth/default_credentials.rb".freeze, "lib/googleauth/iam.rb".freeze, "lib/googleauth/id_tokens.rb".freeze, "lib/googleauth/id_tokens/errors.rb".freeze, "lib/googleauth/id_tokens/key_sources.rb".freeze, "lib/googleauth/id_tokens/verifier.rb".freeze, "lib/googleauth/json_key_reader.rb".freeze, "lib/googleauth/scope_util.rb".freeze, "lib/googleauth/service_account.rb".freeze, "lib/googleauth/signet.rb".freeze, "lib/googleauth/stores/file_token_store.rb".freeze, "lib/googleauth/stores/redis_token_store.rb".freeze, "lib/googleauth/token_store.rb".freeze, "lib/googleauth/user_authorizer.rb".freeze, "lib/googleauth/user_refresh.rb".freeze, "lib/googleauth/version.rb".freeze, "lib/googleauth/web_user_authorizer.rb".freeze] s.homepage = "https://github.com/googleapis/google-auth-library-ruby".freeze s.licenses = ["Apache-2.0".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.6".freeze) s.rubygems_version = "3.2.5".freeze s.summary = "Google Auth Library for Ruby".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, [">= 0.17.3", "< 3.a"]) s.add_runtime_dependency(%q.freeze, [">= 1.4", "< 3.0"]) s.add_runtime_dependency(%q.freeze, ["~> 0.16"]) s.add_runtime_dependency(%q.freeze, ["~> 1.11"]) s.add_runtime_dependency(%q.freeze, [">= 0.9", "< 2.0"]) s.add_runtime_dependency(%q.freeze, [">= 0.16", "< 2.a"]) else s.add_dependency(%q.freeze, [">= 0.17.3", "< 3.a"]) s.add_dependency(%q.freeze, [">= 1.4", "< 3.0"]) s.add_dependency(%q.freeze, ["~> 0.16"]) s.add_dependency(%q.freeze, ["~> 1.11"]) s.add_dependency(%q.freeze, [">= 0.9", "< 2.0"]) s.add_dependency(%q.freeze, [">= 0.16", "< 2.a"]) end end googleauth-1.3.0/lib/0000755000004100000410000000000014333131016014442 5ustar www-datawww-datagoogleauth-1.3.0/lib/googleauth/0000755000004100000410000000000014333131016016600 5ustar www-datawww-datagoogleauth-1.3.0/lib/googleauth/client_id.rb0000644000004100000410000000557214333131016021070 0ustar www-datawww-data# Copyright 2014 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. 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-1.3.0/lib/googleauth/scope_util.rb0000644000004100000410000000255014333131016021275 0ustar www-datawww-data# 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. 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-1.3.0/lib/googleauth/version.rb0000644000004100000410000000136014333131016020612 0ustar www-datawww-data# Copyright 2014 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. module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth VERSION = "1.3.0".freeze end end googleauth-1.3.0/lib/googleauth/id_tokens/0000755000004100000410000000000014333131016020557 5ustar www-datawww-datagoogleauth-1.3.0/lib/googleauth/id_tokens/verifier.rb0000644000004100000410000001122414333131016022717 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2020 Google LLC # # 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. require "jwt" module Google module Auth module IDTokens ## # An object that can verify ID tokens. # # A verifier maintains a set of default settings, including the key # source and fields to verify. However, individual verification calls can # override any of these settings. # class Verifier ## # Create a verifier. # # @param key_source [key source] The default key source to use. All # verification calls must have a key source, so if no default key # source is provided here, then calls to {#verify} _must_ provide # a key source. # @param aud [String,nil] The default audience (`aud`) check, or `nil` # for no check. # @param azp [String,nil] The default authorized party (`azp`) check, # or `nil` for no check. # @param iss [String,nil] The default issuer (`iss`) check, or `nil` # for no check. # def initialize key_source: nil, aud: nil, azp: nil, iss: nil @key_source = key_source @aud = aud @azp = azp @iss = iss end ## # Verify the given token. # # @param token [String] the ID token to verify. # @param key_source [key source] If given, override the key source. # @param aud [String,nil] If given, override the `aud` check. # @param azp [String,nil] If given, override the `azp` check. # @param iss [String,nil] If given, override the `iss` check. # # @return [Hash] the decoded payload, if verification succeeded. # @raise [KeySourceError] if the key source failed to obtain public keys # @raise [VerificationError] if the token verification failed. # Additional data may be available in the error subclass and message. # def verify token, key_source: :default, aud: :default, azp: :default, iss: :default key_source = @key_source if key_source == :default aud = @aud if aud == :default azp = @azp if azp == :default iss = @iss if iss == :default raise KeySourceError, "No key sources" unless key_source keys = key_source.current_keys payload = decode_token token, keys, aud, azp, iss unless payload keys = key_source.refresh_keys payload = decode_token token, keys, aud, azp, iss end raise SignatureError, "Token not verified as issued by Google" unless payload payload end private def decode_token token, keys, aud, azp, iss payload = nil keys.find do |key| options = { algorithms: key.algorithm } decoded_token = JWT.decode token, key.key, true, options payload = decoded_token.first rescue JWT::ExpiredSignature raise ExpiredTokenError, "Token signature is expired" rescue JWT::DecodeError nil # Try the next key end normalize_and_verify_payload payload, aud, azp, iss end def normalize_and_verify_payload payload, aud, azp, iss return nil unless payload # Map the legacy "cid" claim to the canonical "azp" payload["azp"] ||= payload["cid"] if payload.key? "cid" # Payload content validation if aud && (Array(aud) & Array(payload["aud"])).empty? raise AudienceMismatchError, "Token aud mismatch: #{payload['aud']}" end if azp && (Array(azp) & Array(payload["azp"])).empty? raise AuthorizedPartyMismatchError, "Token azp mismatch: #{payload['azp']}" end if iss && (Array(iss) & Array(payload["iss"])).empty? raise IssuerMismatchError, "Token iss mismatch: #{payload['iss']}" end payload end end end end end googleauth-1.3.0/lib/googleauth/id_tokens/errors.rb0000644000004100000410000000311214333131016022415 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2020 Google LLC # # 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. module Google module Auth module IDTokens ## # Failed to obtain keys from the key source. # class KeySourceError < StandardError; end ## # Failed to verify a token. # class VerificationError < StandardError; end ## # Failed to verify a token because it is expired. # class ExpiredTokenError < VerificationError; end ## # Failed to verify a token because its signature did not match. # class SignatureError < VerificationError; end ## # Failed to verify a token because its issuer did not match. # class IssuerMismatchError < VerificationError; end ## # Failed to verify a token because its audience did not match. # class AudienceMismatchError < VerificationError; end ## # Failed to verify a token because its authorized party did not match. # class AuthorizedPartyMismatchError < VerificationError; end end end end googleauth-1.3.0/lib/googleauth/id_tokens/key_sources.rb0000644000004100000410000002766614333131016023460 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2020 Google LLC # # 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. require "base64" require "json" require "monitor" require "net/http" require "openssl" require "jwt" module Google module Auth module IDTokens ## # A public key used for verifying ID tokens. # # This includes the public key data, ID, and the algorithm used for # signature verification. RSA and Elliptical Curve (EC) keys are # supported. # class KeyInfo ## # Create a public key info structure. # # @param id [String] The key ID. # @param key [OpenSSL::PKey::RSA,OpenSSL::PKey::EC] The key itself. # @param algorithm [String] The algorithm (normally `RS256` or `ES256`) # def initialize id: nil, key: nil, algorithm: nil @id = id @key = key @algorithm = algorithm end ## # The key ID. # @return [String] # attr_reader :id ## # The key itself. # @return [OpenSSL::PKey::RSA,OpenSSL::PKey::EC] # attr_reader :key ## # The signature algorithm. (normally `RS256` or `ES256`) # @return [String] # attr_reader :algorithm class << self ## # Create a KeyInfo from a single JWK, which may be given as either a # hash or an unparsed JSON string. # # @param jwk [Hash,String] The JWK specification. # @return [KeyInfo] # @raise [KeySourceError] If the key could not be extracted from the # JWK. # def from_jwk jwk jwk = symbolize_keys ensure_json_parsed jwk key = case jwk[:kty] when "RSA" extract_rsa_key jwk when "EC" extract_ec_key jwk when nil raise KeySourceError, "Key type not found" else raise KeySourceError, "Cannot use key type #{jwk[:kty]}" end new id: jwk[:kid], key: key, algorithm: jwk[:alg] end ## # Create an array of KeyInfo from a JWK Set, which may be given as # either a hash or an unparsed JSON string. # # @param jwk [Hash,String] The JWK Set specification. # @return [Array] # @raise [KeySourceError] If a key could not be extracted from the # JWK Set. # def from_jwk_set jwk_set jwk_set = symbolize_keys ensure_json_parsed jwk_set jwks = jwk_set[:keys] raise KeySourceError, "No keys found in jwk set" unless jwks jwks.map { |jwk| from_jwk jwk } end private def ensure_json_parsed input return input unless input.is_a? String JSON.parse input rescue JSON::ParserError raise KeySourceError, "Unable to parse JSON" end def symbolize_keys hash result = {} hash.each { |key, val| result[key.to_sym] = val } result end def extract_rsa_key jwk begin n_data = Base64.urlsafe_decode64 jwk[:n] e_data = Base64.urlsafe_decode64 jwk[:e] rescue ArgumentError raise KeySourceError, "Badly formatted key data" end n_bn = OpenSSL::BN.new n_data, 2 e_bn = OpenSSL::BN.new e_data, 2 sequence = [OpenSSL::ASN1::Integer.new(n_bn), OpenSSL::ASN1::Integer.new(e_bn)] rsa_key = OpenSSL::PKey::RSA.new OpenSSL::ASN1::Sequence(sequence).to_der rsa_key.public_key end # @private CURVE_NAME_MAP = { "P-256" => "prime256v1", "P-384" => "secp384r1", "P-521" => "secp521r1", "secp256k1" => "secp256k1" }.freeze def extract_ec_key jwk begin x_data = Base64.urlsafe_decode64 jwk[:x] y_data = Base64.urlsafe_decode64 jwk[:y] rescue ArgumentError raise KeySourceError, "Badly formatted key data" end curve_name = CURVE_NAME_MAP[jwk[:crv]] raise KeySourceError, "Unsupported EC curve #{jwk[:crv]}" unless curve_name group = OpenSSL::PKey::EC::Group.new curve_name x_hex = x_data.unpack1 "H*" y_hex = y_data.unpack1 "H*" bn = OpenSSL::BN.new ["04#{x_hex}#{y_hex}"].pack("H*"), 2 point = OpenSSL::PKey::EC::Point.new group, bn sequence = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId("id-ecPublicKey"), OpenSSL::ASN1::ObjectId(curve_name)]), OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed)) ]) OpenSSL::PKey::EC.new sequence.to_der end end end ## # A key source that contains a static set of keys. # class StaticKeySource ## # Create a static key source with the given keys. # # @param keys [Array] The keys # def initialize keys @current_keys = Array(keys) end ## # Return the current keys. Does not perform any refresh. # # @return [Array] # attr_reader :current_keys alias refresh_keys current_keys class << self ## # Create a static key source containing a single key parsed from a # single JWK, which may be given as either a hash or an unparsed # JSON string. # # @param jwk [Hash,String] The JWK specification. # @return [StaticKeySource] # def from_jwk jwk new KeyInfo.from_jwk jwk end ## # Create a static key source containing multiple keys parsed from a # JWK Set, which may be given as either a hash or an unparsed JSON # string. # # @param jwk_set [Hash,String] The JWK Set specification. # @return [StaticKeySource] # def from_jwk_set jwk_set new KeyInfo.from_jwk_set jwk_set end end end ## # A base key source that downloads keys from a URI. Subclasses should # override {HttpKeySource#interpret_json} to parse the response. # class HttpKeySource ## # The default interval between retries in seconds (3600s = 1hr). # # @return [Integer] # DEFAULT_RETRY_INTERVAL = 3600 ## # Create an HTTP key source. # # @param uri [String,URI] The URI from which to download keys. # @param retry_interval [Integer,nil] Override the retry interval in # seconds. This is the minimum time between retries of failed key # downloads. # def initialize uri, retry_interval: nil @uri = URI uri @retry_interval = retry_interval || DEFAULT_RETRY_INTERVAL @allow_refresh_at = Time.now @current_keys = [] @monitor = Monitor.new end ## # The URI from which to download keys. # @return [Array] # attr_reader :uri ## # Return the current keys, without attempting to re-download. # # @return [Array] # attr_reader :current_keys ## # Attempt to re-download keys (if the retry interval has expired) and # return the new keys. # # @return [Array] # @raise [KeySourceError] if key retrieval failed. # def refresh_keys @monitor.synchronize do return @current_keys if Time.now < @allow_refresh_at @allow_refresh_at = Time.now + @retry_interval response = Net::HTTP.get_response uri raise KeySourceError, "Unable to retrieve data from #{uri}" unless response.is_a? Net::HTTPSuccess data = begin JSON.parse response.body rescue JSON::ParserError raise KeySourceError, "Unable to parse JSON" end @current_keys = Array(interpret_json(data)) end end protected def interpret_json _data nil end end ## # A key source that downloads X509 certificates. # Used by the legacy OAuth V1 public certs endpoint. # class X509CertHttpKeySource < HttpKeySource ## # Create a key source that downloads X509 certificates. # # @param uri [String,URI] The URI from which to download keys. # @param algorithm [String] The algorithm to use for signature # verification. Defaults to "`RS256`". # @param retry_interval [Integer,nil] Override the retry interval in # seconds. This is the minimum time between retries of failed key # downloads. # def initialize uri, algorithm: "RS256", retry_interval: nil super uri, retry_interval: retry_interval @algorithm = algorithm end protected def interpret_json data data.map do |id, cert_str| key = OpenSSL::X509::Certificate.new(cert_str).public_key KeyInfo.new id: id, key: key, algorithm: @algorithm end rescue OpenSSL::X509::CertificateError raise KeySourceError, "Unable to parse X509 certificates" end end ## # A key source that downloads a JWK set. # class JwkHttpKeySource < HttpKeySource ## # Create a key source that downloads a JWT Set. # # @param uri [String,URI] The URI from which to download keys. # @param retry_interval [Integer,nil] Override the retry interval in # seconds. This is the minimum time between retries of failed key # downloads. # def initialize uri, retry_interval: nil super uri, retry_interval: retry_interval end protected def interpret_json data KeyInfo.from_jwk_set data end end ## # A key source that aggregates other key sources. This means it will # aggregate the keys provided by its constituent sources. Additionally, # when asked to refresh, it will refresh all its constituent sources. # class AggregateKeySource ## # Create a key source that aggregates other key sources. # # @param sources [Array] The key sources to aggregate. # def initialize sources @sources = Array(sources) end ## # Return the current keys, without attempting to refresh. # # @return [Array] # def current_keys @sources.flat_map(&:current_keys) end ## # Attempt to refresh keys and return the new keys. # # @return [Array] # @raise [KeySourceError] if key retrieval failed. # def refresh_keys @sources.flat_map(&:refresh_keys) end end end end end googleauth-1.3.0/lib/googleauth/credentials.rb0000644000004100000410000004762514333131016021440 0ustar www-datawww-data# 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. require "forwardable" require "json" require "signet/oauth_2/client" require "googleauth/credentials_loader" module Google module Auth ## # Credentials is a high-level base class used by Google's API client # libraries to represent the authentication when connecting to an API. # In most cases, it is subclassed by API-specific credential classes that # can be instantiated by clients. # # ## Options # # Credentials classes are configured with options that dictate default # values for parameters such as scope and audience. These defaults are # expressed as class attributes, and may differ from endpoint to endpoint. # Normally, an API client will provide subclasses specific to each # endpoint, configured with appropriate values. # # Note that these options inherit up the class hierarchy. If a particular # options is not set for a subclass, its superclass is queried. # # Some older users of this class set options via constants. This usage is # deprecated. For example, instead of setting the `AUDIENCE` constant on # your subclass, call the `audience=` method. # # ## Example # # class MyCredentials < Google::Auth::Credentials # # Set the default scope for these credentials # self.scope = "http://example.com/my_scope" # end # # # creds is a credentials object suitable for Google API clients # creds = MyCredentials.default # creds.scope # => ["http://example.com/my_scope"] # # class SubCredentials < MyCredentials # # Override the default scope for this subclass # self.scope = "http://example.com/sub_scope" # end # # creds2 = SubCredentials.default # creds2.scope # => ["http://example.com/sub_scope"] # class Credentials # rubocop:disable Metrics/ClassLength ## # The default token credential URI to be used when none is provided during initialization. TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token".freeze ## # The default target audience ID to be used when none is provided during initialization. AUDIENCE = "https://oauth2.googleapis.com/token".freeze @audience = @scope = @target_audience = @env_vars = @paths = @token_credential_uri = nil ## # The default token credential URI to be used when none is provided during initialization. # The URI is the authorization server's HTTP endpoint capable of issuing tokens and # refreshing expired tokens. # # @return [String] # def self.token_credential_uri lookup_auth_param :token_credential_uri do lookup_local_constant :TOKEN_CREDENTIAL_URI end end ## # Set the default token credential URI to be used when none is provided during initialization. # # @param [String] new_token_credential_uri # def self.token_credential_uri= new_token_credential_uri @token_credential_uri = new_token_credential_uri end ## # The default target audience ID to be used when none is provided during initialization. # Used only by the assertion grant type. # # @return [String] # def self.audience lookup_auth_param :audience do lookup_local_constant :AUDIENCE end end ## # Sets the default target audience ID to be used when none is provided during initialization. # # @param [String] new_audience # def self.audience= new_audience @audience = new_audience end ## # The default scope to be used when none is provided during initialization. # A scope is an access range defined by the authorization server. # The scope can be a single value or a list of values. # # Either {#scope} or {#target_audience}, but not both, should be non-nil. # If {#scope} is set, this credential will produce access tokens. # If {#target_audience} is set, this credential will produce ID tokens. # # @return [String, Array, nil] # def self.scope lookup_auth_param :scope do vals = lookup_local_constant :SCOPE vals ? Array(vals).flatten.uniq : nil end end ## # Sets the default scope to be used when none is provided during initialization. # # Either {#scope} or {#target_audience}, but not both, should be non-nil. # If {#scope} is set, this credential will produce access tokens. # If {#target_audience} is set, this credential will produce ID tokens. # # @param [String, Array, nil] new_scope # def self.scope= new_scope new_scope = Array new_scope unless new_scope.nil? @scope = new_scope end ## # The default final target audience for ID tokens, to be used when none # is provided during initialization. # # Either {#scope} or {#target_audience}, but not both, should be non-nil. # If {#scope} is set, this credential will produce access tokens. # If {#target_audience} is set, this credential will produce ID tokens. # # @return [String, nil] # def self.target_audience lookup_auth_param :target_audience end ## # Sets the default final target audience for ID tokens, to be used when none # is provided during initialization. # # Either {#scope} or {#target_audience}, but not both, should be non-nil. # If {#scope} is set, this credential will produce access tokens. # If {#target_audience} is set, this credential will produce ID tokens. # # @param [String, nil] new_target_audience # def self.target_audience= new_target_audience @target_audience = new_target_audience end ## # The environment variables to search for credentials. Values can either be a file path to the # credentials file, or the JSON contents of the credentials file. # The env_vars will never be nil. If there are no vars, the empty array is returned. # # @return [Array] # def self.env_vars env_vars_internal || [] end ## # @private # Internal recursive lookup for env_vars. # def self.env_vars_internal lookup_auth_param :env_vars, :env_vars_internal do # Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists. path_env_vars = lookup_local_constant :PATH_ENV_VARS json_env_vars = lookup_local_constant :JSON_ENV_VARS (Array(path_env_vars) + Array(json_env_vars)).flatten.uniq if path_env_vars || json_env_vars end end ## # Sets the environment variables to search for credentials. # Setting to `nil` "unsets" the value, and defaults to the superclass # (or to the empty array if there is no superclass). # # @param [String, Array, nil] new_env_vars # def self.env_vars= new_env_vars new_env_vars = Array new_env_vars unless new_env_vars.nil? @env_vars = new_env_vars end ## # The file paths to search for credentials files. # The paths will never be nil. If there are no paths, the empty array is returned. # # @return [Array] # def self.paths paths_internal || [] end ## # @private # Internal recursive lookup for paths. # def self.paths_internal lookup_auth_param :paths, :paths_internal do # Pull in values if the DEFAULT_PATHS constant exists. vals = lookup_local_constant :DEFAULT_PATHS vals ? Array(vals).flatten.uniq : nil end end ## # Set the file paths to search for credentials files. # Setting to `nil` "unsets" the value, and defaults to the superclass # (or to the empty array if there is no superclass). # # @param [String, Array, nil] new_paths # def self.paths= new_paths new_paths = Array new_paths unless new_paths.nil? @paths = new_paths end ## # @private # Return the given parameter value, defaulting up the class hierarchy. # # First returns the value of the instance variable, if set. # Next, calls the given block if provided. (This is generally used to # look up legacy constant-based values.) # Otherwise, calls the superclass method if present. # Returns nil if all steps fail. # # @param name [Symbol] The parameter name # @param method_name [Symbol] The lookup method name, if different # @return [Object] The value # def self.lookup_auth_param name, method_name = name val = instance_variable_get "@#{name}".to_sym val = yield if val.nil? && block_given? return val unless val.nil? return superclass.send method_name if superclass.respond_to? method_name nil end ## # @private # Return the value of the given constant if it is defined directly in # this class, or nil if not. # # @param [Symbol] Name of the constant # @return [Object] The value # def self.lookup_local_constant name const_defined?(name, false) ? const_get(name) : nil end ## # The Signet::OAuth2::Client object the Credentials instance is using. # # @return [Signet::OAuth2::Client] # attr_accessor :client ## # Identifier for the project the client is authenticating with. # # @return [String] # attr_reader :project_id ## # Identifier for a separate project used for billing/quota, if any. # # @return [String,nil] # attr_reader :quota_project_id # @private Delegate client methods to the client object. extend Forwardable ## # @!attribute [r] token_credential_uri # @return [String] The token credential URI. The URI is the authorization server's HTTP # endpoint capable of issuing tokens and refreshing expired tokens. # # @!attribute [r] audience # @return [String] The target audience ID when issuing assertions. Used only by the # assertion grant type. # # @!attribute [r] scope # @return [String, Array] The scope for this client. A scope is an access range # defined by the authorization server. The scope can be a single value or a list of values. # # @!attribute [r] target_audience # @return [String] The final target audience for ID tokens returned by this credential. # # @!attribute [r] issuer # @return [String] The issuer ID associated with this client. # # @!attribute [r] signing_key # @return [String, OpenSSL::PKey] The signing key associated with this client. # # @!attribute [r] updater_proc # @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method, # suitable for passing as a closure. # def_delegators :@client, :token_credential_uri, :audience, :scope, :issuer, :signing_key, :updater_proc, :target_audience ## # Creates a new Credentials instance with the provided auth credentials, and with the default # values configured on the class. # # @param [String, Hash, Signet::OAuth2::Client] keyfile # The keyfile can be provided as one of the following: # # * The path to a JSON keyfile (as a +String+) # * The contents of a JSON keyfile (as a +Hash+) # * A +Signet::OAuth2::Client+ object # @param [Hash] options # The options for configuring the credentials instance. The following is supported: # # * +:scope+ - the scope for the client # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client # * +:connection_builder+ - the connection builder to use for the client # * +:default_connection+ - the default connection to use for the client # def initialize keyfile, options = {} verify_keyfile_provided! keyfile @project_id = options["project_id"] || options["project"] @quota_project_id = options["quota_project_id"] case keyfile when Signet::OAuth2::Client update_from_signet keyfile when Hash update_from_hash keyfile, options else update_from_filepath keyfile, options end CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id @project_id ||= CredentialsLoader.load_gcloud_project_id @client.fetch_access_token! if @client.needs_access_token? @env_vars = nil @paths = nil @scope = nil end ## # Creates a new Credentials instance with auth credentials acquired by searching the # environment variables and paths configured on the class, and with the default values # configured on the class. # # The auth credentials are searched for in the following order: # # 1. configured environment variables (see {Credentials.env_vars}) # 2. configured default file paths (see {Credentials.paths}) # 3. application default (see {Google::Auth.get_application_default}) # # @param [Hash] options # The options for configuring the credentials instance. The following is supported: # # * +:scope+ - the scope for the client # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client # * +:connection_builder+ - the connection builder to use for the client # * +:default_connection+ - the default connection to use for the client # # @return [Credentials] # def self.default options = {} # First try to find keyfile file or json from environment variables. client = from_env_vars options # Second try to find keyfile file from known file paths. client ||= from_default_paths options # Finally get instantiated client from Google::Auth client ||= from_application_default options client end ## # @private Lookup Credentials from environment variables. def self.from_env_vars options env_vars.each do |env_var| str = ENV[env_var] next if str.nil? io = if ::File.file? str ::StringIO.new ::File.read str else json = ::JSON.parse str rescue nil json ? ::StringIO.new(str) : nil end next if io.nil? return from_io io, options end nil end ## # @private Lookup Credentials from default file paths. def self.from_default_paths options paths.each do |path| next unless path && ::File.file?(path) io = ::StringIO.new ::File.read path return from_io io, options end nil end ## # @private Lookup Credentials using Google::Auth.get_application_default. def self.from_application_default options scope = options[:scope] || self.scope auth_opts = { token_credential_uri: options[:token_credential_uri] || token_credential_uri, audience: options[:audience] || audience, target_audience: options[:target_audience] || target_audience, enable_self_signed_jwt: options[:enable_self_signed_jwt] && options[:scope].nil? } client = Google::Auth.get_application_default scope, auth_opts new client, options end # @private Read credentials from a JSON stream. def self.from_io io, options creds_input = { json_key_io: io, scope: options[:scope] || scope, target_audience: options[:target_audience] || target_audience, enable_self_signed_jwt: options[:enable_self_signed_jwt] && options[:scope].nil?, token_credential_uri: options[:token_credential_uri] || token_credential_uri, audience: options[:audience] || audience } client = Google::Auth::DefaultCredentials.make_creds creds_input new client end private_class_method :from_env_vars, :from_default_paths, :from_application_default, :from_io 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.to_h.transform_keys(&:to_s) end # rubocop:disable Metrics/AbcSize def client_options options # Keyfile options have higher priority over constructor defaults options["token_credential_uri"] ||= self.class.token_credential_uri options["audience"] ||= self.class.audience options["scope"] ||= self.class.scope options["target_audience"] ||= self.class.target_audience if !Array(options["scope"]).empty? && options["target_audience"] raise ArgumentError, "Cannot specify both scope and target_audience" end needs_scope = options["target_audience"].nil? # client options for initializing signet client { token_credential_uri: options["token_credential_uri"], audience: options["audience"], scope: (needs_scope ? Array(options["scope"]) : nil), target_audience: options["target_audience"], issuer: options["client_email"], signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) } end # rubocop:enable Metrics/AbcSize def update_from_signet client @project_id ||= client.project_id if client.respond_to? :project_id @quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id @client = client end def update_from_hash hash, options hash = stringify_hash_keys hash hash["scope"] ||= options[:scope] hash["target_audience"] ||= options[:target_audience] @project_id ||= (hash["project_id"] || hash["project"]) @quota_project_id ||= hash["quota_project_id"] @client = init_client hash, options end def update_from_filepath path, options verify_keyfile_exists! path json = JSON.parse ::File.read(path) json["scope"] ||= options[:scope] json["target_audience"] ||= options[:target_audience] @project_id ||= (json["project_id"] || json["project"]) @quota_project_id ||= json["quota_project_id"] @client = init_client json, options end end end end googleauth-1.3.0/lib/googleauth/application_default.rb0000644000004100000410000000544414333131016023143 0ustar www-datawww-data# 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. require "googleauth/compute_engine" require "googleauth/default_credentials" module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth NOT_FOUND_ERROR = <<~ERROR_MESSAGE.freeze Could not load the default credentials. Browse to https://developers.google.com/accounts/docs/application-default-credentials for more information ERROR_MESSAGE module_function # Obtains the default credentials implementation to use in this # environment. # # Use this to obtain the Application Default Credentials for accessing # Google APIs. Application Default Credentials are described in detail # at https://cloud.google.com/docs/authentication/production. # # If supplied, scope is used to create the credentials instance, when it can # be applied. E.g, on google compute engine and for user credentials the # scope is ignored. # # @param scope [string|array|nil] the scope(s) to access # @param options [Hash] Connection options. These may be used to configure # the `Faraday::Connection` used for outgoing HTTP requests. For # example, if a connection proxy must be used in the current network, # you may provide a connection with with the needed proxy options. # The following keys are recognized: # * `:default_connection` The connection object to use for token # refresh requests. # * `:connection_builder` A `Proc` that creates and returns a # connection to use for token refresh requests. # * `:connection` The connection to use to determine whether GCE # metadata credentials are available. def get_application_default scope = nil, options = {} creds = DefaultCredentials.from_env(scope, options) || DefaultCredentials.from_well_known_path(scope, options) || DefaultCredentials.from_system_default_path(scope, options) return creds unless creds.nil? unless GCECredentials.on_gce? options # Clear cache of the result of GCECredentials.on_gce? GCECredentials.unmemoize_all raise NOT_FOUND_ERROR end GCECredentials.new scope: scope end end end googleauth-1.3.0/lib/googleauth/web_user_authorizer.rb0000644000004100000410000002502014333131016023213 0ustar www-datawww-data# Copyright 2014 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. require "multi_json" require "googleauth/signet" require "googleauth/user_authorizer" require "googleauth/user_refresh" require "securerandom" module Google module Auth # Varation on {Google::Auth::UserAuthorizer} adapted for Rack based # web applications. # # Example usage: # # get('/') do # user_id = request.session['user_email'] # credentials = authorizer.get_credentials(user_id, request) # if credentials.nil? # redirect authorizer.get_authorization_url(user_id: user_id, # request: request) # end # # Credentials are valid, can call APIs # ... # end # # get('/oauth2callback') do # url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred( # request) # redirect url # end # # Instead of implementing the callback directly, applications are # encouraged to use {Google::Auth::WebUserAuthorizer::CallbackApp} instead. # # @see CallbackApp # @note Requires sessions are enabled class WebUserAuthorizer < Google::Auth::UserAuthorizer STATE_PARAM = "state".freeze AUTH_CODE_KEY = "code".freeze ERROR_CODE_KEY = "error".freeze SESSION_ID_KEY = "session_id".freeze CALLBACK_STATE_KEY = "g-auth-callback".freeze CURRENT_URI_KEY = "current_uri".freeze XSRF_KEY = "g-xsrf-token".freeze SCOPE_KEY = "scope".freeze NIL_REQUEST_ERROR = "Request is required.".freeze NIL_SESSION_ERROR = "Sessions must be enabled".freeze MISSING_AUTH_CODE_ERROR = "Missing authorization code in request".freeze AUTHORIZATION_ERROR = "Authorization error: %s".freeze INVALID_STATE_TOKEN_ERROR = "State token does not match expected value".freeze class << self attr_accessor :default end # Handle the result of the oauth callback. This version defers the # exchange of the code by temporarily stashing the results in the user's # session. This allows apps to use the generic # {Google::Auth::WebUserAuthorizer::CallbackApp} handler for the callback # without any additional customization. # # Apps that wish to handle the callback directly should use # {#handle_auth_callback} instead. # # @param [Rack::Request] request # Current request def self.handle_auth_callback_deferred request callback_state, redirect_uri = extract_callback_state request request.session[CALLBACK_STATE_KEY] = MultiJson.dump callback_state redirect_uri end # Initialize the authorizer # # @param [Google::Auth::ClientID] client_id # Configured ID & secret for this application # @param [String, Array] scope # Authorization scope to request # @param [Google::Auth::Stores::TokenStore] token_store # Backing storage for persisting user credentials # @param [String] callback_uri # URL (either absolute or relative) of the auth callback. Defaults # to '/oauth2callback' def initialize client_id, scope, token_store, callback_uri = nil super client_id, scope, token_store, callback_uri end # Handle the result of the oauth callback. Exchanges the authorization # code from the request and persists to storage. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Rack::Request] request # Current request # @return (Google::Auth::UserRefreshCredentials, String) # credentials & next URL to redirect to def handle_auth_callback user_id, request callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state( request ) WebUserAuthorizer.validate_callback_state callback_state, request credentials = get_and_store_credentials_from_code( user_id: user_id, code: callback_state[AUTH_CODE_KEY], scope: callback_state[SCOPE_KEY], base_url: request.url ) [credentials, redirect_uri] end # Build the URL for requesting authorization. # # @param [String] login_hint # Login hint if need to authorize a specific account. Should be a # user's email address or unique profile ID. # @param [Rack::Request] request # Current request # @param [String] redirect_to # Optional URL to proceed to after authorization complete. Defaults to # the current URL. # @param [String, Array] scope # Authorization scope to request. Overrides the instance scopes if # not nil. # @param [Hash] state # Optional key-values to be returned to the oauth callback. # @return [String] # Authorization url def get_authorization_url options = {} options = options.dup request = options[:request] raise NIL_REQUEST_ERROR if request.nil? raise NIL_SESSION_ERROR if request.session.nil? state = options[:state] || {} redirect_to = options[:redirect_to] || request.url request.session[XSRF_KEY] = SecureRandom.base64 options[:state] = MultiJson.dump(state.merge( SESSION_ID_KEY => request.session[XSRF_KEY], CURRENT_URI_KEY => redirect_to )) options[:base_url] = request.url super options end # Fetch stored credentials for the user from the given request session. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Rack::Request] request # Current request. Optional. If omitted, this will attempt to fall back # on the base class behavior of reading from the token store. # @param [Array, String] scope # If specified, only returns credentials that have all the \ # requested scopes # @return [Google::Auth::UserRefreshCredentials] # Stored credentials, nil if none present # @raise [Signet::AuthorizationError] # May raise an error if an authorization code is present in the session # and exchange of the code fails def get_credentials user_id, request = nil, scope = nil if request&.session&.key? CALLBACK_STATE_KEY # Note - in theory, no need to check required scope as this is # expected to be called immediately after a return from authorization state_json = request.session.delete CALLBACK_STATE_KEY callback_state = MultiJson.load state_json WebUserAuthorizer.validate_callback_state callback_state, request get_and_store_credentials_from_code( user_id: user_id, code: callback_state[AUTH_CODE_KEY], scope: callback_state[SCOPE_KEY], base_url: request.url ) else super user_id, scope end end def self.extract_callback_state request state = MultiJson.load(request[STATE_PARAM] || "{}") redirect_uri = state[CURRENT_URI_KEY] callback_state = { AUTH_CODE_KEY => request[AUTH_CODE_KEY], ERROR_CODE_KEY => request[ERROR_CODE_KEY], SESSION_ID_KEY => state[SESSION_ID_KEY], SCOPE_KEY => request[SCOPE_KEY] } [callback_state, redirect_uri] end # Verifies the results of an authorization callback # # @param [Hash] state # Callback state # @option state [String] AUTH_CODE_KEY # The authorization code # @option state [String] ERROR_CODE_KEY # Error message if failed # @param [Rack::Request] request # Current request def self.validate_callback_state state, request raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[AUTH_CODE_KEY].nil? if state[ERROR_CODE_KEY] raise Signet::AuthorizationError, format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY]) elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY] raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR end end # Small Rack app which acts as the default callback handler for the app. # # To configure in Rails, add to routes.rb: # # match '/oauth2callback', # to: Google::Auth::WebUserAuthorizer::CallbackApp, # via: :all # # With Rackup, add to config.ru: # # map '/oauth2callback' do # run Google::Auth::WebUserAuthorizer::CallbackApp # end # # Or in a classic Sinatra app: # # get('/oauth2callback') do # Google::Auth::WebUserAuthorizer::CallbackApp.call(env) # end # # @see Google::Auth::WebUserAuthorizer class CallbackApp LOCATION_HEADER = "Location".freeze REDIR_STATUS = 302 ERROR_STATUS = 500 # Handle a rack request. Simply stores the results the authorization # in the session temporarily and redirects back to to the previously # saved redirect URL. Credentials can be later retrieved by calling. # {Google::Auth::Web::WebUserAuthorizer#get_credentials} # # See {Google::Auth::Web::WebUserAuthorizer#get_authorization_uri} # for how to initiate authorization requests. # # @param [Hash] env # Rack environment # @return [Array] # HTTP response def self.call env request = Rack::Request.new env return_url = WebUserAuthorizer.handle_auth_callback_deferred request if return_url [REDIR_STATUS, { LOCATION_HEADER => return_url }, []] else [ERROR_STATUS, {}, ["No return URL is present in the request."]] end end def call env self.class.call env end end end end end googleauth-1.3.0/lib/googleauth/signet.rb0000644000004100000410000000703014333131016020416 0ustar www-datawww-data# 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. 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 # The token type as symbol, either :id_token or :access_token def token_type target_audience ? :id_token : :access_token end # Whether the id_token or access_token is missing or about to expire. def needs_access_token? send(token_type).nil? || expires_within?(60) end # Updates a_hash updated with the authentication token def apply! a_hash, opts = {} # fetch the access token there is currently not one, or if the client # has expired fetch_access_token! opts if needs_access_token? a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}" end # Returns a clone of a_hash updated with the authentication token def apply a_hash, opts = {} a_copy = a_hash.clone apply! a_copy, opts a_copy end # Returns a reference to the #apply method, suitable for passing as # a closure def updater_proc proc { |a_hash, opts = {}| apply a_hash, opts } end def on_refresh &block @refresh_listeners = [] unless defined? @refresh_listeners @refresh_listeners << block end alias orig_fetch_access_token! fetch_access_token! def fetch_access_token! options = {} unless options[:connection] connection = build_default_connection options = options.merge connection: connection if connection end info = retry_with_error do orig_fetch_access_token! options end notify_refresh_listeners info end def notify_refresh_listeners listeners = defined?(@refresh_listeners) ? @refresh_listeners : [] listeners.each do |block| block.call self end end def build_default_connection if !defined?(@connection_info) nil elsif @connection_info.respond_to? :call @connection_info.call else @connection_info end end def retry_with_error max_retry_count = 5 retry_count = 0 begin yield rescue StandardError => e raise e if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError) if retry_count < max_retry_count retry_count += 1 sleep retry_count * 0.3 retry else msg = "Unexpected error: #{e.inspect}" raise Signet::AuthorizationError, msg end end end end end end googleauth-1.3.0/lib/googleauth/stores/0000755000004100000410000000000014333131016020117 5ustar www-datawww-datagoogleauth-1.3.0/lib/googleauth/stores/file_token_store.rb0000644000004100000410000000276314333131016024007 0ustar www-datawww-data# Copyright 2014 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. 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 = {} super() 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-1.3.0/lib/googleauth/stores/redis_token_store.rb0000644000004100000410000000462414333131016024174 0ustar www-datawww-data# Copyright 2014 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. 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 = {} super() 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-1.3.0/lib/googleauth/id_tokens.rb0000644000004100000410000002107414333131016021110 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2020 Google LLC # # 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. require "googleauth/id_tokens/errors" require "googleauth/id_tokens/key_sources" require "googleauth/id_tokens/verifier" module Google module Auth ## # ## Verifying Google ID tokens # # This module verifies ID tokens issued by Google. This can be used to # authenticate signed-in users using OpenID Connect. See # https://developers.google.com/identity/sign-in/web/backend-auth for more # information. # # ### Basic usage # # To verify an ID token issued by Google accounts: # # payload = Google::Auth::IDTokens.verify_oidc the_token, # aud: "my-app-client-id" # # If verification succeeds, you will receive the token's payload as a hash. # If verification fails, an exception (normally a subclass of # {Google::Auth::IDTokens::VerificationError}) will be raised. # # To verify an ID token issued by the Google identity-aware proxy (IAP): # # payload = Google::Auth::IDTokens.verify_iap the_token, # aud: "my-app-client-id" # # These methods will automatically download and cache the Google public # keys necessary to verify these tokens. They will also automatically # verify the issuer (`iss`) field for their respective types of ID tokens. # # ### Advanced usage # # If you want to provide your own public keys, either by pointing at a # custom URI or by providing the key data directly, use the Verifier class # and pass in a key source. # # To point to a custom URI that returns a JWK set: # # source = Google::Auth::IDTokens::JwkHttpKeySource.new "https://example.com/jwk" # verifier = Google::Auth::IDTokens::Verifier.new key_source: source # payload = verifier.verify the_token, aud: "my-app-client-id" # # To provide key data directly: # # jwk_data = { # keys: [ # { # alg: "ES256", # crv: "P-256", # kid: "LYyP2g", # kty: "EC", # use: "sig", # x: "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU", # y: "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI" # } # ] # } # source = Google::Auth::IDTokens::StaticKeySource.from_jwk_set jwk_data # verifier = Google::Auth::IDTokens::Verifier key_source: source # payload = verifier.verify the_token, aud: "my-app-client-id" # module IDTokens ## # A list of issuers expected for Google OIDC-issued tokens. # # @return [Array] # OIDC_ISSUERS = ["accounts.google.com", "https://accounts.google.com"].freeze ## # A list of issuers expected for Google IAP-issued tokens. # # @return [Array] # IAP_ISSUERS = ["https://cloud.google.com/iap"].freeze ## # The URL for Google OAuth2 V3 public certs # # @return [String] # OAUTH2_V3_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs" ## # The URL for Google IAP public keys # # @return [String] # IAP_JWK_URL = "https://www.gstatic.com/iap/verify/public_key-jwk" class << self ## # The key source providing public keys that can be used to verify # ID tokens issued by Google OIDC. # # @return [Google::Auth::IDTokens::JwkHttpKeySource] # def oidc_key_source @oidc_key_source ||= JwkHttpKeySource.new OAUTH2_V3_CERTS_URL end ## # The key source providing public keys that can be used to verify # ID tokens issued by Google IAP. # # @return [Google::Auth::IDTokens::JwkHttpKeySource] # def iap_key_source @iap_key_source ||= JwkHttpKeySource.new IAP_JWK_URL end ## # Reset all convenience key sources. Used for testing. # @private # def forget_sources! @oidc_key_source = @iap_key_source = nil self end ## # A convenience method that verifies a token allegedly issued by Google # OIDC. # # @param token [String] The ID token to verify # @param aud [String,Array,nil] The expected audience. At least # one `aud` field in the token must match at least one of the # provided audiences, or the verification will fail with # {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the # default), no audience checking is performed. # @param azp [String,Array,nil] The expected authorized party # (azp). At least one `azp` field in the token must match at least # one of the provided values, or the verification will fail with # {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil` # (the default), no azp checking is performed. # @param aud [String,Array,nil] The expected audience. At least # one `iss` field in the token must match at least one of the # provided issuers, or the verification will fail with # {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer # checking is performed. Default is to check against {OIDC_ISSUERS}. # # @return [Hash] The decoded token payload. # @raise [KeySourceError] if the key source failed to obtain public keys # @raise [VerificationError] if the token verification failed. # Additional data may be available in the error subclass and message. # def verify_oidc token, aud: nil, azp: nil, iss: OIDC_ISSUERS verifier = Verifier.new key_source: oidc_key_source, aud: aud, azp: azp, iss: iss verifier.verify token end ## # A convenience method that verifies a token allegedly issued by Google # IAP. # # @param token [String] The ID token to verify # @param aud [String,Array,nil] The expected audience. At least # one `aud` field in the token must match at least one of the # provided audiences, or the verification will fail with # {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the # default), no audience checking is performed. # @param azp [String,Array,nil] The expected authorized party # (azp). At least one `azp` field in the token must match at least # one of the provided values, or the verification will fail with # {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil` # (the default), no azp checking is performed. # @param aud [String,Array,nil] The expected audience. At least # one `iss` field in the token must match at least one of the # provided issuers, or the verification will fail with # {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer # checking is performed. Default is to check against {IAP_ISSUERS}. # # @return [Hash] The decoded token payload. # @raise [KeySourceError] if the key source failed to obtain public keys # @raise [VerificationError] if the token verification failed. # Additional data may be available in the error subclass and message. # def verify_iap token, aud: nil, azp: nil, iss: IAP_ISSUERS verifier = Verifier.new key_source: iap_key_source, aud: aud, azp: azp, iss: iss verifier.verify token end end end end end googleauth-1.3.0/lib/googleauth/service_account.rb0000644000004100000410000002014714333131016022305 0ustar www-datawww-data# 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. require "googleauth/signet" require "googleauth/credentials_loader" require "googleauth/json_key_reader" require "jwt" require "multi_json" require "stringio" module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth # Authenticates requests using Google's Service Account credentials via an # OAuth access token. # # This class allows authorizing requests for service accounts directly # from credentials from a json key file downloaded from the developer # console (via 'Generate new Json Key'). # # cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production) class ServiceAccountCredentials < Signet::OAuth2::Client TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze extend CredentialsLoader extend JsonKeyReader attr_reader :project_id attr_reader :quota_project_id def enable_self_signed_jwt? @enable_self_signed_jwt end # 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, enable_self_signed_jwt, target_audience, audience, token_credential_uri = options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience, :audience, :token_credential_uri raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience if json_key_io private_key, client_email, project_id, quota_project_id = read_json_key json_key_io else private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR] client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] project_id = ENV[CredentialsLoader::PROJECT_ID_VAR] quota_project_id = nil end project_id ||= CredentialsLoader.load_gcloud_project_id new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI, audience: audience || TOKEN_CRED_URI, scope: scope, enable_self_signed_jwt: enable_self_signed_jwt, target_audience: target_audience, issuer: client_email, signing_key: OpenSSL::PKey::RSA.new(private_key), project_id: project_id, quota_project_id: quota_project_id) .configure_connection(options) end # Handles certain escape sequences that sometimes appear in input. # Specifically, interprets the "\n" sequence for newline, and removes # enclosing quotes. def self.unescape str str = str.gsub '\n', "\n" str = str[1..-2] if str.start_with?('"') && str.end_with?('"') str end def initialize options = {} @project_id = options[:project_id] @quota_project_id = options[:quota_project_id] @enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false super options end # Extends the base class to use a transient # ServiceAccountJwtHeaderCredentials for certain cases. def apply! a_hash, opts = {} # Use a self-singed JWT if there's no information that can be used to # obtain an OAuth token, OR if there are scopes but also an assertion # that they are default scopes that shouldn't be used to fetch a token. if target_audience.nil? && (scope.nil? || enable_self_signed_jwt?) apply_self_signed_jwt! a_hash else super end end private def apply_self_signed_jwt! a_hash # Use the ServiceAccountJwtHeaderCredentials using the same cred values cred_json = { private_key: @signing_key.to_s, client_email: @issuer, project_id: @project_id, quota_project_id: @quota_project_id } key_io = StringIO.new MultiJson.dump(cred_json) alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io, scope: scope alt.apply! a_hash end end # Authenticates requests using Google's Service Account credentials via # JWT Header. # # This class allows authorizing requests for service accounts directly # from credentials from a json key file downloaded from the developer # console (via 'Generate new Json Key'). It is not part of any OAuth2 # flow, rather it creates a JWT and sends that as a credential. # # cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production) class ServiceAccountJwtHeaderCredentials JWT_AUD_URI_KEY = :jwt_aud_uri AUTH_METADATA_KEY = Signet::OAuth2::AUTH_METADATA_KEY TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze SIGNING_ALGORITHM = "RS256".freeze EXPIRY = 60 extend CredentialsLoader extend JsonKeyReader attr_reader :project_id attr_reader :quota_project_id # Create a ServiceAccountJwtHeaderCredentials. # # @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 new json_key_io: json_key_io, scope: scope end # Initializes a ServiceAccountJwtHeaderCredentials. # # @param json_key_io [IO] an IO from which the JSON key can be read def initialize options = {} json_key_io = options[:json_key_io] if json_key_io @private_key, @issuer, @project_id, @quota_project_id = self.class.read_json_key json_key_io else @private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR] @issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] @project_id = ENV[CredentialsLoader::PROJECT_ID_VAR] @quota_project_id = nil end @project_id ||= CredentialsLoader.load_gcloud_project_id @signing_key = OpenSSL::PKey::RSA.new @private_key @scope = options[:scope] 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? && @scope.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 proc { |a_hash, opts = {}| apply a_hash, opts } end protected # Creates a jwt uri token. def new_jwt_token jwt_aud_uri = nil, options = {} now = Time.new skew = options[:skew] || 60 assertion = { "iss" => @issuer, "sub" => @issuer, "exp" => (now + EXPIRY).to_i, "iat" => (now - skew).to_i } jwt_aud_uri = nil if @scope assertion["scope"] = Array(@scope).join " " if @scope assertion["aud"] = jwt_aud_uri if jwt_aud_uri JWT.encode assertion, @signing_key, SIGNING_ALGORITHM end end end end googleauth-1.3.0/lib/googleauth/default_credentials.rb0000644000004100000410000000516014333131016023130 0ustar www-datawww-data# 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. 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-1.3.0/lib/googleauth/token_store.rb0000644000004100000410000000311514333131016021461 0ustar www-datawww-data# Copyright 2014 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. 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-1.3.0/lib/googleauth/credentials_loader.rb0000644000004100000410000002020114333131016022743 0ustar www-datawww-data# 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. 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 ENV_VAR = "GOOGLE_APPLICATION_CREDENTIALS".freeze PRIVATE_KEY_VAR = "GOOGLE_PRIVATE_KEY".freeze CLIENT_EMAIL_VAR = "GOOGLE_CLIENT_EMAIL".freeze CLIENT_ID_VAR = "GOOGLE_CLIENT_ID".freeze CLIENT_SECRET_VAR = "GOOGLE_CLIENT_SECRET".freeze REFRESH_TOKEN_VAR = "GOOGLE_REFRESH_TOKEN".freeze ACCOUNT_TYPE_VAR = "GOOGLE_ACCOUNT_TYPE".freeze PROJECT_ID_VAR = "GOOGLE_PROJECT_ID".freeze GCLOUD_POSIX_COMMAND = "gcloud".freeze GCLOUD_WINDOWS_COMMAND = "gcloud.cmd".freeze GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none".freeze CREDENTIALS_FILE_NAME = "application_default_credentials.json".freeze NOT_FOUND_ERROR = "Unable to read the credential file specified by #{ENV_VAR}".freeze WELL_KNOWN_PATH = "gcloud/#{CREDENTIALS_FILE_NAME}".freeze WELL_KNOWN_ERROR = "Unable to read the default credential file".freeze SYSTEM_DEFAULT_ERROR = "Unable to read the system default credential file".freeze CLOUD_SDK_CLIENT_ID = "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.app" \ "s.googleusercontent.com".freeze CLOUD_SDK_CREDENTIALS_WARNING = "Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most " \ "server applications use service accounts instead. If your application continues to use end user credentials " \ 'from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about ' \ "service accounts, see https://cloud.google.com/docs/authentication/. To suppress this message, set the " \ "GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS environment variable.".freeze # make_creds proxies the construction of a credentials instance # # By default, it calls #new on the current class, but this behaviour can # be modified, allowing different instances to be created. def make_creds *args creds = new(*args) creds = creds.configure_connection args[0] if creds.respond_to?(:configure_connection) && args.size == 1 creds end # Creates an instance from the path specified in an environment # variable. # # @param scope [string|array|nil] the scope(s) to access # @param options [Hash] Connection options. These may be used to configure # how OAuth tokens are retrieved, by providing a suitable # `Faraday::Connection`. For example, if a connection proxy must be # used in the current network, you may provide a connection with # with the needed proxy options. # The following keys are recognized: # * `:default_connection` The connection object to use. # * `:connection_builder` A `Proc` that returns a connection. def from_env scope = nil, options = {} options = interpret_options scope, options if ENV.key?(ENV_VAR) && !ENV[ENV_VAR].empty? path = ENV[ENV_VAR] raise "file #{path} does not exist" unless File.exist? path File.open path do |f| return make_creds options.merge(json_key_io: f) end elsif service_account_env_vars? || authorized_user_env_vars? make_creds options end rescue StandardError => e raise "#{NOT_FOUND_ERROR}: #{e}" end # Creates an instance from a well known path. # # @param scope [string|array|nil] the scope(s) to access # @param options [Hash] Connection options. These may be used to configure # how OAuth tokens are retrieved, by providing a suitable # `Faraday::Connection`. For example, if a connection proxy must be # used in the current network, you may provide a connection with # with the needed proxy options. # The following keys are recognized: # * `:default_connection` The connection object to use. # * `:connection_builder` A `Proc` that returns a connection. def from_well_known_path scope = nil, options = {} options = interpret_options scope, options home_var = OS.windows? ? "APPDATA" : "HOME" base = WELL_KNOWN_PATH root = ENV[home_var].nil? ? "" : ENV[home_var] base = File.join ".config", base unless OS.windows? path = File.join root, base return nil unless File.exist? path File.open path do |f| return make_creds options.merge(json_key_io: f) end rescue StandardError => e raise "#{WELL_KNOWN_ERROR}: #{e}" end # Creates an instance from the system default path # # @param scope [string|array|nil] the scope(s) to access # @param options [Hash] Connection options. These may be used to configure # how OAuth tokens are retrieved, by providing a suitable # `Faraday::Connection`. For example, if a connection proxy must be # used in the current network, you may provide a connection with # with the needed proxy options. # The following keys are recognized: # * `:default_connection` The connection object to use. # * `:connection_builder` A `Proc` that returns a connection. def from_system_default_path scope = nil, options = {} options = interpret_options scope, options if OS.windows? return nil unless ENV["ProgramData"] prefix = File.join ENV["ProgramData"], "Google/Auth" else prefix = "/etc/google/auth/" end path = File.join prefix, CREDENTIALS_FILE_NAME return nil unless File.exist? path File.open path do |f| return make_creds options.merge(json_key_io: f) end rescue StandardError => e raise "#{SYSTEM_DEFAULT_ERROR}: #{e}" end module_function # Issues warning if cloud sdk client id is used def warn_if_cloud_sdk_credentials client_id return if ENV["GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS"] warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID end # Finds project_id from gcloud CLI configuration def load_gcloud_project_id gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows? gcloud = GCLOUD_POSIX_COMMAND unless OS.windows? gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", in: :close, err: :close, &:read) config = MultiJson.load gcloud_json config["configuration"]["properties"]["core"]["project"] rescue StandardError nil end private def interpret_options scope, options if scope.is_a? Hash options = scope scope = nil end return options.merge scope: scope if scope && !options[:scope] options end def service_account_env_vars? ([PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR] - ENV.keys).empty? && !ENV.to_h.fetch_values(PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR).join(" ").empty? end def authorized_user_env_vars? ([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] - ENV.keys).empty? && !ENV.to_h.fetch_values(CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR).join(" ").empty? end end end end googleauth-1.3.0/lib/googleauth/iam.rb0000644000004100000410000000351314333131016017675 0ustar www-datawww-data# 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. 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 proc { |a_hash, _opts = {}| apply a_hash } end end end end googleauth-1.3.0/lib/googleauth/user_refresh.rb0000644000004100000410000001067114333131016021626 0ustar www-datawww-data# 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. require "googleauth/signet" require "googleauth/credentials_loader" require "googleauth/scope_util" require "multi_json" module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth # Authenticates requests using User Refresh credentials. # # This class allows authorizing requests from user refresh tokens. # # This the end of the result of a 3LO flow. E.g, the end result of # 'gcloud auth login' saves a file with these contents in well known # location # # cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production) class UserRefreshCredentials < Signet::OAuth2::Client TOKEN_CRED_URI = "https://oauth2.googleapis.com/token".freeze AUTHORIZATION_URI = "https://accounts.google.com/o/oauth2/auth".freeze REVOKE_TOKEN_URI = "https://oauth2.googleapis.com/revoke".freeze extend CredentialsLoader attr_reader :project_id attr_reader :quota_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], "quota_project_id" => nil } 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"], quota_project_id: user_creds["quota_project_id"], scope: scope) .configure_connection(options) end # Reads the client_id, client_secret and refresh_token fields from the # JSON key. def self.read_json_key json_key_io json_key = MultiJson.load json_key_io.read wanted = ["client_id", "client_secret", "refresh_token"] wanted.each do |key| raise "the json is missing the #{key} field" unless json_key.key? key end json_key end def initialize options = {} options ||= {} options[:token_credential_uri] ||= TOKEN_CRED_URI options[:authorization_uri] ||= AUTHORIZATION_URI @project_id = options[:project_id] @project_id ||= CredentialsLoader.load_gcloud_project_id @quota_project_id = options[:quota_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-1.3.0/lib/googleauth/compute_engine.rb0000644000004100000410000001072614333131016022134 0ustar www-datawww-data# 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. require "faraday" require "googleauth/signet" require "memoist" module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth NO_METADATA_SERVER_ERROR = <<~ERROR.freeze Error code 404 trying to get security access token from Compute Engine metadata for the default service account. This may be because the virtual machine instance does not have permission scopes specified. ERROR UNEXPECTED_ERROR_SUFFIX = <<~ERROR.freeze trying to get security access token from Compute Engine metadata for the default service account ERROR # Extends Signet::OAuth2::Client so that the auth token is obtained from # the GCE metadata server. class GCECredentials < Signet::OAuth2::Client # The IP Address is used in the URIs to speed up failures on non-GCE # systems. DEFAULT_METADATA_HOST = "169.254.169.254".freeze # @private Unused and deprecated COMPUTE_AUTH_TOKEN_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze # @private Unused and deprecated COMPUTE_ID_TOKEN_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze # @private Unused and deprecated COMPUTE_CHECK_URI = "http://169.254.169.254".freeze class << self extend Memoist def metadata_host ENV.fetch "GCE_METADATA_HOST", DEFAULT_METADATA_HOST end def compute_check_uri "http://#{metadata_host}".freeze end def compute_auth_token_uri "#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/token".freeze end def compute_id_token_uri "#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/identity".freeze end # Detect if this appear to be a GCE instance, by checking if metadata # is available. def on_gce? options = {} # TODO: This should use google-cloud-env instead. c = options[:connection] || Faraday.default_connection headers = { "Metadata-Flavor" => "Google" } resp = c.get compute_check_uri, nil, headers do |req| req.options.timeout = 1.0 req.options.open_timeout = 0.1 end return false unless resp.status == 200 resp.headers["Metadata-Flavor"] == "Google" rescue Faraday::TimeoutError, Faraday::ConnectionFailed false end memoize :on_gce? end # Overrides the super class method to change how access tokens are # fetched. def fetch_access_token options = {} c = options[:connection] || Faraday.default_connection retry_with_error do uri = target_audience ? GCECredentials.compute_id_token_uri : GCECredentials.compute_auth_token_uri query = target_audience ? { "audience" => target_audience, "format" => "full" } : {} query[:scopes] = Array(scope).join "," if scope resp = c.get uri, query, "Metadata-Flavor" => "Google" case resp.status when 200 content_type = resp.headers["content-type"] if ["text/html", "application/text"].include? content_type { (target_audience ? "id_token" : "access_token") => resp.body } else Signet::OAuth2.parse_credentials resp.body, content_type end when 403, 500 msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}" raise Signet::UnexpectedStatusError, msg 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-1.3.0/lib/googleauth/user_authorizer.rb0000644000004100000410000002460214333131016022363 0ustar www-datawww-data# Copyright 2014 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. require "uri" require "multi_json" require "googleauth/signet" require "googleauth/user_refresh" module Google module Auth # Handles an interactive 3-Legged-OAuth2 (3LO) user consent authorization. # # Example usage for a simple command line app: # # credentials = authorizer.get_credentials(user_id) # if credentials.nil? # url = authorizer.get_authorization_url( # base_url: OOB_URI) # puts "Open the following URL in the browser and enter the " + # "resulting code after authorization" # puts url # code = gets # credentials = authorizer.get_and_store_credentials_from_code( # user_id: user_id, code: code, base_url: OOB_URI) # end # # Credentials ready to use, call APIs # ... class UserAuthorizer MISMATCHED_CLIENT_ID_ERROR = "Token client ID of %s does not match configured client id %s".freeze NIL_CLIENT_ID_ERROR = "Client id can not be nil.".freeze NIL_SCOPE_ERROR = "Scope can not be nil.".freeze NIL_USER_ID_ERROR = "User ID can not be nil.".freeze NIL_TOKEN_STORE_ERROR = "Can not call method if token store is nil".freeze MISSING_ABSOLUTE_URL_ERROR = 'Absolute base url required for relative callback url "%s"'.freeze # Initialize the authorizer # # @param [Google::Auth::ClientID] client_id # Configured ID & secret for this application # @param [String, Array] scope # Authorization scope to request # @param [Google::Auth::Stores::TokenStore] token_store # Backing storage for persisting user credentials # @param [String] callback_uri # URL (either absolute or relative) of the auth callback. # Defaults to '/oauth2callback' def initialize client_id, scope, token_store, callback_uri = nil raise NIL_CLIENT_ID_ERROR if client_id.nil? raise NIL_SCOPE_ERROR if scope.nil? @client_id = client_id @scope = Array(scope) @token_store = token_store @callback_uri = callback_uri || "/oauth2callback" end # Build the URL for requesting authorization. # # @param [String] login_hint # Login hint if need to authorize a specific account. Should be a # user's email address or unique profile ID. # @param [String] state # Opaque state value to be returned to the oauth callback. # @param [String] base_url # Absolute URL to resolve the configured callback uri against. Required # if the configured callback uri is a relative. # @param [String, Array] scope # Authorization scope to request. Overrides the instance scopes if not # nil. # @return [String] # Authorization url def get_authorization_url options = {} scope = options[:scope] || @scope credentials = UserRefreshCredentials.new( client_id: @client_id.id, client_secret: @client_id.secret, scope: scope ) redirect_uri = redirect_uri_for options[:base_url] url = credentials.authorization_uri(access_type: "offline", redirect_uri: redirect_uri, approval_prompt: "force", state: options[:state], include_granted_scopes: true, login_hint: options[:login_hint]) url.to_s end # Fetch stored credentials for the user. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Array, String] scope # If specified, only returns credentials that have all # the requested scopes # @return [Google::Auth::UserRefreshCredentials] # Stored credentials, nil if none present def get_credentials user_id, scope = nil saved_token = stored_token user_id return nil if saved_token.nil? data = MultiJson.load saved_token if data.fetch("client_id", @client_id.id) != @client_id.id raise format(MISMATCHED_CLIENT_ID_ERROR, data["client_id"], @client_id.id) end credentials = UserRefreshCredentials.new( client_id: @client_id.id, client_secret: @client_id.secret, scope: data["scope"] || @scope, access_token: data["access_token"], refresh_token: data["refresh_token"], expires_at: data.fetch("expiration_time_millis", 0) / 1000 ) scope ||= @scope return monitor_credentials user_id, credentials if credentials.includes_scope? scope nil end # Exchanges an authorization code returned in the oauth callback # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [String] code # The authorization code from the OAuth callback # @param [String, Array] scope # Authorization scope requested. Overrides the instance # scopes if not nil. # @param [String] base_url # Absolute URL to resolve the configured callback uri against. # Required if the configured # callback uri is a relative. # @return [Google::Auth::UserRefreshCredentials] # Credentials if exchange is successful def get_credentials_from_code options = {} user_id = options[:user_id] code = options[:code] scope = options[:scope] || @scope base_url = options[:base_url] credentials = UserRefreshCredentials.new( client_id: @client_id.id, client_secret: @client_id.secret, redirect_uri: redirect_uri_for(base_url), scope: scope ) credentials.code = code credentials.fetch_access_token!({}) monitor_credentials user_id, credentials end # Exchanges an authorization code returned in the oauth callback. # Additionally, stores the resulting credentials in the token store if # the exchange is successful. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [String] code # The authorization code from the OAuth callback # @param [String, Array] scope # Authorization scope requested. Overrides the instance # scopes if not nil. # @param [String] base_url # Absolute URL to resolve the configured callback uri against. # Required if the configured # callback uri is a relative. # @return [Google::Auth::UserRefreshCredentials] # Credentials if exchange is successful def get_and_store_credentials_from_code options = {} credentials = get_credentials_from_code options store_credentials options[:user_id], credentials end # Revokes a user's credentials. This both revokes the actual # grant as well as removes the token from the token store. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. def revoke_authorization user_id credentials = get_credentials user_id if credentials begin @token_store.delete user_id ensure credentials.revoke! end end nil end # Store credentials for a user. Generally not required to be # called directly, but may be used to migrate tokens from one # store to another. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Google::Auth::UserRefreshCredentials] credentials # Credentials to store. def store_credentials user_id, credentials json = MultiJson.dump( client_id: credentials.client_id, access_token: credentials.access_token, refresh_token: credentials.refresh_token, scope: credentials.scope, expiration_time_millis: credentials.expires_at.to_i * 1000 ) @token_store.store user_id, json credentials end private # @private Fetch stored token with given user_id # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @return [String] The saved token from @token_store def stored_token user_id raise NIL_USER_ID_ERROR if user_id.nil? raise NIL_TOKEN_STORE_ERROR if @token_store.nil? @token_store.load user_id end # Begin watching a credential for refreshes so the access token can be # saved. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Google::Auth::UserRefreshCredentials] credentials # Credentials to store. def monitor_credentials user_id, credentials credentials.on_refresh do |cred| store_credentials user_id, cred end credentials end # Resolve the redirect uri against a base. # # @param [String] base_url # Absolute URL to resolve the callback against if necessary. # @return [String] # Redirect URI def redirect_uri_for base_url return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil? raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil? URI.join(base_url, @callback_uri).to_s end # Check if URI is Google's postmessage flow (not a valid redirect_uri by spec, but allowed) def uri_is_postmessage? uri uri.to_s.casecmp("postmessage").zero? end end end end googleauth-1.3.0/lib/googleauth/json_key_reader.rb0000644000004100000410000000240414333131016022270 0ustar www-datawww-data# 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. module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth # JsonKeyReader contains the behaviour used to read private key and # client email fields from the service account module JsonKeyReader def read_json_key json_key_io json_key = MultiJson.load json_key_io.read raise "missing client_email" unless json_key.key? "client_email" raise "missing private_key" unless json_key.key? "private_key" [ json_key["private_key"], json_key["client_email"], json_key["project_id"], json_key["quota_project_id"] ] end end end end googleauth-1.3.0/lib/googleauth.rb0000644000004100000410000000150014333131016017121 0ustar www-datawww-data# 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. require "googleauth/application_default" require "googleauth/client_id" require "googleauth/credentials" require "googleauth/default_credentials" require "googleauth/id_tokens" require "googleauth/user_authorizer" require "googleauth/web_user_authorizer" googleauth-1.3.0/.yardopts0000644000004100000410000000022214333131016015536 0ustar www-datawww-data--no-private --title=Google Auth --markup markdown --markup-provider redcarpet ./lib/**/*.rb - README.md CHANGELOG.md CODE_OF_CONDUCT.md LICENSE googleauth-1.3.0/SECURITY.md0000644000004100000410000000051114333131016015462 0ustar www-datawww-data# Security Policy To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). The Google Security Team will respond within 5 working days of your report on g.co/vulnz. We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.