oauth2 1.4.7 → 2.0.2
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/CHANGELOG.md +114 -22
- data/CONTRIBUTING.md +18 -0
- data/LICENSE +1 -1
- data/README.md +383 -138
- data/SECURITY.md +20 -0
- data/lib/oauth2/access_token.rb +32 -21
- data/lib/oauth2/authenticator.rb +12 -5
- data/lib/oauth2/client.rb +142 -81
- data/lib/oauth2/error.rb +29 -18
- data/lib/oauth2/response.rb +63 -19
- data/lib/oauth2/snaky_hash.rb +8 -0
- data/lib/oauth2/strategy/assertion.rb +66 -39
- data/lib/oauth2/strategy/auth_code.rb +15 -2
- data/lib/oauth2/strategy/base.rb +2 -0
- data/lib/oauth2/strategy/client_credentials.rb +3 -1
- data/lib/oauth2/strategy/implicit.rb +10 -1
- data/lib/oauth2/strategy/password.rb +3 -1
- data/lib/oauth2/version.rb +1 -59
- data/lib/oauth2.rb +21 -1
- metadata +75 -97
- data/lib/oauth2/mac_token.rb +0 -130
- data/spec/helper.rb +0 -37
- data/spec/oauth2/access_token_spec.rb +0 -216
- data/spec/oauth2/authenticator_spec.rb +0 -84
- data/spec/oauth2/client_spec.rb +0 -506
- data/spec/oauth2/mac_token_spec.rb +0 -117
- data/spec/oauth2/response_spec.rb +0 -90
- data/spec/oauth2/strategy/assertion_spec.rb +0 -58
- data/spec/oauth2/strategy/auth_code_spec.rb +0 -107
- data/spec/oauth2/strategy/base_spec.rb +0 -5
- data/spec/oauth2/strategy/client_credentials_spec.rb +0 -69
- data/spec/oauth2/strategy/implicit_spec.rb +0 -26
- data/spec/oauth2/strategy/password_spec.rb +0 -55
- data/spec/oauth2/version_spec.rb +0 -23
data/lib/oauth2/access_token.rb
CHANGED
@@ -1,31 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
|
-
class AccessToken
|
3
|
-
attr_reader :client, :token, :expires_in, :expires_at, :params
|
4
|
-
attr_accessor :options, :refresh_token
|
4
|
+
class AccessToken # rubocop:disable Metrics/ClassLength
|
5
|
+
attr_reader :client, :token, :expires_in, :expires_at, :expires_latency, :params
|
6
|
+
attr_accessor :options, :refresh_token, :response
|
5
7
|
|
6
|
-
# Should these methods be deprecated?
|
7
8
|
class << self
|
8
9
|
# Initializes an AccessToken from a Hash
|
9
10
|
#
|
10
|
-
# @param [Client] the OAuth2::Client instance
|
11
|
-
# @param [Hash] a hash of AccessToken property values
|
12
|
-
# @return [AccessToken] the
|
11
|
+
# @param client [Client] the OAuth2::Client instance
|
12
|
+
# @param hash [Hash] a hash of AccessToken property values
|
13
|
+
# @return [AccessToken] the initialized AccessToken
|
13
14
|
def from_hash(client, hash)
|
14
15
|
hash = hash.dup
|
15
|
-
new(client, hash.delete('access_token') || hash.delete(:access_token), hash)
|
16
|
+
new(client, hash.delete('access_token') || hash.delete(:access_token) || hash.delete('token') || hash.delete(:token), hash)
|
16
17
|
end
|
17
18
|
|
18
19
|
# Initializes an AccessToken from a key/value application/x-www-form-urlencoded string
|
19
20
|
#
|
20
21
|
# @param [Client] client the OAuth2::Client instance
|
21
22
|
# @param [String] kvform the application/x-www-form-urlencoded string
|
22
|
-
# @return [AccessToken] the
|
23
|
+
# @return [AccessToken] the initialized AccessToken
|
23
24
|
def from_kvform(client, kvform)
|
24
25
|
from_hash(client, Rack::Utils.parse_query(kvform))
|
25
26
|
end
|
27
|
+
|
28
|
+
def contains_token?(hash)
|
29
|
+
hash.key?('access_token') || hash.key?('id_token') || hash.key?('token')
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
|
-
#
|
33
|
+
# Initialize an AccessToken
|
29
34
|
#
|
30
35
|
# @param [Client] client the OAuth2::Client instance
|
31
36
|
# @param [String] token the Access Token value
|
@@ -33,25 +38,28 @@ module OAuth2
|
|
33
38
|
# @option opts [String] :refresh_token (nil) the refresh_token value
|
34
39
|
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
|
35
40
|
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
|
41
|
+
# @option opts [FixNum, String] :expires_latency (nil) the number of seconds by which AccessToken validity will be reduced to offset latency, @version 2.0+
|
36
42
|
# @option opts [Symbol] :mode (:header) the transmission mode of the Access Token parameter value
|
37
43
|
# one of :header, :body or :query
|
38
44
|
# @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
|
39
45
|
# @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the
|
40
46
|
# Access Token value in :body or :query transmission mode
|
41
|
-
def initialize(client, token, opts = {})
|
47
|
+
def initialize(client, token, opts = {})
|
42
48
|
@client = client
|
43
49
|
@token = token.to_s
|
44
50
|
opts = opts.dup
|
45
|
-
[
|
51
|
+
%i[refresh_token expires_in expires_at expires_latency].each do |arg|
|
46
52
|
instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
|
47
53
|
end
|
48
54
|
@expires_in ||= opts.delete('expires')
|
49
55
|
@expires_in &&= @expires_in.to_i
|
50
56
|
@expires_at &&= convert_expires_at(@expires_at)
|
57
|
+
@expires_latency &&= @expires_latency.to_i
|
51
58
|
@expires_at ||= Time.now.to_i + @expires_in if @expires_in
|
52
|
-
@
|
53
|
-
|
54
|
-
:
|
59
|
+
@expires_at -= @expires_latency if @expires_latency
|
60
|
+
@options = {mode: opts.delete(:mode) || :header,
|
61
|
+
header_format: opts.delete(:header_format) || 'Bearer %s',
|
62
|
+
param_name: opts.delete(:param_name) || 'access_token'}
|
55
63
|
@params = opts
|
56
64
|
end
|
57
65
|
|
@@ -73,29 +81,32 @@ module OAuth2
|
|
73
81
|
#
|
74
82
|
# @return [Boolean]
|
75
83
|
def expired?
|
76
|
-
expires? && (expires_at
|
84
|
+
expires? && (expires_at <= Time.now.to_i)
|
77
85
|
end
|
78
86
|
|
79
87
|
# Refreshes the current Access Token
|
80
88
|
#
|
81
89
|
# @return [AccessToken] a new AccessToken
|
82
90
|
# @note options should be carried over to the new AccessToken
|
83
|
-
def refresh
|
91
|
+
def refresh(params = {}, access_token_opts = {})
|
84
92
|
raise('A refresh_token is not available') unless refresh_token
|
85
93
|
|
86
94
|
params[:grant_type] = 'refresh_token'
|
87
95
|
params[:refresh_token] = refresh_token
|
88
|
-
new_token = @client.get_token(params)
|
96
|
+
new_token = @client.get_token(params, access_token_opts)
|
89
97
|
new_token.options = options
|
90
98
|
new_token.refresh_token = refresh_token unless new_token.refresh_token
|
91
99
|
new_token
|
92
100
|
end
|
101
|
+
# A compatibility alias
|
102
|
+
# @note does not modify the receiver, so bang is not the default method
|
103
|
+
alias refresh! refresh
|
93
104
|
|
94
105
|
# Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
|
95
106
|
#
|
96
107
|
# @return [Hash] a hash of AccessToken property values
|
97
108
|
def to_hash
|
98
|
-
params.merge(:
|
109
|
+
params.merge(access_token: token, refresh_token: refresh_token, expires_at: expires_at)
|
99
110
|
end
|
100
111
|
|
101
112
|
# Make a request with the Access Token
|
@@ -151,7 +162,7 @@ module OAuth2
|
|
151
162
|
|
152
163
|
private
|
153
164
|
|
154
|
-
def configure_authentication!(opts)
|
165
|
+
def configure_authentication!(opts)
|
155
166
|
case options[:mode]
|
156
167
|
when :header
|
157
168
|
opts[:headers] ||= {}
|
@@ -164,7 +175,7 @@ module OAuth2
|
|
164
175
|
if opts[:body].is_a?(Hash)
|
165
176
|
opts[:body][options[:param_name]] = token
|
166
177
|
else
|
167
|
-
opts[:body]
|
178
|
+
opts[:body] += "&#{options[:param_name]}=#{token}"
|
168
179
|
end
|
169
180
|
# @todo support for multi-part (file uploads)
|
170
181
|
else
|
data/lib/oauth2/authenticator.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
|
3
5
|
module OAuth2
|
@@ -35,7 +37,7 @@ module OAuth2
|
|
35
37
|
end
|
36
38
|
|
37
39
|
def self.encode_basic_auth(user, password)
|
38
|
-
|
40
|
+
"Basic #{Base64.strict_encode64("#{user}:#{password}")}"
|
39
41
|
end
|
40
42
|
|
41
43
|
private
|
@@ -43,13 +45,18 @@ module OAuth2
|
|
43
45
|
# Adds client_id and client_secret request parameters if they are not
|
44
46
|
# already set.
|
45
47
|
def apply_params_auth(params)
|
46
|
-
|
48
|
+
result = {}
|
49
|
+
result['client_id'] = id unless id.nil?
|
50
|
+
result['client_secret'] = secret unless secret.nil?
|
51
|
+
result.merge(params)
|
47
52
|
end
|
48
53
|
|
49
54
|
# When using schemes that don't require the client_secret to be passed i.e TLS Client Auth,
|
50
55
|
# we don't want to send the secret
|
51
56
|
def apply_client_id(params)
|
52
|
-
|
57
|
+
result = {}
|
58
|
+
result['client_id'] = id unless id.nil?
|
59
|
+
result.merge(params)
|
53
60
|
end
|
54
61
|
|
55
62
|
# Adds an `Authorization` header with Basic Auth credentials if and only if
|
@@ -57,10 +64,10 @@ module OAuth2
|
|
57
64
|
def apply_basic_auth(params)
|
58
65
|
headers = params.fetch(:headers, {})
|
59
66
|
headers = basic_auth_header.merge(headers)
|
60
|
-
params.merge(:
|
67
|
+
params.merge(headers: headers)
|
61
68
|
end
|
62
69
|
|
63
|
-
# @see https://
|
70
|
+
# @see https://datatracker.ietf.org/doc/html/rfc2617#section-2
|
64
71
|
def basic_auth_header
|
65
72
|
{'Authorization' => self.class.encode_basic_auth(id, secret)}
|
66
73
|
end
|
data/lib/oauth2/client.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'faraday'
|
2
4
|
require 'logger'
|
3
5
|
|
4
6
|
module OAuth2
|
7
|
+
ConnectionError = Class.new(Faraday::ConnectionFailed)
|
8
|
+
TimeoutError = Class.new(Faraday::TimeoutError)
|
9
|
+
|
5
10
|
# The OAuth2::Client class
|
6
11
|
class Client # rubocop:disable Metrics/ClassLength
|
7
12
|
RESERVED_PARAM_KEYS = %w[headers parse].freeze
|
@@ -16,17 +21,19 @@ module OAuth2
|
|
16
21
|
#
|
17
22
|
# @param [String] client_id the client_id value
|
18
23
|
# @param [String] client_secret the client_secret value
|
19
|
-
# @param [Hash]
|
20
|
-
# @option
|
21
|
-
# @option
|
22
|
-
# @option
|
23
|
-
# @option
|
24
|
-
# @option
|
25
|
-
# @option
|
26
|
-
# @option
|
27
|
-
# @option
|
28
|
-
# @option
|
29
|
-
# @option
|
24
|
+
# @param [Hash] options the options to create the client with
|
25
|
+
# @option options [String] :site the OAuth2 provider site host
|
26
|
+
# @option options [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange
|
27
|
+
# @option options [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint
|
28
|
+
# @option options [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint
|
29
|
+
# @option options [Symbol] :token_method (:post) HTTP method to use to request token (:get, :post, :post_with_query_string)
|
30
|
+
# @option options [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
|
31
|
+
# @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
|
32
|
+
# @option options [FixNum] :max_redirects (5) maximum number of redirects to follow
|
33
|
+
# @option options [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
|
34
|
+
# @option options [Logger] :logger (::Logger.new($stdout)) which logger to use when OAUTH_DEBUG is enabled
|
35
|
+
# @option options [Proc] :extract_access_token proc that takes the client and the response Hash and extracts the access token from the response (DEPRECATED)
|
36
|
+
# @option options [Class] :access_token_class [Class] class of access token for easier subclassing OAuth2::AccessToken, @version 2.0+
|
30
37
|
# @yield [builder] The Faraday connection builder
|
31
38
|
def initialize(client_id, client_secret, options = {}, &block)
|
32
39
|
opts = options.dup
|
@@ -36,22 +43,23 @@ module OAuth2
|
|
36
43
|
ssl = opts.delete(:ssl)
|
37
44
|
|
38
45
|
@options = {
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
46
|
+
authorize_url: 'oauth/authorize',
|
47
|
+
token_url: 'oauth/token',
|
48
|
+
token_method: :post,
|
49
|
+
auth_scheme: :basic_auth,
|
50
|
+
connection_opts: {},
|
51
|
+
connection_build: block,
|
52
|
+
max_redirects: 5,
|
53
|
+
raise_errors: true,
|
54
|
+
logger: ::Logger.new($stdout),
|
55
|
+
access_token_class: AccessToken,
|
48
56
|
}.merge(opts)
|
49
57
|
@options[:connection_opts][:ssl] = ssl if ssl
|
50
58
|
end
|
51
59
|
|
52
60
|
# Set the site host
|
53
61
|
#
|
54
|
-
# @param [String] the OAuth2 provider site host
|
62
|
+
# @param value [String] the OAuth2 provider site host
|
55
63
|
def site=(value)
|
56
64
|
@connection = nil
|
57
65
|
@site = value
|
@@ -59,15 +67,16 @@ module OAuth2
|
|
59
67
|
|
60
68
|
# The Faraday connection object
|
61
69
|
def connection
|
62
|
-
@connection ||=
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
options[:connection_build].call(
|
70
|
+
@connection ||=
|
71
|
+
Faraday.new(site, options[:connection_opts]) do |builder|
|
72
|
+
oauth_debug_logging(builder)
|
73
|
+
if options[:connection_build]
|
74
|
+
options[:connection_build].call(builder)
|
75
|
+
else
|
76
|
+
builder.request :url_encoded # form-encode POST params
|
77
|
+
builder.adapter Faraday.default_adapter # make requests with Net::HTTP
|
67
78
|
end
|
68
79
|
end
|
69
|
-
conn
|
70
|
-
end
|
71
80
|
end
|
72
81
|
|
73
82
|
# The authorize endpoint URL of the OAuth2 provider
|
@@ -86,6 +95,9 @@ module OAuth2
|
|
86
95
|
end
|
87
96
|
|
88
97
|
# Makes a request relative to the specified site root.
|
98
|
+
# Updated HTTP 1.1 specification (IETF RFC 7231) relaxed the original constraint (IETF RFC 2616),
|
99
|
+
# allowing the use of relative URLs in Location headers.
|
100
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2
|
89
101
|
#
|
90
102
|
# @param [Symbol] verb one of :get, :post, :put, :delete
|
91
103
|
# @param [String] url URL path of request
|
@@ -97,16 +109,8 @@ module OAuth2
|
|
97
109
|
# code response for this request. Will default to client option
|
98
110
|
# @option opts [Symbol] :parse @see Response::initialize
|
99
111
|
# @yield [req] The Faraday request
|
100
|
-
def request(verb, url, opts = {})
|
101
|
-
|
102
|
-
|
103
|
-
url = connection.build_url(url).to_s
|
104
|
-
|
105
|
-
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
|
106
|
-
req.params.update(opts[:params]) if opts[:params]
|
107
|
-
yield(req) if block_given?
|
108
|
-
end
|
109
|
-
response = Response.new(response, :parse => opts[:parse])
|
112
|
+
def request(verb, url, opts = {})
|
113
|
+
response = execute_request(verb, url, opts)
|
110
114
|
|
111
115
|
case response.status
|
112
116
|
when 301, 302, 303, 307
|
@@ -118,7 +122,14 @@ module OAuth2
|
|
118
122
|
verb = :get
|
119
123
|
opts.delete(:body)
|
120
124
|
end
|
121
|
-
|
125
|
+
location = response.headers['location']
|
126
|
+
if location
|
127
|
+
full_location = response.response.env.url.merge(location)
|
128
|
+
request(verb, full_location, opts)
|
129
|
+
else
|
130
|
+
error = Error.new(response)
|
131
|
+
raise(error, "Got #{response.status} status code, but no Location header was present")
|
132
|
+
end
|
122
133
|
when 200..299, 300..399
|
123
134
|
# on non-redirecting 3xx statuses, just return the response
|
124
135
|
response
|
@@ -126,7 +137,6 @@ module OAuth2
|
|
126
137
|
error = Error.new(response)
|
127
138
|
raise(error) if opts.fetch(:raise_errors, options[:raise_errors])
|
128
139
|
|
129
|
-
response.error = error
|
130
140
|
response
|
131
141
|
else
|
132
142
|
error = Error.new(response)
|
@@ -136,22 +146,21 @@ module OAuth2
|
|
136
146
|
|
137
147
|
# Initializes an AccessToken by making a request to the token endpoint
|
138
148
|
#
|
139
|
-
# @param [Hash]
|
140
|
-
# @param [Hash] access token options, to pass to the AccessToken object
|
141
|
-
# @param [
|
149
|
+
# @param params [Hash] a Hash of params for the token endpoint
|
150
|
+
# @param access_token_opts [Hash] access token options, to pass to the AccessToken object
|
151
|
+
# @param extract_access_token [Proc] proc that extracts the access token from the response (DEPRECATED)
|
142
152
|
# @return [AccessToken] the initialized AccessToken
|
143
|
-
def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token])
|
153
|
+
def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token])
|
144
154
|
params = params.map do |key, value|
|
145
155
|
if RESERVED_PARAM_KEYS.include?(key)
|
146
156
|
[key.to_sym, value]
|
147
157
|
else
|
148
158
|
[key, value]
|
149
159
|
end
|
150
|
-
end
|
151
|
-
params = Hash[params]
|
160
|
+
end.to_h
|
152
161
|
|
153
|
-
params =
|
154
|
-
opts = {:
|
162
|
+
params = authenticator.apply(params)
|
163
|
+
opts = {raise_errors: options[:raise_errors], parse: params.delete(:parse)}
|
155
164
|
headers = params.delete(:headers) || {}
|
156
165
|
if options[:token_method] == :post
|
157
166
|
opts[:body] = params
|
@@ -161,45 +170,44 @@ module OAuth2
|
|
161
170
|
opts[:headers] = {}
|
162
171
|
end
|
163
172
|
opts[:headers].merge!(headers)
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
build_access_token(response, access_token_opts, extract_access_token)
|
168
|
-
rescue StandardError
|
169
|
-
nil
|
170
|
-
end
|
173
|
+
http_method = options[:token_method]
|
174
|
+
http_method = :post if http_method == :post_with_query_string
|
175
|
+
response = request(http_method, token_url, opts)
|
171
176
|
|
172
|
-
|
173
|
-
|
174
|
-
|
177
|
+
# In v1.4.x, the deprecated extract_access_token option retrieves the token from the response.
|
178
|
+
# We preserve this behavior here, but a custom access_token_class that implements #from_hash
|
179
|
+
# should be used instead.
|
180
|
+
if extract_access_token
|
181
|
+
parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
|
182
|
+
else
|
183
|
+
parse_response(response, access_token_opts)
|
175
184
|
end
|
176
|
-
access_token
|
177
185
|
end
|
178
186
|
|
179
187
|
# The Authorization Code strategy
|
180
188
|
#
|
181
|
-
# @see http://
|
189
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
|
182
190
|
def auth_code
|
183
191
|
@auth_code ||= OAuth2::Strategy::AuthCode.new(self)
|
184
192
|
end
|
185
193
|
|
186
194
|
# The Implicit strategy
|
187
195
|
#
|
188
|
-
# @see http://
|
196
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2
|
189
197
|
def implicit
|
190
198
|
@implicit ||= OAuth2::Strategy::Implicit.new(self)
|
191
199
|
end
|
192
200
|
|
193
201
|
# The Resource Owner Password Credentials strategy
|
194
202
|
#
|
195
|
-
# @see http://
|
203
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3
|
196
204
|
def password
|
197
205
|
@password ||= OAuth2::Strategy::Password.new(self)
|
198
206
|
end
|
199
207
|
|
200
208
|
# The Client Credentials strategy
|
201
209
|
#
|
202
|
-
# @see http://
|
210
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4
|
203
211
|
def client_credentials
|
204
212
|
@client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self)
|
205
213
|
end
|
@@ -219,10 +227,10 @@ module OAuth2
|
|
219
227
|
#
|
220
228
|
# @api semipublic
|
221
229
|
#
|
222
|
-
# @see https://
|
223
|
-
# @see https://
|
224
|
-
# @see https://
|
225
|
-
# @see https://
|
230
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
|
231
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
|
232
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
|
233
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
|
226
234
|
# @return [Hash] the params to add to a request or URL
|
227
235
|
def redirection_params
|
228
236
|
if options[:redirect_uri]
|
@@ -232,26 +240,79 @@ module OAuth2
|
|
232
240
|
end
|
233
241
|
end
|
234
242
|
|
235
|
-
|
236
|
-
|
237
|
-
|
243
|
+
private
|
244
|
+
|
245
|
+
def execute_request(verb, url, opts = {})
|
246
|
+
url = connection.build_url(url).to_s
|
247
|
+
|
248
|
+
begin
|
249
|
+
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
|
250
|
+
req.params.update(opts[:params]) if opts[:params]
|
251
|
+
yield(req) if block_given?
|
252
|
+
end
|
253
|
+
rescue Faraday::ConnectionFailed => e
|
254
|
+
raise ConnectionError, e
|
255
|
+
rescue Faraday::TimeoutError => e
|
256
|
+
raise TimeoutError, e
|
257
|
+
end
|
258
|
+
|
259
|
+
Response.new(response, parse: opts[:parse])
|
238
260
|
end
|
239
261
|
|
240
|
-
|
262
|
+
# Returns the authenticator object
|
263
|
+
#
|
264
|
+
# @return [Authenticator] the initialized Authenticator
|
265
|
+
def authenticator
|
266
|
+
Authenticator.new(id, secret, options[:auth_scheme])
|
267
|
+
end
|
241
268
|
|
242
|
-
def
|
243
|
-
|
244
|
-
return unless parsed_response.is_a?(Hash)
|
269
|
+
def parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
|
270
|
+
access_token = build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
|
245
271
|
|
246
|
-
|
272
|
+
return access_token if access_token
|
247
273
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
274
|
+
if options[:raise_errors]
|
275
|
+
error = Error.new(response)
|
276
|
+
raise(error)
|
277
|
+
end
|
278
|
+
|
279
|
+
nil
|
280
|
+
end
|
281
|
+
|
282
|
+
def parse_response(response, access_token_opts)
|
283
|
+
access_token_class = options[:access_token_class]
|
284
|
+
data = response.parsed
|
285
|
+
|
286
|
+
unless data.is_a?(Hash) && access_token_class.contains_token?(data)
|
287
|
+
return unless options[:raise_errors]
|
288
|
+
|
289
|
+
error = Error.new(response)
|
290
|
+
raise(error)
|
254
291
|
end
|
292
|
+
|
293
|
+
build_access_token(response, access_token_opts, access_token_class)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Builds the access token from the response of the HTTP call
|
297
|
+
#
|
298
|
+
# @return [AccessToken] the initialized AccessToken
|
299
|
+
def build_access_token(response, access_token_opts, access_token_class)
|
300
|
+
access_token_class.from_hash(self, response.parsed.merge(access_token_opts)).tap do |access_token|
|
301
|
+
access_token.response = response if access_token.respond_to?(:response=)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Builds the access token from the response of the HTTP call with legacy extract_access_token
|
306
|
+
#
|
307
|
+
# @return [AccessToken] the initialized AccessToken
|
308
|
+
def build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
|
309
|
+
extract_access_token.call(self, response.parsed.merge(access_token_opts))
|
310
|
+
rescue StandardError
|
311
|
+
nil
|
312
|
+
end
|
313
|
+
|
314
|
+
def oauth_debug_logging(builder)
|
315
|
+
builder.response :logger, options[:logger], bodies: true if ENV['OAUTH_DEBUG'] == 'true'
|
255
316
|
end
|
256
317
|
end
|
257
318
|
end
|
data/lib/oauth2/error.rb
CHANGED
@@ -1,40 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
class Error < StandardError
|
3
5
|
attr_reader :response, :code, :description
|
4
6
|
|
5
|
-
# standard error
|
6
|
-
#
|
7
|
+
# standard error codes include:
|
8
|
+
# 'invalid_request', 'invalid_client', 'invalid_token', 'invalid_grant', 'unsupported_grant_type', 'invalid_scope'
|
7
9
|
def initialize(response)
|
8
|
-
response.error = self
|
9
10
|
@response = response
|
11
|
+
message_opts = {}
|
10
12
|
|
11
13
|
if response.parsed.is_a?(Hash)
|
12
14
|
@code = response.parsed['error']
|
13
15
|
@description = response.parsed['error_description']
|
14
|
-
|
16
|
+
message_opts = parse_error_description(@code, @description)
|
15
17
|
end
|
16
18
|
|
17
|
-
super(error_message(response.body,
|
19
|
+
super(error_message(response.body, message_opts))
|
18
20
|
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
# @param [String] opts :error_description error description to show first line
|
22
|
+
private
|
23
|
+
|
23
24
|
def error_message(response_body, opts = {})
|
24
|
-
|
25
|
+
lines = []
|
26
|
+
|
27
|
+
lines << opts[:error_description] if opts[:error_description]
|
25
28
|
|
26
|
-
|
29
|
+
error_string = if response_body.respond_to?(:encode) && opts[:error_description].respond_to?(:encoding)
|
30
|
+
script_encoding = opts[:error_description].encoding
|
31
|
+
response_body.encode(script_encoding, invalid: :replace, undef: :replace)
|
32
|
+
else
|
33
|
+
response_body
|
34
|
+
end
|
35
|
+
|
36
|
+
lines << error_string
|
37
|
+
|
38
|
+
lines.join("\n")
|
39
|
+
end
|
27
40
|
|
28
|
-
|
29
|
-
|
30
|
-
response_body.encode(script_encoding, :invalid => :replace, :undef => :replace)
|
31
|
-
else
|
32
|
-
response_body
|
33
|
-
end
|
41
|
+
def parse_error_description(code, description)
|
42
|
+
return {} unless code || description
|
34
43
|
|
35
|
-
|
44
|
+
error_description = ''
|
45
|
+
error_description += "#{code}: " if code
|
46
|
+
error_description += description if description
|
36
47
|
|
37
|
-
|
48
|
+
{error_description: error_description}
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|