koala 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +10 -0
- data/Manifest +15 -0
- data/Rakefile +1 -1
- data/examples/oauth_playground/Capfile +2 -0
- data/examples/oauth_playground/LICENSE +22 -0
- data/examples/oauth_playground/Rakefile +4 -0
- data/examples/oauth_playground/config.ru +27 -0
- data/examples/oauth_playground/config/deploy.rb +39 -0
- data/examples/oauth_playground/config/facebook.yml +13 -0
- data/examples/oauth_playground/lib/load_facebook.rb +3 -0
- data/examples/oauth_playground/lib/oauth_playground.rb +187 -0
- data/examples/oauth_playground/readme.md +8 -0
- data/examples/oauth_playground/spec/oauth_playground_spec.rb +35 -0
- data/examples/oauth_playground/spec/spec_helper.rb +36 -0
- data/examples/oauth_playground/tmp/restart.txt +0 -0
- data/examples/oauth_playground/views/index.erb +206 -0
- data/examples/oauth_playground/views/layout.erb +39 -0
- data/koala.gemspec +3 -3
- data/lib/koala.rb +36 -6
- data/lib/koala/graph_api.rb +63 -2
- data/lib/koala/http_services.rb +3 -2
- data/readme.md +37 -12
- data/spec/facebook_data.yml +17 -5
- data/spec/koala/api_base_tests.rb +15 -0
- data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +13 -4
- data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +14 -3
- data/spec/koala/graph_api/graph_collection_tests.rb +104 -0
- data/spec/koala/net_http_service_tests.rb +1 -1
- data/spec/koala/oauth/oauth_tests.rb +245 -113
- data/spec/koala_spec_helper.rb +1 -0
- data/spec/mock_facebook_responses.yml +39 -26
- data/spec/mock_http_service.rb +1 -1
- metadata +18 -3
data/readme.md
CHANGED
@@ -10,16 +10,38 @@ Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.co
|
|
10
10
|
Graph API
|
11
11
|
----
|
12
12
|
The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
|
13
|
+
|
13
14
|
graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
|
14
15
|
profile = graph.get_object("me")
|
15
|
-
friends = graph.
|
16
|
+
friends = graph.get_connections("me", "friends")
|
16
17
|
graph.put_object("me", "feed", :message => "I am writing on my wall!")
|
17
18
|
|
19
|
+
The response of most requests is the JSON data returned from the Facebook servers as a Hash.
|
20
|
+
|
21
|
+
When retrieving data that returns an array of results (for example, when calling GraphAPI#get_connections or GraphAPI#search) a GraphCollection object (a sub-class of Array) will be returned, which contains added methods for getting the next and previous page of results:
|
22
|
+
|
23
|
+
# Returns the feed items for the currently logged-in user as a GraphCollection
|
24
|
+
feed = graph.get_connections("me", "feed")
|
25
|
+
|
26
|
+
# GraphCollection is a sub-class of Array, so you can use it as a usual Array
|
27
|
+
first_entry = feed[0]
|
28
|
+
last_entry = feed.last
|
29
|
+
|
30
|
+
# Returns the next page of results (also as a GraphCollection)
|
31
|
+
next_feed = feed.next_page
|
32
|
+
|
33
|
+
# Returns an array describing the URL for the next page: [path, arguments]
|
34
|
+
# This is useful for paging across multiple requests
|
35
|
+
next_path, next_args = feed.next_page_params
|
36
|
+
|
37
|
+
# You can use those params to easily get the next (or prevous) page
|
38
|
+
page = graph.get_page(feed.next_page_params)
|
39
|
+
|
18
40
|
Check out the wiki for more examples.
|
19
41
|
|
20
42
|
The old-school REST API
|
21
43
|
-----
|
22
|
-
Where the Graph API and the old REST API overlap, you should choose the Graph API. Unfortunately, that overlap is far from complete, and there are many important API calls
|
44
|
+
Where the Graph API and the old REST API overlap, you should choose the Graph API. Unfortunately, that overlap is far from complete, and there are many important API calls that can't yet be done via the Graph.
|
23
45
|
|
24
46
|
Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
|
25
47
|
|
@@ -40,16 +62,19 @@ If your application uses Koala and the Facebook [JavaScript SDK](http://github.c
|
|
40
62
|
@oauth.get_user_from_cookie(cookies)
|
41
63
|
|
42
64
|
And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
65
|
+
# generate authenticating URL
|
66
|
+
@oauth.url_for_oauth_code
|
67
|
+
# fetch the access token once you have the code
|
68
|
+
@oauth.get_access_token(code)
|
47
69
|
|
48
70
|
You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
|
49
71
|
@oauth.get_app_access_token
|
50
72
|
|
51
73
|
That's it! It's pretty simple once you get the hang of it. If you're new to OAuth, though, check out the wiki and the OAuth Playground example site (see below).
|
52
74
|
|
75
|
+
*Signed Requests:* Excited to try out the new signed request authentication scheme? Good news! Koala now supports parsing those parameters:
|
76
|
+
@oauth.parse_signed_request(request)
|
77
|
+
|
53
78
|
*Exchanging session keys:* Stuck building tab applications on Facebook? Wishing you had an OAuth token so you could use the Graph API? You're in luck! Koala now allows you to exchange session keys for OAuth access tokens:
|
54
79
|
@oauth.get_token_from_session_key(session_key)
|
55
80
|
@oauth.get_tokens_from_session_keys(array_of_session_keys)
|
@@ -60,11 +85,11 @@ The Graph API now allows your application to subscribe to real-time updates for
|
|
60
85
|
|
61
86
|
Currently, Facebook only supports subscribing to users, permissions and errors. On top of that, there are limitations on what attributes and connections for each of these objects you can subscribe to updates for. Check the [official Facebook documentation](http://developers.facebook.com/docs/api/realtime) for more details.
|
62
87
|
|
63
|
-
Koala makes it easy to interact with your applications using the
|
88
|
+
Koala makes it easy to interact with your applications using the RealtimeUpdates class:
|
64
89
|
|
65
|
-
@updates = Koala::Facebook::
|
90
|
+
@updates = Koala::Facebook::RealtimeUpdates.new(:app_id => app_id, :secret => secret)
|
66
91
|
|
67
|
-
You can do just about anything with your real-time update subscriptions using the
|
92
|
+
You can do just about anything with your real-time update subscriptions using the RealtimeUpdates class:
|
68
93
|
|
69
94
|
# Add/modify a subscription to updates for when the first_name or last_name fields of any of your users is changed
|
70
95
|
@updates.subscribe("user", "first_name, last_name", callback_token, verify_token)
|
@@ -75,12 +100,12 @@ You can do just about anything with your real-time update subscriptions using th
|
|
75
100
|
# Unsubscribe from updates for an object
|
76
101
|
@updates.unsubscribe("user")
|
77
102
|
|
78
|
-
And to top it all off,
|
103
|
+
And to top it all off, RealtimeUpdates provides a static method to respond to Facebook servers' verification of your callback URLs:
|
79
104
|
|
80
105
|
# Returns the hub.challenge parameter in params if the verify token in params matches verify_token
|
81
|
-
Koala::Facebook::
|
106
|
+
Koala::Facebook::RealtimeUpdates.meet_challenge(params, your_verify_token)
|
82
107
|
|
83
|
-
For more information about meet_challenge and the
|
108
|
+
For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
|
84
109
|
|
85
110
|
See examples, ask questions
|
86
111
|
-----
|
data/spec/facebook_data.yml
CHANGED
@@ -5,18 +5,18 @@
|
|
5
5
|
|
6
6
|
# You must supply this value yourself to test the GraphAPI class.
|
7
7
|
# Your OAuth token should have publish_stream and read_stream permissions.
|
8
|
-
oauth_token:
|
8
|
+
oauth_token: 119908831367602|2.K0IVdhrRngS7VQM4Z_s6_g__.3600.1285844400-2905623|Q3VNUb9haS3s29X4SYPk6VL1f9A
|
9
9
|
|
10
10
|
# for testing the OAuth class
|
11
11
|
# baseline app
|
12
12
|
oauth_test_data:
|
13
13
|
# You must supply this value yourself, since they will expire.
|
14
|
-
code:
|
14
|
+
code: 2.K0IVdhrRngS7VQM4Z_s6_g__.3600.1285844400-2905623|b5PCXOhEMe2FJXPHAg_mY3Psl6M
|
15
15
|
# easiest way to get session keys: use multiple test accounts with the Javascript login at http://oauth.twoalex.com
|
16
|
-
session_key:
|
16
|
+
session_key: 2.K0IVdhrRngS7VQM4Z_s6_g__.3600.1285844400-2905623
|
17
17
|
multiple_session_keys:
|
18
|
-
-
|
19
|
-
-
|
18
|
+
- 2.K0IVdhrRngS7VQM4Z_s6_g__.3600.1285844400-2905623
|
19
|
+
- 2.K0IVdhrRngS7VQM4Z_s6_g__.3600.1285844400-2905623
|
20
20
|
|
21
21
|
# These values will work out of the box
|
22
22
|
app_id: 119908831367602
|
@@ -34,7 +34,19 @@ oauth_test_data:
|
|
34
34
|
offline_access_cookies:
|
35
35
|
# note: I've revoked the offline access for security reasons, so you can't make calls against this :)
|
36
36
|
fbs_119908831367602: '"access_token=119908831367602|08170230801eb225068e7a70-2905623|Q3LDCYYF8CX9cstxnZLsxiR0nwg.&expires=0&secret=78abaee300b392e275072a9f2727d436&session_key=08170230801eb225068e7a70-2905623&sig=423b8aa4b6fa1f9a571955f8e929d567&uid=2905623"'
|
37
|
+
|
38
|
+
# These values will work out of the box
|
39
|
+
# They're from Facebook's example at http://developers.facebook.com/docs/authentication/canvas
|
40
|
+
# You can update this to live data if desired
|
41
|
+
# request_secret is optional and will fall back to the secret above if absent
|
42
|
+
request_secret: "secret"
|
43
|
+
signed_request: "vlXgu64BQGFSQrY0ZcJBZASMvYvTHu9GQ0YM9rjPSso.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsIjAiOiJwYXlsb2FkIn0"
|
44
|
+
signed_request_result:
|
45
|
+
"0": payload
|
46
|
+
algorithm: HMAC-SHA256
|
37
47
|
|
48
|
+
|
49
|
+
|
38
50
|
subscription_test_data:
|
39
51
|
subscription_path: http://oauth.twoalex.com/subscriptions
|
40
52
|
verify_token: "myverificationtoken|1f54545d5f722733e17faae15377928f"
|
@@ -76,5 +76,20 @@ class ApiBaseTests < Test::Unit::TestCase
|
|
76
76
|
Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'false', {}))
|
77
77
|
@service.api('anything').should be_false
|
78
78
|
end
|
79
|
+
|
80
|
+
describe "with regard to leading slashes" do
|
81
|
+
it "should add a leading / to the path if not present" do
|
82
|
+
path = "anything"
|
83
|
+
Koala.should_receive(:make_request).with("/#{path}", anything, anything, anything).and_return(Koala::Response.new(200, 'true', {}))
|
84
|
+
@service.api(path)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "shouldn't change the path if a leading / is present" do
|
88
|
+
path = "/anything"
|
89
|
+
Koala.should_receive(:make_request).with(path, anything, anything, anything).and_return(Koala::Response.new(200, 'true', {}))
|
90
|
+
@service.api(path)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
79
94
|
end
|
80
95
|
end
|
@@ -39,9 +39,17 @@ shared_examples_for "Koala GraphAPI without an access token" do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
it "should be able to access connections from public Pages" do
|
42
|
-
result = @api.get_connections("contextoptional", "
|
42
|
+
result = @api.get_connections("contextoptional", "photos")
|
43
43
|
result.should be_a(Array)
|
44
44
|
end
|
45
|
+
|
46
|
+
# paging
|
47
|
+
# see also graph_collection_tests
|
48
|
+
it "should make a request for a page when provided a specific set of page params" do
|
49
|
+
query = [1, 2]
|
50
|
+
@api.should_receive(:graph_call).with(*query)
|
51
|
+
@api.get_page(query)
|
52
|
+
end
|
45
53
|
|
46
54
|
it "should not be able to put an object" do
|
47
55
|
lambda { @result = @api.put_object("lukeshepard", "feed", :message => "Hello, world") }.should raise_error(Koala::Facebook::APIError)
|
@@ -66,8 +74,7 @@ shared_examples_for "Koala GraphAPI without an access token" do
|
|
66
74
|
it "should not be able to like an object" do
|
67
75
|
lambda { @api.put_like("7204941866_119776748033392") }.should raise_error(Koala::Facebook::APIError)
|
68
76
|
end
|
69
|
-
|
70
|
-
|
77
|
+
|
71
78
|
# DELETE
|
72
79
|
it "should not be able to delete posts" do
|
73
80
|
# test post on the Ruby SDK Test application
|
@@ -77,9 +84,11 @@ shared_examples_for "Koala GraphAPI without an access token" do
|
|
77
84
|
# SEARCH
|
78
85
|
it "should be able to search" do
|
79
86
|
result = @api.search("facebook")
|
80
|
-
result
|
87
|
+
result.length.should be_an(Integer)
|
81
88
|
end
|
82
89
|
|
90
|
+
it_should_behave_like "Koala GraphAPI with GraphCollection"
|
91
|
+
|
83
92
|
# API
|
84
93
|
it "should never use the rest api server" do
|
85
94
|
Koala.should_receive(:make_request).with(
|
@@ -1,5 +1,5 @@
|
|
1
1
|
shared_examples_for "Koala GraphAPI with an access token" do
|
2
|
-
it "should get public data about a user" do
|
2
|
+
it "should get public data about a user" do
|
3
3
|
result = @api.get_object("koppel")
|
4
4
|
# the results should have an ID and a name, among other things
|
5
5
|
(result["id"] && result["name"]).should_not be_nil
|
@@ -41,10 +41,19 @@ it "should get public data about a user" do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it "should be able to access connections from public Pages" do
|
44
|
-
result = @api.get_connections("contextoptional", "
|
44
|
+
result = @api.get_connections("contextoptional", "photos")
|
45
45
|
result.should be_a(Array)
|
46
46
|
end
|
47
47
|
|
48
|
+
# paging
|
49
|
+
# see also graph_collection_tests
|
50
|
+
it "should make a request for a page when provided a specific set of page params" do
|
51
|
+
query = [1, 2]
|
52
|
+
@api.should_receive(:graph_call).with(*query)
|
53
|
+
@api.get_page(query)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
48
57
|
# PUT
|
49
58
|
it "should be able to write an object to the graph" do
|
50
59
|
result = @api.put_wall_post("Hello, world, from the test suite!")
|
@@ -120,11 +129,13 @@ it "should get public data about a user" do
|
|
120
129
|
# SEARCH
|
121
130
|
it "should be able to search" do
|
122
131
|
result = @api.search("facebook")
|
123
|
-
result
|
132
|
+
result.length.should be_an(Integer)
|
124
133
|
end
|
125
134
|
|
126
135
|
# API
|
127
136
|
# the above tests test this already, but we should consider additional api tests
|
137
|
+
|
138
|
+
it_should_behave_like "Koala GraphAPI with GraphCollection"
|
128
139
|
end
|
129
140
|
|
130
141
|
class FacebookWithAccessTokenTests < Test::Unit::TestCase
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# GraphCollection
|
2
|
+
shared_examples_for "Koala GraphAPI with GraphCollection" do
|
3
|
+
|
4
|
+
it "should create an array-like object" do
|
5
|
+
call = @api.graph_call("contextoptional/photos")
|
6
|
+
Koala::Facebook::GraphCollection.new(call, @api).should be_an(Array)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "when getting a collection" do
|
10
|
+
# GraphCollection methods
|
11
|
+
it "should get a GraphCollection when getting connections" do
|
12
|
+
@result = @api.get_connections("contextoptional", "photos")
|
13
|
+
@result.should be_a(Koala::Facebook::GraphCollection)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return nil if the get_collections call fails with nil" do
|
17
|
+
# this happens sometimes
|
18
|
+
@api.should_receive(:graph_call).and_return(nil)
|
19
|
+
@api.get_connections("contextoptional", "photos").should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should get a GraphCollection when searching" do
|
23
|
+
result = @api.search("facebook")
|
24
|
+
result.should be_a(Koala::Facebook::GraphCollection)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return nil if the search call fails with nil" do
|
28
|
+
# this happens sometimes
|
29
|
+
@api.should_receive(:graph_call).and_return(nil)
|
30
|
+
@api.search("facebook").should be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should get a GraphCollection when paging through results" do
|
34
|
+
@results = @api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=>"2010-09-23T21:17:33+0000"}])
|
35
|
+
@results.should be_a(Koala::Facebook::GraphCollection)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return nil if the page call fails with nil" do
|
39
|
+
# this happens sometimes
|
40
|
+
@api.should_receive(:graph_call).and_return(nil)
|
41
|
+
@api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=>"2010-09-23T21:17:33+0000"}]).should be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# GraphCollection attributes
|
45
|
+
describe "the GraphCollection" do
|
46
|
+
before(:each) do
|
47
|
+
@result = @api.get_connections("contextoptional", "photos")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should have a read-only paging attribute" do
|
51
|
+
lambda { @result.paging }.should_not raise_error
|
52
|
+
lambda { @result.paging = "paging" }.should raise_error(NoMethodError)
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "when getting a whole page" do
|
56
|
+
before(:each) do
|
57
|
+
@second_page = stub("page of Fb graph results")
|
58
|
+
@base = stub("base")
|
59
|
+
@args = stub("args")
|
60
|
+
@page_of_results = stub("page of results")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return the previous page of results" do
|
64
|
+
@result.should_receive(:previous_page_params).and_return([@base, @args])
|
65
|
+
@api.should_receive(:graph_call).with(@base, @args).and_return(@second_page)
|
66
|
+
Koala::Facebook::GraphCollection.should_receive(:new).with(@second_page, @api).and_return(@page_of_results)
|
67
|
+
|
68
|
+
@result.previous_page(@api).should == @page_of_results
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return the next page of results" do
|
72
|
+
@result.should_receive(:next_page_params).and_return([@base, @args])
|
73
|
+
@api.should_receive(:graph_call).with(@base, @args).and_return(@second_page)
|
74
|
+
Koala::Facebook::GraphCollection.should_receive(:new).with(@second_page, @api).and_return(@page_of_results)
|
75
|
+
|
76
|
+
@result.next_page.should == @page_of_results
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should return nil it there are no other pages" do
|
80
|
+
%w{next previous}.each do |this|
|
81
|
+
@result.should_receive("#{this}_page_params".to_sym).and_return(nil)
|
82
|
+
@result.send("#{this}_page", @api).should == nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "when parsing page paramters" do
|
88
|
+
before(:each) do
|
89
|
+
@graph_collection = Koala::Facebook::GraphCollection.new({"data" => []}, Koala::Facebook::GraphAPI.new)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return the base as the first array entry" do
|
93
|
+
base = "url_path"
|
94
|
+
@graph_collection.parse_page_url("anything.com/#{base}?anything").first.should == base
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should return the arguments as a hash as the last array entry" do
|
98
|
+
args_hash = {"one" => "val_one", "two" => "val_two"}
|
99
|
+
@graph_collection.parse_page_url("anything.com/anything?#{args_hash.map {|k,v| "#{k}=#{v}" }.join("&")}").last.should == args_hash
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -74,7 +74,7 @@ class NetHTTPServiceTests < Test::Unit::TestCase
|
|
74
74
|
Bear.make_request('anything', {}, 'post')
|
75
75
|
end
|
76
76
|
|
77
|
-
it "should go to the specified path" do
|
77
|
+
it "should go to the specified path adding a / if it doesn't exist" do
|
78
78
|
path = mock('Path')
|
79
79
|
@http_yield_mock.should_receive(:post).with(path, anything).and_return(@http_request_result)
|
80
80
|
|
@@ -10,7 +10,7 @@ class Time
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class FacebookOAuthTests < Test::Unit::TestCase
|
13
|
-
describe "Koala
|
13
|
+
describe "Koala OAuth tests" do
|
14
14
|
before :each do
|
15
15
|
# make the relevant test data easily accessible
|
16
16
|
@oauth_data = $testing_data["oauth_test_data"]
|
@@ -21,6 +21,13 @@ class FacebookOAuthTests < Test::Unit::TestCase
|
|
21
21
|
@raw_token_string = @oauth_data["raw_token_string"]
|
22
22
|
@raw_offline_access_token_string = @oauth_data["raw_offline_access_token_string"]
|
23
23
|
|
24
|
+
# per Facebook's example:
|
25
|
+
# http://developers.facebook.com/docs/authentication/canvas
|
26
|
+
# this allows us to use Facebook's example data while running the other live data
|
27
|
+
@request_secret = @oauth_data["request_secret"] || @secret
|
28
|
+
@signed_request = @oauth_data["signed_request"]
|
29
|
+
@signed_request_result = @oauth_data["signed_request_result"]
|
30
|
+
|
24
31
|
# this should expanded to cover all variables
|
25
32
|
raise Exception, "Must supply app data to run FacebookOAuthTests!" unless @app_id && @secret && @callback_url &&
|
26
33
|
@code && @raw_token_string &&
|
@@ -51,7 +58,7 @@ class FacebookOAuthTests < Test::Unit::TestCase
|
|
51
58
|
@oauth.oauth_callback_url == nil).should be_true
|
52
59
|
end
|
53
60
|
|
54
|
-
describe "cookie parsing" do
|
61
|
+
describe "for cookie parsing" do
|
55
62
|
describe "get_user_info_from_cookies" do
|
56
63
|
it "should properly parse valid cookies" do
|
57
64
|
result = @oauth.get_user_info_from_cookies(@oauth_data["valid_cookies"])
|
@@ -113,113 +120,155 @@ class FacebookOAuthTests < Test::Unit::TestCase
|
|
113
120
|
|
114
121
|
# OAuth URLs
|
115
122
|
|
116
|
-
|
117
|
-
it "should generate a properly formatted OAuth code URL with the default values" do
|
118
|
-
url = @oauth.url_for_oauth_code
|
119
|
-
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{@callback_url}"
|
120
|
-
end
|
121
|
-
|
122
|
-
it "should generate a properly formatted OAuth code URL when a callback is given" do
|
123
|
-
callback = "foo.com"
|
124
|
-
url = @oauth.url_for_oauth_code(:callback => callback)
|
125
|
-
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}"
|
126
|
-
end
|
123
|
+
describe "for URL generation" do
|
127
124
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
125
|
+
describe "for OAuth codes" do
|
126
|
+
# url_for_oauth_code
|
127
|
+
it "should generate a properly formatted OAuth code URL with the default values" do
|
128
|
+
url = @oauth.url_for_oauth_code
|
129
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{@callback_url}"
|
130
|
+
end
|
133
131
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
132
|
+
it "should generate a properly formatted OAuth code URL when a callback is given" do
|
133
|
+
callback = "foo.com"
|
134
|
+
url = @oauth.url_for_oauth_code(:callback => callback)
|
135
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}"
|
136
|
+
end
|
139
137
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
end
|
138
|
+
it "should generate a properly formatted OAuth code URL when permissions are requested as a string" do
|
139
|
+
permissions = "publish_stream,read_stream"
|
140
|
+
url = @oauth.url_for_oauth_code(:permissions => permissions)
|
141
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{@callback_url}&scope=#{permissions}"
|
142
|
+
end
|
146
143
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
144
|
+
it "should generate a properly formatted OAuth code URL when permissions are requested as a string" do
|
145
|
+
permissions = ["publish_stream", "read_stream"]
|
146
|
+
url = @oauth.url_for_oauth_code(:permissions => permissions)
|
147
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{@callback_url}&scope=#{permissions.join(",")}"
|
148
|
+
end
|
151
149
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
150
|
+
it "should generate a properly formatted OAuth code URL when both permissions and callback are provided" do
|
151
|
+
permissions = "publish_stream,read_stream"
|
152
|
+
callback = "foo.com"
|
153
|
+
url = @oauth.url_for_oauth_code(:callback => callback, :permissions => permissions)
|
154
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}&scope=#{permissions}"
|
155
|
+
end
|
157
156
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
describe "get_access_token_info" do
|
165
|
-
it "should properly get and parse an access token token results into a hash" do
|
166
|
-
result = @oauth.get_access_token_info(@code)
|
167
|
-
result.should be_a(Hash)
|
157
|
+
it "should raise an exception if no callback is given in initialization or the call" do
|
158
|
+
oauth2 = Koala::Facebook::OAuth.new(@app_id, @secret)
|
159
|
+
lambda { oauth2.url_for_oauth_code }.should raise_error(ArgumentError)
|
160
|
+
end
|
168
161
|
end
|
162
|
+
|
163
|
+
describe "for access token URLs" do
|
169
164
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
165
|
+
# url_for_access_token
|
166
|
+
it "should generate a properly formatted OAuth token URL when provided a code" do
|
167
|
+
url = @oauth.url_for_access_token(@code)
|
168
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{@callback_url}&client_secret=#{@secret}&code=#{@code}"
|
169
|
+
end
|
174
170
|
|
175
|
-
|
176
|
-
|
171
|
+
it "should generate a properly formatted OAuth token URL when provided a callback" do
|
172
|
+
callback = "foo.com"
|
173
|
+
url = @oauth.url_for_access_token(@code, :callback => callback)
|
174
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@secret}&code=#{@code}"
|
175
|
+
end
|
177
176
|
end
|
178
177
|
end
|
178
|
+
|
179
|
+
describe "for fetching access tokens" do
|
180
|
+
describe "get_access_token_info" do
|
181
|
+
it "should properly get and parse an access token token results into a hash" do
|
182
|
+
result = @oauth.get_access_token_info(@code)
|
183
|
+
result.should be_a(Hash)
|
184
|
+
end
|
179
185
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
end
|
186
|
+
it "should properly include the access token results" do
|
187
|
+
result = @oauth.get_access_token_info(@code)
|
188
|
+
result["access_token"].should
|
189
|
+
end
|
185
190
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
result.should == original["access_token"]
|
190
|
-
end
|
191
|
-
|
192
|
-
it "should raise an error when get_access_token is called with a bad code" do
|
193
|
-
lambda { @oauth.get_access_token("foo") }.should raise_error(Koala::Facebook::APIError)
|
191
|
+
it "should raise an error when get_access_token is called with a bad code" do
|
192
|
+
lambda { @oauth.get_access_token_info("foo") }.should raise_error(Koala::Facebook::APIError)
|
193
|
+
end
|
194
194
|
end
|
195
|
-
end
|
196
195
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
196
|
+
describe "get_access_token" do
|
197
|
+
it "should use get_access_token_info to get and parse an access token token results" do
|
198
|
+
result = @oauth.get_access_token(@code)
|
199
|
+
result.should be_a(String)
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should return the access token as a string" do
|
203
|
+
result = @oauth.get_access_token(@code)
|
204
|
+
original = @oauth.get_access_token_info(@code)
|
205
|
+
result.should == original["access_token"]
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should raise an error when get_access_token is called with a bad code" do
|
209
|
+
lambda { @oauth.get_access_token("foo") }.should raise_error(Koala::Facebook::APIError)
|
210
|
+
end
|
201
211
|
end
|
212
|
+
|
213
|
+
describe "get_app_access_token_info" do
|
214
|
+
it "should properly get and parse an app's access token as a hash" do
|
215
|
+
result = @oauth.get_app_access_token_info
|
216
|
+
result.should be_a(Hash)
|
217
|
+
end
|
202
218
|
|
203
|
-
|
204
|
-
|
205
|
-
|
219
|
+
it "should include the access token" do
|
220
|
+
result = @oauth.get_app_access_token_info
|
221
|
+
result["access_token"].should
|
222
|
+
end
|
206
223
|
end
|
207
|
-
end
|
208
224
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
225
|
+
describe "get_app_acess_token" do
|
226
|
+
it "should use get_access_token_info to get and parse an access token token results" do
|
227
|
+
result = @oauth.get_app_access_token
|
228
|
+
result.should be_a(String)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should return the access token as a string" do
|
232
|
+
result = @oauth.get_app_access_token
|
233
|
+
original = @oauth.get_app_access_token_info
|
234
|
+
result.should == original["access_token"]
|
235
|
+
end
|
213
236
|
end
|
237
|
+
|
238
|
+
describe "protected methods" do
|
239
|
+
|
240
|
+
# protected methods
|
241
|
+
# since these are pretty fundamental and pretty testable, we want to test them
|
242
|
+
|
243
|
+
# parse_access_token
|
244
|
+
it "should properly parse access token results" do
|
245
|
+
result = @oauth.send(:parse_access_token, @raw_token_string)
|
246
|
+
has_both_parts = result["access_token"] && result["expires"]
|
247
|
+
has_both_parts.should
|
248
|
+
end
|
214
249
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
250
|
+
it "should properly parse offline access token results" do
|
251
|
+
result = @oauth.send(:parse_access_token, @raw_offline_access_token_string)
|
252
|
+
has_both_parts = result["access_token"] && !result["expires"]
|
253
|
+
has_both_parts.should
|
254
|
+
end
|
255
|
+
|
256
|
+
# fetch_token_string
|
257
|
+
# somewhat duplicative with the tests for get_access_token and get_app_access_token
|
258
|
+
# but no harm in thoroughness
|
259
|
+
it "should fetch a proper token string from Facebook when given a code" do
|
260
|
+
result = @oauth.send(:fetch_token_string, :code => @code, :redirect_uri => @callback_url)
|
261
|
+
result.should =~ /^access_token/
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should fetch a proper token string from Facebook when asked for the app token" do
|
265
|
+
result = @oauth.send(:fetch_token_string, {:type => 'client_cred'}, true)
|
266
|
+
result.should =~ /^access_token/
|
267
|
+
end
|
219
268
|
end
|
220
269
|
end
|
221
270
|
|
222
|
-
describe "exchanging session keys" do
|
271
|
+
describe "for exchanging session keys" do
|
223
272
|
describe "with get_token_info_from_session_keys" do
|
224
273
|
it "should get an array of session keys from Facebook when passed a single key" do
|
225
274
|
result = @oauth.get_tokens_from_session_keys([@oauth_data["session_key"]])
|
@@ -237,6 +286,18 @@ class FacebookOAuthTests < Test::Unit::TestCase
|
|
237
286
|
result = @oauth.get_token_info_from_session_keys(@oauth_data["multiple_session_keys"])
|
238
287
|
result[0].should be_a(Hash)
|
239
288
|
end
|
289
|
+
|
290
|
+
it "should properly handle invalid session keys" do
|
291
|
+
result = @oauth.get_token_info_from_session_keys(["foo", "bar"])
|
292
|
+
#it should return nil for each of the invalid ones
|
293
|
+
result.each {|r| r.should be_nil}
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should properly handle a mix of valid and invalid session keys" do
|
297
|
+
result = @oauth.get_token_info_from_session_keys(["foo"].concat(@oauth_data["multiple_session_keys"]))
|
298
|
+
# it should return nil for each of the invalid ones
|
299
|
+
result.each_with_index {|r, index| index > 0 ? r.should(be_a(Hash)) : r.should(be_nil)}
|
300
|
+
end
|
240
301
|
end
|
241
302
|
|
242
303
|
describe "with get_tokens_from_session_keys" do
|
@@ -251,6 +312,18 @@ class FacebookOAuthTests < Test::Unit::TestCase
|
|
251
312
|
result = @oauth.get_tokens_from_session_keys(args)
|
252
313
|
result.each {|r| r.should be_a(String) }
|
253
314
|
end
|
315
|
+
|
316
|
+
it "should properly handle invalid session keys" do
|
317
|
+
result = @oauth.get_tokens_from_session_keys(["foo", "bar"])
|
318
|
+
# it should return nil for each of the invalid ones
|
319
|
+
result.each {|r| r.should be_nil}
|
320
|
+
end
|
321
|
+
|
322
|
+
it "should properly handle a mix of valid and invalid session keys" do
|
323
|
+
result = @oauth.get_tokens_from_session_keys(["foo"].concat(@oauth_data["multiple_session_keys"]))
|
324
|
+
# it should return nil for each of the invalid ones
|
325
|
+
result.each_with_index {|r, index| index > 0 ? r.should(be_a(String)) : r.should(be_nil)}
|
326
|
+
end
|
254
327
|
end
|
255
328
|
|
256
329
|
describe "get_token_from_session_key" do
|
@@ -270,39 +343,98 @@ class FacebookOAuthTests < Test::Unit::TestCase
|
|
270
343
|
array = @oauth.get_tokens_from_session_keys([@oauth_data["session_key"]])
|
271
344
|
result.should == array[0]
|
272
345
|
end
|
346
|
+
|
347
|
+
it "should properly handle an invalid session key" do
|
348
|
+
result = @oauth.get_token_from_session_key("foo")
|
349
|
+
result.should be_nil
|
350
|
+
end
|
273
351
|
end
|
274
352
|
end
|
275
353
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
has_both_parts = result["access_token"] && result["expires"]
|
283
|
-
has_both_parts.should
|
284
|
-
end
|
285
|
-
|
286
|
-
it "should properly parse offline access token results" do
|
287
|
-
result = @oauth.send(:parse_access_token, @raw_offline_access_token_string)
|
288
|
-
has_both_parts = result["access_token"] && !result["expires"]
|
289
|
-
has_both_parts.should
|
290
|
-
end
|
291
|
-
|
292
|
-
# fetch_token_string
|
293
|
-
# somewhat duplicative with the tests for get_access_token and get_app_access_token
|
294
|
-
# but no harm in thoroughness
|
295
|
-
it "should fetch a proper token string from Facebook when given a code" do
|
296
|
-
result = @oauth.send(:fetch_token_string, :code => @code, :redirect_uri => @callback_url)
|
297
|
-
result.should =~ /^access_token/
|
298
|
-
end
|
354
|
+
describe "for parsing signed requests" do
|
355
|
+
before :each do
|
356
|
+
# you can test against live data by updating the YML, or you can use the default data
|
357
|
+
# which tests against Facebook's example on http://developers.facebook.com/docs/authentication/canvas
|
358
|
+
@oauth = Koala::Facebook::OAuth.new(@app_id, @request_secret || @app_secret)
|
359
|
+
end
|
299
360
|
|
300
|
-
|
301
|
-
|
302
|
-
|
361
|
+
it "should break the request into the encoded signature and the payload" do
|
362
|
+
@signed_request.should_receive(:split).with(".").and_return(["", ""])
|
363
|
+
@oauth.parse_signed_request(@signed_request)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should base64 URL decode the signed request" do
|
367
|
+
sig = ""
|
368
|
+
@signed_request.should_receive(:split).with(".").and_return([sig, "1"])
|
369
|
+
@oauth.should_receive(:base64_url_decode).with(sig).and_return("4")
|
370
|
+
@oauth.parse_signed_request(@signed_request)
|
371
|
+
end
|
372
|
+
|
373
|
+
it "should base64 URL decode the signed request" do
|
374
|
+
sig = @signed_request.split(".")[0]
|
375
|
+
@oauth.should_receive(:base64_url_decode).with(sig).and_return(nil)
|
376
|
+
@oauth.parse_signed_request(@signed_request)
|
377
|
+
end
|
378
|
+
|
379
|
+
it "should get the sha64 encoded payload using proper arguments from OpenSSL::HMAC" do
|
380
|
+
payload = ""
|
381
|
+
@signed_request.should_receive(:split).with(".").and_return(["1", payload])
|
382
|
+
OpenSSL::HMAC.should_receive(:digest).with("sha256", @request_secret, payload)
|
383
|
+
@oauth.parse_signed_request(@signed_request)
|
384
|
+
end
|
385
|
+
|
386
|
+
it "should compare the encoded payload with the signature" do
|
387
|
+
sig = "2"
|
388
|
+
@oauth.should_receive(:base64_url_decode).and_return(sig)
|
389
|
+
encoded_payload = "1"
|
390
|
+
OpenSSL::HMAC.should_receive(:digest).with(anything, anything, anything).and_return(encoded_payload)
|
391
|
+
encoded_payload.should_receive(:==).with(sig)
|
392
|
+
@oauth.parse_signed_request(@signed_request)
|
393
|
+
end
|
394
|
+
|
395
|
+
describe "if the encoded payload matches the signature" do
|
396
|
+
before :each do
|
397
|
+
# set it up so the sig will match the encoded payload
|
398
|
+
raw_sig = ""
|
399
|
+
@sig = "2"
|
400
|
+
@payload = "1"
|
401
|
+
@signed_request.should_receive(:split).and_return([raw_sig, @payload])
|
402
|
+
@oauth.should_receive(:base64_url_decode).with(raw_sig).and_return(@sig)
|
403
|
+
OpenSSL::HMAC.should_receive(:digest).with(anything, anything, anything).and_return(@sig.dup)
|
404
|
+
end
|
405
|
+
|
406
|
+
it "should base64_url_decode the payload" do
|
407
|
+
@oauth.should_receive(:base64_url_decode).with(@payload).ordered.and_return("{}")
|
408
|
+
@oauth.parse_signed_request(@signed_request)
|
409
|
+
end
|
410
|
+
|
411
|
+
it "should JSON decode the payload" do
|
412
|
+
result = "{}"
|
413
|
+
@oauth.should_receive(:base64_url_decode).with(@payload).and_return(result)
|
414
|
+
JSON.should_receive(:parse).with(result)
|
415
|
+
@oauth.parse_signed_request(@signed_request)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
describe "if the encoded payload does not match the signature" do
|
420
|
+
before :each do
|
421
|
+
sig = ""
|
422
|
+
@signed_request.should_receive(:split).and_return([sig, ""])
|
423
|
+
OpenSSL::HMAC.should_receive(:digest).with(anything, anything, anything).and_return("hi")
|
424
|
+
end
|
425
|
+
|
426
|
+
it "should return nil" do
|
427
|
+
@oauth.parse_signed_request(@signed_request).should be_nil
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
describe "run against data" do
|
432
|
+
it "should work" do
|
433
|
+
@oauth.parse_signed_request(@signed_request).should == @signed_request_result
|
434
|
+
end
|
435
|
+
end
|
303
436
|
end
|
304
|
-
|
305
|
-
# END CODE THAT NEEDS MOCKING
|
437
|
+
|
306
438
|
end # describe
|
307
439
|
|
308
440
|
end #class
|