rack-oauth2 0.5.1 → 0.6.0.alpha
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/VERSION +1 -1
- data/lib/rack/oauth2.rb +2 -1
- data/lib/rack/oauth2/access_token.rb +30 -0
- data/lib/rack/oauth2/access_token/bearer.rb +29 -0
- data/lib/rack/oauth2/access_token/mac.rb +109 -0
- data/lib/rack/oauth2/access_token/mac/body_hash.rb +15 -0
- data/lib/rack/oauth2/access_token/mac/signature.rb +49 -0
- data/lib/rack/oauth2/access_token/mac/verifier.rb +43 -0
- data/lib/rack/oauth2/server/authorize/code.rb +0 -1
- data/lib/rack/oauth2/server/resource.rb +55 -1
- data/lib/rack/oauth2/server/resource/bearer.rb +12 -39
- data/lib/rack/oauth2/server/resource/bearer/error.rb +5 -60
- data/lib/rack/oauth2/server/resource/error.rb +81 -0
- data/lib/rack/oauth2/server/resource/mac.rb +39 -0
- data/lib/rack/oauth2/server/resource/mac/error.rb +24 -0
- data/lib/rack/oauth2/server/token.rb +3 -10
- data/lib/rack/oauth2/server/token/refresh_token.rb +0 -2
- data/lib/rack/oauth2/util.rb +10 -0
- data/spec/fake_response/facebook_token_response.txt +1 -0
- data/spec/fake_response/resources/fake.txt +1 -0
- data/spec/helpers/time.rb +19 -0
- data/spec/rack/oauth2/access_token/bearer_spec.rb +43 -0
- data/spec/rack/oauth2/access_token/mac/verifier_spec.rb +23 -0
- data/spec/rack/oauth2/access_token/mac_spec.rb +163 -0
- data/spec/rack/oauth2/access_token_spec.rb +48 -0
- data/spec/rack/oauth2/client_spec.rb +18 -6
- data/spec/rack/oauth2/server/resource/bearer/error_spec.rb +9 -87
- data/spec/rack/oauth2/server/resource/bearer_spec.rb +40 -69
- data/spec/rack/oauth2/server/resource/error_spec.rb +147 -0
- data/spec/rack/oauth2/server/resource/mac/error_spec.rb +52 -0
- data/spec/rack/oauth2/server/resource/mac_spec.rb +92 -0
- data/spec/rack/oauth2/server/resource_spec.rb +23 -0
- data/spec/rack/oauth2/server/token/authorization_code_spec.rb +1 -2
- data/spec/rack/oauth2/server/token/client_credentials_spec.rb +1 -2
- data/spec/rack/oauth2/server/token/password_spec.rb +1 -2
- data/spec/rack/oauth2/server/token/refresh_token_spec.rb +1 -2
- data/spec/rack/oauth2/server/token_spec.rb +1 -2
- data/spec/rack/oauth2/util_spec.rb +10 -0
- data/spec/spec_helper.rb +1 -0
- metadata +38 -6
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0.alpha
|
data/lib/rack/oauth2.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class AccessToken
|
4
|
+
include AttrRequired, AttrOptional
|
5
|
+
attr_required :access_token, :token_type
|
6
|
+
attr_optional :refresh_token, :expires_in, :scope
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
(required_attributes + optional_attributes).each do |key|
|
10
|
+
self.send :"#{key}=", attributes[key]
|
11
|
+
end
|
12
|
+
@token_type = self.class.to_s.split('::').last.underscore.to_sym
|
13
|
+
attr_missing!
|
14
|
+
end
|
15
|
+
|
16
|
+
def token_response(options = {})
|
17
|
+
{
|
18
|
+
:access_token => access_token,
|
19
|
+
:refresh_token => refresh_token,
|
20
|
+
:token_type => token_type,
|
21
|
+
:expires_in => expires_in,
|
22
|
+
:scope => Array(scope).join(' ')
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'rack/oauth2/access_token/bearer'
|
30
|
+
require 'rack/oauth2/access_token/mac'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class AccessToken
|
4
|
+
class Bearer < AccessToken
|
5
|
+
def get(url, headers = {}, &block)
|
6
|
+
RestClient.get url, authenticate(headers), &block
|
7
|
+
end
|
8
|
+
|
9
|
+
def post(url, payload, headers = {}, &block)
|
10
|
+
RestClient.post url, payload, authenticate(headers), &block
|
11
|
+
end
|
12
|
+
|
13
|
+
def put(url, payload, headers = {}, &block)
|
14
|
+
RestClient.put url, payload, authenticate(headers), &block
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(url, headers = {}, &block)
|
18
|
+
RestClient.delete url, authenticate(headers), &block
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def authenticate(headers)
|
24
|
+
headers.merge(:HTTP_AUTHORIZATION => "Bearer #{access_token}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class AccessToken
|
4
|
+
class MAC < AccessToken
|
5
|
+
attr_required :secret, :algorithm
|
6
|
+
attr_optional :timestamp, :nonce, :body_hash, :signature
|
7
|
+
|
8
|
+
def token_response
|
9
|
+
super.merge(
|
10
|
+
:secret => secret,
|
11
|
+
:algorithm => algorithm
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def verify!(request)
|
16
|
+
if request.body_hash.present?
|
17
|
+
_body_hash_ = BodyHash.new(
|
18
|
+
:raw_body => request.body.read,
|
19
|
+
:algorithm => self.algorithm
|
20
|
+
)
|
21
|
+
_body_hash_.verify!(request.body_hash)
|
22
|
+
end
|
23
|
+
_signature_ = Signature.new(
|
24
|
+
:token => request.access_token,
|
25
|
+
:secret => self.secret,
|
26
|
+
:algorithm => self.algorithm,
|
27
|
+
:timestamp => request.timestamp,
|
28
|
+
:nonce => request.nonce,
|
29
|
+
:body_hash => request.body_hash,
|
30
|
+
:method => request.request_method,
|
31
|
+
:host => request.host,
|
32
|
+
:port => request.port,
|
33
|
+
:path => request.path,
|
34
|
+
:query => request.GET
|
35
|
+
)
|
36
|
+
_signature_.verify!(request.signature)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get(url, headers = {}, &block)
|
40
|
+
_headers_ = authenticate(:get, url, headers)
|
41
|
+
RestClient.get url, _headers_, &block
|
42
|
+
end
|
43
|
+
|
44
|
+
def post(url, payload, headers = {}, &block)
|
45
|
+
_headers_ = authenticate(:post, url, headers, payload)
|
46
|
+
RestClient.post url, payload, _headers_, &block
|
47
|
+
end
|
48
|
+
|
49
|
+
def put(url, payload, headers = {}, &block)
|
50
|
+
_headers_ = authenticate(:put, url, headers, payload)
|
51
|
+
RestClient.put url, payload, _headers_, &block
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete(url, headers = {}, &block)
|
55
|
+
_headers_ = authenticate(:delete, url, headers)
|
56
|
+
RestClient.delete url, _headers_, &block
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def authenticate(method, url, headers = {}, payload = {})
|
62
|
+
_url_ = URI.parse(url)
|
63
|
+
self.timestamp = Time.now.to_i
|
64
|
+
self.nonce = generate_nonce
|
65
|
+
if payload.present?
|
66
|
+
raw_body = RestClient::Payload.generate(payload).to_s
|
67
|
+
_body_hash_ = BodyHash.new(
|
68
|
+
:raw_body => raw_body,
|
69
|
+
:algorithm => self.algorithm
|
70
|
+
)
|
71
|
+
self.body_hash = _body_hash_.calculate
|
72
|
+
end
|
73
|
+
_signature_ = Signature.new(
|
74
|
+
:token => self.access_token,
|
75
|
+
:secret => self.secret,
|
76
|
+
:algorithm => self.algorithm,
|
77
|
+
:timestamp => self.timestamp,
|
78
|
+
:nonce => self.nonce,
|
79
|
+
:body_hash => self.body_hash,
|
80
|
+
:method => method,
|
81
|
+
:host => _url_.host,
|
82
|
+
:port => _url_.port,
|
83
|
+
:path => _url_.path,
|
84
|
+
:query => Rack::Utils.parse_nested_query(_url_.query)
|
85
|
+
)
|
86
|
+
self.signature = _signature_.calculate
|
87
|
+
headers.merge(:HTTP_AUTHORIZATION => authorization_header)
|
88
|
+
end
|
89
|
+
|
90
|
+
def authorization_header
|
91
|
+
header = "MAC"
|
92
|
+
header << " token=\"#{access_token}\""
|
93
|
+
header << " timestamp=\"#{timestamp}\""
|
94
|
+
header << " nonce=\"#{nonce}\""
|
95
|
+
header << " bodyhash=\"#{body_hash}\"" if self.body_hash.present?
|
96
|
+
header << " signature=\"#{signature}\""
|
97
|
+
end
|
98
|
+
|
99
|
+
def generate_nonce
|
100
|
+
ActiveSupport::SecureRandom.hex(16)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
require 'rack/oauth2/access_token/mac/verifier'
|
108
|
+
require 'rack/oauth2/access_token/mac/body_hash'
|
109
|
+
require 'rack/oauth2/access_token/mac/signature'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class AccessToken
|
4
|
+
class MAC
|
5
|
+
class Signature < Verifier
|
6
|
+
attr_required :token, :secret, :timestamp, :nonce, :method, :host, :port, :path
|
7
|
+
attr_optional :body_hash, :query
|
8
|
+
|
9
|
+
def calculate
|
10
|
+
Rack::OAuth2::Util.base64_encode OpenSSL::HMAC.digest(
|
11
|
+
hash_generator,
|
12
|
+
secret,
|
13
|
+
normalized_request_string
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalized_request_string
|
18
|
+
arr = [
|
19
|
+
token,
|
20
|
+
secret,
|
21
|
+
algorithm,
|
22
|
+
timestamp,
|
23
|
+
nonce,
|
24
|
+
body_hash,
|
25
|
+
method,
|
26
|
+
host,
|
27
|
+
port,
|
28
|
+
path,
|
29
|
+
normalized_query
|
30
|
+
]
|
31
|
+
arr.join("\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalized_query
|
35
|
+
if query.present?
|
36
|
+
query.inject([]) do |result, (key, value)|
|
37
|
+
result << [key, value]
|
38
|
+
end.sort.inject('') do |result, (key, value)|
|
39
|
+
result << "#{Rack::OAuth2::Util.rfc3986_encode key}=#{Rack::OAuth2::Util.rfc3986_encode value}\n"
|
40
|
+
end
|
41
|
+
else
|
42
|
+
query.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class AccessToken
|
4
|
+
class MAC
|
5
|
+
class Verifier
|
6
|
+
include AttrRequired, AttrOptional
|
7
|
+
attr_required :algorithm
|
8
|
+
|
9
|
+
# TODO: rescue this in proper location later
|
10
|
+
class VerificationFailed < StandardError; end
|
11
|
+
|
12
|
+
def initialize(attributes = {})
|
13
|
+
(required_attributes + optional_attributes).each do |key|
|
14
|
+
self.send :"#{key}=", attributes[key]
|
15
|
+
end
|
16
|
+
attr_missing!
|
17
|
+
end
|
18
|
+
|
19
|
+
def verify!(expected)
|
20
|
+
if expected == self.calculate
|
21
|
+
:verified
|
22
|
+
else
|
23
|
+
raise VerificationFailed.new("#{self.class.to_s.split('::').last} Invalid")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def hash_generator
|
30
|
+
case algorithm.to_s
|
31
|
+
when 'hmac-sha-1'
|
32
|
+
OpenSSL::Digest::SHA1.new
|
33
|
+
when 'hmac-sha-256'
|
34
|
+
OpenSSL::Digest::SHA256.new
|
35
|
+
else
|
36
|
+
raise 'Unsupported Algorithm'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1 +1,55 @@
|
|
1
|
-
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
module Server
|
4
|
+
class Resource < Abstract::Handler
|
5
|
+
ACCESS_TOKEN = 'rack.oauth2.access_token'
|
6
|
+
DEFAULT_REALM = 'Protected by OAuth 2.0'
|
7
|
+
attr_accessor :realm, :request
|
8
|
+
|
9
|
+
def initialize(app, realm = nil, &authenticator)
|
10
|
+
@app = app
|
11
|
+
@realm = realm
|
12
|
+
super &authenticator
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
if request.oauth2?
|
17
|
+
authenticate! request.setup!
|
18
|
+
env[ACCESS_TOKEN] = request.access_token
|
19
|
+
end
|
20
|
+
@app.call(env)
|
21
|
+
rescue Rack::OAuth2::Server::Abstract::Error => e
|
22
|
+
e.realm ||= realm
|
23
|
+
e.finish
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def authenticate!(request)
|
29
|
+
@authenticator.call(request)
|
30
|
+
end
|
31
|
+
|
32
|
+
class Request < Rack::Request
|
33
|
+
attr_reader :access_token
|
34
|
+
|
35
|
+
def initialize(env)
|
36
|
+
@env = env
|
37
|
+
@auth_header = Rack::Auth::AbstractRequest.new(env)
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup!
|
41
|
+
raise 'Define me!'
|
42
|
+
end
|
43
|
+
|
44
|
+
def oauth2?
|
45
|
+
raise 'Define me!'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
require 'rack/oauth2/server/resource/error'
|
54
|
+
require 'rack/oauth2/server/resource/bearer'
|
55
|
+
require 'rack/oauth2/server/resource/mac'
|
@@ -1,56 +1,29 @@
|
|
1
1
|
module Rack
|
2
2
|
module OAuth2
|
3
3
|
module Server
|
4
|
-
|
5
|
-
class Bearer <
|
6
|
-
ACCESS_TOKEN = 'rack.oauth2.bearer_token'
|
7
|
-
DEFAULT_REALM = 'Bearer Token Required'
|
8
|
-
attr_accessor :realm
|
9
|
-
|
10
|
-
def initialize(app, realm = nil,&authenticator)
|
11
|
-
@app = app
|
12
|
-
@realm = realm
|
13
|
-
super(&authenticator)
|
14
|
-
end
|
15
|
-
|
4
|
+
class Resource
|
5
|
+
class Bearer < Resource
|
16
6
|
def call(env)
|
17
|
-
request = Request.new(env)
|
18
|
-
|
19
|
-
authenticate!(request)
|
20
|
-
env[ACCESS_TOKEN] = request.access_token
|
21
|
-
end
|
22
|
-
@app.call(env)
|
23
|
-
rescue Rack::OAuth2::Server::Abstract::Error => e
|
24
|
-
e.realm ||= realm
|
25
|
-
e.finish
|
7
|
+
self.request = Request.new(env)
|
8
|
+
super
|
26
9
|
end
|
27
10
|
|
28
11
|
private
|
29
12
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
class Request < Rack::Request
|
35
|
-
def initialize(env)
|
36
|
-
@env = env
|
37
|
-
@auth_header = Rack::Auth::AbstractRequest.new(env)
|
38
|
-
end
|
39
|
-
|
40
|
-
def bearer?
|
41
|
-
access_token.present?
|
42
|
-
end
|
43
|
-
|
44
|
-
def access_token
|
13
|
+
class Request < Resource::Request
|
14
|
+
def setup!
|
45
15
|
tokens = [access_token_in_haeder, access_token_in_payload].compact
|
46
|
-
case Array(tokens).size
|
47
|
-
when 0
|
48
|
-
nil
|
16
|
+
@access_token = case Array(tokens).size
|
49
17
|
when 1
|
50
18
|
tokens.first
|
51
19
|
else
|
52
20
|
invalid_request!('Both Authorization header and payload includes access token.')
|
53
21
|
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def oauth2?
|
26
|
+
(access_token_in_haeder || access_token_in_payload).present?
|
54
27
|
end
|
55
28
|
|
56
29
|
def access_token_in_haeder
|
@@ -1,74 +1,19 @@
|
|
1
1
|
module Rack
|
2
2
|
module OAuth2
|
3
3
|
module Server
|
4
|
-
|
4
|
+
class Resource
|
5
5
|
class Bearer
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
class Unauthorized < Abstract::Unauthorized
|
10
|
-
def finish
|
11
|
-
super do |response|
|
12
|
-
self.realm ||= DEFAULT_REALM
|
13
|
-
header = response.header['WWW-Authenticate'] = "Bearer realm=\"#{realm}\""
|
14
|
-
if ErrorMethods::DEFAULT_DESCRIPTION.keys.include?(error)
|
15
|
-
header << " error=\"#{error}\""
|
16
|
-
header << " error_description=\"#{description}\"" if description.present?
|
17
|
-
header << " error_uri=\"#{uri}\"" if uri.present?
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Forbidden < Abstract::Forbidden
|
24
|
-
attr_accessor :scope
|
25
|
-
|
26
|
-
def initialize(error = :forbidden, description = nil, options = {})
|
27
|
-
super
|
28
|
-
@scope = options[:scope]
|
29
|
-
end
|
30
|
-
|
31
|
-
def protocol_params
|
32
|
-
super.merge(:scope => Array(scope).join(' '))
|
6
|
+
class Unauthorized < Resource::Unauthorized
|
7
|
+
def scheme
|
8
|
+
:Bearer
|
33
9
|
end
|
34
10
|
end
|
35
11
|
|
36
12
|
module ErrorMethods
|
37
|
-
|
38
|
-
:invalid_request => "The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.",
|
39
|
-
:invalid_token => "The access token provided is expired, revoked, malformed or invalid for other reasons.",
|
40
|
-
:insufficient_scope => "The request requires higher privileges than provided by the access token."
|
41
|
-
}
|
42
|
-
|
43
|
-
def self.included(klass)
|
44
|
-
DEFAULT_DESCRIPTION.each do |error, default_description|
|
45
|
-
error_method = case error
|
46
|
-
when :invalid_request
|
47
|
-
:bad_request!
|
48
|
-
when :insufficient_scope
|
49
|
-
:forbidden!
|
50
|
-
else
|
51
|
-
:unauthorized!
|
52
|
-
end
|
53
|
-
klass.class_eval <<-ERROR
|
54
|
-
def #{error}!(description = "#{default_description}", options = {})
|
55
|
-
#{error_method} :#{error}, description, options
|
56
|
-
end
|
57
|
-
ERROR
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def bad_request!(error, description = nil, options = {})
|
62
|
-
raise BadRequest.new(error, description, options)
|
63
|
-
end
|
64
|
-
|
13
|
+
include Resource::ErrorMethods
|
65
14
|
def unauthorized!(error = nil, description = nil, options = {})
|
66
15
|
raise Unauthorized.new(error, description, options)
|
67
16
|
end
|
68
|
-
|
69
|
-
def forbidden!(error, description = nil, options = {})
|
70
|
-
raise Forbidden.new(error, description, options)
|
71
|
-
end
|
72
17
|
end
|
73
18
|
|
74
19
|
Request.send :include, ErrorMethods
|