cloudkit-jruby 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
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