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
@@ -0,0 +1,81 @@
1
+ module Rack
2
+ module OAuth2
3
+ module Server
4
+ class Resource
5
+ class BadRequest < Abstract::BadRequest
6
+ end
7
+
8
+ class Unauthorized < Abstract::Unauthorized
9
+ def scheme
10
+ raise 'Define me!'
11
+ end
12
+
13
+ def finish
14
+ super do |response|
15
+ self.realm ||= DEFAULT_REALM
16
+ header = response.header['WWW-Authenticate'] = "#{scheme} realm=\"#{realm}\""
17
+ if ErrorMethods::DEFAULT_DESCRIPTION.keys.include?(error)
18
+ header << " error=\"#{error}\""
19
+ header << " error_description=\"#{description}\"" if description.present?
20
+ header << " error_uri=\"#{uri}\"" if uri.present?
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ class Forbidden < Abstract::Forbidden
27
+ attr_accessor :scope
28
+
29
+ def initialize(error = :forbidden, description = nil, options = {})
30
+ super
31
+ @scope = options[:scope]
32
+ end
33
+
34
+ def protocol_params
35
+ super.merge(:scope => Array(scope).join(' '))
36
+ end
37
+ end
38
+
39
+ module ErrorMethods
40
+ DEFAULT_DESCRIPTION = {
41
+ :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.",
42
+ :invalid_token => "The access token provided is expired, revoked, malformed or invalid for other reasons.",
43
+ :insufficient_scope => "The request requires higher privileges than provided by the access token."
44
+ }
45
+
46
+ def self.included(klass)
47
+ DEFAULT_DESCRIPTION.each do |error, default_description|
48
+ error_method = case error
49
+ when :invalid_request
50
+ :bad_request!
51
+ when :insufficient_scope
52
+ :forbidden!
53
+ else
54
+ :unauthorized!
55
+ end
56
+ klass.class_eval <<-ERROR
57
+ def #{error}!(description = "#{default_description}", options = {})
58
+ #{error_method} :#{error}, description, options
59
+ end
60
+ ERROR
61
+ end
62
+ end
63
+
64
+ def bad_request!(error, description = nil, options = {})
65
+ raise BadRequest.new(error, description, options)
66
+ end
67
+
68
+ def unauthorized!(error = nil, description = nil, options = {})
69
+ raise 'Define me!'
70
+ end
71
+
72
+ def forbidden!(error, description = nil, options = {})
73
+ raise Forbidden.new(error, description, options)
74
+ end
75
+ end
76
+
77
+ Request.send :include, ErrorMethods
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,39 @@
1
+ module Rack
2
+ module OAuth2
3
+ module Server
4
+ class Resource
5
+ class MAC < Resource
6
+ def call(env)
7
+ self.request = Request.new(env)
8
+ super
9
+ end
10
+
11
+ private
12
+
13
+ class Request < Resource::Request
14
+ attr_reader :timestamp, :nonce, :body_hash, :signature
15
+
16
+ def setup!
17
+ auth_params = @auth_header.params.split(' ').inject({}) do |auth_params, pair|
18
+ key, value = pair.scan(/^(.*)=\"(.*)\"/).flatten
19
+ auth_params.merge!(key => value)
20
+ end.with_indifferent_access
21
+ @access_token = auth_params[:token]
22
+ @timestamp = auth_params[:timestamp]
23
+ @nonce = auth_params[:nonce]
24
+ @body_hash = auth_params[:bodyhash]
25
+ @signature = auth_params[:signature]
26
+ self
27
+ end
28
+
29
+ def oauth2?
30
+ @auth_header.provided? && @auth_header.scheme == :mac
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ require 'rack/oauth2/server/resource/mac/error'
@@ -0,0 +1,24 @@
1
+ module Rack
2
+ module OAuth2
3
+ module Server
4
+ class Resource
5
+ class MAC
6
+ class Unauthorized < Resource::Unauthorized
7
+ def scheme
8
+ :MAC
9
+ end
10
+ end
11
+
12
+ module ErrorMethods
13
+ include Resource::ErrorMethods
14
+ def unauthorized!(error = nil, description = nil, options = {})
15
+ raise Unauthorized.new(error, description, options)
16
+ end
17
+ end
18
+
19
+ Request.send :include, ErrorMethods
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -46,23 +46,16 @@ module Rack
46
46
  end
47
47
 
48
48
  class Response < Abstract::Response
49
- attr_required :access_token, :token_type
50
- attr_optional :refresh_token, :expires_in, :scope
49
+ attr_required :access_token
51
50
 
52
51
  def protocol_params
53
- {
54
- :access_token => access_token,
55
- :refresh_token => refresh_token,
56
- :token_type => token_type,
57
- :expires_in => expires_in,
58
- :scope => Array(scope).join(' ')
59
- }
52
+ access_token.token_response
60
53
  end
61
54
 
62
55
  def finish
56
+ attr_missing!
63
57
  write Util.compact_hash(protocol_params).to_json
64
58
  header['Content-Type'] = "application/json"
65
- attr_missing!
66
59
  super
67
60
  end
68
61
  end
@@ -3,7 +3,6 @@ module Rack
3
3
  module Server
4
4
  class Token
5
5
  class RefreshToken < Abstract::Handler
6
-
7
6
  def call(env)
8
7
  @request = Request.new(env)
9
8
  @response = Response.new(request)
@@ -20,7 +19,6 @@ module Rack
20
19
  attr_missing!
21
20
  end
22
21
  end
23
-
24
22
  end
25
23
  end
26
24
  end
@@ -1,7 +1,17 @@
1
+ require 'base64'
2
+
1
3
  module Rack
2
4
  module OAuth2
3
5
  module Util
4
6
  class << self
7
+ def rfc3986_encode(text)
8
+ URI.encode(text, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
9
+ end
10
+
11
+ def base64_encode(text)
12
+ Base64.encode64(text).gsub(/\n/, '')
13
+ end
14
+
5
15
  def compact_hash(hash)
6
16
  hash.reject do |key, value|
7
17
  value.blank?
@@ -0,0 +1 @@
1
+ access_token=access_token&expires_in=3600
@@ -0,0 +1,19 @@
1
+ class Time
2
+ class << self
3
+ def now_with_fixed_time
4
+ if @fixed_time
5
+ @fixed_time.dup
6
+ else
7
+ now_without_fixed_time
8
+ end
9
+ end
10
+ alias_method_chain :now, :fixed_time
11
+
12
+ def fix(time = Time.now)
13
+ @fixed_time = time
14
+ yield
15
+ ensure
16
+ @fixed_time = nil
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::OAuth2::AccessToken::Bearer do
4
+ let :token do
5
+ Rack::OAuth2::AccessToken::Bearer.new(
6
+ :access_token => 'access_token'
7
+ )
8
+ end
9
+ let(:resource_endpoint) { 'https://server.example.com/resources/fake' }
10
+
11
+ [:get, :delete].each do |method|
12
+ before do
13
+ fake_response(method, resource_endpoint, 'resources/fake.txt')
14
+ end
15
+
16
+ describe method.to_s.upcase do
17
+ it 'should have Bearer Authorization header' do
18
+ RestClient.should_receive(method).with(
19
+ resource_endpoint,
20
+ :HTTP_AUTHORIZATION => 'Bearer access_token'
21
+ )
22
+ token.send method, resource_endpoint
23
+ end
24
+ end
25
+ end
26
+
27
+ [:post, :put].each do |method|
28
+ before do
29
+ fake_response(method, resource_endpoint, 'resources/fake.txt')
30
+ end
31
+
32
+ describe method.to_s.upcase do
33
+ it 'should have Bearer Authorization header' do
34
+ RestClient.should_receive(method).with(
35
+ resource_endpoint,
36
+ {:key => :value},
37
+ {:HTTP_AUTHORIZATION => 'Bearer access_token'}
38
+ )
39
+ token.send method, resource_endpoint, {:key => :value}
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::OAuth2::AccessToken::MAC::Verifier do
4
+ let(:verifier) { Rack::OAuth2::AccessToken::MAC::Verifier.new(:algorithm => algorithm) }
5
+ subject { verifier }
6
+
7
+ context 'when "hmac-sha-1" is specified' do
8
+ let(:algorithm) { 'hmac-sha-1' }
9
+ its(:hash_generator) { should be_instance_of OpenSSL::Digest::SHA1 }
10
+ end
11
+
12
+ context 'when "hmac-sha-256" is specified' do
13
+ let(:algorithm) { 'hmac-sha-256' }
14
+ its(:hash_generator) { should be_instance_of OpenSSL::Digest::SHA256 }
15
+ end
16
+
17
+ context 'otherwise' do
18
+ let(:algorithm) { 'invalid' }
19
+ it do
20
+ expect { verifier.send(:hash_generator) }.should raise_error(StandardError, 'Unsupported Algorithm')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::OAuth2::AccessToken::MAC do
4
+ let :token do
5
+ Rack::OAuth2::AccessToken::MAC.new(
6
+ :access_token => 'access_token',
7
+ :secret => 'secret',
8
+ :algorithm => 'hmac-sha-256'
9
+ )
10
+ end
11
+ let(:resource_endpoint) { 'https://server.example.com/resources/fake' }
12
+ subject { token }
13
+
14
+ its(:secret) { should == 'secret' }
15
+ its(:algorithm) { should == 'hmac-sha-256' }
16
+ its(:token_response) do
17
+ should == {
18
+ :token_type => :mac,
19
+ :access_token => 'access_token',
20
+ :secret => 'secret',
21
+ :algorithm => 'hmac-sha-256',
22
+ :expires_in => nil,
23
+ :refresh_token => nil,
24
+ :scope => ''
25
+ }
26
+ end
27
+ its(:generate_nonce) { should be_a String }
28
+
29
+ describe 'HTTP methods' do
30
+ before do
31
+ token.should_receive(:generate_nonce).and_return("51e74de734c05613f37520872e68db5f")
32
+ end
33
+
34
+ describe :GET do
35
+ let(:resource_endpoint) { 'https://server.example.com/resources/fake?key=value' }
36
+ it 'should have MAC Authorization header' do
37
+ Time.fix(Time.at(1302361200)) do
38
+ RestClient.should_receive(:get).with(
39
+ resource_endpoint,
40
+ :HTTP_AUTHORIZATION => "MAC token=\"access_token\" timestamp=\"1302361200\" nonce=\"51e74de734c05613f37520872e68db5f\" signature=\"yYDSkZMrEbOOqj0anHNLA9ougNA+lxU0zmPiMSPtmJ8=\""
41
+ )
42
+ token.get resource_endpoint
43
+ end
44
+ end
45
+ end
46
+
47
+ describe :POST do
48
+ it 'should have MAC Authorization header' do
49
+ Time.fix(Time.at(1302361200)) do
50
+ RestClient.should_receive(:post).with(
51
+ resource_endpoint,
52
+ {:key => :value},
53
+ {:HTTP_AUTHORIZATION => "MAC token=\"access_token\" timestamp=\"1302361200\" nonce=\"51e74de734c05613f37520872e68db5f\" bodyhash=\"Vj8DVxGNBe8UXWvd8pZswj6Gyo8vAT+RXlZa/fCfeiM=\" signature=\"xRvIiA+rmjhPjULVpyCCgiHEsOkLEHZik4ZaB+cyqgk=\""}
54
+ )
55
+ token.post resource_endpoint, :key => :value
56
+ end
57
+ end
58
+ end
59
+
60
+ describe :PUT do
61
+ it 'should have MAC Authorization header' do
62
+ Time.fix(Time.at(1302361200)) do
63
+ RestClient.should_receive(:put).with(
64
+ resource_endpoint,
65
+ {:key => :value},
66
+ {:HTTP_AUTHORIZATION => "MAC token=\"access_token\" timestamp=\"1302361200\" nonce=\"51e74de734c05613f37520872e68db5f\" bodyhash=\"Vj8DVxGNBe8UXWvd8pZswj6Gyo8vAT+RXlZa/fCfeiM=\" signature=\"2lWgkUCtD9lNBlDi5fe9eVDwEwbxfLGAqjgykaSV1ww=\""}
67
+ )
68
+ token.put resource_endpoint, :key => :value
69
+ end
70
+ end
71
+ end
72
+
73
+ describe :DELETE do
74
+ it 'should have MAC Authorization header' do
75
+ Time.fix(Time.at(1302361200)) do
76
+ RestClient.should_receive(:delete).with(
77
+ resource_endpoint,
78
+ :HTTP_AUTHORIZATION => "MAC token=\"access_token\" timestamp=\"1302361200\" nonce=\"51e74de734c05613f37520872e68db5f\" signature=\"PX2GhHuo5yYNEs51e4Zlllw8itQ4Te0v+6ZuRCK7k+s=\""
79
+ )
80
+ token.delete resource_endpoint
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe 'verify!' do
87
+ let(:request) { Rack::OAuth2::Server::Resource::MAC::Request.new(env) }
88
+
89
+ context 'when no body_hash is given' do
90
+ let(:env) do
91
+ Rack::MockRequest.env_for(
92
+ '/protected_resources',
93
+ 'HTTP_AUTHORIZATION' => "MAC token=\"access_token\" timestamp=\"1302361200\" nonce=\"51e74de734c05613f37520872e68db5f\" signature=\"#{signature}\""
94
+ )
95
+ end
96
+
97
+ context 'when signature is valid' do
98
+ let(:signature) { 'jaOwJ1eEjabkiRJJ4hdOeVvCGfiCHgeb9NDSrkM0ka4=' }
99
+ it do
100
+ Time.fix(Time.at(1302361200)) do
101
+ token.verify!(request.setup!).should == :verified
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'otherwise' do
107
+ let(:signature) { 'invalid' }
108
+ it do
109
+ expect { token.verify!(request.setup!) }.should raise_error(
110
+ Rack::OAuth2::AccessToken::MAC::Verifier::VerificationFailed,
111
+ 'Signature Invalid'
112
+ )
113
+ end
114
+ end
115
+ end
116
+
117
+ context 'when body_hash is given' do
118
+ let(:env) do
119
+ Rack::MockRequest.env_for(
120
+ '/protected_resources',
121
+ :method => :POST,
122
+ :params => {
123
+ :key1 => 'value1'
124
+ },
125
+ 'HTTP_AUTHORIZATION' => "MAC token=\"access_token\" timestamp=\"1302361200\" nonce=\"51e74de734c05613f37520872e68db5f\" bodyhash=\"#{body_hash}\" signature=\"#{signature}\""
126
+ )
127
+ end
128
+ let(:signature) { 'invalid' }
129
+
130
+ context 'when body_hash is invalid' do
131
+ let(:body_hash) { 'invalid' }
132
+ it do
133
+ expect { token.verify!(request.setup!) }.should raise_error(
134
+ Rack::OAuth2::AccessToken::MAC::Verifier::VerificationFailed,
135
+ 'BodyHash Invalid'
136
+ )
137
+ end
138
+ end
139
+
140
+ context 'when body_hash is valid' do
141
+ let(:body_hash) { 'TPzUbFn1S16mpfmwXCi1L+8oZHRxlLX9/D1ZwAV781o=' }
142
+
143
+ context 'when signature is valid' do
144
+ let(:signature) { 'ZSicF/YCg5bdDef103/NLGeuhH7ylHwrnYUUMcCz12A=' }
145
+ it do
146
+ Time.fix(Time.at(1302361200)) do
147
+ token.verify!(request.setup!).should == :verified
148
+ end
149
+ end
150
+ end
151
+
152
+ context 'otherwise' do
153
+ it do
154
+ expect { token.verify!(request.setup!) }.should raise_error(
155
+ Rack::OAuth2::AccessToken::MAC::Verifier::VerificationFailed,
156
+ 'Signature Invalid'
157
+ )
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end