googleauth 0.4.2 → 0.5.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 +4 -4
- data/.rubocop_todo.yml +23 -6
- data/.travis.yml +3 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile +20 -0
- data/README.md +80 -1
- data/googleauth.gemspec +1 -9
- data/lib/googleauth.rb +6 -3
- data/lib/googleauth/client_id.rb +102 -0
- data/lib/googleauth/scope_util.rb +61 -0
- data/lib/googleauth/service_account.rb +23 -18
- data/lib/googleauth/signet.rb +20 -1
- data/lib/googleauth/stores/file_token_store.rb +64 -0
- data/lib/googleauth/stores/redis_token_store.rb +95 -0
- data/lib/googleauth/token_store.rb +69 -0
- data/lib/googleauth/user_authorizer.rb +273 -0
- data/lib/googleauth/user_refresh.rb +53 -16
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +289 -0
- data/spec/googleauth/apply_auth_examples.rb +36 -58
- data/spec/googleauth/client_id_spec.rb +140 -0
- data/spec/googleauth/compute_engine_spec.rb +34 -71
- data/spec/googleauth/get_application_default_spec.rb +26 -35
- data/spec/googleauth/scope_util_spec.rb +75 -0
- data/spec/googleauth/service_account_spec.rb +16 -11
- data/spec/googleauth/signet_spec.rb +14 -9
- data/spec/googleauth/stores/file_token_store_spec.rb +58 -0
- data/spec/googleauth/stores/redis_token_store_spec.rb +50 -0
- data/spec/googleauth/stores/store_examples.rb +58 -0
- data/spec/googleauth/user_authorizer_spec.rb +314 -0
- data/spec/googleauth/user_refresh_spec.rb +77 -13
- data/spec/googleauth/web_user_authorizer_spec.rb +159 -0
- data/spec/spec_helper.rb +33 -1
- metadata +37 -113
data/lib/googleauth/signet.rb
CHANGED
@@ -42,7 +42,7 @@ module Signet
|
|
42
42
|
def apply!(a_hash, opts = {})
|
43
43
|
# fetch the access token there is currently not one, or if the client
|
44
44
|
# has expired
|
45
|
-
fetch_access_token!(opts) if access_token.nil? ||
|
45
|
+
fetch_access_token!(opts) if access_token.nil? || expires_within?(60)
|
46
46
|
a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}"
|
47
47
|
end
|
48
48
|
|
@@ -58,6 +58,25 @@ module Signet
|
|
58
58
|
def updater_proc
|
59
59
|
lambda(&method(:apply))
|
60
60
|
end
|
61
|
+
|
62
|
+
def on_refresh(&block)
|
63
|
+
@refresh_listeners ||= []
|
64
|
+
@refresh_listeners << block
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :orig_fetch_access_token!, :fetch_access_token!
|
68
|
+
def fetch_access_token!(options = {})
|
69
|
+
info = orig_fetch_access_token!(options)
|
70
|
+
notify_refresh_listeners
|
71
|
+
info
|
72
|
+
end
|
73
|
+
|
74
|
+
def notify_refresh_listeners
|
75
|
+
listeners = @refresh_listeners || []
|
76
|
+
listeners.each do |block|
|
77
|
+
block.call(self)
|
78
|
+
end
|
79
|
+
end
|
61
80
|
end
|
62
81
|
end
|
63
82
|
end
|
@@ -0,0 +1,64 @@
|
|
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
|
+
require 'yaml/store'
|
31
|
+
require 'googleauth/token_store'
|
32
|
+
|
33
|
+
module Google
|
34
|
+
module Auth
|
35
|
+
module Stores
|
36
|
+
# Implementation of user token storage backed by a local YAML file
|
37
|
+
class FileTokenStore < Google::Auth::TokenStore
|
38
|
+
# Create a new store with the supplied file.
|
39
|
+
#
|
40
|
+
# @param [String, File] file
|
41
|
+
# Path to storage file
|
42
|
+
def initialize(options = {})
|
43
|
+
path = options[:file]
|
44
|
+
@store = YAML::Store.new(path)
|
45
|
+
end
|
46
|
+
|
47
|
+
# (see Google::Auth::Stores::TokenStore#load)
|
48
|
+
def load(id)
|
49
|
+
@store.transaction { @store[id] }
|
50
|
+
end
|
51
|
+
|
52
|
+
# (see Google::Auth::Stores::TokenStore#store)
|
53
|
+
def store(id, token)
|
54
|
+
@store.transaction { @store[id] = token }
|
55
|
+
end
|
56
|
+
|
57
|
+
# (see Google::Auth::Stores::TokenStore#delete)
|
58
|
+
def delete(id)
|
59
|
+
@store.transaction { @store.delete(id) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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
|
+
require 'redis'
|
31
|
+
require 'googleauth/token_store'
|
32
|
+
|
33
|
+
module Google
|
34
|
+
module Auth
|
35
|
+
module Stores
|
36
|
+
# Implementation of user token storage backed by Redis. Tokens
|
37
|
+
# are stored as JSON using the supplied key, prefixed with
|
38
|
+
# `g-user-token:`
|
39
|
+
class RedisTokenStore < Google::Auth::TokenStore
|
40
|
+
DEFAULT_KEY_PREFIX = 'g-user-token:'
|
41
|
+
|
42
|
+
# Create a new store with the supplied redis client.
|
43
|
+
#
|
44
|
+
# @param [::Redis, String] redis
|
45
|
+
# Initialized redis client to connect to.
|
46
|
+
# @param [String] prefix
|
47
|
+
# Prefix for keys in redis. Defaults to 'g-user-token:'
|
48
|
+
# @note If no redis instance is provided, a new one is created and
|
49
|
+
# the options passed through. You may include any other keys accepted
|
50
|
+
# by `Redis.new`
|
51
|
+
def initialize(options = {})
|
52
|
+
redis = options.delete(:redis)
|
53
|
+
prefix = options.delete(:prefix)
|
54
|
+
case redis
|
55
|
+
when Redis
|
56
|
+
@redis = redis
|
57
|
+
else
|
58
|
+
@redis = Redis.new(options)
|
59
|
+
end
|
60
|
+
@prefix = prefix || DEFAULT_KEY_PREFIX
|
61
|
+
end
|
62
|
+
|
63
|
+
# (see Google::Auth::Stores::TokenStore#load)
|
64
|
+
def load(id)
|
65
|
+
key = key_for(id)
|
66
|
+
@redis.get(key)
|
67
|
+
end
|
68
|
+
|
69
|
+
# (see Google::Auth::Stores::TokenStore#store)
|
70
|
+
def store(id, token)
|
71
|
+
key = key_for(id)
|
72
|
+
@redis.set(key, token)
|
73
|
+
end
|
74
|
+
|
75
|
+
# (see Google::Auth::Stores::TokenStore#delete)
|
76
|
+
def delete(id)
|
77
|
+
key = key_for(id)
|
78
|
+
@redis.del(key)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Generate a redis key from a token ID
|
84
|
+
#
|
85
|
+
# @param [String] id
|
86
|
+
# ID of the token
|
87
|
+
# @return [String]
|
88
|
+
# Redis key
|
89
|
+
def key_for(id)
|
90
|
+
@prefix + id
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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
|
32
|
+
# Interface definition for token stores. It is not required that
|
33
|
+
# implementations inherit from this class. It is provided for documentation
|
34
|
+
# purposes to illustrate the API contract.
|
35
|
+
class TokenStore
|
36
|
+
class << self
|
37
|
+
attr_accessor :default
|
38
|
+
end
|
39
|
+
|
40
|
+
# Load the token data from storage for the given ID.
|
41
|
+
#
|
42
|
+
# @param [String] id
|
43
|
+
# ID of token data to load.
|
44
|
+
# @return [String]
|
45
|
+
# The loaded token data.
|
46
|
+
def load(_id)
|
47
|
+
fail 'Not implemented'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Put the token data into storage for the given ID.
|
51
|
+
#
|
52
|
+
# @param [String] id
|
53
|
+
# ID of token data to store.
|
54
|
+
# @param [String] token
|
55
|
+
# The token data to store.
|
56
|
+
def store(_id, _token)
|
57
|
+
fail 'Not implemented'
|
58
|
+
end
|
59
|
+
|
60
|
+
# Remove the token data from storage for the given ID.
|
61
|
+
#
|
62
|
+
# @param [String] id
|
63
|
+
# ID of the token data to delete
|
64
|
+
def delete(_id)
|
65
|
+
fail 'Not implemented'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,273 @@
|
|
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
|
+
require 'uri'
|
31
|
+
require 'multi_json'
|
32
|
+
require 'googleauth/signet'
|
33
|
+
require 'googleauth/user_refresh'
|
34
|
+
|
35
|
+
module Google
|
36
|
+
module Auth
|
37
|
+
# Handles an interactive 3-Legged-OAuth2 (3LO) user consent authorization.
|
38
|
+
#
|
39
|
+
# Example usage for a simple command line app:
|
40
|
+
#
|
41
|
+
# credentials = authorizer.get_credentials(user_id)
|
42
|
+
# if credentials.nil?
|
43
|
+
# url = authorizer.get_authorization_url(
|
44
|
+
# base_url: OOB_URI)
|
45
|
+
# puts "Open the following URL in the browser and enter the " +
|
46
|
+
# "resulting code after authorization"
|
47
|
+
# puts url
|
48
|
+
# code = gets
|
49
|
+
# credentials = authorizer.get_and_store_credentials_from_code(
|
50
|
+
# user_id: user_id, code: code, base_url: OOB_URI)
|
51
|
+
# end
|
52
|
+
# # Credentials ready to use, call APIs
|
53
|
+
# ...
|
54
|
+
class UserAuthorizer
|
55
|
+
MISMATCHED_CLIENT_ID_ERROR =
|
56
|
+
'Token client ID of %s does not match configured client id %s'
|
57
|
+
NIL_CLIENT_ID_ERROR = 'Client id can not be nil.'
|
58
|
+
NIL_SCOPE_ERROR = 'Scope can not be nil.'
|
59
|
+
NIL_USER_ID_ERROR = 'User ID can not be nil.'
|
60
|
+
NIL_TOKEN_STORE_ERROR = 'Can not call method if token store is nil'
|
61
|
+
MISSING_ABSOLUTE_URL_ERROR =
|
62
|
+
'Absolute base url required for relative callback url "%s"'
|
63
|
+
|
64
|
+
# Initialize the authorizer
|
65
|
+
#
|
66
|
+
# @param [Google::Auth::ClientID] client_id
|
67
|
+
# Configured ID & secret for this application
|
68
|
+
# @param [String, Array<String>] scope
|
69
|
+
# Authorization scope to request
|
70
|
+
# @param [Google::Auth::Stores::TokenStore] token_store
|
71
|
+
# Backing storage for persisting user credentials
|
72
|
+
# @param [String] callback_uri
|
73
|
+
# URL (either absolute or relative) of the auth callback.
|
74
|
+
# Defaults to '/oauth2callback'
|
75
|
+
def initialize(client_id, scope, token_store, callback_uri = nil)
|
76
|
+
fail NIL_CLIENT_ID_ERROR if client_id.nil?
|
77
|
+
fail NIL_SCOPE_ERROR if scope.nil?
|
78
|
+
|
79
|
+
@client_id = client_id
|
80
|
+
@scope = Array(scope)
|
81
|
+
@token_store = token_store
|
82
|
+
@callback_uri = callback_uri || '/oauth2callback'
|
83
|
+
end
|
84
|
+
|
85
|
+
# Build the URL for requesting authorization.
|
86
|
+
#
|
87
|
+
# @param [String] login_hint
|
88
|
+
# Login hint if need to authorize a specific account. Should be a
|
89
|
+
# user's email address or unique profile ID.
|
90
|
+
# @param [String] state
|
91
|
+
# Opaque state value to be returned to the oauth callback.
|
92
|
+
# @param [String] base_url
|
93
|
+
# Absolute URL to resolve the configured callback uri against. Required
|
94
|
+
# if the configured callback uri is a relative.
|
95
|
+
# @param [String, Array<String>] scope
|
96
|
+
# Authorization scope to request. Overrides the instance scopes if not
|
97
|
+
# nil.
|
98
|
+
# @return [String]
|
99
|
+
# Authorization url
|
100
|
+
def get_authorization_url(options = {})
|
101
|
+
scope = options[:scope] || @scope
|
102
|
+
credentials = UserRefreshCredentials.new(
|
103
|
+
client_id: @client_id.id,
|
104
|
+
client_secret: @client_id.secret,
|
105
|
+
scope: scope)
|
106
|
+
redirect_uri = redirect_uri_for(options[:base_url])
|
107
|
+
url = credentials.authorization_uri(access_type: 'offline',
|
108
|
+
redirect_uri: redirect_uri,
|
109
|
+
approval_prompt: 'force',
|
110
|
+
state: options[:state],
|
111
|
+
include_granted_scopes: true,
|
112
|
+
login_hint: options[:login_hint])
|
113
|
+
url.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
# Fetch stored credentials for the user.
|
117
|
+
#
|
118
|
+
# @param [String] user_id
|
119
|
+
# Unique ID of the user for loading/storing credentials.
|
120
|
+
# @param [Array<String>, String] scope
|
121
|
+
# If specified, only returns credentials that have all
|
122
|
+
# the requested scopes
|
123
|
+
# @return [Google::Auth::UserRefreshCredentials]
|
124
|
+
# Stored credentials, nil if none present
|
125
|
+
def get_credentials(user_id, scope = nil)
|
126
|
+
fail NIL_USER_ID_ERROR if user_id.nil?
|
127
|
+
fail NIL_TOKEN_STORE_ERROR if @token_store.nil?
|
128
|
+
|
129
|
+
scope ||= @scope
|
130
|
+
saved_token = @token_store.load(user_id)
|
131
|
+
return nil if saved_token.nil?
|
132
|
+
data = MultiJson.load(saved_token)
|
133
|
+
|
134
|
+
if data.fetch('client_id', @client_id.id) != @client_id.id
|
135
|
+
fail sprintf(MISMATCHED_CLIENT_ID_ERROR,
|
136
|
+
data['client_id'], @client_id.id)
|
137
|
+
end
|
138
|
+
|
139
|
+
credentials = UserRefreshCredentials.new(
|
140
|
+
client_id: @client_id.id,
|
141
|
+
client_secret: @client_id.secret,
|
142
|
+
scope: data['scope'] || @scope,
|
143
|
+
access_token: data['access_token'],
|
144
|
+
refresh_token: data['refresh_token'],
|
145
|
+
expires_at: data.fetch('expiration_time_millis', 0) / 1000)
|
146
|
+
if credentials.includes_scope?(scope)
|
147
|
+
monitor_credentials(user_id, credentials)
|
148
|
+
return credentials
|
149
|
+
end
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
|
153
|
+
# Exchanges an authorization code returned in the oauth callback
|
154
|
+
#
|
155
|
+
# @param [String] user_id
|
156
|
+
# Unique ID of the user for loading/storing credentials.
|
157
|
+
# @param [String] code
|
158
|
+
# The authorization code from the OAuth callback
|
159
|
+
# @param [String, Array<String>] scope
|
160
|
+
# Authorization scope requested. Overrides the instance
|
161
|
+
# scopes if not nil.
|
162
|
+
# @param [String] base_url
|
163
|
+
# Absolute URL to resolve the configured callback uri against.
|
164
|
+
# Required if the configured
|
165
|
+
# callback uri is a relative.
|
166
|
+
# @return [Google::Auth::UserRefreshCredentials]
|
167
|
+
# Credentials if exchange is successful
|
168
|
+
def get_credentials_from_code(options = {})
|
169
|
+
user_id = options[:user_id]
|
170
|
+
code = options[:code]
|
171
|
+
scope = options[:scope] || @scope
|
172
|
+
base_url = options[:base_url]
|
173
|
+
credentials = UserRefreshCredentials.new(
|
174
|
+
client_id: @client_id.id,
|
175
|
+
client_secret: @client_id.secret,
|
176
|
+
redirect_uri: redirect_uri_for(base_url),
|
177
|
+
scope: scope)
|
178
|
+
credentials.code = code
|
179
|
+
credentials.fetch_access_token!({})
|
180
|
+
monitor_credentials(user_id, credentials)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Exchanges an authorization code returned in the oauth callback.
|
184
|
+
# Additionally, stores the resulting credentials in the token store if
|
185
|
+
# the exchange is successful.
|
186
|
+
#
|
187
|
+
# @param [String] user_id
|
188
|
+
# Unique ID of the user for loading/storing credentials.
|
189
|
+
# @param [String] code
|
190
|
+
# The authorization code from the OAuth callback
|
191
|
+
# @param [String, Array<String>] scope
|
192
|
+
# Authorization scope requested. Overrides the instance
|
193
|
+
# scopes if not nil.
|
194
|
+
# @param [String] base_url
|
195
|
+
# Absolute URL to resolve the configured callback uri against.
|
196
|
+
# Required if the configured
|
197
|
+
# callback uri is a relative.
|
198
|
+
# @return [Google::Auth::UserRefreshCredentials]
|
199
|
+
# Credentials if exchange is successful
|
200
|
+
def get_and_store_credentials_from_code(options = {})
|
201
|
+
credentials = get_credentials_from_code(options)
|
202
|
+
monitor_credentials(options[:user_id], credentials)
|
203
|
+
store_credentials(options[:user_id], credentials)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Revokes a user's credentials. This both revokes the actual
|
207
|
+
# grant as well as removes the token from the token store.
|
208
|
+
#
|
209
|
+
# @param [String] user_id
|
210
|
+
# Unique ID of the user for loading/storing credentials.
|
211
|
+
def revoke_authorization(user_id)
|
212
|
+
credentials = get_credentials(user_id)
|
213
|
+
if credentials
|
214
|
+
begin
|
215
|
+
@token_store.delete(user_id)
|
216
|
+
ensure
|
217
|
+
credentials.revoke!
|
218
|
+
end
|
219
|
+
end
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
|
223
|
+
# Store credentials for a user. Generally not required to be
|
224
|
+
# called directly, but may be used to migrate tokens from one
|
225
|
+
# store to another.
|
226
|
+
#
|
227
|
+
# @param [String] user_id
|
228
|
+
# Unique ID of the user for loading/storing credentials.
|
229
|
+
# @param [Google::Auth::UserRefreshCredentials] credentials
|
230
|
+
# Credentials to store.
|
231
|
+
def store_credentials(user_id, credentials)
|
232
|
+
json = MultiJson.dump(
|
233
|
+
client_id: credentials.client_id,
|
234
|
+
access_token: credentials.access_token,
|
235
|
+
refresh_token: credentials.refresh_token,
|
236
|
+
scope: credentials.scope,
|
237
|
+
expiration_time_millis: (credentials.expires_at.to_i) * 1000)
|
238
|
+
@token_store.store(user_id, json)
|
239
|
+
credentials
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
# Begin watching a credential for refreshes so the access token can be
|
245
|
+
# saved.
|
246
|
+
#
|
247
|
+
# @param [String] user_id
|
248
|
+
# Unique ID of the user for loading/storing credentials.
|
249
|
+
# @param [Google::Auth::UserRefreshCredentials] credentials
|
250
|
+
# Credentials to store.
|
251
|
+
def monitor_credentials(user_id, credentials)
|
252
|
+
credentials.on_refresh do |cred|
|
253
|
+
store_credentials(user_id, cred)
|
254
|
+
end
|
255
|
+
credentials
|
256
|
+
end
|
257
|
+
|
258
|
+
# Resolve the redirect uri against a base.
|
259
|
+
#
|
260
|
+
# @param [String] base_url
|
261
|
+
# Absolute URL to resolve the callback against if necessary.
|
262
|
+
# @return [String]
|
263
|
+
# Redirect URI
|
264
|
+
def redirect_uri_for(base_url)
|
265
|
+
return @callback_uri unless URI(@callback_uri).scheme.nil?
|
266
|
+
fail sprintf(
|
267
|
+
MISSING_ABSOLUTE_URL_ERROR,
|
268
|
+
@callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
|
269
|
+
URI.join(base_url, @callback_uri).to_s
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|