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.
Files changed (34) hide show
  1. data/.gitignore +1 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +90 -0
  4. data/Rakefile +27 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/simple_facebook_connect/connect_controller.rb +59 -0
  7. data/app/views/shared/_facebook_connect_button.html.erb +15 -0
  8. data/config/simple_facebook_connect_routes.rb +4 -0
  9. data/generators/simple_facebook_connect_features/simple_facebook_connect_features_generator.rb +14 -0
  10. data/generators/simple_facebook_connect_features/templates/facebook_auth_getsession.xml +9 -0
  11. data/generators/simple_facebook_connect_features/templates/facebook_connect.feature +27 -0
  12. data/generators/simple_facebook_connect_features/templates/facebook_connect_steps.rb +19 -0
  13. data/generators/simple_facebook_connect_features/templates/facebook_connect_stubs.rb +44 -0
  14. data/generators/simple_facebook_connect_features/templates/facebook_users_getinfo.xml +119 -0
  15. data/generators/simple_facebook_connect_migration/simple_facebook_connect_migration_generator.rb +13 -0
  16. data/generators/simple_facebook_connect_migration/templates/migration.rb +17 -0
  17. data/init.rb +35 -0
  18. data/lib/simple_facebook_connect.rb +14 -0
  19. data/lib/simple_facebook_connect/controller_extension.rb +11 -0
  20. data/lib/simple_facebook_connect/extensions/routes.rb +13 -0
  21. data/lib/simple_facebook_connect/parser.rb +161 -0
  22. data/lib/simple_facebook_connect/service.rb +34 -0
  23. data/lib/simple_facebook_connect/session.rb +73 -0
  24. data/lib/simple_facebook_connect/user.rb +26 -0
  25. data/lib/simple_facebook_connect/user_extension.rb +21 -0
  26. data/rails/init.rb +1 -0
  27. data/simple_facebook_connect.gemspec +71 -0
  28. data/spec/fixtures/facebook.auth.getSession/default.xml +9 -0
  29. data/spec/fixtures/facebook.users.getInfo/default.xml +119 -0
  30. data/spec/lib/simple_facebook_connect/parser_spec.rb +12 -0
  31. data/spec/lib/simple_facebook_connect/user_extension_spec.rb +16 -0
  32. data/spec/lib/simple_facebook_connect/user_spec.rb +30 -0
  33. data/spec/spec_helper.rb +31 -0
  34. metadata +89 -0
@@ -0,0 +1,13 @@
1
+ class SimpleFacebookConnectMigrationGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ record do |r|
5
+ r.migration_template 'migration.rb', "db/migrate"
6
+ end
7
+ end
8
+
9
+ def file_name
10
+ "add_facebook_connect"
11
+ end
12
+
13
+ end
@@ -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