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.
Files changed (64) hide show
  1. data/CHANGES +47 -0
  2. data/COPYING +20 -0
  3. data/README +84 -0
  4. data/Rakefile +42 -0
  5. data/TODO +21 -0
  6. data/cloudkit.gemspec +89 -0
  7. data/doc/curl.html +388 -0
  8. data/doc/images/example-code.gif +0 -0
  9. data/doc/images/json-title.gif +0 -0
  10. data/doc/images/oauth-discovery-logo.gif +0 -0
  11. data/doc/images/openid-logo.gif +0 -0
  12. data/doc/index.html +90 -0
  13. data/doc/main.css +151 -0
  14. data/doc/rest-api.html +467 -0
  15. data/examples/1.ru +3 -0
  16. data/examples/2.ru +3 -0
  17. data/examples/3.ru +6 -0
  18. data/examples/4.ru +5 -0
  19. data/examples/5.ru +9 -0
  20. data/examples/6.ru +11 -0
  21. data/examples/TOC +17 -0
  22. data/lib/cloudkit.rb +92 -0
  23. data/lib/cloudkit/constants.rb +34 -0
  24. data/lib/cloudkit/exceptions.rb +10 -0
  25. data/lib/cloudkit/flash_session.rb +20 -0
  26. data/lib/cloudkit/oauth_filter.rb +266 -0
  27. data/lib/cloudkit/oauth_store.rb +48 -0
  28. data/lib/cloudkit/openid_filter.rb +236 -0
  29. data/lib/cloudkit/openid_store.rb +100 -0
  30. data/lib/cloudkit/rack/builder.rb +120 -0
  31. data/lib/cloudkit/rack/router.rb +20 -0
  32. data/lib/cloudkit/request.rb +177 -0
  33. data/lib/cloudkit/service.rb +162 -0
  34. data/lib/cloudkit/store.rb +349 -0
  35. data/lib/cloudkit/store/memory_table.rb +99 -0
  36. data/lib/cloudkit/store/resource.rb +269 -0
  37. data/lib/cloudkit/store/response.rb +52 -0
  38. data/lib/cloudkit/store/response_helpers.rb +84 -0
  39. data/lib/cloudkit/templates/authorize_request_token.erb +19 -0
  40. data/lib/cloudkit/templates/oauth_descriptor.erb +43 -0
  41. data/lib/cloudkit/templates/oauth_meta.erb +8 -0
  42. data/lib/cloudkit/templates/openid_login.erb +31 -0
  43. data/lib/cloudkit/templates/request_authorization.erb +23 -0
  44. data/lib/cloudkit/templates/request_token_denied.erb +18 -0
  45. data/lib/cloudkit/uri.rb +88 -0
  46. data/lib/cloudkit/user_store.rb +37 -0
  47. data/lib/cloudkit/util.rb +25 -0
  48. data/spec/ext_spec.rb +76 -0
  49. data/spec/flash_session_spec.rb +20 -0
  50. data/spec/memory_table_spec.rb +86 -0
  51. data/spec/oauth_filter_spec.rb +326 -0
  52. data/spec/oauth_store_spec.rb +10 -0
  53. data/spec/openid_filter_spec.rb +81 -0
  54. data/spec/openid_store_spec.rb +101 -0
  55. data/spec/rack_builder_spec.rb +39 -0
  56. data/spec/request_spec.rb +191 -0
  57. data/spec/resource_spec.rb +310 -0
  58. data/spec/service_spec.rb +1039 -0
  59. data/spec/spec_helper.rb +32 -0
  60. data/spec/store_spec.rb +10 -0
  61. data/spec/uri_spec.rb +93 -0
  62. data/spec/user_store_spec.rb +10 -0
  63. data/spec/util_spec.rb +11 -0
  64. metadata +180 -0
@@ -0,0 +1,19 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <title>OAuth Request Token Authorized</title>
7
+ <style type="text/css">
8
+ body {
9
+ font-family: 'Helvetica', 'Arial', san-serif;
10
+ }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <p>
15
+ Your OAuth Request Token has been authorized. Please return to the application
16
+ that initiated this request to complete the setup.
17
+ </p>
18
+ </body>
19
+ </html>
@@ -0,0 +1,43 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <XRDS xmlns="xri://$xrds">
3
+ <XRD xml:id="oauth" xmlns:simple="http://xrds-simple.net/core/1.0" xmlns="xri://$XRD*($v*2.0)" version="2.0">
4
+ <Type>xri://$xrds*simple</Type>
5
+ <Expires><%= Time.at(Time.now.to_i + 2592000).utc.xmlschema %></Expires>
6
+ <Service priority="10">
7
+ <Type>http://oauth.net/core/1.0/endpoint/request</Type>
8
+ <Type>http://oauth.net/core/1.0/parameters/auth-header</Type>
9
+ <Type>http://oauth.net/core/1.0/parameters/uri-query</Type>
10
+ <Type>http://oauth.net/core/1.0/signature/PLAINTEXT</Type>
11
+ <URI><%= request.scheme %>://<%= request.env['HTTP_HOST'] %>/oauth/request_tokens</URI>
12
+ </Service>
13
+ <Service priority="10">
14
+ <Type>http://oauth.net/core/1.0/endpoint/authorize</Type>
15
+ <Type>http://oauth.net/core/1.0/parameters/uri-query</Type>
16
+ <URI><%= request.scheme %>://<%= request.env['HTTP_HOST'] %>/oauth/authorization</URI>
17
+ </Service>
18
+ <Service priority="10">
19
+ <Type>http://oauth.net/core/1.0/endpoint/access</Type>
20
+ <Type>http://oauth.net/core/1.0/parameters/auth-header</Type>
21
+ <Type>http://oauth.net/core/1.0/parameters/uri-query</Type>
22
+ <Type>http://oauth.net/core/1.0/signature/PLAINTEXT</Type>
23
+ <URI><%= request.scheme %>://<%= request.env['HTTP_HOST'] %>/oauth/access_tokens</URI>
24
+ </Service>
25
+ <Service priority="10">
26
+ <Type>http://oauth.net/core/1.0/endpoint/resource</Type>
27
+ <Type>http://oauth.net/core/1.0/parameters/auth-header</Type>
28
+ <Type>http://oauth.net/core/1.0/parameters/uri-query</Type>
29
+ <Type>http://oauth.net/core/1.0/signature/HMAC-SHA1</Type>
30
+ </Service>
31
+ <Service priority="10">
32
+ <Type>http://oauth.net/discovery/1.0/consumer-identity/static</Type>
33
+ <LocalID>cloudkitconsumer</LocalID>
34
+ </Service>
35
+ </XRD>
36
+ <XRD xmlns="xri://$XRD*($v*2.0)" version="2.0">
37
+ <Type>xri://$xrds*simple</Type>
38
+ <Service priority="10">
39
+ <Type>http://oauth.net/discovery/1.0</Type>
40
+ <URI>#oauth</URI>
41
+ </Service>
42
+ </XRD>
43
+ </XRDS>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <XRD>
3
+ <Type>http://oauth.net/discovery/1.0</Type>
4
+ <Service>
5
+ <Type>http://oauth.net/discovery/1.0/rel/provider</Type>
6
+ <URI><%= request.scheme %>://<%= request.env['HTTP_HOST'] %>/oauth</URI>
7
+ </Service>
8
+ </XRD>
@@ -0,0 +1,31 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <title>Sign Up or Sign In</title>
7
+ <style type="text/css">
8
+ body {
9
+ font-family: 'Helvetica', 'Arial', san-serif;
10
+ }
11
+ #openid_url {
12
+ background: url(data:image/gif;base64,R0lGODlhEAAQANUAAPr6+t/f37Gxsby8vJycnKWlpaampq+vr/zUpfz8/Lm5ufLy8r6+vp2dncvLy/muVsjIyLW1tcTExKGhof7+/sDAwMHBwbS0tNnZ2erq6qioqPn5+bi4uLu7u+np6e3t7evr66mpqeXl5b29vaqqqtfX18zMzO7u7u/v756enveTHv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAQABAAAAZlwJVwKEQQj0eECslcqZbN4zMqhTY/EoFzKbCAVoDNKjFIERTbFaeRcpwWCU3qgBJOVxVDhOEpH4hTFBkBIyQiKSkBgFZEiBiLTBApBgBDd0gXKROKaU0mZiEFl0wAJR0FD1SqQ0EAOw%3D%3D) no-repeat #FFF 5px;
13
+ padding-left: 25px;
14
+ width: 300px;
15
+ }
16
+ #submit {
17
+ display: block;
18
+ margin-top: 10px;
19
+ }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <%= request.flash[:error] %>
24
+ <%= request.flash[:info] %>
25
+ <p>Sign Up or Sign In with OpenID</p>
26
+ <form action="/login" method="POST">
27
+ <input type="text" id="openid_url" name="openid_url"/>
28
+ <input id="submit" type="submit" value="Sign In"/>
29
+ </form>
30
+ </body>
31
+ </html>
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <title>OAuth Authorization Requested</title>
7
+ <style type="text/css">
8
+ body {
9
+ font-family: 'Helvetica', 'Arial', san-serif;
10
+ }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <p>
15
+ Another application or website has requested access to your data.
16
+ </p>
17
+ <form action="/oauth/authorized_request_tokens/<%= request['oauth_token'] %>" method="POST">
18
+ <input type="hidden" name="_method" value="PUT"/>
19
+ <input type="submit" value="Approve"/>
20
+ <input type="submit" value="Deny"/>
21
+ </form>
22
+ </body>
23
+ </html>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <title>OAuth Request Token Authorization Denied</title>
7
+ <style type="text/css">
8
+ body {
9
+ font-family: 'Helvetica', 'Arial', san-serif;
10
+ }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <p>
15
+ Your OAuth Request Token was not denied and has been removed.
16
+ </p>
17
+ </body>
18
+ </html>
@@ -0,0 +1,88 @@
1
+ module CloudKit
2
+
3
+ # A CloudKit::URI wraps a URI string, adding methods useful for routing
4
+ # in CloudKit as well as caching URI components for future comparisons.
5
+ class URI
6
+
7
+ # The string form of a URI.
8
+ attr_reader :string
9
+
10
+ # Create a new URI with the given string.
11
+ def initialize(string)
12
+ @string = string
13
+ end
14
+
15
+ # Return the resource collection URI fragment.
16
+ # Example: URI.new('/foos/123').collection_uri_fragment => '/foos
17
+ def collection_uri_fragment
18
+ "/#{components[0]}" rescue nil
19
+ end
20
+
21
+ # Splits a URI into its components
22
+ def components
23
+ @components ||= @string.split('/').reject{|x| x == '' || x == nil} rescue []
24
+ end
25
+
26
+ # Return the resource collection referenced by a URI.
27
+ # Example: URI.new('/foos/123').collection_type => :foos
28
+ def collection_type
29
+ components[0].to_sym rescue nil
30
+ end
31
+
32
+ # Return the URI for the current version of a resource.
33
+ # Example: URI.new('/foos/123/versions/abc').current_resource_uri => '/foos/123'
34
+ def current_resource_uri
35
+ "/#{components[0..1].join('/')}" rescue nil
36
+ end
37
+
38
+ # Returns true if URI matches /cloudkit-meta
39
+ def meta_uri?
40
+ return components.size == 1 && components[0] == 'cloudkit-meta'
41
+ end
42
+
43
+ # Returns true if URI matches /{collection}
44
+ def resource_collection_uri?
45
+ return components.size == 1 && components[0] != 'cloudkit-meta'
46
+ end
47
+
48
+ # Returns true if URI matches /{collection}/_resolved
49
+ def resolved_resource_collection_uri?
50
+ return components.size == 2 && components[1] == '_resolved'
51
+ end
52
+
53
+ # Returns true if URI matches /{collection}/{uuid}
54
+ def resource_uri?
55
+ return components.size == 2 && components[1] != '_resolved'
56
+ end
57
+
58
+ # Returns true if URI matches /{collection}/{uuid}/versions
59
+ def version_collection_uri?
60
+ return components.size == 3 && components[2] == 'versions'
61
+ end
62
+
63
+ # Returns true if URI matches /{collection}/{uuid}/versions/_resolved
64
+ def resolved_version_collection_uri?
65
+ return components.size == 4 && components[2] == 'versions' && components[3] == '_resolved'
66
+ end
67
+
68
+ # Returns true if URI matches /{collection}/{uuid}/versions/{etag}
69
+ def resource_version_uri?
70
+ return components.size == 4 && components[2] == 'versions' && components[3] != '_resolved'
71
+ end
72
+
73
+ # Returns a cannonical URI for a given URI/URI fragment, generating it if
74
+ # required.
75
+ # Example: URI.new('/items/123').cannoncal_uri_string => /items/123
76
+ #
77
+ # Example: URI.new('/items').cannonical_uri_string => /items/some-new-uuid
78
+ def cannonical_uri_string
79
+ @cannonical_uri_string ||= if resource_collection_uri?
80
+ "#{@string}/#{UUID.generate}"
81
+ elsif resource_uri?
82
+ @string
83
+ else
84
+ raise CloudKit::InvalidURIFormat
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,37 @@
1
+ module CloudKit
2
+
3
+ # A thin layer on top of CloudKit::Store providing consistent URIs and
4
+ # automatic upgrades if required for future releases.
5
+ class UserStore
6
+ @@store = nil
7
+
8
+ def initialize(uri=nil)
9
+ unless @@store
10
+ @@store = Store.new(:collections => [:cloudkit_users])
11
+ end
12
+ end
13
+
14
+ def get(uri, options={}) #:nodoc:
15
+ @@store.get(CloudKit::URI.new(uri), options)
16
+ end
17
+
18
+ def post(uri, options={}) #:nodoc:
19
+ @@store.post(CloudKit::URI.new(uri), options)
20
+ end
21
+
22
+ def put(uri, options={}) #:nodoc:
23
+ @@store.put(CloudKit::URI.new(uri), options)
24
+ end
25
+
26
+ def delete(uri, options={}) #:nodoc:
27
+ @@store.delete(CloudKit::URI.new(uri), options)
28
+ end
29
+
30
+ def resolve_uris(uris) #:nodoc:
31
+ @@store.resolve_uris(uris.map { |uri| CloudKit::URI.new(uri) })
32
+ end
33
+
34
+ # Return the version for this UserStore
35
+ def version; 1; end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ module CloudKit
2
+ module Util
3
+
4
+ # Render ERB content
5
+ def erb(request, template, headers={'Content-Type' => 'text/html'}, status=200)
6
+ template_file = open(
7
+ File.join(File.dirname(__FILE__),
8
+ 'templates',
9
+ "#{template.to_s}.erb"))
10
+ template = ERB.new(template_file.read)
11
+ result = template.result(binding)
12
+ Rack::Response.new(result, status, headers).finish
13
+ end
14
+
15
+ # Build a Rack::Router instance
16
+ def r(method, path, params=[])
17
+ Rack::Router.new(method, path, params)
18
+ end
19
+
20
+ # Remove the outer double quotes from a given string.
21
+ def unquote(text)
22
+ (text =~ /^\".*\"$/) ? text[1..-2] : text
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,76 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "A Hash" do
4
+
5
+ it "should re-key an entry if it exists" do
6
+ x = {:a => 1, :b => 2}
7
+ x.rekey!(:b, :c)
8
+ x.should == {:a => 1, :c => 2}
9
+ x.rekey!(:d, :b)
10
+ x.should == {:a => 1, :c => 2}
11
+ end
12
+
13
+ it "should re-key false and nil values" do
14
+ x = {:a => false, :b => nil}
15
+ x.rekey!(:b, :c)
16
+ x.should == {:a => false, :c => nil}
17
+ x.rekey!(:d, :b)
18
+ x.should == {:a => false, :c => nil}
19
+ end
20
+
21
+ it "should merge conditionally" do
22
+ x = {:a => 1}
23
+ y = {:b => 2}
24
+ x.filter_merge!(:c => y[:c])
25
+ x.should == {:a => 1}
26
+ x.filter_merge!(:c => y[:b])
27
+ x.should == {:a => 1, :c => 2}
28
+ x = {}.filter_merge!(:a => 1)
29
+ x.should == {:a => 1}
30
+ end
31
+
32
+ it "should merge false values correctly" do
33
+ x = {:a => 1}
34
+ y = {:b => 2}
35
+ x.filter_merge!(:c => false)
36
+ x.should == {:a => 1, :c => false}
37
+ x.filter_merge!(:c => y[:b])
38
+ x.should == {:a => 1, :c => 2}
39
+ x = {}.filter_merge!(:a => false)
40
+ x.should == {:a => false}
41
+ end
42
+
43
+ it "should exclude pairs using a single key" do
44
+ x = {:a => 1, :b => 2}
45
+ y = x.excluding(:b)
46
+ y.should == {:a => 1}
47
+ end
48
+
49
+ it "should exclude pairs using a list of keys" do
50
+ x = {:a => 1, :b => 2, :c => 3}
51
+ y = x.excluding(:b, :c)
52
+ y.should == {:a => 1}
53
+ end
54
+
55
+ end
56
+
57
+ describe "An Array" do
58
+
59
+ it "should exclude elements" do
60
+ x = [0, 1, 2, 3]
61
+ y = x.excluding(1, 3)
62
+ y.should == [0, 2]
63
+ end
64
+
65
+ end
66
+
67
+ describe "An Object" do
68
+
69
+ it "should try" do
70
+ x = {:a => 'a'}
71
+ result = x[:a].try(:upcase)
72
+ result.should == 'A'
73
+ lambda { x[:b].try(:upcase) }.should_not raise_error
74
+ end
75
+
76
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "A FlashSession" do
4
+
5
+ before do
6
+ @flash = CloudKit::FlashSession.new
7
+ end
8
+
9
+ it "should accept a value for a key" do
10
+ @flash['greeting'] = 'hello'
11
+ @flash['greeting'].should == 'hello'
12
+ end
13
+
14
+ it "should erase a key/value pair after access" do
15
+ @flash['greeting'] = 'hello'
16
+ x = @flash['greeting']
17
+ @flash['greeting'].should be_nil
18
+ end
19
+
20
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "A MemoryTable" do
4
+ before(:each) do
5
+ @table = CloudKit::MemoryTable.new
6
+ end
7
+
8
+ after(:each) do
9
+ @table.clear
10
+ end
11
+
12
+ it "should reject non-hash records" do
13
+ @table['a'] = 1
14
+ @table['a'].should be_nil
15
+ end
16
+
17
+ it "should reject non-string record keys" do
18
+ @table['a'] = {:foo => 'bar'}
19
+ @table['a'].should be_nil
20
+ end
21
+
22
+ it "should reject non-string record values" do
23
+ @table['a'] = {'foo' => 1}
24
+ @table['a'].should be_nil
25
+ end
26
+
27
+ it "should get and set values for table keys like a hash" do
28
+ @table['a'] = {'foo' => 'bar'}
29
+ @table['a'].should == {'foo' => 'bar'}
30
+ end
31
+
32
+ it "should clear its contents" do
33
+ @table['a'] = {'foo' => 'bar'}
34
+ @table['b'] = {'foo' => 'baz'}
35
+ @table.clear
36
+ @table['a'].should be_nil
37
+ @table['b'].should be_nil
38
+ @table.keys.should be_empty
39
+ end
40
+
41
+ it "should keep an ordered set of keys" do
42
+ @table['b'] = {'foo' => 'bar'}
43
+ @table['a'] = {'foo' => 'baz'}
44
+ @table.keys.should == ['b', 'a']
45
+ end
46
+
47
+ it "should generate incrementing ids" do
48
+ ids = []
49
+ 4.times { ids << @table.generate_unique_id }
50
+ ids.should == [1, 2, 3, 4]
51
+ end
52
+
53
+ it "should query using a block supporting :eql comparisons" do
54
+ # For this release, only :eql comparisons are required
55
+ @table['a'] = {'foo' => 'bar', 'color' => 'blue'}
56
+ @table['b'] = {'foo' => 'baz', 'color' => 'blue'}
57
+ @table.query { |q|
58
+ q.add_condition('foo', :eql, 'bar')
59
+ }.should == [{'foo' => 'bar', 'color' => 'blue', :pk => 'a'}]
60
+ @table.query { |q|
61
+ q.add_condition('foo', :eql, 'baz')
62
+ }.should == [{'foo' => 'baz', 'color' => 'blue', :pk => 'b'}]
63
+ @table.query { |q|
64
+ q.add_condition('color', :eql, 'blue')
65
+ }.should == [
66
+ {'foo' => 'bar', 'color' => 'blue', :pk => 'a'},
67
+ {'foo' => 'baz', 'color' => 'blue', :pk => 'b'}]
68
+ @table.query { |q|
69
+ q.add_condition('foo', :eql, 'bar')
70
+ q.add_condition('color', :eql, 'blue')
71
+ }.should == [{'foo' => 'bar', 'color' => 'blue', :pk => 'a'}]
72
+ @table.query { |q|
73
+ q.add_condition('foo', :eql, 'bar')
74
+ q.add_condition('color', :eql, 'red')
75
+ }.should == []
76
+ end
77
+
78
+ it "should query without a block" do
79
+ @table['a'] = {'foo' => 'bar', 'color' => 'blue'}
80
+ @table['b'] = {'foo' => 'baz', 'color' => 'blue'}
81
+ @table.query.should == [
82
+ {'foo' => 'bar', 'color' => 'blue', :pk => 'a'},
83
+ {'foo' => 'baz', 'color' => 'blue', :pk => 'b'}]
84
+ end
85
+
86
+ end