cloudkit-jruby 0.11.2
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 +47 -0
- data/COPYING +20 -0
- data/README +84 -0
- data/Rakefile +42 -0
- data/TODO +21 -0
- data/cloudkit.gemspec +89 -0
- data/doc/curl.html +388 -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 +90 -0
- data/doc/main.css +151 -0
- data/doc/rest-api.html +467 -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 +9 -0
- data/examples/6.ru +11 -0
- data/examples/TOC +17 -0
- data/lib/cloudkit.rb +92 -0
- data/lib/cloudkit/constants.rb +34 -0
- data/lib/cloudkit/exceptions.rb +10 -0
- data/lib/cloudkit/flash_session.rb +20 -0
- data/lib/cloudkit/oauth_filter.rb +266 -0
- data/lib/cloudkit/oauth_store.rb +48 -0
- data/lib/cloudkit/openid_filter.rb +236 -0
- data/lib/cloudkit/openid_store.rb +100 -0
- data/lib/cloudkit/rack/builder.rb +120 -0
- data/lib/cloudkit/rack/router.rb +20 -0
- data/lib/cloudkit/request.rb +177 -0
- data/lib/cloudkit/service.rb +162 -0
- data/lib/cloudkit/store.rb +349 -0
- data/lib/cloudkit/store/memory_table.rb +99 -0
- data/lib/cloudkit/store/resource.rb +269 -0
- data/lib/cloudkit/store/response.rb +52 -0
- data/lib/cloudkit/store/response_helpers.rb +84 -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/uri.rb +88 -0
- data/lib/cloudkit/user_store.rb +37 -0
- data/lib/cloudkit/util.rb +25 -0
- 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 +81 -0
- data/spec/openid_store_spec.rb +101 -0
- data/spec/rack_builder_spec.rb +39 -0
- data/spec/request_spec.rb +191 -0
- data/spec/resource_spec.rb +310 -0
- data/spec/service_spec.rb +1039 -0
- data/spec/spec_helper.rb +32 -0
- 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 +180 -0
@@ -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
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "An OpenIDFilter" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
openid_app = Rack::Builder.new {
|
7
|
+
use Rack::Lint
|
8
|
+
use Rack::Session::Pool
|
9
|
+
use CloudKit::OpenIDFilter, :allow => ['/foo']
|
10
|
+
run echo_env(CLOUDKIT_AUTH_KEY)
|
11
|
+
}
|
12
|
+
@request = Rack::MockRequest.new(openid_app)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should allow root url pass through" do
|
16
|
+
response = @request.get('/')
|
17
|
+
response.status.should == 200
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow pass through of URIs defined in :allow" do
|
21
|
+
response = @request.get('/foo')
|
22
|
+
response.status.should == 200
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should allow pass through of URIs defined in bypass route callback" do
|
26
|
+
openid_app = Rack::Builder.new {
|
27
|
+
use Rack::Lint
|
28
|
+
use Rack::Session::Pool
|
29
|
+
use CloudKit::OpenIDFilter, :allow => ['/foo'] do |url|
|
30
|
+
['/bar'].include? url
|
31
|
+
end
|
32
|
+
run echo_env(CLOUDKIT_AUTH_KEY)
|
33
|
+
}
|
34
|
+
request = Rack::MockRequest.new(openid_app)
|
35
|
+
response = request.get('/bar')
|
36
|
+
response.status.should == 200
|
37
|
+
response = request.get('/foo')
|
38
|
+
response.status.should == 200
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should redirect to the login page if authorization is required" do
|
42
|
+
response = @request.get('/protected')
|
43
|
+
response.status.should == 302
|
44
|
+
response['Location'].should == '/login'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should notify downstream nodes of its presence" do
|
48
|
+
app = Rack::Builder.new do
|
49
|
+
use Rack::Session::Pool
|
50
|
+
use CloudKit::OpenIDFilter
|
51
|
+
run echo_env(CLOUDKIT_VIA)
|
52
|
+
end
|
53
|
+
response = Rack::MockRequest.new(app).get('/')
|
54
|
+
response.body.should == CLOUDKIT_OPENID_FILTER_KEY
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "with upstream authorization middleware" do
|
58
|
+
|
59
|
+
it "should allow pass through if the auth env variable is populated" do
|
60
|
+
response = @request.get('/protected', VALID_TEST_AUTH)
|
61
|
+
response.status.should == 200
|
62
|
+
response.body.should == TEST_REMOTE_USER
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should return the auth challenge header" do
|
66
|
+
response = @request.get('/protected',
|
67
|
+
CLOUDKIT_VIA => CLOUDKIT_OAUTH_FILTER_KEY,
|
68
|
+
CLOUDKIT_AUTH_CHALLENGE => {'WWW-Authenticate' => 'etc.'})
|
69
|
+
response['WWW-Authenticate'].should_not be_nil
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return a 401 status if authorization is required" do
|
73
|
+
response = @request.get('/protected',
|
74
|
+
CLOUDKIT_VIA => CLOUDKIT_OAUTH_FILTER_KEY,
|
75
|
+
CLOUDKIT_AUTH_CHALLENGE => {'WWW-Authenticate' => 'etc.'})
|
76
|
+
response.status.should == 401
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "An OpenIDStore" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
CloudKit.setup_storage_adapter unless CloudKit.storage_adapter
|
7
|
+
@store = CloudKit::OpenIDStore.new
|
8
|
+
@server = 'http://openid.claimid.com/server'
|
9
|
+
@handle = "{HMAC-SHA1}{736kwv3j}{wbhwEK==}"
|
10
|
+
@secret = "\350\068\753\436\567\8327\232\241\025\254\3117&\016\031\355#sV"
|
11
|
+
@issued = Time.now
|
12
|
+
@lifetime = 120960
|
13
|
+
@type = "HMAC-SHA1"
|
14
|
+
@association = OpenID::Association.new(@handle, @secret, @issued, @lifetime, @type)
|
15
|
+
@store.store_association(@server, @association)
|
16
|
+
@result = CloudKit::Resource.all.first.parsed_json
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:each) do
|
20
|
+
CloudKit.storage_adapter.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should know its version" do
|
24
|
+
@store.version.should == 1
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when storing an association" do
|
28
|
+
|
29
|
+
it "should base64 encode the handle" do
|
30
|
+
@result['handle'].should == Base64.encode64(@handle)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should base64 encode the secret" do
|
34
|
+
@result['secret'].should == Base64.encode64(@secret)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should convert the issue time to an integer" do
|
38
|
+
@result['issued'].should == @issued.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should store the lifetime as given" do
|
42
|
+
@result['lifetime'].should == @lifetime
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should store the association type as given" do
|
46
|
+
@result['assoc_type'].should == @type
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should remove previous associations with the given server_url and association handle" do
|
50
|
+
association = OpenID::Association.new(@handle, @secret, @issued, @lifetime, @type)
|
51
|
+
@store.store_association(@server, association)
|
52
|
+
associations = CloudKit::Resource.current(
|
53
|
+
:collection_reference => "/cloudkit_openid_associations")
|
54
|
+
associations.size.should == 1
|
55
|
+
result = associations.first.parsed_json
|
56
|
+
result['secret'].should == Base64.encode64(@secret)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "when removing an association" do
|
61
|
+
|
62
|
+
it "should succeed" do
|
63
|
+
@store.remove_association(@server, @association.handle)
|
64
|
+
associations = CloudKit::Resource.current(
|
65
|
+
:collection_reference => "/cloudkit_openid_associations")
|
66
|
+
associations.size.should == 0
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when finding an association" do
|
71
|
+
|
72
|
+
it "should return the correct object" do
|
73
|
+
association = @store.get_association(@server, @association.handle)
|
74
|
+
association.should == @association
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "when using a nonce" do
|
79
|
+
|
80
|
+
before(:each) do
|
81
|
+
@time = Time.now.to_i
|
82
|
+
@salt = 'salt'
|
83
|
+
@store.use_nonce(@server, @time, @salt)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should store the nonce" do
|
87
|
+
nonce = CloudKit::Resource.first(
|
88
|
+
:collection_reference => "/cloudkit_openid_nonces",
|
89
|
+
:deleted => false)
|
90
|
+
nonce.should_not be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should reject the nonce if it has already been used" do
|
94
|
+
@store.use_nonce(@server, @time, @salt).should_not be_nil
|
95
|
+
nonces = CloudKit::Resource.all(
|
96
|
+
:collection_reference => "/cloudkit_openid_nonces",
|
97
|
+
:deleted => false)
|
98
|
+
nonces.size.should == 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|