rest-core 0.7.2 → 0.8.0

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