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
@@ -0,0 +1,198 @@
|
|
1
|
+
module CloudKit
|
2
|
+
|
3
|
+
# An OpenIDFilter provides OpenID authentication, listening for upstream
|
4
|
+
# OAuth authentication and bypassing if already authorized.
|
5
|
+
#
|
6
|
+
# Responds to the following URIs:
|
7
|
+
# /login
|
8
|
+
# /logout
|
9
|
+
# /openid_complete
|
10
|
+
#
|
11
|
+
class OpenIDFilter
|
12
|
+
include Util
|
13
|
+
|
14
|
+
@@lock = Mutex.new
|
15
|
+
@@store = nil
|
16
|
+
|
17
|
+
def initialize(app, options={})
|
18
|
+
@app = app; @options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
@@lock.synchronize do
|
23
|
+
@@store = OpenIDStore.new(env[storage_uri_key])
|
24
|
+
@users = UserStore.new(env[storage_uri_key])
|
25
|
+
@@store.get_association('x') rescue nil # refresh sqlite3
|
26
|
+
end unless @@store
|
27
|
+
|
28
|
+
request = Request.new(env)
|
29
|
+
request.announce_auth(openid_filter_key)
|
30
|
+
|
31
|
+
case request
|
32
|
+
when r(:get, request.login_url); request_login(request)
|
33
|
+
when r(:post, request.login_url); begin_openid_login(request)
|
34
|
+
when r(:get, '/openid_complete'); complete_openid_login(request)
|
35
|
+
when r(:post, request.logout_url); logout(request)
|
36
|
+
else
|
37
|
+
if (root_request?(request) || valid_auth_key?(request) || logged_in?(request))
|
38
|
+
@app.call(env)
|
39
|
+
else
|
40
|
+
if request.env[challenge_key]
|
41
|
+
store_location(request)
|
42
|
+
erb(request, :openid_login, request.env[challenge_key], 401)
|
43
|
+
elsif !request.via.include?(oauth_filter_key)
|
44
|
+
store_location(request)
|
45
|
+
login_redirect(request)
|
46
|
+
else
|
47
|
+
[500, {}, ['server misconfigured']]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def logout(request)
|
54
|
+
user_uri = request.session.delete('user_uri')
|
55
|
+
result = @users.get(user_uri)
|
56
|
+
user = result.parsed_content
|
57
|
+
user.delete('remember_me_token')
|
58
|
+
user.delete('remember_me_expiration')
|
59
|
+
json = JSON.generate(user)
|
60
|
+
@users.put(user_uri, :etag => result.etag, :json => json)
|
61
|
+
|
62
|
+
request.env[auth_key] = nil
|
63
|
+
request.flash['info'] = 'You have been logged out.'
|
64
|
+
response = Rack::Response.new([], 302, {'Location' => request.login_url})
|
65
|
+
response.delete_cookie('remember_me')
|
66
|
+
response.finish
|
67
|
+
end
|
68
|
+
|
69
|
+
def request_login(request)
|
70
|
+
erb(request, :openid_login)
|
71
|
+
end
|
72
|
+
|
73
|
+
def begin_openid_login(request)
|
74
|
+
begin
|
75
|
+
response = openid_consumer(request).begin request[:openid_url]
|
76
|
+
rescue => e
|
77
|
+
request.flash[:error] = e
|
78
|
+
return login_redirect(request)
|
79
|
+
end
|
80
|
+
|
81
|
+
redirect_url = response.redirect_url(base_url(request), full_url(request))
|
82
|
+
[302, {'Location' => redirect_url}, []]
|
83
|
+
end
|
84
|
+
|
85
|
+
def complete_openid_login(request)
|
86
|
+
begin
|
87
|
+
idp_response = openid_consumer(request).complete(request.params, full_url(request))
|
88
|
+
rescue => e
|
89
|
+
request.flash[:error] = e
|
90
|
+
return login_redirect(request)
|
91
|
+
end
|
92
|
+
|
93
|
+
if idp_response.is_a?(OpenID::Consumer::FailureResponse)
|
94
|
+
request.flash[:error] = idp_response.message
|
95
|
+
return login_redirect(request)
|
96
|
+
end
|
97
|
+
|
98
|
+
result = @users.get(
|
99
|
+
'/cloudkit_login_view',
|
100
|
+
:identity_url => idp_response.endpoint.claimed_id)
|
101
|
+
user_uris = result.parsed_content['uris']
|
102
|
+
|
103
|
+
if user_uris.empty?
|
104
|
+
json = JSON.generate(:identity_url => idp_response.endpoint.claimed_id)
|
105
|
+
result = @users.post('/cloudkit_users', :json => json)
|
106
|
+
user_uri = result.parsed_content['uri']
|
107
|
+
else
|
108
|
+
user_uri = user_uris.first
|
109
|
+
end
|
110
|
+
user_result = @users.resolve_uris([user_uri]).first
|
111
|
+
user = user_result.parsed_content
|
112
|
+
|
113
|
+
if request.session['user_uri'] = user_uri
|
114
|
+
request.current_user = user_uri
|
115
|
+
user['remember_me_expiration'] = two_weeks_from_now
|
116
|
+
user['remember_me_token'] = Base64.encode64(
|
117
|
+
OpenSSL::Random.random_bytes(32)).gsub(/\W/,'')
|
118
|
+
url = request.session.delete('return_to')
|
119
|
+
response = Rack::Response.new([], 302, {'Location' => (url || '/')})
|
120
|
+
response.set_cookie(
|
121
|
+
'remember_me', {
|
122
|
+
:value => user['remember_me_token'],
|
123
|
+
:expires => Time.at(user['remember_me_expiration']).utc})
|
124
|
+
json = JSON.generate(user)
|
125
|
+
@users.put(user_uri, :etag => user_result.etag, :json => json)
|
126
|
+
request.flash[:notice] = 'You have been logged in.'
|
127
|
+
response.finish
|
128
|
+
else
|
129
|
+
request.flash[:error] = 'Could not log on with your OpenID.'
|
130
|
+
login_redirect(request)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def login_redirect(request)
|
135
|
+
[302, {'Location' => request.login_url}, []]
|
136
|
+
end
|
137
|
+
|
138
|
+
def base_url(request)
|
139
|
+
"#{request.scheme}://#{request.env['HTTP_HOST']}/"
|
140
|
+
end
|
141
|
+
|
142
|
+
def full_url(request)
|
143
|
+
base_url(request) + 'openid_complete'
|
144
|
+
end
|
145
|
+
|
146
|
+
def logged_in?(request)
|
147
|
+
logged_in = user_in_session?(request) || valid_remember_me_token?(request)
|
148
|
+
request.current_user = request.session['user_uri'] if logged_in
|
149
|
+
logged_in
|
150
|
+
end
|
151
|
+
|
152
|
+
def user_in_session?(request)
|
153
|
+
request.session['user_uri'] != nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def store_location(request)
|
157
|
+
request.session['return_to'] = request.url
|
158
|
+
end
|
159
|
+
|
160
|
+
def root_request?(request)
|
161
|
+
request.path_info == '/' || request.path_info == '/favicon.ico'
|
162
|
+
end
|
163
|
+
|
164
|
+
def valid_auth_key?(request)
|
165
|
+
request.env[auth_key] && request.env[auth_key] != ''
|
166
|
+
end
|
167
|
+
|
168
|
+
def openid_consumer(request)
|
169
|
+
@openid_consumer ||= OpenID::Consumer.new(
|
170
|
+
request.session, OpenIDStore.new)
|
171
|
+
end
|
172
|
+
|
173
|
+
def valid_remember_me_token?(request)
|
174
|
+
return false unless token = request.cookies['remember_me']
|
175
|
+
|
176
|
+
result = @users.get('/cloudkit_login_view', :remember_me_token => token)
|
177
|
+
return false unless result.status == 200
|
178
|
+
|
179
|
+
user_uris = result.parsed_content['uris']
|
180
|
+
return false unless user_uris.try(:size) == 1
|
181
|
+
|
182
|
+
user_uri = user_uris.first
|
183
|
+
user_result = @users.resolve_uris([user_uri]).first
|
184
|
+
user = user_result.parsed_content
|
185
|
+
return false unless Time.now.to_i < user['remember_me_expiration']
|
186
|
+
|
187
|
+
user['remember_me_expiration'] = two_weeks_from_now
|
188
|
+
json = JSON.generate(user)
|
189
|
+
@users.put(user_uri, :etag => user_result.etag, :json => json)
|
190
|
+
request.session['user_uri'] = user_uri
|
191
|
+
true
|
192
|
+
end
|
193
|
+
|
194
|
+
def two_weeks_from_now
|
195
|
+
Time.now.to_i+1209600
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'openid/store/interface'
|
2
|
+
module CloudKit
|
3
|
+
|
4
|
+
# An OpenIDStore provides the interface expected by the ruby-openid gem,
|
5
|
+
# mapping it to a CloudKit::Store instance.
|
6
|
+
class OpenIDStore < OpenID::Store::Interface
|
7
|
+
@@store = nil
|
8
|
+
|
9
|
+
# Initialize an OpenIDStore and its required views.
|
10
|
+
def initialize(uri=nil)
|
11
|
+
unless @@store
|
12
|
+
association_view = ExtractionView.new(
|
13
|
+
:cloudkit_openid_server_handles,
|
14
|
+
:observe => :cloudkit_openid_associations,
|
15
|
+
:extract => [:server_url, :handle])
|
16
|
+
@@store = Store.new(
|
17
|
+
:collections => [:cloudkit_openid_associations, :cloudkit_openid_nonces],
|
18
|
+
:views => [association_view],
|
19
|
+
:adapter => SQLAdapter.new(uri))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_association(server_url, handle=nil) #:nodoc:
|
24
|
+
options = {:server_url => server_url}
|
25
|
+
options.merge!(:handle => Base64.encode64(handle)) if (handle && handle != '')
|
26
|
+
result = @@store.get('/cloudkit_openid_server_handles', options)
|
27
|
+
return nil unless result.status == 200
|
28
|
+
return nil if result.parsed_content['total'] == 0
|
29
|
+
|
30
|
+
ignore, associations = resolve_associations(result.parsed_content)
|
31
|
+
return nil if associations.empty?
|
32
|
+
|
33
|
+
associations.sort_by{|a| a['issued']}
|
34
|
+
a = associations[-1]
|
35
|
+
OpenID::Association.new(
|
36
|
+
Base64.decode64(a['handle']),
|
37
|
+
Base64.decode64(a['secret']),
|
38
|
+
Time.at(a['issued']),
|
39
|
+
a['lifetime'],
|
40
|
+
a['assoc_type'])
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_association(server_url, handle) #:nodoc:
|
44
|
+
result = @@store.get(
|
45
|
+
'/cloudkit_openid_server_handles',
|
46
|
+
:server_url => server_url,
|
47
|
+
:handle => Base64.encode64(handle))
|
48
|
+
return nil unless result.status == 200
|
49
|
+
|
50
|
+
responses, associations = resolve_associations(result.parsed_content)
|
51
|
+
return nil if associations.empty?
|
52
|
+
|
53
|
+
uris = result.parsed_content['uris']
|
54
|
+
responses.each_with_index do |r, index|
|
55
|
+
@@store.delete(uris[index], :etag => r.etag)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def store_association(server_url, association) #:nodoc:
|
60
|
+
remove_association(server_url, association.handle)
|
61
|
+
json = JSON.generate(
|
62
|
+
:server_url => server_url,
|
63
|
+
:handle => Base64.encode64(association.handle),
|
64
|
+
:secret => Base64.encode64(association.secret),
|
65
|
+
:issued => association.issued.to_i,
|
66
|
+
:lifetime => association.lifetime,
|
67
|
+
:assoc_type => association.assoc_type)
|
68
|
+
result = @@store.post('/cloudkit_openid_associations', :json => json)
|
69
|
+
return (result.status == 201)
|
70
|
+
end
|
71
|
+
|
72
|
+
def use_nonce(server_url, timestamp, salt) #:nodoc:
|
73
|
+
return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
|
74
|
+
|
75
|
+
fragment = URI.escape([server_url, timestamp, salt].join('-'))
|
76
|
+
uri = "/cloudkit_openid_nonces/#{fragment}"
|
77
|
+
result = @@store.put(uri, :json => '{}')
|
78
|
+
return (result.status == 201)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.cleanup #:nodoc:
|
82
|
+
# TODO
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.cleanup_associations #:nodoc:
|
86
|
+
# TODO
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.cleanup_nonces #:nodoc:
|
90
|
+
# TODO
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def resolve_associations(parsed_content) #:nodoc:
|
96
|
+
uri_list = parsed_content['uris']
|
97
|
+
association_responses = @@store.resolve_uris(uri_list)
|
98
|
+
return association_responses, association_responses.map{|a| a.parsed_content}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Rack #:nodoc:
|
2
|
+
class Builder
|
3
|
+
alias_method :cloudkit_to_app, :to_app
|
4
|
+
|
5
|
+
# Extends Rack::Builder's to_app method to detect if the last piece of
|
6
|
+
# middleware in the stack is a CloudKit shortcut (contain or expose), adding
|
7
|
+
# adding a default developer page at the root and a 404 everywhere else.
|
8
|
+
def to_app
|
9
|
+
default_app = lambda do |env|
|
10
|
+
if (env['PATH_INFO'] == '/')
|
11
|
+
[200, {'Content-Type' => 'text/html'}, [welcome]]
|
12
|
+
else
|
13
|
+
[404, {}, []]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
@ins << default_app if @last_cloudkit_id == @ins.last.object_id
|
17
|
+
cloudkit_to_app
|
18
|
+
end
|
19
|
+
|
20
|
+
# Setup resource collections hosted behind OAuth and OpenID auth filters.
|
21
|
+
#
|
22
|
+
# ===Example
|
23
|
+
# contain :notes, :projects
|
24
|
+
#
|
25
|
+
def contain(*args)
|
26
|
+
@ins << lambda do |app|
|
27
|
+
Rack::Session::Pool.new(
|
28
|
+
CloudKit::OAuthFilter.new(
|
29
|
+
CloudKit::OpenIDFilter.new(
|
30
|
+
CloudKit::Service.new(app, :collections => args.to_a))))
|
31
|
+
end
|
32
|
+
@last_cloudkit_id = @ins.last.object_id
|
33
|
+
end
|
34
|
+
|
35
|
+
# Setup resource collections without authentication.
|
36
|
+
#
|
37
|
+
# ===Example
|
38
|
+
# expose :notes, :projects
|
39
|
+
#
|
40
|
+
def expose(*args)
|
41
|
+
@ins << lambda do |app|
|
42
|
+
CloudKit::Service.new(app, :collections => args.to_a)
|
43
|
+
end
|
44
|
+
@last_cloudkit_id = @ins.last.object_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def welcome #:nodoc:
|
48
|
+
doc = <<HTML
|
49
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
50
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
51
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
52
|
+
<head>
|
53
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
54
|
+
<title>CloudKit via cURL</title>
|
55
|
+
<style type="text/css">
|
56
|
+
body {
|
57
|
+
font-family: 'Helvetica', 'Arial', san-serif;
|
58
|
+
font-size: 15px;
|
59
|
+
margin: 0;
|
60
|
+
padding: 0;
|
61
|
+
color: #222222;
|
62
|
+
}
|
63
|
+
h1 {
|
64
|
+
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', san-serif;
|
65
|
+
font-size: 73px;
|
66
|
+
font-weight: bold;
|
67
|
+
line-height: 28px;
|
68
|
+
margin: 20px 0px 20px 0px;
|
69
|
+
}
|
70
|
+
.wrapper {
|
71
|
+
width: 500px;
|
72
|
+
margin: 0 auto;
|
73
|
+
clear: both;
|
74
|
+
}
|
75
|
+
p {
|
76
|
+
margin-top: 0px;
|
77
|
+
line-height: 1.5em;
|
78
|
+
}
|
79
|
+
#header {
|
80
|
+
background-color: #ffffcc;
|
81
|
+
display: block;
|
82
|
+
padding: 2px 0;
|
83
|
+
margin: 35px 0px 10px 0px;
|
84
|
+
border-top: 1px solid #ffcc66;
|
85
|
+
border-bottom: 1px solid #ffcc66;
|
86
|
+
}
|
87
|
+
a {
|
88
|
+
color: #6b8df2;
|
89
|
+
text-decoration: none;
|
90
|
+
}
|
91
|
+
.meta {
|
92
|
+
padding: 7px 7px 7px 7px;
|
93
|
+
background-color: #ffccff;
|
94
|
+
border-top: 1px solid #cc99ff;
|
95
|
+
border-bottom: 1px solid #cc99ff;
|
96
|
+
font-size: 14px;
|
97
|
+
display: block;
|
98
|
+
margin: 10px 0px 10px 0px;
|
99
|
+
}
|
100
|
+
</style>
|
101
|
+
</head>
|
102
|
+
<body>
|
103
|
+
<div id="header">
|
104
|
+
<div class="wrapper">
|
105
|
+
<h1>CloudKit</h1>
|
106
|
+
</div>
|
107
|
+
</div>
|
108
|
+
<div class="meta">
|
109
|
+
<p class="wrapper">
|
110
|
+
This page is appearing because you have not set up a default app in your
|
111
|
+
rackup file. To learn more about CloudKit, check out
|
112
|
+
<a href="http://getcloudkit.com">the site</a>.
|
113
|
+
</p>
|
114
|
+
</div>
|
115
|
+
</body>
|
116
|
+
</html>
|
117
|
+
HTML
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rack #:nodoc:
|
2
|
+
|
3
|
+
# A minimal router providing just what is needed for the OAuth and OpenID
|
4
|
+
# filters.
|
5
|
+
class Router
|
6
|
+
|
7
|
+
# Create an instance of Router to match on method, path and params.
|
8
|
+
def initialize(method, path, params=[])
|
9
|
+
@method = method.to_s.upcase; @path = path; @params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
# By overriding the case comparison operator, we can match routes in a case
|
13
|
+
# statement.
|
14
|
+
#
|
15
|
+
# See also: CloudKit::Util#r, CloudKit::Request#match?
|
16
|
+
def ===(request)
|
17
|
+
request.match?(@method, @path, @params)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module CloudKit
|
2
|
+
|
3
|
+
# A subclass of Rack::Request providing CloudKit-specific features.
|
4
|
+
class Request < Rack::Request
|
5
|
+
include CloudKit::Util
|
6
|
+
alias_method :cloudkit_params, :params
|
7
|
+
|
8
|
+
def initialize(env)
|
9
|
+
super(env)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Return a merged set of both standard params and OAuth header params.
|
13
|
+
def params
|
14
|
+
@cloudkit_params ||= cloudkit_params.merge(oauth_header_params)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return true if method, path, and required_params match.
|
18
|
+
def match?(method, path, required_params=[])
|
19
|
+
(request_method == method) &&
|
20
|
+
path_info.match(path.gsub(':id', '*')) && # just enough to work for now
|
21
|
+
param_match?(required_params)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return true of the array of required params match the request params. If
|
25
|
+
# a hash in passed in for a param, its value is also used in the match.
|
26
|
+
def param_match?(required_params)
|
27
|
+
required_params.all? do |required_param|
|
28
|
+
case required_param
|
29
|
+
when Hash
|
30
|
+
key = required_param.keys.first
|
31
|
+
return false unless params.has_key? key
|
32
|
+
return false unless params[key] == required_param[key]
|
33
|
+
when String
|
34
|
+
return false unless params.has_key? required_param
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return OAuth header params in a hash.
|
43
|
+
def oauth_header_params
|
44
|
+
# This is a copy of the same method from the OAuth gem.
|
45
|
+
# TODO: Refactor the OAuth gem so that this method is available via a
|
46
|
+
# mixin, outside of the request proxy context.
|
47
|
+
%w( X-HTTP_AUTHORIZATION Authorization HTTP_AUTHORIZATION ).each do |header|
|
48
|
+
next unless @env.include?(header)
|
49
|
+
header = @env[header]
|
50
|
+
next unless header[0,6] == 'OAuth '
|
51
|
+
oauth_param_string = header[6,header.length].split(/[,=]/)
|
52
|
+
oauth_param_string.map!{|v| unescape(v.strip)}
|
53
|
+
oauth_param_string.map!{|v| v =~ /^\".*\"$/ ? v[1..-2] : v}
|
54
|
+
oauth_params = Hash[*oauth_param_string.flatten]
|
55
|
+
oauth_params.reject!{|k,v| k !~ /^oauth_/}
|
56
|
+
return oauth_params
|
57
|
+
end
|
58
|
+
return {}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Unescape a value according to the OAuth spec.
|
62
|
+
def unescape(value)
|
63
|
+
URI.unescape(value.gsub('+', '%2B'))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return the last path element in the request URI.
|
67
|
+
def last_path_element
|
68
|
+
path_element(-1)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Return a specific path element
|
72
|
+
def path_element(index)
|
73
|
+
path_info.split('/')[index] rescue nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return an array containing one entry for each piece of upstream
|
77
|
+
# middleware. This is in the same spirit as Via headers in HTTP, but does
|
78
|
+
# not use the header because the transition from one piece of middleware to
|
79
|
+
# the next does not use HTTP.
|
80
|
+
def via
|
81
|
+
@env[via_key].split(', ') rescue []
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return parsed contents of an If-Match header.
|
85
|
+
#
|
86
|
+
# Note: Only a single ETag is useful in the context of CloudKit, so a list
|
87
|
+
# is treated as one ETag; the result of using the wrong ETag or a list of
|
88
|
+
# ETags is the same in the context of PUT and DELETE where If-Match
|
89
|
+
# headers are required.
|
90
|
+
def if_match
|
91
|
+
etag = @env['HTTP_IF_MATCH']
|
92
|
+
return nil unless etag
|
93
|
+
etag.strip!
|
94
|
+
etag = unquote(etag)
|
95
|
+
return nil if etag == '*'
|
96
|
+
etag
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add a via entry to the Rack environment.
|
100
|
+
def inject_via(key)
|
101
|
+
items = via << key
|
102
|
+
@env[via_key] = items.join(', ')
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return the current user URI.
|
106
|
+
def current_user
|
107
|
+
return nil unless @env[auth_key] && @env[auth_key] != ''
|
108
|
+
@env[auth_key]
|
109
|
+
end
|
110
|
+
|
111
|
+
# Set the current user URI.
|
112
|
+
def current_user=(user)
|
113
|
+
@env[auth_key] = user
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return true if authentication is being used.
|
117
|
+
def using_auth?
|
118
|
+
@env[auth_presence_key] != nil
|
119
|
+
end
|
120
|
+
|
121
|
+
# Report to downstream middleware that authentication is in use.
|
122
|
+
def announce_auth(via)
|
123
|
+
inject_via(via)
|
124
|
+
@env[auth_presence_key] = 1
|
125
|
+
end
|
126
|
+
|
127
|
+
# Return the session associated with this request.
|
128
|
+
def session
|
129
|
+
@env['rack.session']
|
130
|
+
end
|
131
|
+
|
132
|
+
# Return the login URL for this request. This is stashed in the Rack
|
133
|
+
# environment so the OpenID and OAuth middleware can cooperate during the
|
134
|
+
# token authorization step in the OAuth flow.
|
135
|
+
def login_url
|
136
|
+
@env[login_url_key] || '/login'
|
137
|
+
end
|
138
|
+
|
139
|
+
# Set the login url for this request.
|
140
|
+
def login_url=(url)
|
141
|
+
@env[login_url_key] = url
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return the logout URL for this request.
|
145
|
+
def logout_url
|
146
|
+
@env[logout_url_key] || '/logout'
|
147
|
+
end
|
148
|
+
|
149
|
+
# Set the logout URL for this request.
|
150
|
+
def logout_url=(url)
|
151
|
+
@env[logout_url_key] = url
|
152
|
+
end
|
153
|
+
|
154
|
+
# Return the flash session for this request.
|
155
|
+
def flash
|
156
|
+
session[flash_key] ||= CloudKit::FlashSession.new
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|