rack-oauth2 0.5.1 → 0.6.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
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