oauth2 1.4.2 → 1.4.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -2
- data/CODE_OF_CONDUCT.md +105 -46
- data/LICENSE +1 -1
- data/README.md +290 -102
- data/lib/oauth2/access_token.rb +15 -5
- data/lib/oauth2/authenticator.rb +13 -1
- data/lib/oauth2/client.rb +133 -51
- data/lib/oauth2/error.rb +3 -1
- data/lib/oauth2/mac_token.rb +18 -10
- data/lib/oauth2/response.rb +7 -3
- data/lib/oauth2/strategy/assertion.rb +6 -4
- 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 +5 -3
- data/lib/oauth2/version.rb +9 -3
- data/lib/oauth2.rb +2 -0
- data/spec/fixtures/README.md +11 -0
- data/spec/fixtures/RS256/jwtRS256.key +51 -0
- data/spec/fixtures/RS256/jwtRS256.key.pub +14 -0
- data/spec/helper.rb +33 -0
- data/spec/oauth2/access_token_spec.rb +218 -0
- data/spec/oauth2/authenticator_spec.rb +86 -0
- data/spec/oauth2/client_spec.rb +556 -0
- data/spec/oauth2/mac_token_spec.rb +122 -0
- data/spec/oauth2/response_spec.rb +96 -0
- data/spec/oauth2/strategy/assertion_spec.rb +113 -0
- data/spec/oauth2/strategy/auth_code_spec.rb +108 -0
- data/spec/oauth2/strategy/base_spec.rb +7 -0
- data/spec/oauth2/strategy/client_credentials_spec.rb +71 -0
- data/spec/oauth2/strategy/implicit_spec.rb +28 -0
- data/spec/oauth2/strategy/password_spec.rb +58 -0
- data/spec/oauth2/version_spec.rb +23 -0
- metadata +57 -95
- data/.document +0 -5
- data/.gitignore +0 -19
- data/.jrubyrc +0 -1
- data/.rspec +0 -2
- data/.rubocop.yml +0 -80
- data/.rubocop_rspec.yml +0 -26
- data/.rubocop_todo.yml +0 -15
- data/.ruby-version +0 -1
- data/.travis.yml +0 -70
- data/CONTRIBUTING.md +0 -18
- data/Gemfile +0 -40
- data/Rakefile +0 -45
- data/gemfiles/jruby_1.7.gemfile +0 -11
- data/gemfiles/jruby_9.0.gemfile +0 -7
- data/gemfiles/jruby_9.1.gemfile +0 -3
- data/gemfiles/jruby_9.2.gemfile +0 -3
- data/gemfiles/jruby_head.gemfile +0 -3
- data/gemfiles/ruby_1.9.gemfile +0 -11
- data/gemfiles/ruby_2.0.gemfile +0 -6
- data/gemfiles/ruby_2.1.gemfile +0 -6
- data/gemfiles/ruby_2.2.gemfile +0 -3
- data/gemfiles/ruby_2.3.gemfile +0 -3
- data/gemfiles/ruby_2.4.gemfile +0 -3
- data/gemfiles/ruby_2.5.gemfile +0 -3
- data/gemfiles/ruby_2.6.gemfile +0 -9
- data/gemfiles/ruby_head.gemfile +0 -9
- data/gemfiles/truffleruby.gemfile +0 -3
- data/oauth2.gemspec +0 -44
data/lib/oauth2/client.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
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
|
10
|
+
RESERVED_PARAM_KEYS = %w[headers parse].freeze
|
11
|
+
|
7
12
|
attr_reader :id, :secret, :site
|
8
13
|
attr_accessor :options
|
9
14
|
attr_writer :connection
|
@@ -14,17 +19,18 @@ module OAuth2
|
|
14
19
|
#
|
15
20
|
# @param [String] client_id the client_id value
|
16
21
|
# @param [String] client_secret the client_secret value
|
17
|
-
# @param [Hash]
|
18
|
-
# @option
|
19
|
-
# @option
|
20
|
-
# @option
|
21
|
-
# @option
|
22
|
-
# @option
|
23
|
-
# @option
|
24
|
-
# @option
|
25
|
-
# @option
|
26
|
-
# @option
|
27
|
-
#
|
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
|
28
34
|
# @yield [builder] The Faraday connection builder
|
29
35
|
def initialize(client_id, client_secret, options = {}, &block)
|
30
36
|
opts = options.dup
|
@@ -32,20 +38,22 @@ module OAuth2
|
|
32
38
|
@secret = client_secret
|
33
39
|
@site = opts.delete(:site)
|
34
40
|
ssl = opts.delete(:ssl)
|
35
|
-
@options = {:authorize_url
|
36
|
-
:token_url
|
37
|
-
:token_method
|
38
|
-
:auth_scheme
|
39
|
-
:connection_opts
|
41
|
+
@options = {:authorize_url => 'oauth/authorize',
|
42
|
+
:token_url => 'oauth/token',
|
43
|
+
:token_method => :post,
|
44
|
+
:auth_scheme => :request_body,
|
45
|
+
:connection_opts => {},
|
40
46
|
:connection_build => block,
|
41
|
-
:max_redirects
|
42
|
-
:raise_errors
|
47
|
+
:max_redirects => 5,
|
48
|
+
:raise_errors => true,
|
49
|
+
:extract_access_token => DEFAULT_EXTRACT_ACCESS_TOKEN, # DEPRECATED
|
50
|
+
:logger => ::Logger.new($stdout)}.merge(opts)
|
43
51
|
@options[:connection_opts][:ssl] = ssl if ssl
|
44
52
|
end
|
45
53
|
|
46
54
|
# Set the site host
|
47
55
|
#
|
48
|
-
# @param [String] the OAuth2 provider site host
|
56
|
+
# @param value [String] the OAuth2 provider site host
|
49
57
|
def site=(value)
|
50
58
|
@connection = nil
|
51
59
|
@site = value
|
@@ -53,15 +61,16 @@ module OAuth2
|
|
53
61
|
|
54
62
|
# The Faraday connection object
|
55
63
|
def connection
|
56
|
-
@connection ||=
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
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
|
61
72
|
end
|
62
73
|
end
|
63
|
-
conn
|
64
|
-
end
|
65
74
|
end
|
66
75
|
|
67
76
|
# The authorize endpoint URL of the OAuth2 provider
|
@@ -91,14 +100,18 @@ module OAuth2
|
|
91
100
|
# code response for this request. Will default to client option
|
92
101
|
# @option opts [Symbol] :parse @see Response::initialize
|
93
102
|
# @yield [req] The Faraday request
|
94
|
-
def request(verb, url, opts = {}) # rubocop:disable
|
95
|
-
|
96
|
-
|
97
|
-
url = connection.build_url(url, opts[:params]).to_s
|
103
|
+
def request(verb, url, opts = {}) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
104
|
+
url = connection.build_url(url).to_s
|
98
105
|
|
99
|
-
|
100
|
-
|
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
|
101
113
|
end
|
114
|
+
|
102
115
|
response = Response.new(response, :parse => opts[:parse])
|
103
116
|
|
104
117
|
case response.status
|
@@ -106,17 +119,25 @@ module OAuth2
|
|
106
119
|
opts[:redirect_count] ||= 0
|
107
120
|
opts[:redirect_count] += 1
|
108
121
|
return response if opts[:redirect_count] > options[:max_redirects]
|
122
|
+
|
109
123
|
if response.status == 303
|
110
124
|
verb = :get
|
111
125
|
opts.delete(:body)
|
112
126
|
end
|
113
|
-
|
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
|
114
134
|
when 200..299, 300..399
|
115
135
|
# on non-redirecting 3xx statuses, just return the response
|
116
136
|
response
|
117
137
|
when 400..599
|
118
138
|
error = Error.new(response)
|
119
139
|
raise(error) if opts.fetch(:raise_errors, options[:raise_errors])
|
140
|
+
|
120
141
|
response.error = error
|
121
142
|
response
|
122
143
|
else
|
@@ -127,12 +148,21 @@ module OAuth2
|
|
127
148
|
|
128
149
|
# Initializes an AccessToken by making a request to the token endpoint
|
129
150
|
#
|
130
|
-
# @param [Hash]
|
131
|
-
# @param [Hash] access token options, to pass to the AccessToken object
|
132
|
-
# @param [Class] class of access token for easier subclassing OAuth2::AccessToken
|
133
|
-
# @return [AccessToken] the
|
134
|
-
def get_token(params, access_token_opts = {},
|
135
|
-
params =
|
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
|
154
|
+
# @return [AccessToken] the initialized AccessToken
|
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
|
156
|
+
params = params.map do |key, value|
|
157
|
+
if RESERVED_PARAM_KEYS.include?(key)
|
158
|
+
[key.to_sym, value]
|
159
|
+
else
|
160
|
+
[key, value]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
params = Hash[params]
|
164
|
+
|
165
|
+
params = authenticator.apply(params)
|
136
166
|
opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
|
137
167
|
headers = params.delete(:headers) || {}
|
138
168
|
if options[:token_method] == :post
|
@@ -142,39 +172,55 @@ module OAuth2
|
|
142
172
|
opts[:params] = params
|
143
173
|
opts[:headers] = {}
|
144
174
|
end
|
145
|
-
opts[:headers].merge
|
146
|
-
|
147
|
-
|
175
|
+
opts[:headers] = opts[:headers].merge(headers)
|
176
|
+
http_method = options[:token_method]
|
177
|
+
response = request(http_method, token_url, opts)
|
178
|
+
|
179
|
+
access_token = begin
|
180
|
+
build_access_token(response, access_token_opts, extract_access_token)
|
181
|
+
rescue StandardError
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
|
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
|
148
191
|
error = Error.new(response)
|
149
192
|
raise(error)
|
193
|
+
elsif !response_contains_token
|
194
|
+
return nil
|
150
195
|
end
|
151
|
-
|
196
|
+
|
197
|
+
access_token
|
152
198
|
end
|
153
199
|
|
154
200
|
# The Authorization Code strategy
|
155
201
|
#
|
156
|
-
# @see http://
|
202
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
|
157
203
|
def auth_code
|
158
204
|
@auth_code ||= OAuth2::Strategy::AuthCode.new(self)
|
159
205
|
end
|
160
206
|
|
161
207
|
# The Implicit strategy
|
162
208
|
#
|
163
|
-
# @see http://
|
209
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2
|
164
210
|
def implicit
|
165
211
|
@implicit ||= OAuth2::Strategy::Implicit.new(self)
|
166
212
|
end
|
167
213
|
|
168
214
|
# The Resource Owner Password Credentials strategy
|
169
215
|
#
|
170
|
-
# @see http://
|
216
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3
|
171
217
|
def password
|
172
218
|
@password ||= OAuth2::Strategy::Password.new(self)
|
173
219
|
end
|
174
220
|
|
175
221
|
# The Client Credentials strategy
|
176
222
|
#
|
177
|
-
# @see http://
|
223
|
+
# @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4
|
178
224
|
def client_credentials
|
179
225
|
@client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self)
|
180
226
|
end
|
@@ -194,10 +240,10 @@ module OAuth2
|
|
194
240
|
#
|
195
241
|
# @api semipublic
|
196
242
|
#
|
197
|
-
# @see https://
|
198
|
-
# @see https://
|
199
|
-
# @see https://
|
200
|
-
# @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
|
201
247
|
# @return [Hash] the params to add to a request or URL
|
202
248
|
def redirection_params
|
203
249
|
if options[:redirect_uri]
|
@@ -206,5 +252,41 @@ module OAuth2
|
|
206
252
|
{}
|
207
253
|
end
|
208
254
|
end
|
255
|
+
|
256
|
+
DEFAULT_EXTRACT_ACCESS_TOKEN = proc do |client, hash|
|
257
|
+
token = hash.delete('access_token') || hash.delete(:access_token)
|
258
|
+
token && AccessToken.new(client, token, hash)
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
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
|
273
|
+
def build_access_token(response, access_token_opts, extract_access_token)
|
274
|
+
parsed_response = response.parsed.dup
|
275
|
+
return unless parsed_response.is_a?(Hash)
|
276
|
+
|
277
|
+
hash = parsed_response.merge(access_token_opts)
|
278
|
+
|
279
|
+
# Provide backwards compatibility for old AccessToken.form_hash pattern
|
280
|
+
# Will be deprecated in 2.x
|
281
|
+
if extract_access_token.is_a?(Class) && extract_access_token.respond_to?(:from_hash)
|
282
|
+
extract_access_token.from_hash(self, hash)
|
283
|
+
else
|
284
|
+
extract_access_token.call(self, hash)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def oauth_debug_logging(builder)
|
289
|
+
builder.response :logger, options[:logger], :bodies => true if ENV['OAUTH_DEBUG'] == 'true'
|
290
|
+
end
|
209
291
|
end
|
210
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'
|
@@ -95,16 +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
|
-
|
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
|
108
116
|
end
|
109
117
|
|
110
118
|
private
|
data/lib/oauth2/response.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'multi_json'
|
2
4
|
require 'multi_xml'
|
3
5
|
require 'rack'
|
@@ -11,9 +13,9 @@ module OAuth2
|
|
11
13
|
# Procs that, when called, will parse a response body according
|
12
14
|
# to the specified format.
|
13
15
|
@@parsers = {
|
14
|
-
:json
|
16
|
+
:json => lambda { |body| MultiJson.load(body) rescue body }, # rubocop:disable Style/RescueModifier
|
15
17
|
:query => lambda { |body| Rack::Utils.parse_query(body) },
|
16
|
-
:text
|
18
|
+
:text => lambda { |body| body },
|
17
19
|
}
|
18
20
|
|
19
21
|
# Content type assignments for various potential HTTP content types.
|
@@ -68,6 +70,7 @@ module OAuth2
|
|
68
70
|
# application/json Content-Type response bodies
|
69
71
|
def parsed
|
70
72
|
return nil unless @@parsers.key?(parser)
|
73
|
+
|
71
74
|
@parsed ||= @@parsers[parser].call(body)
|
72
75
|
end
|
73
76
|
|
@@ -79,11 +82,12 @@ module OAuth2
|
|
79
82
|
# Determines the parser that will be used to supply the content of #parsed
|
80
83
|
def parser
|
81
84
|
return options[:parse].to_sym if @@parsers.key?(options[:parse])
|
85
|
+
|
82
86
|
@@content_types[content_type]
|
83
87
|
end
|
84
88
|
end
|
85
89
|
end
|
86
90
|
|
87
91
|
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
|
92
|
+
MultiXml.parse(body) rescue body # rubocop:disable Style/RescueModifier
|
89
93
|
end
|
@@ -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,
|
@@ -50,10 +52,10 @@ module OAuth2
|
|
50
52
|
def build_request(params)
|
51
53
|
assertion = build_assertion(params)
|
52
54
|
{
|
53
|
-
:grant_type
|
55
|
+
:grant_type => 'assertion',
|
54
56
|
:assertion_type => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
55
|
-
:assertion
|
56
|
-
:scope
|
57
|
+
:assertion => assertion,
|
58
|
+
:scope => params[:scope],
|
57
59
|
}
|
58
60
|
end
|
59
61
|
|
@@ -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
|
#
|
@@ -18,8 +20,8 @@ module OAuth2
|
|
18
20
|
# @param [Hash] params additional params
|
19
21
|
def get_token(username, password, params = {}, opts = {})
|
20
22
|
params = {'grant_type' => 'password',
|
21
|
-
'username'
|
22
|
-
'password'
|
23
|
+
'username' => username,
|
24
|
+
'password' => password}.merge(params)
|
23
25
|
@client.get_token(params, opts)
|
24
26
|
end
|
25
27
|
end
|
data/lib/oauth2/version.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
module Version
|
5
|
+
VERSION = to_s
|
6
|
+
|
3
7
|
module_function
|
4
8
|
|
5
9
|
# The major version
|
@@ -20,12 +24,12 @@ module OAuth2
|
|
20
24
|
#
|
21
25
|
# @return [Integer]
|
22
26
|
def patch
|
23
|
-
|
27
|
+
9
|
24
28
|
end
|
25
29
|
|
26
30
|
# The pre-release version, if any
|
27
31
|
#
|
28
|
-
# @return [
|
32
|
+
# @return [String, NilClass]
|
29
33
|
def pre
|
30
34
|
nil
|
31
35
|
end
|
@@ -53,7 +57,9 @@ module OAuth2
|
|
53
57
|
#
|
54
58
|
# @return [String]
|
55
59
|
def to_s
|
56
|
-
|
60
|
+
v = [major, minor, patch].compact.join('.')
|
61
|
+
v += "-#{pre}" if pre
|
62
|
+
v
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|
data/lib/oauth2.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
# RS256
|
2
|
+
|
3
|
+
## How keys were made
|
4
|
+
|
5
|
+
```shell
|
6
|
+
# No passphrase
|
7
|
+
# Generates the public and private keys:
|
8
|
+
ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
|
9
|
+
# Converts the key to PEM format
|
10
|
+
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
|
11
|
+
```
|
@@ -0,0 +1,51 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
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
|
51
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,14 @@
|
|
1
|
+
-----BEGIN PUBLIC KEY-----
|
2
|
+
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5hdXV/4YSymY1T9VNvK2
|
3
|
+
bWRfulwIty1RnAPNINQmfh3aRRkV+PNrbC2Crji9G0AHmQwgW1bZ3kgkkpIm6RVn
|
4
|
+
44fHvBvuXkZ9ABgXw0d2cLIHmwOFxSKmWAm/EW//GszUTLLLsMZUe2udtFJW0jxX
|
5
|
+
B2GRY0WVYuo6Oo58RCeP719lw3Ags0YF9/IobxKkGd4BautUPw6ZszAa3o+j0zR7
|
6
|
+
4x7ouPxybZAOuPsMxqanyeYJeH4osJjLMYV9qem9uG2sj7GENJ8UszcpmGbqxBhe
|
7
|
+
xPEB7mgDeONIF0XJF23zdOf8ANE5mAU2h2v7M6moAfkdUzJ+j48+VT2omHAzAL5y
|
8
|
+
Ncmrl2xiWdyoxOw1Y1UmfEmJYV5VgGYyZ12JZRKY+szPT+vR+MDuYxbquF40O7kv
|
9
|
+
kFNBfL1yCpzfSQCLnEs4rX8qRzZXciLeyq4Ht5FLuRFgxjA//XI8LAmp0u7gk+Q7
|
10
|
+
FUH1UgW3kmJDTG0XaxQxYTBSIO7mcmyjDyBgKVuQmt5E1ycFeteOVdPD/CG/fPYh
|
11
|
+
thvc4UytEFwsMdNy3iD6/wuUH68tAKam28UZaOb0qK+00cQQD8fulY9rKtSL10Lv
|
12
|
+
JFWUOa/SJyLvk9vUmfvFn182il1nX6GpyxyMmE/FCnH4CT/DjrSZf08mOO8eL5of
|
13
|
+
YHMK/oiXr1eODqx+pOwClNsCAwEAAQ==
|
14
|
+
-----END PUBLIC KEY-----
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
DEBUG = ENV['DEBUG'] == 'true'
|
4
|
+
RUN_COVERAGE = ENV['CI_CODECOV'] || ENV['CI'].nil?
|
5
|
+
|
6
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
7
|
+
minimum_version = ->(version) { ruby_version >= Gem::Version.new(version) && RUBY_ENGINE == 'ruby' }
|
8
|
+
coverage = minimum_version.call('2.7') && RUN_COVERAGE
|
9
|
+
debug = minimum_version.call('2.5') && DEBUG
|
10
|
+
|
11
|
+
require 'simplecov' if coverage
|
12
|
+
require 'byebug' if debug
|
13
|
+
|
14
|
+
require 'oauth2'
|
15
|
+
require 'addressable/uri'
|
16
|
+
require 'rspec'
|
17
|
+
require 'rspec/stubbed_env'
|
18
|
+
require 'rspec/pending_for'
|
19
|
+
require 'silent_stream'
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.expect_with :rspec do |c|
|
23
|
+
c.syntax = :expect
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Faraday.default_adapter = :test
|
28
|
+
|
29
|
+
RSpec.configure do |conf|
|
30
|
+
conf.include SilentStream
|
31
|
+
end
|
32
|
+
|
33
|
+
VERBS = [:get, :post, :put, :delete].freeze
|