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.
@@ -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://tools.ietf.org/html/rfc2617#section-2
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] opts the options to create the client with
20
- # @option opts [String] :site the OAuth2 provider site host
21
- # @option opts [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange
22
- # @option opts [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint
23
- # @option opts [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint
24
- # @option opts [Symbol] :token_method (:post) HTTP method to use to request token (:get or :post)
25
- # @option opts [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
26
- # @option opts [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
27
- # @option opts [FixNum] :max_redirects (5) maximum number of redirects to follow
28
- # @option opts [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
29
- # @option opts [Proc] :extract_access_token proc that extracts the access token from the response
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
- @options = {
39
- :authorize_url => '/oauth/authorize',
40
- :token_url => '/oauth/token',
41
- :token_method => :post,
42
- :auth_scheme => :request_body,
43
- :connection_opts => {},
44
- :connection_build => block,
45
- :max_redirects => 5,
46
- :raise_errors => true,
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 ||= begin
63
- conn = Faraday.new(site, options[:connection_opts])
64
- if options[:connection_build]
65
- conn.build do |b|
66
- options[:connection_build].call(b)
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/MethodLength, Metrics/AbcSize
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
- 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?
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
- request(verb, response.headers['location'], opts)
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] params a Hash of params for the token endpoint
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 = Authenticator.new(id, secret, options[:auth_scheme]).apply(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!(headers)
164
- response = request(options[:token_method], token_url, opts)
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
- if options[:raise_errors] && !access_token
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://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1
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://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2
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://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3
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://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
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://tools.ietf.org/html/rfc6749#section-4.1
223
- # @see https://tools.ietf.org/html/rfc6749#section-4.1.3
224
- # @see https://tools.ietf.org/html/rfc6749#section-4.2.1
225
- # @see https://tools.ietf.org/html/rfc6749#section-10.6
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 AcessToken.form_hash pattern
249
- # Should be deprecated in 2.x
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
@@ -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::MD5.hexdigest([timestamp, SecureRandom.hex].join(':'))
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 = begin
99
- case alg.to_s
100
- when 'hmac-sha-1'
101
- begin
102
- OpenSSL::Digest('SHA1').new
103
- rescue StandardError
104
- OpenSSL::Digest.new('SHA1')
105
- end
106
- when 'hmac-sha-256'
107
- begin
108
- OpenSSL::Digest('SHA256').new
109
- rescue StandardError
110
- OpenSSL::Digest.new('SHA256')
111
- end
112
- else
113
- raise(ArgumentError, 'Unsupported algorithm')
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'multi_json'
2
4
  require 'multi_xml'
3
5
  require 'rack'
@@ -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 http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
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://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1
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
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OAuth2
2
4
  module Strategy
3
5
  class Base
@@ -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://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
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://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2
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://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3
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
  #
@@ -24,7 +24,7 @@ module OAuth2
24
24
  #
25
25
  # @return [Integer]
26
26
  def patch
27
- 7
27
+ 10
28
28
  end
29
29
 
30
30
  # The pre-release version, if any
data/lib/oauth2.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'oauth2/error'
2
4
  require 'oauth2/authenticator'
3
5
  require 'oauth2/client'