cloudkit 0.9.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 +2 -0
- data/COPYING +20 -0
- data/README +55 -0
- data/Rakefile +35 -0
- data/TODO +22 -0
- data/cloudkit.gemspec +82 -0
- data/doc/curl.html +329 -0
- data/doc/images/example-code.gif +0 -0
- data/doc/images/json-title.gif +0 -0
- data/doc/images/oauth-discovery-logo.gif +0 -0
- data/doc/images/openid-logo.gif +0 -0
- data/doc/index.html +87 -0
- data/doc/main.css +151 -0
- data/doc/rest-api.html +358 -0
- data/examples/1.ru +3 -0
- data/examples/2.ru +3 -0
- data/examples/3.ru +6 -0
- data/examples/4.ru +5 -0
- data/examples/5.ru +10 -0
- data/examples/6.ru +10 -0
- data/examples/TOC +17 -0
- data/lib/cloudkit.rb +74 -0
- data/lib/cloudkit/flash_session.rb +22 -0
- data/lib/cloudkit/oauth_filter.rb +273 -0
- data/lib/cloudkit/oauth_store.rb +56 -0
- data/lib/cloudkit/openid_filter.rb +198 -0
- data/lib/cloudkit/openid_store.rb +101 -0
- data/lib/cloudkit/rack/builder.rb +120 -0
- data/lib/cloudkit/rack/router.rb +20 -0
- data/lib/cloudkit/request.rb +159 -0
- data/lib/cloudkit/service.rb +135 -0
- data/lib/cloudkit/store.rb +459 -0
- data/lib/cloudkit/store/adapter.rb +9 -0
- data/lib/cloudkit/store/extraction_view.rb +57 -0
- data/lib/cloudkit/store/response.rb +51 -0
- data/lib/cloudkit/store/response_helpers.rb +72 -0
- data/lib/cloudkit/store/sql_adapter.rb +36 -0
- data/lib/cloudkit/templates/authorize_request_token.erb +19 -0
- data/lib/cloudkit/templates/oauth_descriptor.erb +43 -0
- data/lib/cloudkit/templates/oauth_meta.erb +8 -0
- data/lib/cloudkit/templates/openid_login.erb +31 -0
- data/lib/cloudkit/templates/request_authorization.erb +23 -0
- data/lib/cloudkit/templates/request_token_denied.erb +18 -0
- data/lib/cloudkit/user_store.rb +44 -0
- data/lib/cloudkit/util.rb +60 -0
- data/test/ext_test.rb +57 -0
- data/test/flash_session_test.rb +22 -0
- data/test/helper.rb +50 -0
- data/test/oauth_filter_test.rb +331 -0
- data/test/oauth_store_test.rb +12 -0
- data/test/openid_filter_test.rb +54 -0
- data/test/openid_store_test.rb +12 -0
- data/test/rack_builder_test.rb +41 -0
- data/test/request_test.rb +197 -0
- data/test/service_test.rb +718 -0
- data/test/store_test.rb +99 -0
- data/test/user_store_test.rb +12 -0
- data/test/util_test.rb +13 -0
- metadata +190 -0
data/test/ext_test.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'helper'
|
2
|
+
class ExtTest < Test::Unit::TestCase
|
3
|
+
|
4
|
+
context "A Hash" do
|
5
|
+
|
6
|
+
should "re-key an entry if it exists" do
|
7
|
+
x = {:a => 1, :b => 2}
|
8
|
+
x.rekey!(:b, :c)
|
9
|
+
assert x == {:a => 1, :c => 2}
|
10
|
+
x.rekey!(:d, :b)
|
11
|
+
assert x == {:a => 1, :c => 2}
|
12
|
+
end
|
13
|
+
|
14
|
+
should "merge conditionally" do
|
15
|
+
x = {:a => 1}
|
16
|
+
y = {:b => 2}
|
17
|
+
x.filter_merge!(:c => y[:c])
|
18
|
+
assert x == {:a => 1}
|
19
|
+
x.filter_merge!(:c => y[:b])
|
20
|
+
assert x == {:a => 1, :c => 2}
|
21
|
+
x = {}.filter_merge!(:a => 1)
|
22
|
+
assert x == {:a => 1}
|
23
|
+
end
|
24
|
+
|
25
|
+
should "exclude pairs using a single key" do
|
26
|
+
x = {:a => 1, :b => 2}
|
27
|
+
y = x.excluding(:b)
|
28
|
+
assert y == {:a => 1}
|
29
|
+
end
|
30
|
+
|
31
|
+
should "exclude pairs using a list of keys" do
|
32
|
+
x = {:a => 1, :b => 2, :c => 3}
|
33
|
+
y = x.excluding(:b, :c)
|
34
|
+
assert y == {:a => 1}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "An Array" do
|
39
|
+
|
40
|
+
should "exclude elements" do
|
41
|
+
x = [0, 1, 2, 3]
|
42
|
+
y = x.excluding(1, 3)
|
43
|
+
assert_equal [0, 2], y
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "An Object" do
|
48
|
+
|
49
|
+
should "try" do
|
50
|
+
x = {:a => 'a'}
|
51
|
+
result = x[:a].try(:upcase)
|
52
|
+
assert_equal 'A', result
|
53
|
+
assert_nothing_raised {x[:b].try(:upcase)}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'helper'
|
2
|
+
class FlashSessionTest < Test::Unit::TestCase
|
3
|
+
|
4
|
+
context "A FlashSession" do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@flash = CloudKit::FlashSession.new
|
8
|
+
end
|
9
|
+
|
10
|
+
should "accept a value for a key" do
|
11
|
+
@flash['greeting'] = 'hello'
|
12
|
+
assert_equal 'hello', @flash['greeting']
|
13
|
+
end
|
14
|
+
|
15
|
+
should "erase a key/value pair after access" do
|
16
|
+
@flash['greeting'] = 'hello'
|
17
|
+
x = @flash['greeting']
|
18
|
+
assert_nil @flash['greeting']
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__)) + '/../lib'
|
2
|
+
require 'cloudkit'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'shoulda'
|
5
|
+
require 'rexml/document'
|
6
|
+
|
7
|
+
def auth_key; "cloudkit.user"; end
|
8
|
+
def remote_user; "/cloudkit_users/abcdef"; end
|
9
|
+
|
10
|
+
def echo_text(text)
|
11
|
+
lambda {|env| [200, {'Content-Type' => 'text/html'}, [text]]}
|
12
|
+
end
|
13
|
+
|
14
|
+
def echo_env(key)
|
15
|
+
lambda {|env| [200, {'Content-Type' => 'text/html'}, [env[key] || '']]}
|
16
|
+
end
|
17
|
+
|
18
|
+
def auth
|
19
|
+
{auth_key => remote_user}
|
20
|
+
end
|
21
|
+
|
22
|
+
def plain_service
|
23
|
+
Rack::Builder.new do
|
24
|
+
use Rack::Config do |env|
|
25
|
+
env['cloudkit.storage.uri'] = 'sqlite://service.db'
|
26
|
+
end
|
27
|
+
use CloudKit::Service, :collections => [:items, :things]
|
28
|
+
run echo_text('martino')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def authed_service
|
33
|
+
Rack::Builder.new do
|
34
|
+
use Rack::Config do |env|
|
35
|
+
env['cloudkit.storage.uri'] = 'sqlite://service.db'
|
36
|
+
r = CloudKit::Request.new(env)
|
37
|
+
r.announce_auth('cloudkit.filter.oauth') # mock
|
38
|
+
end
|
39
|
+
use CloudKit::Service, :collections => [:items, :things]
|
40
|
+
run echo_text('martino')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def openid_app
|
45
|
+
Rack::Builder.new do
|
46
|
+
use Rack::Session::Pool
|
47
|
+
use CloudKit::OpenIDFilter
|
48
|
+
run echo_env(auth_key)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,331 @@
|
|
1
|
+
require 'helper'
|
2
|
+
class OAuthFilterTest < Test::Unit::TestCase
|
3
|
+
|
4
|
+
context "An OAuthFilter" do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@oauth_filtered_app = CloudKit::OAuthFilter.new(echo_env(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
|
+
teardown do
|
20
|
+
@store.reset!
|
21
|
+
@store.load_static_consumer
|
22
|
+
end
|
23
|
+
|
24
|
+
should "verify signatures" do
|
25
|
+
response = do_get
|
26
|
+
assert_equal 200, response.status
|
27
|
+
assert_equal 'martino', response.body
|
28
|
+
end
|
29
|
+
|
30
|
+
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
|
+
assert_equal 'cloudkit.filter.oauth', response.body
|
34
|
+
end
|
35
|
+
|
36
|
+
should "not allow a nonce/timestamp combination to appear twice" do
|
37
|
+
do_get
|
38
|
+
response = do_get
|
39
|
+
assert_equal '', response.body
|
40
|
+
get_request_token
|
41
|
+
response = get_request_token
|
42
|
+
assert_equal 401, response.status
|
43
|
+
end
|
44
|
+
|
45
|
+
should "add the remote user to the rack environment for verified requests" do
|
46
|
+
response = do_get
|
47
|
+
assert_equal 'martino', response.body
|
48
|
+
end
|
49
|
+
|
50
|
+
should "allow requests for / to pass through" do
|
51
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get('/')
|
52
|
+
assert_equal 200, response.status
|
53
|
+
end
|
54
|
+
|
55
|
+
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
|
+
assert_equal '', response.body
|
66
|
+
end
|
67
|
+
|
68
|
+
context "supporting OAuth Discovery" do
|
69
|
+
|
70
|
+
should "set the auth challenge for unauthorized requests" do
|
71
|
+
app = CloudKit::OAuthFilter.new(
|
72
|
+
lambda {|env| [200, {}, [env['cloudkit.challenge']['WWW-Authenticate'] || '']]})
|
73
|
+
response = Rack::MockRequest.new(app).get(
|
74
|
+
'/items', 'HTTP_HOST' => 'example.org')
|
75
|
+
assert_equal 'OAuth realm="http://example.org"', response.body
|
76
|
+
app = CloudKit::OAuthFilter.new(
|
77
|
+
lambda {|env| [200, {}, [env['cloudkit.challenge']['Link'] || '']]})
|
78
|
+
response = Rack::MockRequest.new(app).get(
|
79
|
+
'/items', 'HTTP_HOST' => 'example.org')
|
80
|
+
assert_equal '<http://example.org/oauth/meta>; rel="http://oauth.net/discovery/1.0/rel/provider"',
|
81
|
+
response.body
|
82
|
+
end
|
83
|
+
|
84
|
+
should "provide XRD metadata on GET /oauth/meta" do
|
85
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
86
|
+
'/oauth/meta', 'HTTP_HOST' => 'example.org')
|
87
|
+
assert_equal 200, response.status
|
88
|
+
doc = REXML::Document.new(response.body)
|
89
|
+
assert REXML::XPath.first(doc, '//XRD/Type')
|
90
|
+
assert_equal 'http://oauth.net/discovery/1.0',
|
91
|
+
REXML::XPath.first(doc, '//XRD/Type').children[0].to_s
|
92
|
+
assert REXML::XPath.first(doc, '//XRD/Service/Type')
|
93
|
+
assert_equal 'http://oauth.net/discovery/1.0/rel/provider',
|
94
|
+
REXML::XPath.first(doc, '//XRD/Service/Type').children[0].to_s
|
95
|
+
assert REXML::XPath.first(doc, '//XRD/Service/URI')
|
96
|
+
assert_equal 'http://example.org/oauth',
|
97
|
+
REXML::XPath.first(doc, '//XRD/Service/URI').children[0].to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
should "respond to OAuth Discovery Draft 2 / XRDS-Simple Discovery" do
|
101
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
102
|
+
'/anything',
|
103
|
+
'HTTP_HOST' => 'example.org',
|
104
|
+
'HTTP_ACCEPT' => 'application/xrds+xml')
|
105
|
+
assert 200, response.status
|
106
|
+
assert_equal 'http://example.org/oauth', response['X-XRDS-Location']
|
107
|
+
end
|
108
|
+
|
109
|
+
should "provide a descriptor document on GET /oauth" do
|
110
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
111
|
+
'/oauth', 'HTTP_HOST' => 'example.org')
|
112
|
+
assert_equal 200, response.status
|
113
|
+
assert_equal 'application/xrds+xml', response['Content-Type']
|
114
|
+
end
|
115
|
+
|
116
|
+
should "populate the static consumer on startup" do
|
117
|
+
response = @store.get('/cloudkit_oauth_consumers/cloudkitconsumer')
|
118
|
+
assert_equal 200, response.status
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
context "supporting authorization" do
|
124
|
+
|
125
|
+
should "generate request tokens" do
|
126
|
+
response = get_request_token
|
127
|
+
assert_equal 201, response.status
|
128
|
+
token, secret = response.body.split('&')
|
129
|
+
token_parts = token.split('=')
|
130
|
+
secret_parts = secret.split('=')
|
131
|
+
assert_equal 'oauth_token', token_parts.first
|
132
|
+
assert_equal 'oauth_token_secret', secret_parts.first
|
133
|
+
assert token_parts.last
|
134
|
+
assert !token_parts.last.empty?
|
135
|
+
assert secret_parts.last
|
136
|
+
assert !secret_parts.last.empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
should "not generate request tokens for invalid consumers" do
|
140
|
+
# this does not mean consumers must register, only that they
|
141
|
+
# should use the static value provided in the xrd document
|
142
|
+
# or one that is specified in the consumer database
|
143
|
+
pre_sign = Rack::Request.new(Rack::MockRequest.env_for(
|
144
|
+
'http://photos.example.net/oauth/request_tokens',
|
145
|
+
'Authorization' => 'OAuth realm="http://photos.example.net", ' +
|
146
|
+
'oauth_version="1.0", ' +
|
147
|
+
'oauth_consumer_key="mysteryconsumer", ' +
|
148
|
+
'oauth_timestamp="1191242096", ' +
|
149
|
+
'oauth_nonce="AAAAAAAAAAAAAAAAA", ' +
|
150
|
+
'oauth_signature_method="HMAC-SHA1"',
|
151
|
+
:method => "POST"))
|
152
|
+
signature = OAuth::Signature.build(pre_sign) do |token, consumer_key|
|
153
|
+
[nil, '']
|
154
|
+
end
|
155
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).post(
|
156
|
+
'http://photos.example.net/oauth/request_tokens?' +
|
157
|
+
'oauth_version=1.0' +
|
158
|
+
'&oauth_consumer_key=mysteryconsumer' +
|
159
|
+
'&oauth_timestamp=1191242096' +
|
160
|
+
'&oauth_nonce=AAAAAAAAAAAAAAAAA' +
|
161
|
+
'&oauth_signature=' + CGI.escape(signature.signature) +
|
162
|
+
'&oauth_signature_method=HMAC-SHA1')
|
163
|
+
assert_equal 401, response.status
|
164
|
+
end
|
165
|
+
|
166
|
+
should "store request tokens for authorizaton" do
|
167
|
+
response = get_request_token
|
168
|
+
assert_equal 201, response.status
|
169
|
+
token, secret = extract_token(response)
|
170
|
+
request_token = @store.get("/cloudkit_oauth_request_tokens/#{token}").parsed_content
|
171
|
+
assert request_token
|
172
|
+
assert_equal secret, request_token['secret']
|
173
|
+
assert !request_token['authorized_at']
|
174
|
+
end
|
175
|
+
|
176
|
+
should "redirect to login before allowing GET requests for request token authorization" do
|
177
|
+
response = get_request_token
|
178
|
+
token, secret = extract_token(response)
|
179
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
180
|
+
"/oauth/authorization?oauth_token=#{token}")
|
181
|
+
assert_equal 302, response.status
|
182
|
+
assert_equal '/login', response['Location']
|
183
|
+
end
|
184
|
+
|
185
|
+
should "respond successfully to authorization GET requests for logged-in users with a valid request token" do
|
186
|
+
response = get_request_token
|
187
|
+
token, secret = extract_token(response)
|
188
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
189
|
+
"/oauth/authorization?oauth_token=#{token}", auth)
|
190
|
+
assert_equal 200, response.status
|
191
|
+
end
|
192
|
+
|
193
|
+
should "reject authorization GET requests with invalid tokens" do
|
194
|
+
response = get_request_token
|
195
|
+
token, secret = extract_token(response)
|
196
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
197
|
+
"/oauth/authorization?oauth_token=fail", auth)
|
198
|
+
assert_equal 401, response.status
|
199
|
+
end
|
200
|
+
|
201
|
+
should "authorize request tokens for verified requests" do
|
202
|
+
response = get_request_token
|
203
|
+
token, secret = extract_token(response)
|
204
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
205
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", auth)
|
206
|
+
assert_equal 200, response.status
|
207
|
+
request_token = @store.get("/cloudkit_oauth_request_tokens/#{token}").parsed_content
|
208
|
+
assert request_token['authorized_at']
|
209
|
+
assert request_token['user_id']
|
210
|
+
end
|
211
|
+
|
212
|
+
should "removed denied request tokens" do
|
213
|
+
response = get_request_token
|
214
|
+
token, secret = extract_token(response)
|
215
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
216
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Deny", auth)
|
217
|
+
assert_equal 200, response.status
|
218
|
+
request_token = @store.get("/cloudkit_oauth_request_tokens/#{token}").parsed_content
|
219
|
+
assert 410, response.status
|
220
|
+
end
|
221
|
+
|
222
|
+
should "redirect to login for authorization PUT requests unless logged-in" do
|
223
|
+
response = get_request_token
|
224
|
+
token, secret = extract_token(response)
|
225
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
226
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve")
|
227
|
+
assert_equal 302, response.status
|
228
|
+
assert_equal '/login', response['Location']
|
229
|
+
end
|
230
|
+
|
231
|
+
should "not create access tokens for request tokens that have already been authorized" do
|
232
|
+
response = get_request_token
|
233
|
+
token, secret = extract_token(response)
|
234
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
235
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", auth)
|
236
|
+
assert_equal 200, response.status
|
237
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
238
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", auth)
|
239
|
+
assert_equal 401, response.status
|
240
|
+
end
|
241
|
+
|
242
|
+
should "provide access tokens in exchange for authorized request tokens" do
|
243
|
+
response = get_access_token
|
244
|
+
assert_equal 201, response.status
|
245
|
+
token, secret = extract_token(response)
|
246
|
+
assert !token.empty?
|
247
|
+
assert !secret.empty?
|
248
|
+
end
|
249
|
+
|
250
|
+
should "remove request tokens after creating access tokens" do
|
251
|
+
response = get_access_token
|
252
|
+
assert_equal 201, response.status
|
253
|
+
request_tokens = @store.get('/cloudkit_oauth_request_tokens').parsed_content
|
254
|
+
assert_equal 0, request_tokens['uris'].size
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def do_get
|
261
|
+
Rack::MockRequest.new(@oauth_filtered_app).get(
|
262
|
+
'http://photos.example.net/photos?file=vacation.jpg&size=original' +
|
263
|
+
'&oauth_version=1.0' +
|
264
|
+
'&oauth_consumer_key=dpf43f3p2l4k3l03' +
|
265
|
+
'&oauth_token=nnch734d00sl2jdk' +
|
266
|
+
'&oauth_timestamp=1191242096' +
|
267
|
+
'&oauth_nonce=kllo9940pd9333jh' +
|
268
|
+
'&oauth_signature=tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D' +
|
269
|
+
'&oauth_signature_method=HMAC-SHA1')
|
270
|
+
end
|
271
|
+
|
272
|
+
def get_request_token
|
273
|
+
pre_sign = Rack::Request.new(Rack::MockRequest.env_for(
|
274
|
+
'http://photos.example.net/oauth/request_tokens',
|
275
|
+
'Authorization' => 'OAuth realm="http://photos.example.net", ' +
|
276
|
+
'oauth_version="1.0", ' +
|
277
|
+
'oauth_consumer_key="cloudkitconsumer", ' +
|
278
|
+
'oauth_timestamp="1191242096", ' +
|
279
|
+
'oauth_nonce="AAAAAAAAAAAAAAAAA", ' +
|
280
|
+
'oauth_signature_method="HMAC-SHA1"',
|
281
|
+
:method => "POST"))
|
282
|
+
signature = OAuth::Signature.build(pre_sign) do |token, consumer_key|
|
283
|
+
[nil, '']
|
284
|
+
end
|
285
|
+
Rack::MockRequest.new(@oauth_filtered_app).post(
|
286
|
+
'http://photos.example.net/oauth/request_tokens?' +
|
287
|
+
'oauth_version=1.0' +
|
288
|
+
'&oauth_consumer_key=cloudkitconsumer' +
|
289
|
+
'&oauth_timestamp=1191242096' +
|
290
|
+
'&oauth_nonce=AAAAAAAAAAAAAAAAA' +
|
291
|
+
'&oauth_signature=' + CGI.escape(signature.signature) +
|
292
|
+
'&oauth_signature_method=HMAC-SHA1')
|
293
|
+
end
|
294
|
+
|
295
|
+
def get_access_token
|
296
|
+
response = get_request_token
|
297
|
+
token, secret = extract_token(response)
|
298
|
+
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
299
|
+
"/oauth/authorized_request_tokens/#{token}", auth)
|
300
|
+
assert_equal 200, response.status
|
301
|
+
pre_sign = Rack::Request.new(Rack::MockRequest.env_for(
|
302
|
+
'http://photos.example.net/oauth/access_tokens',
|
303
|
+
'Authorization' => 'OAuth realm="http://photos.example.net", ' +
|
304
|
+
'oauth_version="1.0", ' +
|
305
|
+
'oauth_consumer_key="cloudkitconsumer", ' +
|
306
|
+
'oauth_token="' + token + '", ' +
|
307
|
+
'oauth_timestamp="1191242097", ' +
|
308
|
+
'oauth_nonce="AAAAAAAAAAAAAAAAA", ' +
|
309
|
+
'oauth_signature_method="HMAC-SHA1"',
|
310
|
+
:method => "POST"))
|
311
|
+
signature = OAuth::Signature.build(pre_sign) do |token, consumer_key|
|
312
|
+
[secret, '']
|
313
|
+
end
|
314
|
+
Rack::MockRequest.new(@oauth_filtered_app).post(
|
315
|
+
'http://photos.example.net/oauth/access_tokens?' +
|
316
|
+
'oauth_version=1.0' +
|
317
|
+
'&oauth_consumer_key=cloudkitconsumer' +
|
318
|
+
'&oauth_token=' + token +
|
319
|
+
'&oauth_timestamp=1191242097' +
|
320
|
+
'&oauth_nonce=AAAAAAAAAAAAAAAAA' +
|
321
|
+
'&oauth_signature=' + CGI.escape(signature.signature) +
|
322
|
+
'&oauth_signature_method=HMAC-SHA1')
|
323
|
+
end
|
324
|
+
|
325
|
+
def extract_token(response)
|
326
|
+
token, secret = response.body.split('&')
|
327
|
+
token_parts = token.split('=')
|
328
|
+
secret_parts = secret.split('=')
|
329
|
+
return token_parts.last, secret_parts.last
|
330
|
+
end
|
331
|
+
end
|