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 +7 -0
- data/Gemfile.lock +9 -0
- data/README.rdoc +119 -2
- data/VERSION +1 -1
- data/lib/meal_ticket.rb +174 -12
- data/meal_ticket.gemspec +8 -3
- data/test/test_meal_ticket.rb +179 -2
- metadata +40 -13
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
|
-
|
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
|
-
|
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.
|
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.
|
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-
|
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"])
|
data/test/test_meal_ticket.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
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:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
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-
|
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: :
|
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:
|
35
|
+
name: crack
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
prerelease: false
|
39
|
-
type: :
|
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: *
|
80
|
+
version_requirements: *id004
|
53
81
|
- !ruby/object:Gem::Dependency
|
54
82
|
prerelease: false
|
55
83
|
type: :development
|
56
|
-
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: *
|
96
|
+
version_requirements: *id005
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
prerelease: false
|
71
99
|
type: :development
|
72
|
-
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: *
|
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
|