meal_ticket 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,6 +3,13 @@ source "http://rubygems.org"
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
5
 
6
+ gem 'crack'
7
+ gem 'json'
8
+
9
+ group :test do
10
+ gem 'webmock'
11
+ end
12
+
6
13
  # Add dependencies to develop your gem here.
7
14
  # Include everything needed to run rake, tests, features, etc.
8
15
  group :development do
data/Gemfile.lock CHANGED
@@ -1,20 +1,29 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ addressable (2.2.4)
5
+ crack (0.1.8)
4
6
  git (1.2.5)
5
7
  jeweler (1.5.2)
6
8
  bundler (~> 1.0.0)
7
9
  git (>= 1.2.5)
8
10
  rake
11
+ json (1.5.1)
9
12
  rake (0.8.7)
10
13
  rcov (0.9.9)
11
14
  shoulda (2.11.3)
15
+ webmock (1.6.2)
16
+ addressable (>= 2.2.2)
17
+ crack (>= 0.1.7)
12
18
 
13
19
  PLATFORMS
14
20
  ruby
15
21
 
16
22
  DEPENDENCIES
17
23
  bundler (~> 1.0.0)
24
+ crack
18
25
  jeweler (~> 1.5.2)
26
+ json
19
27
  rcov
20
28
  shoulda
29
+ webmock
data/README.rdoc CHANGED
@@ -1,6 +1,123 @@
1
- = meal_ticket
1
+ MealTicket simplifies the process of authenticating with 3rd-party APIs by eliminating the stuff that's the same for everyone,
2
+ letting you focus solely on the parts of authentication that matter to you.
2
3
 
3
- Description goes here.
4
+ == Overview
5
+
6
+ 1. You pick a service you want to authenticate against
7
+ 1. You decide what permissions you need
8
+ 1. You redirect your user to something like facebook_auth_url(root_url, "user_photos,publish_stream")
9
+ 1. MealTicket handles the gruesome details of the various psuedo-OAuth schemes
10
+ 1. MealTicket redirects the user back to a url of your choice along with their access token
11
+
12
+ == Currently Supported Services
13
+
14
+ * facebook
15
+ * flickr
16
+
17
+ == Getting Started
18
+
19
+ 1. Require the gem. In your gemfile:
20
+
21
+ gem 'meal_ticket'
22
+
23
+ 1. Install the gem. In your console:
24
+
25
+ bundle install (or maybe 'sudo bundle install')
26
+
27
+ 1. Install MealTicket as middleware to handle cross-domain communication. In Rails, you'd add something like this to your application.rb:
28
+
29
+ module YourAppName
30
+ class Application < Rails::Application
31
+ config.middleware.use "MealTicket"
32
+
33
+ 1. Make meal_ticket URLs available to your views. In Rails, you'd add something like this to your application_helper.rb:
34
+
35
+ require 'meal_ticket'
36
+
37
+ module ApplicationHelper
38
+ include MealTicketRoutes
39
+ end
40
+
41
+ 1. Optionally, make meal_ticket URLs available to your controllers. In Rails, you'd add something like this to your application_controller.rb:
42
+
43
+ require 'meal_ticket'
44
+
45
+ class ApplicationController < ActionController::Base
46
+ include MealTicketRoutes
47
+ end
48
+
49
+ Now that you've finished installing MealTicket, look below for further instructions on how to connect with individual services.
50
+
51
+ = Service-Specific Instructions
52
+
53
+ For each service you want to integrate with, find it here and follow the steps to get your API keys.
54
+
55
+ In general, you'll need to do a couple things for each service:
56
+
57
+ 1. Go to their site, get your API keys, and make global constants for them.
58
+ 1. Create a callback method to receive the user's access token. Make sure you also map a route for this method.
59
+
60
+ == Facebook
61
+
62
+ 1. Log in to Facebook.
63
+ 1. Go to https://www.facebook.com/developers/apps.php and click the "Set Up New App" button.
64
+ 1. Fill out the forms to create a new app.
65
+ 1. Once you land on the "Edit" page, click the "Web Site" tab on the left.
66
+ 1. In the "Site Url" field, type the address of your site. For development, use something like +localhost:3000+. You may want
67
+ to set up a separate app for production.
68
+
69
+ Create global constants that look something like this:
70
+
71
+ FACEBOOK_APP_ID = "158079864105359" # facebook calls this "App ID"
72
+ FACEBOOK_SECRET = "98882d6d6cf0d7b69a5de5cc43abc10" # facebook calls this "App Secret"
73
+ FACEBOOK_CALLBACK = "path/to/my/facebook/callback" # whatever URL you've created to grab the user data and do something useful
74
+
75
+ Now, redirect users to facebook_auth_url, passing the permissions you want to ask for. Like so:
76
+
77
+ # For a full list of permissions, see https://developers.facebook.com/docs/authentication/permissions/
78
+ redirect_to facebook_auth_url("user_photos,publish_stream")
79
+
80
+
81
+ After they authenticate, they'll be redirected to your +FACEBOOK_CALLBACK+ URL with query string params like:
82
+
83
+ ?facebook[token]=q2jf89ojq.j32f|FQf9j23la&facebook[expires]=4829
84
+ - or, more legibly: -
85
+ {:facebook => {:token => "q2jf89ojq.j32f|FQf9j23la", :expires => "4829"}}
86
+
87
+
88
+ Notes:
89
+
90
+ * Your facebook "API key" is never actually used.
91
+ * The expires value is the number of seconds for while this token is valid. If you request the offline_access permission, expires will be blank and the token is valid forever.
92
+
93
+ == Flickr
94
+
95
+ 1. Log in to Flickr.
96
+ 1. Go to http://www.flickr.com/services/apps/create/apply/ to register for API keys.
97
+ 1. Fill out the forms to create a new app.
98
+ 1. Once you're done, find the "edit authentication flow" page ({www.flickr.com/services/apps/YOUR_FLICKR_APP_ID/auth/}[www.flickr.com/services/apps]) and
99
+ set the Callback URL to <your root url>/meal_ticket/flickr_callback
100
+
101
+ Create global constants that look something like this:
102
+
103
+ FLICKR_TOKEN = "3637b1e30ae90503fedf9aaca8a4c370"
104
+ FLICKR_SECRET = "3570d29a7a3c086b"
105
+ FLICKR_CALLBACK = "path/to/my/flickr/callback" # whatever URL you've created to grab the user data and do something useful
106
+
107
+ Now, redirect users to flickr_auth_url, passing the permission level you want to ask for. Like so:
108
+
109
+ # For a full list of permissions, see https://developers.facebook.com/docs/authentication/permissions/
110
+ redirect_to flickr_auth_url("write")
111
+
112
+ After they authenticate, they'll be redirected to your +FACEBOOK_CALLBACK+ method with params like:
113
+
114
+ ?flickr[token]=q2jf89ojq.j32f|FQf9j23la&facebook[user_id]=
115
+ - or, more legibly: -
116
+ {:flickr => {:token => "3215562516a046266-919fd54999d6e104", :user_id => "27934656@N00"}}
117
+
118
+ Notes:
119
+
120
+ * _nothing_ _to_ _note_
4
121
 
5
122
  == Contributing to meal_ticket
6
123
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
data/lib/meal_ticket.rb CHANGED
@@ -1,14 +1,176 @@
1
- #
2
- # == Steps
3
- #
4
- # 1. Developer signs up for 3rd-part API (let's say Facebook)
5
- # 1. Developer copies & pastes access info into some config location
6
- # 1. Developer creates a callback action that will receive the user's token
7
- # !. Developer redirects the user to something like facebook_auth_url() passing a parameter indicating what permissions he needs
8
- #
9
- #
10
- module MealTicket
11
-
12
-
13
1
 
2
+ # :include:README.rdoc
3
+ require 'cgi'
4
+ module MealTicketRoutes
5
+
6
+ FLICKR_API_BASE_URL = "http://api.flickr.com/services/" # :nodoc:
7
+
8
+ # [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
9
+ # [scope] A comma-separated list of permissions. For a full list of permissions, see https://developers.facebook.com/docs/authentication/permissions/
10
+ def facebook_auth_url(root_url, scope)
11
+ "https://graph.facebook.com/oauth/authorize?client_id=#{FACEBOOK_APP_ID}&redirect_uri=#{root_url}meal_ticket/facebook_callback&scope=#{scope}"
12
+ end
13
+
14
+ # Generates the URL for the 2nd step of Facebook auth - exchanging the code for user data
15
+ def facebook_exchange_url(root_url, code) # :nodoc:
16
+ "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}"
17
+ end
18
+
19
+ # [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
20
+ def flickr_auth_url(perm)
21
+ flickr_url({"perms" => perm}, "auth")
22
+ end
23
+
24
+ # Generates the URL for the 2nd step of Facebook auth - exchanging the frob for user data
25
+ def flickr_frob_url(frob) # :nodoc:
26
+ flickr_url({"method" => "flickr.auth.getToken", "frob" => frob})
27
+ end
28
+
29
+ private
30
+
31
+ def flickr_url(arg_hash, endpoint = "rest")
32
+ arg_hash.merge!({"api_key" => FLICKR_TOKEN})
33
+ arg_list = []
34
+ arg_hash.each do |key, value|
35
+ arg_list << "#{key}=#{value}"
36
+ end
37
+ "#{FLICKR_API_BASE_URL}#{endpoint}/?&api_sig=#{flickr_sign(arg_hash)}&#{arg_list.join('&')}"
38
+ end
39
+
40
+ def flickr_sign(arg_hash)
41
+ arg_list = []
42
+ arg_hash.keys.sort.each do |key|
43
+ arg_list << key
44
+ arg_list << arg_hash[key]
45
+ end
46
+ Digest::MD5.hexdigest("#{FLICKR_SECRET}#{arg_list.join()}")
47
+ end
14
48
  end
49
+
50
+
51
+ require 'net/http'
52
+ require 'net/https'
53
+ require 'crack'
54
+ require 'json'
55
+ class MealTicket
56
+ include MealTicketRoutes
57
+
58
+ def initialize(app)
59
+ @app = app
60
+ end
61
+
62
+ def call(env)
63
+ @env = env
64
+ if env["PATH_INFO"] =~ /^\/meal_ticket\/facebook_callback/
65
+ facebook_callback
66
+ elsif env["PATH_INFO"] =~ /^\/meal_ticket\/flickr_callback/
67
+ flickr_callback
68
+ else
69
+ @app.call(env)
70
+ end
71
+ end
72
+
73
+ def facebook_callback
74
+ response = get facebook_exchange_url(get_root_url, get_query_string_parameter("code"))
75
+
76
+ if response.body.include? "access_token"
77
+ response.body =~ /access_token=([^&]+)(?:&expires=(.*))?/ # TODO: genericize get_query_string_parameter to handle this?
78
+ token = $1 || ""
79
+ expires = $2 || ""
80
+ query_string = "facebook[token]=#{CGI.escape(token)}&facebook[expires]=#{CGI.escape(expires)}"
81
+ else
82
+ parsed = JSON.parse(response.body)
83
+ query_string = to_params({:facebook => parsed})
84
+ end
85
+
86
+ body = %(<html><body>You are being redirected.</body></html>)
87
+ headers = {
88
+ 'Location' => "#{get_root_url}#{FACEBOOK_CALLBACK}?#{query_string}",
89
+ 'Content-Type' => 'text/html',
90
+ 'Content-Length' => body.length.to_s
91
+ }
92
+ [302, headers, [body]]
93
+ end
94
+
95
+ def flickr_callback
96
+
97
+ xml = get flickr_frob_url(get_query_string_parameter("frob"))
98
+
99
+ parsed = Crack::XML.parse(xml.body)
100
+ if parsed["rsp"]["stat"] == "ok"
101
+ remote_id = parsed["rsp"]["auth"]["user"]["nsid"] || ""
102
+ token = parsed["rsp"]["auth"]["token"] || ""
103
+ query_string = "flickr[token]=#{CGI.escape(token)}&flickr[user_id]=#{CGI.escape(remote_id)}"
104
+ else
105
+ code = parsed["rsp"]["err"]["code"]
106
+ msg = parsed["rsp"]["err"]["msg"]
107
+ query_string = "flickr[error][code]=#{CGI.escape code}&flickr[error][msg]=#{CGI.escape msg}"
108
+ end
109
+
110
+ body = %(<html><body>You are being redirected.</body></html>)
111
+ headers = {
112
+ 'Location' => "#{get_root_url}#{FLICKR_CALLBACK}?#{query_string}",
113
+ 'Content-Type' => 'text/html',
114
+ 'Content-Length' => body.length.to_s
115
+ }
116
+ [302, headers, [body]]
117
+ end
118
+
119
+ private
120
+ def get(url)
121
+ use_ssl = url.include? 'https'
122
+ url = URI.parse(url)
123
+
124
+ path = url.query.blank? ? url.path : "#{url.path}?#{url.query}"
125
+
126
+ http = Net::HTTP.new(url.host, use_ssl ? 443 : nil)
127
+ http.use_ssl = use_ssl
128
+ res = http.get(path)
129
+
130
+ if res.code == '302'
131
+ return get(res['location']) # follow redirects
132
+ end
133
+ res
134
+ end
135
+
136
+ private
137
+ def get_query_string_parameter(param)
138
+ @env["QUERY_STRING"] =~ Regexp.new("#{param}=(.*)&|#{param}=(.*)$")
139
+ return $1 || $2 # whichever one worked
140
+ end
141
+
142
+ def get_root_url
143
+ @env["REQUEST_URI"] =~ /(^.*?:\/\/.*?\/)/ # looking for the form .....://.........../
144
+ return $1
145
+ end
146
+
147
+
148
+ # stolen from merb, according to StackOverflow
149
+ def to_params(obj)
150
+ params = ''
151
+ stack = []
152
+
153
+ obj.each do |k, v|
154
+ if v.is_a?(Hash)
155
+ stack << [k, v]
156
+ else
157
+ params << "#{CGI.escape k}=#{CGI.escape v}&"
158
+ end
159
+ end
160
+
161
+ stack.each do |parent, hash|
162
+ hash.each do |k, v|
163
+ if v.is_a?(Hash)
164
+ stack << ["#{parent}[#{k}]", v]
165
+ else
166
+ params << "#{parent}[#{CGI.escape k}]=#{CGI.escape v}&"
167
+ end
168
+ end
169
+ end
170
+
171
+ params.chop! # trailing &
172
+ params
173
+ end
174
+ end
175
+
176
+
data/meal_ticket.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{meal_ticket}
8
- s.version = "0.0.0"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Chris Doyle", "Kerri Miller", "John Postlethwait"]
12
- s.date = %q{2011-03-02}
12
+ s.date = %q{2011-03-08}
13
13
  s.description = %q{Meal Ticket is the easiest way to authenticate users for 3rd-party social APIs. Built to be used in conjunction with Buffet.}
14
14
  s.email = %q{archslide@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -25,7 +25,6 @@ Gem::Specification.new do |s|
25
25
  "Rakefile",
26
26
  "VERSION",
27
27
  "lib/meal_ticket.rb",
28
- "meal_ticket-0.0.0.gem",
29
28
  "meal_ticket.gemspec",
30
29
  "test/helper.rb",
31
30
  "test/test_meal_ticket.rb"
@@ -44,17 +43,23 @@ Gem::Specification.new do |s|
44
43
  s.specification_version = 3
45
44
 
46
45
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
46
+ s.add_runtime_dependency(%q<crack>, [">= 0"])
47
+ s.add_runtime_dependency(%q<json>, [">= 0"])
47
48
  s.add_development_dependency(%q<shoulda>, [">= 0"])
48
49
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
49
50
  s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
50
51
  s.add_development_dependency(%q<rcov>, [">= 0"])
51
52
  else
53
+ s.add_dependency(%q<crack>, [">= 0"])
54
+ s.add_dependency(%q<json>, [">= 0"])
52
55
  s.add_dependency(%q<shoulda>, [">= 0"])
53
56
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
54
57
  s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
55
58
  s.add_dependency(%q<rcov>, [">= 0"])
56
59
  end
57
60
  else
61
+ s.add_dependency(%q<crack>, [">= 0"])
62
+ s.add_dependency(%q<json>, [">= 0"])
58
63
  s.add_dependency(%q<shoulda>, [">= 0"])
59
64
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
65
  s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
@@ -1,7 +1,184 @@
1
1
  require 'helper'
2
+ require 'webmock'
3
+
4
+ FACEBOOK_APP_ID = "123456789"
5
+ FACEBOOK_SECRET = "abcdef"
6
+ FACEBOOK_CALLBACK = "callbacks"
7
+
8
+ FLICKR_TOKEN = "abcde"
9
+ FLICKR_SECRET = "lmnop"
10
+ FLICKR_CALLBACK = "callbacks"
11
+
12
+ # reopen MealTicket so we can set the @env instance variable and call private methods
13
+ class MealTicket
14
+ attr_accessor :env
15
+
16
+ def _get_query_string_parameter(param)
17
+ get_query_string_parameter(param)
18
+ end
19
+
20
+ def _get_root_url
21
+ get_root_url
22
+ end
23
+ end
2
24
 
3
25
  class TestMealTicket < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
26
+ include MealTicketRoutes
27
+ include WebMock::API
28
+
29
+ # utility methods
30
+ context 'Calling the get_query_string_parameter method' do
31
+ setup do
32
+ @rack_app = MealTicket.new({})
33
+ end
34
+
35
+ tests = ["", "one=1", "code=calliope", "one=1@code=calliope", "code=calliope&two=1", "one=1&code=calliope&two=2"]
36
+ expectations = [nil, nil, "calliope", "calliope", "calliope", "calliope"]
37
+
38
+ tests.each_with_index do |query_string, i|
39
+ context "on #{query_string}" do
40
+ setup do
41
+ @rack_app.env = {"QUERY_STRING" => query_string}
42
+ end
43
+
44
+ should "return #{expectations[i]}" do
45
+ assert_equal expectations[i], @rack_app._get_query_string_parameter("code")
46
+ end
47
+ end
48
+ end
6
49
  end
50
+
51
+ context 'Calling the get_root_url method' do
52
+ setup do
53
+ @rack_app = MealTicket.new({})
54
+ end
55
+
56
+ tests = ["http://localhost:3000/", "http://localhost:3000/u/r/i", "https://localhost:3000/", "http://a.b.c.d.e.f.g.com/", "http://a.b.c/d/e/f"]
57
+ expectations = ["http://localhost:3000/", "http://localhost:3000/", "https://localhost:3000/", "http://a.b.c.d.e.f.g.com/", "http://a.b.c/"]
58
+
59
+ tests.each_with_index do |uri, i|
60
+ context "on #{uri}" do
61
+ setup do
62
+ @rack_app.env = {"REQUEST_URI" => uri}
63
+ end
64
+
65
+ should "return #{expectations[i]}" do
66
+ assert_equal expectations[i], @rack_app._get_root_url
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # facebook
73
+ context 'Requesting the facebook auth url' do
74
+ context 'with valid parameters' do
75
+ setup do
76
+ @url = facebook_auth_url("http://localhost:3000/", "user_photos")
77
+ end
78
+
79
+ should 'return a valid url' do
80
+ assert_equal "https://graph.facebook.com/oauth/authorize?client_id=123456789&redirect_uri=http://localhost:3000/meal_ticket/facebook_callback&scope=user_photos", @url
81
+ end
82
+ end
83
+ end
84
+
85
+ context "When a facebook authentication is successful, the facebook_callback method" do
86
+ setup do
87
+ stub_request(:get, "https://graph.facebook.com/oauth/access_token?client_id=123456789&client_secret=abcdef&code=12345&redirect_uri=http://localhost:3000/meal_ticket/facebook_callback").
88
+ with(:headers => {'Accept'=>'*/*'}).
89
+ to_return(:status => 200, :body => "access_token=12345&expires=1000", :headers => {})
90
+
91
+ # create a new instance
92
+ rack_app = MealTicket.new({})
93
+
94
+ # set up the environment - the request where facebook is redirecting the user to our callback
95
+ rack_app.env = {"REQUEST_URI" => "http://localhost:3000/meal_ticket/facebook_callback?code=12345",
96
+ "QUERY_STRING" => "code=12345",
97
+ "PATH_INFO" => "/meal_ticket/facebook_callback"}
98
+
99
+ # process the request
100
+ @response = rack_app.facebook_callback
101
+ end
102
+
103
+ should "redirect to a URL that includes the facebook token and expiration" do
104
+ assert_equal 302, @response[0]
105
+ assert_equal "http://localhost:3000/callbacks?facebook[token]=12345&facebook[expires]=1000", @response[1]['Location']
106
+ end
107
+ end
108
+
109
+ context "When a facebook authentication is unsuccessful, the facebook_callback method" do
110
+ setup do
111
+ stub_request(:get, "https://graph.facebook.com/oauth/access_token?client_id=123456789&client_secret=abcdef&code=12345&redirect_uri=http://localhost:3000/meal_ticket/facebook_callback").
112
+ with(:headers => {'Accept'=>'*/*'}).
113
+ to_return(:status => 400, :body => '{"error": {"type": "OAuthException","message": "Error validating verification code."}}', :headers => {})
114
+
115
+ # create a new instance
116
+ rack_app = MealTicket.new({})
117
+
118
+ # set up the environment - the request where facebook is redirecting the user to our callback
119
+ rack_app.env = {"REQUEST_URI" => "http://localhost:3000/meal_ticket/facebook_callback?code=12345",
120
+ "QUERY_STRING" => "code=12345",
121
+ "PATH_INFO" => "/meal_ticket/facebook_callback"}
122
+
123
+ # process the request
124
+ @response = rack_app.facebook_callback
125
+ end
126
+
127
+ should "redirect to a URL that includes the facebook token and expiration" do
128
+ assert_equal 302, @response[0]
129
+ assert_equal "http://localhost:3000/callbacks?facebook[error][type]=OAuthException&facebook[error][message]=Error+validating+verification+code.", @response[1]['Location']
130
+ end
131
+ end
132
+
133
+
134
+ #flickr
135
+ context "When a flickr authentication is successful, the flickr_callback method" do
136
+ setup do
137
+ stub_request(:get, "http://api.flickr.com/services/rest/?api_key=abcde&api_sig=5a97a411c4281b61eda6d3d746d7afc6&frob=12345&method=flickr.auth.getToken").
138
+ with(:headers => {'Accept'=>'*/*'}).
139
+ to_return(:status => 200, :body => '<?xml version="1.0" encoding="utf-8" ?> <rsp stat="ok"> <auth> <token>12345</token> <perms>delete</perms> <user nsid="12345@N00"/> </auth> </rsp> ', :headers => {})
140
+
141
+ # create a new instance
142
+ rack_app = MealTicket.new({})
143
+
144
+ # set up the environment - the request where facebook is redirecting the user to our callback
145
+ rack_app.env = {"REQUEST_URI" => "http://localhost:3000/meal_ticket/flickr_callback?frob=12345",
146
+ "QUERY_STRING" => "frob=12345",
147
+ "PATH_INFO" => "/meal_ticket/flickr_callback"}
148
+
149
+ # process the request
150
+ @response = rack_app.flickr_callback
151
+ end
152
+
153
+ should "redirect to a URL that includes the facebook token and expiration" do
154
+ assert_equal 302, @response[0]
155
+ assert_equal "http://localhost:3000/callbacks?flickr[token]=12345&flickr[user_id]=12345%40N00", @response[1]['Location']
156
+ end
157
+ end
158
+
159
+ context "When a flickr authentication is successful, the flickr_callback method" do
160
+ setup do
161
+ stub_request(:get, "http://api.flickr.com/services/rest/?api_key=abcde&api_sig=5a97a411c4281b61eda6d3d746d7afc6&frob=12345&method=flickr.auth.getToken").
162
+ with(:headers => {'Accept'=>'*/*'}).
163
+ to_return(:status => 200, :body => '<?xml version="1.0" encoding="utf-8" ?> <rsp stat="fail"> <err code="108" msg="Invalid frob" /> </rsp>', :headers => {})
164
+
165
+ # create a new instance
166
+ rack_app = MealTicket.new({})
167
+
168
+ # set up the environment - the request where facebook is redirecting the user to our callback
169
+ rack_app.env = {"REQUEST_URI" => "http://localhost:3000/meal_ticket/flickr_callback?frob=12345",
170
+ "QUERY_STRING" => "frob=12345",
171
+ "PATH_INFO" => "/meal_ticket/flickr_callback"}
172
+
173
+ # process the request
174
+ @response = rack_app.flickr_callback
175
+ end
176
+
177
+ should "redirect to a URL that includes the facebook token and expiration" do
178
+ assert_equal 302, @response[0]
179
+ assert_equal "http://localhost:3000/callbacks?flickr[error][code]=108&flickr[error][msg]=Invalid+frob", @response[1]['Location']
180
+ end
181
+ end
182
+
183
+
7
184
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meal_ticket
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 0.0.0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Chris Doyle
@@ -17,12 +17,12 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-03-02 00:00:00 -08:00
20
+ date: 2011-03-08 00:00:00 -08:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency
24
24
  prerelease: false
25
- type: :development
25
+ type: :runtime
26
26
  requirement: &id001 !ruby/object:Gem::Requirement
27
27
  none: false
28
28
  requirements:
@@ -32,12 +32,40 @@ dependencies:
32
32
  segments:
33
33
  - 0
34
34
  version: "0"
35
- name: shoulda
35
+ name: crack
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
38
38
  prerelease: false
39
- type: :development
39
+ type: :runtime
40
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ name: json
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ prerelease: false
53
+ type: :development
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ name: shoulda
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ prerelease: false
67
+ type: :development
68
+ requirement: &id004 !ruby/object:Gem::Requirement
41
69
  none: false
42
70
  requirements:
43
71
  - - ~>
@@ -49,11 +77,11 @@ dependencies:
49
77
  - 0
50
78
  version: 1.0.0
51
79
  name: bundler
52
- version_requirements: *id002
80
+ version_requirements: *id004
53
81
  - !ruby/object:Gem::Dependency
54
82
  prerelease: false
55
83
  type: :development
56
- requirement: &id003 !ruby/object:Gem::Requirement
84
+ requirement: &id005 !ruby/object:Gem::Requirement
57
85
  none: false
58
86
  requirements:
59
87
  - - ~>
@@ -65,11 +93,11 @@ dependencies:
65
93
  - 2
66
94
  version: 1.5.2
67
95
  name: jeweler
68
- version_requirements: *id003
96
+ version_requirements: *id005
69
97
  - !ruby/object:Gem::Dependency
70
98
  prerelease: false
71
99
  type: :development
72
- requirement: &id004 !ruby/object:Gem::Requirement
100
+ requirement: &id006 !ruby/object:Gem::Requirement
73
101
  none: false
74
102
  requirements:
75
103
  - - ">="
@@ -79,7 +107,7 @@ dependencies:
79
107
  - 0
80
108
  version: "0"
81
109
  name: rcov
82
- version_requirements: *id004
110
+ version_requirements: *id006
83
111
  description: Meal Ticket is the easiest way to authenticate users for 3rd-party social APIs. Built to be used in conjunction with Buffet.
84
112
  email: archslide@gmail.com
85
113
  executables: []
@@ -98,7 +126,6 @@ files:
98
126
  - Rakefile
99
127
  - VERSION
100
128
  - lib/meal_ticket.rb
101
- - meal_ticket-0.0.0.gem
102
129
  - meal_ticket.gemspec
103
130
  - test/helper.rb
104
131
  - test/test_meal_ticket.rb