oauth2 1.4.7 → 1.4.10
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 +36 -3
- data/CONTRIBUTING.md +18 -0
- data/LICENSE +1 -1
- data/README.md +301 -116
- data/SECURITY.md +20 -0
- data/lib/oauth2/access_token.rb +4 -2
- data/lib/oauth2/authenticator.rb +3 -1
- data/lib/oauth2/client.rb +91 -56
- data/lib/oauth2/error.rb +3 -1
- data/lib/oauth2/mac_token.rb +19 -19
- data/lib/oauth2/response.rb +2 -0
- data/lib/oauth2/strategy/assertion.rb +3 -1
- data/lib/oauth2/strategy/auth_code.rb +3 -1
- data/lib/oauth2/strategy/base.rb +2 -0
- data/lib/oauth2/strategy/client_credentials.rb +3 -1
- data/lib/oauth2/strategy/implicit.rb +3 -1
- data/lib/oauth2/strategy/password.rb +3 -1
- data/lib/oauth2/version.rb +1 -1
- data/lib/oauth2.rb +2 -0
- metadata +50 -87
- 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/authenticator.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
|
3
5
|
module OAuth2
|
@@ -60,7 +62,7 @@ module OAuth2
|
|
60
62
|
params.merge(:headers => headers)
|
61
63
|
end
|
62
64
|
|
63
|
-
# @see https://
|
65
|
+
# @see https://datatracker.ietf.org/doc/html/rfc2617#section-2
|
64
66
|
def basic_auth_header
|
65
67
|
{'Authorization' => self.class.encode_basic_auth(id, secret)}
|
66
68
|
end
|
data/lib/oauth2/client.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
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)
|
5
8
|
# The OAuth2::Client class
|
6
9
|
class Client # rubocop:disable Metrics/ClassLength
|
7
10
|
RESERVED_PARAM_KEYS = %w[headers parse].freeze
|
@@ -16,17 +19,18 @@ module OAuth2
|
|
16
19
|
#
|
17
20
|
# @param [String] client_id the client_id value
|
18
21
|
# @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
|
22
|
+
# @param [Hash] options the options to create the client with
|
23
|
+
# @option options [String] :site the OAuth2 provider site host
|
24
|
+
# @option options [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange
|
25
|
+
# @option options [String] :authorize_url ('oauth/authorize') absolute or relative URL path to the Authorization endpoint
|
26
|
+
# @option options [String] :token_url ('oauth/token') absolute or relative URL path to the Token endpoint
|
27
|
+
# @option options [Symbol] :token_method (:post) HTTP method to use to request token (:get or :post)
|
28
|
+
# @option options [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
|
29
|
+
# @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
|
30
|
+
# @option options [FixNum] :max_redirects (5) maximum number of redirects to follow
|
31
|
+
# @option options [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
|
32
|
+
# @option options [Logger] :logger (::Logger.new($stdout)) which logger to use when OAUTH_DEBUG is enabled
|
33
|
+
# @option options [Proc] (DEPRECATED) :extract_access_token proc that extracts the access token from the response
|
30
34
|
# @yield [builder] The Faraday connection builder
|
31
35
|
def initialize(client_id, client_secret, options = {}, &block)
|
32
36
|
opts = options.dup
|
@@ -34,24 +38,22 @@ module OAuth2
|
|
34
38
|
@secret = client_secret
|
35
39
|
@site = opts.delete(:site)
|
36
40
|
ssl = opts.delete(:ssl)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
:extract_access_token => DEFAULT_EXTRACT_ACCESS_TOKEN,
|
48
|
-
}.merge(opts)
|
41
|
+
@options = {:authorize_url => 'oauth/authorize',
|
42
|
+
:token_url => 'oauth/token',
|
43
|
+
:token_method => :post,
|
44
|
+
:auth_scheme => :request_body,
|
45
|
+
:connection_opts => {},
|
46
|
+
:connection_build => block,
|
47
|
+
:max_redirects => 5,
|
48
|
+
:raise_errors => true,
|
49
|
+
:extract_access_token => DEFAULT_EXTRACT_ACCESS_TOKEN, # DEPRECATED
|
50
|
+
:logger => ::Logger.new($stdout)}.merge(opts)
|
49
51
|
@options[:connection_opts][:ssl] = ssl if ssl
|
50
52
|
end
|
51
53
|
|
52
54
|
# Set the site host
|
53
55
|
#
|
54
|
-
# @param [String] the OAuth2 provider site host
|
56
|
+
# @param value [String] the OAuth2 provider site host
|
55
57
|
def site=(value)
|
56
58
|
@connection = nil
|
57
59
|
@site = value
|
@@ -59,15 +61,16 @@ module OAuth2
|
|
59
61
|
|
60
62
|
# The Faraday connection object
|
61
63
|
def connection
|
62
|
-
@connection ||=
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
options[:connection_build].call(
|
64
|
+
@connection ||=
|
65
|
+
Faraday.new(site, options[:connection_opts]) do |builder|
|
66
|
+
oauth_debug_logging(builder)
|
67
|
+
if options[:connection_build]
|
68
|
+
options[:connection_build].call(builder)
|
69
|
+
else
|
70
|
+
builder.request :url_encoded # form-encode POST params
|
71
|
+
builder.adapter Faraday.default_adapter # make requests with Net::HTTP
|
67
72
|
end
|
68
73
|
end
|
69
|
-
conn
|
70
|
-
end
|
71
74
|
end
|
72
75
|
|
73
76
|
# The authorize endpoint URL of the OAuth2 provider
|
@@ -97,15 +100,18 @@ module OAuth2
|
|
97
100
|
# code response for this request. Will default to client option
|
98
101
|
# @option opts [Symbol] :parse @see Response::initialize
|
99
102
|
# @yield [req] The Faraday request
|
100
|
-
def request(verb, url, opts = {}) # rubocop:disable Metrics/
|
101
|
-
connection.response :logger, ::Logger.new($stdout) if ENV['OAUTH_DEBUG'] == 'true'
|
102
|
-
|
103
|
+
def request(verb, url, opts = {}) # rubocop:disable Metrics/AbcSize
|
103
104
|
url = connection.build_url(url).to_s
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
106
|
+
begin
|
107
|
+
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
|
108
|
+
req.params.update(opts[:params]) if opts[:params]
|
109
|
+
yield(req) if block_given?
|
110
|
+
end
|
111
|
+
rescue Faraday::ConnectionFailed => e
|
112
|
+
raise ConnectionError, e
|
108
113
|
end
|
114
|
+
|
109
115
|
response = Response.new(response, :parse => opts[:parse])
|
110
116
|
|
111
117
|
case response.status
|
@@ -118,7 +124,13 @@ module OAuth2
|
|
118
124
|
verb = :get
|
119
125
|
opts.delete(:body)
|
120
126
|
end
|
121
|
-
|
127
|
+
location = response.headers['location']
|
128
|
+
if location
|
129
|
+
request(verb, location, opts)
|
130
|
+
else
|
131
|
+
error = Error.new(response)
|
132
|
+
raise(error, "Got #{response.status} status code, but no Location header was present")
|
133
|
+
end
|
122
134
|
when 200..299, 300..399
|
123
135
|
# on non-redirecting 3xx statuses, just return the response
|
124
136
|
response
|
@@ -136,11 +148,11 @@ module OAuth2
|
|
136
148
|
|
137
149
|
# Initializes an AccessToken by making a request to the token endpoint
|
138
150
|
#
|
139
|
-
# @param [Hash]
|
140
|
-
# @param [Hash] access token options, to pass to the AccessToken object
|
141
|
-
# @param [Class] class of access token for easier subclassing OAuth2::AccessToken
|
151
|
+
# @param params [Hash] a Hash of params for the token endpoint
|
152
|
+
# @param access_token_opts [Hash] access token options, to pass to the AccessToken object
|
153
|
+
# @param access_token_class [Class] class of access token for easier subclassing OAuth2::AccessToken
|
142
154
|
# @return [AccessToken] the initialized AccessToken
|
143
|
-
def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token]) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
155
|
+
def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token]) # # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity Metrics/AbcSize, Metrics/MethodLength
|
144
156
|
params = params.map do |key, value|
|
145
157
|
if RESERVED_PARAM_KEYS.include?(key)
|
146
158
|
[key.to_sym, value]
|
@@ -150,7 +162,7 @@ module OAuth2
|
|
150
162
|
end
|
151
163
|
params = Hash[params]
|
152
164
|
|
153
|
-
params =
|
165
|
+
params = authenticator.apply(params)
|
154
166
|
opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
|
155
167
|
headers = params.delete(:headers) || {}
|
156
168
|
if options[:token_method] == :post
|
@@ -160,8 +172,9 @@ module OAuth2
|
|
160
172
|
opts[:params] = params
|
161
173
|
opts[:headers] = {}
|
162
174
|
end
|
163
|
-
opts[:headers].merge
|
164
|
-
|
175
|
+
opts[:headers] = opts[:headers].merge(headers)
|
176
|
+
http_method = options[:token_method]
|
177
|
+
response = request(http_method, token_url, opts)
|
165
178
|
|
166
179
|
access_token = begin
|
167
180
|
build_access_token(response, access_token_opts, extract_access_token)
|
@@ -169,37 +182,45 @@ module OAuth2
|
|
169
182
|
nil
|
170
183
|
end
|
171
184
|
|
172
|
-
|
185
|
+
response_contains_token = access_token || (
|
186
|
+
response.parsed.is_a?(Hash) &&
|
187
|
+
(response.parsed['access_token'] || response.parsed['id_token'])
|
188
|
+
)
|
189
|
+
|
190
|
+
if options[:raise_errors] && !response_contains_token
|
173
191
|
error = Error.new(response)
|
174
192
|
raise(error)
|
193
|
+
elsif !response_contains_token
|
194
|
+
return nil
|
175
195
|
end
|
196
|
+
|
176
197
|
access_token
|
177
198
|
end
|
178
199
|
|
179
200
|
# The Authorization Code strategy
|
180
201
|
#
|
181
|
-
# @see http://
|
202
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
|
182
203
|
def auth_code
|
183
204
|
@auth_code ||= OAuth2::Strategy::AuthCode.new(self)
|
184
205
|
end
|
185
206
|
|
186
207
|
# The Implicit strategy
|
187
208
|
#
|
188
|
-
# @see http://
|
209
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2
|
189
210
|
def implicit
|
190
211
|
@implicit ||= OAuth2::Strategy::Implicit.new(self)
|
191
212
|
end
|
192
213
|
|
193
214
|
# The Resource Owner Password Credentials strategy
|
194
215
|
#
|
195
|
-
# @see http://
|
216
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3
|
196
217
|
def password
|
197
218
|
@password ||= OAuth2::Strategy::Password.new(self)
|
198
219
|
end
|
199
220
|
|
200
221
|
# The Client Credentials strategy
|
201
222
|
#
|
202
|
-
# @see http://
|
223
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4
|
203
224
|
def client_credentials
|
204
225
|
@client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self)
|
205
226
|
end
|
@@ -219,10 +240,10 @@ module OAuth2
|
|
219
240
|
#
|
220
241
|
# @api semipublic
|
221
242
|
#
|
222
|
-
# @see https://
|
223
|
-
# @see https://
|
224
|
-
# @see https://
|
225
|
-
# @see https://
|
243
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
|
244
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
|
245
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
|
246
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
|
226
247
|
# @return [Hash] the params to add to a request or URL
|
227
248
|
def redirection_params
|
228
249
|
if options[:redirect_uri]
|
@@ -239,19 +260,33 @@ module OAuth2
|
|
239
260
|
|
240
261
|
private
|
241
262
|
|
263
|
+
# Returns the authenticator object
|
264
|
+
#
|
265
|
+
# @return [Authenticator] the initialized Authenticator
|
266
|
+
def authenticator
|
267
|
+
Authenticator.new(id, secret, options[:auth_scheme])
|
268
|
+
end
|
269
|
+
|
270
|
+
# Builds the access token from the response of the HTTP call
|
271
|
+
#
|
272
|
+
# @return [AccessToken] the initialized AccessToken
|
242
273
|
def build_access_token(response, access_token_opts, extract_access_token)
|
243
274
|
parsed_response = response.parsed.dup
|
244
275
|
return unless parsed_response.is_a?(Hash)
|
245
276
|
|
246
277
|
hash = parsed_response.merge(access_token_opts)
|
247
278
|
|
248
|
-
# Provide backwards compatibility for old
|
249
|
-
#
|
279
|
+
# Provide backwards compatibility for old AccessToken.form_hash pattern
|
280
|
+
# Will be deprecated in 2.x
|
250
281
|
if extract_access_token.is_a?(Class) && extract_access_token.respond_to?(:from_hash)
|
251
282
|
extract_access_token.from_hash(self, hash)
|
252
283
|
else
|
253
284
|
extract_access_token.call(self, hash)
|
254
285
|
end
|
255
286
|
end
|
287
|
+
|
288
|
+
def oauth_debug_logging(builder)
|
289
|
+
builder.response :logger, options[:logger], :bodies => true if ENV['OAUTH_DEBUG'] == 'true'
|
290
|
+
end
|
256
291
|
end
|
257
292
|
end
|
data/lib/oauth2/error.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
class Error < StandardError
|
3
5
|
attr_reader :response, :code, :description
|
@@ -23,7 +25,7 @@ module OAuth2
|
|
23
25
|
def error_message(response_body, opts = {})
|
24
26
|
message = []
|
25
27
|
|
26
|
-
opts[:error_description] && message << opts[:error_description]
|
28
|
+
opts[:error_description] && (message << opts[:error_description])
|
27
29
|
|
28
30
|
error_message = if opts[:error_description] && opts[:error_description].respond_to?(:encoding)
|
29
31
|
script_encoding = opts[:error_description].encoding
|
data/lib/oauth2/mac_token.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
require 'digest'
|
3
5
|
require 'openssl'
|
@@ -60,7 +62,7 @@ module OAuth2
|
|
60
62
|
# @param [String] url the HTTP URL path of the request
|
61
63
|
def header(verb, url)
|
62
64
|
timestamp = Time.now.utc.to_i
|
63
|
-
nonce = Digest::
|
65
|
+
nonce = Digest::SHA256.hexdigest([timestamp, SecureRandom.hex].join(':'))
|
64
66
|
|
65
67
|
uri = URI.parse(url)
|
66
68
|
|
@@ -95,24 +97,22 @@ module OAuth2
|
|
95
97
|
#
|
96
98
|
# @param [String] alg the algorithm to use (one of 'hmac-sha-1', 'hmac-sha-256')
|
97
99
|
def algorithm=(alg)
|
98
|
-
@algorithm =
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
end
|
100
|
+
@algorithm = case alg.to_s
|
101
|
+
when 'hmac-sha-1'
|
102
|
+
begin
|
103
|
+
OpenSSL::Digest('SHA1').new
|
104
|
+
rescue StandardError
|
105
|
+
OpenSSL::Digest.new('SHA1')
|
106
|
+
end
|
107
|
+
when 'hmac-sha-256'
|
108
|
+
begin
|
109
|
+
OpenSSL::Digest('SHA256').new
|
110
|
+
rescue StandardError
|
111
|
+
OpenSSL::Digest.new('SHA256')
|
112
|
+
end
|
113
|
+
else
|
114
|
+
raise(ArgumentError, 'Unsupported algorithm')
|
115
|
+
end
|
116
116
|
end
|
117
117
|
|
118
118
|
private
|
data/lib/oauth2/response.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'jwt'
|
2
4
|
|
3
5
|
module OAuth2
|
4
6
|
module Strategy
|
5
7
|
# The Client Assertion Strategy
|
6
8
|
#
|
7
|
-
# @see
|
9
|
+
# @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-10#section-4.1.3
|
8
10
|
#
|
9
11
|
# Sample usage:
|
10
12
|
# client = OAuth2::Client.new(client_id, client_secret,
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
module Strategy
|
3
5
|
# The Authorization Code Strategy
|
4
6
|
#
|
5
|
-
# @see http://
|
7
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
|
6
8
|
class AuthCode < Base
|
7
9
|
# The required query parameters for the authorize URL
|
8
10
|
#
|
data/lib/oauth2/strategy/base.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
module Strategy
|
3
5
|
# The Client Credentials Strategy
|
4
6
|
#
|
5
|
-
# @see http://
|
7
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4
|
6
8
|
class ClientCredentials < Base
|
7
9
|
# Not used for this strategy
|
8
10
|
#
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
module Strategy
|
3
5
|
# The Implicit Strategy
|
4
6
|
#
|
5
|
-
# @see http://
|
7
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2
|
6
8
|
class Implicit < Base
|
7
9
|
# The required query parameters for the authorize URL
|
8
10
|
#
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
module Strategy
|
3
5
|
# The Resource Owner Password Credentials Authorization Strategy
|
4
6
|
#
|
5
|
-
# @see http://
|
7
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3
|
6
8
|
class Password < Base
|
7
9
|
# Not used for this strategy
|
8
10
|
#
|
data/lib/oauth2/version.rb
CHANGED