rest-core 0.7.2 → 0.8.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.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # CHANGES
2
2
 
3
+ ## rest-core 0.8.0 -- 2011-11-29
4
+
5
+ Changes are mostly related to OAuth.
6
+
7
+ ### Incompatible changes
8
+
9
+ * [OAuth1Header] `callback` is renamed to `oauth_callback`
10
+ * [OAuth1Header] `verifier` is renamed to `oauth_verifier`
11
+
12
+ * [Oauth2Header] The first argument is changed from `access_token` to
13
+ `access_token_type`. Previously, the access_token_type is "OAuth" which
14
+ is used in Mixi. But mostly, we might want to use "Bearer" (according to
15
+ [OAuth 2.0 spec][]) Argument for the access_token is changed to the second
16
+ argument.
17
+
18
+ * [Defaults] Now we're no longer call `call` for any default values.
19
+ That is, if you're using this: `use s::Defaults, :data => lambda{{}}`
20
+ that would break. Previously, this middleware would call `call` on the
21
+ lambda so that `data` is default to a newly created hash. Now, it would
22
+ merely be default to the lambda. To make it work as before, please define
23
+ `def default_data; {}; end` in the client directly. Please see
24
+ `OAuth1Client` as an example.
25
+
26
+ [OAuth 2.0 spec]: http://tools.ietf.org/html/draft-ietf-oauth-v2-22
27
+
28
+ ### Enhancement
29
+
30
+ * [AuthBasic] Added a new middleware which could do [basic authentication][].
31
+
32
+ * [OAuth1Header] Introduced `data` which is a hash and is used to store
33
+ tokens and other information sent from authorization servers.
34
+
35
+ * [ClientOauth1] Now `authorize_url!` accepts opts which you can pass
36
+ `authorize_url!(:oauth_callback => 'http://localhost/callback')`.
37
+
38
+ * [ClientOauth1] Introduced `authorize_url` which would not try to ask
39
+ for a request token, instead, it would use the current token as the
40
+ request token. If you don't understand what does this mean, then keep
41
+ using `authorize_url!`, which would call this underneath.
42
+
43
+ * [ClientOauth1] Introduced `authorized?`
44
+ * [ClientOauth1] Now it would set `data['authorized'] = 'true'` when
45
+ `authorize!` is called, and it is also used to check if we're authorized
46
+ or not in `authorized?`
47
+
48
+ * [ClientOauth1] Introduced `data_json` and `data_json=` which allow you to
49
+ serialize and deserialize `data` with JSON along with a `sig` to check
50
+ if it hasn't been changed. You can put this into browser cookie. Because
51
+ of the `sig`, you would know if the user changed something in data without
52
+ using `consumer_secret` to generate a correct sig corresponded to the data.
53
+
54
+ * [ClientOauth1] Introduced `oauth_token`, `oauth_token=`,
55
+ `oauth_token_secret`, `oauth_token_secret=`, `oauth_callback`,
56
+ and `oauth_callback=` which take the advantage of `data`.
57
+
58
+ * [ClientOauth1] Introduced `default_data` which is a hash.
59
+
60
+ [basic authentication]: http://en.wikipedia.org/wiki/Basic_access_authentication
61
+
3
62
  ## rest-core 0.7.2 -- 2011-11-04
4
63
 
5
64
  * Moved rib-rest-core to [rest-more][]
@@ -19,7 +78,7 @@ _rest-more_ if you want to use them.
19
78
 
20
79
  ## rest-core 0.4.0 -- 2011-09-26
21
80
 
22
- ### Incompatible changes:
81
+ ### Incompatible changes
23
82
 
24
83
  * [dry] Now `RestCore::Ask` is renamed to `RestCore::Dry` for better
25
84
  understanding. Thanks miaout17
@@ -37,7 +96,7 @@ _rest-more_ if you want to use them.
37
96
  See *test_client.rb* for more detailed definition. If you don't understand
38
97
  this, don't worry, since then this won't affect you.
39
98
 
40
- ### Compatible changes:
99
+ ### Compatible changes
41
100
 
42
101
  * [client] Introduced a new method `request_full` which is exactly the same
43
102
  as `request` but also returns various information from the app, including
data/README.md CHANGED
@@ -5,7 +5,7 @@ by Cardinal Blue <http://cardinalblue.com>
5
5
  ## LINKS:
6
6
 
7
7
  * [github](https://github.com/cardinalblue/rest-core)
8
- * [rubygems](http://rubygems.org/gems/rest-core)
8
+ * [rubygems](https://rubygems.org/gems/rest-core)
9
9
  * [rdoc](http://rdoc.info/projects/cardinalblue/rest-core)
10
10
  * [mailing list](http://groups.google.com/group/rest-core/topics)
11
11
 
data/lib/rest-core.rb CHANGED
@@ -30,6 +30,7 @@ module RestCore
30
30
  autoload :ParseQuery , 'rest-core/util/parse_query'
31
31
 
32
32
  # middlewares
33
+ autoload :AuthBasic , 'rest-core/middleware/auth_basic'
33
34
  autoload :Bypass , 'rest-core/middleware/bypass'
34
35
  autoload :Cache , 'rest-core/middleware/cache'
35
36
  autoload :CommonLogger , 'rest-core/middleware/common_logger'
@@ -8,7 +8,8 @@ RestCore::Universal = RestCore::Builder.client(:data) do
8
8
  use s::DefaultQuery , {}
9
9
 
10
10
  use s::CommonLogger , method(:puts)
11
- use s::Cache , {}, 600 do
11
+ use s::AuthBasic , nil, nil
12
+ use s::Cache , {}, 600 do
12
13
  use s::ErrorHandler, nil
13
14
  use s::ErrorDetectorHttp
14
15
  use s::JsonDecode , false
@@ -4,22 +4,76 @@ require 'rest-core'
4
4
  module RestCore::ClientOauth1
5
5
  include RestCore
6
6
 
7
- def authorize_url!
8
- set_token(ParseQuery.parse_query(
9
- post(request_token_path, {}, {}, {:json_decode => false})))
7
+ def authorize_url! opts={}
8
+ self.data = ParseQuery.parse_query(
9
+ post(request_token_path, {}, {}, {:json_decode => false}.merge(opts)))
10
10
 
11
- url(authorize_path, :oauth_token => oauth_token, :format => false)
11
+ authorize_url
12
12
  end
13
13
 
14
- def authorize! verifier
15
- set_token(ParseQuery.parse_query(
16
- post(access_token_path, {}, {}, {:verifier => verifier,
17
- :json_decode => false})))
14
+ def authorize_url
15
+ url(authorize_path, :oauth_token => oauth_token)
16
+ end
17
+
18
+ def authorize! opts={}
19
+ self.data = ParseQuery.parse_query(
20
+ post(access_token_path, {}, {}, {:json_decode => false}.merge(opts)))
21
+
22
+ data['authorized'] = 'true'
23
+ data
24
+ end
25
+
26
+ def authorized?
27
+ !!(oauth_token && oauth_token_secret && data['authorized'])
28
+ end
29
+
30
+ def data_json
31
+ JsonDecode.json_encode(data.merge('sig' => calculate_sig))
32
+ end
33
+
34
+ def data_json= json
35
+ self.data = check_sig_and_return_data(JsonDecode.json_decode(json))
36
+ rescue JsonDecode.const_get(:ParseError)
37
+ self.data = nil
38
+ end
39
+
40
+ def oauth_token
41
+ data['oauth_token'] if data.kind_of?(Hash)
42
+ end
43
+ def oauth_token= token
44
+ data['oauth_token'] = token if data.kind_of?(Hash)
45
+ end
46
+ def oauth_token_secret
47
+ data['oauth_token_secret'] if data.kind_of?(Hash)
48
+ end
49
+ def oauth_token_secret= secret
50
+ data['oauth_token_secret'] = secret if data.kind_of?(Hash)
51
+ end
52
+ def oauth_callback
53
+ data['oauth_callback'] if data.kind_of?(Hash)
54
+ end
55
+ def oauth_callback= uri
56
+ data['oauth_callback'] = uri if data.kind_of?(Hash)
18
57
  end
19
58
 
20
59
  private
21
- def set_token query
22
- self.oauth_token = query['oauth_token']
23
- self.oauth_token_secret = query['oauth_token_secret']
60
+ def default_data
61
+ {}
62
+ end
63
+
64
+ def check_sig_and_return_data hash
65
+ hash if consumer_secret && hash.kind_of?(Hash) &&
66
+ calculate_sig(hash) == hash['sig']
67
+ end
68
+
69
+ def calculate_sig hash=data
70
+ base = hash.reject{ |(k, _)| k == 'sig' }.sort.map{ |(k, v)|
71
+ "#{escape(k.to_s)}=#{escape(v.to_s)}"
72
+ }.join('&')
73
+ Digest::MD5.hexdigest("#{escape(consumer_secret)}&#{base}")
74
+ end
75
+
76
+ def escape string
77
+ CGI.escape(string).gsub('+', '%20')
24
78
  end
25
79
  end
@@ -0,0 +1,29 @@
1
+
2
+ require 'rest-core/middleware'
3
+
4
+ class RestCore::AuthBasic
5
+ def self.members; [:username, :password]; end
6
+ include RestCore::Middleware
7
+
8
+ def call env
9
+ if username(env)
10
+ if password(env)
11
+ app.call(env.merge(REQUEST_HEADERS =>
12
+ auth_basic_header(env).merge(env[REQUEST_HEADERS] || {})))
13
+ else
14
+ app.call(log(env, "AuthBasic: username provided: #{username(env)}," \
15
+ " but password is missing."))
16
+ end
17
+ elsif password(env)
18
+ app.call(log(env, "AuthBasic: password provided: #{password(env)}," \
19
+ " but username is missing."))
20
+ else
21
+ app.call(env)
22
+ end
23
+ end
24
+
25
+ def auth_basic_header env
26
+ {'Authorization' =>
27
+ "Basic #{["#{username(env)}:#{password(env)}"].pack('m').tr("\n",'')}"}
28
+ end
29
+ end
@@ -24,11 +24,7 @@ class RestCore::Defaults
24
24
  def method_missing msg, *args, &block
25
25
  env = args.first
26
26
  if env.kind_of?(Hash) && (d = defaults(env)) && d.key?(msg)
27
- if (value = defaults(env)[msg]).respond_to?(:call)
28
- value.call
29
- else
30
- value
31
- end
27
+ defaults(env)[msg]
32
28
  else
33
29
  super
34
30
  end
@@ -9,7 +9,8 @@ class RestCore::Oauth1Header
9
9
  def self.members
10
10
  [:request_token_path, :access_token_path, :authorize_path,
11
11
  :consumer_key, :consumer_secret,
12
- :callback, :verifier, :oauth_token, :oauth_token_secret]
12
+ :oauth_callback, :oauth_verifier,
13
+ :oauth_token, :oauth_token_secret, :data]
13
14
  end
14
15
  include RestCore::Middleware
15
16
  def call env
@@ -35,8 +36,8 @@ class RestCore::Oauth1Header
35
36
  'oauth_timestamp' => Time.now.to_i.to_s,
36
37
  'oauth_nonce' => nonce,
37
38
  'oauth_version' => '1.0',
38
- 'oauth_callback' => callback(env),
39
- 'oauth_verifier' => verifier(env),
39
+ 'oauth_callback' => oauth_callback(env),
40
+ 'oauth_verifier' => oauth_verifier(env),
40
41
  'oauth_token' => oauth_token(env))
41
42
 
42
43
  "OAuth #{header.map{ |(k, v)| "#{k}=\"#{v}\"" }.join(', ')}"
@@ -2,12 +2,13 @@
2
2
  require 'rest-core/middleware'
3
3
 
4
4
  class RestCore::Oauth2Header
5
- def self.members; [:access_token]; end
5
+ def self.members; [:access_token_type, :access_token]; end
6
6
  include RestCore::Middleware
7
7
 
8
8
  def call env
9
9
  start_time = Time.now
10
- headers = {'Authorization' => "OAuth #{access_token(env)}"}.
10
+ headers = {'Authorization' =>
11
+ "#{access_token_type(env)} #{access_token(env)}"}.
11
12
  merge(env[REQUEST_HEADERS] || {}) if access_token(env)
12
13
 
13
14
  event = Event::WithHeader.new(Time.now - start_time,
@@ -1,4 +1,4 @@
1
1
 
2
2
  module RestCore
3
- VERSION = '0.7.2'
3
+ VERSION = '0.8.0'
4
4
  end
data/rest-core.gemspec CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "rest-core"
5
- s.version = "0.7.2"
5
+ s.version = "0.8.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = [
9
9
  "Cardinal Blue",
10
10
  "Lin Jen-Shin (godfat)"]
11
- s.date = "2011-11-04"
11
+ s.date = "2011-11-29"
12
12
  s.description = "Modular Ruby clients interface for REST APIs\n\nThere has been an explosion in the number of REST APIs available today.\nTo address the need for a way to access these APIs easily and elegantly,\nwe have developed [rest-core][], which consists of composable middleware\nthat allows you to build a REST client for any REST API. Or in the case of\ncommon APIs such as Facebook, Github, and Twitter, you can simply use the\ndedicated clients provided by [rest-more][].\n\n[rest-core]: http://github.com/cardinalblue/rest-core\n[rest-more]: http://github.com/cardinalblue/rest-more"
13
13
  s.email = ["dev (XD) cardinalblue.com"]
14
14
  s.files = [
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
38
38
  "lib/rest-core/error.rb",
39
39
  "lib/rest-core/event.rb",
40
40
  "lib/rest-core/middleware.rb",
41
+ "lib/rest-core/middleware/auth_basic.rb",
41
42
  "lib/rest-core/middleware/bypass.rb",
42
43
  "lib/rest-core/middleware/cache.rb",
43
44
  "lib/rest-core/middleware/common_logger.rb",
@@ -63,18 +64,24 @@ Gem::Specification.new do |s|
63
64
  "rest-core.gemspec",
64
65
  "task/.gitignore",
65
66
  "task/gemgem.rb",
67
+ "test/test_auth_basic.rb",
66
68
  "test/test_builder.rb",
67
69
  "test/test_client.rb",
70
+ "test/test_client_oauth1.rb",
68
71
  "test/test_oauth1_header.rb",
72
+ "test/test_universal.rb",
69
73
  "test/test_wrapper.rb"]
70
74
  s.homepage = "https://github.com/cardinalblue/rest-core"
71
75
  s.require_paths = ["lib"]
72
76
  s.rubygems_version = "1.8.11"
73
77
  s.summary = "Modular Ruby clients interface for REST APIs"
74
78
  s.test_files = [
79
+ "test/test_auth_basic.rb",
75
80
  "test/test_builder.rb",
76
81
  "test/test_client.rb",
82
+ "test/test_client_oauth1.rb",
77
83
  "test/test_oauth1_header.rb",
84
+ "test/test_universal.rb",
78
85
  "test/test_wrapper.rb"]
79
86
 
80
87
  if s.respond_to? :specification_version then
@@ -0,0 +1,36 @@
1
+
2
+ require 'rest-core/test'
3
+
4
+ describe RestCore::AuthBasic do
5
+ before do
6
+ @auth = RestCore::AuthBasic.new(RestCore::Dry.new, nil, nil)
7
+ end
8
+
9
+ should 'do nothing' do
10
+ @auth.call({}).should.eq({})
11
+ end
12
+
13
+ should 'send Authorization header' do
14
+ @auth.instance_eval{@username = 'Aladdin'}
15
+ @auth.instance_eval{@password = 'open sesame'}
16
+
17
+ @auth.call({}).should.eq({RestCore::REQUEST_HEADERS =>
18
+ {'Authorization' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}})
19
+
20
+ acc = {'Accept' => 'text/plain'}
21
+ env = {RestCore::REQUEST_HEADERS => acc}
22
+
23
+ @auth.call(env).should.eq({RestCore::REQUEST_HEADERS =>
24
+ {'Authorization' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}.merge(acc)})
25
+ end
26
+
27
+ should 'leave a log if username are not both provided' do
28
+ @auth.instance_eval{@username = 'Aladdin'}
29
+ @auth.call({})[RestCore::LOG].size.should.eq 1
30
+ end
31
+
32
+ should 'leave a log if password are not both provided' do
33
+ @auth.instance_eval{@password = 'open sesame'}
34
+ @auth.call({})[RestCore::LOG].size.should.eq 1
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+
2
+ require 'rest-core/test'
3
+
4
+ describe RC::ClientOauth1 do
5
+ after do
6
+ WebMock.reset!
7
+ RR.verify
8
+ end
9
+
10
+ client = RC::Builder.client do
11
+ s = self.class # this is only for ruby 1.8!
12
+ use s::Oauth1Header
13
+ end
14
+
15
+ client.send(:include, RC::ClientOauth1)
16
+
17
+ should 'restore data with correct sig' do
18
+ data = {'a' => 'b', 'c' => 'd'}
19
+ sig = Digest::MD5.hexdigest('e&a=b&c=d')
20
+ data_sig = data.merge('sig' => sig)
21
+ data_json = RC::JsonDecode.json_encode(data_sig)
22
+ @client = client.new(:data => data, :consumer_secret => 'e')
23
+
24
+ @client.send(:calculate_sig).should.eq sig
25
+ @client.data_json.should.eq data_json
26
+
27
+ @client.data_json = data_json
28
+ @client.data.should.eq data_sig
29
+
30
+ @client.data_json = RC::JsonDecode.json_encode(
31
+ data_sig.merge('sig' => 'wrong'))
32
+ @client.data.should.eq({})
33
+
34
+ @client.data_json = data_json
35
+ @client.data.should.eq data_sig
36
+
37
+ @client.data_json = 'bad json'
38
+ @client.data.should.eq({})
39
+ end
40
+
41
+ should 'have correct default data' do
42
+ @client = client.new
43
+ @client.data.should.eq({})
44
+ @client.data = nil
45
+ @client.data['a'] = 'b'
46
+ @client.data['a'].should.eq 'b'
47
+ end
48
+
49
+ should 'authorize' do
50
+ stub_request(:post, 'http://localhost').
51
+ to_return(:body => 'oauth_token=abc')
52
+
53
+ stub_request(:post, 'http://nocalhost').
54
+ to_return(:body => 'user_id=123&haha=point')
55
+
56
+ @client = client.new(:request_token_path => 'http://localhost',
57
+ :authorize_path => 'http://mocalhost',
58
+ :access_token_path => 'http://nocalhost')
59
+
60
+ @client.authorize_url!.should.eq 'http://mocalhost?oauth_token=abc'
61
+ @client.authorize!.should.eq('user_id' => '123', 'haha' => 'point',
62
+ 'authorized' => 'true')
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+
2
+ require 'rest-core/test'
3
+
4
+ describe RestCore::Universal do
5
+ should 'send Authorization header' do
6
+ u = RestCore::Universal.new(:log_method => false)
7
+ u.username = 'Aladdin'
8
+ u.password = 'open sesame'
9
+
10
+ u.request_full({}, u.dry)[RestCore::REQUEST_HEADERS].should.eq(
11
+ {'Authorization' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='})
12
+
13
+ acc = {'Accept' => 'text/plain'}
14
+ env = {RestCore::REQUEST_HEADERS => acc}
15
+
16
+ u.request_full(env, u.dry)[RestCore::REQUEST_HEADERS].should.eq(
17
+ {'Authorization' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}.merge(acc))
18
+ end
19
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-11-04 00:00:00.000000000 Z
13
+ date: 2011-11-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rest-client
17
- requirement: &2153817700 !ruby/object:Gem::Requirement
17
+ requirement: &2164286260 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,7 +22,7 @@ dependencies:
22
22
  version: '0'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2153817700
25
+ version_requirements: *2164286260
26
26
  description: ! 'Modular Ruby clients interface for REST APIs
27
27
 
28
28
 
@@ -74,6 +74,7 @@ files:
74
74
  - lib/rest-core/error.rb
75
75
  - lib/rest-core/event.rb
76
76
  - lib/rest-core/middleware.rb
77
+ - lib/rest-core/middleware/auth_basic.rb
77
78
  - lib/rest-core/middleware/bypass.rb
78
79
  - lib/rest-core/middleware/cache.rb
79
80
  - lib/rest-core/middleware/common_logger.rb
@@ -99,9 +100,12 @@ files:
99
100
  - rest-core.gemspec
100
101
  - task/.gitignore
101
102
  - task/gemgem.rb
103
+ - test/test_auth_basic.rb
102
104
  - test/test_builder.rb
103
105
  - test/test_client.rb
106
+ - test/test_client_oauth1.rb
104
107
  - test/test_oauth1_header.rb
108
+ - test/test_universal.rb
105
109
  - test/test_wrapper.rb
106
110
  homepage: https://github.com/cardinalblue/rest-core
107
111
  licenses: []
@@ -128,7 +132,10 @@ signing_key:
128
132
  specification_version: 3
129
133
  summary: Modular Ruby clients interface for REST APIs
130
134
  test_files:
135
+ - test/test_auth_basic.rb
131
136
  - test/test_builder.rb
132
137
  - test/test_client.rb
138
+ - test/test_client_oauth1.rb
133
139
  - test/test_oauth1_header.rb
140
+ - test/test_universal.rb
134
141
  - test/test_wrapper.rb