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.
- data/CHANGES +11 -0
- data/README +7 -6
- data/Rakefile +13 -6
- data/TODO +7 -5
- data/cloudkit.gemspec +23 -23
- data/doc/curl.html +2 -2
- data/doc/index.html +4 -6
- data/examples/5.ru +2 -3
- data/examples/TOC +1 -3
- data/lib/cloudkit.rb +17 -10
- data/lib/cloudkit/constants.rb +0 -6
- data/lib/cloudkit/exceptions.rb +10 -0
- data/lib/cloudkit/flash_session.rb +1 -3
- data/lib/cloudkit/oauth_filter.rb +8 -16
- data/lib/cloudkit/oauth_store.rb +9 -13
- data/lib/cloudkit/openid_filter.rb +25 -7
- data/lib/cloudkit/openid_store.rb +14 -17
- data/lib/cloudkit/request.rb +6 -1
- data/lib/cloudkit/service.rb +15 -15
- data/lib/cloudkit/store.rb +97 -284
- data/lib/cloudkit/store/memory_table.rb +105 -0
- data/lib/cloudkit/store/resource.rb +256 -0
- data/lib/cloudkit/store/response_helpers.rb +0 -1
- data/lib/cloudkit/uri.rb +88 -0
- data/lib/cloudkit/user_store.rb +7 -14
- 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 +64 -0
- data/spec/openid_store_spec.rb +101 -0
- data/spec/rack_builder_spec.rb +39 -0
- data/spec/request_spec.rb +185 -0
- data/spec/resource_spec.rb +291 -0
- data/spec/service_spec.rb +974 -0
- data/{test/helper.rb → spec/spec_helper.rb} +14 -2
- 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 +37 -61
- data/examples/6.ru +0 -10
- data/lib/cloudkit/store/adapter.rb +0 -8
- data/lib/cloudkit/store/extraction_view.rb +0 -57
- data/lib/cloudkit/store/sql_adapter.rb +0 -36
- data/test/ext_test.rb +0 -76
- data/test/flash_session_test.rb +0 -22
- data/test/oauth_filter_test.rb +0 -331
- data/test/oauth_store_test.rb +0 -12
- data/test/openid_filter_test.rb +0 -60
- data/test/openid_store_test.rb +0 -12
- data/test/rack_builder_test.rb +0 -41
- data/test/request_test.rb +0 -197
- data/test/service_test.rb +0 -971
- data/test/store_test.rb +0 -93
- data/test/user_store_test.rb +0 -12
- data/test/util_test.rb +0 -13
data/lib/cloudkit/oauth_store.rb
CHANGED
@@ -1,42 +1,38 @@
|
|
1
1
|
module CloudKit
|
2
2
|
|
3
3
|
# An OAuthStore is a thin abstraction around CloudKit::Store, providing
|
4
|
-
# consistent collection names, and allowing automatic
|
4
|
+
# consistent collection names, and allowing automatic upgrades in later
|
5
5
|
# releases if needed.
|
6
6
|
class OAuthStore
|
7
7
|
@@store = nil
|
8
8
|
|
9
9
|
# Initialize a Store for use with OAuth middleware. Load the static consumer
|
10
10
|
# resource if it does not exist.
|
11
|
-
def initialize
|
11
|
+
def initialize
|
12
12
|
@@store = Store.new(
|
13
13
|
:collections => [
|
14
14
|
:cloudkit_oauth_nonces,
|
15
15
|
:cloudkit_oauth_tokens,
|
16
16
|
:cloudkit_oauth_request_tokens,
|
17
|
-
:cloudkit_oauth_consumers]
|
18
|
-
|
17
|
+
:cloudkit_oauth_consumers]
|
18
|
+
) unless @@store
|
19
19
|
load_static_consumer
|
20
20
|
end
|
21
21
|
|
22
22
|
def get(uri, options={}) #:nodoc:
|
23
|
-
@@store.get(uri, options)
|
23
|
+
@@store.get(CloudKit::URI.new(uri), options)
|
24
24
|
end
|
25
25
|
|
26
26
|
def post(uri, options={}) #:nodoc:
|
27
|
-
@@store.post(uri, options)
|
27
|
+
@@store.post(CloudKit::URI.new(uri), options)
|
28
28
|
end
|
29
29
|
|
30
30
|
def put(uri, options={}) #:nodoc:
|
31
|
-
@@store.put(uri, options)
|
31
|
+
@@store.put(CloudKit::URI.new(uri), options)
|
32
32
|
end
|
33
33
|
|
34
34
|
def delete(uri, options={}) #:nodoc:
|
35
|
-
@@store.delete(uri, options)
|
36
|
-
end
|
37
|
-
|
38
|
-
def reset! #:nodoc:
|
39
|
-
@@store.reset!
|
35
|
+
@@store.delete(CloudKit::URI.new(uri), options)
|
40
36
|
end
|
41
37
|
|
42
38
|
# Return the version number for this store.
|
@@ -46,7 +42,7 @@ module CloudKit
|
|
46
42
|
# See the OAuth Discovery spec for more info on static consumers.
|
47
43
|
def load_static_consumer
|
48
44
|
json = JSON.generate(:secret => '')
|
49
|
-
@@store.put('/cloudkit_oauth_consumers/cloudkitconsumer', :json => json)
|
45
|
+
@@store.put(CloudKit::URI.new('/cloudkit_oauth_consumers/cloudkitconsumer'), :json => json)
|
50
46
|
end
|
51
47
|
end
|
52
48
|
end
|
@@ -3,6 +3,11 @@ module CloudKit
|
|
3
3
|
# An OpenIDFilter provides OpenID authentication, listening for upstream
|
4
4
|
# OAuth authentication and bypassing if already authorized.
|
5
5
|
#
|
6
|
+
# The root URI, "/", is always bypassed. More URIs can also be bypassed using
|
7
|
+
# the :allow option:
|
8
|
+
#
|
9
|
+
# use OpenIDFilter, :allow => ['/foo', '/bar']
|
10
|
+
#
|
6
11
|
# Responds to the following URIs:
|
7
12
|
# /login
|
8
13
|
# /logout
|
@@ -15,14 +20,14 @@ module CloudKit
|
|
15
20
|
@@store = nil
|
16
21
|
|
17
22
|
def initialize(app, options={})
|
18
|
-
@app
|
23
|
+
@app = app
|
24
|
+
@options = options
|
19
25
|
end
|
20
26
|
|
21
27
|
def call(env)
|
22
28
|
@@lock.synchronize do
|
23
|
-
@@store = OpenIDStore.new
|
24
|
-
@users = UserStore.new
|
25
|
-
@@store.get_association('x') rescue nil # refresh sqlite3
|
29
|
+
@@store = OpenIDStore.new
|
30
|
+
@users = UserStore.new
|
26
31
|
end unless @@store
|
27
32
|
|
28
33
|
request = Request.new(env)
|
@@ -34,7 +39,7 @@ module CloudKit
|
|
34
39
|
when r(:get, '/openid_complete'); complete_openid_login(request)
|
35
40
|
when r(:post, request.logout_url); logout(request)
|
36
41
|
else
|
37
|
-
if
|
42
|
+
if bypass?(request)
|
38
43
|
@app.call(env)
|
39
44
|
else
|
40
45
|
if request.env[CLOUDKIT_AUTH_CHALLENGE]
|
@@ -103,7 +108,8 @@ module CloudKit
|
|
103
108
|
end
|
104
109
|
|
105
110
|
result = @users.get(
|
106
|
-
'/
|
111
|
+
'/cloudkit_users',
|
112
|
+
# '/cloudkit_login_view',
|
107
113
|
:identity_url => idp_response.endpoint.claimed_id)
|
108
114
|
user_uris = result.parsed_content['uris']
|
109
115
|
|
@@ -183,7 +189,8 @@ module CloudKit
|
|
183
189
|
def valid_remember_me_token?(request)
|
184
190
|
return false unless token = request.cookies['remember_me']
|
185
191
|
|
186
|
-
result = @users.get('/cloudkit_login_view', :remember_me_token => token)
|
192
|
+
# result = @users.get('/cloudkit_login_view', :remember_me_token => token)
|
193
|
+
result = @users.get('/cloudkit_users', :remember_me_token => token)
|
187
194
|
return false unless result.status == 200
|
188
195
|
|
189
196
|
user_uris = result.parsed_content['uris']
|
@@ -204,5 +211,16 @@ module CloudKit
|
|
204
211
|
def two_weeks_from_now
|
205
212
|
Time.now.to_i+1209600
|
206
213
|
end
|
214
|
+
|
215
|
+
def allow?(uri)
|
216
|
+
@options[:allow] && @options[:allow].include?(uri)
|
217
|
+
end
|
218
|
+
|
219
|
+
def bypass?(request)
|
220
|
+
root_request?(request) ||
|
221
|
+
allow?(request.path_info) ||
|
222
|
+
valid_auth_key?(request) ||
|
223
|
+
logged_in?(request)
|
224
|
+
end
|
207
225
|
end
|
208
226
|
end
|
@@ -6,24 +6,18 @@ module CloudKit
|
|
6
6
|
class OpenIDStore < OpenID::Store::Interface
|
7
7
|
@@store = nil
|
8
8
|
|
9
|
-
# Initialize an OpenIDStore
|
10
|
-
def initialize
|
9
|
+
# Initialize an OpenIDStore.
|
10
|
+
def initialize
|
11
11
|
unless @@store
|
12
|
-
association_view = ExtractionView.new(
|
13
|
-
:cloudkit_openid_server_handles,
|
14
|
-
:observe => :cloudkit_openid_associations,
|
15
|
-
:extract => [:server_url, :handle])
|
16
12
|
@@store = Store.new(
|
17
|
-
:collections => [:cloudkit_openid_associations, :cloudkit_openid_nonces]
|
18
|
-
:views => [association_view],
|
19
|
-
:adapter => SQLAdapter.new(uri))
|
13
|
+
:collections => [:cloudkit_openid_associations, :cloudkit_openid_nonces])
|
20
14
|
end
|
21
15
|
end
|
22
16
|
|
23
17
|
def get_association(server_url, handle=nil) #:nodoc:
|
24
18
|
options = {:server_url => server_url}
|
25
19
|
options.merge!(:handle => Base64.encode64(handle)) if (handle && handle != '')
|
26
|
-
result = @@store.get('/
|
20
|
+
result = @@store.get(CloudKit::URI.new('/cloudkit_openid_associations'), options)
|
27
21
|
return nil unless result.status == 200
|
28
22
|
return nil if result.parsed_content['total'] == 0
|
29
23
|
|
@@ -42,7 +36,7 @@ module CloudKit
|
|
42
36
|
|
43
37
|
def remove_association(server_url, handle) #:nodoc:
|
44
38
|
result = @@store.get(
|
45
|
-
'/
|
39
|
+
CloudKit::URI.new('/cloudkit_openid_associations'),
|
46
40
|
:server_url => server_url,
|
47
41
|
:handle => Base64.encode64(handle))
|
48
42
|
return nil unless result.status == 200
|
@@ -52,7 +46,7 @@ module CloudKit
|
|
52
46
|
|
53
47
|
uris = result.parsed_content['uris']
|
54
48
|
responses.each_with_index do |r, index|
|
55
|
-
@@store.delete(uris[index], :etag => r.etag)
|
49
|
+
@@store.delete(CloudKit::URI.new(uris[index]), :etag => r.etag)
|
56
50
|
end
|
57
51
|
end
|
58
52
|
|
@@ -65,18 +59,18 @@ module CloudKit
|
|
65
59
|
:issued => association.issued.to_i,
|
66
60
|
:lifetime => association.lifetime,
|
67
61
|
:assoc_type => association.assoc_type)
|
68
|
-
result = @@store.post('/cloudkit_openid_associations', :json => json)
|
62
|
+
result = @@store.post(CloudKit::URI.new('/cloudkit_openid_associations'), :json => json)
|
69
63
|
return (result.status == 201)
|
70
64
|
end
|
71
65
|
|
72
66
|
def use_nonce(server_url, timestamp, salt) #:nodoc:
|
73
67
|
return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
|
74
68
|
|
75
|
-
fragment = URI.escape(
|
69
|
+
fragment = ::URI.escape(
|
76
70
|
[server_url, timestamp, salt].join('-'),
|
77
|
-
Regexp.union(URI::REGEXP::UNSAFE, '/', ':'))
|
71
|
+
Regexp.union(::URI::REGEXP::UNSAFE, '/', ':'))
|
78
72
|
uri = "/cloudkit_openid_nonces/#{fragment}"
|
79
|
-
result = @@store.put(uri, :json => '{}')
|
73
|
+
result = @@store.put(CloudKit::URI.new(uri), :json => '{}')
|
80
74
|
return (result.status == 201)
|
81
75
|
end
|
82
76
|
|
@@ -92,10 +86,13 @@ module CloudKit
|
|
92
86
|
# TODO
|
93
87
|
end
|
94
88
|
|
89
|
+
# Return the version number for this store.
|
90
|
+
def version; 1; end
|
91
|
+
|
95
92
|
protected
|
96
93
|
|
97
94
|
def resolve_associations(parsed_content) #:nodoc:
|
98
|
-
uri_list = parsed_content['uris']
|
95
|
+
uri_list = parsed_content['uris'].map! { |u| CloudKit::URI.new(u) }
|
99
96
|
association_responses = @@store.resolve_uris(uri_list)
|
100
97
|
return association_responses, association_responses.map{|a| a.parsed_content}
|
101
98
|
end
|
data/lib/cloudkit/request.rb
CHANGED
@@ -20,6 +20,11 @@ module CloudKit
|
|
20
20
|
self.body.read
|
21
21
|
end
|
22
22
|
|
23
|
+
# Return a CloudKit::URI instance representing the rack request's path info.
|
24
|
+
def uri
|
25
|
+
@uri ||= CloudKit::URI.new(self.path_info)
|
26
|
+
end
|
27
|
+
|
23
28
|
# Return true if method, path, and required_params match.
|
24
29
|
def match?(method, path, required_params=[])
|
25
30
|
(request_method == method) &&
|
@@ -66,7 +71,7 @@ module CloudKit
|
|
66
71
|
|
67
72
|
# Unescape a value according to the OAuth spec.
|
68
73
|
def unescape(value)
|
69
|
-
URI.unescape(value.gsub('+', '%2B'))
|
74
|
+
::URI.unescape(value.gsub('+', '%2B'))
|
70
75
|
end
|
71
76
|
|
72
77
|
# Return the last path element in the request URI.
|
data/lib/cloudkit/service.rb
CHANGED
@@ -29,6 +29,8 @@ module CloudKit
|
|
29
29
|
|
30
30
|
@@lock = Mutex.new
|
31
31
|
|
32
|
+
attr_reader :store
|
33
|
+
|
32
34
|
def initialize(app, options)
|
33
35
|
@app = app
|
34
36
|
@collections = options[:collections]
|
@@ -36,9 +38,7 @@ module CloudKit
|
|
36
38
|
|
37
39
|
def call(env)
|
38
40
|
@@lock.synchronize do
|
39
|
-
@store = Store.new(
|
40
|
-
:adapter => SQLAdapter.new(env[CLOUDKIT_STORAGE_URI]),
|
41
|
-
:collections => @collections)
|
41
|
+
@store = Store.new(:collections => @collections)
|
42
42
|
end unless @store
|
43
43
|
|
44
44
|
request = Request.new(env)
|
@@ -55,7 +55,7 @@ module CloudKit
|
|
55
55
|
|
56
56
|
def get(request)
|
57
57
|
response = @store.get(
|
58
|
-
request.
|
58
|
+
request.uri,
|
59
59
|
{}.filter_merge!(
|
60
60
|
:remote_user => request.current_user,
|
61
61
|
:offset => request['offset'],
|
@@ -69,14 +69,14 @@ module CloudKit
|
|
69
69
|
return send(request['_method'].downcase)
|
70
70
|
end
|
71
71
|
@store.post(
|
72
|
-
request.
|
72
|
+
request.uri,
|
73
73
|
{:json => request.json}.filter_merge!(
|
74
74
|
:remote_user => request.current_user)).to_rack
|
75
75
|
end
|
76
76
|
|
77
77
|
def put(request)
|
78
78
|
@store.put(
|
79
|
-
request.
|
79
|
+
request.uri,
|
80
80
|
{:json => request.json}.filter_merge!(
|
81
81
|
:remote_user => request.current_user,
|
82
82
|
:etag => request.if_match)).to_rack
|
@@ -84,7 +84,7 @@ module CloudKit
|
|
84
84
|
|
85
85
|
def delete(request)
|
86
86
|
@store.delete(
|
87
|
-
request.
|
87
|
+
request.uri,
|
88
88
|
{}.filter_merge!(
|
89
89
|
:remote_user => request.current_user,
|
90
90
|
:etag => request.if_match)).to_rack
|
@@ -92,7 +92,7 @@ module CloudKit
|
|
92
92
|
|
93
93
|
def head(request)
|
94
94
|
response = @store.head(
|
95
|
-
request.
|
95
|
+
request.uri,
|
96
96
|
{}.filter_merge!(
|
97
97
|
:remote_user => request.current_user,
|
98
98
|
:offset => request['offset'],
|
@@ -102,15 +102,15 @@ module CloudKit
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def options(request)
|
105
|
-
@store.options(request.
|
105
|
+
@store.options(request.uri).to_rack
|
106
106
|
end
|
107
107
|
|
108
108
|
def inject_link_headers(request, response)
|
109
|
-
response['Link'] = versions_link_header(request) if
|
110
|
-
response['Link'] = resolved_link_header(request) if
|
111
|
-
response['Link'] = index_link_header(request) if
|
112
|
-
response['Link'] = resolved_link_header(request) if
|
113
|
-
response['Link'] = index_link_header(request) if
|
109
|
+
response['Link'] = versions_link_header(request) if request.uri.resource_uri?
|
110
|
+
response['Link'] = resolved_link_header(request) if request.uri.resource_collection_uri?
|
111
|
+
response['Link'] = index_link_header(request) if request.uri.resolved_resource_collection_uri?
|
112
|
+
response['Link'] = resolved_link_header(request) if request.uri.version_collection_uri?
|
113
|
+
response['Link'] = index_link_header(request) if request.uri.resolved_version_collection_uri?
|
114
114
|
end
|
115
115
|
|
116
116
|
def versions_link_header(request)
|
@@ -147,7 +147,7 @@ module CloudKit
|
|
147
147
|
|
148
148
|
def bypass?(request)
|
149
149
|
collection = @collections.detect{|type| request.path_info.match("/#{type.to_s}")}
|
150
|
-
!collection &&
|
150
|
+
!collection && !request.uri.meta_uri?
|
151
151
|
end
|
152
152
|
end
|
153
153
|
end
|
data/lib/cloudkit/store.rb
CHANGED
@@ -3,36 +3,22 @@ module CloudKit
|
|
3
3
|
# A functional storage interface with HTTP semantics and pluggable adapters.
|
4
4
|
class Store
|
5
5
|
include ResponseHelpers
|
6
|
+
include CloudKit::Util
|
6
7
|
|
7
|
-
# Initialize a new Store
|
8
|
-
#
|
8
|
+
# Initialize a new Store. All resources in a Store are automatically
|
9
|
+
# versioned.
|
9
10
|
#
|
10
11
|
# ===Options
|
11
|
-
# - :adapter - Optional. An instance of Adapter. Defaults to in-memory SQLite.
|
12
12
|
# - :collections - Array of resource collections to manage.
|
13
|
-
# - :views - Optional. Array of views to be updated based on JSON content.
|
14
13
|
#
|
15
14
|
# ===Example
|
16
15
|
# store = CloudKit::Store.new(:collections => [:foos, :bars])
|
17
16
|
#
|
18
|
-
#
|
19
|
-
# adapter = CloudKit::SQLAdapter.new('mysql://user:pass@localhost/my_db')
|
20
|
-
# fruit_color_view = CloudKit::ExtractionView.new(
|
21
|
-
# :fruits_by_color_and_season,
|
22
|
-
# :observe => :fruits,
|
23
|
-
# :extract => [:color, :season])
|
24
|
-
# store = CloudKit::Store.new(
|
25
|
-
# :adapter => adapter,
|
26
|
-
# :collections => [:foos, :fruits],
|
27
|
-
# :views => [fruit_color_view])
|
28
|
-
#
|
29
|
-
# See also: Adapter, ExtractionView, Response
|
17
|
+
# See also: Response
|
30
18
|
#
|
31
19
|
def initialize(options)
|
32
|
-
|
20
|
+
CloudKit.setup_storage_adapter unless CloudKit.storage_adapter
|
33
21
|
@collections = options[:collections]
|
34
|
-
@views = options[:views]
|
35
|
-
@views.each {|view| view.initialize_storage(@db)} if @views
|
36
22
|
end
|
37
23
|
|
38
24
|
# Retrieve a resource or collection of resources based on a URI.
|
@@ -45,7 +31,7 @@ module CloudKit
|
|
45
31
|
# - :remote_user - Optional. Scopes the dataset if provided.
|
46
32
|
# - :limit - Optional. Default is unlimited. Limit the number of records returned by a collection request.
|
47
33
|
# - :offset - Optional. Start the list of resources in a collection at offset (0-based).
|
48
|
-
# - :any - Optional. Not a literal ":any", but any key or keys
|
34
|
+
# - :any - Optional. Not a literal ":any", but any key or keys that are top level JSON keys. This is a starting point for future JSONPath/JSONQuery support.
|
49
35
|
#
|
50
36
|
# ===URI Types
|
51
37
|
# /cloudkit-meta
|
@@ -55,7 +41,6 @@ module CloudKit
|
|
55
41
|
# /{collection}/{uuid}/versions
|
56
42
|
# /{collection}/{uuid}/versions/_resolved
|
57
43
|
# /{collection}/{uuid}/versions/{etag}
|
58
|
-
# /{view}
|
59
44
|
#
|
60
45
|
# ===Examples
|
61
46
|
# get('/cloudkit-meta')
|
@@ -65,20 +50,18 @@ module CloudKit
|
|
65
50
|
# get('/foos/123')
|
66
51
|
# get('/foos/123/versions')
|
67
52
|
# get('/foos/123/versions/abc')
|
68
|
-
# get('/shiny_foos', :color => 'green')
|
69
53
|
#
|
70
54
|
# See also: {REST API}[http://getcloudkit.com/rest-api.html]
|
71
55
|
#
|
72
56
|
def get(uri, options={})
|
73
|
-
return invalid_entity_type if !valid_collection_type?(collection_type
|
74
|
-
return meta if meta_uri?
|
75
|
-
return resource_collection(uri, options) if resource_collection_uri?
|
76
|
-
return resolved_resource_collection(uri, options) if resolved_resource_collection_uri?
|
77
|
-
return resource(uri, options) if resource_uri?
|
78
|
-
return version_collection(uri, options) if version_collection_uri?
|
79
|
-
return resolved_version_collection(uri, options) if resolved_version_collection_uri?
|
80
|
-
return resource_version(uri, options) if resource_version_uri?
|
81
|
-
return view(uri, options) if view_uri?(uri)
|
57
|
+
return invalid_entity_type if !valid_collection_type?(uri.collection_type)
|
58
|
+
return meta if uri.meta_uri?
|
59
|
+
return resource_collection(uri, options) if uri.resource_collection_uri?
|
60
|
+
return resolved_resource_collection(uri, options) if uri.resolved_resource_collection_uri?
|
61
|
+
return resource(uri, options) if uri.resource_uri?
|
62
|
+
return version_collection(uri, options) if uri.version_collection_uri?
|
63
|
+
return resolved_version_collection(uri, options) if uri.resolved_version_collection_uri?
|
64
|
+
return resource_version(uri, options) if uri.resource_version_uri?
|
82
65
|
status_404
|
83
66
|
end
|
84
67
|
|
@@ -87,19 +70,14 @@ module CloudKit
|
|
87
70
|
# to the way CloudKit stores its ETags and Last-Modified information on
|
88
71
|
# write.
|
89
72
|
def head(uri, options={})
|
90
|
-
return invalid_entity_type unless @collections.include?(collection_type
|
91
|
-
if resource_uri?
|
73
|
+
return invalid_entity_type unless @collections.include?(uri.collection_type)
|
74
|
+
if uri.resource_uri? || uri.resource_version_uri?
|
92
75
|
# ETag and Last-Modified are already stored for single items, so a slight
|
93
76
|
# optimization can be made for HEAD requests.
|
94
|
-
result =
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
result = result.first
|
99
|
-
return status_410.head if result[:deleted]
|
100
|
-
return response(200, '', result[:etag], result[:last_modified])
|
101
|
-
end
|
102
|
-
status_404.head
|
77
|
+
result = CloudKit::Resource.first(options.merge(:uri => uri.string))
|
78
|
+
return status_404.head unless result
|
79
|
+
return status_410.head if result.deleted?
|
80
|
+
return response(200, '', result.etag, result.last_modified)
|
103
81
|
else
|
104
82
|
get(uri, options).head
|
105
83
|
end
|
@@ -110,7 +88,7 @@ module CloudKit
|
|
110
88
|
def put(uri, options={})
|
111
89
|
methods = methods_for_uri(uri)
|
112
90
|
return status_405(methods) unless methods.include?('PUT')
|
113
|
-
return invalid_entity_type unless @collections.include?(collection_type
|
91
|
+
return invalid_entity_type unless @collections.include?(uri.collection_type)
|
114
92
|
return data_required unless options[:json]
|
115
93
|
current_resource = resource(uri, options.excluding(:json, :etag, :remote_user))
|
116
94
|
return update_resource(uri, options) if current_resource.status == 200
|
@@ -122,9 +100,8 @@ module CloudKit
|
|
122
100
|
def post(uri, options={})
|
123
101
|
methods = methods_for_uri(uri)
|
124
102
|
return status_405(methods) unless methods.include?('POST')
|
125
|
-
return invalid_entity_type unless @collections.include?(collection_type
|
103
|
+
return invalid_entity_type unless @collections.include?(uri.collection_type)
|
126
104
|
return data_required unless options[:json]
|
127
|
-
uri = "#{collection_uri_fragment(uri)}/#{UUID.generate}"
|
128
105
|
create_resource(uri, options)
|
129
106
|
end
|
130
107
|
|
@@ -132,31 +109,15 @@ module CloudKit
|
|
132
109
|
def delete(uri, options={})
|
133
110
|
methods = methods_for_uri(uri)
|
134
111
|
return status_405(methods) unless methods.include?('DELETE')
|
135
|
-
return invalid_entity_type unless @collections.include?(collection_type
|
112
|
+
return invalid_entity_type unless @collections.include?(uri.collection_type)
|
136
113
|
return etag_required unless options[:etag]
|
137
|
-
|
138
|
-
|
139
|
-
if
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
version_uri = ''
|
145
|
-
@db.transaction do
|
146
|
-
version_uri = "#{item[:uri]}/versions/#{item[:etag]}"
|
147
|
-
original.update(:uri => version_uri)
|
148
|
-
@db[CLOUDKIT_STORE].insert(
|
149
|
-
:uri => item[:uri],
|
150
|
-
:collection_reference => item[:collection_reference],
|
151
|
-
:resource_reference => item[:resource_reference],
|
152
|
-
:remote_user => item[:remote_user],
|
153
|
-
:content => item[:content],
|
154
|
-
:deleted => true)
|
155
|
-
unmap(uri)
|
156
|
-
end
|
157
|
-
return json_meta_response(200, version_uri, item[:etag], item[:last_modified])
|
158
|
-
end
|
159
|
-
status_404
|
114
|
+
resource = CloudKit::Resource.first(options.excluding(:etag).merge(:uri => uri.string))
|
115
|
+
return status_404 unless (resource && (resource.remote_user == options[:remote_user]))
|
116
|
+
return status_410 if resource.deleted?
|
117
|
+
return status_412 if resource.etag != options[:etag]
|
118
|
+
|
119
|
+
resource.delete
|
120
|
+
return json_meta_response(200, resource.uri.string, resource.etag, resource.last_modified)
|
160
121
|
end
|
161
122
|
|
162
123
|
# Build a response containing the allowed methods for a given URI.
|
@@ -167,13 +128,13 @@ module CloudKit
|
|
167
128
|
|
168
129
|
# Return a list of allowed methods for a given URI.
|
169
130
|
def methods_for_uri(uri)
|
170
|
-
return meta_methods if meta_uri?
|
171
|
-
return resource_collection_methods if resource_collection_uri?
|
172
|
-
return resolved_resource_collection_methods if resolved_resource_collection_uri?
|
173
|
-
return resource_methods if resource_uri?
|
174
|
-
return version_collection_methods if version_collection_uri?
|
175
|
-
return resolved_version_collection_methods if resolved_version_collection_uri?
|
176
|
-
return resource_version_methods if resource_version_uri?
|
131
|
+
return meta_methods if uri.meta_uri?
|
132
|
+
return resource_collection_methods if uri.resource_collection_uri?
|
133
|
+
return resolved_resource_collection_methods if uri.resolved_resource_collection_uri?
|
134
|
+
return resource_methods if uri.resource_uri?
|
135
|
+
return version_collection_methods if uri.version_collection_uri?
|
136
|
+
return resolved_version_collection_methods if uri.resolved_version_collection_uri?
|
137
|
+
return resource_version_methods if uri.resource_version_uri?
|
177
138
|
end
|
178
139
|
|
179
140
|
# Return the list of methods allowed for the cloudkit-meta URI.
|
@@ -221,91 +182,17 @@ module CloudKit
|
|
221
182
|
['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS']
|
222
183
|
end
|
223
184
|
|
224
|
-
# Return the resource collection URI fragment.
|
225
|
-
# Example: collection_uri_fragment('/foos/123') => '/foos
|
226
|
-
def collection_uri_fragment(uri)
|
227
|
-
"/#{uri_components(uri)[0]}" rescue nil
|
228
|
-
end
|
229
|
-
|
230
|
-
# Return the resource collection referenced by a URI.
|
231
|
-
# Example: collection_type('/foos/123') => :foos
|
232
|
-
def collection_type(uri)
|
233
|
-
uri_components(uri)[0].to_sym rescue nil
|
234
|
-
end
|
235
|
-
|
236
|
-
# Return the URI for the current version of a resource.
|
237
|
-
# Example: current_resource_uri('/foos/123/versions/abc') => '/foos/123'
|
238
|
-
def current_resource_uri(uri)
|
239
|
-
"/#{uri_components(uri)[0..1].join('/')}" rescue nil
|
240
|
-
end
|
241
|
-
|
242
|
-
# Splits a URI into its components
|
243
|
-
def uri_components(uri)
|
244
|
-
uri.split('/').reject{|x| x == '' || x == nil} rescue []
|
245
|
-
end
|
246
|
-
|
247
|
-
# Returns true if URI matches /cloudkit-meta
|
248
|
-
def meta_uri?(uri)
|
249
|
-
c = uri_components(uri)
|
250
|
-
return c.size == 1 && c[0] == 'cloudkit-meta'
|
251
|
-
end
|
252
|
-
|
253
|
-
# Returns true if URI matches /{collection}
|
254
|
-
def resource_collection_uri?(uri)
|
255
|
-
c = uri_components(uri)
|
256
|
-
return c.size == 1 && @collections.include?(c[0].to_sym)
|
257
|
-
end
|
258
|
-
|
259
|
-
# Returns true if URI matches /{collection}/_resolved
|
260
|
-
def resolved_resource_collection_uri?(uri)
|
261
|
-
c = uri_components(uri)
|
262
|
-
return c.size == 2 && @collections.include?(c[0].to_sym) && c[1] == '_resolved'
|
263
|
-
end
|
264
|
-
|
265
|
-
# Returns true if URI matches /{collection}/{uuid}
|
266
|
-
def resource_uri?(uri)
|
267
|
-
c = uri_components(uri)
|
268
|
-
return c.size == 2 && @collections.include?(c[0].to_sym) && c[1] != '_resolved'
|
269
|
-
end
|
270
|
-
|
271
|
-
# Returns true if URI matches /{collection}/{uuid}/versions
|
272
|
-
def version_collection_uri?(uri)
|
273
|
-
c = uri_components(uri)
|
274
|
-
return c.size == 3 && @collections.include?(c[0].to_sym) && c[2] == 'versions'
|
275
|
-
end
|
276
|
-
|
277
|
-
# Returns true if URI matches /{collection}/{uuid}/versions/_resolved
|
278
|
-
def resolved_version_collection_uri?(uri)
|
279
|
-
c = uri_components(uri)
|
280
|
-
return c.size == 4 && @collections.include?(c[0].to_sym) && c[2] == 'versions' && c[3] == '_resolved'
|
281
|
-
end
|
282
|
-
|
283
|
-
# Returns true if URI matches /{collection}/{uuid}/versions/{etag}
|
284
|
-
def resource_version_uri?(uri)
|
285
|
-
c = uri_components(uri)
|
286
|
-
return c.size == 4 && @collections.include?(c[0].to_sym) && c[2] == 'versions' && c[3] != '_resolved'
|
287
|
-
end
|
288
|
-
|
289
|
-
# Returns true if URI matches /{view}
|
290
|
-
def view_uri?(uri)
|
291
|
-
c = uri_components(uri)
|
292
|
-
return c.size == 1 && @views && @views.map{|v| v.name}.include?(c[0].to_sym)
|
293
|
-
end
|
294
|
-
|
295
185
|
# Return an array containing the response for each URI in a list.
|
296
|
-
def resolve_uris(uris)
|
186
|
+
def resolve_uris(uris) # TODO - remove if no longer needed
|
297
187
|
result = []
|
298
188
|
uris.each do |uri|
|
299
189
|
result << get(uri)
|
300
190
|
end
|
301
191
|
result
|
302
192
|
end
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
@db.schema.keys.each do |table|
|
307
|
-
@db[table.gsub('`','').to_sym].delete
|
308
|
-
end
|
193
|
+
|
194
|
+
def storage_adapter
|
195
|
+
CloudKit.storage_adapter
|
309
196
|
end
|
310
197
|
|
311
198
|
# Return the version number of this Store.
|
@@ -322,145 +209,96 @@ module CloudKit
|
|
322
209
|
# Return a list of resource URIs for the given collection URI. Sorted by
|
323
210
|
# Last-Modified date in descending order.
|
324
211
|
def resource_collection(uri, options)
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
bundle_collection_result(uri, options, result)
|
212
|
+
filter = options.excluding(:offset, :limit).merge(
|
213
|
+
:deleted => false,
|
214
|
+
:collection_reference => uri.collection_uri_fragment,
|
215
|
+
:archived => false)
|
216
|
+
result = CloudKit::Resource.current(filter)
|
217
|
+
bundle_collection_result(uri.string, options, result)
|
332
218
|
end
|
333
219
|
|
334
220
|
# Return all documents and their associated metadata for the given
|
335
221
|
# collection URI.
|
336
222
|
def resolved_resource_collection(uri, options)
|
337
|
-
result =
|
338
|
-
|
339
|
-
|
340
|
-
filter('resource_reference = uri').
|
341
|
-
reverse_order(:id)
|
223
|
+
result = CloudKit::Resource.current(
|
224
|
+
options.excluding(:offset, :limit).merge(
|
225
|
+
:collection_reference => uri.collection_uri_fragment))
|
342
226
|
bundle_resolved_collection_result(uri, options, result)
|
343
227
|
end
|
344
228
|
|
345
229
|
# Return the resource for the given URI. Return 404 if not found or if
|
346
230
|
# protected and unauthorized, 410 if authorized but deleted.
|
347
231
|
def resource(uri, options)
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
if result.any?
|
352
|
-
result = result.first
|
353
|
-
return status_410 if result[:deleted]
|
354
|
-
return response(200, result[:content], result[:etag], result[:last_modified])
|
232
|
+
if resource = CloudKit::Resource.first(options.merge!(:uri => uri.string))
|
233
|
+
return status_410 if resource.deleted?
|
234
|
+
return response(200, resource.json, resource.etag, resource.last_modified)
|
355
235
|
end
|
356
236
|
status_404
|
357
237
|
end
|
358
238
|
|
359
239
|
# Return a collection of URIs for all versions of a resource including the
|
360
|
-
#current version. Sorted by Last-Modified date in descending order.
|
240
|
+
# current version. Sorted by Last-Modified date in descending order.
|
361
241
|
def version_collection(uri, options)
|
362
|
-
found =
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
reverse_order(:id)
|
372
|
-
bundle_collection_result(uri, options, result)
|
242
|
+
found = CloudKit::Resource.first(
|
243
|
+
options.excluding(:offset, :limit).merge(
|
244
|
+
:uri => uri.current_resource_uri))
|
245
|
+
return status_404 unless found
|
246
|
+
result = CloudKit::Resource.all( # TODO - just use found.versions
|
247
|
+
options.excluding(:offset, :limit).merge(
|
248
|
+
:resource_reference => uri.current_resource_uri,
|
249
|
+
:deleted => false))
|
250
|
+
bundle_collection_result(uri.string, options, result)
|
373
251
|
end
|
374
252
|
|
375
253
|
# Return all document versions and their associated metadata for a given
|
376
254
|
# resource including the current version. Sorted by Last-Modified date in
|
377
255
|
# descending order.
|
378
256
|
def resolved_version_collection(uri, options)
|
379
|
-
found =
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
reverse_order(:id)
|
257
|
+
found = CloudKit::Resource.first(
|
258
|
+
options.excluding(:offset, :limit).merge(
|
259
|
+
:uri => uri.current_resource_uri))
|
260
|
+
return status_404 unless found
|
261
|
+
result = CloudKit::Resource.all(
|
262
|
+
options.excluding(:offset, :limit).merge(
|
263
|
+
:resource_reference => uri.current_resource_uri,
|
264
|
+
:deleted => false))
|
388
265
|
bundle_resolved_collection_result(uri, options, result)
|
389
266
|
end
|
390
267
|
|
391
268
|
# Return a specific version of a resource.
|
392
269
|
def resource_version(uri, options)
|
393
|
-
result =
|
394
|
-
|
395
|
-
|
396
|
-
return status_404 unless result.any?
|
397
|
-
result = result.first
|
398
|
-
response(200, result[:content], result[:etag], result[:last_modified])
|
399
|
-
end
|
400
|
-
|
401
|
-
# Return a list of URIs for all resources matching the list of key value
|
402
|
-
# pairs provided in the options arg.
|
403
|
-
def view(uri, options)
|
404
|
-
result = @db[collection_type(uri)].
|
405
|
-
select(:uri).
|
406
|
-
filter(options.excluding(:offset, :limit))
|
407
|
-
bundle_collection_result(uri, options, result)
|
270
|
+
result = CloudKit::Resource.first(options.merge(:uri => uri.string))
|
271
|
+
return status_404 unless result
|
272
|
+
response(200, result.json, result.etag, result.last_modified)
|
408
273
|
end
|
409
274
|
|
410
275
|
# Create a resource at the specified URI.
|
411
276
|
def create_resource(uri, options)
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
@db[CLOUDKIT_STORE].insert(
|
416
|
-
:uri => uri,
|
417
|
-
:collection_reference => collection_uri_fragment(uri),
|
418
|
-
:resource_reference => uri,
|
419
|
-
:etag => etag,
|
420
|
-
:last_modified => last_modified,
|
421
|
-
:remote_user => options[:remote_user],
|
422
|
-
:content => options[:json])
|
423
|
-
map(uri, data)
|
424
|
-
json_meta_response(201, uri, etag, last_modified)
|
277
|
+
JSON.parse(options[:json]) rescue (return status_422)
|
278
|
+
resource = CloudKit::Resource.create(uri, options[:json], options[:remote_user])
|
279
|
+
json_meta_response(201, resource.uri.string, resource.etag, resource.last_modified)
|
425
280
|
end
|
426
281
|
|
427
282
|
# Update the resource at the specified URI. Requires the :etag option.
|
428
283
|
def update_resource(uri, options)
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
etag = UUID.generate
|
438
|
-
last_modified = timestamp
|
439
|
-
@db.transaction do
|
440
|
-
original.update(:uri => "#{uri}/versions/#{item[:etag]}")
|
441
|
-
@db[CLOUDKIT_STORE].insert(
|
442
|
-
:uri => uri,
|
443
|
-
:collection_reference => item[:collection_reference],
|
444
|
-
:resource_reference => item[:resource_reference],
|
445
|
-
:etag => etag,
|
446
|
-
:last_modified => last_modified,
|
447
|
-
:remote_user => options[:remote_user],
|
448
|
-
:content => options[:json])
|
449
|
-
end
|
450
|
-
map(uri, data)
|
451
|
-
return json_meta_response(200, uri, etag, last_modified)
|
452
|
-
end
|
453
|
-
status_404
|
284
|
+
JSON.parse(options[:json]) rescue (return status_422)
|
285
|
+
resource = CloudKit::Resource.first(
|
286
|
+
options.excluding(:json, :etag).merge(:uri => uri.string))
|
287
|
+
return status_404 unless (resource && (resource.remote_user == options[:remote_user]))
|
288
|
+
return etag_required unless options[:etag]
|
289
|
+
return status_412 unless options[:etag] == resource.etag
|
290
|
+
resource.update(options[:json])
|
291
|
+
return json_meta_response(200, uri.string, resource.etag, resource.last_modified)
|
454
292
|
end
|
455
293
|
|
456
294
|
# Bundle a collection of results as a list of URIs for the response.
|
457
295
|
def bundle_collection_result(uri, options, result)
|
458
|
-
total = result.
|
296
|
+
total = result.size
|
459
297
|
offset = options[:offset].try(:to_i) || 0
|
460
298
|
max = options[:limit] ? offset + options[:limit].to_i : total
|
461
|
-
list = result.
|
299
|
+
list = result.to_a[offset...max].map{|r| r.uri}
|
462
300
|
json = uri_list(list, total, offset)
|
463
|
-
last_modified = result.first
|
301
|
+
last_modified = result.first.try(:last_modified) if result.any?
|
464
302
|
response(200, json, build_etag(json), last_modified)
|
465
303
|
end
|
466
304
|
|
@@ -468,18 +306,18 @@ module CloudKit
|
|
468
306
|
# metadata (last_modified, uri, etag) that would have accompanied a response
|
469
307
|
# to their singular request.
|
470
308
|
def bundle_resolved_collection_result(uri, options, result)
|
471
|
-
total = result.
|
309
|
+
total = result.size
|
472
310
|
offset = options[:offset].try(:to_i) || 0
|
473
311
|
max = options[:limit] ? offset + options[:limit].to_i : total
|
474
|
-
list = result.
|
312
|
+
list = result.to_a[offset...max]
|
475
313
|
json = resource_list(list, total, offset)
|
476
|
-
last_modified = result.first
|
314
|
+
last_modified = result.first.last_modified if result.any?
|
477
315
|
response(200, json, build_etag(json), last_modified)
|
478
316
|
end
|
479
317
|
|
480
318
|
# Generate a JSON URI list.
|
481
319
|
def uri_list(list, total, offset)
|
482
|
-
JSON.generate(:total => total, :offset => offset, :uris => list)
|
320
|
+
JSON.generate(:total => total, :offset => offset, :uris => list.map { |u| u.string })
|
483
321
|
end
|
484
322
|
|
485
323
|
# Generate a JSON document list.
|
@@ -487,10 +325,10 @@ module CloudKit
|
|
487
325
|
results = []
|
488
326
|
list.each do |resource|
|
489
327
|
results << {
|
490
|
-
:uri => resource
|
491
|
-
:etag => resource
|
492
|
-
:last_modified => resource
|
493
|
-
:document => resource
|
328
|
+
:uri => resource.uri.string,
|
329
|
+
:etag => resource.etag,
|
330
|
+
:last_modified => resource.last_modified,
|
331
|
+
:document => resource.json}
|
494
332
|
end
|
495
333
|
JSON.generate(:total => total, :offset => offset, :documents => results)
|
496
334
|
end
|
@@ -502,34 +340,9 @@ module CloudKit
|
|
502
340
|
MD5::md5(data.to_s).hexdigest
|
503
341
|
end
|
504
342
|
|
505
|
-
# Returns true if the collection type represents a view.
|
506
|
-
def is_view?(collection_type)
|
507
|
-
@views && @views.map{|v| v.name}.include?(collection_type)
|
508
|
-
end
|
509
|
-
|
510
343
|
# Returns true if the collection type is valid for this Store.
|
511
344
|
def valid_collection_type?(collection_type)
|
512
|
-
@collections.include?(collection_type) ||
|
513
|
-
is_view?(collection_type) ||
|
514
|
-
collection_type.to_s == 'cloudkit-meta'
|
515
|
-
end
|
516
|
-
|
517
|
-
# Delegates the mapping of data from a resource into a view.
|
518
|
-
def map(uri, data)
|
519
|
-
@views.each{|view| view.map(@db, collection_type(uri), uri, data)} if @views
|
345
|
+
@collections.include?(collection_type) || collection_type.to_s == 'cloudkit-meta'
|
520
346
|
end
|
521
|
-
|
522
|
-
# Delegates removal of view data.
|
523
|
-
def unmap(uri)
|
524
|
-
@views.each{|view| view.unmap(@db, collection_type(uri), uri)} if @views
|
525
|
-
end
|
526
|
-
|
527
|
-
# Return a HTTP date representing 'now.'
|
528
|
-
def timestamp
|
529
|
-
Time.now.httpdate
|
530
|
-
end
|
531
|
-
|
532
|
-
# Return the adapter instance used by this Store.
|
533
|
-
def db; @db; end
|
534
347
|
end
|
535
348
|
end
|