oauth2 1.4.7 → 1.4.11

Sign up to get free protection for your applications and to get access to all the features.
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
+ 11
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'