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.
Files changed (40) hide show
  1. data/VERSION +1 -1
  2. data/lib/rack/oauth2.rb +2 -1
  3. data/lib/rack/oauth2/access_token.rb +30 -0
  4. data/lib/rack/oauth2/access_token/bearer.rb +29 -0
  5. data/lib/rack/oauth2/access_token/mac.rb +109 -0
  6. data/lib/rack/oauth2/access_token/mac/body_hash.rb +15 -0
  7. data/lib/rack/oauth2/access_token/mac/signature.rb +49 -0
  8. data/lib/rack/oauth2/access_token/mac/verifier.rb +43 -0
  9. data/lib/rack/oauth2/server/authorize/code.rb +0 -1
  10. data/lib/rack/oauth2/server/resource.rb +55 -1
  11. data/lib/rack/oauth2/server/resource/bearer.rb +12 -39
  12. data/lib/rack/oauth2/server/resource/bearer/error.rb +5 -60
  13. data/lib/rack/oauth2/server/resource/error.rb +81 -0
  14. data/lib/rack/oauth2/server/resource/mac.rb +39 -0
  15. data/lib/rack/oauth2/server/resource/mac/error.rb +24 -0
  16. data/lib/rack/oauth2/server/token.rb +3 -10
  17. data/lib/rack/oauth2/server/token/refresh_token.rb +0 -2
  18. data/lib/rack/oauth2/util.rb +10 -0
  19. data/spec/fake_response/facebook_token_response.txt +1 -0
  20. data/spec/fake_response/resources/fake.txt +1 -0
  21. data/spec/helpers/time.rb +19 -0
  22. data/spec/rack/oauth2/access_token/bearer_spec.rb +43 -0
  23. data/spec/rack/oauth2/access_token/mac/verifier_spec.rb +23 -0
  24. data/spec/rack/oauth2/access_token/mac_spec.rb +163 -0
  25. data/spec/rack/oauth2/access_token_spec.rb +48 -0
  26. data/spec/rack/oauth2/client_spec.rb +18 -6
  27. data/spec/rack/oauth2/server/resource/bearer/error_spec.rb +9 -87
  28. data/spec/rack/oauth2/server/resource/bearer_spec.rb +40 -69
  29. data/spec/rack/oauth2/server/resource/error_spec.rb +147 -0
  30. data/spec/rack/oauth2/server/resource/mac/error_spec.rb +52 -0
  31. data/spec/rack/oauth2/server/resource/mac_spec.rb +92 -0
  32. data/spec/rack/oauth2/server/resource_spec.rb +23 -0
  33. data/spec/rack/oauth2/server/token/authorization_code_spec.rb +1 -2
  34. data/spec/rack/oauth2/server/token/client_credentials_spec.rb +1 -2
  35. data/spec/rack/oauth2/server/token/password_spec.rb +1 -2
  36. data/spec/rack/oauth2/server/token/refresh_token_spec.rb +1 -2
  37. data/spec/rack/oauth2/server/token_spec.rb +1 -2
  38. data/spec/rack/oauth2/util_spec.rb +10 -0
  39. data/spec/spec_helper.rb +1 -0
  40. metadata +38 -6
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.6.0.alpha
@@ -6,4 +6,5 @@ require 'attr_required'
6
6
  require 'attr_optional'
7
7
  require 'rack/oauth2/util'
8
8
  require 'rack/oauth2/server'
9
- require 'rack/oauth2/client'
9
+ require 'rack/oauth2/client'
10
+ require 'rack/oauth2/access_token'
@@ -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,15 @@
1
+ module Rack
2
+ module OAuth2
3
+ class AccessToken
4
+ class MAC
5
+ class BodyHash < Verifier
6
+ attr_optional :raw_body
7
+
8
+ def calculate
9
+ Rack::OAuth2::Util.base64_encode hash_generator.digest(raw_body)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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
@@ -3,7 +3,6 @@ module Rack
3
3
  module Server
4
4
  class Authorize
5
5
  class Code < Abstract::Handler
6
-
7
6
  def call(env)
8
7
  @request = Request.new env
9
8
  @response = Response.new request
@@ -1 +1,55 @@
1
- require 'rack/oauth2/server/resource/bearer'
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
- module Resource
5
- class Bearer < Abstract::Handler
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
- if request.bearer?
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
- def authenticate!(request)
31
- @authenticator.call(request)
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
- module Resource
4
+ class Resource
5
5
  class Bearer
6
- class BadRequest < Abstract::BadRequest
7
- end
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
- DEFAULT_DESCRIPTION = {
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