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
@@ -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