cloudkit 0.10.1 → 0.11.0

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