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.
Files changed (58) hide show
  1. data/CHANGES +11 -0
  2. data/README +7 -6
  3. data/Rakefile +13 -6
  4. data/TODO +7 -5
  5. data/cloudkit.gemspec +23 -23
  6. data/doc/curl.html +2 -2
  7. data/doc/index.html +4 -6
  8. data/examples/5.ru +2 -3
  9. data/examples/TOC +1 -3
  10. data/lib/cloudkit.rb +17 -10
  11. data/lib/cloudkit/constants.rb +0 -6
  12. data/lib/cloudkit/exceptions.rb +10 -0
  13. data/lib/cloudkit/flash_session.rb +1 -3
  14. data/lib/cloudkit/oauth_filter.rb +8 -16
  15. data/lib/cloudkit/oauth_store.rb +9 -13
  16. data/lib/cloudkit/openid_filter.rb +25 -7
  17. data/lib/cloudkit/openid_store.rb +14 -17
  18. data/lib/cloudkit/request.rb +6 -1
  19. data/lib/cloudkit/service.rb +15 -15
  20. data/lib/cloudkit/store.rb +97 -284
  21. data/lib/cloudkit/store/memory_table.rb +105 -0
  22. data/lib/cloudkit/store/resource.rb +256 -0
  23. data/lib/cloudkit/store/response_helpers.rb +0 -1
  24. data/lib/cloudkit/uri.rb +88 -0
  25. data/lib/cloudkit/user_store.rb +7 -14
  26. data/spec/ext_spec.rb +76 -0
  27. data/spec/flash_session_spec.rb +20 -0
  28. data/spec/memory_table_spec.rb +86 -0
  29. data/spec/oauth_filter_spec.rb +326 -0
  30. data/spec/oauth_store_spec.rb +10 -0
  31. data/spec/openid_filter_spec.rb +64 -0
  32. data/spec/openid_store_spec.rb +101 -0
  33. data/spec/rack_builder_spec.rb +39 -0
  34. data/spec/request_spec.rb +185 -0
  35. data/spec/resource_spec.rb +291 -0
  36. data/spec/service_spec.rb +974 -0
  37. data/{test/helper.rb → spec/spec_helper.rb} +14 -2
  38. data/spec/store_spec.rb +10 -0
  39. data/spec/uri_spec.rb +93 -0
  40. data/spec/user_store_spec.rb +10 -0
  41. data/spec/util_spec.rb +11 -0
  42. metadata +37 -61
  43. data/examples/6.ru +0 -10
  44. data/lib/cloudkit/store/adapter.rb +0 -8
  45. data/lib/cloudkit/store/extraction_view.rb +0 -57
  46. data/lib/cloudkit/store/sql_adapter.rb +0 -36
  47. data/test/ext_test.rb +0 -76
  48. data/test/flash_session_test.rb +0 -22
  49. data/test/oauth_filter_test.rb +0 -331
  50. data/test/oauth_store_test.rb +0 -12
  51. data/test/openid_filter_test.rb +0 -60
  52. data/test/openid_store_test.rb +0 -12
  53. data/test/rack_builder_test.rb +0 -41
  54. data/test/request_test.rb +0 -197
  55. data/test/service_test.rb +0 -971
  56. data/test/store_test.rb +0 -93
  57. data/test/user_store_test.rb +0 -12
  58. data/test/util_test.rb +0 -13
@@ -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 schema upgrades if required for future releases.
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
- login_view = ExtractionView.new(
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