lib/meal_ticket.rb in meal_ticket-0.0.0 vs lib/meal_ticket.rb in meal_ticket-0.1.0
- old
+ new
@@ -1,14 +1,176 @@
-#
-# == Steps
-#
-# 1. Developer signs up for 3rd-part API (let's say Facebook)
-# 1. Developer copies & pastes access info into some config location
-# 1. Developer creates a callback action that will receive the user's token
-# !. Developer redirects the user to something like facebook_auth_url() passing a parameter indicating what permissions he needs
-#
-#
-module MealTicket
-
+# :include:README.rdoc
+require 'cgi'
+module MealTicketRoutes
+ FLICKR_API_BASE_URL = "http://api.flickr.com/services/" # :nodoc:
+
+ # [root_url] The base url of your app, eg "http://www.google.com/". If you're running a rails app, you can literally type root_url
+ # [scope] A comma-separated list of permissions. For a full list of permissions, see https://developers.facebook.com/docs/authentication/permissions/
+ def facebook_auth_url(root_url, scope)
+ "https://graph.facebook.com/oauth/authorize?client_id=#{FACEBOOK_APP_ID}&redirect_uri=#{root_url}meal_ticket/facebook_callback&scope=#{scope}"
+ end
+
+ # Generates the URL for the 2nd step of Facebook auth - exchanging the code for user data
+ def facebook_exchange_url(root_url, code) # :nodoc:
+ "https://graph.facebook.com/oauth/access_token?client_id=#{FACEBOOK_APP_ID}&redirect_uri=#{root_url}meal_ticket/facebook_callback&client_secret=#{FACEBOOK_SECRET}&code=#{CGI::escape code}"
+ end
+
+ # [perm] A single permission level. Permissions can be read, write, or delete. Each successive permission implies the ones before it, eg "write" implies "read". For more information, see http://www.flickr.com/services/api/auth.spec.html
+ def flickr_auth_url(perm)
+ flickr_url({"perms" => perm}, "auth")
+ end
+
+ # Generates the URL for the 2nd step of Facebook auth - exchanging the frob for user data
+ def flickr_frob_url(frob) # :nodoc:
+ flickr_url({"method" => "flickr.auth.getToken", "frob" => frob})
+ end
+
+ private
+
+ def flickr_url(arg_hash, endpoint = "rest")
+ arg_hash.merge!({"api_key" => FLICKR_TOKEN})
+ arg_list = []
+ arg_hash.each do |key, value|
+ arg_list << "#{key}=#{value}"
+ end
+ "#{FLICKR_API_BASE_URL}#{endpoint}/?&api_sig=#{flickr_sign(arg_hash)}&#{arg_list.join('&')}"
+ end
+
+ def flickr_sign(arg_hash)
+ arg_list = []
+ arg_hash.keys.sort.each do |key|
+ arg_list << key
+ arg_list << arg_hash[key]
+ end
+ Digest::MD5.hexdigest("#{FLICKR_SECRET}#{arg_list.join()}")
+ end
end
+
+
+require 'net/http'
+require 'net/https'
+require 'crack'
+require 'json'
+class MealTicket
+ include MealTicketRoutes
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @env = env
+ if env["PATH_INFO"] =~ /^\/meal_ticket\/facebook_callback/
+ facebook_callback
+ elsif env["PATH_INFO"] =~ /^\/meal_ticket\/flickr_callback/
+ flickr_callback
+ else
+ @app.call(env)
+ end
+ end
+
+ def facebook_callback
+ response = get facebook_exchange_url(get_root_url, get_query_string_parameter("code"))
+
+ if response.body.include? "access_token"
+ response.body =~ /access_token=([^&]+)(?:&expires=(.*))?/ # TODO: genericize get_query_string_parameter to handle this?
+ token = $1 || ""
+ expires = $2 || ""
+ query_string = "facebook[token]=#{CGI.escape(token)}&facebook[expires]=#{CGI.escape(expires)}"
+ else
+ parsed = JSON.parse(response.body)
+ query_string = to_params({:facebook => parsed})
+ end
+
+ body = %(<html><body>You are being redirected.</body></html>)
+ headers = {
+ 'Location' => "#{get_root_url}#{FACEBOOK_CALLBACK}?#{query_string}",
+ 'Content-Type' => 'text/html',
+ 'Content-Length' => body.length.to_s
+ }
+ [302, headers, [body]]
+ end
+
+ def flickr_callback
+
+ xml = get flickr_frob_url(get_query_string_parameter("frob"))
+
+ parsed = Crack::XML.parse(xml.body)
+ if parsed["rsp"]["stat"] == "ok"
+ remote_id = parsed["rsp"]["auth"]["user"]["nsid"] || ""
+ token = parsed["rsp"]["auth"]["token"] || ""
+ query_string = "flickr[token]=#{CGI.escape(token)}&flickr[user_id]=#{CGI.escape(remote_id)}"
+ else
+ code = parsed["rsp"]["err"]["code"]
+ msg = parsed["rsp"]["err"]["msg"]
+ query_string = "flickr[error][code]=#{CGI.escape code}&flickr[error][msg]=#{CGI.escape msg}"
+ end
+
+ body = %(<html><body>You are being redirected.</body></html>)
+ headers = {
+ 'Location' => "#{get_root_url}#{FLICKR_CALLBACK}?#{query_string}",
+ 'Content-Type' => 'text/html',
+ 'Content-Length' => body.length.to_s
+ }
+ [302, headers, [body]]
+ end
+
+ private
+ def get(url)
+ use_ssl = url.include? 'https'
+ url = URI.parse(url)
+
+ path = url.query.blank? ? url.path : "#{url.path}?#{url.query}"
+
+ http = Net::HTTP.new(url.host, use_ssl ? 443 : nil)
+ http.use_ssl = use_ssl
+ res = http.get(path)
+
+ if res.code == '302'
+ return get(res['location']) # follow redirects
+ end
+ res
+ end
+
+ private
+ def get_query_string_parameter(param)
+ @env["QUERY_STRING"] =~ Regexp.new("#{param}=(.*)&|#{param}=(.*)$")
+ return $1 || $2 # whichever one worked
+ end
+
+ def get_root_url
+ @env["REQUEST_URI"] =~ /(^.*?:\/\/.*?\/)/ # looking for the form .....://.........../
+ return $1
+ end
+
+
+ # stolen from merb, according to StackOverflow
+ def to_params(obj)
+ params = ''
+ stack = []
+
+ obj.each do |k, v|
+ if v.is_a?(Hash)
+ stack << [k, v]
+ else
+ params << "#{CGI.escape k}=#{CGI.escape v}&"
+ end
+ end
+
+ stack.each do |parent, hash|
+ hash.each do |k, v|
+ if v.is_a?(Hash)
+ stack << ["#{parent}[#{k}]", v]
+ else
+ params << "#{parent}[#{CGI.escape k}]=#{CGI.escape v}&"
+ end
+ end
+ end
+
+ params.chop! # trailing &
+ params
+ end
+end
+
+