# :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 = %(You are being redirected.) 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 = %(You are being redirected.) 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