social_auth 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +39 -0
  4. data/app/models/social_auth/facebook_service.rb +60 -0
  5. data/app/models/social_auth/google_plus_service.rb +106 -0
  6. data/app/models/social_auth/service.rb +152 -0
  7. data/app/models/social_auth/twitter_service.rb +67 -0
  8. data/config/routes.rb +2 -0
  9. data/lib/generators/social_auth/install/install_generator.rb +37 -0
  10. data/lib/generators/social_auth/install/templates/create_social_auth_services.rb +12 -0
  11. data/lib/generators/social_auth/install/templates/initializer.rb +15 -0
  12. data/lib/social_auth.rb +73 -0
  13. data/lib/social_auth/acts_as_social_user.rb +23 -0
  14. data/lib/social_auth/engine.rb +12 -0
  15. data/lib/social_auth/railtie.rb +7 -0
  16. data/lib/social_auth/version.rb +3 -0
  17. data/lib/tasks/social_auth_tasks.rake +4 -0
  18. data/spec/dummy/rails-4.2.0/Gemfile +42 -0
  19. data/spec/dummy/rails-4.2.0/README.rdoc +28 -0
  20. data/spec/dummy/rails-4.2.0/Rakefile +6 -0
  21. data/spec/dummy/rails-4.2.0/app/assets/javascripts/application.js +16 -0
  22. data/spec/dummy/rails-4.2.0/app/assets/stylesheets/application.css +15 -0
  23. data/spec/dummy/rails-4.2.0/app/controllers/application_controller.rb +5 -0
  24. data/spec/dummy/rails-4.2.0/app/helpers/application_helper.rb +2 -0
  25. data/spec/dummy/rails-4.2.0/app/models/user.rb +2 -0
  26. data/spec/dummy/rails-4.2.0/app/views/layouts/application.html.erb +14 -0
  27. data/spec/dummy/rails-4.2.0/bin/bundle +3 -0
  28. data/spec/dummy/rails-4.2.0/bin/rails +4 -0
  29. data/spec/dummy/rails-4.2.0/bin/rake +4 -0
  30. data/spec/dummy/rails-4.2.0/bin/setup +29 -0
  31. data/spec/dummy/rails-4.2.0/config.ru +4 -0
  32. data/spec/dummy/rails-4.2.0/config/application.rb +26 -0
  33. data/spec/dummy/rails-4.2.0/config/boot.rb +3 -0
  34. data/spec/dummy/rails-4.2.0/config/database.yml +15 -0
  35. data/spec/dummy/rails-4.2.0/config/environment.rb +5 -0
  36. data/spec/dummy/rails-4.2.0/config/environments/development.rb +41 -0
  37. data/spec/dummy/rails-4.2.0/config/environments/production.rb +79 -0
  38. data/spec/dummy/rails-4.2.0/config/environments/test.rb +42 -0
  39. data/spec/dummy/rails-4.2.0/config/initializers/assets.rb +11 -0
  40. data/spec/dummy/rails-4.2.0/config/initializers/backtrace_silencers.rb +7 -0
  41. data/spec/dummy/rails-4.2.0/config/initializers/cookies_serializer.rb +3 -0
  42. data/spec/dummy/rails-4.2.0/config/initializers/filter_parameter_logging.rb +4 -0
  43. data/spec/dummy/rails-4.2.0/config/initializers/inflections.rb +16 -0
  44. data/spec/dummy/rails-4.2.0/config/initializers/mime_types.rb +4 -0
  45. data/spec/dummy/rails-4.2.0/config/initializers/session_store.rb +3 -0
  46. data/spec/dummy/rails-4.2.0/config/initializers/social_auth.rb +15 -0
  47. data/spec/dummy/rails-4.2.0/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/rails-4.2.0/config/locales/en.yml +23 -0
  49. data/spec/dummy/rails-4.2.0/config/routes.rb +56 -0
  50. data/spec/dummy/rails-4.2.0/config/secrets.yml +22 -0
  51. data/spec/dummy/rails-4.2.0/db/migrate/20150504044515878_create_social_auth_services.rb +12 -0
  52. data/spec/dummy/rails-4.2.0/db/migrate/20150504044519_create_users.rb +9 -0
  53. data/spec/dummy/rails-4.2.0/db/schema.rb +35 -0
  54. data/spec/dummy/rails-4.2.0/db/seeds.rb +7 -0
  55. data/spec/dummy/rails-4.2.0/log/test.log +2570 -0
  56. data/spec/dummy/rails-4.2.0/public/404.html +67 -0
  57. data/spec/dummy/rails-4.2.0/public/422.html +67 -0
  58. data/spec/dummy/rails-4.2.0/public/500.html +66 -0
  59. data/spec/dummy/rails-4.2.0/public/favicon.ico +0 -0
  60. data/spec/dummy/rails-4.2.0/public/robots.txt +5 -0
  61. data/spec/dummy/rails-4.2.0/test/fixtures/users.yml +7 -0
  62. data/spec/dummy/rails-4.2.0/test/models/user_test.rb +7 -0
  63. data/spec/dummy/rails-4.2.0/test/test_helper.rb +10 -0
  64. data/spec/fixtures/vcr_cassettes/facebook_service/invalid_friends_request.yml +54 -0
  65. data/spec/fixtures/vcr_cassettes/facebook_service/invalid_request.yml +50 -0
  66. data/spec/fixtures/vcr_cassettes/facebook_service/invalid_token.yml +50 -0
  67. data/spec/fixtures/vcr_cassettes/facebook_service/valid_friends_request.yml +110 -0
  68. data/spec/fixtures/vcr_cassettes/facebook_service/valid_request.yml +55 -0
  69. data/spec/fixtures/vcr_cassettes/google_plus_service/invalid_authorization.yml +51 -0
  70. data/spec/fixtures/vcr_cassettes/google_plus_service/invalid_friends_request.yml +53 -0
  71. data/spec/fixtures/vcr_cassettes/google_plus_service/invalid_token.yml +51 -0
  72. data/spec/fixtures/vcr_cassettes/google_plus_service/valid_authorization.yml +56 -0
  73. data/spec/fixtures/vcr_cassettes/google_plus_service/valid_friends_request.yml +132 -0
  74. data/spec/fixtures/vcr_cassettes/google_plus_service/valid_request.yml +110 -0
  75. data/spec/fixtures/vcr_cassettes/twitter_service/invalid_friends_request.yml +47 -0
  76. data/spec/fixtures/vcr_cassettes/twitter_service/valid_friends_request.yml +159 -0
  77. data/spec/fixtures/vcr_cassettes/twitter_service/valid_request.yml +337 -0
  78. data/spec/models/facebook_service_spec.rb +127 -0
  79. data/spec/models/google_plus_service_spec.rb +133 -0
  80. data/spec/models/service_spec.rb +236 -0
  81. data/spec/models/twitter_service_spec.rb +100 -0
  82. data/spec/spec_helper.rb +74 -0
  83. data/spec/support/database.yml +15 -0
  84. data/spec/support/rails_template.rb +15 -0
  85. metadata +376 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7b73fa75b8a6c8200dfa7dd06516c356ed7bc6fa
4
+ data.tar.gz: 3df34c0d057b106e60658529905ad5b5d6a3258f
5
+ SHA512:
6
+ metadata.gz: 1806f1a18ece05d1522f280a6746b675384708be1933bc87ac940c20f2eefb129af1ae11bb4c8f98272cfb5afbd02cb3fe8bbe4ea20aa4b39fe4e1fc024cd765
7
+ data.tar.gz: 6348918e0a59e0aced64adbb948fc72ca3d86f8658e38a5fa28aa932aee3a1eedd3a1dda83b621c34958e9ef67bb77c2ee000ef21eff00ebc57b4c4572d756b9
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 William Porter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ require "bundler"
2
+ require 'rake'
3
+ Bundler.setup
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ def cmd(command)
7
+ puts command
8
+ raise unless system command
9
+ end
10
+
11
+ # Import all our rake tasks
12
+ FileList['tasks/**/*.rake'].each { |task| import task }
13
+
14
+ # begin
15
+ # require 'bundler/setup'
16
+ # rescue LoadError
17
+ # puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
18
+ # end
19
+
20
+ # require 'rdoc/task'
21
+
22
+ # RDoc::Task.new(:rdoc) do |rdoc|
23
+ # rdoc.rdoc_dir = 'rdoc'
24
+ # rdoc.title = 'SocialAuth'
25
+ # rdoc.options << '--line-numbers'
26
+ # rdoc.rdoc_files.include('README.rdoc')
27
+ # rdoc.rdoc_files.include('lib/**/*.rb')
28
+ # end
29
+
30
+ # APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
31
+ # load 'rails/tasks/engine.rake'
32
+
33
+
34
+ # load 'rails/tasks/statistics.rake'
35
+
36
+
37
+
38
+ # Bundler::GemHelper.install_tasks
39
+
@@ -0,0 +1,60 @@
1
+ require 'fb_graph2'
2
+
3
+ module SocialAuth
4
+ class FacebookService < Service
5
+ def name
6
+ "Facebook"
7
+ end
8
+
9
+ def self.init_with(auth_token={})
10
+ request = create_connection(auth_token)
11
+
12
+ return create_with_request(
13
+ request.id,
14
+ User.create_with_facebook_request(request),
15
+ "Authenticated",
16
+ {access_token: request.access_token}
17
+ )
18
+ end
19
+
20
+ def self.connect_with(user, auth_token={}, method="Connected")
21
+ request = create_connection(auth_token)
22
+
23
+ return create_with_request(
24
+ request.id,
25
+ user,
26
+ method,
27
+ {access_token: request.access_token}
28
+ )
29
+ end
30
+
31
+ def self.create_connection(auth_token={})
32
+ fb_user = FbGraph2::User.me(auth_token[:access_token])
33
+ fb_user.fetch
34
+
35
+ rescue FbGraph2::Exception::InvalidToken => e
36
+ raise InvalidToken.new(e.message)
37
+ rescue FbGraph2::Exception::BadRequest => e
38
+ raise BadRequest.new(e.message)
39
+ end
40
+
41
+ def friend_ids
42
+ if redis_instance.exists(redis_key(:friends))
43
+ friend_ids = redis_instance.smembers(redis_key(:friends))
44
+ else
45
+ friend_ids = self.class.create_connection(access_token).friends.map(&:id)
46
+ unless friend_ids.empty?
47
+ redis_instance.del(redis_key(:friends))
48
+ redis_instance.sadd(redis_key(:friends), friend_ids)
49
+ redis_instance.expire(redis_key(:friends), REDIS_CACHE)
50
+ end
51
+ end
52
+ friend_ids
53
+
54
+ rescue InvalidToken => e
55
+ disconnect(e)
56
+ return []
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,106 @@
1
+ require 'google_plus'
2
+ require 'typhoeus'
3
+
4
+ module SocialAuth
5
+ class GooglePlusService < Service
6
+
7
+ def name
8
+ "Google Plus"
9
+ end
10
+
11
+ def self.init_with(auth_token={})
12
+ access_token = fetch_access_token(auth_token)
13
+ request = create_connection(access_token).get('me')
14
+
15
+ return create_with_request(
16
+ request.id,
17
+ User.create_with_google_plus_request(request),
18
+ "Authenticated",
19
+ {refresh_token: access_token[:refresh_token]}
20
+ )
21
+ rescue GooglePlus::RequestError => e
22
+ raise InvalidToken.new(e.message)
23
+ end
24
+
25
+ def self.connect_with(user, auth_token={}, method="Connected")
26
+ access_token = fetch_access_token(auth_token)
27
+ request = create_connection(access_token).get('me')
28
+
29
+ return create_with_request(
30
+ request.id,
31
+ user,
32
+ method,
33
+ {refresh_token: access_token[:refresh_token]}
34
+ )
35
+ rescue GooglePlus::RequestError => e
36
+ raise InvalidToken.new(e.message)
37
+ end
38
+
39
+ def self.fetch_access_token(auth_token={})
40
+ params = {
41
+ client_id: SocialAuth.google_client_id,
42
+ client_secret: SocialAuth.google_client_secret,
43
+ redirect_uri: SocialAuth.google_redirect_uri
44
+ }
45
+ if auth_token[:auth_token].present?
46
+ params[:code] = auth_token[:auth_token]
47
+ params[:grant_type] = "authorization_code"
48
+ request = Typhoeus::Request.new(
49
+ "https://www.googleapis.com/oauth2/v3/token",
50
+ method: :post,
51
+ params: params
52
+ )
53
+ else
54
+ params[:refresh_token] = auth_token[:refresh_token]
55
+ params[:grant_type] = "refresh_token"
56
+ request = Typhoeus::Request.new(
57
+ "https://www.googleapis.com/oauth2/v3/token",
58
+ method: :post,
59
+ params: params
60
+ )
61
+ end
62
+
63
+ request.on_complete do |response|
64
+ body = JSON.parse(response.body).with_indifferent_access
65
+ if response.success?
66
+ return body
67
+ else
68
+ raise InvalidToken.new(body[:error_description])
69
+ end
70
+ end
71
+
72
+ request.run
73
+ end
74
+
75
+ def self.create_connection(auth_token={})
76
+ GooglePlus.api_key = SocialAuth.google_api_key
77
+ GooglePlus.access_token = auth_token[:access_token]
78
+ GooglePlus::Person
79
+ end
80
+
81
+ def google_items
82
+ self.class.create_connection(self.class.fetch_access_token(access_token)).list.items
83
+ end
84
+
85
+ def friend_ids
86
+ if redis_instance.exists(redis_key(:friends))
87
+ friend_ids = redis_instance.smembers(redis_key(:friends))
88
+ else
89
+ items = google_items
90
+ friend_ids = items.map(&:id) if items.present?
91
+ if friend_ids.present?
92
+ redis_instance.del(redis_key(:friends))
93
+ redis_instance.sadd(redis_key(:friends), friend_ids)
94
+ redis_instance.expire(redis_key(:friends), REDIS_CACHE)
95
+ else
96
+ return []
97
+ end
98
+ end
99
+ friend_ids
100
+
101
+ rescue InvalidToken => e
102
+ disconnect
103
+ return []
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,152 @@
1
+ module SocialAuth
2
+ class Service < ActiveRecord::Base
3
+ #validations
4
+ validates_presence_of :user, :access_token, :remote_id, :method
5
+ validates_uniqueness_of :remote_id, scope: [:type], :if => lambda { |service| service.method == 'Authenticated' }
6
+ validates_uniqueness_of :remote_id, scope: [:type, :user_id], :if => lambda { |service| service.method == 'Connected' }
7
+
8
+ before_validation :validate_methods
9
+
10
+ #relations
11
+ belongs_to :user
12
+
13
+ #settings
14
+ self.table_name = "social_auth_services"
15
+
16
+ #callbacks
17
+ after_create :append_to_associated_services
18
+
19
+ ACCEPTED_METHODS = %w(Authenticated Connected)
20
+ REDIS_CACHE = 2_592_000 # cache expiry in seconds
21
+
22
+ def name
23
+ raise "need to implement"
24
+ end
25
+
26
+ def access_token
27
+ if super.blank?
28
+ {}
29
+ else
30
+ super.with_indifferent_access
31
+ end
32
+ end
33
+
34
+ def self.init_with(auth_token)
35
+ raise "need to override"
36
+ end
37
+
38
+ def self.connect_with(user, auth_token)
39
+ raise "need to override"
40
+ end
41
+
42
+ def self.create_with_request(remote_id, user, method="Connected", access_token={})
43
+ remote_id = remote_id.to_s
44
+
45
+ unless (service = find_by_remote_id_and_method(remote_id, method)) && method == "Authenticated"
46
+ #attempts to look if some other user connected this same facebook account if its an authentication request
47
+ if count = where(remote_id: remote_id, method: "Connected").count == 1 and method == "Authenticated"
48
+ service = find_by_remote_id_and_method(remote_id, "Connected")
49
+ else
50
+ service = new
51
+ service.remote_id = remote_id
52
+
53
+ #gives the owner one last chance to perform some app level logic on the user before being created
54
+ user = user.validate_existing_user(remote_id, service.type) if user.respond_to?(:validate_existing_user)
55
+ service.user = user
56
+ service.method = method
57
+ end
58
+ end
59
+
60
+ service.access_token = access_token
61
+ service.save
62
+
63
+ return service
64
+ end
65
+
66
+ def services
67
+ self.class.where('remote_id IN (?)', friend_ids)
68
+ end
69
+
70
+ def self.append_to_associated_services(id)
71
+ service = find(id)
72
+ service.services.each do |s|
73
+ s.append_to_friends_list(service)
74
+ end
75
+ end
76
+
77
+ def append_to_friends_list(service)
78
+ if redis_instance.exists(self.redis_key(:friends))
79
+ redis_instance.sadd(self.redis_key(:friends), service.remote_id)
80
+ end
81
+ user.friend_joined_the_app_callback(service.user) if user.respond_to?(:friend_joined_the_app_callback)
82
+ end
83
+
84
+ def friend_ids
85
+ if redis_instance.exists(redis_key(:friends))
86
+ friend_ids = redis_instance.smembers(redis_key(:friends))
87
+ else
88
+ []
89
+ end
90
+ end
91
+
92
+ def self.disconnect_user(user)
93
+ service = find_by_user_id(user.id)
94
+ if service
95
+ raise Error.new("Cannot disconnect a service you used to authenticate with") if service.authenticated?
96
+
97
+ service.disconnect(nil, false)
98
+ else
99
+ raise ServiceDoesNotExist.new("Couldn't find service for this user")
100
+ end
101
+ end
102
+
103
+ def disconnect(e=nil, callback=true)
104
+ if connected?
105
+ #destroys service
106
+ self.destroy
107
+ #notifies the user that their service is about to be disconnected
108
+ user.service_disconnected_callback(self) if user.respond_to?(:service_disconnected_callback) and callback
109
+ else
110
+ #re_raises the exception
111
+ raise InvalidToken.new(e ? e.message : "Token has become invalid") #move to localization file
112
+ end
113
+ end
114
+
115
+ # helper method to generate redis keys
116
+ def redis_key(str)
117
+ "#{type}:#{id}:#{str}"
118
+ end
119
+
120
+ def redis_instance
121
+ $redis #need to change out for an configurable var
122
+ end
123
+
124
+ def authenticated?
125
+ return true if method == 'Authenticated'
126
+ false
127
+ end
128
+
129
+ def connected?
130
+ return true if method == 'Connected'
131
+ false
132
+ end
133
+
134
+ private
135
+
136
+ def validate_methods
137
+ errors.add(:method, 'not an accepted option') unless ACCEPTED_METHODS.include?(method)
138
+ end
139
+
140
+ def append_to_associated_services
141
+ self.class.delay.append_to_associated_services(self.id)
142
+ end
143
+
144
+ end
145
+
146
+ #exceptions
147
+ class InvalidToken < StandardError ; end
148
+ class BadRequest < StandardError ; end
149
+ class ServiceDoesNotExist < StandardError ; end
150
+ class Error < StandardError ; end
151
+
152
+ end
@@ -0,0 +1,67 @@
1
+ require 'twitter'
2
+
3
+ module SocialAuth
4
+ class TwitterService < Service
5
+
6
+ def name
7
+ "Twitter"
8
+ end
9
+
10
+ def self.init_with(auth_token={})
11
+ request = create_connection(auth_token)
12
+
13
+ return create_with_request(
14
+ request.user.id,
15
+ User.create_with_twitter_request(request.user),
16
+ "Authenticated",
17
+ {access_token: request.access_token, access_token_secret: request.access_token_secret}
18
+ )
19
+
20
+ rescue Twitter::Error::Unauthorized => e
21
+ raise InvalidToken.new(e.message)
22
+ end
23
+
24
+ def self.connect_with(user, auth_token={}, method="Connected")
25
+ request = create_connection(auth_token)
26
+
27
+ return create_with_request(
28
+ request.user.id,
29
+ user,
30
+ method,
31
+ {access_token: request.access_token, access_token_secret: request.access_token_secret}
32
+ )
33
+
34
+ rescue Twitter::Error::Unauthorized => e
35
+ raise InvalidToken.new(e.message)
36
+ end
37
+
38
+ def self.create_connection(auth_token={})
39
+ Twitter::REST::Client.new do |config|
40
+ config.consumer_key = SocialAuth.twitter_consumer_key
41
+ config.consumer_secret = SocialAuth.twitter_consumer_secret
42
+ config.access_token = auth_token[:access_token]
43
+ config.access_token_secret = auth_token[:access_token_secret]
44
+ end
45
+ #the reason why we don't catch any exceptions here is because it only initializes the connection no
46
+ #requests are actually made here
47
+ end
48
+
49
+ def friend_ids
50
+ if redis_instance.exists(redis_key(:friends))
51
+ friend_ids = redis_instance.smembers(redis_key(:friends))
52
+ else
53
+ friend_ids = self.class.create_connection(access_token).friend_ids.to_hash[:ids].map(&:to_s)
54
+ unless friend_ids.empty?
55
+ redis_instance.del(redis_key(:friends))
56
+ redis_instance.sadd(redis_key(:friends), friend_ids.to_s)
57
+ redis_instance.expire(redis_key(:friends), REDIS_CACHE)
58
+ end
59
+ end
60
+ friend_ids
61
+ rescue Twitter::Error::Unauthorized => e
62
+ disconnect
63
+ return []
64
+ end
65
+
66
+ end
67
+ end