googleauth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +36 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/CONTRIBUTING.md +32 -0
- data/COPYING +202 -0
- data/Gemfile +4 -0
- data/README.md +90 -0
- data/Rakefile +15 -0
- data/googleauth.gemspec +39 -0
- data/lib/googleauth.rb +66 -0
- data/lib/googleauth/compute_engine.rb +86 -0
- data/lib/googleauth/service_account.rb +118 -0
- data/lib/googleauth/signet.rb +63 -0
- data/lib/googleauth/version.rb +36 -0
- data/spec/googleauth/apply_auth_examples.rb +169 -0
- data/spec/googleauth/compute_engine_spec.rb +108 -0
- data/spec/googleauth/get_application_default_spec.rb +134 -0
- data/spec/googleauth/service_account_spec.rb +143 -0
- data/spec/googleauth/signet_spec.rb +66 -0
- data/spec/spec_helper.rb +51 -0
- metadata +212 -0
data/lib/googleauth.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright 2015, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require 'googleauth/service_account'
|
31
|
+
require 'googleauth/compute_engine'
|
32
|
+
|
33
|
+
module Google
|
34
|
+
# Module Auth provides classes that provide Google-specific authorization
|
35
|
+
# used to access Google APIs.
|
36
|
+
module Auth
|
37
|
+
NOT_FOUND_ERROR = <<END
|
38
|
+
Could not load the default credentials. Browse to
|
39
|
+
https://developers.google.com/accounts/docs/application-default-credentials
|
40
|
+
for more information
|
41
|
+
END
|
42
|
+
|
43
|
+
# Obtains the default credentials implementation to use in this
|
44
|
+
# environment.
|
45
|
+
#
|
46
|
+
# Use this to obtain the Application Default Credentials for accessing
|
47
|
+
# Google APIs. Application Default Credentials are described in detail
|
48
|
+
# at http://goo.gl/IUuyuX.
|
49
|
+
#
|
50
|
+
# If supplied, scope is used to create the credentials instance, when it
|
51
|
+
# can applied. E.g, on compute engine, the scope is ignored.
|
52
|
+
#
|
53
|
+
# @param scope [string|array] the scope(s) to access
|
54
|
+
# @param options [hash] allows override of the connection being used
|
55
|
+
def get_application_default(scope, options = {})
|
56
|
+
creds = ServiceAccountCredentials.from_env(scope)
|
57
|
+
return creds unless creds.nil?
|
58
|
+
creds = ServiceAccountCredentials.from_well_known_path(scope)
|
59
|
+
return creds unless creds.nil?
|
60
|
+
fail NOT_FOUND_ERROR unless GCECredentials.on_gce?(options)
|
61
|
+
GCECredentials.new
|
62
|
+
end
|
63
|
+
|
64
|
+
module_function :get_application_default
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Copyright 2015, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require 'faraday'
|
31
|
+
require 'googleauth/signet'
|
32
|
+
require 'memoist'
|
33
|
+
|
34
|
+
module Google
|
35
|
+
# Module Auth provides classes that provide Google-specific authorization
|
36
|
+
# used to access Google APIs.
|
37
|
+
module Auth
|
38
|
+
# Extends Signet::OAuth2::Client so that the auth token is obtained from
|
39
|
+
# the GCE metadata server.
|
40
|
+
class GCECredentials < Signet::OAuth2::Client
|
41
|
+
# The IP Address is used in the URIs to speed up failures on non-GCE
|
42
|
+
# systems.
|
43
|
+
COMPUTE_AUTH_TOKEN_URI = 'http://169.254.169.254/computeMetadata/v1/'\
|
44
|
+
'instance/service-accounts/default/token'
|
45
|
+
COMPUTE_CHECK_URI = 'http://169.254.169.254'
|
46
|
+
|
47
|
+
class << self
|
48
|
+
extend Memoist
|
49
|
+
|
50
|
+
# Detect if this appear to be a GCE instance, by checking if metadata
|
51
|
+
# is available
|
52
|
+
def on_gce?(options = {})
|
53
|
+
c = options[:connection] || Faraday.default_connection
|
54
|
+
resp = c.get(COMPUTE_CHECK_URI) do |req|
|
55
|
+
# Comment from: oauth2client/client.py
|
56
|
+
#
|
57
|
+
# Note: the explicit `timeout` below is a workaround. The underlying
|
58
|
+
# issue is that resolving an unknown host on some networks will take
|
59
|
+
# 20-30 seconds; making this timeout short fixes the issue, but
|
60
|
+
# could lead to false negatives in the event that we are on GCE, but
|
61
|
+
# the metadata resolution was particularly slow. The latter case is
|
62
|
+
# "unlikely".
|
63
|
+
req.options.timeout = 0.1
|
64
|
+
end
|
65
|
+
return false unless resp.status == 200
|
66
|
+
return false unless resp.headers.key?('Metadata-Flavor')
|
67
|
+
return resp.headers['Metadata-Flavor'] == 'Google'
|
68
|
+
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
memoize :on_gce?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Overrides the super class method to change how access tokens are
|
76
|
+
# fetched.
|
77
|
+
def fetch_access_token(options = {})
|
78
|
+
c = options[:connection] || Faraday.default_connection
|
79
|
+
c.headers = { 'Metadata-Flavor' => 'Google' }
|
80
|
+
resp = c.get(COMPUTE_AUTH_TOKEN_URI)
|
81
|
+
Signet::OAuth2.parse_credentials(resp.body,
|
82
|
+
resp.headers['content-type'])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Copyright 2015, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require 'googleauth/signet'
|
31
|
+
require 'memoist'
|
32
|
+
require 'multi_json'
|
33
|
+
require 'openssl'
|
34
|
+
require 'rbconfig'
|
35
|
+
|
36
|
+
# Reads the private key and client email fields from service account JSON key.
|
37
|
+
def read_json_key(json_key_io)
|
38
|
+
json_key = MultiJson.load(json_key_io.read)
|
39
|
+
fail 'missing client_email' unless json_key.key?('client_email')
|
40
|
+
fail 'missing private_key' unless json_key.key?('private_key')
|
41
|
+
[json_key['private_key'], json_key['client_email']]
|
42
|
+
end
|
43
|
+
|
44
|
+
module Google
|
45
|
+
# Module Auth provides classes that provide Google-specific authorization
|
46
|
+
# used to access Google APIs.
|
47
|
+
module Auth
|
48
|
+
# Authenticates requests using Google's Service Account credentials.
|
49
|
+
#
|
50
|
+
# This class allows authorizing requests for service accounts directly
|
51
|
+
# from credentials from a json key file downloaded from the developer
|
52
|
+
# console (via 'Generate new Json Key').
|
53
|
+
#
|
54
|
+
# cf [Application Default Credentials](http://goo.gl/mkAHpZ)
|
55
|
+
class ServiceAccountCredentials < Signet::OAuth2::Client
|
56
|
+
ENV_VAR = 'GOOGLE_APPLICATION_CREDENTIALS'
|
57
|
+
NOT_FOUND_ERROR =
|
58
|
+
"Unable to read the credential file specified by #{ENV_VAR}"
|
59
|
+
TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token'
|
60
|
+
WELL_KNOWN_PATH = 'gcloud/application_default_credentials.json'
|
61
|
+
WELL_KNOWN_ERROR = 'Unable to read the default credential file'
|
62
|
+
|
63
|
+
class << self
|
64
|
+
extend Memoist
|
65
|
+
|
66
|
+
# determines if the current OS is windows
|
67
|
+
def windows?
|
68
|
+
RbConfig::CONFIG['host_os'] =~ /Windows|mswin/
|
69
|
+
end
|
70
|
+
memoize :windows?
|
71
|
+
|
72
|
+
# Creates an instance from the path specified in an environment
|
73
|
+
# variable.
|
74
|
+
#
|
75
|
+
# @param scope [string|array] the scope(s) to access
|
76
|
+
def from_env(scope)
|
77
|
+
return nil unless ENV.key?(ENV_VAR)
|
78
|
+
path = ENV[ENV_VAR]
|
79
|
+
fail 'file #{path} does not exist' unless File.exist?(path)
|
80
|
+
File.open(path) do |f|
|
81
|
+
return new(scope, f)
|
82
|
+
end
|
83
|
+
rescue StandardError => e
|
84
|
+
raise "#{NOT_FOUND_ERROR}: #{e}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates an instance from a well known path.
|
88
|
+
#
|
89
|
+
# @param scope [string|array] the scope(s) to access
|
90
|
+
def from_well_known_path(scope)
|
91
|
+
home_var, base = windows? ? 'APPDATA' : 'HOME', WELL_KNOWN_PATH
|
92
|
+
root = ENV[home_var].nil? ? '' : ENV[home_var]
|
93
|
+
base = File.join('.config', base) unless windows?
|
94
|
+
path = File.join(root, base)
|
95
|
+
return nil unless File.exist?(path)
|
96
|
+
File.open(path) do |f|
|
97
|
+
return new(scope, f)
|
98
|
+
end
|
99
|
+
rescue StandardError => e
|
100
|
+
raise "#{WELL_KNOWN_ERROR}: #{e}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Initializes a ServiceAccountCredentials.
|
105
|
+
#
|
106
|
+
# @param scope [string|array] the scope(s) to access
|
107
|
+
# @param json_key_io [IO] an IO from which the JSON key can be read
|
108
|
+
def initialize(scope, json_key_io)
|
109
|
+
private_key, client_email = read_json_key(json_key_io)
|
110
|
+
super(token_credential_uri: TOKEN_CRED_URI,
|
111
|
+
audience: TOKEN_CRED_URI, # TODO: confirm this
|
112
|
+
scope: scope,
|
113
|
+
issuer: client_email,
|
114
|
+
signing_key: OpenSSL::PKey::RSA.new(private_key))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2015, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require 'signet/oauth_2/client'
|
31
|
+
|
32
|
+
module Signet
|
33
|
+
# OAuth2 supports OAuth2 authentication.
|
34
|
+
module OAuth2
|
35
|
+
AUTH_METADATA_KEY = :Authorization
|
36
|
+
# Signet::OAuth2::Client creates an OAuth2 client
|
37
|
+
#
|
38
|
+
# This reopens Client to add #apply and #apply! methods which update a
|
39
|
+
# hash with the fetched authentication token.
|
40
|
+
class Client
|
41
|
+
# Updates a_hash updated with the authentication token
|
42
|
+
def apply!(a_hash, opts = {})
|
43
|
+
# fetch the access token there is currently not one, or if the client
|
44
|
+
# has expired
|
45
|
+
fetch_access_token!(opts) if access_token.nil? || expired?
|
46
|
+
a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a clone of a_hash updated with the authentication token
|
50
|
+
def apply(a_hash, opts = {})
|
51
|
+
a_copy = a_hash.clone
|
52
|
+
apply!(a_copy, opts)
|
53
|
+
a_copy
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a reference to the #apply method, suitable for passing as
|
57
|
+
# a closure
|
58
|
+
def updater_proc
|
59
|
+
lambda(&method(:apply))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright 2014, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
module Google
|
31
|
+
# Module Auth provides classes that provide Google-specific authorization
|
32
|
+
# used to access Google APIs.
|
33
|
+
module Auth
|
34
|
+
VERSION = '0.1.0'
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# Copyright 2015, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
|
31
|
+
$LOAD_PATH.unshift(spec_dir)
|
32
|
+
$LOAD_PATH.uniq!
|
33
|
+
|
34
|
+
require 'faraday'
|
35
|
+
require 'spec_helper'
|
36
|
+
|
37
|
+
def build_json_response(payload)
|
38
|
+
[200,
|
39
|
+
{ 'Content-Type' => 'application/json; charset=utf-8' },
|
40
|
+
MultiJson.dump(payload)]
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_access_token_json(token)
|
44
|
+
build_json_response('access_token' => token,
|
45
|
+
'token_type' => 'Bearer',
|
46
|
+
'expires_in' => 3600)
|
47
|
+
end
|
48
|
+
|
49
|
+
WANTED_AUTH_KEY = :Authorization
|
50
|
+
|
51
|
+
shared_examples 'apply/apply! are OK' do
|
52
|
+
# tests that use these examples need to define
|
53
|
+
#
|
54
|
+
# @client which should be an auth client
|
55
|
+
#
|
56
|
+
# @make_auth_stubs, which should stub out the expected http behaviour of the
|
57
|
+
# auth client
|
58
|
+
describe '#fetch_access_token' do
|
59
|
+
it 'should set access_token to the fetched value' do
|
60
|
+
token = '1/abcdef1234567890'
|
61
|
+
stubs = make_auth_stubs access_token: token
|
62
|
+
c = Faraday.new do |b|
|
63
|
+
b.adapter(:test, stubs)
|
64
|
+
end
|
65
|
+
|
66
|
+
@client.fetch_access_token!(connection: c)
|
67
|
+
expect(@client.access_token).to eq(token)
|
68
|
+
stubs.verify_stubbed_calls
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#apply!' do
|
73
|
+
it 'should update the target hash with fetched access token' do
|
74
|
+
token = '1/abcdef1234567890'
|
75
|
+
stubs = make_auth_stubs access_token: token
|
76
|
+
c = Faraday.new do |b|
|
77
|
+
b.adapter(:test, stubs)
|
78
|
+
end
|
79
|
+
|
80
|
+
md = { foo: 'bar' }
|
81
|
+
@client.apply!(md, connection: c)
|
82
|
+
want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
|
83
|
+
expect(md).to eq(want)
|
84
|
+
stubs.verify_stubbed_calls
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'updater_proc' do
|
89
|
+
it 'should provide a proc that updates a hash with the access token' do
|
90
|
+
token = '1/abcdef1234567890'
|
91
|
+
stubs = make_auth_stubs access_token: token
|
92
|
+
c = Faraday.new do |b|
|
93
|
+
b.adapter(:test, stubs)
|
94
|
+
end
|
95
|
+
|
96
|
+
md = { foo: 'bar' }
|
97
|
+
the_proc = @client.updater_proc
|
98
|
+
got = the_proc.call(md, connection: c)
|
99
|
+
want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
|
100
|
+
expect(got).to eq(want)
|
101
|
+
stubs.verify_stubbed_calls
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#apply' do
|
106
|
+
it 'should not update the original hash with the access token' do
|
107
|
+
token = '1/abcdef1234567890'
|
108
|
+
stubs = make_auth_stubs access_token: token
|
109
|
+
c = Faraday.new do |b|
|
110
|
+
b.adapter(:test, stubs)
|
111
|
+
end
|
112
|
+
|
113
|
+
md = { foo: 'bar' }
|
114
|
+
@client.apply(md, connection: c)
|
115
|
+
want = { foo: 'bar' }
|
116
|
+
expect(md).to eq(want)
|
117
|
+
stubs.verify_stubbed_calls
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should add the token to the returned hash' do
|
121
|
+
token = '1/abcdef1234567890'
|
122
|
+
stubs = make_auth_stubs access_token: token
|
123
|
+
c = Faraday.new do |b|
|
124
|
+
b.adapter(:test, stubs)
|
125
|
+
end
|
126
|
+
|
127
|
+
md = { foo: 'bar' }
|
128
|
+
got = @client.apply(md, connection: c)
|
129
|
+
want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
|
130
|
+
expect(got).to eq(want)
|
131
|
+
stubs.verify_stubbed_calls
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should not fetch a new token if the current is not expired' do
|
135
|
+
token = '1/abcdef1234567890'
|
136
|
+
stubs = make_auth_stubs access_token: token
|
137
|
+
c = Faraday.new do |b|
|
138
|
+
b.adapter(:test, stubs)
|
139
|
+
end
|
140
|
+
|
141
|
+
n = 5 # arbitrary
|
142
|
+
n.times do |_t|
|
143
|
+
md = { foo: 'bar' }
|
144
|
+
got = @client.apply(md, connection: c)
|
145
|
+
want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
|
146
|
+
expect(got).to eq(want)
|
147
|
+
end
|
148
|
+
stubs.verify_stubbed_calls
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should fetch a new token if the current one is expired' do
|
152
|
+
token_1 = '1/abcdef1234567890'
|
153
|
+
token_2 = '2/abcdef1234567890'
|
154
|
+
|
155
|
+
[token_1, token_2].each do |t|
|
156
|
+
stubs = make_auth_stubs access_token: t
|
157
|
+
c = Faraday.new do |b|
|
158
|
+
b.adapter(:test, stubs)
|
159
|
+
end
|
160
|
+
md = { foo: 'bar' }
|
161
|
+
got = @client.apply(md, connection: c)
|
162
|
+
want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{t}" }
|
163
|
+
expect(got).to eq(want)
|
164
|
+
stubs.verify_stubbed_calls
|
165
|
+
@client.expires_at -= 3601 # default is to expire in 1hr
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|