googleauth-0.5.1/0000755000175000017500000000000013043403213012670 5ustar pravipravigoogleauth-0.5.1/CHANGELOG.md0000644000175000017500000000356113043403213014506 0ustar pravipravi## 0.5.1 (06/01/2016) ### Changes * Change header name emitted by `Client#apply` from "Authorization" to "authorization" ([@murgatroid99][]) * Fix ADC not working on some windows machines ([@vsubramani][]) [#55](https://github.com/google/google-auth-library-ruby/issues/55) ## 0.5.0 (12/10/2015) ### Changes * Initial support for user credentials ([@sqrrrl][]) * Update Signet to 0.7 ## 0.4.2 (05/08/2015) ### Changes * Updated UserRefreshCredentials hash to use string keys ([@haabaato][]) [#36](https://github.com/google/google-auth-library-ruby/issues/36) * Add support for a system default credentials file. ([@mr-salty][]) [#33](https://github.com/google/google-auth-library-ruby/issues/33) * Fix bug when loading credentials from ENV ([@dwilkie][]) [#31](https://github.com/google/google-auth-library-ruby/issues/31) * Relax the constraint of dependent version of multi_json ([@igrep][]) [#30](https://github.com/google/google-auth-library-ruby/issues/30) ### Changes * Enables passing credentials via environment variables. ([@haabaato][]) [#27](https://github.com/google/google-auth-library-ruby/issues/27) ## 0.4.1 (25/04/2015) ### Changes * Improves handling of --no-scopes GCE authorization ([@tbetbetbe][]) * Refactoring and cleanup ([@joneslee85][]) ## 0.4.0 (25/03/2015) ### Changes * Adds an implementation of JWT header auth ([@tbetbetbe][]) ## 0.3.0 (23/03/2015) ### Changes * makes the scope parameter's optional in all APIs. ([@tbetbetbe][]) * changes the scope parameter's position in various constructors. ([@tbetbetbe][]) [@dwilkie]: https://github.com/dwilkie [@haabaato]: https://github.com/haabaato [@igrep]: https://github.com/igrep [@joneslee85]: https://github.com/joneslee85 [@mr-salty]: https://github.com/mr-salty [@tbetbetbe]: https://github.com/tbetbetbe [@murgatroid99]: https://github.com/murgatroid99 [@vsubramani]: https://github.com/vsubramani googleauth-0.5.1/README.md0000644000175000017500000001360613043403213014155 0ustar pravipravi# Google Auth Library for Ruby
Homepage
http://www.github.com/google/google-auth-library-ruby
Authors
Tim Emiola
Copyright
Copyright © 2015 Google, Inc.
License
Apache 2.0
[![Gem Version](https://badge.fury.io/rb/googleauth.svg)](http://badge.fury.io/rb/googleauth) [![Build Status](https://secure.travis-ci.org/google/google-auth-library-ruby.png)](http://travis-ci.org/google/google-auth-library-ruby) [![Coverage Status](https://coveralls.io/repos/google/google-auth-library-ruby/badge.png)](https://coveralls.io/r/google/google-auth-library-ruby) [![Dependency Status](https://gemnasium.com/google/google-auth-library-ruby.png)](https://gemnasium.com/google/google-auth-library-ruby) ## Description This is Google's officially supported ruby client library for using OAuth 2.0 authorization and authentication with Google APIs. ## Alpha This library is in Alpha. We will make an effort to support the library, but we reserve the right to make incompatible changes when necessary. ## Install Be sure `https://rubygems.org/` is in your gem sources. For normal client usage, this is sufficient: ```bash $ gem install googleauth ``` ## Example Usage ```ruby require 'googleauth' # Get the environment configured authorization scopes = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/compute'] authorization = Google::Auth.get_application_default(scopes) # Add the the access token obtained using the authorization to a hash, e.g # headers. some_headers = {} authorization.apply(some_headers) ``` ## Application Default Credentials This library provides an implementation of [application default credentials][application default credentials] for Ruby. The Application Default Credentials provide a simple way to get authorization credentials for use in calling Google APIs. They are best suited for cases when the call needs to have the same identity and authorization level for the application independent of the user. This is the recommended approach to authorize calls to Cloud APIs, particularly when you're building an application that uses Google Compute Engine. ## User Credentials The library also provides support for requesting and storing user credentials (3-Legged OAuth2.) Two implementations are currently available, a generic authorizer useful for command line apps or custom integrations as well as a web variant tailored toward Rack-based applications. The authorizers are intended for authorization use cases. For sign-on, see [Google Idenity 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(user_id: 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' scope = 'https://www.googleapis.com/auth/drive' client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json') token_store = Google::Auth::Stores::FileTokenStore.new( :file => '/path/to/tokens.yaml') authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store) credentials = authorizer.get_credentials(user_id) if credentials.nil? url = authorizer.get_authorization_url(base_url: 'urn:ietf:wg:oauth:2.0:oob') 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 ``` ### Storage Authorizers require a storage instance to manage long term persistence of access and refresh tokens. Two storage implementations are included: * Google::Auth::Stores::FileTokenStore * Google::Auth::Stores::RedisTokenStore Custom storage implementations can also be used. See [token_store.rb](lib/googleauth/token_store.rb) for additional details. ## What about auth in google-apis-ruby-client? The goal is for all auth done by [google-apis-ruby-client][google-apis-ruby-client] to be performed by this library. I.e, eventually google-apis-ruby-client will just take a dependency on this library. This update is a work in progress, but should be completed by Q2 2015. ## License This library is licensed under Apache 2.0. Full license text is available in [COPYING][copying]. ## Contributing See [CONTRIBUTING][contributing]. ## Support Please [report bugs at the project on Github](https://github.com/google/google-auth-library-ruby/issues). Don't hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-auth-library-ruby) about the client or APIs on [StackOverflow](http://stackoverflow.com). [google-apis-ruby-client]: (https://github.com/google/google-api-ruby-client) [application default credentials]: (https://developers.google.com/accounts/docs/application-default-credentials) [contributing]: https://github.com/google/google-auth-library-ruby/tree/master/CONTRIBUTING.md [copying]: https://github.com/google/google-auth-library-ruby/tree/master/COPYING googleauth-0.5.1/.rubocop.yml0000644000175000017500000000004013043403213015134 0ustar pravipraviinherit_from: .rubocop_todo.yml googleauth-0.5.1/googleauth.gemspec0000755000175000017500000000232713043403213016402 0ustar pravipravi# -*- ruby -*- # encoding: utf-8 $LOAD_PATH.push File.expand_path('../lib', __FILE__) require 'googleauth/version' Gem::Specification.new do |s| s.name = 'googleauth' s.version = Google::Auth::VERSION s.authors = ['Tim Emiola'] s.email = 'temiola@google.com' s.homepage = 'https://github.com/google/google-auth-library-ruby' s.summary = 'Google Auth Library for Ruby' s.license = 'Apache-2.0' s.description = <<-eos Allows simple authorization for accessing Google APIs. Provide support for Application Default Credentials, as described at https://developers.google.com/accounts/docs/application-default-credentials eos s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- spec/*`.split("\n") s.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f| File.basename(f) end s.require_paths = ['lib'] s.platform = Gem::Platform::RUBY s.add_dependency 'faraday', '~> 0.9' s.add_dependency 'logging', '~> 2.0' s.add_dependency 'jwt', '~> 1.4' s.add_dependency 'memoist', '~> 0.12' s.add_dependency 'multi_json', '~> 1.11' s.add_dependency 'os', '~> 0.9' s.add_dependency 'signet', '~> 0.7' end googleauth-0.5.1/lib/0000755000175000017500000000000013043403213013436 5ustar pravipravigoogleauth-0.5.1/lib/googleauth/0000755000175000017500000000000013043403213015574 5ustar pravipravigoogleauth-0.5.1/lib/googleauth/user_refresh.rb0000644000175000017500000001135513043403213020622 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'googleauth/signet' require 'googleauth/credentials_loader' require 'googleauth/scope_util' require 'multi_json' module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth # Authenticates requests using User Refresh credentials. # # This class allows authorizing requests from user refresh tokens. # # This the end of the result of a 3LO flow. E.g, the end result of # 'gcloud auth login' saves a file with these contents in well known # location # # cf [Application Default Credentials](http://goo.gl/mkAHpZ) class UserRefreshCredentials < Signet::OAuth2::Client TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token' AUTHORIZATION_URI = 'https://accounts.google.com/o/oauth2/auth' REVOKE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/revoke' extend CredentialsLoader # 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] } 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'], scope: scope) end # Reads the client_id, client_secret and refresh_token fields from the # JSON key. def self.read_json_key(json_key_io) json_key = MultiJson.load(json_key_io.read) wanted = %w(client_id client_secret refresh_token) wanted.each do |key| fail "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 super(options) end # Revokes the credential def revoke!(options = {}) c = options[:connection] || Faraday.default_connection resp = c.get(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 fail(Signet::AuthorizationError, "Unexpected error code #{resp.status}") end end # Verifies that a credential grants the requested scope # # @param [Array, String] required_scope # Scope to verify # @return [Boolean] # True if scope is granted def includes_scope?(required_scope) missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) - Google::Auth::ScopeUtil.normalize(scope) missing_scope.empty? end end end end googleauth-0.5.1/lib/googleauth/token_store.rb0000644000175000017500000000500513043403213020455 0ustar pravipravi# Copyright 2014, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. module Google module Auth # Interface definition for token stores. It is not required that # implementations inherit from this class. It is provided for documentation # purposes to illustrate the API contract. class TokenStore class << self attr_accessor :default end # Load the token data from storage for the given ID. # # @param [String] id # ID of token data to load. # @return [String] # The loaded token data. def load(_id) fail '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) fail '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) fail 'Not implemented' end end end end googleauth-0.5.1/lib/googleauth/compute_engine.rb0000644000175000017500000001050713043403213021125 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'faraday' require 'googleauth/signet' require 'memoist' module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth NO_METADATA_SERVER_ERROR = < 'Google' } resp = c.get(COMPUTE_AUTH_TOKEN_URI) case resp.status when 200 Signet::OAuth2.parse_credentials(resp.body, resp.headers['content-type']) when 404 fail(Signet::AuthorizationError, NO_METADATA_SERVER_ERROR) else msg = "Unexpected error code #{resp.status}" + UNEXPECTED_ERROR_SUFFIX fail(Signet::AuthorizationError, msg) end end end end end googleauth-0.5.1/lib/googleauth/iam.rb0000644000175000017500000000534413043403213016675 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'googleauth/signet' require 'googleauth/credentials_loader' require 'multi_json' module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth # Authenticates requests using IAM credentials. class IAMCredentials SELECTOR_KEY = 'x-goog-iam-authority-selector' TOKEN_KEY = 'x-goog-iam-authorization-token' # Initializes an IAMCredentials. # # @param selector the IAM selector. # @param token the IAM token. def initialize(selector, token) fail TypeError unless selector.is_a? String fail TypeError unless token.is_a? String @selector = selector @token = token end # Adds the credential fields to the hash. def apply!(a_hash) a_hash[SELECTOR_KEY] = @selector a_hash[TOKEN_KEY] = @token a_hash end # Returns a clone of a_hash updated with the authoriation header def apply(a_hash) a_copy = a_hash.clone apply!(a_copy) a_copy end # Returns a reference to the #apply method, suitable for passing as # a closure def updater_proc lambda(&method(:apply)) end end end end googleauth-0.5.1/lib/googleauth/signet.rb0000644000175000017500000000601113043403213017410 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'signet/oauth_2/client' module Signet # OAuth2 supports OAuth2 authentication. module OAuth2 AUTH_METADATA_KEY = :authorization # Signet::OAuth2::Client creates an OAuth2 client # # This reopens Client to add #apply and #apply! methods which update a # hash with the fetched authentication token. class Client # Updates a_hash updated with the authentication token def apply!(a_hash, opts = {}) # fetch the access token there is currently not one, or if the client # has expired fetch_access_token!(opts) if access_token.nil? || expires_within?(60) a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}" end # Returns a clone of a_hash updated with the authentication token def apply(a_hash, opts = {}) a_copy = a_hash.clone apply!(a_copy, opts) a_copy end # Returns a reference to the #apply method, suitable for passing as # a closure def updater_proc lambda(&method(:apply)) end def on_refresh(&block) @refresh_listeners ||= [] @refresh_listeners << block end alias_method :orig_fetch_access_token!, :fetch_access_token! def fetch_access_token!(options = {}) info = orig_fetch_access_token!(options) notify_refresh_listeners info end def notify_refresh_listeners listeners = @refresh_listeners || [] listeners.each do |block| block.call(self) end end end end end googleauth-0.5.1/lib/googleauth/user_authorizer.rb0000644000175000017500000002535013043403213021360 0ustar pravipravi# Copyright 2014, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'uri' require 'multi_json' require 'googleauth/signet' require 'googleauth/user_refresh' module Google module Auth # Handles an interactive 3-Legged-OAuth2 (3LO) user consent authorization. # # Example usage for a simple command line app: # # credentials = authorizer.get_credentials(user_id) # if credentials.nil? # url = authorizer.get_authorization_url( # base_url: OOB_URI) # puts "Open the following URL in the browser and enter the " + # "resulting code after authorization" # puts url # code = gets # credentials = authorizer.get_and_store_credentials_from_code( # user_id: user_id, code: code, base_url: OOB_URI) # end # # Credentials ready to use, call APIs # ... class UserAuthorizer MISMATCHED_CLIENT_ID_ERROR = 'Token client ID of %s does not match configured client id %s' NIL_CLIENT_ID_ERROR = 'Client id can not be nil.' NIL_SCOPE_ERROR = 'Scope can not be nil.' NIL_USER_ID_ERROR = 'User ID can not be nil.' NIL_TOKEN_STORE_ERROR = 'Can not call method if token store is nil' MISSING_ABSOLUTE_URL_ERROR = 'Absolute base url required for relative callback url "%s"' # 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) fail NIL_CLIENT_ID_ERROR if client_id.nil? fail 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) fail NIL_USER_ID_ERROR if user_id.nil? fail NIL_TOKEN_STORE_ERROR if @token_store.nil? scope ||= @scope saved_token = @token_store.load(user_id) return nil if saved_token.nil? data = MultiJson.load(saved_token) if data.fetch('client_id', @client_id.id) != @client_id.id fail sprintf(MISMATCHED_CLIENT_ID_ERROR, data['client_id'], @client_id.id) end credentials = UserRefreshCredentials.new( client_id: @client_id.id, client_secret: @client_id.secret, scope: data['scope'] || @scope, access_token: data['access_token'], refresh_token: data['refresh_token'], expires_at: data.fetch('expiration_time_millis', 0) / 1000) if credentials.includes_scope?(scope) monitor_credentials(user_id, credentials) return credentials end nil end # Exchanges an authorization code returned in the oauth callback # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [String] code # The authorization code from the OAuth callback # @param [String, Array] scope # Authorization scope requested. Overrides the instance # scopes if not nil. # @param [String] base_url # Absolute URL to resolve the configured callback uri against. # Required if the configured # callback uri is a relative. # @return [Google::Auth::UserRefreshCredentials] # Credentials if exchange is successful def get_credentials_from_code(options = {}) user_id = options[:user_id] code = options[:code] scope = options[:scope] || @scope base_url = options[:base_url] credentials = UserRefreshCredentials.new( client_id: @client_id.id, client_secret: @client_id.secret, redirect_uri: redirect_uri_for(base_url), scope: scope) credentials.code = code credentials.fetch_access_token!({}) monitor_credentials(user_id, credentials) end # Exchanges an authorization code returned in the oauth callback. # Additionally, stores the resulting credentials in the token store if # the exchange is successful. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [String] code # The authorization code from the OAuth callback # @param [String, Array] scope # Authorization scope requested. Overrides the instance # scopes if not nil. # @param [String] base_url # Absolute URL to resolve the configured callback uri against. # Required if the configured # callback uri is a relative. # @return [Google::Auth::UserRefreshCredentials] # Credentials if exchange is successful def get_and_store_credentials_from_code(options = {}) credentials = get_credentials_from_code(options) monitor_credentials(options[:user_id], credentials) 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 # Begin watching a credential for refreshes so the access token can be # saved. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Google::Auth::UserRefreshCredentials] credentials # Credentials to store. def monitor_credentials(user_id, credentials) credentials.on_refresh do |cred| store_credentials(user_id, cred) end credentials end # Resolve the redirect uri against a base. # # @param [String] base_url # Absolute URL to resolve the callback against if necessary. # @return [String] # Redirect URI def redirect_uri_for(base_url) return @callback_uri unless URI(@callback_uri).scheme.nil? fail sprintf( MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil? URI.join(base_url, @callback_uri).to_s end end end end googleauth-0.5.1/lib/googleauth/client_id.rb0000644000175000017500000000725513043403213020064 0ustar pravipravi# Copyright 2014, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'multi_json' module Google module Auth # Representation of an application's identity for user authorization # flows. class ClientId INSTALLED_APP = 'installed' WEB_APP = 'web' CLIENT_ID = 'client_id' CLIENT_SECRET = 'client_secret' MISSING_TOP_LEVEL_ELEMENT_ERROR = "Expected top level property 'installed' or 'web' to be present." # 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) fail 'Client id can not be nil' if id.nil? fail 'Client secret can not be nil' if secret.nil? @id = id @secret = secret end # Constructs a Client ID from a JSON file downloaed from the # Google Developers Console. # # @param [String, File] file # Path of file to read from # @return [Google::Auth::ClientID] def self.from_file(file) fail '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) fail 'Hash can not be nil.' if config.nil? raw_detail = config[INSTALLED_APP] || config[WEB_APP] fail MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil? ClientId.new(raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET]) end end end end googleauth-0.5.1/lib/googleauth/web_user_authorizer.rb0000644000175000017500000002604313043403213022215 0ustar pravipravi# Copyright 2014, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'multi_json' require 'googleauth/signet' require 'googleauth/user_authorizer' require 'googleauth/user_refresh' require 'securerandom' module Google module Auth # Varation on {Google::Auth::UserAuthorizer} adapted for Rack based # web applications. # # Example usage: # # get('/') do # user_id = request.session['user_email'] # credentials = authorizer.get_credentials(user_id, request) # if credentials.nil? # redirect authorizer.get_authorization_url(user_id: user_id, # request: request) # end # # Credentials are valid, can call APIs # ... # end # # get('/oauth2callback') do # url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred( # request) # redirect url # end # # Instead of implementing the callback directly, applications are # encouraged to use {Google::Auth::Web::AuthCallbackApp} instead. # # For rails apps, see {Google::Auth::ControllerHelpers} # # @see {Google::Auth::AuthCallbackApp} # @see {Google::Auth::ControllerHelpers} # @note Requires sessions are enabled class WebUserAuthorizer < Google::Auth::UserAuthorizer STATE_PARAM = 'state' AUTH_CODE_KEY = 'code' ERROR_CODE_KEY = 'error' SESSION_ID_KEY = 'session_id' CALLBACK_STATE_KEY = 'g-auth-callback' CURRENT_URI_KEY = 'current_uri' XSRF_KEY = 'g-xsrf-token' SCOPE_KEY = 'scope' NIL_REQUEST_ERROR = 'Request is required.' NIL_SESSION_ERROR = 'Sessions must be enabled' MISSING_AUTH_CODE_ERROR = 'Missing authorization code in request' AUTHORIZATION_ERROR = 'Authorization error: %s' INVALID_STATE_TOKEN_ERROR = 'State token does not match expected value' 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. # @return [String] # Authorization url def get_authorization_url(options = {}) options = options.dup request = options[:request] fail NIL_REQUEST_ERROR if request.nil? fail NIL_SESSION_ERROR if request.session.nil? redirect_to = options[:redirect_to] || request.url request.session[XSRF_KEY] = SecureRandom.base64 options[:state] = MultiJson.dump( SESSION_ID_KEY => request.session[XSRF_KEY], CURRENT_URI_KEY => redirect_to) options[:base_url] = request.url super(options) end # Fetch stored credentials for the user. # # @param [String] user_id # Unique ID of the user for loading/storing credentials. # @param [Rack::Request] request # Current request # @param [Array, String] scope # If specified, only returns credentials that have all the \ # requested scopes # @return [Google::Auth::UserRefreshCredentials] # Stored credentials, nil if none present # @raise [Signet::AuthorizationError] # May raise an error if an authorization code is present in the session # and exchange of the code fails def get_credentials(user_id, request, scope = nil) if request.session.key?(CALLBACK_STATE_KEY) # Note - in theory, no need to check required scope as this is # expected to be called immediately after a return from authorization state_json = request.session.delete(CALLBACK_STATE_KEY) callback_state = MultiJson.load(state_json) WebUserAuthorizer.validate_callback_state(callback_state, request) get_and_store_credentials_from_code( user_id: user_id, code: callback_state[AUTH_CODE_KEY], scope: callback_state[SCOPE_KEY], base_url: request.url) else super(user_id, scope) end end def self.extract_callback_state(request) state = MultiJson.load(request[STATE_PARAM] || '{}') redirect_uri = state[CURRENT_URI_KEY] callback_state = { AUTH_CODE_KEY => request[AUTH_CODE_KEY], ERROR_CODE_KEY => request[ERROR_CODE_KEY], SESSION_ID_KEY => state[SESSION_ID_KEY], SCOPE_KEY => request[SCOPE_KEY] } [callback_state, redirect_uri] end # Verifies the results of an authorization callback # # @param [Hash] state # Callback state # @option state [String] AUTH_CODE_KEY # The authorization code # @option state [String] ERROR_CODE_KEY # Error message if failed # @param [Rack::Request] request # Current request def self.validate_callback_state(state, request) if state[AUTH_CODE_KEY].nil? fail Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR elsif state[ERROR_CODE_KEY] fail Signet::AuthorizationError, sprintf(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY]) elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY] fail 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' REDIR_STATUS = 302 ERROR_STATUS = 500 # Handle a rack request. Simply stores the results the authorization # in the session temporarily and redirects back to to the previously # saved redirect URL. Credentials can be later retrieved by calling. # {Google::Auth::Web::WebUserAuthorizer#get_credentials} # # See {Google::Auth::Web::WebUserAuthorizer#get_authorization_uri} # for how to initiate authorization requests. # # @param [Hash] env # Rack environment # @return [Array] # HTTP response def self.call(env) request = Rack::Request.new(env) return_url = WebUserAuthorizer.handle_auth_callback_deferred(request) if return_url [REDIR_STATUS, { LOCATION_HEADER => return_url }, []] else [ERROR_STATUS, {}, ['No return URL is present in the request.']] end end def call(env) self.class.call(env) end end end end end googleauth-0.5.1/lib/googleauth/credentials_loader.rb0000644000175000017500000001173413043403213021752 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'memoist' require 'os' require 'rbconfig' module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth # CredentialsLoader contains the behaviour used to locate and find default # credentials files on the file system. module CredentialsLoader extend Memoist ENV_VAR = 'GOOGLE_APPLICATION_CREDENTIALS' PRIVATE_KEY_VAR = 'GOOGLE_PRIVATE_KEY' CLIENT_EMAIL_VAR = 'GOOGLE_CLIENT_EMAIL' CLIENT_ID_VAR = 'GOOGLE_CLIENT_ID' CLIENT_SECRET_VAR = 'GOOGLE_CLIENT_SECRET' REFRESH_TOKEN_VAR = 'GOOGLE_REFRESH_TOKEN' ACCOUNT_TYPE_VAR = 'GOOGLE_ACCOUNT_TYPE' CREDENTIALS_FILE_NAME = 'application_default_credentials.json' NOT_FOUND_ERROR = "Unable to read the credential file specified by #{ENV_VAR}" WELL_KNOWN_PATH = "gcloud/#{CREDENTIALS_FILE_NAME}" WELL_KNOWN_ERROR = 'Unable to read the default credential file' SYSTEM_DEFAULT_ERROR = 'Unable to read the system default credential file' # 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) new(*args) end # Creates an instance from the path specified in an environment # variable. # # @param scope [string|array|nil] the scope(s) to access def from_env(scope = nil) if ENV.key?(ENV_VAR) path = ENV[ENV_VAR] fail "file #{path} does not exist" unless File.exist?(path) File.open(path) do |f| return make_creds(json_key_io: f, scope: scope) end elsif service_account_env_vars? || authorized_user_env_vars? return make_creds(scope: scope) 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 def from_well_known_path(scope = nil) 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(json_key_io: f, scope: scope) 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 def from_system_default_path(scope = nil) 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(json_key_io: f, scope: scope) end rescue StandardError => e raise "#{SYSTEM_DEFAULT_ERROR}: #{e}" end private def service_account_env_vars? ([PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR] - ENV.keys).empty? end def authorized_user_env_vars? ([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] - ENV.keys).empty? end end end end googleauth-0.5.1/lib/googleauth/stores/0000755000175000017500000000000013043403213017113 5ustar pravipravigoogleauth-0.5.1/lib/googleauth/stores/file_token_store.rb0000644000175000017500000000463713043403213023005 0ustar pravipravi# Copyright 2014, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'yaml/store' require 'googleauth/token_store' module Google module Auth module Stores # Implementation of user token storage backed by a local YAML file class FileTokenStore < Google::Auth::TokenStore # Create a new store with the supplied file. # # @param [String, File] file # Path to storage file def initialize(options = {}) path = options[:file] @store = YAML::Store.new(path) end # (see Google::Auth::Stores::TokenStore#load) def load(id) @store.transaction { @store[id] } end # (see Google::Auth::Stores::TokenStore#store) def store(id, token) @store.transaction { @store[id] = token } end # (see Google::Auth::Stores::TokenStore#delete) def delete(id) @store.transaction { @store.delete(id) } end end end end end googleauth-0.5.1/lib/googleauth/stores/redis_token_store.rb0000644000175000017500000000643513043403213023172 0ustar pravipravi# Copyright 2014, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'redis' require 'googleauth/token_store' module Google module Auth module Stores # Implementation of user token storage backed by Redis. Tokens # are stored as JSON using the supplied key, prefixed with # `g-user-token:` class RedisTokenStore < Google::Auth::TokenStore DEFAULT_KEY_PREFIX = 'g-user-token:' # Create a new store with the supplied redis client. # # @param [::Redis, String] redis # Initialized redis client to connect to. # @param [String] prefix # Prefix for keys in redis. Defaults to 'g-user-token:' # @note If no redis instance is provided, a new one is created and # the options passed through. You may include any other keys accepted # by `Redis.new` def initialize(options = {}) redis = options.delete(:redis) prefix = options.delete(:prefix) case redis when Redis @redis = redis else @redis = Redis.new(options) end @prefix = prefix || DEFAULT_KEY_PREFIX end # (see Google::Auth::Stores::TokenStore#load) def load(id) key = key_for(id) @redis.get(key) end # (see Google::Auth::Stores::TokenStore#store) def store(id, token) key = key_for(id) @redis.set(key, token) end # (see Google::Auth::Stores::TokenStore#delete) def delete(id) key = key_for(id) @redis.del(key) end private # Generate a redis key from a token ID # # @param [String] id # ID of the token # @return [String] # Redis key def key_for(id) @prefix + id end end end end end googleauth-0.5.1/lib/googleauth/scope_util.rb0000644000175000017500000000443513043403213020275 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'googleauth/signet' require 'googleauth/credentials_loader' require 'multi_json' module Google module Auth # Small utility for normalizing scopes into canonical form module ScopeUtil ALIASES = { 'email' => 'https://www.googleapis.com/auth/userinfo.email', 'profile' => 'https://www.googleapis.com/auth/userinfo.profile', 'openid' => 'https://www.googleapis.com/auth/plus.me' } 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 fail 'Invalid scope value. Must be string or array' end end end end end googleauth-0.5.1/lib/googleauth/service_account.rb0000644000175000017500000001713613043403213021305 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'googleauth/signet' require 'googleauth/credentials_loader' require 'jwt' require 'multi_json' require 'stringio' module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth # Authenticates requests using Google's Service Account credentials via an # OAuth access token. # # This class allows authorizing requests for service accounts directly # from credentials from a json key file downloaded from the developer # console (via 'Generate new Json Key'). # # cf [Application Default Credentials](http://goo.gl/mkAHpZ) class ServiceAccountCredentials < Signet::OAuth2::Client TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token' extend CredentialsLoader # Creates a ServiceAccountCredentials. # # @param json_key_io [IO] an IO from which the JSON key can be read # @param scope [string|array|nil] the scope(s) to access def self.make_creds(options = {}) json_key_io, scope = options.values_at(:json_key_io, :scope) if json_key_io private_key, client_email = read_json_key(json_key_io) else private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR] client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] end new(token_credential_uri: TOKEN_CRED_URI, audience: TOKEN_CRED_URI, scope: scope, issuer: client_email, signing_key: OpenSSL::PKey::RSA.new(private_key)) end # Reads the private key and client email fields from the service account # JSON key. def self.read_json_key(json_key_io) json_key = MultiJson.load(json_key_io.read) fail 'missing client_email' unless json_key.key?('client_email') fail 'missing private_key' unless json_key.key?('private_key') [json_key['private_key'], json_key['client_email']] end def initialize(options = {}) super(options) end # Extends the base class. # # If scope(s) is not set, it creates a transient # ServiceAccountJwtHeaderCredentials instance and uses that to # authenticate instead. def apply!(a_hash, opts = {}) # Use the base implementation if scopes are set unless scope.nil? super return end # Use the ServiceAccountJwtHeaderCredentials using the same cred values # if no scopes are set. cred_json = { private_key: @signing_key.to_s, client_email: @issuer } alt_clz = ServiceAccountJwtHeaderCredentials key_io = StringIO.new(MultiJson.dump(cred_json)) alt = alt_clz.make_creds(json_key_io: key_io) alt.apply!(a_hash) end end # Authenticates requests using Google's Service Account credentials via # JWT Header. # # This class allows authorizing requests for service accounts directly # from credentials from a json key file downloaded from the developer # console (via 'Generate new Json Key'). It is not part of any OAuth2 # flow, rather it creates a JWT and sends that as a credential. # # cf [Application Default Credentials](http://goo.gl/mkAHpZ) class ServiceAccountJwtHeaderCredentials JWT_AUD_URI_KEY = :jwt_aud_uri AUTH_METADATA_KEY = Signet::OAuth2::AUTH_METADATA_KEY TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token' SIGNING_ALGORITHM = 'RS256' EXPIRY = 60 extend CredentialsLoader # make_creds proxies the construction of a credentials instance # # make_creds is used by the methods in CredentialsLoader. # # By default, it calls #new with 2 args, the second one being an # optional scope. Here's the constructor only has one param, so # we modify make_creds to reflect this. def self.make_creds(*args) new(json_key_io: args[0][:json_key_io]) end # Reads the private key and client email fields from the service account # JSON key. def self.read_json_key(json_key_io) json_key = MultiJson.load(json_key_io.read) fail 'missing client_email' unless json_key.key?('client_email') fail 'missing private_key' unless json_key.key?('private_key') [json_key['private_key'], json_key['client_email']] 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, client_email = self.class.read_json_key(json_key_io) else private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR] client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] end @private_key = private_key @issuer = client_email @signing_key = OpenSSL::PKey::RSA.new(private_key) end # Construct a jwt token if the JWT_AUD_URI key is present in the input # hash. # # The jwt token is used as the value of a 'Bearer '. def apply!(a_hash, opts = {}) jwt_aud_uri = a_hash.delete(JWT_AUD_URI_KEY) return a_hash if jwt_aud_uri.nil? jwt_token = new_jwt_token(jwt_aud_uri, opts) a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}" a_hash end # Returns a clone of a_hash updated with the authoriation header def apply(a_hash, opts = {}) a_copy = a_hash.clone apply!(a_copy, opts) a_copy end # Returns a reference to the #apply method, suitable for passing as # a closure def updater_proc lambda(&method(:apply)) end protected # Creates a jwt uri token. def new_jwt_token(jwt_aud_uri, options = {}) now = Time.new skew = options[:skew] || 60 assertion = { 'iss' => @issuer, 'sub' => @issuer, 'aud' => jwt_aud_uri, 'exp' => (now + EXPIRY).to_i, 'iat' => (now - skew).to_i } JWT.encode(assertion, @signing_key, SIGNING_ALGORITHM) end end end end googleauth-0.5.1/lib/googleauth/version.rb0000644000175000017500000000324113043403213017606 0ustar pravipravi# Copyright 2014, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth VERSION = '0.5.1' end end googleauth-0.5.1/lib/googleauth.rb0000644000175000017500000001142513043403213016124 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'multi_json' require 'stringio' require 'googleauth/credentials_loader' require 'googleauth/compute_engine' require 'googleauth/service_account' require 'googleauth/user_refresh' require 'googleauth/client_id' require 'googleauth/user_authorizer' require 'googleauth/web_user_authorizer' module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth NOT_FOUND_ERROR = < 1.9' gem 'simplecov', '~> 0.9' gem 'coveralls', '~> 0.7' gem 'fakefs', '~> 0.6' gem 'rake', '~> 10.0' gem 'rubocop', '~> 0.30' gem 'rspec', '~> 3.0' gem 'redis', '~> 3.2' gem 'fakeredis', '~> 0.5' gem 'webmock', '~> 1.21' gem 'rack-test', '~> 0.6' gem 'sinatra' end platforms :jruby do group :development do end end googleauth-0.5.1/.rspec0000644000175000017500000000004013043403213013777 0ustar pravipravi--colour --format documentation googleauth-0.5.1/spec/0000755000175000017500000000000013043403213013622 5ustar pravipravigoogleauth-0.5.1/spec/spec_helper.rb0000644000175000017500000000535413043403213016447 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.dirname(__FILE__)) root_dir = File.expand_path(File.join(spec_dir, '..')) lib_dir = File.expand_path(File.join(root_dir, 'lib')) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift(lib_dir) $LOAD_PATH.uniq! # set up coverage require 'simplecov' require 'coveralls' SimpleCov.formatters = [ Coveralls::SimpleCov::Formatter, SimpleCov::Formatter::HTMLFormatter ] SimpleCov.start require 'faraday' require 'rspec' require 'logging' require 'rspec/logging_helper' require 'webmock/rspec' require 'multi_json' # Preload adapter to work around Rubinius error with FakeFS MultiJson.use(:json_gem) # Allow Faraday to support test stubs Faraday::Adapter.load_middleware(:test) # Configure RSpec to capture log messages for each test. The output from the # logs will be stored in the @log_output variable. It is a StringIO instance. RSpec.configure do |config| include RSpec::LoggingHelper config.capture_log_messages config.include WebMock::API end module TestHelpers include WebMock::API include WebMock::Matchers end class DummyTokenStore def initialize @tokens = {} end def load(id) @tokens[id] end def store(id, token) @tokens[id] = token end def delete(id) @tokens.delete(id) end end googleauth-0.5.1/spec/googleauth/0000755000175000017500000000000013043403213015760 5ustar pravipravigoogleauth-0.5.1/spec/googleauth/service_account_spec.rb0000644000175000017500000002724713043403213022507 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'apply_auth_examples' require 'fakefs/safe' require 'fileutils' require 'googleauth/service_account' require 'jwt' require 'multi_json' require 'openssl' require 'spec_helper' require 'tmpdir' include Google::Auth::CredentialsLoader shared_examples 'jwt header auth' do context 'when jwt_aud_uri is present' do let(:test_uri) { 'https://www.googleapis.com/myservice' } let(:auth_prefix) { 'Bearer ' } let(:auth_key) { ServiceAccountJwtHeaderCredentials::AUTH_METADATA_KEY } let(:jwt_uri_key) { ServiceAccountJwtHeaderCredentials::JWT_AUD_URI_KEY } def expect_is_encoded_jwt(hdr) expect(hdr).to_not be_nil expect(hdr.start_with?(auth_prefix)).to be true authorization = hdr[auth_prefix.length..-1] payload, = JWT.decode(authorization, @key.public_key) expect(payload['aud']).to eq(test_uri) expect(payload['iss']).to eq(client_email) end describe '#apply!' do it 'should update the target hash with a jwt token' do md = { foo: 'bar' } md[jwt_uri_key] = test_uri @client.apply!(md) auth_header = md[auth_key] expect_is_encoded_jwt(auth_header) expect(md[jwt_uri_key]).to be_nil end end describe 'updater_proc' do it 'should provide a proc that updates a hash with a jwt token' do md = { foo: 'bar' } md[jwt_uri_key] = test_uri the_proc = @client.updater_proc got = the_proc.call(md) auth_header = got[auth_key] expect_is_encoded_jwt(auth_header) expect(got[jwt_uri_key]).to be_nil expect(md[jwt_uri_key]).to_not be_nil end end describe '#apply' do it 'should not update the original hash with a jwt token' do md = { foo: 'bar' } md[jwt_uri_key] = test_uri the_proc = @client.updater_proc got = the_proc.call(md) auth_header = md[auth_key] expect(auth_header).to be_nil expect(got[jwt_uri_key]).to be_nil expect(md[jwt_uri_key]).to_not be_nil end it 'should add a jwt token to the returned hash' do md = { foo: 'bar' } md[jwt_uri_key] = test_uri got = @client.apply(md) auth_header = got[auth_key] expect_is_encoded_jwt(auth_header) end end end end describe Google::Auth::ServiceAccountCredentials do ServiceAccountCredentials = Google::Auth::ServiceAccountCredentials let(:client_email) { 'app@developer.gserviceaccount.com' } let(:cred_json) do { private_key_id: 'a_private_key_id', private_key: @key.to_pem, client_email: client_email, client_id: 'app.apps.googleusercontent.com', type: 'service_account' } end before(:example) do @key = OpenSSL::PKey::RSA.new(2048) @client = ServiceAccountCredentials.make_creds( json_key_io: StringIO.new(cred_json_text), scope: 'https://www.googleapis.com/auth/userinfo.profile' ) end def make_auth_stubs(opts = {}) access_token = opts[:access_token] || '' body = MultiJson.dump('access_token' => access_token, 'token_type' => 'Bearer', 'expires_in' => 3600) blk = proc do |request| params = Addressable::URI.form_unencode(request.body) _claim, _header = JWT.decode(params.assoc('assertion').last, @key.public_key) end stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') .with(body: hash_including( 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer'), &blk) .to_return(body: body, status: 200, headers: { 'Content-Type' => 'application/json' }) end def cred_json_text MultiJson.dump(cred_json) end it_behaves_like 'apply/apply! are OK' context 'when scope is nil' do before(:example) do @client.scope = nil end it_behaves_like 'jwt header auth' end describe '#from_env' do before(:example) do @var_name = ENV_VAR @credential_vars = [ ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR] @original_env_vals = {} @credential_vars.each { |var| @original_env_vals[var] = ENV[var] } ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] @scope = 'https://www.googleapis.com/auth/userinfo.profile' @clz = ServiceAccountCredentials end after(:example) do @credential_vars.each { |var| ENV[var] = @original_env_vals[var] } end it 'returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset' do ENV.delete(@var_name) unless ENV[@var_name].nil? expect(ServiceAccountCredentials.from_env(@scope)).to be_nil end it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do ENV.delete(@var_name) unless ENV[@var_name].nil? expect(ServiceAccountCredentials.from_env(@scope)).to be_nil Dir.mktmpdir do |dir| key_path = File.join(dir, 'does-not-exist') ENV[@var_name] = key_path expect { @clz.from_env(@scope) }.to raise_error RuntimeError end end it 'succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid' do Dir.mktmpdir do |dir| key_path = File.join(dir, 'my_cert_file') FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV[@var_name] = key_path expect(@clz.from_env(@scope)).to_not be_nil end end it 'succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are'\ ' valid' do ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] expect(@clz.from_env(@scope)).to_not be_nil end end describe '#from_well_known_path' do before(:example) do @home = ENV['HOME'] @scope = 'https://www.googleapis.com/auth/userinfo.profile' @known_path = WELL_KNOWN_PATH @clz = ServiceAccountCredentials end after(:example) do ENV['HOME'] = @home unless @home == ENV['HOME'] end it 'is nil if no file exists' do ENV['HOME'] = File.dirname(__FILE__) expect(ServiceAccountCredentials.from_well_known_path(@scope)).to be_nil end it 'successfully loads the file when it is present' do Dir.mktmpdir do |dir| key_path = File.join(dir, '.config', @known_path) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir expect(@clz.from_well_known_path(@scope)).to_not be_nil end end end describe '#from_system_default_path' do before(:example) do @scope = 'https://www.googleapis.com/auth/userinfo.profile' @path = File.join('/etc/google/auth/', CREDENTIALS_FILE_NAME) @clz = ServiceAccountCredentials end it 'is nil if no file exists' do FakeFS do expect(ServiceAccountCredentials.from_system_default_path(@scope)) .to be_nil end end it 'successfully loads the file when it is present' do FakeFS do FileUtils.mkdir_p(File.dirname(@path)) File.write(@path, cred_json_text) expect(@clz.from_system_default_path(@scope)).to_not be_nil File.delete(@path) end end end end describe Google::Auth::ServiceAccountJwtHeaderCredentials do ServiceAccountJwtHeaderCredentials = Google::Auth::ServiceAccountJwtHeaderCredentials let(:client_email) { 'app@developer.gserviceaccount.com' } let(:clz) { Google::Auth::ServiceAccountJwtHeaderCredentials } let(:cred_json) do { private_key_id: 'a_private_key_id', private_key: @key.to_pem, client_email: client_email, client_id: 'app.apps.googleusercontent.com', type: 'service_account' } end before(:example) do @key = OpenSSL::PKey::RSA.new(2048) @client = clz.make_creds(json_key_io: StringIO.new(cred_json_text)) end def cred_json_text MultiJson.dump(cred_json) end it_behaves_like 'jwt header auth' describe '#from_env' do before(:example) do @var_name = ENV_VAR @credential_vars = [ ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR] @original_env_vals = {} @credential_vars.each { |var| @original_env_vals[var] = ENV[var] } ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] end after(:example) do @credential_vars.each { |var| ENV[var] = @original_env_vals[var] } end it 'returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset' do ENV.delete(@var_name) unless ENV[@var_name].nil? expect(clz.from_env).to be_nil end it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do ENV.delete(@var_name) unless ENV[@var_name].nil? expect(clz.from_env).to be_nil Dir.mktmpdir do |dir| key_path = File.join(dir, 'does-not-exist') ENV[@var_name] = key_path expect { clz.from_env }.to raise_error RuntimeError end end it 'succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid' do Dir.mktmpdir do |dir| key_path = File.join(dir, 'my_cert_file') FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV[@var_name] = key_path expect(clz.from_env).to_not be_nil end end it 'succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are'\ ' valid' do ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] expect(clz.from_env(@scope)).to_not be_nil end end describe '#from_well_known_path' do before(:example) do @home = ENV['HOME'] end after(:example) do ENV['HOME'] = @home unless @home == ENV['HOME'] end it 'is nil if no file exists' do ENV['HOME'] = File.dirname(__FILE__) expect(clz.from_well_known_path).to be_nil end it 'successfully loads the file when it is present' do Dir.mktmpdir do |dir| key_path = File.join(dir, '.config', WELL_KNOWN_PATH) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir expect(clz.from_well_known_path).to_not be_nil end end end end googleauth-0.5.1/spec/googleauth/signet_spec.rb0000644000175000017500000000563713043403213020623 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'apply_auth_examples' require 'googleauth/signet' require 'jwt' require 'openssl' require 'spec_helper' describe Signet::OAuth2::Client do before(:example) do @key = OpenSSL::PKey::RSA.new(2048) @client = Signet::OAuth2::Client.new( token_credential_uri: 'https://accounts.google.com/o/oauth2/token', scope: 'https://www.googleapis.com/auth/userinfo.profile', issuer: 'app@example.com', audience: 'https://accounts.google.com/o/oauth2/token', signing_key: @key) end def make_auth_stubs(opts) access_token = opts[:access_token] || '' body = MultiJson.dump('access_token' => access_token, 'token_type' => 'Bearer', 'expires_in' => 3600) blk = proc do |request| params = Addressable::URI.form_unencode(request.body) _claim, _header = JWT.decode(params.assoc('assertion').last, @key.public_key) end stub_request(:post, 'https://accounts.google.com/o/oauth2/token') .with(body: hash_including( 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer'), &blk) .to_return(body: body, status: 200, headers: { 'Content-Type' => 'application/json' }) end it_behaves_like 'apply/apply! are OK' end googleauth-0.5.1/spec/googleauth/client_id_spec.rb0000644000175000017500000000775513043403213021267 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'spec_helper' require 'fakefs/safe' require 'googleauth' describe Google::Auth::ClientId do shared_examples 'it has a valid config' do it 'should include a valid id' do expect(client_id.id).to eql 'abc@example.com' end it 'should include a valid secret' do expect(client_id.secret).to eql 'notasecret' end end shared_examples 'it can successfully load client_id' do context 'loaded from hash' do let(:client_id) { Google::Auth::ClientId.from_hash(config) } it_behaves_like 'it has a valid config' end context 'loaded from file' do file_path = '/client_secrets.json' let(:client_id) do FakeFS do content = MultiJson.dump(config) File.write(file_path, content) Google::Auth::ClientId.from_file(file_path) end end it_behaves_like 'it has a valid config' end end describe 'with web config' do let(:config) do { 'web' => { 'client_id' => 'abc@example.com', 'client_secret' => 'notasecret' } } end it_behaves_like 'it can successfully load client_id' end describe 'with installed app config' do let(:config) do { 'installed' => { 'client_id' => 'abc@example.com', 'client_secret' => 'notasecret' } } end it_behaves_like 'it can successfully load client_id' end context 'with missing top level property' do let(:config) do { 'notvalid' => { 'client_id' => 'abc@example.com', 'client_secret' => 'notasecret' } } end it 'should raise error' do expect { Google::Auth::ClientId.from_hash(config) }.to raise_error( /Expected top level property/) end end context 'with missing client id' do let(:config) do { 'web' => { 'client_secret' => 'notasecret' } } end it 'should raise error' do expect { Google::Auth::ClientId.from_hash(config) }.to raise_error( /Client id can not be nil/) end end context 'with missing client secret' do let(:config) do { 'web' => { 'client_id' => 'abc@example.com' } } end it 'should raise error' do expect { Google::Auth::ClientId.from_hash(config) }.to raise_error( /Client secret can not be nil/) end end end googleauth-0.5.1/spec/googleauth/scope_util_spec.rb0000644000175000017500000000547413043403213021477 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'googleauth/scope_util' describe Google::Auth::ScopeUtil do shared_examples 'normalizes scopes' do let(:normalized) { Google::Auth::ScopeUtil.normalize(source) } it 'normalizes the email scope' do expect(normalized).to include( 'https://www.googleapis.com/auth/userinfo.email') expect(normalized).to_not include 'email' end it 'normalizes the profile scope' do expect(normalized).to include( 'https://www.googleapis.com/auth/userinfo.profile') expect(normalized).to_not include 'profile' end it 'normalizes the openid scope' do expect(normalized).to include 'https://www.googleapis.com/auth/plus.me' expect(normalized).to_not include 'openid' end it 'leaves other other scopes as-is' do expect(normalized).to include 'https://www.googleapis.com/auth/drive' end end context 'with scope as string' do let(:source) do 'email profile openid https://www.googleapis.com/auth/drive' end it_behaves_like 'normalizes scopes' end context 'with scope as Array' do let(:source) do %w(email profile openid https://www.googleapis.com/auth/drive) end it_behaves_like 'normalizes scopes' end end googleauth-0.5.1/spec/googleauth/user_refresh_spec.rb0000644000175000017500000002302013043403213022010 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'apply_auth_examples' require 'fakefs/safe' require 'fileutils' require 'googleauth/user_refresh' require 'jwt' require 'multi_json' require 'openssl' require 'spec_helper' require 'tmpdir' include Google::Auth::CredentialsLoader describe Google::Auth::UserRefreshCredentials do UserRefreshCredentials = Google::Auth::UserRefreshCredentials let(:cred_json) do { client_secret: 'privatekey', client_id: 'client123', refresh_token: 'refreshtoken', type: 'authorized_user' } end before(:example) do @key = OpenSSL::PKey::RSA.new(2048) @client = UserRefreshCredentials.make_creds( json_key_io: StringIO.new(cred_json_text), scope: 'https://www.googleapis.com/auth/userinfo.profile' ) end def make_auth_stubs(opts = {}) access_token = opts[:access_token] || '' body = MultiJson.dump('access_token' => access_token, 'token_type' => 'Bearer', 'expires_in' => 3600) stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') .with(body: hash_including('grant_type' => 'refresh_token')) .to_return(body: body, status: 200, headers: { 'Content-Type' => 'application/json' }) end def cred_json_text(missing = nil) cred_json.delete(missing.to_sym) unless missing.nil? MultiJson.dump(cred_json) end it_behaves_like 'apply/apply! are OK' describe '#from_env' do before(:example) do @var_name = ENV_VAR @credential_vars = [ ENV_VAR, CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR, ACCOUNT_TYPE_VAR] @original_env_vals = {} @credential_vars.each { |var| @original_env_vals[var] = ENV[var] } @scope = 'https://www.googleapis.com/auth/userinfo.profile' @clz = UserRefreshCredentials end after(:example) do @credential_vars.each { |var| ENV[var] = @original_env_vals[var] } end it 'returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset' do ENV.delete(@var_name) unless ENV[@var_name].nil? expect(UserRefreshCredentials.from_env(@scope)).to be_nil end it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do ENV.delete(@var_name) unless ENV[@var_name].nil? expect(UserRefreshCredentials.from_env(@scope)).to be_nil Dir.mktmpdir do |dir| key_path = File.join(dir, 'does-not-exist') ENV[@var_name] = key_path expect { @clz.from_env(@scope) }.to raise_error RuntimeError end end it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path file is invalid' do needed = %w(client_id client_secret refresh_token) needed.each do |missing| Dir.mktmpdir do |dir| key_path = File.join(dir, 'my_cert_file') FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text(missing)) ENV[@var_name] = key_path expect { @clz.from_env(@scope) }.to raise_error RuntimeError end end end it 'succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid' do Dir.mktmpdir do |dir| key_path = File.join(dir, 'my_cert_file') FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV[@var_name] = key_path expect(@clz.from_env(@scope)).to_not be_nil end end it 'succeeds when GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and '\ 'GOOGLE_REFRESH_TOKEN env vars are valid' do ENV[CLIENT_ID_VAR] = cred_json[:client_id] ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret] ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] creds = @clz.from_env(@scope) expect(creds).to_not be_nil expect(creds.client_id).to eq(cred_json[:client_id]) expect(creds.client_secret).to eq(cred_json[:client_secret]) expect(creds.refresh_token).to eq(cred_json[:refresh_token]) end end describe '#from_well_known_path' do before(:example) do @home = ENV['HOME'] @scope = 'https://www.googleapis.com/auth/userinfo.profile' @known_path = WELL_KNOWN_PATH @clz = UserRefreshCredentials end after(:example) do ENV['HOME'] = @home unless @home == ENV['HOME'] end it 'is nil if no file exists' do ENV['HOME'] = File.dirname(__FILE__) expect(UserRefreshCredentials.from_well_known_path(@scope)).to be_nil end it 'fails if the file is invalid' do needed = %w(client_id client_secret refresh_token) needed.each do |missing| Dir.mktmpdir do |dir| key_path = File.join(dir, '.config', @known_path) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text(missing)) ENV['HOME'] = dir expect { @clz.from_well_known_path(@scope) } .to raise_error RuntimeError end end end it 'successfully loads the file when it is present' do Dir.mktmpdir do |dir| key_path = File.join(dir, '.config', @known_path) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir expect(@clz.from_well_known_path(@scope)).to_not be_nil end end end describe '#from_system_default_path' do before(:example) do @scope = 'https://www.googleapis.com/auth/userinfo.profile' @path = File.join('/etc/google/auth/', CREDENTIALS_FILE_NAME) @clz = UserRefreshCredentials end it 'is nil if no file exists' do FakeFS do expect(UserRefreshCredentials.from_system_default_path(@scope)) .to be_nil end end it 'fails if the file is invalid' do needed = %w(client_id client_secret refresh_token) needed.each do |missing| FakeFS do FileUtils.mkdir_p(File.dirname(@path)) File.write(@path, cred_json_text(missing)) expect { @clz.from_system_default_path(@scope) } .to raise_error RuntimeError File.delete(@path) end end end it 'successfully loads the file when it is present' do FakeFS do FileUtils.mkdir_p(File.dirname(@path)) File.write(@path, cred_json_text) expect(@clz.from_system_default_path(@scope)).to_not be_nil File.delete(@path) end end end shared_examples 'revoked token' do it 'should nil the refresh token' do expect(@client.refresh_token).to be_nil end it 'should nil the access token' do expect(@client.access_token).to be_nil end it 'should mark the token as expired' do expect(@client.expired?).to be_truthy end end describe 'when revoking a refresh token' do let(:stub) do stub_request(:get, 'https://accounts.google.com/o/oauth2/revoke' \ '?token=refreshtoken') .to_return(status: 200, headers: { 'Content-Type' => 'application/json' }) end before(:example) do stub @client.revoke! end it_behaves_like 'revoked token' end describe 'when revoking an access token' do let(:stub) do stub_request(:get, 'https://accounts.google.com/o/oauth2/revoke' \ '?token=accesstoken') .to_return(status: 200, headers: { 'Content-Type' => 'application/json' }) end before(:example) do stub @client.refresh_token = nil @client.access_token = 'accesstoken' @client.revoke! end it_behaves_like 'revoked token' end describe 'when revoking an invalid token' do let(:stub) do stub_request(:get, 'https://accounts.google.com/o/oauth2/revoke' \ '?token=refreshtoken') .to_return(status: 400, headers: { 'Content-Type' => 'application/json' }) end it 'raises an authorization error' do stub expect { @client.revoke! }.to raise_error( Signet::AuthorizationError) end end end googleauth-0.5.1/spec/googleauth/get_application_default_spec.rb0000644000175000017500000002137213043403213024172 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'faraday' require 'fakefs/safe' require 'googleauth' require 'spec_helper' describe '#get_application_default' do # Pass unique options each time to bypass memoization let(:options) { |example| { dememoize: example } } before(:example) do @key = OpenSSL::PKey::RSA.new(2048) @var_name = ENV_VAR @credential_vars = [ ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR, ACCOUNT_TYPE_VAR] @original_env_vals = {} @credential_vars.each { |var| @original_env_vals[var] = ENV[var] } @home = ENV['HOME'] @scope = 'https://www.googleapis.com/auth/userinfo.profile' end after(:example) do @credential_vars.each { |var| ENV[var] = @original_env_vals[var] } ENV['HOME'] = @home unless @home == ENV['HOME'] end shared_examples 'it cannot load misconfigured credentials' do it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do Dir.mktmpdir do |dir| key_path = File.join(dir, 'does-not-exist') ENV[@var_name] = key_path expect { Google::Auth.get_application_default(@scope, options) } .to raise_error RuntimeError end end it 'fails without default file or env if not on compute engine' do stub = stub_request(:get, 'http://169.254.169.254') .to_return(status: 404, headers: { 'Metadata-Flavor' => 'NotGoogle' }) Dir.mktmpdir do |dir| ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV['HOME'] = dir # no config present in this tmp dir blk = proc do Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end expect(stub).to have_been_requested end end shared_examples 'it can successfully load credentials' do it 'succeeds if the GOOGLE_APPLICATION_CREDENTIALS file is valid' do Dir.mktmpdir do |dir| key_path = File.join(dir, 'my_cert_file') FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV[@var_name] = key_path expect(Google::Auth.get_application_default(@scope, options)) .to_not be_nil end end it 'succeeds with default file without GOOGLE_APPLICATION_CREDENTIALS' do ENV.delete(@var_name) unless ENV[@var_name].nil? Dir.mktmpdir do |dir| key_path = File.join(dir, '.config', WELL_KNOWN_PATH) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir expect(Google::Auth.get_application_default(@scope, options)) .to_not be_nil end end it 'succeeds with default file without a scope' do ENV.delete(@var_name) unless ENV[@var_name].nil? Dir.mktmpdir do |dir| key_path = File.join(dir, '.config', WELL_KNOWN_PATH) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir expect(Google::Auth.get_application_default(nil, options)).to_not be_nil end end it 'succeeds without default file or env if on compute engine' do stub = stub_request(:get, 'http://169.254.169.254') .to_return(status: 200, headers: { 'Metadata-Flavor' => 'Google' }) Dir.mktmpdir do |dir| ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV['HOME'] = dir # no config present in this tmp dir creds = Google::Auth.get_application_default(@scope, options) expect(creds).to_not be_nil end expect(stub).to have_been_requested end it 'succeeds with system default file' do ENV.delete(@var_name) unless ENV[@var_name].nil? FakeFS do key_path = File.join('/etc/google/auth/', CREDENTIALS_FILE_NAME) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) expect(Google::Auth.get_application_default(@scope, options)) .to_not be_nil File.delete(key_path) end end it 'succeeds if environment vars are valid' do ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_ID_VAR] = cred_json[:client_id] ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret] ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] expect(Google::Auth.get_application_default(@scope, options)) .to_not be_nil end end describe 'when credential type is service account' do let(:cred_json) do { private_key_id: 'a_private_key_id', private_key: @key.to_pem, client_email: 'app@developer.gserviceaccount.com', client_id: 'app.apps.googleusercontent.com', type: 'service_account' } end def cred_json_text MultiJson.dump(cred_json) end it_behaves_like 'it can successfully load credentials' it_behaves_like 'it cannot load misconfigured credentials' end describe 'when credential type is authorized_user' do let(:cred_json) do { client_secret: 'privatekey', refresh_token: 'refreshtoken', client_id: 'app.apps.googleusercontent.com', type: 'authorized_user' } end def cred_json_text MultiJson.dump(cred_json) end it_behaves_like 'it can successfully load credentials' it_behaves_like 'it cannot load misconfigured credentials' end describe 'when credential type is unknown' do let(:cred_json) do { client_secret: 'privatekey', refresh_token: 'refreshtoken', client_id: 'app.apps.googleusercontent.com', private_key: @key.to_pem, client_email: 'app@developer.gserviceaccount.com', type: 'not_known_type' } end def cred_json_text MultiJson.dump(cred_json) end it 'fails if the GOOGLE_APPLICATION_CREDENTIALS file contains the creds' do Dir.mktmpdir do |dir| key_path = File.join(dir, 'my_cert_file') FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV[@var_name] = key_path blk = proc do Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end end it 'fails if the well known file contains the creds' do ENV.delete(@var_name) unless ENV[@var_name].nil? Dir.mktmpdir do |dir| key_path = File.join(dir, '.config', WELL_KNOWN_PATH) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir blk = proc do Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end end it 'fails if env vars are set' do ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] blk = proc do Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end end end googleauth-0.5.1/spec/googleauth/compute_engine_spec.rb0000644000175000017500000001046013043403213022321 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'apply_auth_examples' require 'faraday' require 'googleauth/compute_engine' require 'spec_helper' describe Google::Auth::GCECredentials do MD_URI = 'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token' GCECredentials = Google::Auth::GCECredentials before(:example) do @client = GCECredentials.new end def make_auth_stubs(opts = {}) access_token = opts[:access_token] || '' body = MultiJson.dump('access_token' => access_token, 'token_type' => 'Bearer', 'expires_in' => 3600) stub_request(:get, MD_URI) .with(headers: { 'Metadata-Flavor' => 'Google' }) .to_return(body: body, status: 200, headers: { 'Content-Type' => 'application/json' }) end it_behaves_like 'apply/apply! are OK' context 'metadata is unavailable' do describe '#fetch_access_token' do it 'should fail if the metadata request returns a 404' do stub = stub_request(:get, MD_URI) .to_return(status: 404, headers: { 'Metadata-Flavor' => 'Google' }) blk = proc { @client.fetch_access_token! } expect(&blk).to raise_error Signet::AuthorizationError expect(stub).to have_been_requested end it 'should fail if the metadata request returns an unexpected code' do stub = stub_request(:get, MD_URI) .to_return(status: 503, headers: { 'Metadata-Flavor' => 'Google' }) blk = proc { @client.fetch_access_token! } expect(&blk).to raise_error Signet::AuthorizationError expect(stub).to have_been_requested end end end describe '#on_gce?' do it 'should be true when Metadata-Flavor is Google' do stub = stub_request(:get, 'http://169.254.169.254') .to_return(status: 200, headers: { 'Metadata-Flavor' => 'Google' }) expect(GCECredentials.on_gce?({}, true)).to eq(true) expect(stub).to have_been_requested end it 'should be false when Metadata-Flavor is not Google' do stub = stub_request(:get, 'http://169.254.169.254') .to_return(status: 200, headers: { 'Metadata-Flavor' => 'NotGoogle' }) expect(GCECredentials.on_gce?({}, true)).to eq(false) expect(stub).to have_been_requested end it 'should be false if the response is not 200' do stub = stub_request(:get, 'http://169.254.169.254') .to_return(status: 404, headers: { 'Metadata-Flavor' => 'NotGoogle' }) expect(GCECredentials.on_gce?({}, true)).to eq(false) expect(stub).to have_been_requested end end end googleauth-0.5.1/spec/googleauth/apply_auth_examples.rb0000644000175000017500000001160113043403213022350 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'faraday' require 'spec_helper' shared_examples 'apply/apply! are OK' do let(:auth_key) { :authorization } # tests that use these examples need to define # # @client which should be an auth client # # @make_auth_stubs, which should stub out the expected http behaviour of the # auth client describe '#fetch_access_token' do let(:token) { '1/abcdef1234567890' } let(:stub) do make_auth_stubs access_token: token end it 'should set access_token to the fetched value' do stub @client.fetch_access_token! expect(@client.access_token).to eq(token) expect(stub).to have_been_requested end it 'should notify refresh listeners after updating' do stub expect do |b| @client.on_refresh(&b) @client.fetch_access_token! end.to yield_with_args(have_attributes( access_token: '1/abcdef1234567890')) expect(stub).to have_been_requested end end describe '#apply!' do it 'should update the target hash with fetched access token' do token = '1/abcdef1234567890' stub = make_auth_stubs access_token: token md = { foo: 'bar' } @client.apply!(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(md).to eq(want) expect(stub).to have_been_requested end end describe 'updater_proc' do it 'should provide a proc that updates a hash with the access token' do token = '1/abcdef1234567890' stub = make_auth_stubs access_token: token md = { foo: 'bar' } the_proc = @client.updater_proc got = the_proc.call(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(got).to eq(want) expect(stub).to have_been_requested end end describe '#apply' do it 'should not update the original hash with the access token' do token = '1/abcdef1234567890' stub = make_auth_stubs access_token: token md = { foo: 'bar' } @client.apply(md) want = { foo: 'bar' } expect(md).to eq(want) expect(stub).to have_been_requested end it 'should add the token to the returned hash' do token = '1/abcdef1234567890' stub = make_auth_stubs access_token: token md = { foo: 'bar' } got = @client.apply(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(got).to eq(want) expect(stub).to have_been_requested end it 'should not fetch a new token if the current is not expired' do token = '1/abcdef1234567890' stub = make_auth_stubs access_token: token n = 5 # arbitrary n.times do |_t| md = { foo: 'bar' } got = @client.apply(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(got).to eq(want) end expect(stub).to have_been_requested end it 'should fetch a new token if the current one is expired' do token_1 = '1/abcdef1234567890' token_2 = '2/abcdef1234567891' [token_1, token_2].each do |t| make_auth_stubs access_token: t md = { foo: 'bar' } got = @client.apply(md) want = { :foo => 'bar', auth_key => "Bearer #{t}" } expect(got).to eq(want) @client.expires_at -= 3601 # default is to expire in 1hr end end end end googleauth-0.5.1/spec/googleauth/stores/0000755000175000017500000000000013043403213017277 5ustar pravipravigoogleauth-0.5.1/spec/googleauth/stores/redis_token_store_spec.rb0000644000175000017500000000372613043403213024370 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'googleauth' require 'googleauth/stores/redis_token_store' require 'spec_helper' require 'fakeredis/rspec' require 'googleauth/stores/store_examples' describe Google::Auth::Stores::RedisTokenStore do let(:redis) do Redis.new end let(:store) do Google::Auth::Stores::RedisTokenStore.new(redis: redis) end it_behaves_like 'token store' end googleauth-0.5.1/spec/googleauth/stores/store_examples.rb0000644000175000017500000000425413043403213022663 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'spec_helper' shared_examples 'token store' do before(:each) do store.store('default', 'test') end it 'should return a stored value' do expect(store.load('default')).to eq 'test' end it 'should return nil for missing tokens' do expect(store.load('notavalidkey')).to be_nil end it 'should return nil for deleted tokens' do store.delete('default') expect(store.load('default')).to be_nil end it 'should save overwrite values on store' do store.store('default', 'test2') expect(store.load('default')).to eq 'test2' end end googleauth-0.5.1/spec/googleauth/stores/file_token_store_spec.rb0000644000175000017500000000421613043403213024174 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'googleauth' require 'googleauth/stores/file_token_store' require 'spec_helper' require 'fakefs/safe' require 'fakefs/spec_helpers' require 'googleauth/stores/store_examples' module FakeFS class File # FakeFS doesn't implement. And since we don't need to actually lock, # just stub out... def flock(*) end end end describe Google::Auth::Stores::FileTokenStore do include FakeFS::SpecHelpers let(:store) do Google::Auth::Stores::FileTokenStore.new(file: '/tokens.yaml') end it_behaves_like 'token store' end googleauth-0.5.1/spec/googleauth/web_user_authorizer_spec.rb0000644000175000017500000001272013043403213023410 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'googleauth' require 'googleauth/web_user_authorizer' require 'uri' require 'multi_json' require 'spec_helper' require 'rack' describe Google::Auth::WebUserAuthorizer do include TestHelpers let(:client_id) { Google::Auth::ClientId.new('testclient', 'notasecret') } let(:scope) { %w(email profile) } let(:token_store) { DummyTokenStore.new } let(:authorizer) do Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store) end describe '#get_authorization_url' do let(:env) do Rack::MockRequest.env_for( 'http://example.com:8080/test', 'REMOTE_ADDR' => '10.10.10.10') end let(:request) { Rack::Request.new(env) } it 'should include current url in state' do url = authorizer.get_authorization_url(request: request) expect(url).to match( %r{%22current_uri%22:%22http://example.com:8080/test%22}) end it 'should include request forgery token in state' do expect(SecureRandom).to receive(:base64).and_return('aGVsbG8=') url = authorizer.get_authorization_url(request: request) expect(url).to match(/%22session_id%22:%22aGVsbG8=%22/) end it 'should include request forgery token in session' do expect(SecureRandom).to receive(:base64).and_return('aGVsbG8=') authorizer.get_authorization_url(request: request) expect(request.session['g-xsrf-token']).to eq 'aGVsbG8=' end it 'should resolve callback against base URL' do url = authorizer.get_authorization_url(request: request) expect(url).to match( %r{redirect_uri=http://example.com:8080/oauth2callback}) end it 'should allow overriding the current URL' do url = authorizer.get_authorization_url( request: request, redirect_to: '/foo') expect(url).to match %r{%22current_uri%22:%22/foo%22} end it 'should pass through login hint' do url = authorizer.get_authorization_url( request: request, login_hint: 'user@example.com') expect(url).to match(/login_hint=user@example.com/) end end shared_examples 'handles callback' do let(:token_json) do MultiJson.dump('access_token' => '1/abc123', 'token_type' => 'Bearer', 'expires_in' => 3600) end before(:example) do stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') .to_return(body: token_json, status: 200, headers: { 'Content-Type' => 'application/json' }) end let(:env) do Rack::MockRequest.env_for( 'http://example.com:8080/oauth2callback?code=authcode&'\ 'state=%7B%22current_uri%22%3A%22%2Ffoo%22%2C%22'\ 'session_id%22%3A%22abc%22%7D', 'REMOTE_ADDR' => '10.10.10.10') end let(:request) { Rack::Request.new(env) } before(:example) do request.session['g-xsrf-token'] = 'abc' end it 'should return credentials when valid code present' do expect(credentials).to be_instance_of( Google::Auth::UserRefreshCredentials) end it 'should return next URL to redirect to' do expect(next_url).to eq '/foo' end it 'should fail if xrsf token in session and does not match request' do request.session['g-xsrf-token'] = '123' expect { credentials }.to raise_error(Signet::AuthorizationError) end end describe '#handle_auth_callback' do let(:result) { authorizer.handle_auth_callback('user1', request) } let(:credentials) { result[0] } let(:next_url) { result[1] } it_behaves_like 'handles callback' end describe '#handle_auth_callback_deferred and #get_credentials' do let(:next_url) do Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) end let(:credentials) do next_url authorizer.get_credentials('user1', request) end it_behaves_like 'handles callback' end end googleauth-0.5.1/spec/googleauth/iam_spec.rb0000644000175000017500000000617713043403213020100 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'googleauth/iam' describe Google::Auth::IAMCredentials do IAMCredentials = Google::Auth::IAMCredentials let(:test_selector) { 'the-test-selector' } let(:test_token) { 'the-test-token' } let(:test_creds) { IAMCredentials.new(test_selector, test_token) } describe '#apply!' do it 'should update the target hash with the iam values' do md = { foo: 'bar' } test_creds.apply!(md) expect(md[IAMCredentials::SELECTOR_KEY]).to eq test_selector expect(md[IAMCredentials::TOKEN_KEY]).to eq test_token expect(md[:foo]).to eq 'bar' end end describe 'updater_proc' do it 'should provide a proc that updates a hash with the iam values' do md = { foo: 'bar' } the_proc = test_creds.updater_proc got = the_proc.call(md) expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token expect(got[:foo]).to eq 'bar' end end describe '#apply' do it 'should not update the original hash with the iam values' do md = { foo: 'bar' } test_creds.apply(md) expect(md[IAMCredentials::SELECTOR_KEY]).to be_nil expect(md[IAMCredentials::TOKEN_KEY]).to be_nil expect(md[:foo]).to eq 'bar' end it 'should return a with the iam values' do md = { foo: 'bar' } got = test_creds.apply(md) expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token expect(got[:foo]).to eq 'bar' end end end googleauth-0.5.1/spec/googleauth/user_authorizer_spec.rb0000644000175000017500000002366713043403213022567 0ustar pravipravi# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'googleauth' require 'googleauth/user_authorizer' require 'uri' require 'multi_json' require 'spec_helper' describe Google::Auth::UserAuthorizer do include TestHelpers let(:client_id) { Google::Auth::ClientId.new('testclient', 'notasecret') } let(:scope) { %w(email profile) } let(:token_store) { DummyTokenStore.new } let(:callback_uri) { 'https://www.example.com/oauth/callback' } let(:authorizer) do Google::Auth::UserAuthorizer.new(client_id, scope, token_store, callback_uri) end shared_examples 'valid authorization url' do it 'should have a valid base URI' do expect(uri).to match %r{https://accounts.google.com/o/oauth2/auth} end it 'should request offline access' do expect(URI(uri).query).to match(/access_type=offline/) end it 'should request response type code' do expect(URI(uri).query).to match(/response_type=code/) end it 'should force approval' do expect(URI(uri).query).to match(/approval_prompt=force/) end it 'should include granted scopes' do expect(URI(uri).query).to match(/include_granted_scopes=true/) end it 'should include the correct client id' do expect(URI(uri).query).to match(/client_id=testclient/) end it 'should not include a client secret' do expect(URI(uri).query).to_not match(/client_secret/) end it 'should include the callback uri' do expect(URI(uri).query).to match( %r{redirect_uri=https://www.example.com/oauth/callback}) end it 'should include the scope' do expect(URI(uri).query).to match(/scope=email%20profile/) end end context 'when generating authorization URLs with user ID & state' do let(:uri) do authorizer.get_authorization_url(login_hint: 'user1', state: 'mystate') end it_behaves_like 'valid authorization url' it 'includes a login hint' do expect(URI(uri).query).to match(/login_hint=user1/) end it 'includes the app state' do expect(URI(uri).query).to match(/state=mystate/) end end context 'when generating authorization URLs with user ID and no state' do let(:uri) { authorizer.get_authorization_url(login_hint: 'user1') } it_behaves_like 'valid authorization url' it 'includes a login hint' do expect(URI(uri).query).to match(/login_hint=user1/) end it 'does not include the state parameter' do expect(URI(uri).query).to_not match(/state/) end end context 'when generating authorization URLs with no user ID and no state' do let(:uri) { authorizer.get_authorization_url } it_behaves_like 'valid authorization url' it 'does not include the login hint parameter' do expect(URI(uri).query).to_not match(/login_hint/) end it 'does not include the state parameter' do expect(URI(uri).query).to_not match(/state/) end end context 'when retrieving tokens' do let(:token_json) do MultiJson.dump( access_token: 'accesstoken', refresh_token: 'refreshtoken', expiration_time_millis: 1_441_234_742_000) end context 'with a valid user id' do let(:credentials) do token_store.store('user1', token_json) authorizer.get_credentials('user1') end it 'should return an instance of UserRefreshCredentials' do expect(credentials).to be_instance_of( Google::Auth::UserRefreshCredentials) end it 'should return credentials with a valid refresh token' do expect(credentials.refresh_token).to eq 'refreshtoken' end it 'should return credentials with a valid access token' do expect(credentials.access_token).to eq 'accesstoken' end it 'should return credentials with a valid client ID' do expect(credentials.client_id).to eq 'testclient' end it 'should return credentials with a valid client secret' do expect(credentials.client_secret).to eq 'notasecret' end it 'should return credentials with a valid scope' do expect(credentials.scope).to eq %w(email profile) end it 'should return credentials with a valid expiration time' do expect(credentials.expires_at).to eq Time.at(1_441_234_742) end end context 'with an invalid user id' do it 'should return nil' do expect(authorizer.get_credentials('notauser')).to be_nil end end end context 'when saving tokens' do let(:expiry) { Time.now.to_i } let(:credentials) do Google::Auth::UserRefreshCredentials.new( client_id: client_id.id, client_secret: client_id.secret, scope: scope, refresh_token: 'refreshtoken', access_token: 'accesstoken', expires_at: expiry ) end let(:token_json) do authorizer.store_credentials('user1', credentials) token_store.load('user1') end it 'should persist in the token store' do expect(token_json).to_not be_nil end it 'should persist the refresh token' do expect(MultiJson.load(token_json)['refresh_token']).to eq 'refreshtoken' end it 'should persist the access token' do expect(MultiJson.load(token_json)['access_token']).to eq 'accesstoken' end it 'should persist the client id' do expect(MultiJson.load(token_json)['client_id']).to eq 'testclient' end it 'should persist the scope' do expect(MultiJson.load(token_json)['scope']).to include('email', 'profile') end it 'should persist the expiry as milliseconds' do expected_expiry = expiry * 1000 expect(MultiJson.load(token_json)['expiration_time_millis']).to eql( expected_expiry) end end context 'with valid authorization code' do let(:token_json) do MultiJson.dump('access_token' => '1/abc123', 'token_type' => 'Bearer', 'expires_in' => 3600) end before(:example) do stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') .to_return(body: token_json, status: 200, headers: { 'Content-Type' => 'application/json' }) end it 'should exchange a code for credentials' do credentials = authorizer.get_credentials_from_code( user_id: 'user1', code: 'code') expect(credentials.access_token).to eq '1/abc123' end it 'should not store credentials when get only requested' do authorizer.get_credentials_from_code(user_id: 'user1', code: 'code') expect(token_store.load('user1')).to be_nil end it 'should store credentials when requested' do authorizer.get_and_store_credentials_from_code( user_id: 'user1', code: 'code') expect(token_store.load('user1')).to_not be_nil end end context 'with invalid authorization code' do before(:example) do stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') .to_return(status: 400) end it 'should raise an authorization error' do expect do authorizer.get_credentials_from_code(user_id: 'user1', code: 'badcode') end.to raise_error Signet::AuthorizationError end it 'should not store credentials when exchange fails' do expect do authorizer.get_credentials_from_code(user_id: 'user1', code: 'badcode') end.to raise_error Signet::AuthorizationError expect(token_store.load('user1')).to be_nil end end context 'when reovking authorization' do let(:token_json) do MultiJson.dump( access_token: 'accesstoken', refresh_token: 'refreshtoken', expiration_time_millis: 1_441_234_742_000) end before(:example) do token_store.store('user1', token_json) stub_request( :get, 'https://accounts.google.com/o/oauth2/revoke?token=refreshtoken') .to_return(status: 200) end it 'should revoke the grant' do authorizer.revoke_authorization('user1') expect(a_request( :get, 'https://accounts.google.com/o/oauth2/revoke?token=refreshtoken')) .to have_been_made end it 'should remove the token from storage' do authorizer.revoke_authorization('user1') expect(token_store.load('user1')).to be_nil end end # TODO: - Test that tokens are monitored # TODO - Test scope enforcement (auth if upgrade required) end googleauth-0.5.1/COPYING0000644000175000017500000002611613043403213013731 0ustar pravipravi 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.