cloudkit 0.10.1 → 0.11.0
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/CHANGES +11 -0
- data/README +7 -6
- data/Rakefile +13 -6
- data/TODO +7 -5
- data/cloudkit.gemspec +23 -23
- data/doc/curl.html +2 -2
- data/doc/index.html +4 -6
- data/examples/5.ru +2 -3
- data/examples/TOC +1 -3
- data/lib/cloudkit.rb +17 -10
- data/lib/cloudkit/constants.rb +0 -6
- data/lib/cloudkit/exceptions.rb +10 -0
- data/lib/cloudkit/flash_session.rb +1 -3
- data/lib/cloudkit/oauth_filter.rb +8 -16
- data/lib/cloudkit/oauth_store.rb +9 -13
- data/lib/cloudkit/openid_filter.rb +25 -7
- data/lib/cloudkit/openid_store.rb +14 -17
- data/lib/cloudkit/request.rb +6 -1
- data/lib/cloudkit/service.rb +15 -15
- data/lib/cloudkit/store.rb +97 -284
- data/lib/cloudkit/store/memory_table.rb +105 -0
- data/lib/cloudkit/store/resource.rb +256 -0
- data/lib/cloudkit/store/response_helpers.rb +0 -1
- data/lib/cloudkit/uri.rb +88 -0
- data/lib/cloudkit/user_store.rb +7 -14
- data/spec/ext_spec.rb +76 -0
- data/spec/flash_session_spec.rb +20 -0
- data/spec/memory_table_spec.rb +86 -0
- data/spec/oauth_filter_spec.rb +326 -0
- data/spec/oauth_store_spec.rb +10 -0
- data/spec/openid_filter_spec.rb +64 -0
- data/spec/openid_store_spec.rb +101 -0
- data/spec/rack_builder_spec.rb +39 -0
- data/spec/request_spec.rb +185 -0
- data/spec/resource_spec.rb +291 -0
- data/spec/service_spec.rb +974 -0
- data/{test/helper.rb → spec/spec_helper.rb} +14 -2
- data/spec/store_spec.rb +10 -0
- data/spec/uri_spec.rb +93 -0
- data/spec/user_store_spec.rb +10 -0
- data/spec/util_spec.rb +11 -0
- metadata +37 -61
- data/examples/6.ru +0 -10
- data/lib/cloudkit/store/adapter.rb +0 -8
- data/lib/cloudkit/store/extraction_view.rb +0 -57
- data/lib/cloudkit/store/sql_adapter.rb +0 -36
- data/test/ext_test.rb +0 -76
- data/test/flash_session_test.rb +0 -22
- data/test/oauth_filter_test.rb +0 -331
- data/test/oauth_store_test.rb +0 -12
- data/test/openid_filter_test.rb +0 -60
- data/test/openid_store_test.rb +0 -12
- data/test/rack_builder_test.rb +0 -41
- data/test/request_test.rb +0 -197
- data/test/service_test.rb +0 -971
- data/test/store_test.rb +0 -93
- data/test/user_store_test.rb +0 -12
- data/test/util_test.rb +0 -13
data/lib/cloudkit/user_store.rb
CHANGED
@@ -1,41 +1,34 @@
|
|
1
1
|
module CloudKit
|
2
2
|
|
3
3
|
# A thin layer on top of CloudKit::Store providing consistent URIs and
|
4
|
-
# automatic
|
4
|
+
# automatic upgrades if required for future releases.
|
5
5
|
class UserStore
|
6
6
|
@@store = nil
|
7
7
|
|
8
8
|
def initialize(uri=nil)
|
9
9
|
unless @@store
|
10
|
-
|
11
|
-
:cloudkit_login_view,
|
12
|
-
:observe => :cloudkit_users,
|
13
|
-
:extract => [:identity_url, :remember_me_token, :remember_me_expiration])
|
14
|
-
@@store = Store.new(
|
15
|
-
:collections => [:cloudkit_users],
|
16
|
-
:views => [login_view],
|
17
|
-
:adapter => SQLAdapter.new(uri))
|
10
|
+
@@store = Store.new(:collections => [:cloudkit_users])
|
18
11
|
end
|
19
12
|
end
|
20
13
|
|
21
14
|
def get(uri, options={}) #:nodoc:
|
22
|
-
@@store.get(uri, options)
|
15
|
+
@@store.get(CloudKit::URI.new(uri), options)
|
23
16
|
end
|
24
17
|
|
25
18
|
def post(uri, options={}) #:nodoc:
|
26
|
-
@@store.post(uri, options)
|
19
|
+
@@store.post(CloudKit::URI.new(uri), options)
|
27
20
|
end
|
28
21
|
|
29
22
|
def put(uri, options={}) #:nodoc:
|
30
|
-
@@store.put(uri, options)
|
23
|
+
@@store.put(CloudKit::URI.new(uri), options)
|
31
24
|
end
|
32
25
|
|
33
26
|
def delete(uri, options={}) #:nodoc:
|
34
|
-
@@store.delete(uri, options)
|
27
|
+
@@store.delete(CloudKit::URI.new(uri), options)
|
35
28
|
end
|
36
29
|
|
37
30
|
def resolve_uris(uris) #:nodoc:
|
38
|
-
@@store.resolve_uris(uris)
|
31
|
+
@@store.resolve_uris(uris.map { |uri| CloudKit::URI.new(uri) })
|
39
32
|
end
|
40
33
|
|
41
34
|
# Return the version for this UserStore
|
data/spec/ext_spec.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A Hash" do
|
4
|
+
|
5
|
+
it "should re-key an entry if it exists" do
|
6
|
+
x = {:a => 1, :b => 2}
|
7
|
+
x.rekey!(:b, :c)
|
8
|
+
x.should == {:a => 1, :c => 2}
|
9
|
+
x.rekey!(:d, :b)
|
10
|
+
x.should == {:a => 1, :c => 2}
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should re-key false and nil values" do
|
14
|
+
x = {:a => false, :b => nil}
|
15
|
+
x.rekey!(:b, :c)
|
16
|
+
x.should == {:a => false, :c => nil}
|
17
|
+
x.rekey!(:d, :b)
|
18
|
+
x.should == {:a => false, :c => nil}
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should merge conditionally" do
|
22
|
+
x = {:a => 1}
|
23
|
+
y = {:b => 2}
|
24
|
+
x.filter_merge!(:c => y[:c])
|
25
|
+
x.should == {:a => 1}
|
26
|
+
x.filter_merge!(:c => y[:b])
|
27
|
+
x.should == {:a => 1, :c => 2}
|
28
|
+
x = {}.filter_merge!(:a => 1)
|
29
|
+
x.should == {:a => 1}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should merge false values correctly" do
|
33
|
+
x = {:a => 1}
|
34
|
+
y = {:b => 2}
|
35
|
+
x.filter_merge!(:c => false)
|
36
|
+
x.should == {:a => 1, :c => false}
|
37
|
+
x.filter_merge!(:c => y[:b])
|
38
|
+
x.should == {:a => 1, :c => 2}
|
39
|
+
x = {}.filter_merge!(:a => false)
|
40
|
+
x.should == {:a => false}
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should exclude pairs using a single key" do
|
44
|
+
x = {:a => 1, :b => 2}
|
45
|
+
y = x.excluding(:b)
|
46
|
+
y.should == {:a => 1}
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should exclude pairs using a list of keys" do
|
50
|
+
x = {:a => 1, :b => 2, :c => 3}
|
51
|
+
y = x.excluding(:b, :c)
|
52
|
+
y.should == {:a => 1}
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "An Array" do
|
58
|
+
|
59
|
+
it "should exclude elements" do
|
60
|
+
x = [0, 1, 2, 3]
|
61
|
+
y = x.excluding(1, 3)
|
62
|
+
y.should == [0, 2]
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "An Object" do
|
68
|
+
|
69
|
+
it "should try" do
|
70
|
+
x = {:a => 'a'}
|
71
|
+
result = x[:a].try(:upcase)
|
72
|
+
result.should == 'A'
|
73
|
+
lambda { x[:b].try(:upcase) }.should_not raise_error
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A FlashSession" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@flash = CloudKit::FlashSession.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should accept a value for a key" do
|
10
|
+
@flash['greeting'] = 'hello'
|
11
|
+
@flash['greeting'].should == 'hello'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should erase a key/value pair after access" do
|
15
|
+
@flash['greeting'] = 'hello'
|
16
|
+
x = @flash['greeting']
|
17
|
+
@flash['greeting'].should be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A MemoryTable" do
|
4
|
+
before(:each) do
|
5
|
+
@table = CloudKit::MemoryTable.new
|
6
|
+
end
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
@table.clear
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should reject non-hash records" do
|
13
|
+
@table['a'] = 1
|
14
|
+
@table['a'].should be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should reject non-string record keys" do
|
18
|
+
@table['a'] = {:foo => 'bar'}
|
19
|
+
@table['a'].should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should reject non-string record values" do
|
23
|
+
@table['a'] = {'foo' => 1}
|
24
|
+
@table['a'].should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should get and set values for table keys like a hash" do
|
28
|
+
@table['a'] = {'foo' => 'bar'}
|
29
|
+
@table['a'].should == {'foo' => 'bar'}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should clear its contents" do
|
33
|
+
@table['a'] = {'foo' => 'bar'}
|
34
|
+
@table['b'] = {'foo' => 'baz'}
|
35
|
+
@table.clear
|
36
|
+
@table['a'].should be_nil
|
37
|
+
@table['b'].should be_nil
|
38
|
+
@table.keys.should be_empty
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should keep an ordered set of keys" do
|
42
|
+
@table['b'] = {'foo' => 'bar'}
|
43
|
+
@table['a'] = {'foo' => 'baz'}
|
44
|
+
@table.keys.should == ['b', 'a']
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should generate incrementing ids" do
|
48
|
+
ids = []
|
49
|
+
4.times { ids << @table.generate_unique_id }
|
50
|
+
ids.should == [1, 2, 3, 4]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should query using a block supporting :eql comparisons" do
|
54
|
+
# For this release, only :eql comparisons are required
|
55
|
+
@table['a'] = {'foo' => 'bar', 'color' => 'blue'}
|
56
|
+
@table['b'] = {'foo' => 'baz', 'color' => 'blue'}
|
57
|
+
@table.query { |q|
|
58
|
+
q.add_condition('foo', :eql, 'bar')
|
59
|
+
}.should == [{'foo' => 'bar', 'color' => 'blue', :pk => 'a'}]
|
60
|
+
@table.query { |q|
|
61
|
+
q.add_condition('foo', :eql, 'baz')
|
62
|
+
}.should == [{'foo' => 'baz', 'color' => 'blue', :pk => 'b'}]
|
63
|
+
@table.query { |q|
|
64
|
+
q.add_condition('color', :eql, 'blue')
|
65
|
+
}.should == [
|
66
|
+
{'foo' => 'bar', 'color' => 'blue', :pk => 'a'},
|
67
|
+
{'foo' => 'baz', 'color' => 'blue', :pk => 'b'}]
|
68
|
+
@table.query { |q|
|
69
|
+
q.add_condition('foo', :eql, 'bar')
|
70
|
+
q.add_condition('color', :eql, 'blue')
|
71
|
+
}.should == [{'foo' => 'bar', 'color' => 'blue', :pk => 'a'}]
|
72
|
+
@table.query { |q|
|
73
|
+
q.add_condition('foo', :eql, 'bar')
|
74
|
+
q.add_condition('color', :eql, 'red')
|
75
|
+
}.should == []
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should query without a block" do
|
79
|
+
@table['a'] = {'foo' => 'bar', 'color' => 'blue'}
|
80
|
+
@table['b'] = {'foo' => 'baz', 'color' => 'blue'}
|
81
|
+
@table.query.should == [
|
82
|
+
{'foo' => 'bar', 'color' => 'blue', :pk => 'a'},
|
83
|
+
{'foo' => 'baz', 'color' => 'blue', :pk => 'b'}]
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "An OAuthFilter" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
CloudKit.setup_storage_adapter unless CloudKit.storage_adapter
|
7
|
+
@oauth_filtered_app = CloudKit::OAuthFilter.new(echo_env(CLOUDKIT_AUTH_KEY))
|
8
|
+
token = JSON.generate(
|
9
|
+
:secret => 'pfkkdhi9sl3r4s00',
|
10
|
+
:consumer_key => 'dpf43f3p2l4k3l03',
|
11
|
+
:consumer_secret => 'kd94hf93k423kf44',
|
12
|
+
:user_id => 'martino')
|
13
|
+
Rack::MockRequest.new(@oauth_filtered_app).get('/') # prime the storage
|
14
|
+
@store = @oauth_filtered_app.store
|
15
|
+
result = @store.put('/cloudkit_oauth_tokens/nnch734d00sl2jdk', :json => token)
|
16
|
+
@token_etag = result.parsed_content['etag']
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:each) do
|
20
|
+
CloudKit.storage_adapter.clear
|
21
|
+
@store.load_static_consumer
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should verify signatures" do
|
25
|
+
response = do_get
|
26
|
+
response.status.should == 200
|
27
|
+
response.body.should == 'martino'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should notify downstream nodes of its presence" do
|
31
|
+
app = CloudKit::OAuthFilter.new(echo_env(CLOUDKIT_VIA))
|
32
|
+
response = Rack::MockRequest.new(app).get('/')
|
33
|
+
response.body.should == CLOUDKIT_OAUTH_FILTER_KEY
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should not allow a nonce/timestamp combination to appear twice" do
|
37
|
+
do_get
|
38
|
+
response = do_get
|
39
|
+
response.body.should == ''
|
40
|
+
get_request_token
|
41
|
+
response = get_request_token
|
42
|
+
response.status.should == 401
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should add the remote user to the rack environment for verified requests" do
|
46
|
+
response = do_get
|
47
|
+
response.body.should == 'martino'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should allow requests for / to pass through" do
|
51
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get('/')
|
52
|
+
response.status.should == 200
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should reject unauthorized requests" do
|
56
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
57
|
+
'http://photos.example.net/photos?file=vacation.jpg&size=original' +
|
58
|
+
'&oauth_version=1.0' +
|
59
|
+
'&oauth_consumer_key=dpf43f3p2l4k3l03' +
|
60
|
+
'&oauth_token=nnch734d00sl2jdk' +
|
61
|
+
'&oauth_timestamp=1191242096' +
|
62
|
+
'&oauth_nonce=kllo9940pd9333jh' +
|
63
|
+
'&oauth_signature=fail'+
|
64
|
+
'&oauth_signature_method=HMAC-SHA1', 'X-Remote-User' => 'intruder') # TODO rework
|
65
|
+
response.body.should == ''
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "supporting OAuth Discovery" do
|
69
|
+
|
70
|
+
it "should set the auth challenge for unauthorized requests" do
|
71
|
+
app = CloudKit::OAuthFilter.new(
|
72
|
+
lambda {|env| [200, {}, [env[CLOUDKIT_AUTH_CHALLENGE]['WWW-Authenticate'] || '']]})
|
73
|
+
response = Rack::MockRequest.new(app).get(
|
74
|
+
'/items', 'HTTP_HOST' => 'example.org')
|
75
|
+
response.body.should == 'OAuth realm="http://example.org"'
|
76
|
+
app = CloudKit::OAuthFilter.new(
|
77
|
+
lambda {|env| [200, {}, [env[CLOUDKIT_AUTH_CHALLENGE]['Link'] || '']]})
|
78
|
+
response = Rack::MockRequest.new(app).get(
|
79
|
+
'/items', 'HTTP_HOST' => 'example.org')
|
80
|
+
response.body.should == '<http://example.org/oauth/meta>; rel="http://oauth.net/discovery/1.0/rel/provider"'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should provide XRD metadata on GET /oauth/meta" do
|
84
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
85
|
+
'/oauth/meta', 'HTTP_HOST' => 'example.org')
|
86
|
+
response.status.should == 200
|
87
|
+
doc = REXML::Document.new(response.body)
|
88
|
+
REXML::XPath.first(doc, '//XRD/Type').should_not be_nil
|
89
|
+
REXML::XPath.first(doc, '//XRD/Type').children[0].to_s.should == 'http://oauth.net/discovery/1.0'
|
90
|
+
REXML::XPath.first(doc, '//XRD/Service/Type').should_not be_nil
|
91
|
+
REXML::XPath.first(doc, '//XRD/Service/Type').children[0].to_s.should == 'http://oauth.net/discovery/1.0/rel/provider'
|
92
|
+
REXML::XPath.first(doc, '//XRD/Service/URI').should_not be_nil
|
93
|
+
REXML::XPath.first(doc, '//XRD/Service/URI').children[0].to_s.should == 'http://example.org/oauth'
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should respond to OAuth Discovery Draft 2 / XRDS-Simple Discovery" do
|
97
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
98
|
+
'/anything',
|
99
|
+
'HTTP_HOST' => 'example.org',
|
100
|
+
'HTTP_ACCEPT' => 'application/xrds+xml')
|
101
|
+
response.status.should == 200
|
102
|
+
response['X-XRDS-Location'].should == 'http://example.org/oauth'
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should provide a descriptor document on GET /oauth" do
|
106
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
107
|
+
'/oauth', 'HTTP_HOST' => 'example.org')
|
108
|
+
response.status.should == 200
|
109
|
+
response['Content-Type'].should == 'application/xrds+xml'
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should populate the static consumer on startup" do
|
113
|
+
response = @store.get('/cloudkit_oauth_consumers/cloudkitconsumer')
|
114
|
+
response.status.should == 200
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "supporting authorization" do
|
120
|
+
|
121
|
+
it "should generate request tokens" do
|
122
|
+
response = get_request_token
|
123
|
+
response.status.should == 201
|
124
|
+
token, secret = response.body.split('&')
|
125
|
+
token_parts = token.split('=')
|
126
|
+
secret_parts = secret.split('=')
|
127
|
+
token_parts.first.should == 'oauth_token'
|
128
|
+
secret_parts.first.should == 'oauth_token_secret'
|
129
|
+
token_parts.last.should_not be_nil
|
130
|
+
token_parts.last.should_not be_empty
|
131
|
+
secret_parts.last.should_not be_nil
|
132
|
+
secret_parts.last.should_not be_empty
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should not generate request tokens for invalid consumers" do
|
136
|
+
# this does not mean consumers must register, only that they
|
137
|
+
# should use the static value provided in the xrd document
|
138
|
+
# or one that is specified in the consumer database
|
139
|
+
pre_sign = Rack::Request.new(Rack::MockRequest.env_for(
|
140
|
+
'http://photos.example.net/oauth/request_tokens',
|
141
|
+
'Authorization' => 'OAuth realm="http://photos.example.net", ' +
|
142
|
+
'oauth_version="1.0", ' +
|
143
|
+
'oauth_consumer_key="mysteryconsumer", ' +
|
144
|
+
'oauth_timestamp="1191242096", ' +
|
145
|
+
'oauth_nonce="AAAAAAAAAAAAAAAAA", ' +
|
146
|
+
'oauth_signature_method="HMAC-SHA1"',
|
147
|
+
:method => "POST"))
|
148
|
+
signature = OAuth::Signature.build(pre_sign) do |token, consumer_key|
|
149
|
+
[nil, '']
|
150
|
+
end
|
151
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).post(
|
152
|
+
'http://photos.example.net/oauth/request_tokens?' +
|
153
|
+
'oauth_version=1.0' +
|
154
|
+
'&oauth_consumer_key=mysteryconsumer' +
|
155
|
+
'&oauth_timestamp=1191242096' +
|
156
|
+
'&oauth_nonce=AAAAAAAAAAAAAAAAA' +
|
157
|
+
'&oauth_signature=' + CGI.escape(signature.signature) +
|
158
|
+
'&oauth_signature_method=HMAC-SHA1')
|
159
|
+
response.status.should == 401
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should store request tokens for authorizaton" do
|
163
|
+
response = get_request_token
|
164
|
+
response.status.should == 201
|
165
|
+
token, secret = extract_token(response)
|
166
|
+
request_token = @store.get("/cloudkit_oauth_request_tokens/#{token}").parsed_content
|
167
|
+
request_token.should_not be_nil
|
168
|
+
request_token['secret'].should == secret
|
169
|
+
request_token['authorized_at'].should be_nil
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should redirect to login before allowing GET requests for request token authorization" do
|
173
|
+
response = get_request_token
|
174
|
+
token, secret = extract_token(response)
|
175
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
176
|
+
"/oauth/authorization?oauth_token=#{token}")
|
177
|
+
response.status.should == 302
|
178
|
+
response['Location'].should == '/login'
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should respond successfully to authorization GET requests for logged-in users with a valid request token" do
|
182
|
+
response = get_request_token
|
183
|
+
token, secret = extract_token(response)
|
184
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
185
|
+
"/oauth/authorization?oauth_token=#{token}", VALID_TEST_AUTH)
|
186
|
+
response.status.should == 200
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should reject authorization GET requests with invalid tokens" do
|
190
|
+
response = get_request_token
|
191
|
+
token, secret = extract_token(response)
|
192
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
193
|
+
"/oauth/authorization?oauth_token=fail", VALID_TEST_AUTH)
|
194
|
+
response.status.should == 401
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should authorize request tokens for verified requests" do
|
198
|
+
response = get_request_token
|
199
|
+
token, secret = extract_token(response)
|
200
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
201
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", VALID_TEST_AUTH)
|
202
|
+
response.status.should == 200
|
203
|
+
request_token = @store.get("/cloudkit_oauth_request_tokens/#{token}").parsed_content
|
204
|
+
request_token['authorized_at'].should_not be_nil
|
205
|
+
request_token['user_id'].should_not be_nil
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should removed denied request tokens" do
|
209
|
+
response = get_request_token
|
210
|
+
token, secret = extract_token(response)
|
211
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
212
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Deny", VALID_TEST_AUTH)
|
213
|
+
response.status.should == 200
|
214
|
+
response = @store.get("/cloudkit_oauth_request_tokens/#{token}")
|
215
|
+
response.status.should == 410
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should redirect to login for authorization PUT requests unless logged-in" do
|
219
|
+
response = get_request_token
|
220
|
+
token, secret = extract_token(response)
|
221
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
222
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve")
|
223
|
+
response.status.should == 302
|
224
|
+
response['Location'].should == '/login'
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should not create access tokens for request tokens that have already been authorized" do
|
228
|
+
response = get_request_token
|
229
|
+
token, secret = extract_token(response)
|
230
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
231
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", VALID_TEST_AUTH)
|
232
|
+
response.status.should == 200
|
233
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
234
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", VALID_TEST_AUTH)
|
235
|
+
response.status.should == 401
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should provide access tokens in exchange for authorized request tokens" do
|
239
|
+
response = get_access_token
|
240
|
+
response.status.should == 201
|
241
|
+
token, secret = extract_token(response)
|
242
|
+
token.should_not be_empty
|
243
|
+
secret.should_not be_empty
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should remove request tokens after creating access tokens" do
|
247
|
+
response = get_access_token
|
248
|
+
response.status.should == 201
|
249
|
+
request_tokens = @store.get('/cloudkit_oauth_request_tokens').parsed_content
|
250
|
+
request_tokens['uris'].size.should == 0
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def do_get
|
257
|
+
Rack::MockRequest.new(@oauth_filtered_app).get(
|
258
|
+
'http://photos.example.net/photos?file=vacation.jpg&size=original' +
|
259
|
+
'&oauth_version=1.0' +
|
260
|
+
'&oauth_consumer_key=dpf43f3p2l4k3l03' +
|
261
|
+
'&oauth_token=nnch734d00sl2jdk' +
|
262
|
+
'&oauth_timestamp=1191242096' +
|
263
|
+
'&oauth_nonce=kllo9940pd9333jh' +
|
264
|
+
'&oauth_signature=tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D' +
|
265
|
+
'&oauth_signature_method=HMAC-SHA1')
|
266
|
+
end
|
267
|
+
|
268
|
+
def get_request_token
|
269
|
+
pre_sign = Rack::Request.new(Rack::MockRequest.env_for(
|
270
|
+
'http://photos.example.net/oauth/request_tokens',
|
271
|
+
'Authorization' => 'OAuth realm="http://photos.example.net", ' +
|
272
|
+
'oauth_version="1.0", ' +
|
273
|
+
'oauth_consumer_key="cloudkitconsumer", ' +
|
274
|
+
'oauth_timestamp="1191242096", ' +
|
275
|
+
'oauth_nonce="AAAAAAAAAAAAAAAAA", ' +
|
276
|
+
'oauth_signature_method="HMAC-SHA1"',
|
277
|
+
:method => "POST"))
|
278
|
+
signature = OAuth::Signature.build(pre_sign) do |token, consumer_key|
|
279
|
+
[nil, '']
|
280
|
+
end
|
281
|
+
Rack::MockRequest.new(@oauth_filtered_app).post(
|
282
|
+
'http://photos.example.net/oauth/request_tokens?' +
|
283
|
+
'oauth_version=1.0' +
|
284
|
+
'&oauth_consumer_key=cloudkitconsumer' +
|
285
|
+
'&oauth_timestamp=1191242096' +
|
286
|
+
'&oauth_nonce=AAAAAAAAAAAAAAAAA' +
|
287
|
+
'&oauth_signature=' + CGI.escape(signature.signature) +
|
288
|
+
'&oauth_signature_method=HMAC-SHA1')
|
289
|
+
end
|
290
|
+
|
291
|
+
def get_access_token
|
292
|
+
response = get_request_token
|
293
|
+
token, secret = extract_token(response)
|
294
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
295
|
+
"/oauth/authorized_request_tokens/#{token}", VALID_TEST_AUTH)
|
296
|
+
response.status.should == 200
|
297
|
+
pre_sign = Rack::Request.new(Rack::MockRequest.env_for(
|
298
|
+
'http://photos.example.net/oauth/access_tokens',
|
299
|
+
'Authorization' => 'OAuth realm="http://photos.example.net", ' +
|
300
|
+
'oauth_version="1.0", ' +
|
301
|
+
'oauth_consumer_key="cloudkitconsumer", ' +
|
302
|
+
'oauth_token="' + token + '", ' +
|
303
|
+
'oauth_timestamp="1191242097", ' +
|
304
|
+
'oauth_nonce="AAAAAAAAAAAAAAAAA", ' +
|
305
|
+
'oauth_signature_method="HMAC-SHA1"',
|
306
|
+
:method => "POST"))
|
307
|
+
signature = OAuth::Signature.build(pre_sign) do |token, consumer_key|
|
308
|
+
[secret, '']
|
309
|
+
end
|
310
|
+
Rack::MockRequest.new(@oauth_filtered_app).post(
|
311
|
+
'http://photos.example.net/oauth/access_tokens?' +
|
312
|
+
'oauth_version=1.0' +
|
313
|
+
'&oauth_consumer_key=cloudkitconsumer' +
|
314
|
+
'&oauth_token=' + token +
|
315
|
+
'&oauth_timestamp=1191242097' +
|
316
|
+
'&oauth_nonce=AAAAAAAAAAAAAAAAA' +
|
317
|
+
'&oauth_signature=' + CGI.escape(signature.signature) +
|
318
|
+
'&oauth_signature_method=HMAC-SHA1')
|
319
|
+
end
|
320
|
+
|
321
|
+
def extract_token(response)
|
322
|
+
token, secret = response.body.split('&')
|
323
|
+
token_parts = token.split('=')
|
324
|
+
secret_parts = secret.split('=')
|
325
|
+
return token_parts.last, secret_parts.last
|
326
|
+
end
|