rack-oauth2 1.15.0 → 2.2.0
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/.github/FUNDING.yml +3 -0
- data/.github/workflows/spec.yml +32 -0
- data/CHANGELOG.md +25 -0
- data/README.rdoc +1 -20
- data/VERSION +1 -1
- data/lib/rack/oauth2/access_token/authenticator.rb +1 -10
- data/lib/rack/oauth2/access_token/bearer.rb +1 -1
- data/lib/rack/oauth2/access_token/mtls.rb +2 -2
- data/lib/rack/oauth2/access_token.rb +4 -6
- data/lib/rack/oauth2/client.rb +93 -40
- data/lib/rack/oauth2/server/abstract/error.rb +2 -1
- data/lib/rack/oauth2/server/extension/pkce.rb +1 -1
- data/lib/rack/oauth2/server/rails/response_ext.rb +3 -3
- data/lib/rack/oauth2/server/resource/error.rb +4 -4
- data/lib/rack/oauth2/server/resource.rb +0 -1
- data/lib/rack/oauth2/server/token/error.rb +3 -1
- data/lib/rack/oauth2/server/token.rb +16 -5
- data/lib/rack/oauth2/util.rb +4 -4
- data/lib/rack/oauth2.rb +11 -10
- data/rack-oauth2.gemspec +6 -4
- data/spec/helpers/webmock_helper.rb +8 -2
- data/spec/rack/oauth2/access_token/authenticator_spec.rb +2 -22
- data/spec/rack/oauth2/access_token/bearer_spec.rb +2 -2
- data/spec/rack/oauth2/access_token_spec.rb +0 -17
- data/spec/rack/oauth2/client_spec.rb +152 -74
- data/spec/rack/oauth2/oauth2_spec.rb +0 -43
- data/spec/rack/oauth2/server/authorize/error_spec.rb +6 -6
- data/spec/rack/oauth2/server/resource/bearer/error_spec.rb +2 -2
- data/spec/rack/oauth2/server/resource/bearer_spec.rb +9 -9
- data/spec/rack/oauth2/server/resource/error_spec.rb +14 -14
- data/spec/rack/oauth2/server/token/authorization_code_spec.rb +2 -2
- data/spec/rack/oauth2/server/token/client_credentials_spec.rb +32 -2
- data/spec/rack/oauth2/server/token/error_spec.rb +5 -5
- data/spec/rack/oauth2/server/token_spec.rb +71 -2
- data/spec/rack/oauth2/util_spec.rb +6 -6
- metadata +43 -47
- data/.travis.yml +0 -7
- data/lib/rack/oauth2/access_token/legacy.rb +0 -19
- data/lib/rack/oauth2/access_token/mac/sha256_hex_verifier.rb +0 -17
- data/lib/rack/oauth2/access_token/mac/signature.rb +0 -34
- data/lib/rack/oauth2/access_token/mac/verifier.rb +0 -44
- data/lib/rack/oauth2/access_token/mac.rb +0 -103
- data/lib/rack/oauth2/debugger/request_filter.rb +0 -30
- data/lib/rack/oauth2/debugger.rb +0 -3
- data/lib/rack/oauth2/server/resource/mac/error.rb +0 -24
- data/lib/rack/oauth2/server/resource/mac.rb +0 -36
- data/spec/mock_response/tokens/legacy.json +0 -5
- data/spec/mock_response/tokens/legacy.txt +0 -1
- data/spec/mock_response/tokens/legacy_without_expires_in.txt +0 -1
- data/spec/mock_response/tokens/mac.json +0 -8
- data/spec/rack/oauth2/access_token/legacy_spec.rb +0 -23
- data/spec/rack/oauth2/access_token/mac/sha256_hex_verifier_spec.rb +0 -28
- data/spec/rack/oauth2/access_token/mac/signature_spec.rb +0 -59
- data/spec/rack/oauth2/access_token/mac/verifier_spec.rb +0 -25
- data/spec/rack/oauth2/access_token/mac_spec.rb +0 -141
- data/spec/rack/oauth2/debugger/request_filter_spec.rb +0 -33
- data/spec/rack/oauth2/server/resource/mac/error_spec.rb +0 -52
- data/spec/rack/oauth2/server/resource/mac_spec.rb +0 -119
- /data/spec/mock_response/{blank → blank.txt} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 45ba67ac4566f374465673cc5711e71c15006bbe966531a4c1de2473206879b2
|
|
4
|
+
data.tar.gz: 56f8718f283533c369b1743dfd86499e49e5d828a83ac060fa919fac57a935d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63316467536c2c98cddea9b2b7907b3ff5fd6b53b892bd338709e1f7a6b014aa4dc20d71b12cd01ffac502c1ab0964218aac7ff6a0e81141ff8aa10e80557cdd
|
|
7
|
+
data.tar.gz: 97e685531853c4837a0e86636c865827033e25f646c4572d254e2584a811f937faa6dc7fe780742814bd9657066c9fc16394723ba87029605761d5acf2d490f7
|
data/.github/FUNDING.yml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Spec
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
spec:
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
os: ['ubuntu-20.04']
|
|
17
|
+
ruby-version: ['2.6', '2.7', '3.0', '3.1']
|
|
18
|
+
# ubuntu 22.04 only supports ssl 3 and thus only ruby 3.1
|
|
19
|
+
include:
|
|
20
|
+
- os: 'ubuntu-22.04'
|
|
21
|
+
ruby-version: '3.1'
|
|
22
|
+
runs-on: ${{ matrix.os }}
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v3
|
|
26
|
+
- name: Set up Ruby
|
|
27
|
+
uses: ruby/setup-ruby@v1
|
|
28
|
+
with:
|
|
29
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
30
|
+
bundler-cache: true
|
|
31
|
+
- name: Run Specs
|
|
32
|
+
run: bundle exec rake spec
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [2.1.0] - 2022-10-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- accept local_http_config on Rack::OAuth2::Client#access_token! & revoke! to support custom headers etc. by @nov in https://github.com/nov/rack-oauth2/pull/93
|
|
8
|
+
|
|
9
|
+
## [2.0.1] - 2022-10-09
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- changes for mTLS on faraday by @nov in https://github.com/nov/rack-oauth2/pull/92
|
|
14
|
+
|
|
15
|
+
## [2.0.0] - 2022-10-09
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- start recording CHANGELOG
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Switch from httpclient to faraday v2 https://github.com/nov/rack-oauth2/pull/91
|
|
24
|
+
- make url-encoded the default https://github.com/nov/rack-oauth2/commit/98faf139be4f5bf9ec6134d31f65a298259d8a8b
|
|
25
|
+
- let faraday encode params https://github.com/nov/rack-oauth2/commit/f399b3afb8facb3635b8842baee8584bc4d3ce73
|
data/README.rdoc
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
= rack-oauth2
|
|
2
2
|
|
|
3
3
|
OAuth 2.0 Server & Client Library.
|
|
4
|
-
Both Bearer
|
|
5
|
-
|
|
6
|
-
{<img src="https://secure.travis-ci.org/nov/rack-oauth2.png" />}[http://travis-ci.org/nov/rack-oauth2]
|
|
4
|
+
Both Bearer token type are supported.
|
|
7
5
|
|
|
8
6
|
The OAuth 2.0 Authorization Framework (RFC 6749)
|
|
9
7
|
http://www.rfc-editor.org/rfc/rfc6749.txt
|
|
@@ -11,9 +9,6 @@ http://www.rfc-editor.org/rfc/rfc6749.txt
|
|
|
11
9
|
The OAuth 2.0 Authorization Framework: Bearer Token Usage (RFC 6750)
|
|
12
10
|
http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-06
|
|
13
11
|
|
|
14
|
-
HTTP Authentication: MAC Access Authentication (draft 01)
|
|
15
|
-
http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
|
16
|
-
|
|
17
12
|
== Installation
|
|
18
13
|
|
|
19
14
|
gem install rack-oauth2
|
|
@@ -31,31 +26,17 @@ http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
|
|
31
26
|
Source on GitHub
|
|
32
27
|
https://github.com/nov/rack-oauth2-sample
|
|
33
28
|
|
|
34
|
-
=== MAC
|
|
35
|
-
|
|
36
|
-
Source on GitHub
|
|
37
|
-
https://github.com/nov/rack-oauth2-sample-mac
|
|
38
|
-
|
|
39
29
|
== Sample Client
|
|
40
30
|
|
|
41
|
-
=== Common between Bearer and MAC
|
|
42
|
-
|
|
43
31
|
Authorization Request (request_type: 'code' and 'token')
|
|
44
32
|
https://gist.github.com/862393
|
|
45
33
|
|
|
46
34
|
Token Request (grant_type: 'client_credentials', 'password', 'authorization_code' and 'refresh_token')
|
|
47
35
|
https://gist.github.com/883541
|
|
48
36
|
|
|
49
|
-
=== Bearer
|
|
50
|
-
|
|
51
37
|
Resource Request (request both for resource owner resource and for client resource)
|
|
52
38
|
https://gist.github.com/883575
|
|
53
39
|
|
|
54
|
-
=== MAC
|
|
55
|
-
|
|
56
|
-
Resource Request (request both for resource owner resource and for client resource)
|
|
57
|
-
https://gist.github.com/933885
|
|
58
|
-
|
|
59
40
|
== Note on Patches/Pull Requests
|
|
60
41
|
|
|
61
42
|
* Fork the project.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2.2.0
|
|
@@ -6,18 +6,9 @@ module Rack
|
|
|
6
6
|
@token = token
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
# request:: HTTP::Message
|
|
11
|
-
def filter_request(request)
|
|
9
|
+
def authenticate(request)
|
|
12
10
|
@token.authenticate(request)
|
|
13
11
|
end
|
|
14
|
-
|
|
15
|
-
# Callback called in HTTPClient (after received a response)
|
|
16
|
-
# response:: HTTP::Message
|
|
17
|
-
# request:: HTTP::Message
|
|
18
|
-
def filter_response(response, request)
|
|
19
|
-
# nothing to do
|
|
20
|
-
end
|
|
21
12
|
end
|
|
22
13
|
end
|
|
23
14
|
end
|
|
@@ -7,8 +7,8 @@ module Rack
|
|
|
7
7
|
def initialize(attributes = {})
|
|
8
8
|
super
|
|
9
9
|
self.token_type = :bearer
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
http_client.ssl.client_key = private_key
|
|
11
|
+
http_client.ssl.client_cert = certificate
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -5,7 +5,7 @@ module Rack
|
|
|
5
5
|
attr_required :access_token, :token_type
|
|
6
6
|
attr_optional :refresh_token, :expires_in, :scope
|
|
7
7
|
attr_accessor :raw_attributes
|
|
8
|
-
delegate :get, :patch, :post, :put, :delete, to: :
|
|
8
|
+
delegate :get, :patch, :post, :put, :delete, to: :http_client
|
|
9
9
|
|
|
10
10
|
alias_method :to_s, :access_token
|
|
11
11
|
|
|
@@ -18,9 +18,9 @@ module Rack
|
|
|
18
18
|
attr_missing!
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
@
|
|
23
|
-
|
|
21
|
+
def http_client
|
|
22
|
+
@http_client ||= Rack::OAuth2.http_client("#{self.class} (#{VERSION})") do |faraday|
|
|
23
|
+
Authenticator.new(self).authenticate(faraday)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -39,6 +39,4 @@ end
|
|
|
39
39
|
|
|
40
40
|
require 'rack/oauth2/access_token/authenticator'
|
|
41
41
|
require 'rack/oauth2/access_token/bearer'
|
|
42
|
-
require 'rack/oauth2/access_token/mac'
|
|
43
|
-
require 'rack/oauth2/access_token/legacy'
|
|
44
42
|
require 'rack/oauth2/access_token/mtls'
|
data/lib/rack/oauth2/client.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Rack
|
|
|
3
3
|
class Client
|
|
4
4
|
include AttrRequired, AttrOptional
|
|
5
5
|
attr_required :identifier
|
|
6
|
-
attr_optional :secret, :private_key, :certificate, :redirect_uri, :scheme, :host, :port, :authorization_endpoint, :token_endpoint
|
|
6
|
+
attr_optional :secret, :private_key, :certificate, :redirect_uri, :scheme, :host, :port, :authorization_endpoint, :token_endpoint, :revocation_endpoint
|
|
7
7
|
|
|
8
8
|
def initialize(attributes = {})
|
|
9
9
|
(required_attributes + optional_attributes).each do |key|
|
|
@@ -69,35 +69,101 @@ module Rack
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def access_token!(*args)
|
|
72
|
-
headers, params =
|
|
72
|
+
headers, params, http_client, options = authenticated_context_from(*args)
|
|
73
|
+
params[:scope] = Array(options.delete(:scope)).join(' ') if options[:scope].present?
|
|
74
|
+
params.merge! @grant.as_json
|
|
75
|
+
params.merge! options
|
|
76
|
+
handle_response do
|
|
77
|
+
http_client.post(
|
|
78
|
+
absolute_uri_for(token_endpoint),
|
|
79
|
+
Util.compact_hash(params),
|
|
80
|
+
headers
|
|
81
|
+
) do |req|
|
|
82
|
+
yield req if block_given?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def revoke!(*args)
|
|
88
|
+
headers, params, http_client, options = authenticated_context_from(*args)
|
|
89
|
+
|
|
90
|
+
params.merge! case
|
|
91
|
+
when access_token = options.delete(:access_token)
|
|
92
|
+
{
|
|
93
|
+
token: access_token,
|
|
94
|
+
token_type_hint: :access_token
|
|
95
|
+
}
|
|
96
|
+
when refresh_token = options.delete(:refresh_token)
|
|
97
|
+
{
|
|
98
|
+
token: refresh_token,
|
|
99
|
+
token_type_hint: :refresh_token
|
|
100
|
+
}
|
|
101
|
+
when @grant.is_a?(Grant::RefreshToken)
|
|
102
|
+
{
|
|
103
|
+
token: @grant.refresh_token,
|
|
104
|
+
token_type_hint: :refresh_token
|
|
105
|
+
}
|
|
106
|
+
when options[:token].blank?
|
|
107
|
+
raise ArgumentError, 'One of "token", "access_token" and "refresh_token" is required'
|
|
108
|
+
end
|
|
109
|
+
params.merge! options
|
|
110
|
+
|
|
111
|
+
handle_revocation_response do
|
|
112
|
+
http_client.post(
|
|
113
|
+
absolute_uri_for(revocation_endpoint),
|
|
114
|
+
Util.compact_hash(params),
|
|
115
|
+
headers
|
|
116
|
+
) do |req|
|
|
117
|
+
yield req if block_given?
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def absolute_uri_for(endpoint)
|
|
125
|
+
_endpoint_ = Util.parse_uri endpoint
|
|
126
|
+
_endpoint_.scheme ||= self.scheme || 'https'
|
|
127
|
+
_endpoint_.host ||= self.host
|
|
128
|
+
_endpoint_.port ||= self.port
|
|
129
|
+
raise 'No Host Info' unless _endpoint_.host
|
|
130
|
+
_endpoint_.to_s
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def authenticated_context_from(*args)
|
|
134
|
+
headers, params = {}, {}
|
|
73
135
|
http_client = Rack::OAuth2.http_client
|
|
74
136
|
|
|
75
137
|
# NOTE:
|
|
76
138
|
# Using Array#extract_options! for backward compatibility.
|
|
77
139
|
# Until v1.0.5, the first argument was 'client_auth_method' in scalar.
|
|
78
140
|
options = args.extract_options!
|
|
79
|
-
client_auth_method = args.first || options.delete(:client_auth_method)
|
|
80
|
-
|
|
81
|
-
params[:scope] = Array(options.delete(:scope)).join(' ') if options[:scope].present?
|
|
82
|
-
params.merge! options
|
|
141
|
+
client_auth_method = args.first || options.delete(:client_auth_method)&.to_sym || :basic
|
|
83
142
|
|
|
84
143
|
case client_auth_method
|
|
85
144
|
when :basic
|
|
86
145
|
cred = Base64.strict_encode64 [
|
|
87
|
-
Util.
|
|
88
|
-
Util.
|
|
146
|
+
Util.www_form_url_encode(identifier),
|
|
147
|
+
Util.www_form_url_encode(secret)
|
|
89
148
|
].join(':')
|
|
90
149
|
headers.merge!(
|
|
91
150
|
'Authorization' => "Basic #{cred}"
|
|
92
151
|
)
|
|
152
|
+
when :basic_without_www_form_urlencode
|
|
153
|
+
cred = ["#{identifier}:#{secret}"].pack('m').tr("\n", '')
|
|
154
|
+
headers.merge!(
|
|
155
|
+
'Authorization' => "Basic #{cred}"
|
|
156
|
+
)
|
|
93
157
|
when :jwt_bearer
|
|
94
158
|
params.merge!(
|
|
95
159
|
client_assertion_type: URN::ClientAssertionType::JWT_BEARER
|
|
96
160
|
)
|
|
97
161
|
# NOTE: optionally auto-generate client_assertion.
|
|
98
|
-
if
|
|
162
|
+
params[:client_assertion] = if options[:client_assertion].present?
|
|
163
|
+
options.delete(:client_assertion)
|
|
164
|
+
else
|
|
99
165
|
require 'json/jwt'
|
|
100
|
-
|
|
166
|
+
JSON::JWT.new(
|
|
101
167
|
iss: identifier,
|
|
102
168
|
sub: identifier,
|
|
103
169
|
aud: absolute_uri_for(token_endpoint),
|
|
@@ -114,32 +180,16 @@ module Rack
|
|
|
114
180
|
params.merge!(
|
|
115
181
|
client_id: identifier
|
|
116
182
|
)
|
|
117
|
-
http_client.
|
|
118
|
-
http_client.
|
|
183
|
+
http_client.ssl.client_key = private_key
|
|
184
|
+
http_client.ssl.client_cert = certificate
|
|
119
185
|
else
|
|
120
186
|
params.merge!(
|
|
121
187
|
client_id: identifier,
|
|
122
188
|
client_secret: secret
|
|
123
189
|
)
|
|
124
190
|
end
|
|
125
|
-
handle_response do
|
|
126
|
-
http_client.post(
|
|
127
|
-
absolute_uri_for(token_endpoint),
|
|
128
|
-
Util.compact_hash(params),
|
|
129
|
-
headers
|
|
130
|
-
)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
private
|
|
135
191
|
|
|
136
|
-
|
|
137
|
-
_endpoint_ = Util.parse_uri endpoint
|
|
138
|
-
_endpoint_.scheme ||= self.scheme || 'https'
|
|
139
|
-
_endpoint_.host ||= self.host
|
|
140
|
-
_endpoint_.port ||= self.port
|
|
141
|
-
raise 'No Host Info' unless _endpoint_.host
|
|
142
|
-
_endpoint_.to_s
|
|
192
|
+
[headers, params, http_client, options]
|
|
143
193
|
end
|
|
144
194
|
|
|
145
195
|
def handle_response
|
|
@@ -152,27 +202,30 @@ module Rack
|
|
|
152
202
|
end
|
|
153
203
|
end
|
|
154
204
|
|
|
205
|
+
def handle_revocation_response
|
|
206
|
+
response = yield
|
|
207
|
+
case response.status
|
|
208
|
+
when 200..201
|
|
209
|
+
:success
|
|
210
|
+
else
|
|
211
|
+
handle_error_response response
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
155
215
|
def handle_success_response(response)
|
|
156
|
-
token_hash =
|
|
157
|
-
case (@forced_token_type || token_hash[:token_type])
|
|
216
|
+
token_hash = response.body.with_indifferent_access
|
|
217
|
+
case (@forced_token_type || token_hash[:token_type])&.downcase
|
|
158
218
|
when 'bearer'
|
|
159
219
|
AccessToken::Bearer.new(token_hash)
|
|
160
|
-
when 'mac'
|
|
161
|
-
AccessToken::MAC.new(token_hash)
|
|
162
|
-
when nil
|
|
163
|
-
AccessToken::Legacy.new(token_hash)
|
|
164
220
|
else
|
|
165
221
|
raise 'Unknown Token Type'
|
|
166
222
|
end
|
|
167
|
-
rescue JSON::ParserError
|
|
168
|
-
# NOTE: Facebook support (They don't use JSON as token response)
|
|
169
|
-
AccessToken::Legacy.new Rack::Utils.parse_nested_query(response.body).with_indifferent_access
|
|
170
223
|
end
|
|
171
224
|
|
|
172
225
|
def handle_error_response(response)
|
|
173
|
-
error =
|
|
226
|
+
error = response.body.with_indifferent_access
|
|
174
227
|
raise Error.new(response.status, error)
|
|
175
|
-
rescue
|
|
228
|
+
rescue Faraday::ParsingError, NoMethodError
|
|
176
229
|
raise Error.new(response.status, error: 'Unknown', error_description: response.body)
|
|
177
230
|
end
|
|
178
231
|
end
|
|
@@ -27,7 +27,7 @@ module Rack
|
|
|
27
27
|
response.status = status
|
|
28
28
|
yield response if block_given?
|
|
29
29
|
unless response.redirect?
|
|
30
|
-
response.
|
|
30
|
+
response.headers['Content-Type'] = 'application/json'
|
|
31
31
|
response.write Util.compact_hash(protocol_params).to_json
|
|
32
32
|
end
|
|
33
33
|
response.finish
|
|
@@ -42,6 +42,7 @@ module Rack
|
|
|
42
42
|
|
|
43
43
|
class Unauthorized < Error
|
|
44
44
|
def initialize(error = :unauthorized, description = nil, options = {})
|
|
45
|
+
@skip_www_authenticate = options[:skip_www_authenticate]
|
|
45
46
|
super 401, error, description, options
|
|
46
47
|
end
|
|
47
48
|
end
|
|
@@ -27,7 +27,7 @@ module Rack
|
|
|
27
27
|
|
|
28
28
|
def verify_code_verifier!(code_challenge, code_challenge_method = :S256)
|
|
29
29
|
if code_verifier.present? || code_challenge.present?
|
|
30
|
-
case code_challenge_method
|
|
30
|
+
case code_challenge_method&.to_sym
|
|
31
31
|
when :S256
|
|
32
32
|
code_challenge == Util.urlsafe_base64_encode(
|
|
33
33
|
OpenSSL::Digest::SHA256.digest(code_verifier.to_s)
|
|
@@ -21,9 +21,9 @@ module Rack
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def headers
|
|
25
25
|
ensure_finish do
|
|
26
|
-
@
|
|
26
|
+
@headers
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -39,7 +39,7 @@ module Rack
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def ensure_finish
|
|
42
|
-
@status, @
|
|
42
|
+
@status, @headers, @body = finish unless finished?
|
|
43
43
|
yield
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -13,11 +13,11 @@ module Rack
|
|
|
13
13
|
def finish
|
|
14
14
|
super do |response|
|
|
15
15
|
self.realm ||= DEFAULT_REALM
|
|
16
|
-
|
|
16
|
+
headers = response.headers['WWW-Authenticate'] = "#{scheme} realm=\"#{realm}\""
|
|
17
17
|
if ErrorMethods::DEFAULT_DESCRIPTION.keys.include?(error)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
headers << ", error=\"#{error}\""
|
|
19
|
+
headers << ", error_description=\"#{description}\"" if description.present?
|
|
20
|
+
headers << ", error_uri=\"#{uri}\"" if uri.present?
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -8,7 +8,9 @@ module Rack
|
|
|
8
8
|
class Unauthorized < Abstract::Unauthorized
|
|
9
9
|
def finish
|
|
10
10
|
super do |response|
|
|
11
|
-
|
|
11
|
+
unless @skip_www_authenticate
|
|
12
|
+
response.headers['WWW-Authenticate'] = 'Basic realm="OAuth2 Token Endpoint"'
|
|
13
|
+
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
end
|
|
@@ -44,16 +44,27 @@ module Rack
|
|
|
44
44
|
|
|
45
45
|
class Request < Abstract::Request
|
|
46
46
|
attr_required :grant_type
|
|
47
|
-
attr_optional :client_secret
|
|
47
|
+
attr_optional :client_secret, :client_assertion, :client_assertion_type
|
|
48
48
|
|
|
49
49
|
def initialize(env)
|
|
50
50
|
auth = Rack::Auth::Basic::Request.new(env)
|
|
51
51
|
if auth.provided? && auth.basic?
|
|
52
|
-
@client_id, @client_secret = auth.credentials
|
|
52
|
+
@client_id, @client_secret = auth.credentials.map do |cred|
|
|
53
|
+
Util.www_form_url_decode cred
|
|
54
|
+
end
|
|
53
55
|
super
|
|
54
56
|
else
|
|
55
57
|
super
|
|
56
58
|
@client_secret = params['client_secret']
|
|
59
|
+
@client_assertion = params['client_assertion']
|
|
60
|
+
@client_assertion_type = params['client_assertion_type']
|
|
61
|
+
if client_assertion.present? && client_assertion_type == URN::ClientAssertionType::JWT_BEARER
|
|
62
|
+
require 'json/jwt'
|
|
63
|
+
@client_id = JSON::JWT.decode(
|
|
64
|
+
client_assertion,
|
|
65
|
+
:skip_verification
|
|
66
|
+
)[:sub] rescue nil
|
|
67
|
+
end
|
|
57
68
|
end
|
|
58
69
|
@grant_type = params['grant_type'].to_s
|
|
59
70
|
end
|
|
@@ -69,9 +80,9 @@ module Rack
|
|
|
69
80
|
def finish
|
|
70
81
|
attr_missing!
|
|
71
82
|
write Util.compact_hash(protocol_params).to_json
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
headers['Content-Type'] = 'application/json'
|
|
84
|
+
headers['Cache-Control'] = 'no-store'
|
|
85
|
+
headers['Pragma'] = 'no-cache'
|
|
75
86
|
super
|
|
76
87
|
end
|
|
77
88
|
end
|
data/lib/rack/oauth2/util.rb
CHANGED
|
@@ -4,12 +4,12 @@ module Rack
|
|
|
4
4
|
module OAuth2
|
|
5
5
|
module Util
|
|
6
6
|
class << self
|
|
7
|
-
def
|
|
8
|
-
URI.
|
|
7
|
+
def www_form_url_encode(text)
|
|
8
|
+
URI.encode_www_form_component(text)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def
|
|
12
|
-
URI.
|
|
11
|
+
def www_form_url_decode(text)
|
|
12
|
+
URI.decode_www_form_component(text)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def base64_encode(text)
|
data/lib/rack/oauth2.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'rack'
|
|
2
|
-
require '
|
|
2
|
+
require 'faraday'
|
|
3
|
+
require 'faraday/follow_redirects'
|
|
3
4
|
require 'logger'
|
|
4
5
|
require 'active_support'
|
|
5
6
|
require 'active_support/core_ext'
|
|
@@ -40,13 +41,15 @@ module Rack
|
|
|
40
41
|
self.debugging = false
|
|
41
42
|
|
|
42
43
|
def self.http_client(agent_name = "Rack::OAuth2 (#{VERSION})", &local_http_config)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
Faraday.new(headers: {user_agent: agent_name}) do |faraday|
|
|
45
|
+
faraday.request :url_encoded
|
|
46
|
+
faraday.request :json
|
|
47
|
+
faraday.response :json
|
|
48
|
+
faraday.response :logger, Rack::OAuth2.logger, {bodies: true} if debugging?
|
|
49
|
+
faraday.adapter Faraday.default_adapter
|
|
50
|
+
local_http_config&.call(faraday)
|
|
51
|
+
http_config&.call(faraday)
|
|
52
|
+
end
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
def self.http_config(&block)
|
|
@@ -56,7 +59,6 @@ module Rack
|
|
|
56
59
|
def self.reset_http_config!
|
|
57
60
|
@@http_config = nil
|
|
58
61
|
end
|
|
59
|
-
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
|
|
@@ -65,4 +67,3 @@ require 'rack/oauth2/util'
|
|
|
65
67
|
require 'rack/oauth2/server'
|
|
66
68
|
require 'rack/oauth2/client'
|
|
67
69
|
require 'rack/oauth2/access_token'
|
|
68
|
-
require 'rack/oauth2/debugger'
|
data/rack-oauth2.gemspec
CHANGED
|
@@ -2,19 +2,20 @@ Gem::Specification.new do |s|
|
|
|
2
2
|
s.name = 'rack-oauth2'
|
|
3
3
|
s.version = File.read('VERSION')
|
|
4
4
|
s.authors = ['nov matake']
|
|
5
|
-
s.description = %q{OAuth 2.0 Server & Client Library. Both Bearer
|
|
6
|
-
s.summary = %q{OAuth 2.0 Server & Client Library - Both Bearer
|
|
5
|
+
s.description = %q{OAuth 2.0 Server & Client Library. Both Bearer token type are supported.}
|
|
6
|
+
s.summary = %q{OAuth 2.0 Server & Client Library - Both Bearer token type are supported}
|
|
7
7
|
s.email = 'nov@matake.jp'
|
|
8
8
|
s.extra_rdoc_files = ['LICENSE', 'README.rdoc']
|
|
9
9
|
s.rdoc_options = ['--charset=UTF-8']
|
|
10
|
-
s.homepage = '
|
|
10
|
+
s.homepage = 'https://github.com/nov/rack-oauth2'
|
|
11
11
|
s.license = 'MIT'
|
|
12
12
|
s.require_paths = ['lib']
|
|
13
13
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
14
14
|
s.files = `git ls-files`.split("\n")
|
|
15
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
16
16
|
s.add_runtime_dependency 'rack', '>= 2.1.0'
|
|
17
|
-
s.add_runtime_dependency '
|
|
17
|
+
s.add_runtime_dependency 'faraday', '~> 2.0'
|
|
18
|
+
s.add_runtime_dependency 'faraday-follow_redirects'
|
|
18
19
|
s.add_runtime_dependency 'activesupport'
|
|
19
20
|
s.add_runtime_dependency 'attr_required'
|
|
20
21
|
s.add_runtime_dependency 'json-jwt', '>= 1.11.0'
|
|
@@ -23,4 +24,5 @@ Gem::Specification.new do |s|
|
|
|
23
24
|
s.add_development_dependency 'rspec'
|
|
24
25
|
s.add_development_dependency 'rspec-its'
|
|
25
26
|
s.add_development_dependency 'webmock'
|
|
27
|
+
s.add_development_dependency 'rexml'
|
|
26
28
|
end
|
|
@@ -13,7 +13,7 @@ module WebMockHelper
|
|
|
13
13
|
|
|
14
14
|
def request_for(method, options = {})
|
|
15
15
|
request = {}
|
|
16
|
-
params = options
|
|
16
|
+
params = options&.[](:params) || {}
|
|
17
17
|
case method
|
|
18
18
|
when :post, :put, :delete
|
|
19
19
|
request[:body] = params
|
|
@@ -28,7 +28,13 @@ module WebMockHelper
|
|
|
28
28
|
|
|
29
29
|
def response_for(response_file, options = {})
|
|
30
30
|
response = {}
|
|
31
|
-
|
|
31
|
+
format = options[:format] || :json
|
|
32
|
+
if format == :json
|
|
33
|
+
response[:headers] = {
|
|
34
|
+
'Content-Type': 'application/json'
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
response[:body] = File.new(File.join(File.dirname(__FILE__), '../mock_response', "#{response_file}.#{format}"))
|
|
32
38
|
if options[:status]
|
|
33
39
|
response[:status] = options[:status]
|
|
34
40
|
end
|