rest-graph 1.1.1 → 1.2.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.
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key for verifying the integrity of signed cookies.
4
+ # If you change this key, all old signed cookies will become invalid!
5
+ # Make sure the secret is at least 30 characters and all random,
6
+ # no regular words or you'll be exposed to dictionary attacks.
7
+ ActionController::Base.cookie_verifier_secret = '095e8e5c0b6b901901efb23fab50005b68d9d6a9d41f4ec780946cff34b26603762bc0ea1baf204613b252e5ae499d38b232d5b75edac513a723e450e76548a3';
@@ -0,0 +1,21 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # These settings change the behavior of Rails 2 apps and will be defaults
4
+ # for Rails 3. You can remove this initializer when Rails 3 is released.
5
+
6
+ if defined?(ActiveRecord)
7
+ # Include Active Record class name as root for JSON serialized output.
8
+ ActiveRecord::Base.include_root_in_json = true
9
+
10
+ # Store the full class name (including module namespace) in STI type column.
11
+ ActiveRecord::Base.store_full_sti_class = true
12
+ end
13
+
14
+ ActionController::Routing.generate_best_match = false
15
+
16
+ # Use ISO 8601 format for JSON serialized times and dates.
17
+ ActiveSupport.use_standard_json_time_format = true
18
+
19
+ # Don't escape HTML entities in JSON, leave that for the #json_escape helper.
20
+ # if you're including raw json in an HTML page.
21
+ ActiveSupport.escape_html_entities_in_json = false
@@ -0,0 +1,15 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key for verifying cookie session data integrity.
4
+ # If you change this key, all old sessions will become invalid!
5
+ # Make sure the secret is at least 30 characters and all random,
6
+ # no regular words or you'll be exposed to dictionary attacks.
7
+ ActionController::Base.session = {
8
+ :key => '_rails_session',
9
+ :secret => 'c99a19ce0dc4ed1809e32b6b43bd9229c3a504c456230119dd445fdcb63c0ce06b436f1bf1eace27ebbe0da6041ff2b65cbb4ae4beadc3077e3e6ae07ea75118'
10
+ }
11
+
12
+ # Use the database for sessions instead of the cookie-based default,
13
+ # which shouldn't be used to store highly confidential information
14
+ # (create the session table with "rake db:sessions:create")
15
+ # ActionController::Base.session_store = :active_record_store
@@ -0,0 +1,5 @@
1
+
2
+ development:
3
+ app_id: ''
4
+ secret: ''
5
+ canvas: ''
@@ -0,0 +1,43 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ # The priority is based upon order of creation: first created -> highest priority.
3
+
4
+ # Sample of regular route:
5
+ # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6
+ # Keep in mind you can assign values other than :controller and :action
7
+
8
+ # Sample of named route:
9
+ # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
10
+ # This route can be invoked with purchase_url(:id => product.id)
11
+
12
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
13
+ # map.resources :products
14
+
15
+ # Sample resource route with options:
16
+ # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }
17
+
18
+ # Sample resource route with sub-resources:
19
+ # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
20
+
21
+ # Sample resource route with more complex sub-resources
22
+ # map.resources :products do |products|
23
+ # products.resources :comments
24
+ # products.resources :sales, :collection => { :recent => :get }
25
+ # end
26
+
27
+ # Sample resource route within a namespace:
28
+ # map.namespace :admin do |admin|
29
+ # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
30
+ # admin.resources :products
31
+ # end
32
+
33
+ # You can have the root of your site routed with map.root -- just remember to delete public/index.html.
34
+ map.root :controller => 'application'
35
+
36
+ # See how all your routes lay out with "rake routes"
37
+
38
+ # Install the default routes as the lowest priority.
39
+ # Note: These default routes make all actions in every controller accessible via GET requests. You should
40
+ # consider removing or commenting them out if you're using named routes and resources.
41
+ # map.connect ':controller/:action/:id'
42
+ # map.connect ':controller/:action/:id.:format'
43
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path('../../config/boot', __FILE__)
3
+ require 'commands/console'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path('../../config/boot', __FILE__)
3
+ require 'commands/server'
data/init.rb CHANGED
@@ -1,2 +1,3 @@
1
+
1
2
  require 'rest-graph'
2
3
  require 'rest-graph/auto_load'
@@ -1,15 +1,35 @@
1
1
 
2
+ # gem
2
3
  require 'rest_client'
3
4
 
5
+ # stdlib
6
+ require 'digest/md5'
4
7
  require 'cgi'
5
8
 
9
+ # optional gem
10
+ begin
11
+ require 'rack'
12
+ rescue LoadError; end
13
+
14
+ begin
15
+ require 'json'
16
+ rescue LoadError
17
+ begin
18
+ require 'json_pure'
19
+ rescue LoadError; end
20
+ end
21
+
6
22
  # the data structure used in RestGraph
7
23
  RestGraphStruct = Struct.new(:data, :auto_decode,
8
24
  :graph_server, :fql_server,
9
25
  :accept, :lang,
10
- :app_id, :secret)
26
+ :app_id, :secret,
27
+ :error_handler,
28
+ :log_handler) unless defined?(RestGraphStruct)
11
29
 
12
30
  class RestGraph < RestGraphStruct
31
+ class Error < RuntimeError; end
32
+
13
33
  Attributes = RestGraphStruct.members.map(&:to_sym)
14
34
 
15
35
  # honor default attributes
@@ -32,14 +52,19 @@ class RestGraph < RestGraphStruct
32
52
  def default_lang ; 'en-us' ; end
33
53
  def default_app_id ; nil ; end
34
54
  def default_secret ; nil ; end
55
+ def default_error_handler
56
+ lambda{ |error| raise ::RestGraph::Error.new(error) }
57
+ end
58
+ def default_log_handler
59
+ lambda{ |duration, url| }
60
+ end
35
61
  end
36
62
  extend DefaultAttributes
37
63
 
38
- def initialize o = {}
64
+ def initialize o={}
39
65
  (Attributes + [:access_token]).each{ |name|
40
66
  send("#{name}=", o[name]) if o.key?(name)
41
67
  }
42
- check_arguments!
43
68
  end
44
69
 
45
70
  def access_token
@@ -54,27 +79,40 @@ class RestGraph < RestGraphStruct
54
79
  !!access_token
55
80
  end
56
81
 
57
- def get path, opts = {}
82
+ def get path, opts={}
58
83
  request(graph_server, path, opts, :get)
59
84
  end
60
85
 
61
- def delete path, opts = {}
86
+ def delete path, opts={}
62
87
  request(graph_server, path, opts, :delete)
63
88
  end
64
89
 
65
- def post path, payload, opts = {}
90
+ def post path, payload, opts={}
66
91
  request(graph_server, path, opts, :post, payload)
67
92
  end
68
93
 
69
- def put path, payload, opts = {}
94
+ def put path, payload, opts={}
70
95
  request(graph_server, path, opts, :put, payload)
71
96
  end
72
97
 
73
- def fql query, opts = {}
98
+ def fql query, opts={}
74
99
  request(fql_server, 'method/fql.query',
75
100
  {:query => query, :format => 'json'}.merge(opts), :get)
76
101
  end
77
102
 
103
+ def fql_multi queries, opts={}
104
+ q = if queries.respond_to?(:to_json)
105
+ queries.to_json
106
+ else
107
+ middle = queries.inject([]){ |r, (k, v)|
108
+ r << "\"#{k}\":\"#{v.gsub('"','\\"')}\""
109
+ }.join(',')
110
+ "{#{middle}}"
111
+ end
112
+ request(fql_server, 'method/fql.multiquery',
113
+ {:queries => q, :format => 'json'}.merge(opts), :get)
114
+ end
115
+
78
116
  # cookies, app_id, secrect related below
79
117
 
80
118
  if RUBY_VERSION >= '1.9.1'
@@ -98,6 +136,12 @@ class RestGraph < RestGraphStruct
98
136
  check_sig_and_return_data(Rack::Utils.parse_query(fbs[1..-2]))
99
137
  end
100
138
 
139
+ def parse_json! json
140
+ self.data = json &&
141
+ check_sig_and_return_data(JSON.load(json))
142
+ rescue JSON::ParserError
143
+ end
144
+
101
145
  # oauth related
102
146
 
103
147
  def authorize_url opts={}
@@ -112,37 +156,21 @@ class RestGraph < RestGraphStruct
112
156
  end
113
157
 
114
158
  private
115
- def check_arguments!
116
- if auto_decode
117
- begin
118
- require 'json'
119
- rescue LoadError
120
- require 'json_pure'
121
- end
122
- end
123
-
124
- if app_id && secret # want to parse access_token in cookies
125
- require 'digest/md5'
126
- require 'rack'
127
- elsif app_id || secret
128
- raise ArgumentError.new("You may want to pass both" \
129
- " app_id(#{app_id.inspect}) and" \
130
- " secret(#{secret.inspect})")
131
- end
132
- end
133
-
134
159
  def request server, path, opts, method, payload=nil, suppress_decode=false
160
+ start_time = Time.now
161
+ res = RestClient::Resource.new(server)[path + build_query_string(opts)]
135
162
  post_request(
136
- RestClient::Resource.new(server)[path + build_query_string(opts)].
137
- send(method, *[payload, build_headers].compact), suppress_decode)
163
+ res.send(method, *[payload, build_headers].compact), suppress_decode)
138
164
  rescue RestClient::InternalServerError => e
139
165
  post_request(e.http_body, suppress_decode)
166
+ ensure
167
+ log_handler.call(Time.now - start_time, res.url)
140
168
  end
141
169
 
142
- def build_query_string q={}
143
- query = q.merge(access_token ? {:access_token => access_token} : {})
144
- return '' if query.empty?
145
- return '?' + query.map{ |(k, v)| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
170
+ def build_query_string query={}
171
+ q = query.merge(access_token ? {:access_token => access_token} : {})
172
+ return '' if q.empty?
173
+ return '?' + q.map{ |(k, v)| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
146
174
  end
147
175
 
148
176
  def build_headers
@@ -153,11 +181,23 @@ class RestGraph < RestGraphStruct
153
181
  end
154
182
 
155
183
  def post_request result, suppress_decode=false
156
- (auto_decode && !suppress_decode) ? JSON.parse(result) : result
184
+ if auto_decode && !suppress_decode
185
+ check_error(JSON.parse(result))
186
+ else
187
+ result
188
+ end
157
189
  end
158
190
 
159
191
  def check_sig_and_return_data cookies
160
- cookies if calculate_sig(cookies) == cookies['sig']
192
+ cookies if secret && calculate_sig(cookies) == cookies['sig']
193
+ end
194
+
195
+ def check_error hash
196
+ if error_handler && hash.kind_of?(Hash) && hash['error']
197
+ error_handler.call(hash)
198
+ else
199
+ hash
200
+ end
161
201
  end
162
202
 
163
203
  def calculate_sig cookies
@@ -3,40 +3,39 @@ require 'erb'
3
3
  require 'yaml'
4
4
 
5
5
  require 'rest-graph'
6
+ require 'rest-graph/rails_controller' if Object.const_defined?(:Rails)
6
7
 
7
- class RestGraph < RestGraphStruct
8
- module LoadConfig
9
- module_function
10
- def auto_load!
11
- LoadConfig.load_if_rails!
12
- end
13
-
14
- def load_if_rails!
15
- return unless Object.const_defined?(:Rails)
16
- root = Rails.root
17
- file = ["#{root}/config/rest-graph.yaml", # YAML should use .yaml
18
- "#{root}/config/rest-graph.yml"].find{|path| File.exist?(path)}
19
- return unless file
20
-
21
- LoadConfig.load_config!(file, Rails.env)
22
- end
23
-
24
- def load_config! file, env
25
- config = YAML.load(ERB.new(File.read(file)).result(binding))
26
- defaults = config[env]
27
- return unless defaults
28
-
29
- mod = Module.new
30
- mod.module_eval(defaults.inject([]){ |r, (k, v)|
31
- r << <<-RUBY
32
- def default_#{k}
33
- # quote strings, leave others free (e.g. false, numbers, etc)
34
- #{v.kind_of?(String) ? "'#{v}'" : v}
35
- end
36
- RUBY
37
- }.join)
38
-
39
- RestGraph.send(:extend, mod)
40
- end
8
+ module RestGraph::LoadConfig
9
+ module_function
10
+ def auto_load!
11
+ RestGraph::LoadConfig.load_if_rails!
12
+ end
13
+
14
+ def load_if_rails!
15
+ return unless Object.const_defined?(:Rails)
16
+ root = Rails.root
17
+ file = ["#{root}/config/rest-graph.yaml", # YAML should use .yaml
18
+ "#{root}/config/rest-graph.yml"].find{|path| File.exist?(path)}
19
+ return unless file
20
+
21
+ RestGraph::LoadConfig.load_config!(file, Rails.env)
22
+ end
23
+
24
+ def load_config! file, env
25
+ config = YAML.load(ERB.new(File.read(file)).result(binding))
26
+ defaults = config[env]
27
+ return unless defaults
28
+
29
+ mod = Module.new
30
+ mod.module_eval(defaults.inject([]){ |r, (k, v)|
31
+ r << <<-RUBY
32
+ def default_#{k}
33
+ # quote strings, leave others free (e.g. false, numbers, etc)
34
+ #{v.kind_of?(String) ? "'#{v}'" : v}
35
+ end
36
+ RUBY
37
+ }.join)
38
+
39
+ RestGraph.send(:extend, mod)
41
40
  end
42
41
  end
@@ -0,0 +1,100 @@
1
+
2
+ require 'rest-graph'
3
+
4
+ module RestGraph::RailsController
5
+ module_function
6
+ # filters for you
7
+ def setup_iframe
8
+ @fb_sig_in_iframe = true
9
+ end
10
+
11
+ def setup_rest_graph
12
+ rest_graph_create
13
+
14
+ # exchange the code with access_token
15
+ if params[:code]
16
+ @rg.authorize!(:code => params[:code],
17
+ :redirect_uri => normalized_request_uri)
18
+ logger.debug(
19
+ "DEBUG: RestGraph: detected code with #{normalized_request_uri}, " \
20
+ "parsed: #{@rg.data.inspect}")
21
+ end
22
+
23
+ # if the code is bad or not existed,
24
+ # check if there's one in session,
25
+ # meanwhile, there the sig and access_token is correct,
26
+ # that means we're in the context of iframe
27
+ if !@rg.authorized? && params[:session]
28
+ @rg.parse_json!(params[:session])
29
+ logger.debug(
30
+ "DEBUG: RestGraph: detected session, parsed: #{@rg.data.inspect}")
31
+
32
+ if @rg.authorized?
33
+ @fb_sig_in_iframe = true
34
+ else
35
+ logger.warn("WARN: RestGraph: bad session: #{params[:session]}")
36
+ end
37
+ end
38
+
39
+ # if we're not in iframe nor code passed,
40
+ # we could check out cookies as well.
41
+ if !@rg.authorized?
42
+ @rg.parse_cookies!(cookies)
43
+ logger.debug(
44
+ "DEBUG: RestGraph: detected cookies, parsed: #{@rg.data.inspect}")
45
+ end
46
+
47
+ # there are above 3 ways to check the user identity!
48
+ # if nor of them passed, then we can suppose the user
49
+ # didn't authorize for us
50
+ end
51
+
52
+ # override this if you need different app_id and secret
53
+ def rest_graph_create
54
+ @rg ||= RestGraph.new(:error_handler => method(:rest_graph_authorize),
55
+ :log_handler => method(:rest_graph_log))
56
+ end
57
+
58
+ def rest_graph_authorize error=nil
59
+ logger.warn("WARN: RestGraph: #{error.inspect}") if error
60
+
61
+ @authorize_url = @rg.authorize_url(
62
+ {:redirect_uri => normalized_request_uri,
63
+ :scope => rest_graph_authorize_scope}.
64
+ merge(rest_graph_authorize_options))
65
+
66
+ logger.debug("DEBUG: RestGraph: redirect to #{@authorize_url}")
67
+
68
+ rest_graph_authorize_redirect
69
+ return false
70
+ end
71
+
72
+ # override this if you need different access scope
73
+ def rest_graph_authorize_scope
74
+ @rest_graph_authorize_scope ||=
75
+ 'offline_access,publish_stream,read_friendlists'
76
+ end
77
+
78
+ # override this if you want the simple redirect_to
79
+ def rest_graph_authorize_redirect
80
+ render :template => 'rest-graph/authorization'
81
+ end
82
+
83
+ def rest_graph_authorize_options
84
+ @rest_graph_authorize_options ||= {}
85
+ end
86
+
87
+ def rest_graph_log duration, url
88
+ logger.debug("DEBUG: RestGraph: spent #{duration} requesting #{url}")
89
+ end
90
+
91
+ def normalized_request_uri
92
+ if @fb_sig_in_iframe
93
+ "http://apps.facebook.com/" \
94
+ "#{RestGraph.default_canvas}#{request.request_uri}"
95
+ else
96
+ request.url
97
+ end.sub(/[\&\?]session=[^\&]+/, '').
98
+ sub(/[\&\?]code=[^\&]+/, '')
99
+ end
100
+ end