oauth2 1.4.0 → 1.4.5

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.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ # !/usr/bin/env rake
4
+
5
+ require 'bundler/gem_tasks'
6
+
7
+ begin
8
+ require 'wwtd/tasks'
9
+ rescue LoadError
10
+ puts 'failed to load wwtd'
11
+ end
12
+
13
+ begin
14
+ require 'rspec/core/rake_task'
15
+ RSpec::Core::RakeTask.new(:spec)
16
+ rescue LoadError
17
+ task :spec do
18
+ warn 'rspec is disabled'
19
+ end
20
+ end
21
+ task :test => :spec
22
+
23
+ begin
24
+ require 'rubocop/rake_task'
25
+ RuboCop::RakeTask.new do |task|
26
+ task.options = ['-D'] # Display the name of the failing cops
27
+ end
28
+ rescue LoadError
29
+ task :rubocop do
30
+ warn 'RuboCop is disabled'
31
+ end
32
+ end
33
+
34
+ namespace :doc do
35
+ require 'rdoc/task'
36
+ require 'oauth2/version'
37
+ RDoc::Task.new do |rdoc|
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ rdoc.title = "oauth2 #{OAuth2::Version}"
40
+ rdoc.main = 'README.md'
41
+ rdoc.rdoc_files.include('README.md', 'LICENSE.md', 'lib/**/*.rb')
42
+ end
43
+ end
44
+
45
+ task :default => [:test, :rubocop]
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'faraday', '~> 0.15.4'
4
+
5
+ gem 'json', '< 2.0'
6
+ gem 'rack', '~> 1.2'
7
+ gem 'rake', ['>= 10.0', '< 12']
8
+ gem 'term-ansicolor', '< 1.4.0'
9
+ gem 'tins', '< 1.7'
10
+
11
+ gemspec :path => '../'
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'faraday', '~> 0.15.4'
4
+
5
+ gem 'rake', ['>= 10.0', '< 12']
6
+
7
+ gemspec :path => '../'
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '../'
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '../'
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '../'
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'faraday', '~> 0.15.4'
4
+
5
+ gem 'json', '< 2.0'
6
+ gem 'rack', '~> 1.2'
7
+ gem 'rake', ['>= 10.0', '< 12']
8
+ gem 'term-ansicolor', '< 1.4.0'
9
+ gem 'tins', '< 1.7'
10
+
11
+ gemspec :path => '../'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'faraday', '~> 0.15.4'
4
+ gem 'rack', '~> 1.2'
5
+
6
+ gemspec :path => '../'
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'byebug'
5
+ gem 'pry'
6
+ gem 'pry-byebug'
7
+ end
8
+
9
+ gemspec :path => '../'
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '../'
@@ -3,6 +3,7 @@ module OAuth2
3
3
  attr_reader :client, :token, :expires_in, :expires_at, :params
4
4
  attr_accessor :options, :refresh_token
5
5
 
6
+ # Should these methods be deprecated?
6
7
  class << self
7
8
  # Initializes an AccessToken from a Hash
8
9
  #
@@ -46,11 +47,11 @@ module OAuth2
46
47
  end
47
48
  @expires_in ||= opts.delete('expires')
48
49
  @expires_in &&= @expires_in.to_i
49
- @expires_at &&= @expires_at.to_i
50
+ @expires_at &&= convert_expires_at(@expires_at)
50
51
  @expires_at ||= Time.now.to_i + @expires_in if @expires_in
51
- @options = {:mode => opts.delete(:mode) || :header,
52
+ @options = {:mode => opts.delete(:mode) || :header,
52
53
  :header_format => opts.delete(:header_format) || 'Bearer %s',
53
- :param_name => opts.delete(:param_name) || 'access_token'}
54
+ :param_name => opts.delete(:param_name) || 'access_token'}
54
55
  @params = opts
55
56
  end
56
57
 
@@ -81,6 +82,7 @@ module OAuth2
81
82
  # @note options should be carried over to the new AccessToken
82
83
  def refresh!(params = {})
83
84
  raise('A refresh_token is not available') unless refresh_token
85
+
84
86
  params[:grant_type] = 'refresh_token'
85
87
  params[:refresh_token] = refresh_token
86
88
  new_token = @client.get_token(params)
@@ -149,7 +151,7 @@ module OAuth2
149
151
 
150
152
  private
151
153
 
152
- def configure_authentication!(opts) # rubocop:disable MethodLength, Metrics/AbcSize
154
+ def configure_authentication!(opts) # rubocop:disable Metrics/AbcSize
153
155
  case options[:mode]
154
156
  when :header
155
157
  opts[:headers] ||= {}
@@ -169,5 +171,13 @@ module OAuth2
169
171
  raise("invalid :mode option of #{options[:mode]}")
170
172
  end
171
173
  end
174
+
175
+ def convert_expires_at(expires_at)
176
+ expires_at_i = expires_at.to_i
177
+ return expires_at_i if expires_at_i > Time.now.utc.to_i
178
+ return Time.parse(expires_at).to_i if expires_at.is_a?(String)
179
+
180
+ expires_at_i
181
+ end
172
182
  end
173
183
  end
@@ -25,6 +25,10 @@ module OAuth2
25
25
  apply_basic_auth(params)
26
26
  when :request_body
27
27
  apply_params_auth(params)
28
+ when :tls_client_auth
29
+ apply_client_id(params)
30
+ when :private_key_jwt
31
+ params
28
32
  else
29
33
  raise NotImplementedError
30
34
  end
@@ -42,6 +46,12 @@ module OAuth2
42
46
  {'client_id' => id, 'client_secret' => secret}.merge(params)
43
47
  end
44
48
 
49
+ # When using schemes that don't require the client_secret to be passed i.e TLS Client Auth,
50
+ # we don't want to send the secret
51
+ def apply_client_id(params)
52
+ {'client_id' => id}.merge(params)
53
+ end
54
+
45
55
  # Adds an `Authorization` header with Basic Auth credentials if and only if
46
56
  # it is not already set in the params.
47
57
  def apply_basic_auth(params)
data/lib/oauth2/client.rb CHANGED
@@ -4,6 +4,8 @@ require 'logger'
4
4
  module OAuth2
5
5
  # The OAuth2::Client class
6
6
  class Client # rubocop:disable Metrics/ClassLength
7
+ RESERVED_PARAM_KEYS = %w[headers parse].freeze
8
+
7
9
  attr_reader :id, :secret, :site
8
10
  attr_accessor :options
9
11
  attr_writer :connection
@@ -23,8 +25,8 @@ module OAuth2
23
25
  # @option opts [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
24
26
  # @option opts [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
25
27
  # @option opts [FixNum] :max_redirects (5) maximum number of redirects to follow
26
- # @option opts [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error
27
- # on responses with 400+ status codes
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
28
30
  # @yield [builder] The Faraday connection builder
29
31
  def initialize(client_id, client_secret, options = {}, &block)
30
32
  opts = options.dup
@@ -32,14 +34,18 @@ module OAuth2
32
34
  @secret = client_secret
33
35
  @site = opts.delete(:site)
34
36
  ssl = opts.delete(:ssl)
35
- @options = {:authorize_url => '/oauth/authorize',
36
- :token_url => '/oauth/token',
37
- :token_method => :post,
38
- :auth_scheme => :request_body,
39
- :connection_opts => {},
40
- :connection_build => block,
41
- :max_redirects => 5,
42
- :raise_errors => true}.merge(opts)
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)
43
49
  @options[:connection_opts][:ssl] = ssl if ssl
44
50
  end
45
51
 
@@ -91,12 +97,13 @@ module OAuth2
91
97
  # code response for this request. Will default to client option
92
98
  # @option opts [Symbol] :parse @see Response::initialize
93
99
  # @yield [req] The Faraday request
94
- def request(verb, url, opts = {}) # rubocop:disable CyclomaticComplexity, MethodLength, Metrics/AbcSize
100
+ def request(verb, url, opts = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
95
101
  connection.response :logger, ::Logger.new($stdout) if ENV['OAUTH_DEBUG'] == 'true'
96
102
 
97
- url = connection.build_url(url, opts[:params]).to_s
103
+ url = connection.build_url(url).to_s
98
104
 
99
105
  response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
106
+ req.params.update(opts[:params]) if opts[:params]
100
107
  yield(req) if block_given?
101
108
  end
102
109
  response = Response.new(response, :parse => opts[:parse])
@@ -106,6 +113,7 @@ module OAuth2
106
113
  opts[:redirect_count] ||= 0
107
114
  opts[:redirect_count] += 1
108
115
  return response if opts[:redirect_count] > options[:max_redirects]
116
+
109
117
  if response.status == 303
110
118
  verb = :get
111
119
  opts.delete(:body)
@@ -117,6 +125,7 @@ module OAuth2
117
125
  when 400..599
118
126
  error = Error.new(response)
119
127
  raise(error) if opts.fetch(:raise_errors, options[:raise_errors])
128
+
120
129
  response.error = error
121
130
  response
122
131
  else
@@ -130,8 +139,17 @@ module OAuth2
130
139
  # @param [Hash] params a Hash of params for the token endpoint
131
140
  # @param [Hash] access token options, to pass to the AccessToken object
132
141
  # @param [Class] class of access token for easier subclassing OAuth2::AccessToken
133
- # @return [AccessToken] the initalized AccessToken
134
- def get_token(params, access_token_opts = {}, access_token_class = AccessToken) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
142
+ # @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
144
+ params = params.map do |key, value|
145
+ if RESERVED_PARAM_KEYS.include?(key)
146
+ [key.to_sym, value]
147
+ else
148
+ [key, value]
149
+ end
150
+ end
151
+ params = Hash[params]
152
+
135
153
  params = Authenticator.new(id, secret, options[:auth_scheme]).apply(params)
136
154
  opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
137
155
  headers = params.delete(:headers) || {}
@@ -144,11 +162,18 @@ module OAuth2
144
162
  end
145
163
  opts[:headers].merge!(headers)
146
164
  response = request(options[:token_method], token_url, opts)
147
- if options[:raise_errors] && !(response.parsed.is_a?(Hash) && response.parsed['access_token'])
165
+
166
+ access_token = begin
167
+ build_access_token(response, access_token_opts, extract_access_token)
168
+ rescue StandardError
169
+ nil
170
+ end
171
+
172
+ if options[:raise_errors] && !access_token
148
173
  error = Error.new(response)
149
174
  raise(error)
150
175
  end
151
- access_token_class.from_hash(self, response.parsed.merge(access_token_opts))
176
+ access_token
152
177
  end
153
178
 
154
179
  # The Authorization Code strategy
@@ -207,4 +232,26 @@ module OAuth2
207
232
  end
208
233
  end
209
234
  end
235
+
236
+ DEFAULT_EXTRACT_ACCESS_TOKEN = proc do |client, hash|
237
+ token = hash.delete('access_token') || hash.delete(:access_token)
238
+ token && AccessToken.new(client, token, hash)
239
+ end
240
+
241
+ private
242
+
243
+ def build_access_token(response, access_token_opts, extract_access_token)
244
+ parsed_response = response.parsed.dup
245
+ return unless parsed_response.is_a?(Hash)
246
+
247
+ hash = parsed_response.merge(access_token_opts)
248
+
249
+ # Provide backwards compatibility for old AcessToken.form_hash pattern
250
+ # Should be deprecated in 2.x
251
+ if extract_access_token.is_a?(Class) && extract_access_token.respond_to?(:from_hash)
252
+ extract_access_token.from_hash(self, hash)
253
+ else
254
+ extract_access_token.call(self, hash)
255
+ end
256
+ end
210
257
  end
@@ -98,9 +98,17 @@ module OAuth2
98
98
  @algorithm = begin
99
99
  case alg.to_s
100
100
  when 'hmac-sha-1'
101
- OpenSSL::Digest::SHA1.new
101
+ begin
102
+ OpenSSL::Digest('SHA1').new
103
+ rescue StandardError
104
+ OpenSSL::Digest.new('SHA1')
105
+ end
102
106
  when 'hmac-sha-256'
103
- OpenSSL::Digest::SHA256.new
107
+ begin
108
+ OpenSSL::Digest('SHA256').new
109
+ rescue StandardError
110
+ OpenSSL::Digest.new('SHA256')
111
+ end
104
112
  else
105
113
  raise(ArgumentError, 'Unsupported algorithm')
106
114
  end
@@ -111,7 +119,7 @@ module OAuth2
111
119
 
112
120
  # No-op since we need the verb and path
113
121
  # and the MAC always goes in a header
114
- def token=(_)
122
+ def token=(_noop)
115
123
  end
116
124
 
117
125
  # Base64.strict_encode64 is not available on Ruby 1.8.7
@@ -11,9 +11,9 @@ module OAuth2
11
11
  # Procs that, when called, will parse a response body according
12
12
  # to the specified format.
13
13
  @@parsers = {
14
- :json => lambda { |body| MultiJson.load(body) rescue body }, # rubocop:disable RescueModifier
14
+ :json => lambda { |body| MultiJson.load(body) rescue body }, # rubocop:disable Style/RescueModifier
15
15
  :query => lambda { |body| Rack::Utils.parse_query(body) },
16
- :text => lambda { |body| body },
16
+ :text => lambda { |body| body },
17
17
  }
18
18
 
19
19
  # Content type assignments for various potential HTTP content types.
@@ -68,6 +68,7 @@ module OAuth2
68
68
  # application/json Content-Type response bodies
69
69
  def parsed
70
70
  return nil unless @@parsers.key?(parser)
71
+
71
72
  @parsed ||= @@parsers[parser].call(body)
72
73
  end
73
74
 
@@ -79,11 +80,12 @@ module OAuth2
79
80
  # Determines the parser that will be used to supply the content of #parsed
80
81
  def parser
81
82
  return options[:parse].to_sym if @@parsers.key?(options[:parse])
83
+
82
84
  @@content_types[content_type]
83
85
  end
84
86
  end
85
87
  end
86
88
 
87
89
  OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml']) do |body|
88
- MultiXml.parse(body) rescue body # rubocop:disable RescueModifier
90
+ MultiXml.parse(body) rescue body # rubocop:disable Style/RescueModifier
89
91
  end
@@ -50,10 +50,10 @@ module OAuth2
50
50
  def build_request(params)
51
51
  assertion = build_assertion(params)
52
52
  {
53
- :grant_type => 'assertion',
53
+ :grant_type => 'assertion',
54
54
  :assertion_type => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
55
- :assertion => assertion,
56
- :scope => params[:scope],
55
+ :assertion => assertion,
56
+ :scope => params[:scope],
57
57
  }
58
58
  end
59
59
 
@@ -18,8 +18,8 @@ module OAuth2
18
18
  # @param [Hash] params additional params
19
19
  def get_token(username, password, params = {}, opts = {})
20
20
  params = {'grant_type' => 'password',
21
- 'username' => username,
22
- 'password' => password}.merge(params)
21
+ 'username' => username,
22
+ 'password' => password}.merge(params)
23
23
  @client.get_token(params, opts)
24
24
  end
25
25
  end
@@ -1,5 +1,6 @@
1
1
  module OAuth2
2
2
  module Version
3
+ VERSION = to_s
3
4
  module_function
4
5
 
5
6
  # The major version
@@ -20,7 +21,7 @@ module OAuth2
20
21
  #
21
22
  # @return [Integer]
22
23
  def patch
23
- 0
24
+ 5
24
25
  end
25
26
 
26
27
  # The pre-release version, if any