upstream-simple_facebook_connect 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/MIT-LICENSE +20 -0
- data/README.md +90 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/app/controllers/simple_facebook_connect/connect_controller.rb +59 -0
- data/app/views/shared/_facebook_connect_button.html.erb +15 -0
- data/config/simple_facebook_connect_routes.rb +4 -0
- data/generators/simple_facebook_connect_features/simple_facebook_connect_features_generator.rb +14 -0
- data/generators/simple_facebook_connect_features/templates/facebook_auth_getsession.xml +9 -0
- data/generators/simple_facebook_connect_features/templates/facebook_connect.feature +27 -0
- data/generators/simple_facebook_connect_features/templates/facebook_connect_steps.rb +19 -0
- data/generators/simple_facebook_connect_features/templates/facebook_connect_stubs.rb +44 -0
- data/generators/simple_facebook_connect_features/templates/facebook_users_getinfo.xml +119 -0
- data/generators/simple_facebook_connect_migration/simple_facebook_connect_migration_generator.rb +13 -0
- data/generators/simple_facebook_connect_migration/templates/migration.rb +17 -0
- data/init.rb +35 -0
- data/lib/simple_facebook_connect.rb +14 -0
- data/lib/simple_facebook_connect/controller_extension.rb +11 -0
- data/lib/simple_facebook_connect/extensions/routes.rb +13 -0
- data/lib/simple_facebook_connect/parser.rb +161 -0
- data/lib/simple_facebook_connect/service.rb +34 -0
- data/lib/simple_facebook_connect/session.rb +73 -0
- data/lib/simple_facebook_connect/user.rb +26 -0
- data/lib/simple_facebook_connect/user_extension.rb +21 -0
- data/rails/init.rb +1 -0
- data/simple_facebook_connect.gemspec +71 -0
- data/spec/fixtures/facebook.auth.getSession/default.xml +9 -0
- data/spec/fixtures/facebook.users.getInfo/default.xml +119 -0
- data/spec/lib/simple_facebook_connect/parser_spec.rb +12 -0
- data/spec/lib/simple_facebook_connect/user_extension_spec.rb +16 -0
- data/spec/lib/simple_facebook_connect/user_spec.rb +30 -0
- data/spec/spec_helper.rb +31 -0
- metadata +89 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
class AddFacebookConnect < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :users, :fb_uid, :integer
|
4
|
+
add_column :users, :email_hash, :string
|
5
|
+
|
6
|
+
add_index :users, :fb_uid
|
7
|
+
add_index :users, :email_hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.down
|
11
|
+
remove_index :users, :fb_uid
|
12
|
+
remove_index :users, :email_hash
|
13
|
+
|
14
|
+
remove_column :users, :email_hash
|
15
|
+
remove_column :users, :fb_uid
|
16
|
+
end
|
17
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/simple_facebook_connect'
|
2
|
+
|
3
|
+
config_file = Rails.root + 'config/simple_facebook_connect.yml'
|
4
|
+
|
5
|
+
unless File.exist?(config_file)
|
6
|
+
File.open(config_file, 'w') do |f|
|
7
|
+
f << <<-CONF
|
8
|
+
test:
|
9
|
+
api_key:
|
10
|
+
secret_key:
|
11
|
+
|
12
|
+
development:
|
13
|
+
api_key:
|
14
|
+
secret_key:
|
15
|
+
|
16
|
+
production:
|
17
|
+
api_key:
|
18
|
+
secret_key:
|
19
|
+
|
20
|
+
CONF
|
21
|
+
end
|
22
|
+
|
23
|
+
puts "Created the facebook simple connect configuration in #{config_file}. Please enter your facebook API and secret keys there."
|
24
|
+
exit(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
config = YAML::load(File.read(config_file))[Rails.env]
|
28
|
+
|
29
|
+
SimpleFacebookConnect.api_key = config['api_key']
|
30
|
+
SimpleFacebookConnect.secret_key = config['secret_key']
|
31
|
+
|
32
|
+
ApplicationController.send(:include, SimpleFacebookConnect::ControllerExtension)
|
33
|
+
User.send(:include, SimpleFacebookConnect::UserExtension)
|
34
|
+
|
35
|
+
require 'simple_facebook_connect/extensions/routes'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'simple_facebook_connect/parser'
|
4
|
+
require 'simple_facebook_connect/service'
|
5
|
+
require 'simple_facebook_connect/session'
|
6
|
+
require 'simple_facebook_connect/user'
|
7
|
+
require 'simple_facebook_connect/user_extension'
|
8
|
+
require 'simple_facebook_connect/controller_extension'
|
9
|
+
|
10
|
+
module SimpleFacebookConnect
|
11
|
+
class << self
|
12
|
+
attr_accessor :api_key, :secret_key
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module SimpleFacebookConnect
|
2
|
+
module ControllerExtension
|
3
|
+
def facebook_session
|
4
|
+
session[:facebook_session]
|
5
|
+
end
|
6
|
+
|
7
|
+
def facebook_user
|
8
|
+
(session[:facebook_session] && session[:facebook_session].session_key) ? session[:facebook_session].user : nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# without this hack people can't override our routes when subclassing controllers in the app. trick stolen from clearance.
|
2
|
+
class ActionController::Routing::RouteSet
|
3
|
+
def load_routes_with_simple_facebook_connect!
|
4
|
+
lib_path = File.dirname(__FILE__)
|
5
|
+
routes = File.join(lib_path, *%w[.. .. .. config simple_facebook_connect_routes.rb])
|
6
|
+
unless configuration_files.include?(routes)
|
7
|
+
add_configuration_file(routes)
|
8
|
+
end
|
9
|
+
load_routes_without_simple_facebook_connect!
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method_chain :load_routes!, :simple_facebook_connect
|
13
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module SimpleFacebookConnect
|
4
|
+
|
5
|
+
class Parser
|
6
|
+
|
7
|
+
module REXMLElementExtensions
|
8
|
+
def text_value
|
9
|
+
self.children.first.to_s.strip
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
::REXML::Element.__send__(:include, REXMLElementExtensions)
|
14
|
+
|
15
|
+
def self.parse(method, data)
|
16
|
+
Errors.process(data)
|
17
|
+
parser = PARSERS[method]
|
18
|
+
parser.process(
|
19
|
+
data
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.array_of(response_element, element_name)
|
24
|
+
values_to_return = []
|
25
|
+
response_element.elements.each(element_name) do |element|
|
26
|
+
values_to_return << yield(element)
|
27
|
+
end
|
28
|
+
values_to_return
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.array_of_text_values(response_element, element_name)
|
32
|
+
array_of(response_element, element_name) do |element|
|
33
|
+
element.text_value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.array_of_hashes(response_element, element_name)
|
38
|
+
array_of(response_element, element_name) do |element|
|
39
|
+
hashinate(element)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.element(name, data)
|
44
|
+
data = data.body rescue data # either data or an HTTP response
|
45
|
+
doc = REXML::Document.new(data)
|
46
|
+
doc.elements.each(name) do |element|
|
47
|
+
return element
|
48
|
+
end
|
49
|
+
raise "Element #{name} not found in #{data}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.hash_or_value_for(element)
|
53
|
+
if element.children.size == 1 && element.children.first.kind_of?(REXML::Text)
|
54
|
+
element.text_value
|
55
|
+
else
|
56
|
+
hashinate(element)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.hashinate(response_element)
|
61
|
+
response_element.children.reject{|c| c.kind_of? REXML::Text}.inject({}) do |hash, child|
|
62
|
+
# If the node hasn't any child, and is not a list, we want empty strings, not empty hashes,
|
63
|
+
# except if attributes['nil'] == true
|
64
|
+
hash[child.name] =
|
65
|
+
if (child.attributes['nil'] == 'true')
|
66
|
+
nil
|
67
|
+
elsif (child.children.size == 1 && child.children.first.kind_of?(REXML::Text)) || (child.children.size == 0 && child.attributes['list'] != 'true')
|
68
|
+
anonymous_field_from(child, hash) || child.text_value
|
69
|
+
elsif child.attributes['list'] == 'true'
|
70
|
+
child.children.reject{|c| c.kind_of? REXML::Text}.map { |subchild| hash_or_value_for(subchild)}
|
71
|
+
else
|
72
|
+
child.children.reject{|c| c.kind_of? REXML::Text}.inject({}) do |subhash, subchild|
|
73
|
+
subhash[subchild.name] = hash_or_value_for(subchild)
|
74
|
+
subhash
|
75
|
+
end
|
76
|
+
end #if (child.attributes)
|
77
|
+
hash
|
78
|
+
end #do |hash, child|
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.booleanize(response)
|
82
|
+
response == "1" ? true : false
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.anonymous_field_from(child, hash)
|
86
|
+
if child.name == 'anon'
|
87
|
+
(hash[child.name] || []) << child.text_value
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class RevokeAuthorization < self
|
92
|
+
def self.process(data)
|
93
|
+
booleanize(data)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class CreateToken < self
|
98
|
+
def self.process(data)
|
99
|
+
element('auth_createToken_response', data).text_value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class RegisterUsers < self
|
104
|
+
def self.process(data)
|
105
|
+
array_of_text_values(element("connect_registerUsers_response", data), "connect_registerUsers_response_elt")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class GetSession < self
|
110
|
+
def self.process(data)
|
111
|
+
hashinate(element('auth_getSession_response', data))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class UserInfo < self
|
116
|
+
def self.process(data)
|
117
|
+
array_of_hashes(element('users_getInfo_response', data), 'user')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class UserStandardInfo < self
|
122
|
+
def self.process(data)
|
123
|
+
array_of_hashes(element('users_getStandardInfo_response', data), 'standard_user_info')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class GetLoggedInUser < self
|
128
|
+
def self.process(data)
|
129
|
+
Integer(element('users_getLoggedInUser_response', data).text_value)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class ProfileInfo < self
|
134
|
+
def self.process(data)
|
135
|
+
hashinate(element('profile_getInfo_response info_fields', data))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Errors < self
|
140
|
+
def self.process(data)
|
141
|
+
response_element = element('error_response', data) rescue nil
|
142
|
+
if response_element
|
143
|
+
hash = hashinate(response_element)
|
144
|
+
raise StandardError, hash['error_msg']
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
PARSERS = {
|
151
|
+
'facebook.auth.revokeAuthorization' => RevokeAuthorization,
|
152
|
+
'facebook.auth.createToken' => CreateToken,
|
153
|
+
'facebook.auth.getSession' => GetSession,
|
154
|
+
'facebook.connect.registerUsers' => RegisterUsers,
|
155
|
+
'facebook.users.getInfo' => UserInfo,
|
156
|
+
'facebook.users.getStandardInfo' => UserStandardInfo,
|
157
|
+
'facebook.profile.getInfo' => ProfileInfo
|
158
|
+
}
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module SimpleFacebookConnect
|
4
|
+
|
5
|
+
class Service
|
6
|
+
def initialize(api_base, api_path, api_key)
|
7
|
+
@api_base = api_base
|
8
|
+
@api_path = api_path
|
9
|
+
@api_key = api_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def post(params)
|
13
|
+
attempt = 0
|
14
|
+
Parser.parse(params[:method], post_form(url,params) )
|
15
|
+
rescue Errno::ECONNRESET, EOFError
|
16
|
+
if attempt == 0
|
17
|
+
attempt += 1
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def post_form(url,params)
|
23
|
+
Net::HTTP.post_form(url, params.stringify_keys)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def url(base = nil)
|
28
|
+
base ||= @api_base
|
29
|
+
URI.parse('http://'+ base + @api_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module SimpleFacebookConnect
|
2
|
+
|
3
|
+
class Session
|
4
|
+
API_SERVER_BASE_URL = "api.facebook.com"
|
5
|
+
API_PATH_REST = "/restserver.php"
|
6
|
+
WWW_SERVER_BASE_URL = "www.facebook.com"
|
7
|
+
WWW_PATH_LOGIN = "/login.php"
|
8
|
+
|
9
|
+
attr_accessor :auth_token
|
10
|
+
attr_reader :session_key
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(api_key, secret_key)
|
14
|
+
@api_key = api_key
|
15
|
+
@secret_key = secret_key
|
16
|
+
@session_key = nil
|
17
|
+
@uid = nil
|
18
|
+
@auth_token = nil
|
19
|
+
@secret_from_session = nil
|
20
|
+
@expires = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def login_url(options={})
|
24
|
+
"http://#{WWW_SERVER_BASE_URL}#{WWW_PATH_LOGIN}?api_key=#{@api_key}&v=1.0"
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def secure!
|
29
|
+
response = post 'facebook.auth.getSession', :auth_token => auth_token
|
30
|
+
@session_key = response['session_key']
|
31
|
+
@uid = response['uid'].to_i
|
32
|
+
@expires = response['expires'].to_i
|
33
|
+
@secret_from_session = response['secret']
|
34
|
+
end
|
35
|
+
|
36
|
+
def user
|
37
|
+
@user ||= User.new(uid, self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def post(method, params = {}, use_session_key = true, &proc)
|
41
|
+
add_facebook_params(params, method)
|
42
|
+
use_session_key && @session_key && params[:session_key] ||= @session_key
|
43
|
+
final_params = params.merge(:sig => signature_for(params))
|
44
|
+
result = service.post(final_params)
|
45
|
+
result = yield result if block_given?
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def add_facebook_params(hash, method)
|
51
|
+
hash[:method] = method
|
52
|
+
hash[:api_key] = @api_key
|
53
|
+
hash[:call_id] = Time.now.to_f.to_s unless method == 'facebook.auth.getSession'
|
54
|
+
hash[:v] = "1.0"
|
55
|
+
end
|
56
|
+
|
57
|
+
def service
|
58
|
+
@service ||= Service.new(API_SERVER_BASE_URL, API_PATH_REST, @api_key)
|
59
|
+
end
|
60
|
+
|
61
|
+
def uid
|
62
|
+
@uid || (secure!; @uid)
|
63
|
+
end
|
64
|
+
|
65
|
+
def signature_for(params)
|
66
|
+
raw_string = params.inject([]) do |collection, pair|
|
67
|
+
collection << pair.join("=")
|
68
|
+
collection
|
69
|
+
end.sort.join
|
70
|
+
Digest::MD5.hexdigest([raw_string, @secret_key].join)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SimpleFacebookConnect
|
2
|
+
|
3
|
+
class User
|
4
|
+
|
5
|
+
FIELDS = [:uid, :hometown_location, :first_name, :last_name, :current_location, :pic, :locale, :email_hashes]
|
6
|
+
attr_reader(*FIELDS)
|
7
|
+
|
8
|
+
def initialize(uid, session)
|
9
|
+
@uid = uid
|
10
|
+
populate(session)
|
11
|
+
end
|
12
|
+
|
13
|
+
def populate(session)
|
14
|
+
session.post('facebook.users.getInfo', :fields => coma_seperated_fields, :uids => uid) do |response|
|
15
|
+
FIELDS.each do |field|
|
16
|
+
instance_variable_set(:"@#{field}", response.first[field.to_s])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def coma_seperated_fields
|
22
|
+
FIELDS.join(',')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module SimpleFacebookConnect
|
5
|
+
module UserExtension
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
before_save :build_email_hash, :if => :email_changed?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def build_email_hash
|
16
|
+
str = email.strip.downcase
|
17
|
+
self.email_hash = "#{Zlib.crc32(str)}_#{Digest::MD5.hexdigest(str)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../init'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{simple_facebook_connect}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Alexander Lang", "Frank Pr\303\266\303\237dorf"]
|
9
|
+
s.date = %q{2009-07-15}
|
10
|
+
s.email = %q{alex@upstream-berlin.com}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"README.md"
|
13
|
+
]
|
14
|
+
s.files = [
|
15
|
+
".gitignore",
|
16
|
+
"MIT-LICENSE",
|
17
|
+
"README.md",
|
18
|
+
"Rakefile",
|
19
|
+
"VERSION",
|
20
|
+
"app/controllers/simple_facebook_connect/connect_controller.rb",
|
21
|
+
"app/views/shared/_facebook_connect_button.html.erb",
|
22
|
+
"config/simple_facebook_connect_routes.rb",
|
23
|
+
"generators/simple_facebook_connect_features/simple_facebook_connect_features_generator.rb",
|
24
|
+
"generators/simple_facebook_connect_features/templates/facebook_auth_getsession.xml",
|
25
|
+
"generators/simple_facebook_connect_features/templates/facebook_connect.feature",
|
26
|
+
"generators/simple_facebook_connect_features/templates/facebook_connect_steps.rb",
|
27
|
+
"generators/simple_facebook_connect_features/templates/facebook_connect_stubs.rb",
|
28
|
+
"generators/simple_facebook_connect_features/templates/facebook_users_getinfo.xml",
|
29
|
+
"generators/simple_facebook_connect_migration/simple_facebook_connect_migration_generator.rb",
|
30
|
+
"generators/simple_facebook_connect_migration/templates/migration.rb",
|
31
|
+
"init.rb",
|
32
|
+
"lib/simple_facebook_connect.rb",
|
33
|
+
"lib/simple_facebook_connect/controller_extension.rb",
|
34
|
+
"lib/simple_facebook_connect/extensions/routes.rb",
|
35
|
+
"lib/simple_facebook_connect/parser.rb",
|
36
|
+
"lib/simple_facebook_connect/service.rb",
|
37
|
+
"lib/simple_facebook_connect/session.rb",
|
38
|
+
"lib/simple_facebook_connect/user.rb",
|
39
|
+
"lib/simple_facebook_connect/user_extension.rb",
|
40
|
+
"rails/init.rb",
|
41
|
+
"simple_facebook_connect.gemspec",
|
42
|
+
"spec/fixtures/facebook.auth.getSession/default.xml",
|
43
|
+
"spec/fixtures/facebook.users.getInfo/default.xml",
|
44
|
+
"spec/lib/simple_facebook_connect/parser_spec.rb",
|
45
|
+
"spec/lib/simple_facebook_connect/user_extension_spec.rb",
|
46
|
+
"spec/lib/simple_facebook_connect/user_spec.rb",
|
47
|
+
"spec/spec_helper.rb"
|
48
|
+
]
|
49
|
+
s.has_rdoc = true
|
50
|
+
s.homepage = %q{http://github.com/upstream/simple_facebook_connect}
|
51
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubygems_version = %q{1.3.1}
|
54
|
+
s.summary = %q{This plugin adds the ability to sign in/sign up using facebook connect to your Rails application.}
|
55
|
+
s.test_files = [
|
56
|
+
"spec/lib/simple_facebook_connect/parser_spec.rb",
|
57
|
+
"spec/lib/simple_facebook_connect/user_extension_spec.rb",
|
58
|
+
"spec/lib/simple_facebook_connect/user_spec.rb",
|
59
|
+
"spec/spec_helper.rb"
|
60
|
+
]
|
61
|
+
|
62
|
+
if s.respond_to? :specification_version then
|
63
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
64
|
+
s.specification_version = 2
|
65
|
+
|
66
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
67
|
+
else
|
68
|
+
end
|
69
|
+
else
|
70
|
+
end
|
71
|
+
end
|